using System.Collections.Generic; using UnityEngine; using Reset.Core.Tools; using Sirenix.OdinInspector; using Unity.Netcode; namespace Reset.Units{ public class UnitMovementHandler : UnitComponent { [ShowInInspector, InlineProperty, HideLabel, FoldoutGroup("Resolved Movement", expanded: true)] public ResolvedMovement resolvedMovement; // SmoothDamp Velocities private Vector2 refVelocityDirectionChangingHardness; // Smoothing Values private float directionChangeDotLerp; private Vector3 moveSmoothVelocityRef; private float gravitySmoothVelocityRef; // References private CharacterController controller; private IUnitDirectionProvider directionProvider; private IUnitTargetProvider targetProvider; // Movement Data [ShowInInspector, PropertyOrder(2), FoldoutGroup("Movement Data", expanded: true), InlineProperty, HideLabel] public UnitMovementData data = new(); // Other public Vector3 specifiedRotation; // Used for locking a specific direction void Awake(){ controller = GetComponent(); directionProvider = GetComponent(); targetProvider = GetComponent(); InitAllSettings(); } void Start(){ resolvedMovement = new ResolvedMovement{ moveDirection = new ResolvedMovement.MoveDirection(transform) }; } void Update(){ SmoothAllSettings(); UpdateCurrentDirection(); UpdateCurrentGravity(); UpdateCurrentSpeed(); UpdateCurrentRotation(); // Apply movement DoMovement(resolvedMovement.moveDirection.World, resolvedMovement.gravity, resolvedMovement.moveSpeed, data.gravityScale.Value); // Apply movespeed to the Animator Unit.Animation.moveSpeed = resolvedMovement.moveSpeed * resolvedMovement.moveDirection.Local.magnitude / data.moveSpeed.currentValue; DebugOverlayDrawer.ChangeValue("Movement", "Move Direction (Local)", resolvedMovement.moveDirection.Local); DebugOverlayDrawer.ChangeValue("Movement", "Move Direction (World)", resolvedMovement.moveDirection.World); } // Update the direction, called every frame private void UpdateCurrentDirection(){ // Get input value Vector2 targetDirection = new Vector2(directionProvider.Direction.x, directionProvider.Direction.y); // Rotate input by camera rotation (instead of rotating the output direction by camera rotation) if (GetComponent()) { targetDirection = (Camera.main.transform.rotation * targetDirection.ToVector3()).ToVector2(); } // Deadzone if (targetDirection.magnitude < .1f) { targetDirection = resolvedMovement.moveDirection.RawWorld; } // Set Raw Direction (this is used by the camera later) resolvedMovement.moveDirection.RawWorld = targetDirection; // Get current direction Vector2 currentDirection = resolvedMovement.moveDirection.World; // Also need to find the dot value of the current input versus the current move direction float switchedDirection = Vector3.Dot(targetDirection, currentDirection); float switchedDirectionRemapped = Mathf.Lerp(0, 1, switchedDirection); directionChangeDotLerp = Mathf.Lerp(switchedDirection, switchedDirectionRemapped, data.directionSpinningHardness.Value * Time.deltaTime); DebugOverlayDrawer.ChangeValue("Movement", "Direction Change Dot", directionChangeDotLerp); // Smooth movement. Use deaccel smoothing if the input magnitude is lower, and accel smoothing if it's higher // Also checks when grounded to only use Slerp on the ground Vector3 slerpedValue; Vector2 lerpedValue; Vector2 newDirection; if (controller.isGrounded){ slerpedValue = Vector3.Slerp(currentDirection, targetDirection, data.directionSpinningSpeed.Value * Time.deltaTime); lerpedValue = Vector2.SmoothDamp(currentDirection, targetDirection, ref refVelocityDirectionChangingHardness, data.directionChangingSoftness.Value * Time.deltaTime); newDirection = Vector2.Lerp(slerpedValue, lerpedValue, directionChangeDotLerp); } else { newDirection = Vector2.SmoothDamp(currentDirection, targetDirection, ref refVelocityDirectionChangingHardness, data.directionChangingSoftness.Value * data.airDirectionDecay.Value * Time.deltaTime); } newDirection = Vector3.Slerp(resolvedMovement.moveDirection.World, newDirection, directionProvider.Direction.magnitude); // Commit the new direction resolvedMovement.moveDirection.World = newDirection; } // Update the speed, called every frame private void UpdateCurrentSpeed(){ // ""Smooth"" the speed float smoothedSpeed; if (resolvedMovement.moveDirection.Local.magnitude < directionProvider.Direction.magnitude) { smoothedSpeed = Mathf.MoveTowards(resolvedMovement.moveSpeed, data.moveSpeed.Value, data.acceleration.Value * Time.deltaTime); } else { smoothedSpeed = Mathf.MoveTowards(resolvedMovement.moveSpeed, 0f, data.deacceleration.Value * Time.deltaTime); } // Commit the speed resolvedMovement.moveSpeed = smoothedSpeed; DebugOverlayDrawer.ChangeValue("Movement", "Resolved Speed", resolvedMovement.moveSpeed); } // Update the gravity, called every frame // NOTE: most gravity interactions, like when grounded or not, is now handled by the state machine private void UpdateCurrentGravity(){ // Accelerate gravity if (!controller.isGrounded){ resolvedMovement.gravity -= data.gravityAcceleration.Value * Time.deltaTime; } // Create the final gravity value float gravityMoveDirection = Physics.gravity.y * resolvedMovement.gravity; } // Update the rotation, called every frame private void UpdateCurrentRotation(){ // Get input value Vector3 inputMovement = new Vector3(directionProvider.Direction.x, 0f, directionProvider.Direction.y); Quaternion targetRotation = Quaternion.identity; // Switch the desired rotation based on current movement setting switch (data.facingDirection.Value) { // Just look at target case UnitFacingDirection.TowardsTarget: // Look directly at the target if (targetProvider.UnitTarget == null) { Debug.LogError("Trying to rotate towards a target but there is no target. Forcing rotation to Static and continuing"); data.facingDirection.Value = UnitFacingDirection.Static; data.facingDirection.currentValue = UnitFacingDirection.Static; targetRotation = transform.rotation; break; } targetRotation = Quaternion.LookRotation(transform.position.DirectionTo(targetProvider.UnitTarget.transform.position)); break; case UnitFacingDirection.Momentum: // Look towards the current direction the agent is moving if (inputMovement.magnitude > .05f){ targetRotation = Quaternion.LookRotation(resolvedMovement.moveDirection.RawWorld.ToVector3(), Vector3.up); } break; case UnitFacingDirection.MatchInput: // Look towards the input direction- similar to Momentum but snappier if (directionProvider.Direction.magnitude < 0.05f) { break; } targetRotation = Camera.main.transform.rotation * Quaternion.LookRotation(inputMovement); break; case UnitFacingDirection.MatchCamera: // Look the same direction as the camera targetRotation = Quaternion.Euler(Camera.main.transform.rotation.eulerAngles.Flatten(0, null, 0)); break; case UnitFacingDirection.Static: // Don't change targetRotation = resolvedMovement.rotation; break; } DebugOverlayDrawer.ChangeValue("Rotation", "Target Rotation", targetRotation.eulerAngles); // Add the current input into the created rotation if (data.facingDirection.Value == UnitFacingDirection.MatchCamera || data.facingDirection.Value == UnitFacingDirection.TowardsTarget) { resolvedMovement.rotation = targetRotation; } else if (directionProvider.Direction.sqrMagnitude > .1){ resolvedMovement.rotation = targetRotation; } // Apply rotation to the character transform.rotation = Quaternion.Slerp(transform.rotation, resolvedMovement.rotation, data.rotationSpeed.Value * Time.deltaTime).Flatten(0, null, 0); } // Custom move from input private void DoMovement(Vector2 moveDir, float gravDir, float speed, float gravityScale){ // Seperate the different move directions. Additonal becomes it's own because it needs to be added independently of the other two Vector2 moveXZDir = moveDir; float moveYDir = gravDir; // Add their related speeds moveXZDir *= speed * Time.deltaTime; moveYDir *= data.gravityScale.Value * Time.deltaTime; // Construct the direction and move Vector3 finalDir = new Vector3(moveXZDir.x, moveYDir, moveXZDir.y); controller.Move(finalDir); } // Setting absolute to true will cause the current gravity to snap to the new gravity value. // Keeping it false will make it apply additively to the current gravity. Both options use relativty for linear interpolation. public void SetNewGravity(float value, float relativity, bool absolute){ // new float newGravity; if (absolute){ newGravity = Mathf.Lerp(resolvedMovement.gravity, value, relativity); } else { newGravity = Mathf.Lerp(resolvedMovement.gravity, resolvedMovement.gravity + value, relativity); } if (Unit.UnitIsNetworked() && !Unit.UnitIsLocal()) { SetNewGravityRpc(newGravity); } else { resolvedMovement.gravity = newGravity; } } [Rpc(SendTo.Owner)] public void SetNewGravityRpc(float value){ resolvedMovement.gravity = value; } public void SetNewDirection(Vector2 value, float relativity, bool absolute, Vector2 relativeTo = default, bool relativeToRotation = true){ // new Vector2 relativeValue; if (relativeToRotation){ relativeValue = (Quaternion.LookRotation(relativeTo.ToVector3()) * value.ToVector3()).ToVector2(); } else { relativeValue = relativeTo + value; } Vector2 newValue; if (absolute){ newValue = Vector2.Lerp(resolvedMovement.moveDirection.World, relativeValue, relativity); } else { newValue = Vector2.Lerp(resolvedMovement.moveDirection.World, resolvedMovement.moveDirection.World + relativeValue, relativity); } if (Unit.UnitIsNetworked() && !Unit.UnitIsLocal()) { SetNewDirectionRpc(newValue); } else { resolvedMovement.moveDirection.World = newValue; } } [Rpc(SendTo.Owner)] public void SetNewDirectionRpc(Vector2 value){ resolvedMovement.moveDirection.World = value; } public void SetNewRawDirection(Vector2 value, float relativity, bool absolute, Vector2 relativeTo = default){ // new Vector2 relativeValue = relativeTo + value; Vector2 newValue; if (absolute){ newValue = Vector2.Lerp(resolvedMovement.moveDirection.RawWorld, relativeValue, relativity); } else { newValue = Vector2.Lerp(resolvedMovement.moveDirection.RawWorld, resolvedMovement.moveDirection.RawWorld + relativeValue, relativity); } if (Unit.UnitIsNetworked() && !Unit.UnitIsLocal()) { SetNewRawDirectionRpc(newValue); } else { resolvedMovement.moveDirection.RawWorld = newValue; } } [Rpc(SendTo.Owner)] public void SetNewRawDirectionRpc(Vector2 value){ resolvedMovement.moveDirection.RawWorld = value; } public void SetNewSpeed(float value, float relativity, bool absolute, float relativeTo = Mathf.Infinity){ // new float newSpeed; if (absolute){ newSpeed = Mathf.Lerp(resolvedMovement.moveSpeed, value, relativity); } else { newSpeed = Mathf.Lerp(resolvedMovement.moveSpeed, resolvedMovement.moveSpeed + value, relativity); } if (Unit.UnitIsNetworked() && !Unit.UnitIsLocal()) { SetNewSpeedRpc(newSpeed); } else { resolvedMovement.moveSpeed = newSpeed; } } [Rpc(SendTo.Owner)] public void SetNewSpeedRpc(float value){ resolvedMovement.moveSpeed = value; } public void SetNewRotation(Vector3 value, float relativity, bool absolute, Vector3 relativeTo = default){ // new Quaternion valueAsQuaternion = Quaternion.LookRotation(value); if (relativeTo != default) { valueAsQuaternion = Quaternion.LookRotation(relativeTo) * valueAsQuaternion; } Quaternion newRotation; if (absolute){ newRotation = Quaternion.Lerp(resolvedMovement.rotation, valueAsQuaternion, relativity); } else { newRotation = Quaternion.Lerp(resolvedMovement.rotation, resolvedMovement.rotation * valueAsQuaternion, relativity); } if (Unit.UnitIsNetworked() && !Unit.UnitIsLocal()) { SetNewRotationRpc(newRotation); } else { resolvedMovement.rotation = newRotation; } } [Rpc(SendTo.Owner)] public void SetNewRotationRpc(Quaternion value){ resolvedMovement.rotation = value; } public void SetSpecifiedRotation(Vector3 inputRotation){ specifiedRotation = inputRotation; } public Vector2 GetResolvedDirection(){ return resolvedMovement.moveDirection.World; } public Vector2 GetResolvedDirectionLocal(){ return resolvedMovement.moveDirection.Local; } public Vector3 GetResolvedDirectionVector3(){ return resolvedMovement.moveDirection.World.ToVector3(); } public float GetResolvedSpeed(){ return resolvedMovement.moveSpeed; } public float GetResolvedGravity(){ return resolvedMovement.gravity; } public Quaternion GetResolvedRotation(){ return resolvedMovement.rotation; } [Button("Initialize Settings", ButtonHeight = 30), PropertySpace(10,5 )] void InitAllSettings(){ var settingsList = data.GetAllSettings(); foreach (IResettableSettingValue value in settingsList) { value.Initialize(); value.Verify(); } } void SmoothAllSettings(){ var settingsList = data.GetAllSettings(); foreach (IResettableSettingValue value in settingsList) { value.SmoothAndEase(); } } public void AddToCurrentDirection(Vector3 inputDirection, float power){ // Old Debug.LogError("Using an old movement command! Switch to one of the new alternatives!"); } public void SmoothToSpeed(float desiredSpeed, float smoothing){ // Old Debug.LogError("Using an old movement command! Switch to one of the new alternatives!"); } public void SetNewDirection(Vector3 inputDirection){ // NOTE: If smoothing desired add a default bool for smoothing maybe? // Old Debug.LogError("Using an old movement command! Switch to one of the new alternatives!"); } public void SetNewGravity(float value){ Debug.LogError("Using an old movement command! Switch to one of the new alternatives!"); } public void OverwriteDirectionFromInput(Vector2 value, float priority, float speed = Mathf.Infinity){ // Old Debug.LogError("Using an old movement command! Switch to one of the new alternatives!"); } } }