using System; using System.Threading; using KitsuneCafe.Extension; using KitsuneCafe.Sys; using R3; using UnityEngine; using UnityEngine.Events; namespace KitsuneCafe.Entities { public class WaypointFollower : MonoBehaviour { [SerializeField] private Transform[] waypoints; [SerializeField, Range(0.01f, 2f)] private float speed = 0.25f; [SerializeField] private float minimumDistance = 0.05f; [Space(8)] [SerializeField] private UnityEvent OnWaypointReached; public event EventHandler WaypointReached = delegate { }; [SerializeField] private UnityEvent OnMoving; public event EventHandler Moving = delegate { }; [SerializeField] private UnityEvent OnCompleted; public event EventHandler Completed = delegate { }; [SerializeField] private UnityEvent OnRestarted; public event EventHandler Restarted = delegate { }; private readonly ReactiveProperty index = new(0); private float sqrDistance; private CancellationTokenSource disableCancellationTokenSource; private CancellationToken disableCancellationToken => disableCancellationTokenSource.Token; private void OnValidate() { sqrDistance = Mathf.Pow(minimumDistance, 2); } private void OnEnable() { disableCancellationTokenSource = new(); index.AsObservable() .SelectMany(index => DirectionTo(waypoints[index].position) .Do( onNext: _ => NotifyMoving(), onCompleted: result => { if (result.IsSuccess) NotifyWaypointReached(index); } ) ) .Subscribe(ApplyDirection) .RegisterTo(disableCancellationToken); } private void NotifyMoving() { Moving?.Invoke(this, EventArgs.Empty); OnMoving?.Invoke(); } private void NotifyWaypointReached(int index) { WaypointReached?.Invoke(this, index); OnWaypointReached.Invoke(index); if (index == waypoints.Length - 1) { Completed?.Invoke(this, EventArgs.Empty); OnCompleted?.Invoke(); } if (index == 0) { Restarted?.Invoke(this, EventArgs.Empty); OnRestarted?.Invoke(); } } private void ApplyDirection(Vector3 vector) { transform.position += vector.normalized * speed; } private void OnDisable() { disableCancellationTokenSource.Cancel(); } public void MoveNext() { index.Value = (index.Value + 1) % waypoints.Length; } private Observable DirectionTo(Vector3 destination) => Observable.EveryUpdate(UnityFrameProvider.FixedUpdate) .Select(_ => destination - transform.position) .TakeWhile(_ => transform.position.SqrDistance(destination) > sqrDistance); private void OnDrawGizmos() { Gizmos.color = Color.white; foreach (var point in waypoints) { Gizmos.DrawWireSphere(point.position, minimumDistance); } } } }