From d053f8ee25bdb05f2e3166a17b09b46293a8a55e Mon Sep 17 00:00:00 2001 From: Steven Lai <2061455536@qq.com> Date: Fri, 29 Nov 2024 15:12:34 +0800 Subject: [PATCH] Init --- .../DragSubjectFocusTargetInteractCase.cs | 59 +++++ EasyInteractive/EasyInteractive.cs | 239 ++++++++++++++++++ EasyInteractive/GameMonoUpdater.cs | 57 +++++ EasyInteractive/ICanUseGameMonoUpdater.cs | 30 +++ EasyInteractive/IInteractCase.cs | 41 +++ EasyInteractive/IInteractable.cs | 65 +++++ EasyInteractive/InteractCaseAttribute.cs | 19 ++ 7 files changed, 510 insertions(+) create mode 100644 EasyInteractive/DragSubjectFocusTargetInteractCase.cs create mode 100644 EasyInteractive/EasyInteractive.cs create mode 100644 EasyInteractive/GameMonoUpdater.cs create mode 100644 EasyInteractive/ICanUseGameMonoUpdater.cs create mode 100644 EasyInteractive/IInteractCase.cs create mode 100644 EasyInteractive/IInteractable.cs create mode 100644 EasyInteractive/InteractCaseAttribute.cs diff --git a/EasyInteractive/DragSubjectFocusTargetInteractCase.cs b/EasyInteractive/DragSubjectFocusTargetInteractCase.cs new file mode 100644 index 0000000..6556dde --- /dev/null +++ b/EasyInteractive/DragSubjectFocusTargetInteractCase.cs @@ -0,0 +1,59 @@ +using System; + +namespace HalfDog.EasyInteractive +{ + /// + /// 拖拽主体并聚焦目标的交互情景 + /// + public abstract class DragSubjectFocusTargetInteractCase : AbstractInteractCase + { + private bool _isEnter = false; + private bool _isExit = true; + + public DragSubjectFocusTargetInteractCase(Type subject, Type target) : base(subject, target) + { + } + + protected abstract void OnExecute(IDragable subject, IFocusable target); + + public override bool Execute(IFocusable focusable, ISelectable selectable, IDragable dragable) + { + if (focusable == null || dragable == null || focusable.interactTag != target || + dragable.interactTag != subject) + { + if (_isEnter) + { + _isEnter = false; + OnExit(dragable, focusable); + _isExit = true; + } + + return false; + } + + if (_isExit) + { + _isExit = false; + OnEnter(dragable, focusable); + _isEnter = true; + } + + OnExecute(dragable, focusable); + return true; + } + + /// + /// 进入交互情景 + /// + protected virtual void OnEnter(IDragable subject, IFocusable target) + { + } + + /// + /// 退出交互情景 + /// + protected virtual void OnExit(IDragable subject, IFocusable target) + { + } + } +} diff --git a/EasyInteractive/EasyInteractive.cs b/EasyInteractive/EasyInteractive.cs new file mode 100644 index 0000000..09c54c9 --- /dev/null +++ b/EasyInteractive/EasyInteractive.cs @@ -0,0 +1,239 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using HalfDog.GameMonoUpdater; +using UnityEngine; +using UnityEngine.EventSystems; + +namespace HalfDog.EasyInteractive +{ + public class EasyInteractive : ICanUseGameMonoUpdater + { + private static EasyInteractive _instance; + + public static EasyInteractive Instance + { + get + { + if (_instance == null) + { + _instance = new EasyInteractive(); + } + return _instance; + } + } + + private IFocusable _currentFocused; + private IFocusable _previousFocused; + private ISelectable _currentSelected; + private IDragable _currentDraged; + private ISelectable _readySelect; + private IDragable _readyDrag; + private IDragable _possibleDragTarget; + private Vector3 _mouseDownPosition; + private bool _isPointerOverUI; + private Dictionary _allInteractCase = new Dictionary(); + private List _executingInteractCases = new List(); + private List _activeInteractCase = new List(); + private Stack _currentUIItemFocusOnStack = new(); + //当前激活的交互情景 + private IInteractCase _currentActiveInteractCase; + private bool _inUpdate; + + public bool InUpdate + { + get => _inUpdate; + set => _inUpdate = value; + } + + public bool isPointerOverUI => _isPointerOverUI; + public IFocusable currentFocused => _currentFocused; + public ISelectable currentSelected => _currentSelected; + public ISelectable readySelect { get => _readySelect; set => _readySelect = value; } + public IDragable currentDraged => _currentDraged; + public IDragable readyDrag { get => _readyDrag; set => _readyDrag = value; } + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] + private static void MakesureInstanceExist() + { + _instance = new EasyInteractive(); + } + + public EasyInteractive() + { + this.EnableMonoUpdater(); + //加载所有的交互情景 + Assembly assembly = typeof(EasyInteractive).Assembly; + List types = assembly.GetTypes().Where(type => typeof(IInteractCase).IsAssignableFrom(type) && !type.IsAbstract).ToList(); + for (int i = 0; i < types.Count; i++) + { + InteractCaseAttribute attribute = types[i].GetCustomAttribute(); + if (attribute != null) { + IInteractCase ic = Activator.CreateInstance(types[i], attribute.interactSubject, attribute.interactTarget) as IInteractCase; + _allInteractCase.Add(types[i],ic); + ic.enable = attribute.executeOnLoad; + _executingInteractCases.Add(ic); + } + } + } + + public void Update() + { + //当指针处于UI上时停止对场景中交互对象的操作 + if (EventSystem.current?.IsPointerOverGameObject() ?? false) + { + if (!_isPointerOverUI) + { + //ATTENTION 只有当前选择的和准备选择相同时才执行这个操作 否则会出现问题(PointerHandler先于OnFixedUpdate调用) + if (currentSelected == _readySelect) + _readySelect = null; + //ATTENTION 如果从场景转到UI上但是当前聚焦的对象没有RectTransform组件说明当前UI不是一个交互对象 + if (currentFocused != null && !(currentFocused as MonoBehaviour).TryGetComponent(out RectTransform component)) + SetCurrentFocused(null); + _isPointerOverUI = true; + } + } + else + { + if (Camera.main == null) return; + _isPointerOverUI = false; + Ray mouseRay = Camera.main.ScreenPointToRay(Input.mousePosition); + if (Physics.Raycast(mouseRay, out RaycastHit hitInfo, Mathf.Infinity)) + { + if (hitInfo.transform.TryGetComponent(out IFocusable focus)) + { + if (focus != _currentFocused && focus.enableFocus) + SetCurrentFocused(focus); + } + else + { + SetCurrentFocused(null); + } + + if (hitInfo.transform.TryGetComponent(out ISelectable selectable) && selectable.enableSelect) + _readySelect = selectable; + else + _readySelect = null; + + if (hitInfo.transform.TryGetComponent(out IDragable dragable) && dragable.enableDrag) + _readyDrag = dragable; + else + _readyDrag = null; + } + else + { + _readyDrag = null; + _readySelect = null; + //射线没有任何碰撞应该把当前聚焦的对象置空 + SetCurrentFocused(null); + } + } + + + //满足条件的InteractCase会被执行 + //这里的满足条件指的是 交互操作与交互对象类型都要一致 + IInteractCase activeCase = null; + for (int i = 0; i < _executingInteractCases.Count; i++) + { + if (_executingInteractCases[i].enable && _executingInteractCases[i].Execute(currentFocused, currentSelected, currentDraged) ) + { + activeCase = _executingInteractCases[i]; + } + } + //如果当前激活的情景更改了,则把激活的情景放到列表最前方第一个进行处理 + //这是为了在情景更改时首先执行激活情景的退出事件(如果有的话) + if (activeCase!=null && activeCase != _currentActiveInteractCase) + { + _currentActiveInteractCase = activeCase; + _executingInteractCases.Remove(_currentActiveInteractCase); + _executingInteractCases.Insert(0, _currentActiveInteractCase); + } + //Debug.Log($"【Focus:{currentFocused?.interactTag.Name}】【Select:{currentSelected?.interactTag.Name}]】【Drag:{currentDraged?.interactTag.Name}】"); + + if (Input.GetMouseButtonDown(0) && currentFocused!=null) + { + _mouseDownPosition = Input.mousePosition; + _possibleDragTarget = (currentFocused as IDragable); + } + if (Input.GetMouseButtonUp(0)) + { + if (currentDraged == null) + { + if (_readySelect != null)//Vector3.Distance(_mouseDownPosition, Input.mousePosition) < 32f && + { + if (currentSelected != _readySelect) SetCurrentSelected(_readySelect); + //再次点击选择的对象则取消选中 + else if (currentSelected == _readySelect) SetCurrentSelected(null); + } + } + else + { + SetCurrentDraged(null); + } + } + if (Input.GetMouseButton(0)) + { + if(currentDraged == null && Vector3.Distance(_mouseDownPosition, Input.mousePosition) > 16f) + { + if (_possibleDragTarget != null && _readyDrag == _possibleDragTarget) + { + //拖拽与被选中的不能是同一个 + if (_readyDrag == currentSelected) + { + SetCurrentSelected(null); + } + SetCurrentDraged(_readyDrag); + } + } + currentDraged?.ProcessDrag(); + } + //Debug.Log(currentFocused?.interactTag.Name ?? "Null"); + } + public void Reset() + { + SetCurrentDraged(null); + SetCurrentSelected(null); + SetCurrentFocused(null); + } + public void SetCurrentFocused(IFocusable focusable,bool isUIItem = false) + { + _previousFocused = _currentFocused; + _currentFocused?.EndFocus(); + _currentFocused = focusable; + _currentFocused?.OnFocus(); + } + public void SetCurrentSelected(ISelectable selectable) + { + _currentSelected?.EndSelect(); + _currentSelected = selectable; + _currentSelected?.OnSelect(); + } + public void SetCurrentDraged(IDragable dragable) + { + _currentDraged?.EndDrag(); + _currentDraged = dragable; + _currentDraged?.OnDrag(); + } + + public void EnableInteractCase() where T : IInteractCase + { + Type type = typeof(T); + if (_allInteractCase.ContainsKey(type)) + { + _allInteractCase[type].enable = true; + } + } + public void DisableInteractCase() where T : IInteractCase + { + Type type = typeof(T); + if (_allInteractCase.ContainsKey(type)) + { + _allInteractCase[type].enable = false; + } + } + + public void FixedUpdate() { } + public void LateUpdate() { } + } +} diff --git a/EasyInteractive/GameMonoUpdater.cs b/EasyInteractive/GameMonoUpdater.cs new file mode 100644 index 0000000..e73b886 --- /dev/null +++ b/EasyInteractive/GameMonoUpdater.cs @@ -0,0 +1,57 @@ +using System; +using UnityEngine; + +namespace HalfDog.GameMonoUpdater +{ + public class GameMonoUpdater : MonoBehaviour + { + private static GameMonoUpdater _instance = null; + public event Action updateAction; + public event Action fixedUpdateAction; + public event Action lateUpdateAction; + private static GameMonoUpdater instance + { + get + { + if (_instance == null) + { + GameObject obj = new GameObject("[GameMonoUpdater]"); + _instance = obj.AddComponent(); + DontDestroyOnLoad(obj); + } + return _instance; + } + } + + public static void AddUpdateAction(Action action) => instance.updateAction += action; + public static void RemoveUpdateAction(Action action) => instance.updateAction -= action; + public static void AddFixedUpdateAction(Action action) => instance.fixedUpdateAction += action; + public static void RemoveFixedUpdateAction(Action action) => instance.fixedUpdateAction -= action; + public static void AddLateUpdateAction(Action action) => instance.lateUpdateAction += action; + public static void RemoveLateUpdateAction(Action action) => instance.lateUpdateAction -= action; + + void Awake() + { + if (_instance != null) + { + Destroy(gameObject); + return; + } + _instance = this; + DontDestroyOnLoad(gameObject); + } + void Update() + { + updateAction?.Invoke(); + } + + private void FixedUpdate() + { + fixedUpdateAction?.Invoke(); + } + private void LateUpdate() + { + lateUpdateAction?.Invoke(); + } + } +} diff --git a/EasyInteractive/ICanUseGameMonoUpdater.cs b/EasyInteractive/ICanUseGameMonoUpdater.cs new file mode 100644 index 0000000..df1ec6e --- /dev/null +++ b/EasyInteractive/ICanUseGameMonoUpdater.cs @@ -0,0 +1,30 @@ +namespace HalfDog.GameMonoUpdater +{ + public interface ICanUseGameMonoUpdater + { + public void FixedUpdate(); + public void Update(); + public void LateUpdate(); + public bool InUpdate { get; set; } + } + + public static class CanUseMonoUpdaterExtension + { + public static void EnableMonoUpdater(this ICanUseGameMonoUpdater self) + { + if (self.InUpdate) return; + GameMonoUpdater.AddUpdateAction(self.Update); + GameMonoUpdater.AddFixedUpdateAction(self.FixedUpdate); + GameMonoUpdater.AddLateUpdateAction(self.LateUpdate); + self.InUpdate = true; + } + + public static void DisableMonoUpdater(this ICanUseGameMonoUpdater self) + { + GameMonoUpdater.RemoveUpdateAction(self.Update); + GameMonoUpdater.RemoveFixedUpdateAction(self.FixedUpdate); + GameMonoUpdater.RemoveLateUpdateAction(self.LateUpdate); + self.InUpdate = false; + } + } +} \ No newline at end of file diff --git a/EasyInteractive/IInteractCase.cs b/EasyInteractive/IInteractCase.cs new file mode 100644 index 0000000..f9e6683 --- /dev/null +++ b/EasyInteractive/IInteractCase.cs @@ -0,0 +1,41 @@ +using System; + +namespace HalfDog.EasyInteractive +{ + /// + /// 交互情景接口 + /// + public interface IInteractCase + { + public Type subject { get; } + public Type target { get; } + public bool enable { get; set; } + public bool Execute(IFocusable focusable, ISelectable selectable, IDragable dragable); + } + + /// + /// 交互情景抽象类 + /// + public abstract class AbstractInteractCase : IInteractCase + { + private Type _subject; + private Type _target; + private bool _enable; + public Type subject => _subject; + public Type target => _target; + + public bool enable + { + get => _enable; + set => _enable = value; + } + + public AbstractInteractCase(Type subject, Type target) + { + _subject = subject; + _target = target; + } + + public abstract bool Execute(IFocusable focusable, ISelectable selectable, IDragable dragable); + } +} diff --git a/EasyInteractive/IInteractable.cs b/EasyInteractive/IInteractable.cs new file mode 100644 index 0000000..d8635fd --- /dev/null +++ b/EasyInteractive/IInteractable.cs @@ -0,0 +1,65 @@ +using System; + +namespace HalfDog.EasyInteractive +{ + public interface IInteractable + { + /// + /// 交互对象标识 + /// + public Type interactTag { get; } + } + + /// + /// 交互:可聚焦接口 + /// + public interface IFocusable : IInteractable + { + public bool enableFocus { get; } + + /// + /// 焦点进入 + /// + public void OnFocus(); + + /// + /// 焦点离开 + /// + public void EndFocus(); + } + + public interface ISelectable : IFocusable, IInteractable + { + public bool enableSelect { get; } + + /// + /// 选择 + /// + public void OnSelect(); + + /// + /// 取消选择 + /// + public void EndSelect(); + } + + public interface IDragable : IFocusable, IInteractable + { + public bool enableDrag { get; } + + /// + /// 开始拖拽 + /// + public void OnDrag(); + + /// + /// 拖拽中 + /// + public void ProcessDrag(); + + /// + /// 结束拖拽 + /// + public void EndDrag(); + } +} \ No newline at end of file diff --git a/EasyInteractive/InteractCaseAttribute.cs b/EasyInteractive/InteractCaseAttribute.cs new file mode 100644 index 0000000..18ff5e4 --- /dev/null +++ b/EasyInteractive/InteractCaseAttribute.cs @@ -0,0 +1,19 @@ +using System; + +namespace HalfDog.EasyInteractive +{ + [AttributeUsage(AttributeTargets.Class)] + public class InteractCaseAttribute : Attribute + { + public Type interactSubject; + public Type interactTarget; + public bool executeOnLoad; + + public InteractCaseAttribute(Type subject, Type target, bool executeOnLoad = true) + { + interactSubject = subject; + interactTarget = target; + this.executeOnLoad = executeOnLoad; + } + } +}