296 lines
9.3 KiB
C#
296 lines
9.3 KiB
C#
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.UI;
|
|
|
|
namespace XericLibrary.Runtime.Blueprint
|
|
{
|
|
/// <summary>
|
|
/// 蓝图输入工具基类(UGUI)—— 注册 EventTrigger 到蓝图背景上监听鼠标事件,
|
|
/// 并将事件发布到 <see cref="BlueprintToolProvider"/>,同时记录光标上下文。
|
|
/// <para>
|
|
/// 注意:此基类本身不被注册为独立工具(无 <c>[BlueprintTool]</c> 属性)。
|
|
/// 由子类 <c>BlueprintInputTool_InputSystem</c> 和 <c>BlueprintInputTool_InputManager</c>
|
|
/// 通过继承获得 UGUI 绑定能力。
|
|
/// </para>
|
|
/// </summary>
|
|
public class BlueprintInputTool_UGUI : GraphInputTool
|
|
{
|
|
// ===== EventTrigger 绑定 =====
|
|
|
|
private Transform _bgTransform;
|
|
private EventTrigger _eventTrigger;
|
|
private RectTransform _bgRectTransform;
|
|
|
|
private const string BackgroundGoName = "__Bp_GridBackground";
|
|
|
|
// 上一帧坐标:普通交互使用 canvas delta,中键平移使用不受 Pan/Zoom 影响的 local delta。
|
|
private Vector2 _lastCanvasPos;
|
|
private Vector2 _lastLocalPos;
|
|
private Vector2 _dragStartCanvasPos;
|
|
private Vector2 _dragStartLocalPos;
|
|
|
|
/// <summary>
|
|
/// 是否通过 EventTrigger 处理鼠标按键/拖拽/滚轮事件。
|
|
/// 新输入系统路径下为 true(默认),旧输入系统路径下为 false(由 PollUpdate 轮询)。
|
|
/// 子类可在 <see cref="OnInitialize"/> 中修改此值。
|
|
/// </summary>
|
|
protected bool UseEventTriggerForMouse { get; set; } = true;
|
|
|
|
// ===== 生命周期 =====
|
|
|
|
public override void OnInitialize()
|
|
{
|
|
GraphInputTool.RegisterAdapter(Graph, this);
|
|
TryBindEventTrigger();
|
|
}
|
|
|
|
public override void OnUpdate(float deltaTime)
|
|
{
|
|
if (Graph == null) return;
|
|
|
|
// 检查底层的 Unity Canvas 组件是否仍存活
|
|
// Graph?.Canvas 是 C# 接口引用(非 Unity Object),不适用 Unity 的假空检测
|
|
if (Graph.Canvas == null || !Graph.Canvas.IsAlive)
|
|
return;
|
|
|
|
Graph.BeginFrame();
|
|
|
|
BlueprintUGUIInputManager.UpdateMousePosition();
|
|
BlueprintToolProvider.MousePosition = BlueprintUGUIInputManager.MousePosition;
|
|
|
|
// 每帧检查 EventTrigger 是否有效(应对 Editor 重收集工具场景)
|
|
if (_eventTrigger == null || !IsEventTriggerValid())
|
|
{
|
|
_eventTrigger = null; // 强制重新绑定
|
|
TryBindEventTrigger();
|
|
return;
|
|
}
|
|
|
|
var canvasPt = Graph.Canvas.ScreenToCanvas(BlueprintUGUIInputManager.MousePosition);
|
|
var localPt = Graph.Canvas.ScreenToLocal(BlueprintUGUIInputManager.MousePosition);
|
|
PointerLocalDelta = localPt - _lastLocalPos;
|
|
UpdateCursorContext(canvasPt);
|
|
|
|
// ── 每帧分发 PointerMove ──
|
|
var delta = canvasPt - _lastCanvasPos;
|
|
if (delta.sqrMagnitude > 0.0001f || _lastCanvasPos == Vector2.zero)
|
|
BlueprintToolProvider.DispatchPointerMove(Graph, this, canvasPt, delta);
|
|
_lastCanvasPos = canvasPt;
|
|
_lastLocalPos = localPt;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 检查已绑定的 EventTrigger 实例是否仍然有效(回调仍指向本实例)。
|
|
/// </summary>
|
|
private bool IsEventTriggerValid()
|
|
{
|
|
if (_eventTrigger == null) return false;
|
|
var triggersList = _eventTrigger.triggers;
|
|
if (triggersList == null || triggersList.Count == 0) return false;
|
|
var callbacks = triggersList[0].callback;
|
|
if (callbacks == null) return false;
|
|
int persistentCount = callbacks.GetPersistentEventCount();
|
|
if (persistentCount > 0)
|
|
{
|
|
var target = callbacks.GetPersistentTarget(0);
|
|
return target != null && target.Equals(this);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public override void OnDestroy()
|
|
{
|
|
GraphInputTool.UnregisterAdapter(Graph, this);
|
|
UnbindEventTrigger();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 确保场景中存在 EventSystem 和合适的 InputModule。
|
|
/// 旧输入系统 → StandaloneInputModule。
|
|
/// 新输入系统 → InputSystemUIInputModule;无蓝图资产配置时使用模块默认动作兜底。
|
|
/// 不会覆盖 GraphTheoryBlueprintComponent 已配置的模块。
|
|
/// </summary>
|
|
private static void EnsureEventSystemExists()
|
|
{
|
|
var es = Object.FindObjectOfType<EventSystem>();
|
|
if (es == null)
|
|
{
|
|
var go = new GameObject("EventSystem");
|
|
es = go.AddComponent<EventSystem>();
|
|
}
|
|
|
|
#if ENABLE_INPUT_SYSTEM
|
|
if (es.GetComponent<UnityEngine.InputSystem.UI.InputSystemUIInputModule>() == null)
|
|
{
|
|
var standalone = es.GetComponent<StandaloneInputModule>();
|
|
if (standalone != null)
|
|
Object.DestroyImmediate(standalone);
|
|
|
|
var uiModule = es.gameObject.AddComponent<UnityEngine.InputSystem.UI.InputSystemUIInputModule>();
|
|
uiModule.AssignDefaultActions();
|
|
}
|
|
#else
|
|
if (es.GetComponent<StandaloneInputModule>() == null)
|
|
{
|
|
es.gameObject.AddComponent<StandaloneInputModule>();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// ===== EventTrigger 绑定/解绑 =====
|
|
|
|
private void TryBindEventTrigger()
|
|
{
|
|
if (_eventTrigger != null) return;
|
|
if (Graph?.Canvas == null || !Graph.Canvas.IsAlive) return;
|
|
|
|
var root = Graph.Canvas.GetRootTransform();
|
|
if (root == null) return;
|
|
|
|
_bgTransform = root.Find(BackgroundGoName);
|
|
if (_bgTransform == null) return;
|
|
|
|
_bgRectTransform = _bgTransform as RectTransform;
|
|
|
|
// ── 自动创建 EventSystem(含合适 InputModule)──
|
|
EnsureEventSystemExists();
|
|
|
|
_eventTrigger = _bgTransform.gameObject.GetComponent<EventTrigger>();
|
|
if (_eventTrigger == null)
|
|
{
|
|
_eventTrigger = _bgTransform.gameObject.AddComponent<EventTrigger>();
|
|
}
|
|
else
|
|
_eventTrigger.triggers.Clear();
|
|
|
|
BindAllEntries();
|
|
}
|
|
|
|
private void UnbindEventTrigger()
|
|
{
|
|
if (_eventTrigger != null)
|
|
{
|
|
_eventTrigger.triggers.Clear();
|
|
_eventTrigger = null;
|
|
}
|
|
_bgTransform = null;
|
|
_bgRectTransform = null;
|
|
}
|
|
|
|
private void BindAllEntries()
|
|
{
|
|
if (_eventTrigger == null) return;
|
|
|
|
// ── 始终绑定的基础回调(指针进入/离开) ──
|
|
AddEntry(EventTriggerType.PointerEnter, OnPointerEnterEvent);
|
|
AddEntry(EventTriggerType.PointerExit, OnPointerExitEvent);
|
|
|
|
// ── 鼠标按键/拖拽/滚轮(仅在 UseEventTriggerForMouse 时绑定) ──
|
|
if (UseEventTriggerForMouse)
|
|
{
|
|
AddEntry(EventTriggerType.PointerDown, OnPointerDownEvent);
|
|
AddEntry(EventTriggerType.PointerUp, OnPointerUpEvent);
|
|
AddEntry(EventTriggerType.BeginDrag, OnBeginDragEvent);
|
|
AddEntry(EventTriggerType.Drag, OnDragEvent);
|
|
AddEntry(EventTriggerType.EndDrag, OnEndDragEvent);
|
|
AddEntry(EventTriggerType.Scroll, OnScrollEvent);
|
|
}
|
|
}
|
|
|
|
private void AddEntry(EventTriggerType type, UnityEngine.Events.UnityAction<BaseEventData> callback)
|
|
{
|
|
var entry = new EventTrigger.Entry { eventID = type };
|
|
entry.callback.AddListener(callback);
|
|
_eventTrigger.triggers.Add(entry);
|
|
}
|
|
|
|
// ===== 坐标转换 =====
|
|
|
|
private Vector2 ScreenToCanvas(Vector2 screenPos)
|
|
{
|
|
return Graph?.Canvas != null ? Graph.Canvas.ScreenToCanvas(screenPos) : screenPos;
|
|
}
|
|
|
|
private Vector2 PointerCanvasPos(PointerEventData data)
|
|
{
|
|
return ScreenToCanvas(data.position);
|
|
}
|
|
|
|
// ===== EventTrigger 回调(以 self 作为 sender 派发) =====
|
|
|
|
private void OnPointerEnterEvent(BaseEventData _)
|
|
{
|
|
BlueprintToolProvider.DispatchPointerEnter(Graph, this);
|
|
}
|
|
|
|
private void OnPointerExitEvent(BaseEventData _)
|
|
{
|
|
BlueprintToolProvider.DispatchPointerExit(Graph, this);
|
|
}
|
|
|
|
private void OnPointerDownEvent(BaseEventData data)
|
|
{
|
|
if (!(data is PointerEventData pData)) return;
|
|
var pt = PointerCanvasPos(pData);
|
|
Graph?.MarkDirty();
|
|
UpdateCursorContext(pt);
|
|
BlueprintToolProvider.DispatchPointerDown(Graph, this, pt, (int)pData.button);
|
|
}
|
|
|
|
private void OnPointerUpEvent(BaseEventData data)
|
|
{
|
|
if (!(data is PointerEventData pData)) return;
|
|
var pt = PointerCanvasPos(pData);
|
|
UpdateCursorContext(pt);
|
|
|
|
// 有拖拽的释放由 OnEndDragEvent 处理,这里只响应无拖拽的点击释放
|
|
if (!_hadDragThisClick)
|
|
{
|
|
bool isEmptyClick = Graph?.HitTest(pt) == null;
|
|
BlueprintToolProvider.DispatchPointerUp(Graph, this, pt, (int)pData.button, isEmptyClick);
|
|
}
|
|
}
|
|
|
|
private bool _hadDragThisClick;
|
|
|
|
private void OnBeginDragEvent(BaseEventData data)
|
|
{
|
|
if (!(data is PointerEventData pData)) return;
|
|
_dragStartCanvasPos = PointerCanvasPos(pData);
|
|
_dragStartLocalPos = Graph.Canvas.ScreenToLocal(pData.position);
|
|
_hadDragThisClick = true;
|
|
}
|
|
|
|
private void OnDragEvent(BaseEventData data)
|
|
{
|
|
if (!(data is PointerEventData pData)) return;
|
|
var current = PointerCanvasPos(pData);
|
|
var currentLocal = Graph.Canvas.ScreenToLocal(pData.position);
|
|
var canvasDelta = current - _dragStartCanvasPos;
|
|
PointerLocalDelta = currentLocal - _dragStartLocalPos;
|
|
_dragStartCanvasPos = current;
|
|
_dragStartLocalPos = currentLocal;
|
|
UpdateCursorContext(current);
|
|
BlueprintToolProvider.DispatchPointerDrag(Graph, this, current, canvasDelta, (int)pData.button);
|
|
}
|
|
|
|
private void OnEndDragEvent(BaseEventData data)
|
|
{
|
|
if (!(data is PointerEventData pData)) return;
|
|
var pt = PointerCanvasPos(pData);
|
|
UpdateCursorContext(pt);
|
|
BlueprintToolProvider.DispatchPointerUp(Graph, this, pt, (int)pData.button);
|
|
}
|
|
|
|
private void OnScrollEvent(BaseEventData data)
|
|
{
|
|
if (!(data is PointerEventData pData)) return;
|
|
var pt = PointerCanvasPos(pData);
|
|
var scroll = new Vector2(-pData.scrollDelta.x, -pData.scrollDelta.y);
|
|
UpdateCursorContext(pt);
|
|
Graph?.MarkDirty();
|
|
BlueprintToolProvider.DispatchScroll(Graph, this, pt, scroll);
|
|
}
|
|
}
|
|
}
|