canto/Assets/Scripts/Entity/MotorAnimator.cs
2025-10-02 15:28:03 -04:00

145 lines
4.2 KiB
C#

using System.Threading;
using KitsuneCafe.Extension;
using KitsuneCafe.Sys;
using R3;
using UnityEngine;
using UnityForge.PropertyDrawers;
namespace KitsuneCafe.Entities
{
public class MotorAnimator : MonoBehaviour
{
private const float epsilon = 0.05f;
[Header("Dependencies")]
[SerializeField]
private Animator animator;
[SerializeField]
private new Rigidbody rigidbody;
[SerializeField]
private Spring spring;
[SerializeField]
private Motor motor;
[SerializeField]
private AirMotor airMotor;
[SerializeField]
private SprintFeature sprint;
[Header("Movement")]
[SerializeField, AnimatorParameterName(AnimatorControllerParameterType.Float, "animator")]
private string movementSpeedParameter;
[SerializeField]
private float movementSmoothTime = 0.1f;
[SerializeField, AnimatorParameterName(AnimatorControllerParameterType.Bool, "animator")]
private string sprintingParameter;
[SerializeField, Range(0f, 1f)]
private float walkAnimationParamMax = 0.5f;
[SerializeField, Range(0f, 1f)]
private float sprintAnimationParamMax = 1.0f;
[SerializeField, AnimatorParameterName(AnimatorControllerParameterType.Bool, "animator")]
private string fallParam;
[SerializeField, AnimatorParameterName(AnimatorControllerParameterType.Bool, "animator")]
private string hardLandingParam;
private CancellationTokenSource disableCancellationTokenSource;
private CancellationToken disableCancellationToken => disableCancellationTokenSource.Token;
private void Reset()
{
TryGetComponent(out animator);
TryGetComponent(out rigidbody);
TryGetComponent(out spring);
TryGetComponent(out motor);
TryGetComponent(out airMotor);
TryGetComponent(out sprint);
}
private void OnEnable()
{
disableCancellationTokenSource = new();
var d = Disposable.CreateBuilder();
CurrentSpeed()
.Compose(CurrentAnimationSpeed)
.SmoothDamp(
() => animator.GetFloat(movementSpeedParameter),
movementSmoothTime,
UnityTimeProvider.FixedUpdate
)
.Subscribe(speed =>
{
animator.SetFloat(movementSpeedParameter, speed);
})
.AddTo(ref d);
sprint.IsSprinting
.DistinctUntilChanged()
.Subscribe(isSprinting =>
{
animator.SetBool(sprintingParameter, isSprinting);
})
.AddTo(ref d);
Observable.FromEventHandler<LandingForce>(
e => airMotor.Landed += e,
e => airMotor.Landed -= e
)
.Select(eh => eh.e)
.Where(force => force == LandingForce.Hard)
.Subscribe(force => animator.SetTrigger(hardLandingParam))
.AddTo(ref d);
airMotor.ObservePropertyChanged(x => x.IsFalling)
.Subscribe(falling => animator.SetBool(fallParam, falling))
.AddTo(ref d);
d.RegisterTo(disableCancellationToken);
}
private void OnDisable()
{
disableCancellationTokenSource.Cancel();
}
private Observable<float> CurrentSpeed() =>
CurrentVelocity()
.Select(vel => vel.sqrMagnitude)
.DistinctUntilChanged()
.Select(Mathf.Sqrt);
private Observable<Vector3> CurrentVelocity() =>
Observable.EveryUpdate(UnityFrameProvider.FixedUpdate)
.Select(_ => rigidbody.linearVelocity);
private Observable<float> CurrentAnimationSpeed(Observable<float> speedSource) =>
speedSource
.CombineLatest(motor.MaxSpeedSource, AnimationScaleFactor(), NormalizeSpeed)
.DistinctUntilChanged(FEqualityComparer<float>.Create(KMath.IsApproxEqual));
private Observable<float> AnimationScaleFactor() =>
motor.MaxSpeedSource.Select(
maxSpeed => NormalizationFactor(maxSpeed, motor.DefaultMaxSpeed)
);
private float NormalizationFactor(float maxSpeed, float defaultMaxSpeed) =>
maxSpeed > defaultMaxSpeed ? sprintAnimationParamMax : walkAnimationParamMax;
private float NormalizeSpeed(float currentSpeed, float maxSpeed, float factor) =>
currentSpeed > maxSpeed && maxSpeed > epsilon ?
factor :
Mathf.InverseLerp(0, maxSpeed, currentSpeed) * factor;
}
}