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.
1270 lines
35 KiB
1270 lines
35 KiB
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]; |
|
} |
|
} |
|
|
|
} |
|
}
|
|
|