using System.Collections.Generic; using Alchemy.Inspector; using KinematicCharacterController; using LitMotion; using UnityEngine; namespace Code.Game.Gameplay.Player { public enum OrientationMethod { TowardsCamera, TowardsMovement, } public struct PlayerCharacterInputs { public float MoveAxisForward; public float MoveAxisRight; } public class PlayerMovementController : MonoBehaviour, ICharacterController { public KinematicCharacterMotor Motor; [TabGroup("Stable Movement")] public float MaxStableMoveSpeed = 10f; [TabGroup("Stable Movement")] public float StableMovementSharpness = 15f; [TabGroup("Stable Movement")] public float OrientationSharpness = 10f; [TabGroup("Stable Movement")] public OrientationMethod OrientationMethod = OrientationMethod.TowardsMovement; [SerializeField] public float BonusOrientationSharpness; [TabGroup("Misc")] public List IgnoredColliders = new List(); public Vector3 CurrentVelocity => Motor.Velocity; public Quaternion CurrentRotation => Motor.TransientRotation; private Collider[] _probedColliders = new Collider[8]; private RaycastHit[] _probedHits = new RaycastHit[8]; private Vector3 _moveInputVector; private Vector3 _lookInputVector; private Vector3 _internalVelocityAdd = Vector3.zero; private Vector3 lastInnerNormal = Vector3.zero; private Vector3 lastOuterNormal = Vector3.zero; private void Awake() { // Assign the characterController to the motor Motor.CharacterController = this; } /// /// This is called every frame by ExamplePlayer in order to tell the character what its inputs are /// public void SetInputs(ref PlayerCharacterInputs inputs) { // Clamp input Vector3 moveInputVector = Vector3.ClampMagnitude(new Vector3(inputs.MoveAxisRight, 0f, inputs.MoveAxisForward), 1f); // Move and look inputs _moveInputVector = moveInputVector; _lookInputVector = _moveInputVector.normalized; } private Quaternion _tmpTransientRot; private MotionHandle _crouchTransition; /// /// (Called by KinematicCharacterMotor during its update cycle) /// This is called before the character begins its movement update /// public void BeforeCharacterUpdate(float deltaTime) { } /// /// (Called by KinematicCharacterMotor during its update cycle) /// This is where you tell your character what its rotation should be right now. /// This is the ONLY place where you should set the character's rotation /// public void UpdateRotation(ref Quaternion currentRotation, float deltaTime) { if (_lookInputVector.sqrMagnitude > 0f && OrientationSharpness > 0f) { // Smoothly interpolate from current to target look direction Vector3 smoothedLookInputDirection = Vector3.Slerp(Motor.CharacterForward, _lookInputVector, 1 - Mathf.Exp(-OrientationSharpness * deltaTime)).normalized; // Set the current rotation (which will be used by the KinematicCharacterMotor) currentRotation = Quaternion.LookRotation(smoothedLookInputDirection, Motor.CharacterUp); } } /// /// (Called by KinematicCharacterMotor during its update cycle) /// This is where you tell your character what its velocity should be right now. /// This is the ONLY place where you can set the character's velocity /// public void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime) { // Ground movement if (Motor.GroundingStatus.IsStableOnGround) { float currentVelocityMagnitude = currentVelocity.magnitude; Vector3 effectiveGroundNormal = Motor.GroundingStatus.GroundNormal; // Reorient velocity on slope currentVelocity = Motor.GetDirectionTangentToSurface(currentVelocity, effectiveGroundNormal) * currentVelocityMagnitude; // Calculate target velocity Vector3 inputRight = Vector3.Cross(_moveInputVector, Motor.CharacterUp); Vector3 reorientedInput = Vector3.Cross(effectiveGroundNormal, inputRight).normalized * _moveInputVector.magnitude; Vector3 targetMovementVelocity = reorientedInput * MaxStableMoveSpeed; // Smooth movement Velocity currentVelocity = Vector3.Lerp(currentVelocity, targetMovementVelocity, 1f - Mathf.Exp(-StableMovementSharpness * deltaTime)); } // Take into account additive velocity if (_internalVelocityAdd.sqrMagnitude > 0f) { currentVelocity += _internalVelocityAdd; _internalVelocityAdd = Vector3.zero; } } /// /// (Called by KinematicCharacterMotor during its update cycle) /// This is called after the character has finished its movement update /// public void AfterCharacterUpdate(float deltaTime) { } public void PostGroundingUpdate(float deltaTime) { } public bool IsColliderValidForCollisions(Collider coll) { if (IgnoredColliders.Count == 0) { return true; } if (IgnoredColliders.Contains(coll)) { return false; } return true; } public void OnGroundHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport) { } public void OnMovementHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport) { } public void AddVelocity(Vector3 velocity) { _internalVelocityAdd += velocity; } public void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, Vector3 atCharacterPosition, Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport) { } public void OnDiscreteCollisionDetected(Collider hitCollider) { } } }