194 lines
No EOL
6.1 KiB
C#
194 lines
No EOL
6.1 KiB
C#
using UnityEngine;
|
|
using System.Linq;
|
|
|
|
using R3;
|
|
using R3.Triggers;
|
|
using ObservableCollections;
|
|
|
|
using KitsuneCafe.Interaction;
|
|
using KitsuneCafe.Extension;
|
|
using KitsuneCafe.SOAP;
|
|
|
|
namespace KitsuneCafe.Player
|
|
{
|
|
public class Interactor : MonoBehaviour, IInteractor
|
|
{
|
|
[SerializeField]
|
|
private GameObject root;
|
|
public GameObject Root => root;
|
|
|
|
[SerializeField]
|
|
private new Collider collider;
|
|
|
|
[SerializeField]
|
|
private ReactiveEvent<float> interactSource;
|
|
|
|
[SerializeField]
|
|
private int updateFrequency = 10;
|
|
|
|
[SerializeField, Tooltip("How far an object must move before being considered")]
|
|
private float minimumChangeDelta = 0.1f;
|
|
private float sqrtMovementDelta;
|
|
|
|
|
|
[SerializeField]
|
|
private Color outlineColor = Color.orangeRed;
|
|
|
|
[SerializeField]
|
|
private int outlineWidth = 3;
|
|
|
|
[SerializeField]
|
|
private Outline.Mode outlineMode = Outline.Mode.OutlineAll;
|
|
|
|
private readonly ObservableHashSet<IInteractable> interactables = new ObservableHashSet<IInteractable>();
|
|
public ReactiveProperty<IInteractable> CurrentHighlightedInteractable { get; private set; }
|
|
|
|
private Vector3 lastPosition;
|
|
|
|
public new T GetComponent<T>()
|
|
{
|
|
return Root.GetComponent<T>();
|
|
}
|
|
|
|
public new bool TryGetComponent<T>(out T component)
|
|
{
|
|
return root.TryGetComponent<T>(out component);
|
|
}
|
|
|
|
void Reset()
|
|
{
|
|
root = gameObject;
|
|
collider = GetComponent<Collider>();
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
CurrentHighlightedInteractable = new ReactiveProperty<IInteractable>(null);
|
|
lastPosition = transform.position;
|
|
sqrtMovementDelta = Mathf.Sqrt(minimumChangeDelta);
|
|
|
|
var d = Disposable.CreateBuilder();
|
|
|
|
collider.OnTriggerEnterAsObservable()
|
|
.Subscribe(other =>
|
|
{
|
|
if (other.TryGetComponent(out IInteractable interactable))
|
|
{
|
|
interactables.Add(interactable);
|
|
}
|
|
})
|
|
.AddTo(ref d);
|
|
|
|
collider.OnTriggerExitAsObservable()
|
|
.Subscribe(other =>
|
|
{
|
|
if (other.TryGetComponent(out IInteractable interactable))
|
|
{
|
|
interactables.Remove(interactable);
|
|
}
|
|
})
|
|
.AddTo(ref d);
|
|
|
|
Observable.Merge(InteractablesChanged(), PlayerMoved())
|
|
.ThrottleFirstFrame(updateFrequency)
|
|
.DefaultIfEmpty()
|
|
.Select(_ =>
|
|
{
|
|
var closest = interactables
|
|
.Where(x => x.IsInteractable && x.gameObject != null)
|
|
.MinBy(x => Vector3.SqrMagnitude(gameObject.transform.position - x.gameObject.transform.position));
|
|
|
|
return closest;
|
|
})
|
|
.DistinctUntilChanged()
|
|
.Subscribe(highlighted =>
|
|
{
|
|
CurrentHighlightedInteractable.Value = highlighted;
|
|
})
|
|
.AddTo(ref d);
|
|
|
|
interactables.ObserveChanged()
|
|
.ThrottleFirstFrame(updateFrequency, UnityFrameProvider.FixedUpdate)
|
|
.Subscribe(_ =>
|
|
{
|
|
var highlighted = interactables
|
|
.Where(x => x.IsInteractable)
|
|
.MinBy(x => collider.transform.position.SqrDistance(x.gameObject.transform.position));
|
|
|
|
if (CurrentHighlightedInteractable.Value != highlighted)
|
|
{
|
|
CurrentHighlightedInteractable.Value = highlighted;
|
|
}
|
|
})
|
|
.AddTo(ref d);
|
|
|
|
var highlightedInteractable = CurrentHighlightedInteractable
|
|
.Scan((prev, cur) =>
|
|
{
|
|
UnhighlightInteractable(prev);
|
|
HighlightInteractable(cur);
|
|
return cur;
|
|
});
|
|
|
|
interactSource
|
|
.AsObservable()
|
|
.WithLatestFrom(highlightedInteractable, (_, highlighted) => highlighted)
|
|
.WhereNotNull()
|
|
.Subscribe(interactable =>
|
|
{
|
|
var result = interactable.Interact(this);
|
|
if (result.IsOk)
|
|
{
|
|
interactables.Remove(interactable);
|
|
}
|
|
else
|
|
{
|
|
Debug.Log($"interaction failed: {result}");
|
|
}
|
|
})
|
|
.AddTo(ref d);
|
|
|
|
d.RegisterTo(destroyCancellationToken);
|
|
}
|
|
|
|
private Observable<Unit> InteractablesChanged()
|
|
{
|
|
return interactables.ObserveChanged().Select(_ => Unit.Default);
|
|
}
|
|
|
|
private Observable<Unit> PlayerMoved()
|
|
{
|
|
return Observable.EveryUpdate(UnityFrameProvider.FixedUpdate)
|
|
.Select(_ => transform.position)
|
|
.Where(pos => pos.SqrDistance(lastPosition) > sqrtMovementDelta)
|
|
.Do(pos => lastPosition = pos)
|
|
.Select(_ => Unit.Default);
|
|
}
|
|
|
|
private void HighlightInteractable(IInteractable interactable)
|
|
{
|
|
if (interactable == null) { return; }
|
|
|
|
if (!interactable.TryGetComponent(out Outline outline))
|
|
{
|
|
outline = interactable.gameObject.AddComponent<Outline>();
|
|
|
|
outline.OutlineColor = outlineColor;
|
|
outline.OutlineWidth = outlineWidth;
|
|
outline.OutlineMode = outlineMode;
|
|
}
|
|
|
|
outline.enabled = true;
|
|
}
|
|
|
|
private void UnhighlightInteractable(IInteractable interactable)
|
|
{
|
|
if (interactable == null) { return; }
|
|
|
|
if (interactable.TryGetComponent(out Outline outline))
|
|
{
|
|
outline.enabled = false;
|
|
}
|
|
}
|
|
}
|
|
} |