Skip to main content
1 of 4
Bunabyte
  • 331
  • 2
  • 7

Why does this code behave strangely with different camera angles?

Relevant to this issue are two MonoBehaviour scripts: Actor and Player. Actor handles velocity, movement, things like that. Player uses inputs to control an Actor component. This is the code:

// Actor.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(CharacterController))]
public class Actor : MonoBehaviour
{
    enum MovementAxis
    {
        Horizontal,
        Vertical
    }

    public Vector3 Direction
    {
        get
        {
            return _direction;
        }
        set
        {
            if (controller.isGrounded)
                _direction = value.normalized;
            else
                _direction = Vector3.Lerp(_direction, value, airControl * Time.deltaTime).normalized;
        }
    }
    public Vector3 Velocity
    {
        get
        {
            return _velocity;
        }
        set
        {
            _velocity = value;
        }
    }

    public bool IsJumping { get; set; }

    public float Height
    {
        get
        {
            return _height;
        }
        set
        {
            _height = value;
            controller.height = value;
        }
    }

    public float DefaultHeight { get; private set; }

    public bool IsCrouching { get; set; }

    public float acceleration = 48;
    public float deceleration = 56;
    public float skidAcceleration = 72;
    public float topSpeed = 5;

    public float jumpHeight = 8.0f;
    public float airControl = 35.0f;
    public float gravity = 35.0f;
    public float jumpingGravity = 15.0f;
    public float extendedJumpThreshold = 6.0f;
    public float terminalVelocity = 50.0f;

    public float crouchHeight = 0.5f;
    public float crouchSpeed = 0.375f;

    private CharacterController controller;

    private Vector3 _direction;
    private Vector3 _velocity;

    private float _height;
    private float _defaultHeight;

    public void Jump()
    {
        if (controller.isGrounded)
        {
            _velocity.y = jumpHeight;
            _velocity += transform.forward * acceleration * Time.deltaTime;
        }
    }

    // Start is called before the first frame update
    void Start()
    {
        controller = gameObject.GetComponent<CharacterController>();

        DefaultHeight = controller.height;
        _height = DefaultHeight;
    }

    // Update is called once per frame
    void Update()
    {
        if (controller.isGrounded)
        {
            Vector3 planarTarget = new Vector3(_direction.x, 0, _direction.z);
            if (planarTarget.sqrMagnitude > 0.01f)
            {
                Quaternion targetRot = Quaternion.LookRotation(planarTarget);
                transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRot, 600.0f * Time.deltaTime);
            }
        }

        Accelerate(ref _velocity.x, ref _direction.x);
        Accelerate(ref _velocity.z, ref _direction.z);

        Decelerate(ref _velocity.x, ref _direction.x);
        Decelerate(ref _velocity.z, ref _direction.z);

        if (!controller.isGrounded)
        {
            if (IsJumping && _velocity.y > extendedJumpThreshold)
                _velocity.y -= jumpingGravity * Time.deltaTime;
            else
                _velocity.y -= gravity * Time.deltaTime;
        }

        if (IsCrouching)
            Height = DefaultHeight * crouchHeight;
        else
            Height = DefaultHeight;

        _velocity.y = Mathf.Clamp(_velocity.y, -terminalVelocity, terminalVelocity);

        controller.Move(_velocity * Time.deltaTime);
    }

    void FixedUpdate()
    {

    }

    void Accelerate(ref float velComponent, ref float dirComponent)
    {
        bool moving = Mathf.Abs(dirComponent) > 0.0f;
        if (moving)
        {
            float absMotion = Mathf.Abs(velComponent);
            float crouchFactor = IsCrouching ? crouchSpeed : 1.0f;

            if ((velComponent > 0 && dirComponent < 0) ||
                (velComponent < 0 && dirComponent > 0))
            {
                velComponent += dirComponent * skidAcceleration * Time.deltaTime;
            }
            else if (absMotion < topSpeed * crouchFactor)
            {
                velComponent += dirComponent * acceleration * Time.deltaTime;
            }
            else if (absMotion > topSpeed * crouchFactor)
            {
                velComponent -= dirComponent * deceleration * Time.deltaTime;
            }
        }
    }

    void Decelerate(ref float velComponent, ref float dirComponent)
    {
        bool moving = Mathf.Abs(dirComponent) > 0.0f;
        if (!moving)
        {
            if (velComponent < 0)
            {
                velComponent += deceleration * Time.deltaTime;
                if (velComponent > 0)
                    velComponent = 0;
            }
            else if (velComponent > 0)
            {
                velComponent -= deceleration * Time.deltaTime;
                if (velComponent < 0)
                    velComponent = 0;
            }
        }    
    }
}

// Player.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class Player : MonoBehaviour
{
    public Transform camTransform;

    private Actor actor;

    private InputAction moveAction;
    private InputAction jumpAction;
    private InputAction crouchAction;

    // Start is called before the first frame update
    void Start()
    {
        actor = gameObject.GetComponent<Actor>();

        moveAction = InputSystem.actions.FindAction("move");
        jumpAction = InputSystem.actions.FindAction("jump");
        crouchAction = InputSystem.actions.FindAction("crouch");
    }

    // Update is called once per frame
    void Update()
    {
        float oldCamPitch = camTransform.eulerAngles.x;
        float oldCamRoll = camTransform.eulerAngles.z;

        Vector2 moveInput = moveAction.ReadValue<Vector2>();

        Vector3 camForward = camTransform.forward;
        Vector3 camRight = camTransform.right;

        camForward.y = 0;
        camRight.y = 0;

        camForward.Normalize();
        camRight.Normalize();

        Vector3 moveDir = camForward * moveInput.y + camRight * moveInput.x;

        actor.Direction = moveDir.normalized;

        if (jumpAction.WasPressedThisFrame())
            actor.Jump();

        actor.IsJumping = jumpAction.IsPressed();
        actor.IsCrouching = crouchAction.IsPressed();
    }
}

The issue I'm noticing is that when the camera yaw is changed, the corresponding movement direction won't be what is expected. It seems like the player character moves in a strange zigzag pattern or is going the wrong direction, especially when two direction keys are pressed at the same time. I also noticed how, when the player holds down the movement keys, the character's direction does not change alongside the camera angle.

I don't know why this happens. Did I do something wrong with the direction transformation math? Is my velocity system just bad? I really need help here.

Bunabyte
  • 331
  • 2
  • 7