canto/Assets/Scripts/Entity/Motor.cs
2025-07-14 22:22:25 -04:00

203 lines
7.4 KiB
C#

using KitsuneCafe.Extension;
using KitsuneCafe.SOAP;
using R3;
using UnityEngine;
namespace KitsuneCafe.Entity
{
public class Motor : MonoBehaviour
{
[SerializeField]
private new Rigidbody rigidbody;
[SerializeField]
private ReactiveSource<Vector2> direction;
[SerializeField]
private float maxSpeed = 25f;
public float DefaultMaxSpeed => maxSpeed;
[SerializeField]
private float accelerationTime = 1f;
public float AccelerationTime => accelerationTime;
[SerializeField]
private AnimationCurve accelerationCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
[SerializeField]
private float decelerationTime = 0.5f;
public float Decelerationtime => decelerationTime;
[SerializeField]
private AnimationCurve decelerationCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
[SerializeField]
private float rotationSpeed = 25f;
public float RotationSpeed => rotationSpeed;
[SerializeField]
private float epsilon = 0.001f;
private float sqEpsilon;
private ReactiveProperty<float> currentMaxSpeed = new ReactiveProperty<float>();
public float MaxSpeed => currentMaxSpeed.Value;
public ReactiveProperty<float> MaxSpeedSource => currentMaxSpeed;
//private readonly ReactiveProperty<Vector3> direction = new ReactiveProperty<Vector3>(Vector3.zero);
private void Reset()
{
rigidbody = GetComponent<Rigidbody>();
}
private void Awake()
{
var directionSource = direction.AsObservable().Select(v2 => new Vector3(v2.x, 0, v2.y));
currentMaxSpeed.Value = maxSpeed;
sqEpsilon = epsilon * epsilon;
var d = Disposable.CreateBuilder();
CalculateVelocityStream(directionSource, currentMaxSpeed, UnityFrameProvider.FixedUpdate)
.Subscribe(force =>
{
rigidbody.AddForce(force, ForceMode.Acceleration);
})
.AddTo(ref d);
CalculateRotationStream(directionSource, UnityFrameProvider.FixedUpdate)
.Subscribe(targetRotation =>
{
if (Quaternion.Angle(transform.rotation, targetRotation) > 0.1f)
{
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.fixedDeltaTime * rotationSpeed);
}
})
.AddTo(ref d);
d.RegisterTo(destroyCancellationToken);
}
private Observable<Quaternion> CalculateRotationStream(Observable<Vector3> directionSource, FrameProvider provider)
{
var targetRotationStream = directionSource
.Select(inputDir =>
{
if (inputDir.sqrMagnitude > sqEpsilon)
{
return Quaternion.LookRotation(inputDir.normalized);
}
else
{
return transform.rotation;
}
})
.DistinctUntilChanged();
return Observable.EveryUpdate(provider)
.WithLatestFrom(targetRotationStream, (_, targetRotation) => targetRotation);
}
private Observable<Vector3> CalculateVelocityStream(Observable<Vector3> directionSource, Observable<float> maxSpeedSource, FrameProvider provider)
{
return directionSource
.DistinctUntilChanged()
.CombineLatest(maxSpeedSource, (direction, currentMaxSpeed) => (direction, currentMaxSpeed))
.Select(data =>
{
var (direction, maxSpeed) = data;
var start = rigidbody.linearVelocity;
var targetMagnitude = direction.sqrMagnitude > sqEpsilon ? maxSpeed : 0f;
var targetVelocity = direction.normalized * targetMagnitude;
if (start.SqrDistance(targetVelocity) < sqEpsilon)
{
if (targetVelocity.sqrMagnitude < sqEpsilon && start.sqrMagnitude < sqEpsilon)
{
return Observable.Return(Vector3.zero);
}
else
{
return Observable.Return((targetVelocity - start) / Time.fixedDeltaTime);
}
}
float lerpDuration;
AnimationCurve curve;
if (targetMagnitude > epsilon)
{
var ratio = start.magnitude / maxSpeed;
lerpDuration = accelerationTime * (1f - ratio);
lerpDuration = Mathf.Max(lerpDuration, 0.05f);
curve = accelerationCurve;
}
else
{
lerpDuration = decelerationTime;
curve = decelerationCurve;
}
return Interpolate(start, targetVelocity, lerpDuration, provider, curve)
.Select(targetVelocity =>
{
var currentVelocity = rigidbody.linearVelocity;
return (targetVelocity - currentVelocity) / Time.fixedDeltaTime;
})
.Where(_ => targetVelocity.sqrMagnitude > sqEpsilon || rigidbody.linearVelocity.sqrMagnitude > sqEpsilon)
.DefaultIfEmpty(Vector3.zero);
})
.Switch();
}
private static Observable<Vector3> Interpolate(Vector3 start, Vector3 target, float duration)
{
return Interpolate(start, target, duration, UnityFrameProvider.FixedUpdate, AnimationCurve.Linear(0, 0, 1, 1));
}
private static Observable<Vector3> Interpolate(Vector3 start, Vector3 target, float duration, AnimationCurve curve)
{
return Interpolate(start, target, duration, UnityFrameProvider.FixedUpdate, curve);
}
private static Observable<Vector3> Interpolate(Vector3 start, Vector3 target, float duration, FrameProvider provider)
{
return Interpolate(start, target, duration, provider, AnimationCurve.Linear(0, 0, 1, 1));
}
private static Observable<Vector3> Interpolate(Vector3 start, Vector3 target, float duration, FrameProvider provider, AnimationCurve curve)
{
return Observable.EveryUpdate(provider)
.Scan(0f, (elapsedTime, _) => elapsedTime + Time.fixedDeltaTime)
.Select(elapsedTime =>
{
var factor = 0f;
if (duration > float.Epsilon)
{
factor = Mathf.Clamp01(elapsedTime / duration);
}
else
{
factor = 1f;
}
var evaluatedFactor = curve.Evaluate(factor);
return Vector3.Lerp(start, target, evaluatedFactor);
});
}
public void ChangeMaxSpeed(float value)
{
currentMaxSpeed.Value = value;
}
public void ResetMaxSpeed()
{
currentMaxSpeed.Value = maxSpeed;
}
}
}