How to rotate camera fully around player?

(Fairly New Unity User)
I am making a 3rd person game and I found this tutorial that has been extremely helpful in making my game:

While it is nice that when you move the right stick it not only rotates the camera but the player as well, I would like to know if anyone can please help me add a feature where the camera rotates fully around the player when the player is not moving similar to most 3rd person games. This is what I have so far and of course this is using Unity’s new input system. Any help at all is much appreciated, thank you:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class BurnerMoveScript : MonoBehaviour
{
    PlayerInput input;
    Vector2 move;
    Vector2 rotate;
    private CharacterController controller;

    public float speed;
    public float rotationSpeed = 2.0f;

    public Transform head;
    private float currentHeadRotation = 0;
    public float maxHeadRotation = 70.0f;
    public float minHeadRotation = -70.0f;

    private float yVelocity = 0;
    public float jumpSpeed = 15.0f;
    public float gravity = 30.0f;
    private Vector3 moveVelocity = Vector3.zero;

    void Awake()
    {
        controller = GetComponent<CharacterController>();

        input = new PlayerInput();
             
        input.Gameplay.MovePlayer.performed += ctx => move = ctx.ReadValue<Vector2>();
     
        input.Gameplay.MovePlayer.canceled += ctx => move = Vector2.zero;


        input.Gameplay.RotateCamera.performed += ctx => rotate = ctx.ReadValue<Vector2>();

        input.Gameplay.RotateCamera.canceled += ctx => rotate = Vector2.zero;


        input.Gameplay.PlayerJump.performed += ctx => Jump();
        input.Gameplay.PlayerJump.canceled += ctx => Jump();
    }

    // Update is called once per frame
    void Update()
    {
        Vector3 input = new Vector3(move.x, 0, move.y) * speed;
        Vector2 turn = new Vector2(rotate.x, rotate.y);


        yVelocity -= gravity * Time.deltaTime;

        Vector3 velocity = moveVelocity + yVelocity * Vector3.up;

        controller.Move(transform.TransformDirection(input * speed * Time.deltaTime + yVelocity * Vector3.up * Time.deltaTime));  //moves the player without freezing jump movement

        transform.Rotate(Vector3.up, rotate.x * rotationSpeed);

        currentHeadRotation = Mathf.Clamp(currentHeadRotation + rotate.y * rotationSpeed, minHeadRotation, maxHeadRotation);

        head.localRotation = Quaternion.identity;
        head.Rotate(Vector3.left, currentHeadRotation);

        if (controller.isGrounded)
        {
            yVelocity = 0; //This is what stops the player from falling off an edge like a rock
        }
    }

    void Jump()
    {
        if (input.Gameplay.PlayerJump.triggered && controller.isGrounded)
        {
            yVelocity = jumpSpeed;
        }

    }

    void OnEnable()
    {
        input.Gameplay.Enable();
    }

    void OnDisable()
    {
        input.Gameplay.Disable();
    }
}

What is the specific part (of making camera spin or move indepdendently around player) that is giving you trouble? Algorithmically.

So it would be this part which controls both the camera turn and player turn at the same time:

transform.Rotate(Vector3.up, rotate.x * rotationSpeed);

and of course these are the transforms that I use as the thing for the camera to follow as well as general rotation:

currentHeadRotation = Mathf.Clamp(currentHeadRotation + rotate.y * rotationSpeed, minHeadRotation, maxHeadRotation);
head.localRotation = Quaternion.identity;
head.Rotate(Vector3.left, currentHeadRotation);

I really do like these lines but it would be nice to have a function that allows the camera to rotate 360 around the player if they are not moving and also having something to where if the player is say looking with the camera at the opposite direction than the player body is looking, if you move the player then it will move according to whatever direction the camera is looking.

Makes me wonder why do you ahndle camera movement in a component that is not on that camera.

Given your setup it looks like camera is simply parented to the player and its movement is not controlled anywhere. If that’s the scheme used in the tutorial, that’s disappointing.

