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;
+ }
+ }
+}