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.