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

122 lines
3 KiB
C#

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<int> OnWaypointReached;
public event EventHandler<int> 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<int> 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<Vector3> 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);
}
}
}
}