Custom Character Free Look Movement

Hi everyone,

first of all I want to say thank you for the awesome 3rd Person Combat & Traversal course! I really enjoyed it so far. <3

My problem is, that I really don’t like the the behaviour of cinemachines free look camera. Everything feels so fast and weird. So I checked how the 3rd person starter assets work and I found that they use a normal virtual camera and set the Body to 3rd Person Follow and the Aim to Do nothing. I did some research and I found this official Unity video over on Youtube:

(79) Creating a Third Person Camera using Cinemachine in Unity! (Tutorial) - YouTube

I used there code to rotate the camera and ended up with this:

using UnityEngine;

public class PlayerFreeLookState : PlayerBaseState
{
    public PlayerFreeLookState(PlayerStateMachine stateMachine) : base(stateMachine)
    {
    }

    #region State Machine

    public override void Enter()
    {
        stateMachine.InputReader.TargetEvent += OnTarget;

        stateMachine.Animator.CrossFadeInFixedTime(
            Constants.FreeLookBlendTreeHash,
            Constants.CrossFadeDuration
        );
    }

    public override void Tick(float deltaTime)
    {
        HandleStateChanges();

        Vector3 movement = CalculateMovement();
        Vector3 angles = HandleCameraRotation();

        HandlePlayerMovement(movement, deltaTime);
        HandlePlayerRotation(angles, deltaTime);

        UpdateAnimator(deltaTime);
    }

    public override void Exit()
    {
        stateMachine.InputReader.TargetEvent -= OnTarget;
    }

    #endregion

    #region Event Handlers

    private void OnTarget()
    {
        if (!stateMachine.Targeter.SelectTarget()) return;

        stateMachine.SwitchState(new PlayerTargetingState(stateMachine));
    }

    #endregion

    private void HandleStateChanges()
    {
        if (stateMachine.InputReader.IsAttackingPrimary)
        {
            stateMachine.SwitchState(new PlayerAttackingState(stateMachine, 0));
        }
    }

    private Vector3 CalculateMovement()
    {
        Vector3 forward = stateMachine.MainCameraTransform.forward;
        forward.y = 0f;
        forward.Normalize();

        Vector3 right = stateMachine.MainCameraTransform.right;
        right.y = 0f;
        right.Normalize();

        return forward * stateMachine.InputReader.MoveInput.y + right * stateMachine.InputReader.MoveInput.x;
    }

    private Vector3 HandleCameraRotation()
    {
        // Horizontal Rotation
        stateMachine.CinemachineCameraTarget.transform.rotation *= Quaternion.AngleAxis(
            stateMachine.InputReader.LookInput.x * stateMachine.RotationSpeed,
            Vector3.up
        );

        // Vertical Rotation
        stateMachine.CinemachineCameraTarget.transform.rotation *= Quaternion.AngleAxis(
            stateMachine.InputReader.LookInput.y * stateMachine.RotationSpeed,
            Vector3.right
        );

        Vector3 angles = stateMachine.CinemachineCameraTarget.transform.localEulerAngles;
        angles.z = 0f;

        float angle = angles.x;

        // TODO: simplify this angle clamping
        if (angle > 180f && angle < 340f)
        {
            angles.x = 340f;
        }
        else if (angle < 180f && angle > 40f)
        {
            angles.x = 40f;
        }

        stateMachine.CinemachineCameraTarget.transform.localEulerAngles = angles;

        return angles;
    }

    private void HandlePlayerMovement(Vector3 movement, float deltaTime)
    {
        Move(movement * stateMachine.FreeLookSpeed, deltaTime);
    }

    private void HandlePlayerRotation(Vector3 angles, float deltaTime)
    {
        if (!stateMachine.InputReader.IsMoving) return;

        stateMachine.transform.rotation = Quaternion.Euler(
            0f,
            stateMachine.CinemachineCameraTarget.transform.rotation.eulerAngles.y,
            0f
        );

        stateMachine.CinemachineCameraTarget.transform.localEulerAngles = new Vector3(
            angles.x,
            0f,
            0f
        );
    }

    private void UpdateAnimator(float deltaTime)
    {
        stateMachine.Animator.SetFloat(
            Constants.FreeLookSpeedHash,
            stateMachine.InputReader.IsMoving
                ? stateMachine.InputReader.IsWalking
                    ? Constants.WalkSpeed
                    : Constants.RunSpeed
                : 0f,
            Constants.AnimationDampTime,
            deltaTime
        );
    }

    // private void RotatePlayerToMovementDirection(Vector3 movement, float deltaTime)
    // {
    //     if (!stateMachine.InputReader.IsMoving) return;

    //     stateMachine.transform.rotation = Quaternion.Lerp(
    //         stateMachine.transform.rotation,
    //         Quaternion.LookRotation(movement),
    //         deltaTime * Constants.RotationDamping
    //     );
    // }
}

I think this feels and works great. Now I just have two problems.

The first problem is, that my character doesn’t turn into it’s move direction and I don’t understand why. Here is a short video:

Character Doesn’t Turn In Free Look State

Anyone has some advices to let my character turn into the movement direction based on the code above?

The 2nd problem I have is, that I would like to simplify the angle clamping. Something like Mathf.Clamp(). But I don’t really get the bottom and top values out of this if construction. Maybe you got some hints how to simplify that?

I really appreciate any tips and advices and I really thank you in advance for your help.

Perhaps just use the code Nathan wrote in the course. The camera calculation has already been done so there’s no reason to have to change it

private void FaceMovementDirection(Vector3 movement, float deltaTime)
{
    stateMachine.transform.rotation = Quaternion.Lerp(
        stateMachine.transform.rotation,
        Quaternion.LookRotation(movement),
        deltaTime * stateMachine.RotationDamping);
}

The angle here is actually being clamped between 340 and 400. It allows angles from 340 to 360, which is also 0, and then up to 40. Or, 360 + 40 = 400 (btw angles of 180 will remain 180 which I believe is wrong). 0 to 40 is actually meant to be larger than 340 to 360 but Mathf.Clamp(angle, 340, 400) won’t work because it will set values between 40 and 180 to 340 instead of 400. So, the problem statement is that values between 40 and 180 should become 40 (perhaps 400) and values between 180 and 340 should become 340. The best I can do at this time is this

angles.x = Mathf.Clamp(angle < 180 ? angle + 360 : angle, 340f, 400f);

The quaternion will accept 400, but it may mess with the camera a bit because it represents 1-and-a-bit rotations. You could do

var clamped = Mathf.Clamp(angle < 180 ? angle + 360 : angle, 340f, 400f);
angles.x = clamped >= 360 ? clamped - 360 : clamped;

It’s not great. May as well stick to what you have.

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms