Files
project-reset/Assets/Scripts/Units/UnitMovementHandler.cs
Chris 2e28fe17cd fix: player now properly responds to root motion
also contains changes to the animation controller for all the currently made animations, events for the singular attack animation, corresponding methods, and graph changes to utilize them
2026-01-07 01:10:59 -05:00

400 lines
17 KiB
C#

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<CharacterController>();
directionProvider = GetComponent<IUnitDirectionProvider>();
targetProvider = GetComponent<IUnitTargetProvider>();
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);
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<Player>()) {
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!");
}
}
}