What you need to do (or could do) is to split camera logic away from player.
Control camera movement in a component attached to camera.
Have camera component store offset relative to player.
Update camera world position in LateUpdate (this is important) using the offset. This way the player will rotate independently from camera.

Basically, write a component that allows you to rotate a camera around a Vector3 position or world position of a referenced transform. Make sure to do that in LateUpdate.

Then update that component to handle finding the player object and spinning around it. That’s one way to do it pretty much.

1 Like

You are very right my friend. I honestly thought it was weird too when they were bundled but I thought why not since it’s less scripts I have piled on top of each other. I went ahead and added a new component to the camera and I reworked a lot of what I had written into a more manageable version of what i had before. Here is the camera script:

PlayerInput input;
    Vector2 rotate;

    public Transform head;
    private float currentHeadRotation = 0;
    public float rotationSpeed = 2.0f;
    public Vector2 pitchMinMax = new Vector2(-40, 85);
    public float offset;

    float rotateX;
    float rotateY;
   

    void Awake()
    {
        input = new PlayerInput();

        input.Gameplay.RotateCamera.performed += ctx => rotate = ctx.ReadValue<Vector2>();

        input.Gameplay.RotateCamera.canceled += ctx => rotate = Vector2.zero;
    }


    void LateUpdate()
    {
        rotateX += rotate.x * rotationSpeed;
        rotateY -= rotate.y * rotationSpeed;
        rotateY = Mathf.Clamp(rotateY, pitchMinMax.x, pitchMinMax.y);

        Vector3 targetRotation = new Vector3(rotateY, rotateX);
        transform.eulerAngles = targetRotation;

        transform.position = head.position - transform.forward * offset;
    }

That being said, my character does move around the scene nicely but, even though this is better, it does not move according to the direction of the camera when I move so I guess that’s where I am at right now.

1 Like

And for reference this is what my move script currently looks like:

PlayerInput input;
    Vector2 move;
    private CharacterController controller;

    public float speed;

    private float yVelocity = 0;
    public float jumpSpeed = 15.0f;
    public float gravity = 30.0f;
    private Vector3 moveVelocity = Vector3.zero;


    void Awake()
    {
        controller = GetComponent<CharacterController>();

        input = new PlayerInput();
              
        input.Gameplay.MovePlayer.performed += ctx => move = ctx.ReadValue<Vector2>();
      
        input.Gameplay.MovePlayer.canceled += ctx => move = Vector2.zero;
    }

    // Update is called once per frame
    void Update()
    {
        Vector3 input = new Vector3(move.x, 0, move.y) * speed;
        Vector2 turn = new Vector2(rotate.x, rotate.y);


        yVelocity -= gravity * Time.deltaTime;

        Vector3 velocity = moveVelocity + yVelocity * Vector3.up;

        controller.Move(transform.TransformDirection(input * speed * Time.deltaTime + yVelocity * Vector3.up * Time.deltaTime));  //moves the player without freezing jump movement
      

        if (controller.isGrounded)
        {
            yVelocity = 0; //This is what stops the player from falling off an edge like a rock
        }
    }


You need to grab camera’s forward vector, project it onto ground, normalize, and then use dot products with transform.right/forward of player object, to determine how far in character’s space the character wants to go. Same deal with the rest of movement vectors. Or just inverse transform the vectors into player object space. Does thus make sense to you?

If I’ll be honest not really. I’d imagine getting the camera’s forward vector would mean you’d have to start by calling the camera’s transform with something like Camera. Main. Transform but I am not sure how I’d go from there especially when involving the players movement

The easiest way to do this is to parent the camera beneath an empty gameobject, which has its position set to the player position within LateUpdate(). Aim the camera toward the empty gameobject (preferably back along the empty’s Z axis). Simply rotate the empty gameobject and adjust your pan accordingly using transform.localPosition.

