233 lines
6.8 KiB
C#
233 lines
6.8 KiB
C#
using System.ComponentModel;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading;
|
|
using KitsuneCafe.Extension;
|
|
using R3;
|
|
using UnityAtoms.BaseAtoms;
|
|
using UnityEngine;
|
|
|
|
namespace KitsuneCafe.Entities
|
|
{
|
|
public interface IMotor
|
|
{
|
|
Vector3 LinearVelocity { get; }
|
|
bool Move(Vector3 direction);
|
|
};
|
|
|
|
public class Motor : MonoBehaviour, IMotor, INotifyPropertyChanged
|
|
{
|
|
[Header("Dependencies")]
|
|
[SerializeField]
|
|
private new Rigidbody rigidbody;
|
|
|
|
[Header("Movement")]
|
|
[SerializeField]
|
|
private Vector2Reference 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;
|
|
|
|
private readonly ReactiveProperty<float> currentMaxSpeed = new();
|
|
public float MaxSpeed => currentMaxSpeed.Value;
|
|
public ReactiveProperty<float> MaxSpeedSource => currentMaxSpeed;
|
|
|
|
public Vector3 LinearVelocity => rigidbody.linearVelocity;
|
|
|
|
private CancellationTokenSource disableCancellationSource;
|
|
|
|
public event PropertyChangedEventHandler PropertyChanged;
|
|
|
|
private bool canMove = true;
|
|
public bool CanMove
|
|
{
|
|
get => canMove;
|
|
set
|
|
{
|
|
if (canMove != value)
|
|
{
|
|
canMove = value;
|
|
Notify();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Reset()
|
|
{
|
|
rigidbody = GetComponent<Rigidbody>();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
disableCancellationSource = new();
|
|
var d = Disposable.CreateBuilder();
|
|
|
|
currentMaxSpeed.Value = maxSpeed;
|
|
|
|
var directionSource = direction.ObserveChange().Select(v2 => new Vector3(v2.x, 0, v2.y));
|
|
var velocitySource = ObserveVelocity(directionSource, currentMaxSpeed, UnityFrameProvider.FixedUpdate);
|
|
var rotationSource = ObserveRotation(directionSource, UnityFrameProvider.FixedUpdate);
|
|
var canMove = this.ObservePropertyChanged(x => x.CanMove).DefaultIfEmpty(CanMove);
|
|
|
|
canMove.WhereFalse()
|
|
.Subscribe(_ => StopMoving())
|
|
.AddTo(ref d);
|
|
|
|
canMove
|
|
.SwitchIfElse(velocitySource, Observable.Empty<Vector3>())
|
|
.Subscribe(Accelerate)
|
|
.AddTo(ref d);
|
|
|
|
canMove
|
|
.SwitchIfElse(rotationSource, Observable.Empty<Quaternion>())
|
|
.Where(rotation => Quaternion.Angle(transform.rotation, rotation) > 0.1f)
|
|
.Subscribe(ApplyRotation)
|
|
.AddTo(ref d);
|
|
|
|
d.RegisterTo(disableCancellationSource.Token);
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
StopMoving();
|
|
disableCancellationSource.Cancel();
|
|
}
|
|
|
|
private void StopMoving()
|
|
{
|
|
rigidbody.linearVelocity = new Vector3(
|
|
0,
|
|
rigidbody.linearVelocity.y,
|
|
0
|
|
);
|
|
}
|
|
|
|
private void Accelerate(Vector3 force)
|
|
{
|
|
rigidbody.AddForce(force, ForceMode.Acceleration);
|
|
}
|
|
|
|
private void ApplyRotation(Quaternion rotation)
|
|
{
|
|
rigidbody.MoveRotation(
|
|
Quaternion.Slerp(
|
|
transform.rotation,
|
|
rotation,
|
|
Time.fixedDeltaTime * rotationSpeed
|
|
)
|
|
);
|
|
}
|
|
|
|
private Observable<Quaternion> ObserveRotation(Observable<Vector3> directionSource, FrameProvider provider) =>
|
|
ObserveRotation(directionSource, transform, provider);
|
|
|
|
private static Observable<Quaternion> ObserveRotation(Observable<Vector3> directionSource, Transform transform, FrameProvider provider) =>
|
|
Observable.EveryUpdate(provider)
|
|
.WithLatestFrom(
|
|
directionSource
|
|
.Select(dir => dir.IsZero() ?
|
|
transform.rotation :
|
|
Quaternion.LookRotation(dir.normalized)
|
|
)
|
|
.DistinctUntilChanged(),
|
|
(_, rotation) => rotation
|
|
);
|
|
|
|
private Observable<Vector3> ObserveVelocity(Observable<Vector3> directionSource, Observable<float> maxSpeedSource, FrameProvider provider) =>
|
|
directionSource
|
|
.DistinctUntilChanged()
|
|
.CombineLatest(maxSpeedSource, (direction, currentMaxSpeed) => (direction, currentMaxSpeed))
|
|
.Select(data =>
|
|
{
|
|
var (direction, maxSpeed) = data;
|
|
var start = rigidbody.linearVelocity;
|
|
var targetMagnitude = !direction.IsZero() ? maxSpeed : 0f;
|
|
var targetVelocity = direction.normalized * targetMagnitude;
|
|
|
|
if (start.SqrDistance(targetVelocity).IsZero())
|
|
{
|
|
return start.IsZero() && targetVelocity.IsZero() ?
|
|
Observable.Return(Vector3.zero) :
|
|
Observable.Return((targetVelocity - start) / Time.fixedDeltaTime);
|
|
}
|
|
|
|
float lerpDuration;
|
|
AnimationCurve curve;
|
|
|
|
if (targetMagnitude > float.Epsilon)
|
|
{
|
|
var ratio = start.magnitude / maxSpeed;
|
|
lerpDuration = Mathf.Max(accelerationTime * (1f - ratio), 0.05f);
|
|
curve = accelerationCurve;
|
|
}
|
|
else
|
|
{
|
|
lerpDuration = decelerationTime;
|
|
curve = decelerationCurve;
|
|
}
|
|
|
|
return Interpolate(start, targetVelocity, lerpDuration, provider, curve)
|
|
.Select(targetVelocity =>
|
|
(targetVelocity - rigidbody.linearVelocity) / Time.fixedDeltaTime
|
|
)
|
|
.Where(_ => !targetVelocity.IsZero() || !rigidbody.linearVelocity.IsZero())
|
|
.Select(v3 => new Vector3(v3.x, 0, v3.z))
|
|
.DefaultIfEmpty(Vector3.zero);
|
|
})
|
|
.Switch();
|
|
|
|
private static Observable<Vector3> Interpolate(Vector3 start, Vector3 target, float duration, FrameProvider provider, AnimationCurve curve) =>
|
|
Observable.EveryUpdate(provider)
|
|
.Scan(0f, (elapsedTime, _) => elapsedTime + Time.fixedDeltaTime)
|
|
.Select(elapsedTime =>
|
|
{
|
|
var factor = duration > float.Epsilon ? Mathf.Clamp01(elapsedTime / duration) : 1f;
|
|
return Vector3.Lerp(start, target, curve.Evaluate(factor));
|
|
});
|
|
|
|
public bool Move(Vector2 direction)
|
|
{
|
|
this.direction.Value = direction;
|
|
return true;
|
|
}
|
|
|
|
public bool Move(Vector3 direction)
|
|
{
|
|
this.direction.Value = new Vector3(direction.x, 0, direction.z);
|
|
return true;
|
|
}
|
|
|
|
public void ChangeMaxSpeed(float value)
|
|
{
|
|
currentMaxSpeed.Value = value;
|
|
}
|
|
|
|
public void ResetMaxSpeed()
|
|
{
|
|
currentMaxSpeed.Value = maxSpeed;
|
|
}
|
|
|
|
private void Notify([CallerMemberName] string name = default)
|
|
{
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
|
}
|
|
}
|
|
}
|