Search Unity

  1. Unity 2020.1 has been released.
    Dismiss Notice
  2. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

How to use Cinemachine FreeLook for a Third Person Shooter Game?

Discussion in 'Cinemachine' started by Hannibal_Leo, Sep 4, 2019.

  1. Hannibal_Leo


    Nov 5, 2012
    Well this should actually be pretty easy, I thought, a few days ago.

    Setting up Cinemachine Free Look to have a cool Third Person Shooter Camera .. well

    Pretty much standard Cinemachine Free Look
    with an Cinemachine Camera Offset (to avoid aiming at the players head but over his shoulder),
    Orbit-Binding-Mode set to "Lock To Target On Assign",
    Tracked Object Offset set to (0 , 1.4 , 0) on all three rigs as my character-origin is at the feet.

    What are the problems:
    1) Move on Mouse Input
    Well, it's a shooter, so I need pretty direct control over the camera rotation based on the player input - I tried everything on the Axis Control
    - setting the accel and decel Time to 0 = jerky movement
    - setting them to small numbers = jerky movement
    - setting them to higher valus = indirect control + deceleration movement after the physical mouse movement has stopped
    - setting only the decel Time to 0 = jerky movement
    - setting the mouse Input with the old InputSystem or setting it with the new InputSystem results in a somewhat direct control = jerky movement, it stops, then acclerates, stops, acclerates again.

    Code (CSharp):
    1. private void Awake()
    2.        {
    3.             CinemachineCore.GetInputAxis = GetAxisCustom2;
    4.        }
    5.        public float GetAxisCustom2(string axisName)
    6.        {
    7.            input_view = * sensitivity;
    8.            if (axisName == "Mouse X")
    9.            {
    10.                //return Input.GetAxis("Mouse X");
    11.                return input_view.x;
    12.            }
    13.            else if (axisName == "Mouse Y")
    14.            {
    15.                //return Input.GetAxis("Mouse Y");
    16.                return input_view.y;
    17.            }
    19.            return 0;
    20.        }
    jerky movement == from complete jumps (as if the camera teleports in position or rotation) to unconsistent movement/rotation behavior - as if someone is driving the camera, like he's driving a car for the first time, hard breaks, putting in the reverse gear while moving forward, starting slow and then suddenly switching to full throttle and so on.

    2) Rotation of the Camera (without any Mouse-Input)
    No matter what settings I change, the camera always tries to look at the player-movement direction.
    Lookahead time is set to zero, Orbit-Binding-Mode set to "Lock To Target On Assign" - but when the player moves sidewards, the camera stays in place for a while, tries to look at the player, which is restriced by the Orbit-Binding-Mode, so it starts to rotate torwards the player and then look forward again in something like a sine-curve with high frequence. Every 0.3f seconds or so, it changes it's mind and looks either forward as it should or to the player.
    When I don't move the Mouse, the camera should not rotate, just move with the player.

    This makes aiming impossible.
    Now I don't want to give up on Cinemachine as it comes with some handy features and my own Camera-Monobehaviour jitters slightly and feels like the mouse-position is restrained to a grid (which probably happens because of setting the localEulerAngles or even the input itself).

    At this point you might guess my mouse is broken, but it works in every Ego-Shooter / ThirdPersonShooter I bought on Steam - just not in my own games.

    Is there a way to fix these problems? Am I missing something? I've read throught articles, forum posts, Unity Answers and watched almost every Youtube Tutorial on that topic.

    Maybe I should mention that I am not a beginner, I work with Unity since more than 6 years (mostly on FPS-Games).

    Example Video:

    Attached Files:

    Last edited: Sep 4, 2019
  2. Gregoryl


    Unity Technologies

    Dec 22, 2016
    Hey, sorry for all the trouble you're having - you're off to a rocky start with CM, but it will get better. Let's look at your issues one by one.

    Input. By default, the way CM drives the FreeLook axes is designed for joystick input, and is acceleration-based. Often, this is not optimal for mouse input, and you are tripping over that. Fortunately, it's possible to change this behaviour quite easily. This post will show you how to do that:

    Binding Mode. Why are you using LockToTargetOnAssign? That won't guarantee a specific spatial relationship to the target. Normally, it's better to use LockToTargetWithWorldUp. That will put the camera behind the player by default.

    Heading. Use TargetForward, not WorldSpace. That will make X axis.Value=0 match character forward, so recentering will bring the camera properly behind the player.

    Finally, camera movement is jittery in your video when the player moves and you are not moving the mouse. There can be several causes for this, but normally it has to do with uneven animation or inconsistent handling of update and fixed update. How is your player movement implemented? In FixedUpdate or Update? Are you using RigidBodies in the player? It's important that the vcam updates on the same clock as the target's transform. When the game is playing, the vcam's inspector will show you on what clock the vcam is updating. Look at it when the player is moving and when the player is still. What does it say?

    andreiagmu likes this.
  3. Hannibal_Leo


    Nov 5, 2012
    I'll have a look at the thread about the mouse input, thanks for the link and the quick reply.

    I use LockToTargetOnAssign to prevent the camera from rotating torwards the player direction. My player rotates, but I want to keep the camera still. It should not follow the player orientation, how the player moves should not affect the camera rotation at all, it should just move with the player.
    To explain the behaviour in code it might be:

    Code (CSharp):
    1. camera.position = playerTransform.position + cameraOffset;
    For some reason the camera rotates when the player moves - the jittery movement is caused because the camera rotates when it should only move with the player.
    You can see how the camera rotates torwards the player-forward and then switches back to its previous rotation.
    All jitter-problems are caused because the camera rotates when the player moves.

    I only want the cam to rotate when the player moves the mouse.
    In code it might be: (camera parented to an _origin that copies the player-character position but ignores player-character rotation)

    Code (CSharp):
    2.             float hAxis = Input.GetAxis("Mouse X");
    3.             float vAxis = -Input.GetAxis("Mouse Y");
    5.             _origin.Rotate(Vector3.up, hAxis * _rotationSpeed.x, Space.World);
    7.             rotationVertical += vAxis * _rotationSpeed.y;
    8.             rotationVertical = ClampAngle(rotationVertical, _rotationLimitVertical.x, _rotationLimitVertical.y);
    10.             _origin.localEulerAngles = new Vector3(rotationVertical, _origin.localEulerAngles.y,  _origin.localEulerAngles.z);
    Of course those two code snippets won't work together, I'd have to set the _originPosition in the first one, but I think code might be the best way to explain the behaviour I want to have.

    I could also make a video of the camera behaviour I want, as these code snippets are from my self-written ThirdPersonCamera (Script attached) - it just has no smoothing, no ImpulseListener, no blending between cameras and thats why I want to use Cinemachine now.

    Attached Files:

  4. Gregoryl


    Unity Technologies

    Dec 22, 2016
    I'm pretty sure that the script included there will give you the mouse behaviour you're looking for.

    You are using the wrong binding mode. To get what you want you should be using WorldSpace binding mode. That is completely independent of target rotation. All the LockToTarget family of binding modes apply the offset in TargetLocal space, which will rotate with the target. WorldSpace binding applies the offset in world space.
  5. Hannibal_Leo


    Nov 5, 2012
    Well it does not change the camera behaviour at all, when I move my mouse, without any movement on the player, the camera accelerates and decellerates all the time while I move my mouse smoothly.

    Seems like my problem is in the Aiming of the camera.
    Without any aim, the camera looks straight forward and does not rotate to the player, so when I move my mouse, the cam goes around the player along the ring of the current rig - the player disaperars from the screen when the camera is in front of the player.
    The aiming composer causes the cam to look at the player and because of the soft zone, it tries to look at him smoothly and thats what causes my problems:
    The camera should not look at the real player position, because the camera movement is smoothed.
    So when the player moves, the camera does not move immediately - it follows the player after some time, which is OK, but this moves the aim position and causes the camera to rotate - because the vector from camera to player has changed.
    In my case I want to look at a virtual point, which is not the player, but the origin of the camera (origin of the rig-rings) at all times.

    I tried to archive that behaviour by setting the dead-zone of all aiming composers to 1, but now the rotation of the camera to the player changes during play - when the player is on the left side of the screen at the beginning, it somehow changes during play, the camera rotation is not fixed anymore, so the player could then be on the very far left or the very far right of the screen as in the dead zone, there are no corrections happening.

    What is the correct setup of the aiming composer to always look at the virtual position of the camera-origin? (like if the cam was parented to the origin and only the origin moves with the player and rotates according to MouseInput)

    Or is this not possible in cinemachine yet? (I tried all Aiming-Options, HardLookAt, SameAsFollowTarget, POV, Composer, none of them seems suitable for Third Person Shooters)
  6. Gregoryl


    Unity Technologies

    Dec 22, 2016
    Maybe we should back up a little, I think I haven't been properly understanding what you want. Let me describe back to you the camera behaviour I now think you want, and you tell me if it's correct, ok?

    You want the camera to always look at Point X (described below), with the mouse movement orbiting the camera around Point X in both horizontal and vertical directions. The camera rotation will never change unless the user moves the mouse. You want 1:1 tracking of the mouse, no damping or accel/decel of the camera when the mouse is moved.

    Point X is calculated dynamically in relation to the player character's position: it is always a little bit to the right of the character, in screen space. When the character moves, Point X moves with it and the camera follows in a fixed relation to it, without re-orienting unless the mouse is moved. Character rotation is irrelevant.

    You may optionally want some small amount of damping when Point X tracks the character's position.

    Is that right?
    Last edited: Sep 5, 2019
  7. Hannibal_Leo


    Nov 5, 2012
    That's 100% right, yes. Exactly what I want.
    Gregoryl likes this.
  8. Gregoryl


    Unity Technologies

    Dec 22, 2016
    So, the FreeLook is designed by default for looking at and following a target. In your case, the target is Point X, not the Player. We need to build point X to use as a target for the FreeLook.

    Make an empty GameObject, and add to it this custom script (or something like it):
    Code (CSharp):
    1. using UnityEngine;
    3. public class PlayerFollowTarget : MonoBehaviour
    4. {
    5.     public Camera Camera;
    6.     public Transform Target;
    7.     public float Damping;
    8.     public Vector3 ScreenSpaceOffset;
    10.     Vector3 m_CurrentVelocity;
    11.     Vector3 m_DampedPos;
    13.     void OnEnable()
    14.     {
    15.         if (Camera == null)
    16.             Camera = Camera.main;
    17.         if (Target != null)
    18.             m_DampedPos = Target.position;
    19.     }
    21.     void LateUpdate()
    22.     {
    23.         if (Target != null)
    24.         {
    25.             var pos = Target.position;
    26.             m_DampedPos = Damping < 0.01f
    27.                 ? pos : Vector3.SmoothDamp(m_DampedPos, pos, ref m_CurrentVelocity, Damping);
    28.             pos = m_DampedPos;
    29.             if (Camera != null)
    30.             {
    31.                 pos = Camera.transform.worldToLocalMatrix * pos;
    32.                 pos += ScreenSpaceOffset;
    33.                 pos = Camera.transform.localToWorldMatrix * pos;
    34.             }
    35.             transform.position = pos;
    36.         }
    37.     }
    38. }
    Set its Target field to be your player, and set the offset and the damping to be what you want. In my test, I set it up like this:

    Next set that new GameObject to be the LookAt and Follow targets for your FreeLook.
    Set up the FreeLook like this, with all 3 rigs identical:


    Note that the heading and binding mode are both World Space. Set the orbits how you like them.

    Finally, for smoother mouse input, add this script to the FreeLook to override the default input algorithm. Adjust the settings to tune the sensitivity the way you like it (but try the default settings first).
    Code (CSharp):
    1. using UnityEngine;
    2. using Cinemachine;
    4. [RequireComponent(typeof(CinemachineFreeLook)), DisallowMultipleComponent]
    5. public class FreeLookAxisDriver : MonoBehaviour
    6. {
    7.     public CinemachineInputAxisDriver xAxis;
    8.     public CinemachineInputAxisDriver yAxis;
    10.     private CinemachineFreeLook freeLook;
    12.     private void Awake()
    13.     {
    14.         freeLook = GetComponent<CinemachineFreeLook>();
    15.         freeLook.m_XAxis.m_MaxSpeed = freeLook.m_XAxis.m_AccelTime = freeLook.m_XAxis.m_DecelTime = 0;
    16.         freeLook.m_XAxis.m_InputAxisName = string.Empty;
    17.         freeLook.m_YAxis.m_MaxSpeed = freeLook.m_YAxis.m_AccelTime = freeLook.m_YAxis.m_DecelTime = 0;
    18.         freeLook.m_YAxis.m_InputAxisName = string.Empty;
    19.     }
    21.     private void OnValidate()
    22.     {
    23.         xAxis.Validate();
    24.         yAxis.Validate();
    25.     }
    27.     private void Reset()
    28.     {
    29.         xAxis = new CinemachineInputAxisDriver
    30.         {
    31.             multiplier = -10f,
    32.             accelTime = 0.1f,
    33.             decelTime = 0.1f,
    34.             name = "Mouse X",
    35.         };
    36.         yAxis = new CinemachineInputAxisDriver
    37.         {
    38.             multiplier = 0.1f,
    39.             accelTime = 0.1f,
    40.             decelTime = 0.1f,
    41.             name = "Mouse Y",
    42.         };
    43.     }
    45.     private void Update()
    46.     {
    47.         bool changed = yAxis.Update(Time.deltaTime, ref freeLook.m_YAxis);
    48.         changed |= xAxis.Update(Time.deltaTime, ref freeLook.m_XAxis);
    49.         if (changed)
    50.         {
    51.             freeLook.m_RecenterToTargetHeading.CancelRecentering();
    52.             freeLook.m_YAxisRecentering.CancelRecentering();
    53.         }
    54.     }
    55. }
    Let me know if we're on the right track.
    Apsuity and andreiagmu like this.