Its really simple this way! No need for advanced math. Leverage the engine and its gameobjects!

2 Likes

A camera revolves around a point.
Position of the camera - relative to the point - is represented as an offset vector. Which is a difference (subtraction, vector) of camera position (world) and point position. So. CameraPos = RotationCenter + OffsetVector.

When you rotate camera, you rotate that offset.

IN late update. You grab that point around which your camera revolve. Add “offset vector” to it, and set result to camera position. There’s a matter of rotation camera, but you can calculate that using lookat commands and so.(https://docs.unity3d.com/ScriptReference/Transform.LookAt.html)

The difference between rotating around specific point and around player is that instead of using the point, you grab world position of player, or player’s head, or some sort of “anchor transform”, and rotate aroudn THAT. in LateUpdate.

And yeah, you can do that with less math (like TenKH described). In this case, you make an object that rotates using game controls control, and parent camera to it. Like you already did with your tutorial. Let’s name it “camera root”

Except, rather than parenting “camera root” to the player, you add LateUpdate to camera root, and in that function you do “transform.position = player.transform.position”. And that’s all. It will track player and will rotate independently around it.

I prefer mathematical approach because I am more used to it.

Regarding movement.

When player issues commands in third person view, the desired movement vector is specified relative to camera. So, when player presses “right”, he specifies that he wants character to go towards camera.right, and when presses “forward”, things get more complicated. In this case the player wants to go in direction camera faces, BUT, the vector should be parallel to the ground, and camera faces down. So you project either forward or up vector (because camera might be facing down) on to ground plane using Vector3.ProjectOnPlane, normalize it, and use THAT for desired movement.

Once that is done, you scale camera.right and “projected ground forward” vecotrs by strength of axes, and use that to specify to the player avatar where you want it to go.

Past that point, it is the job of player avatar to move and turn in correct direction. Basically, you’ll need to modify controller from “move left/forward” to “move towards provided direction” given a 3d vector of movement. This isn’t very difficult.

Does this make sense now?

1 Like

Yes it makes a lot of sense, thank you for breaking it down. I just got up, I am going to try and see what results I get and will update soon.

Alright I did it. Finally things are working as they should. Thank you so much for your help on this. The only thing left now is that although the player moves as they should according to the camera, the player body does not turn at all. I have tried doing this:

transform.Rotate(Vector3.up, move.x * rotationSpeed);

but all it does is basically forever spin the player for as long as you hold left or right whilst also still moving the player left or right. I’m thinking maybe I should end it here and start a new post. In case of anything, thank you neginfinity for all your help.

You can calculate angle between two vectors by using Mathf.Acos or Mathf.Atan2. So. If you take movement direction vector, remove its vertical component (Vector3.normalize(Vector3.ProjectOnPlane(player.trasnform.up, moveDir))), then you can, calculate angle between player.transform.forward and movement direction, using Mathf.Atan2, by using Vector3.Dot(player.transform.forward, projectedDir) for X, and Vector3.Dotr(player.transform.left, projectDir) for Y.

Knowing the angle and maximum rotational speed, you can calculate how much the player avatar can turn in this frame.
Maximum allowed turn would be Time.deltaTime*maxRotationalSpeed. So. You calculated how much you need to turn, then you clamp that value by maximum allowed turn in current frame and apply result to frames.

Does that make sense for you?

Be aware for +/- signs and radian/degree conversions. Aside from that, this is mostly trigonometry. Probably school program level too.

Sort of. I have seen this before, more specifically in this video made by Sebastian League, skip to 8:32:
https://www.youtube.com/watch?v=ZwD1UHNCzOc

Here he later lays out movement code using Atan2 which is as follows

float targetRotation = Mathf.Atan2(inputDir.x, inputDir.y) * Mathf.Rad2Deg + cameraT.eulerAngles.y;
transform.eulerAngles = Vector3.up * Mathf.SmoothDampAngle(transform.eulerAngles.y, targetRotation, ref turnSmoothVelocity, turnSmoothTime);

I have tried using this but what ends up happening is that if say the player were to keep moving up and down really fast, the player slides left/right and the same if they mash left and right then they slide upwards/downwards. I wasn’t a fan of that so I never really went with it. Sorry, trig was never my strong suit my need to explain this one to me too =/

UPDATE

I notice when I only use
transform.eulerAngles = Vector3.up * Mathf.Atan2(move.x, move.y) * Mathf.Rad2Deg;
The player turns in the input direction but then the player moves they go in weird directions such as up or down moving the player only up and the left or right moving the player down. Much closer I guess

Euler angles are subject to gimbal lock, and I’d recommend to try avoid using them, although if you aren’t controlling planes in the game you might get away with using them.

Additionally, you can’t just assign yaw angle and call it done. Atan2 returns values in range of -pi…+pi (± 180 degrees), you’ll need to handle situation when angles wrap around.

Either ditch storing angle explicitly (you can just rotate player around transform.up, and never store rotation angle), or handle situation when rotational angles are actually close despite being numerically far.

For example, when current direction is 179 degrees, and atan2 says that desired angle is -179 degrees, then you need to turn by 2 degrees and not by -358. You can try drawing a angle circle on coordinate plane and see i this gives insights to you,.

SOLVED
ROTATE PLAYER ACORDING TO CAMERA ROTATION (using cinemacine and custom cameras)
NOTE if you cant get the camera to rotate for classic full rotation with cinemachine, make sure you have your speed up, i made that mistake and couldnt figure it out for days

for anyone looking here in the future, i had the opposite problem
even though my rotation axis were locked outside y my camera would violently shake, after too long i got this:

float X = Input.GetAxis("Mouse X");

r.transform.Rotate(0, X, 0);

r.constraints = rigidbodyConstraints.FreezeRotationZ|RigidbodyConstraints.FreezeRotationX;

add this constraint for good measure

and be 100% SURE you change the default Y AXIS speed from 3 and X AXIS speed=300 to Y AXIS1 and X AXIS 1 with 0.5 for sensitivity on your “mouse X” and/or “mouse Y” axis (that is under the edit > project settings tab, then find input manager in project settings and open the axes arrow

for anyone that stumbles across this looking for a way to rotate a player with your cinemachine camera, here you go, use under for a custom camera

 mouseX += Input.GetAxis("Mouse X") * RotationSpeed;
   mouseY -= Input.GetAxis("Mouse Y") * RotationSpeed;
   mouseY = Mathf.Clamp(mouseY, -35, 60);
   transform.LookAt(Target);

   if (Input.GetKey(KeyCode.LeftShift))
   {
       Target.rotation = Quaternion.Euler(mouseY, mouseX, 0);
   }
   else
   {
       Target.rotation = Quaternion.Euler(mouseY, mouseX, 0);
       Player.rotation = Target.transform.rotation;
   }

alternative

public float RotationSpeed =  1;
public Transform Target, Player;
float mouseX, mouseY;

void Start()
{
   Cursor.visible = false;
   Cursor.lockState = CursorLockMode.Locked;
}

void LateUpdate()
{
   CamControl();

}
void CamControl()
{
   mouseX += Input.GetAxis("Mouse X") * RotationSpeed;
   mouseY -= Input.GetAxis("Mouse Y") * RotationSpeed;
   mouseY = Mathf.Clamp(mouseY, -35, 60);
 
   transform.LookAt(Target);

   if (Input.GetKey(KeyCode.LeftShift))
   {
       Target.rotation = Quaternion.Euler(mouseY, mouseX, 0);
   }
   else
   {
       Target.rotation = Quaternion.Euler(mouseY, mouseX, 0);
       Player.rotation = Target.transform.rotation;
   }

 
}

NOTE
if you cant get the camera to rotate for classic full rotation with cinemachine, make sure you have your speed up, i made that mistake and couldnt figure it out for days