Camera Rotation changes position of player

Hey there,

I have been working on a game using a first person controller. After using charactercontroller for a while, I switched to a Rigidbody character controller for in-air movement functions that were easier with rigidbodies. However, this created a new issue.

I have a few teleporters, essentially just adding a given value to the players position. This makes the player teleport to different places. However, an issue came up where the player was teleported under the map. After some debugging I found out this was due to the player coordinates, mainly the y-value, changing when the camera was rotated using the mouse. I have no clue why or how this happens, but freezing the y movement in the rigidbody component helped. A different thread recommended fixing the center of mass, which kind of helped after placing the center of mass to (0, 10, 0), because the y position would only switch between 0, -9.1… and +9.1… The location values change, without the player position changing visibly. This makes the player teleport to -9.1 under the map when going through a teleporter.

Added some screenshots here:


As you can see, here the y value of the player position is 0.

And after just rotating the camera a bit, suddenly the y value jumped to -9.5… without the player visibly moving.

Relevant code:

[Header("Script References")]
    [SerializeField] private gc gcscript; // groundcheck script
    [Space]
    [Header("Object/Component References")]
    [SerializeField] private Transform pb; // player capsule body
    [SerializeField] private Transform ch; // Player Camera Holder Transform
    [SerializeField] private Transform c; // Player Camera Transform
    [SerializeField] private Rigidbody rb; // Player Rigidbody
    [Space]
    [Header("Character Movement Properties")]
    [SerializeField] private float nSpeed; // Normal speed
    [SerializeField] private float cSpeed; // crouch speed
    [SerializeField] private float crSpeed; // crawl speed
    [SerializeField] private float sSpeed; // sprint speed
    [SerializeField] private float aSpeed; // in-air speed
    [SerializeField] private float jForce; // jump force
    [Space]
    [Header("Camera Properties")]
    [SerializeField] private float sens; // Sensitivity
    [SerializeField] private float clamp = 85f; // maximum/minimum rotation of camera
    [SerializeField] private float scms; // Smooth Camera Movement Speed

void Start()
{
    calcSpeed = nSpeed; // Set calculated speed to normal speed
    Cursor.lockState = CursorLockMode.Locked; //lock cursor
    rb.centerOfMass = new Vector3(0, 10, 0);
} // call on script start, set standard values for vars, lock cursor

private void EarlyUpdate() // first call, get non-movement inputs and calculations
{
    g = gcscript.g; // set ground bool equal to groundcheck ground bool variable
}

void Update() // second call, calculate and execute movement and camera input, player physics
{
    EarlyUpdate(); // Call EarlyUpdate function

    pmInput = new Vector3(Input.GetAxis("Horizontal"), 0f, Input.GetAxis("Vertical")); // Get player input (WASD keys)
    cInput = new Vector2(Input.GetAxis("Mouse X") * sens * Time.deltaTime, Input.GetAxis("Mouse Y") * sens * Time.deltaTime); // Get Mouse Input (Mouse movement)

    Movement(); // call function for player movement
    Cam(); // call function for camera movement
}

private void Movement() // move player and jump
    {
        Vector3 MoveVector = transform.TransformDirection(pmInput) * calcSpeed; // calculate movement
        rb.velocity = new Vector3(MoveVector.x, rb.velocity.y, MoveVector.z); // move player


       
        if(Input.GetButtonDown("Jump") && g && (stat == 0 || stat == 3)) // if jump key defined in input manager is pressed AND player is grounded AND playerstatus is either walking or sprinting
        {
            rb.AddForce(Vector3.up * jForce, ForceMode.Impulse); // Add instantaneous force up
        }
    }

    private void Cam() // calculate camera and player rotation
    {
        xRot -= cInput.y; // Calculate vertical input
        xRot = Mathf.Clamp(xRot, -clamp, clamp); //clamp camera rotation
        ch.transform.localRotation = Quaternion.Euler(xRot, 0f, 0f); //rotate camera based on mouse
        transform.Rotate(new Vector3(0, cInput.x, 0)); //rotate player body based on mouse
    }

Notes: This is not the full code. The code also has functions for view bobbing, crouching/crawling, calculating the speed of the character based on the playerstatus sprinting, walking, crouching or crawling, smooth camera movement and player scale modding for crouching and crawling, and other stuff. These have no influence or relevance to the problem and this code, except for a few references in if statements. I can provide the full code if needed, though it won’t make much difference.

Any help will be much appreciated :slight_smile:
Cheers, Euranium

I see like 64 you are driving the transform directly. That’s a no-no in Physics.

Make sure none of your other use cases or code are driving the position.

With Physics (or Physics2D), never manipulate the Transform directly. If you manipulate the Transform directly, you are bypassing the physics system and you can reasonably expect glitching and missed collisions and other physics mayhem.

Always use the .MovePosition() and .MoveRotation() methods on the Rigidbody (or Rigidbody2D) instance in order to move or rotate things. Doing this keeps the physics system informed about what is going on.

Ah, I see, that makes sense. Thanks for the help!
As for my teleporters, can I still have them use transform.position - new vector3…, or should I also use .MovePosition?
]

Since it’s a teleport, you probably don’t want the player to collide with any triggers etc on the way, so you could set the RigidBody to isKinematic, do the teleport, wait a frame, then set back to non-kinematic.

No, definitely transform.position is correct in the case of a teleporter. I don’t think you should need anything else.

The way I heard it put is that all movement done with the transform actually IS a teleport as far as the physics system is concerned. The physics system basically goes “WHOA! That’s different!” and reconstructs its reality with the new location.

BUT… if your player rematerializes with his nose partially in a wall or something, he’s gonna jump or jerk or go flying, so be sure your teleport destinations are free of obstructions, just like Star Trek teleporters.

Depending on how you do it, you may wish to set the .velocity and .angularVelocity of the Rigidbody to zero after the teleporter, otherwise the player would “carry” all his speed and rotational speed through the teleporter, which might be what you want, might not.

Again thanks for the help!
Actually, yes, i want the player to keep the velocity and everything. I’m making a backrooms-inspired game for a school project, and one of the hardest parts is getting a realistic illusion of non-Euclidian geometry. The way we do it now, is by having a part of a map copied identically to another position, with a teleporter that teleports the player to the same location in the copy of that hallway. This allows us to have a player take a left 5 times, but still end up in a different place.

Which worked fine, until i realized i needed some rigidbody stuff for the players in-air movement. So, I rewrote the entire character controller script using rb instead of the character controller thing. and then it started doing this… Guess I followed the wrong youtube tutorial, haha.