This commit is contained in:
Steven Lai
2024-11-29 15:12:34 +08:00
commit d053f8ee25
7 changed files with 510 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
using System;
namespace HalfDog.EasyInteractive
{
/// <summary>
/// 拖拽主体并聚焦目标的交互情景
/// </summary>
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;
}
/// <summary>
/// 进入交互情景
/// </summary>
protected virtual void OnEnter(IDragable subject, IFocusable target)
{
}
/// <summary>
/// 退出交互情景
/// </summary>
protected virtual void OnExit(IDragable subject, IFocusable target)
{
}
}
}

View File

@@ -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<Type,IInteractCase> _allInteractCase = new Dictionary<Type,IInteractCase>();
private List<IInteractCase> _executingInteractCases = new List<IInteractCase>();
private List<IInteractCase> _activeInteractCase = new List<IInteractCase>();
private Stack<IFocusable> _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<Type> 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<InteractCaseAttribute>();
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<T>() where T : IInteractCase
{
Type type = typeof(T);
if (_allInteractCase.ContainsKey(type))
{
_allInteractCase[type].enable = true;
}
}
public void DisableInteractCase<T>() where T : IInteractCase
{
Type type = typeof(T);
if (_allInteractCase.ContainsKey(type))
{
_allInteractCase[type].enable = false;
}
}
public void FixedUpdate() { }
public void LateUpdate() { }
}
}

View File

@@ -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<GameMonoUpdater>();
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();
}
}
}

View File

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

View File

@@ -0,0 +1,41 @@
using System;
namespace HalfDog.EasyInteractive
{
/// <summary>
/// 交互情景接口
/// </summary>
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);
}
/// <summary>
/// 交互情景抽象类
/// </summary>
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);
}
}

View File

@@ -0,0 +1,65 @@
using System;
namespace HalfDog.EasyInteractive
{
public interface IInteractable
{
/// <summary>
/// 交互对象标识
/// </summary>
public Type interactTag { get; }
}
/// <summary>
/// 交互:可聚焦接口
/// </summary>
public interface IFocusable : IInteractable
{
public bool enableFocus { get; }
/// <summary>
/// 焦点进入
/// </summary>
public void OnFocus();
/// <summary>
/// 焦点离开
/// </summary>
public void EndFocus();
}
public interface ISelectable : IFocusable, IInteractable
{
public bool enableSelect { get; }
/// <summary>
/// 选择
/// </summary>
public void OnSelect();
/// <summary>
/// 取消选择
/// </summary>
public void EndSelect();
}
public interface IDragable : IFocusable, IInteractable
{
public bool enableDrag { get; }
/// <summary>
/// 开始拖拽
/// </summary>
public void OnDrag();
/// <summary>
/// 拖拽中
/// </summary>
public void ProcessDrag();
/// <summary>
/// 结束拖拽
/// </summary>
public void EndDrag();
}
}

View File

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