Files
2026-08-10 14:17:25 +08:00

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