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 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 { event Action Action; } public class MultiAction : MAction { protected HashSet> actions = new HashSet>(); public event Action Action { add { actions.Add(value); } remove { actions.Remove(value); } } public void Invoke(T t) { foreach (Action 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 _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 : MonoBehaviour, IInteractor where interactor : Interactor where interactable : Interactable { private Func _computeCandidateOverride; private bool _clearComputeCandidateOverrideOnSelect = false; private Func _computeShouldSelectOverride; private bool _clearComputeShouldSelectOverrideOnSelect = false; private Func _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 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 _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 computeCandidate, bool shouldClearOverrideOnSelect = true) { _computeCandidateOverride = computeCandidate; _clearComputeCandidateOverrideOnSelect = shouldClearOverrideOnSelect; } public virtual void ClearComputeCandidateOverride() { _computeCandidateOverride = null; _clearComputeCandidateOverrideOnSelect = false; } public virtual void SetComputeShouldSelectOverride(Func computeShouldSelect, bool clearOverrideOnSelect = true) { _computeShouldSelectOverride = computeShouldSelect; _clearComputeShouldSelectOverrideOnSelect = clearOverrideOnSelect; } public virtual void ClearComputeShouldSelectOverride() { _computeShouldSelectOverride = null; _clearComputeShouldSelectOverrideOnSelect = false; } public virtual void SetComputeShouldUnselectOverride(Func 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 WhenStateChanged; int MaxInteractors { get; } int MaxSelectingInteractors { get; } IEnumerable InteractorViews { get; } IEnumerable SelectingInteractorViews { get; } event Action WhenInteractorViewAdded; event Action WhenInteractorViewRemoved; event Action WhenSelectingInteractorViewAdded; event Action 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 where interactor : Interactor where interactable : Interactable { public struct InteractableSet { private readonly IReadOnlyList _data; private readonly ISet _onlyInclude; private readonly interactor _testAgainst; public InteractableSet( ISet 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 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 _interactables; public InteractableRegistry() { _interactables = new List(); } 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 : MonoBehaviour, IInteractable where interactor : Interactor where interactable : Interactable { private List _interactorFilters = new(); private List 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 InteractorViews => _interactors.Cast(); public IEnumerable SelectingInteractorViews => _selectingInteractors.Cast(); private EnumerableHashSet _interactors = new(); private EnumerableHashSet _selectingInteractors = new(); private InteractableState _state = InteractableState.Disabled; public event Action WhenStateChanged = delegate { }; public event Action WhenInteractorViewAdded = delegate { }; public event Action WhenInteractorViewRemoved = delegate { }; public event Action WhenSelectingInteractorViewAdded = delegate { }; public event Action WhenSelectingInteractorViewRemoved = delegate { }; private MultiAction _whenInteractorAdded = new(); private MultiAction _whenInteractorRemoved = new(); private MultiAction _whenSelectingInteractorAdded = new(); private MultiAction _whenSelectingInteractorRemoved = new(); public MAction WhenInteractorAdded => _whenInteractorAdded; public MAction 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 _registry = new(); public static InteractableRegistry 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 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 selectingInteractorsCopy = new List(_selectingInteractors); foreach (interactor selectingInteractor in selectingInteractorsCopy) { RemoveSelectingInteractor(selectingInteractor); } List interactorsCopy = new List(_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 : IEnumerable { new HashSet.Enumerator GetEnumerator(); } public class EnumerableHashSet : HashSet, IEnumerableHashSet { public EnumerableHashSet() : base() { } public EnumerableHashSet(IEnumerable 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 _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]; } } } }