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 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 currentMaxSpeed = new ReactiveProperty(); public float MaxSpeed => currentMaxSpeed.Value; public ReactiveProperty MaxSpeedSource => currentMaxSpeed; //private readonly ReactiveProperty direction = new ReactiveProperty(Vector3.zero); private void Reset() { rigidbody = GetComponent(); } 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 CalculateRotationStream(Observable 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 CalculateVelocityStream(Observable directionSource, Observable 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 Interpolate(Vector3 start, Vector3 target, float duration) { return Interpolate(start, target, duration, UnityFrameProvider.FixedUpdate, AnimationCurve.Linear(0, 0, 1, 1)); } private static Observable Interpolate(Vector3 start, Vector3 target, float duration, AnimationCurve curve) { return Interpolate(start, target, duration, UnityFrameProvider.FixedUpdate, curve); } private static Observable Interpolate(Vector3 start, Vector3 target, float duration, FrameProvider provider) { return Interpolate(start, target, duration, provider, AnimationCurve.Linear(0, 0, 1, 1)); } private static Observable 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; } } }