You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1271 lines
35 KiB
1271 lines
35 KiB
|
1 month ago
|
using System;
|
||
|
|
using System.Linq;
|
||
|
|
using System.Collections.Generic;
|
||
|
|
using UnityEngine;
|
||
|
|
using NaughtyAttributes;
|
||
|
|
|
||
|
|
namespace UDE_HAND_INTERACTION
|
||
|
|
{
|
||
|
|
public enum InteractorState
|
||
|
|
{
|
||
|
|
Normal,
|
||
|
|
Hover,
|
||
|
|
Select,
|
||
|
|
Disabled
|
||
|
|
};
|
||
|
|
|
||
|
|
public enum InteractableState
|
||
|
|
{
|
||
|
|
Normal,
|
||
|
|
Hover,
|
||
|
|
Select,
|
||
|
|
Disabled
|
||
|
|
};
|
||
|
|
|
||
|
|
public interface IActiveState
|
||
|
|
{
|
||
|
|
bool Active { get; }
|
||
|
|
}
|
||
|
|
|
||
|
|
public interface IGameObjectFilter
|
||
|
|
{
|
||
|
|
bool Filter(GameObject gameObject);
|
||
|
|
}
|
||
|
|
|
||
|
|
public struct InteractorStateChangeArgs
|
||
|
|
{
|
||
|
|
public InteractorState PreviousState { get; }
|
||
|
|
public InteractorState NewState { get; }
|
||
|
|
|
||
|
|
public InteractorStateChangeArgs(InteractorState previousState, InteractorState newState)
|
||
|
|
{
|
||
|
|
PreviousState = previousState;
|
||
|
|
NewState = newState;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
public interface IInteractorView
|
||
|
|
{
|
||
|
|
int Identifier { get; }
|
||
|
|
bool HasCandidate { get; }
|
||
|
|
object CandidateProperties { get; }
|
||
|
|
|
||
|
|
bool HasInteractable { get; }
|
||
|
|
bool HasSelectedInteractable { get; }
|
||
|
|
|
||
|
|
InteractorState State { get; }
|
||
|
|
event Action<InteractorStateChangeArgs> WhenStateChanged;
|
||
|
|
event Action WhenPreprocessed;
|
||
|
|
event Action WhenProcessed;
|
||
|
|
event Action WhenPostprocessed;
|
||
|
|
}
|
||
|
|
|
||
|
|
public interface IUpdateDriver
|
||
|
|
{
|
||
|
|
bool IsRootDriver { get; set; }
|
||
|
|
void Drive();
|
||
|
|
}
|
||
|
|
|
||
|
|
public interface IInteractor : IInteractorView, IUpdateDriver
|
||
|
|
{
|
||
|
|
|
||
|
|
void Preprocess();
|
||
|
|
void Process();
|
||
|
|
void Postprocess();
|
||
|
|
|
||
|
|
void ProcessCandidate();
|
||
|
|
void Enable();
|
||
|
|
void Disable();
|
||
|
|
void Hover();
|
||
|
|
void Unhover();
|
||
|
|
void Select();
|
||
|
|
void Unselect();
|
||
|
|
|
||
|
|
bool ShouldHover { get; }
|
||
|
|
bool ShouldUnhover { get; }
|
||
|
|
bool ShouldSelect { get; }
|
||
|
|
bool ShouldUnselect { get; }
|
||
|
|
}
|
||
|
|
|
||
|
|
public interface MAction<out T>
|
||
|
|
{
|
||
|
|
event Action<T> Action;
|
||
|
|
}
|
||
|
|
|
||
|
|
public class MultiAction<T> : MAction<T>
|
||
|
|
{
|
||
|
|
protected HashSet<Action<T>> actions = new HashSet<Action<T>>();
|
||
|
|
|
||
|
|
public event Action<T> Action
|
||
|
|
{
|
||
|
|
add
|
||
|
|
{
|
||
|
|
actions.Add(value);
|
||
|
|
}
|
||
|
|
remove
|
||
|
|
{
|
||
|
|
actions.Remove(value);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Invoke(T t)
|
||
|
|
{
|
||
|
|
foreach (Action<T> action in actions)
|
||
|
|
{
|
||
|
|
action(t);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
public class UniqueIdentifier
|
||
|
|
{
|
||
|
|
public int ID { get; private set; }
|
||
|
|
|
||
|
|
|
||
|
|
private UniqueIdentifier(int identifier)
|
||
|
|
{
|
||
|
|
ID = identifier;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static System.Random Random = new();
|
||
|
|
private static HashSet<int> _identifierSet = new();
|
||
|
|
|
||
|
|
public static UniqueIdentifier Generate()
|
||
|
|
{
|
||
|
|
while (true)
|
||
|
|
{
|
||
|
|
int identifier = Random.Next(Int32.MaxValue);
|
||
|
|
if (_identifierSet.Contains(identifier)) continue;
|
||
|
|
_identifierSet.Add(identifier);
|
||
|
|
return new UniqueIdentifier(identifier);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public static void Release(UniqueIdentifier identifier)
|
||
|
|
{
|
||
|
|
_identifierSet.Remove(identifier.ID);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public abstract class Interactor<interactor, interactable> : MonoBehaviour, IInteractor
|
||
|
|
where interactor : Interactor<interactor, interactable>
|
||
|
|
where interactable : Interactable<interactor, interactable>
|
||
|
|
{
|
||
|
|
private Func<interactable> _computeCandidateOverride;
|
||
|
|
private bool _clearComputeCandidateOverrideOnSelect = false;
|
||
|
|
private Func<bool> _computeShouldSelectOverride;
|
||
|
|
private bool _clearComputeShouldSelectOverrideOnSelect = false;
|
||
|
|
private Func<bool> _computeShouldUnselectOverride;
|
||
|
|
private bool _clearComputeShouldUnselectOverrideOnUnselect;
|
||
|
|
|
||
|
|
protected virtual void DoPreprocess() { }
|
||
|
|
protected virtual void DoNormalUpdate() { }
|
||
|
|
protected virtual void DoHoverUpdate() { }
|
||
|
|
protected virtual void DoSelectUpdate() { }
|
||
|
|
protected virtual void DoPostprocess() { }
|
||
|
|
|
||
|
|
public virtual bool ShouldHover
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
if (State != InteractorState.Normal)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return HasCandidate || ComputeShouldSelect();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public virtual bool ShouldUnhover
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
if (State != InteractorState.Hover)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return _interactable != _candidate || _candidate == null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public bool ShouldSelect
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
if (State != InteractorState.Hover)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_computeShouldSelectOverride != null)
|
||
|
|
{
|
||
|
|
return _computeShouldSelectOverride.Invoke();
|
||
|
|
}
|
||
|
|
|
||
|
|
return _candidate == _interactable && ComputeShouldSelect();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public bool ShouldUnselect
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
if (State != InteractorState.Select)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_computeShouldUnselectOverride != null)
|
||
|
|
{
|
||
|
|
return _computeShouldUnselectOverride.Invoke();
|
||
|
|
}
|
||
|
|
|
||
|
|
return ComputeShouldUnselect();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
protected virtual bool ComputeShouldSelect()
|
||
|
|
{
|
||
|
|
return QueuedSelect;
|
||
|
|
}
|
||
|
|
|
||
|
|
protected virtual bool ComputeShouldUnselect()
|
||
|
|
{
|
||
|
|
return QueuedUnselect;
|
||
|
|
}
|
||
|
|
|
||
|
|
public interface ISelector
|
||
|
|
{
|
||
|
|
event Action WhenSelected;
|
||
|
|
event Action WhenUnselected;
|
||
|
|
}
|
||
|
|
|
||
|
|
private InteractorState _state = InteractorState.Normal;
|
||
|
|
public event Action<InteractorStateChangeArgs> WhenStateChanged = delegate { };
|
||
|
|
public event Action WhenPreprocessed = delegate { };
|
||
|
|
public event Action WhenProcessed = delegate { };
|
||
|
|
public event Action WhenPostprocessed = delegate { };
|
||
|
|
|
||
|
|
private ISelector _selector = null;
|
||
|
|
|
||
|
|
[SerializeField, Label("Check Rate Per Frame")]
|
||
|
|
private int _maxIterationsPerFrame = 3;
|
||
|
|
public int MaxIterationsPerFrame
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
return _maxIterationsPerFrame;
|
||
|
|
}
|
||
|
|
set
|
||
|
|
{
|
||
|
|
_maxIterationsPerFrame = value;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private Queue<bool> _selectorQueue = new();
|
||
|
|
private bool QueuedSelect => _selectorQueue.Count > 0 && _selectorQueue.Peek();
|
||
|
|
private bool QueuedUnselect => _selectorQueue.Count > 0 && !_selectorQueue.Peek();
|
||
|
|
|
||
|
|
public InteractorState State
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
return _state;
|
||
|
|
}
|
||
|
|
private set
|
||
|
|
{
|
||
|
|
if (_state == value)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
InteractorState previousState = _state;
|
||
|
|
_state = value;
|
||
|
|
|
||
|
|
WhenStateChanged(new InteractorStateChangeArgs(previousState, _state));
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
protected interactable _candidate;
|
||
|
|
protected interactable _interactable;
|
||
|
|
protected interactable _selectedInteractable;
|
||
|
|
|
||
|
|
public virtual object CandidateProperties
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public interactable Interactable => _interactable;
|
||
|
|
public bool HasCandidate => _candidate != null;
|
||
|
|
public bool HasInteractable => _interactable != null;
|
||
|
|
public bool HasSelectedInteractable => _selectedInteractable != null;
|
||
|
|
|
||
|
|
protected virtual void InteractableSelected(interactable interactable) { }
|
||
|
|
|
||
|
|
|
||
|
|
private UniqueIdentifier _identifier;
|
||
|
|
public int Identifier => _identifier.ID;
|
||
|
|
|
||
|
|
protected bool _started;
|
||
|
|
|
||
|
|
protected virtual void Awake()
|
||
|
|
{
|
||
|
|
_identifier = UniqueIdentifier.Generate();
|
||
|
|
}
|
||
|
|
|
||
|
|
protected virtual void Start()
|
||
|
|
{
|
||
|
|
this.BeginStart(ref _started);
|
||
|
|
this.EndStart(ref _started);
|
||
|
|
}
|
||
|
|
|
||
|
|
protected virtual void OnEnable()
|
||
|
|
{
|
||
|
|
if (_started)
|
||
|
|
{
|
||
|
|
if (_selector != null)
|
||
|
|
{
|
||
|
|
_selectorQueue.Clear();
|
||
|
|
_selector.WhenSelected += HandleSelected;
|
||
|
|
_selector.WhenUnselected += HandleUnselected;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
protected virtual void OnDisable()
|
||
|
|
{
|
||
|
|
if (_started)
|
||
|
|
{
|
||
|
|
if (_selector != null)
|
||
|
|
{
|
||
|
|
_selector.WhenSelected -= HandleSelected;
|
||
|
|
_selector.WhenUnselected -= HandleUnselected;
|
||
|
|
}
|
||
|
|
Disable();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
protected virtual void OnDestroy()
|
||
|
|
{
|
||
|
|
UniqueIdentifier.Release(_identifier);
|
||
|
|
}
|
||
|
|
|
||
|
|
public virtual void SetComputeCandidateOverride(Func<interactable> computeCandidate,
|
||
|
|
bool shouldClearOverrideOnSelect = true)
|
||
|
|
{
|
||
|
|
_computeCandidateOverride = computeCandidate;
|
||
|
|
_clearComputeCandidateOverrideOnSelect = shouldClearOverrideOnSelect;
|
||
|
|
}
|
||
|
|
|
||
|
|
public virtual void ClearComputeCandidateOverride()
|
||
|
|
{
|
||
|
|
_computeCandidateOverride = null;
|
||
|
|
_clearComputeCandidateOverrideOnSelect = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
public virtual void SetComputeShouldSelectOverride(Func<bool> computeShouldSelect,
|
||
|
|
bool clearOverrideOnSelect = true)
|
||
|
|
{
|
||
|
|
_computeShouldSelectOverride = computeShouldSelect;
|
||
|
|
_clearComputeShouldSelectOverrideOnSelect = clearOverrideOnSelect;
|
||
|
|
}
|
||
|
|
|
||
|
|
public virtual void ClearComputeShouldSelectOverride()
|
||
|
|
{
|
||
|
|
_computeShouldSelectOverride = null;
|
||
|
|
_clearComputeShouldSelectOverrideOnSelect = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
public virtual void SetComputeShouldUnselectOverride(Func<bool> computeShouldUnselect,
|
||
|
|
bool clearOverrideOnUnselect = true)
|
||
|
|
{
|
||
|
|
_computeShouldUnselectOverride = computeShouldUnselect;
|
||
|
|
_clearComputeShouldUnselectOverrideOnUnselect = clearOverrideOnUnselect;
|
||
|
|
}
|
||
|
|
|
||
|
|
public virtual void ClearComputeShouldUnselectOverride()
|
||
|
|
{
|
||
|
|
_computeShouldUnselectOverride = null;
|
||
|
|
_clearComputeShouldUnselectOverrideOnUnselect = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Preprocess()
|
||
|
|
{
|
||
|
|
DoPreprocess();
|
||
|
|
WhenPreprocessed();
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Process()
|
||
|
|
{
|
||
|
|
switch (State)
|
||
|
|
{
|
||
|
|
case InteractorState.Normal:
|
||
|
|
DoNormalUpdate();
|
||
|
|
break;
|
||
|
|
case InteractorState.Hover:
|
||
|
|
DoHoverUpdate();
|
||
|
|
break;
|
||
|
|
case InteractorState.Select:
|
||
|
|
DoSelectUpdate();
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
WhenProcessed();
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Postprocess()
|
||
|
|
{
|
||
|
|
_selectorQueue.Clear();
|
||
|
|
DoPostprocess();
|
||
|
|
WhenPostprocessed();
|
||
|
|
}
|
||
|
|
|
||
|
|
public virtual void ProcessCandidate()
|
||
|
|
{
|
||
|
|
_candidate = null;
|
||
|
|
|
||
|
|
if (_computeCandidateOverride != null)
|
||
|
|
{
|
||
|
|
_candidate = _computeCandidateOverride.Invoke();
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
_candidate = ComputeCandidate();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public void InteractableChangesUpdate()
|
||
|
|
{
|
||
|
|
if (_selectedInteractable != null &&
|
||
|
|
!_selectedInteractable.HasSelectingInteractor(this as interactor))
|
||
|
|
{
|
||
|
|
UnselectInteractable();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_interactable != null &&
|
||
|
|
!_interactable.HasInteractor(this as interactor))
|
||
|
|
{
|
||
|
|
UnsetInteractable();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Hover()
|
||
|
|
{
|
||
|
|
if (State != InteractorState.Normal)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
SetInteractable(_candidate);
|
||
|
|
State = InteractorState.Hover;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Unhover()
|
||
|
|
{
|
||
|
|
if (State != InteractorState.Hover)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
UnsetInteractable();
|
||
|
|
State = InteractorState.Normal;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
public virtual void Select()
|
||
|
|
{
|
||
|
|
if (State != InteractorState.Hover)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_clearComputeCandidateOverrideOnSelect)
|
||
|
|
{
|
||
|
|
ClearComputeCandidateOverride();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_clearComputeShouldSelectOverrideOnSelect)
|
||
|
|
{
|
||
|
|
ClearComputeShouldSelectOverride();
|
||
|
|
}
|
||
|
|
|
||
|
|
while (QueuedSelect)
|
||
|
|
{
|
||
|
|
_selectorQueue.Dequeue();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (Interactable != null)
|
||
|
|
{
|
||
|
|
SelectInteractable(Interactable);
|
||
|
|
}
|
||
|
|
|
||
|
|
State = InteractorState.Select;
|
||
|
|
}
|
||
|
|
|
||
|
|
public virtual void Unselect()
|
||
|
|
{
|
||
|
|
if (State != InteractorState.Select)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (_clearComputeShouldUnselectOverrideOnUnselect)
|
||
|
|
{
|
||
|
|
ClearComputeShouldUnselectOverride();
|
||
|
|
}
|
||
|
|
while (QueuedUnselect)
|
||
|
|
{
|
||
|
|
_selectorQueue.Dequeue();
|
||
|
|
}
|
||
|
|
UnselectInteractable();
|
||
|
|
|
||
|
|
State = InteractorState.Hover;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
protected abstract interactable ComputeCandidate();
|
||
|
|
|
||
|
|
private void SetInteractable(interactable interactable)
|
||
|
|
{
|
||
|
|
if (_interactable == interactable)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
UnsetInteractable();
|
||
|
|
_interactable = interactable;
|
||
|
|
interactable.AddInteractor(this as interactor);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void UnsetInteractable()
|
||
|
|
{
|
||
|
|
interactable interactable = _interactable;
|
||
|
|
if (interactable == null)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
_interactable = null;
|
||
|
|
interactable.RemoveInteractor(this as interactor);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void SelectInteractable(interactable interactable)
|
||
|
|
{
|
||
|
|
Unselect();
|
||
|
|
_selectedInteractable = interactable;
|
||
|
|
interactable.AddSelectingInteractor(this as interactor);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void UnselectInteractable()
|
||
|
|
{
|
||
|
|
interactable interactable = _selectedInteractable;
|
||
|
|
|
||
|
|
if (interactable == null)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
_selectedInteractable = null;
|
||
|
|
interactable.RemoveSelectingInteractor(this as interactor);
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Enable()
|
||
|
|
{
|
||
|
|
if (State == InteractorState.Disabled)
|
||
|
|
{
|
||
|
|
State = InteractorState.Normal;
|
||
|
|
HandleEnabled();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Disable()
|
||
|
|
{
|
||
|
|
if (State == InteractorState.Disabled)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
HandleDisabled();
|
||
|
|
|
||
|
|
if (State == InteractorState.Select)
|
||
|
|
{
|
||
|
|
UnselectInteractable();
|
||
|
|
State = InteractorState.Hover;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (State == InteractorState.Hover)
|
||
|
|
{
|
||
|
|
UnsetInteractable();
|
||
|
|
State = InteractorState.Normal;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (State == InteractorState.Normal)
|
||
|
|
{
|
||
|
|
State = InteractorState.Disabled;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
protected virtual void HandleEnabled() { }
|
||
|
|
protected virtual void HandleDisabled() { }
|
||
|
|
|
||
|
|
protected virtual void HandleSelected()
|
||
|
|
{
|
||
|
|
_selectorQueue.Enqueue(true);
|
||
|
|
}
|
||
|
|
|
||
|
|
protected virtual void HandleUnselected()
|
||
|
|
{
|
||
|
|
_selectorQueue.Enqueue(false);
|
||
|
|
}
|
||
|
|
|
||
|
|
public bool IsRootDriver { get; set; } = true;
|
||
|
|
|
||
|
|
protected virtual void Update()
|
||
|
|
{
|
||
|
|
if (!IsRootDriver)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Drive();
|
||
|
|
}
|
||
|
|
|
||
|
|
public virtual void Drive()
|
||
|
|
{
|
||
|
|
Preprocess();
|
||
|
|
Enable();
|
||
|
|
|
||
|
|
InteractorState previousState = State;
|
||
|
|
for (int i = 0; i < MaxIterationsPerFrame; i++)
|
||
|
|
{
|
||
|
|
if (State == InteractorState.Normal ||
|
||
|
|
(State == InteractorState.Hover && previousState != InteractorState.Normal))
|
||
|
|
{
|
||
|
|
ProcessCandidate();
|
||
|
|
}
|
||
|
|
previousState = State;
|
||
|
|
|
||
|
|
Process();
|
||
|
|
|
||
|
|
if (State == InteractorState.Disabled)
|
||
|
|
{
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (State == InteractorState.Normal)
|
||
|
|
{
|
||
|
|
if (ShouldHover)
|
||
|
|
{
|
||
|
|
Hover();
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (State == InteractorState.Hover)
|
||
|
|
{
|
||
|
|
if (ShouldSelect)
|
||
|
|
{
|
||
|
|
Select();
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (ShouldUnhover)
|
||
|
|
{
|
||
|
|
Unhover();
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (State == InteractorState.Select)
|
||
|
|
{
|
||
|
|
if (ShouldUnselect)
|
||
|
|
{
|
||
|
|
Unselect();
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Postprocess();
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
public struct InteractableStateChangeArgs
|
||
|
|
{
|
||
|
|
public InteractableState PreviousState { get; }
|
||
|
|
public InteractableState NewState { get; }
|
||
|
|
|
||
|
|
public InteractableStateChangeArgs(
|
||
|
|
InteractableState previousState,
|
||
|
|
InteractableState newState)
|
||
|
|
{
|
||
|
|
PreviousState = previousState;
|
||
|
|
NewState = newState;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public interface IInteractableView
|
||
|
|
{
|
||
|
|
InteractableState State { get; }
|
||
|
|
event Action<InteractableStateChangeArgs> WhenStateChanged;
|
||
|
|
|
||
|
|
int MaxInteractors { get; }
|
||
|
|
int MaxSelectingInteractors { get; }
|
||
|
|
|
||
|
|
IEnumerable<IInteractorView> InteractorViews { get; }
|
||
|
|
IEnumerable<IInteractorView> SelectingInteractorViews { get; }
|
||
|
|
|
||
|
|
event Action<IInteractorView> WhenInteractorViewAdded;
|
||
|
|
event Action<IInteractorView> WhenInteractorViewRemoved;
|
||
|
|
event Action<IInteractorView> WhenSelectingInteractorViewAdded;
|
||
|
|
event Action<IInteractorView> WhenSelectingInteractorViewRemoved;
|
||
|
|
}
|
||
|
|
|
||
|
|
public interface IInteractable : IInteractableView
|
||
|
|
{
|
||
|
|
void Enable();
|
||
|
|
void Disable();
|
||
|
|
new int MaxInteractors { get; set; }
|
||
|
|
new int MaxSelectingInteractors { get; set; }
|
||
|
|
void RemoveInteractorByIdentifier(int id);
|
||
|
|
}
|
||
|
|
|
||
|
|
public class InteractableRegistry<interactor, interactable>
|
||
|
|
where interactor : Interactor<interactor, interactable>
|
||
|
|
where interactable : Interactable<interactor, interactable>
|
||
|
|
{
|
||
|
|
public struct InteractableSet
|
||
|
|
{
|
||
|
|
private readonly IReadOnlyList<interactable> _data;
|
||
|
|
private readonly ISet<interactable> _onlyInclude;
|
||
|
|
private readonly interactor _testAgainst;
|
||
|
|
|
||
|
|
public InteractableSet( ISet<interactable> onlyInclude, interactor testAgainst)
|
||
|
|
{
|
||
|
|
_data = _interactables;
|
||
|
|
_onlyInclude = onlyInclude;
|
||
|
|
_testAgainst = testAgainst;
|
||
|
|
}
|
||
|
|
|
||
|
|
public Enumerator GetEnumerator()
|
||
|
|
{
|
||
|
|
return new Enumerator(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
private bool Include(interactable interactable)
|
||
|
|
{
|
||
|
|
if (_onlyInclude != null && !_onlyInclude.Contains(interactable))
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_testAgainst != null && !interactable.CanBeSelectedBy(_testAgainst))
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
public struct Enumerator
|
||
|
|
{
|
||
|
|
private readonly InteractableSet _set;
|
||
|
|
private int _position;
|
||
|
|
|
||
|
|
private IReadOnlyList<interactable> Data => _set._data;
|
||
|
|
|
||
|
|
public Enumerator(in InteractableSet set)
|
||
|
|
{
|
||
|
|
_set = set;
|
||
|
|
_position = -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
public interactable Current
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
if (Data == null || _position < 0)
|
||
|
|
{
|
||
|
|
throw new System.InvalidOperationException();
|
||
|
|
}
|
||
|
|
return Data[_position];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public bool MoveNext()
|
||
|
|
{
|
||
|
|
if (Data == null)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
do
|
||
|
|
{
|
||
|
|
_position++;
|
||
|
|
} while (_position < Data.Count &&
|
||
|
|
!_set.Include(Data[_position]));
|
||
|
|
|
||
|
|
return _position < Data.Count;
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static List<interactable> _interactables;
|
||
|
|
|
||
|
|
public InteractableRegistry()
|
||
|
|
{
|
||
|
|
_interactables = new List<interactable>();
|
||
|
|
}
|
||
|
|
|
||
|
|
public virtual void Register(interactable interactable)
|
||
|
|
{
|
||
|
|
_interactables.Add(interactable);
|
||
|
|
}
|
||
|
|
|
||
|
|
public virtual void Unregister(interactable interactable)
|
||
|
|
{
|
||
|
|
_interactables.Remove(interactable);
|
||
|
|
}
|
||
|
|
|
||
|
|
public virtual InteractableSet List(interactor interactor)
|
||
|
|
{
|
||
|
|
return new InteractableSet(null, interactor);
|
||
|
|
}
|
||
|
|
|
||
|
|
public virtual InteractableSet List()
|
||
|
|
{
|
||
|
|
return new InteractableSet(null, null);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
public abstract class Interactable<interactor, interactable> : MonoBehaviour, IInteractable
|
||
|
|
where interactor : Interactor<interactor, interactable>
|
||
|
|
where interactable : Interactable<interactor, interactable>
|
||
|
|
{
|
||
|
|
|
||
|
|
private List<UnityEngine.Object> _interactorFilters = new();
|
||
|
|
private List<IGameObjectFilter> InteractorFilters = null;
|
||
|
|
private int _maxInteractors = -1;
|
||
|
|
private int _maxSelectingInteractors = -1;
|
||
|
|
private UnityEngine.Object _data = null;
|
||
|
|
public object Data { get; protected set; } = null;
|
||
|
|
protected bool _started = false;
|
||
|
|
|
||
|
|
#region Properties
|
||
|
|
public int MaxInteractors
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
return _maxInteractors;
|
||
|
|
}
|
||
|
|
set
|
||
|
|
{
|
||
|
|
_maxInteractors = value;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public int MaxSelectingInteractors
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
return _maxSelectingInteractors;
|
||
|
|
}
|
||
|
|
set
|
||
|
|
{
|
||
|
|
_maxSelectingInteractors = value;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
#endregion
|
||
|
|
|
||
|
|
|
||
|
|
public IEnumerable<IInteractorView> InteractorViews => _interactors.Cast<IInteractorView>();
|
||
|
|
public IEnumerable<IInteractorView> SelectingInteractorViews => _selectingInteractors.Cast<IInteractorView>();
|
||
|
|
|
||
|
|
private EnumerableHashSet<interactor> _interactors = new();
|
||
|
|
private EnumerableHashSet<interactor> _selectingInteractors = new();
|
||
|
|
|
||
|
|
private InteractableState _state = InteractableState.Disabled;
|
||
|
|
public event Action<InteractableStateChangeArgs> WhenStateChanged = delegate { };
|
||
|
|
|
||
|
|
public event Action<IInteractorView> WhenInteractorViewAdded = delegate { };
|
||
|
|
public event Action<IInteractorView> WhenInteractorViewRemoved = delegate { };
|
||
|
|
public event Action<IInteractorView> WhenSelectingInteractorViewAdded = delegate { };
|
||
|
|
public event Action<IInteractorView> WhenSelectingInteractorViewRemoved = delegate { };
|
||
|
|
|
||
|
|
private MultiAction<interactor> _whenInteractorAdded = new();
|
||
|
|
private MultiAction<interactor> _whenInteractorRemoved = new();
|
||
|
|
private MultiAction<interactor> _whenSelectingInteractorAdded = new();
|
||
|
|
private MultiAction<interactor> _whenSelectingInteractorRemoved = new();
|
||
|
|
public MAction<interactor> WhenInteractorAdded => _whenInteractorAdded;
|
||
|
|
public MAction<interactor> WhenInteractorRemoved => _whenInteractorRemoved;
|
||
|
|
|
||
|
|
public InteractableState State
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
return _state;
|
||
|
|
}
|
||
|
|
private set
|
||
|
|
{
|
||
|
|
if (_state == value) return;
|
||
|
|
InteractableState previousState = _state;
|
||
|
|
_state = value;
|
||
|
|
WhenStateChanged(new InteractableStateChangeArgs(previousState, _state));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static InteractableRegistry<interactor, interactable> _registry = new();
|
||
|
|
|
||
|
|
public static InteractableRegistry<interactor, interactable> Registry => _registry;
|
||
|
|
|
||
|
|
protected virtual void InteractorAdded(interactor interactor)
|
||
|
|
{
|
||
|
|
WhenInteractorViewAdded(interactor);
|
||
|
|
_whenInteractorAdded.Invoke(interactor);
|
||
|
|
}
|
||
|
|
protected virtual void InteractorRemoved(interactor interactor)
|
||
|
|
{
|
||
|
|
WhenInteractorViewRemoved(interactor);
|
||
|
|
_whenInteractorRemoved.Invoke(interactor);
|
||
|
|
}
|
||
|
|
|
||
|
|
protected virtual void SelectingInteractorAdded(interactor interactor)
|
||
|
|
{
|
||
|
|
WhenSelectingInteractorViewAdded(interactor);
|
||
|
|
_whenSelectingInteractorAdded.Invoke(interactor);
|
||
|
|
}
|
||
|
|
protected virtual void SelectingInteractorRemoved(interactor interactor)
|
||
|
|
{
|
||
|
|
WhenSelectingInteractorViewRemoved(interactor);
|
||
|
|
_whenSelectingInteractorRemoved.Invoke(interactor);
|
||
|
|
}
|
||
|
|
|
||
|
|
public IEnumerableHashSet<interactor> Interactors => _interactors;
|
||
|
|
|
||
|
|
|
||
|
|
public void AddInteractor(interactor interactor)
|
||
|
|
{
|
||
|
|
_interactors.Add(interactor);
|
||
|
|
InteractorAdded(interactor);
|
||
|
|
UpdateInteractableState();
|
||
|
|
}
|
||
|
|
|
||
|
|
public void RemoveInteractor(interactor interactor)
|
||
|
|
{
|
||
|
|
if (!_interactors.Remove(interactor))
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
interactor.InteractableChangesUpdate();
|
||
|
|
InteractorRemoved(interactor);
|
||
|
|
UpdateInteractableState();
|
||
|
|
}
|
||
|
|
|
||
|
|
public void AddSelectingInteractor(interactor interactor)
|
||
|
|
{
|
||
|
|
_selectingInteractors.Add(interactor);
|
||
|
|
SelectingInteractorAdded(interactor);
|
||
|
|
UpdateInteractableState();
|
||
|
|
}
|
||
|
|
|
||
|
|
public void RemoveSelectingInteractor(interactor interactor)
|
||
|
|
{
|
||
|
|
if (!_selectingInteractors.Remove(interactor))
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
interactor.InteractableChangesUpdate();
|
||
|
|
SelectingInteractorRemoved(interactor);
|
||
|
|
UpdateInteractableState();
|
||
|
|
}
|
||
|
|
|
||
|
|
private void UpdateInteractableState()
|
||
|
|
{
|
||
|
|
if (State == InteractableState.Disabled) return;
|
||
|
|
|
||
|
|
if (_selectingInteractors.Count > 0)
|
||
|
|
{
|
||
|
|
State = InteractableState.Select;
|
||
|
|
}
|
||
|
|
else if (_interactors.Count > 0)
|
||
|
|
{
|
||
|
|
State = InteractableState.Hover;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
State = InteractableState.Normal;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public bool CanBeSelectedBy(interactor interactor)
|
||
|
|
{
|
||
|
|
if (State == InteractableState.Disabled)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (MaxSelectingInteractors >= 0 &&
|
||
|
|
_selectingInteractors.Count == MaxSelectingInteractors)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (MaxInteractors >= 0 &&
|
||
|
|
_interactors.Count == MaxInteractors &&
|
||
|
|
!_interactors.Contains(interactor))
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (InteractorFilters == null)
|
||
|
|
{
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach (IGameObjectFilter interactorFilter in InteractorFilters)
|
||
|
|
{
|
||
|
|
if (!interactorFilter.Filter(interactor.gameObject))
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
public bool HasInteractor(interactor interactor)
|
||
|
|
{
|
||
|
|
return _interactors.Contains(interactor);
|
||
|
|
}
|
||
|
|
|
||
|
|
public bool HasSelectingInteractor(interactor interactor)
|
||
|
|
{
|
||
|
|
return _selectingInteractors.Contains(interactor);
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Enable()
|
||
|
|
{
|
||
|
|
if (State != InteractableState.Disabled)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_started)
|
||
|
|
{
|
||
|
|
_registry.Register((interactable)this);
|
||
|
|
State = InteractableState.Normal;
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Disable()
|
||
|
|
{
|
||
|
|
if (State == InteractableState.Disabled)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_started)
|
||
|
|
{
|
||
|
|
List<interactor> selectingInteractorsCopy = new List<interactor>(_selectingInteractors);
|
||
|
|
foreach (interactor selectingInteractor in selectingInteractorsCopy)
|
||
|
|
{
|
||
|
|
RemoveSelectingInteractor(selectingInteractor);
|
||
|
|
}
|
||
|
|
|
||
|
|
List<interactor> interactorsCopy = new List<interactor>(_interactors);
|
||
|
|
foreach (interactor interactor in interactorsCopy)
|
||
|
|
{
|
||
|
|
RemoveInteractor(interactor);
|
||
|
|
}
|
||
|
|
|
||
|
|
_registry.Unregister((interactable)this);
|
||
|
|
State = InteractableState.Disabled;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public void RemoveInteractorByIdentifier(int id)
|
||
|
|
{
|
||
|
|
interactor foundInteractor = null;
|
||
|
|
foreach (interactor selectingInteractor in _selectingInteractors)
|
||
|
|
{
|
||
|
|
if (selectingInteractor.Identifier == id)
|
||
|
|
{
|
||
|
|
foundInteractor = selectingInteractor;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (foundInteractor != null)
|
||
|
|
{
|
||
|
|
RemoveSelectingInteractor(foundInteractor);
|
||
|
|
}
|
||
|
|
|
||
|
|
foundInteractor = null;
|
||
|
|
|
||
|
|
foreach (interactor interactor in _interactors)
|
||
|
|
{
|
||
|
|
if (interactor.Identifier == id)
|
||
|
|
{
|
||
|
|
foundInteractor = interactor;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (foundInteractor == null)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
RemoveInteractor(foundInteractor);
|
||
|
|
}
|
||
|
|
|
||
|
|
protected virtual void Awake()
|
||
|
|
{
|
||
|
|
InteractorFilters = _interactorFilters.ConvertAll(mono => mono as IGameObjectFilter);
|
||
|
|
}
|
||
|
|
|
||
|
|
protected virtual void Start()
|
||
|
|
{
|
||
|
|
this.BeginStart(ref _started);
|
||
|
|
|
||
|
|
if (Data == null)
|
||
|
|
{
|
||
|
|
_data = this;
|
||
|
|
Data = _data;
|
||
|
|
}
|
||
|
|
this.EndStart(ref _started);
|
||
|
|
}
|
||
|
|
|
||
|
|
protected virtual void OnEnable()
|
||
|
|
{
|
||
|
|
Enable();
|
||
|
|
}
|
||
|
|
|
||
|
|
protected virtual void OnDisable()
|
||
|
|
{
|
||
|
|
Disable();
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
public interface IEnumerableHashSet<T> : IEnumerable<T>
|
||
|
|
{
|
||
|
|
new HashSet<T>.Enumerator GetEnumerator();
|
||
|
|
}
|
||
|
|
|
||
|
|
public class EnumerableHashSet<T> : HashSet<T>, IEnumerableHashSet<T>
|
||
|
|
{
|
||
|
|
public EnumerableHashSet() : base() { }
|
||
|
|
public EnumerableHashSet(IEnumerable<T> values) : base(values) { }
|
||
|
|
}
|
||
|
|
|
||
|
|
public static class MonoBehaviourStartExtensions
|
||
|
|
{
|
||
|
|
public static void BeginStart(this MonoBehaviour monoBehaviour, ref bool started, Action baseStart = null)
|
||
|
|
{
|
||
|
|
if (!started)
|
||
|
|
{
|
||
|
|
monoBehaviour.enabled = false;
|
||
|
|
started = true;
|
||
|
|
baseStart?.Invoke();
|
||
|
|
started = false;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
baseStart?.Invoke();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public static void EndStart(this MonoBehaviour monoBehaviour, ref bool started)
|
||
|
|
{
|
||
|
|
if (!started)
|
||
|
|
{
|
||
|
|
started = true;
|
||
|
|
monoBehaviour.enabled = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
[Serializable]
|
||
|
|
public class ProgressCurve
|
||
|
|
{
|
||
|
|
[SerializeField]
|
||
|
|
private AnimationCurve _animationCurve;
|
||
|
|
|
||
|
|
[SerializeField]
|
||
|
|
private float _animationLength;
|
||
|
|
|
||
|
|
private Func<float> _timeProvider = () => Time.time;
|
||
|
|
|
||
|
|
private float _animationStartTime;
|
||
|
|
|
||
|
|
public ProgressCurve()
|
||
|
|
{
|
||
|
|
_animationCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
|
||
|
|
_animationLength = 1.0f;
|
||
|
|
}
|
||
|
|
|
||
|
|
public ProgressCurve(AnimationCurve animationCurve, float animationLength)
|
||
|
|
{
|
||
|
|
_animationCurve = animationCurve;
|
||
|
|
_animationLength = animationLength;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Copy(ProgressCurve other)
|
||
|
|
{
|
||
|
|
_animationCurve = other._animationCurve;
|
||
|
|
_animationLength = other._animationLength;
|
||
|
|
_animationStartTime = other._animationStartTime;
|
||
|
|
_timeProvider = other._timeProvider;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Start()
|
||
|
|
{
|
||
|
|
_animationStartTime = _timeProvider();
|
||
|
|
}
|
||
|
|
|
||
|
|
public float Progress()
|
||
|
|
{
|
||
|
|
if (_animationLength <= 0f)
|
||
|
|
{
|
||
|
|
return _animationCurve.Evaluate(1.0f);
|
||
|
|
}
|
||
|
|
|
||
|
|
float normalizedTimeProgress = Mathf.Clamp01(ProgressTime() / _animationLength);
|
||
|
|
return _animationCurve.Evaluate(normalizedTimeProgress);
|
||
|
|
}
|
||
|
|
|
||
|
|
public float ProgressTime()
|
||
|
|
{
|
||
|
|
return Mathf.Clamp(_timeProvider() - _animationStartTime, 0f, _animationLength);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
|
||
|
|
public class InterfaceAttribute : PropertyAttribute
|
||
|
|
{
|
||
|
|
public Type[] Types = null;
|
||
|
|
public string TypeFromFieldName;
|
||
|
|
|
||
|
|
public InterfaceAttribute(Type type, params Type[] types)
|
||
|
|
{
|
||
|
|
Debug.Assert(type.IsInterface, $"{type.Name} needs to be an interface.");
|
||
|
|
|
||
|
|
Types = new Type[types.Length + 1];
|
||
|
|
Types[0] = type;
|
||
|
|
for (int i = 0; i < types.Length; i++)
|
||
|
|
{
|
||
|
|
Debug.Assert(types[i].IsInterface, $"{types[i].Name} needs to be an interface.");
|
||
|
|
Types[i + 1] = types[i];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|