# update 0.6.2.1
更新了蓝图组件相关的用法。
This commit is contained in:
@@ -4,103 +4,88 @@ namespace XericLibrary.Runtime.Blueprint.Canvas
|
||||
{
|
||||
/// <summary>
|
||||
/// 屏幕空间蓝图画布实现,基于 UGUI Canvas。
|
||||
/// <code>
|
||||
/// 使用 RectTransformUtility 处理屏幕坐标和画布坐标之间的转换。
|
||||
/// <para>
|
||||
/// 所有坐标转换统一通过 <see cref="RectTransformUtility"/> 完成,
|
||||
/// 以 <see cref="_renderRoot"/> RectTransform 为坐标基准,
|
||||
/// 不依赖蓝图自身的 RectTransform 是否撑满全屏。
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 支持 ScreenSpaceOverlay / ScreenSpaceCamera / WorldSpace 三种渲染模式。
|
||||
/// 由外层的 Mask(__Bp_GridBackground)裁剪,确保蓝图节点不溢出视图区域。
|
||||
/// 缩放范围:0.1x ~ 3x。
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public class ScreenSpaceBlueprintCanvas : BlueprintCanvasBase
|
||||
{
|
||||
private readonly UnityEngine.Canvas _unityCanvas;
|
||||
private readonly RectTransform _rectTransform;
|
||||
private readonly RectTransform _renderRoot;
|
||||
|
||||
/// <summary>动态获取摄像机引用(不缓存),
|
||||
/// 自动适应 Canvas.renderMode 运行时的变化。</summary>
|
||||
private Camera WorldCamera => _unityCanvas != null ? _unityCanvas.worldCamera : null;
|
||||
|
||||
/// <summary>
|
||||
/// 构造屏幕空间蓝图画布
|
||||
/// </summary>
|
||||
/// <param name="unityCanvas">Unity UGUI Canvas 组件</param>
|
||||
public ScreenSpaceBlueprintCanvas(UnityEngine.Canvas unityCanvas)
|
||||
/// <param name="unityCanvas">所属的 Unity UGUI Canvas 组件</param>
|
||||
/// <param name="renderRoot">蓝图渲染根 RectTransform,所有蓝图层容器和背景挂载于此</param>
|
||||
public ScreenSpaceBlueprintCanvas(UnityEngine.Canvas unityCanvas, RectTransform renderRoot)
|
||||
{
|
||||
_unityCanvas = unityCanvas;
|
||||
_rectTransform = unityCanvas.GetComponent<RectTransform>();
|
||||
_renderRoot = renderRoot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将屏幕坐标转换为画布坐标
|
||||
/// 将屏幕坐标转换为画布坐标。
|
||||
/// 通过 <see cref="RectTransformUtility.ScreenPointToLocalPointInRectangle"/>
|
||||
/// 以 <see cref="_renderRoot"/> 为参照转换,
|
||||
/// 自动处理 Canvas 渲染模式和 RenderRoot 的偏移。
|
||||
/// </summary>
|
||||
/// <param name="screenPoint">屏幕空间的坐标点</param>
|
||||
/// <returns>画布空间的坐标点</returns>
|
||||
public override Vector2 ScreenToCanvas(Vector2 screenPoint)
|
||||
{
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
_rectTransform, screenPoint, _unityCanvas.worldCamera, out Vector2 localPoint);
|
||||
_renderRoot, screenPoint, WorldCamera, out Vector2 localPoint);
|
||||
return (localPoint - _panOffset) / _zoomLevel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将画布坐标转换为屏幕坐标
|
||||
/// 将画布坐标转换为屏幕坐标。
|
||||
/// 先计算 RenderRoot 本地空间位置,再通过 RectTransformUtility 转换为屏幕坐标。
|
||||
/// </summary>
|
||||
/// <param name="canvasPoint">画布空间的坐标点</param>
|
||||
/// <returns>屏幕空间的坐标点</returns>
|
||||
public override Vector2 CanvasToScreen(Vector2 canvasPoint)
|
||||
{
|
||||
Vector2 local = canvasPoint * _zoomLevel + _panOffset;
|
||||
Vector3 world = _rectTransform.TransformPoint(local);
|
||||
if (_unityCanvas.worldCamera != null)
|
||||
{
|
||||
return _unityCanvas.worldCamera.WorldToScreenPoint(world);
|
||||
}
|
||||
|
||||
return world;
|
||||
Vector3 world = _renderRoot.TransformPoint(local);
|
||||
return RectTransformUtility.WorldToScreenPoint(WorldCamera, world);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将画布坐标转换为世界坐标
|
||||
/// <code>
|
||||
/// 屏幕空间画布的世界空间转换依赖于 Canvas 的渲染模式:
|
||||
/// Screen Space - Overlay 时直接使用 localPoint 作为屏幕坐标;
|
||||
/// Screen Space - Camera 时通过 camera 进行坐标转换。
|
||||
/// </code>
|
||||
/// 将画布坐标转换为世界坐标。
|
||||
/// </summary>
|
||||
/// <param name="canvasPoint">画布空间的坐标点</param>
|
||||
/// <returns>世界空间的坐标点</returns>
|
||||
public override Vector3 CanvasToWorld(Vector2 canvasPoint)
|
||||
{
|
||||
Vector2 local = canvasPoint * _zoomLevel + _panOffset;
|
||||
Vector3 world = _rectTransform.TransformPoint(local);
|
||||
if (_unityCanvas.worldCamera != null)
|
||||
{
|
||||
Vector3 screenPoint = _unityCanvas.worldCamera.WorldToScreenPoint(world);
|
||||
return _unityCanvas.worldCamera.ScreenToWorldPoint(screenPoint);
|
||||
}
|
||||
|
||||
return world;
|
||||
return _renderRoot.TransformPoint(local);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将世界坐标转换为画布坐标
|
||||
/// 将世界坐标转换为画布坐标。
|
||||
/// 先转换为屏幕坐标,再通过 RectTransformUtility 转换为 RenderRoot 本地坐标。
|
||||
/// </summary>
|
||||
/// <param name="worldPoint">世界空间的坐标点</param>
|
||||
/// <returns>画布空间的坐标点</returns>
|
||||
public override Vector2 WorldToCanvas(Vector3 worldPoint)
|
||||
{
|
||||
if (_unityCanvas.worldCamera != null)
|
||||
{
|
||||
Vector3 screenPoint = _unityCanvas.worldCamera.WorldToScreenPoint(worldPoint);
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
_rectTransform, screenPoint, _unityCanvas.worldCamera, out Vector2 localPoint);
|
||||
return (localPoint - _panOffset) / _zoomLevel;
|
||||
}
|
||||
|
||||
Vector3 local = _rectTransform.InverseTransformPoint(worldPoint);
|
||||
return (new Vector2(local.x, local.y) - _panOffset) / _zoomLevel;
|
||||
Vector3 screenPoint = RectTransformUtility.WorldToScreenPoint(WorldCamera, worldPoint);
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
_renderRoot, screenPoint, WorldCamera, out Vector2 localPoint);
|
||||
return (localPoint - _panOffset) / _zoomLevel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取渲染根 Transform(UGUI Canvas 的 RectTransform)
|
||||
/// 获取渲染根 Transform(蓝图层容器和背景的父级)
|
||||
/// </summary>
|
||||
public override Transform GetRootTransform()
|
||||
{
|
||||
return _rectTransform;
|
||||
return _renderRoot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -110,5 +95,10 @@ namespace XericLibrary.Runtime.Blueprint.Canvas
|
||||
{
|
||||
get { return _unityCanvas; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Canvas 组件未被销毁,画布可正常使用。
|
||||
/// </summary>
|
||||
public override bool IsAlive => _unityCanvas != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,5 +100,10 @@ namespace XericLibrary.Runtime.Blueprint.Canvas
|
||||
{
|
||||
return _canvasTransform;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transform 组件未被销毁,画布可正常使用。
|
||||
/// </summary>
|
||||
public override bool IsAlive => _canvasTransform != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
{
|
||||
/// <summary>
|
||||
/// 图论蓝图组件 —— 在一个 UGUI Canvas 上挂载蓝图系统。
|
||||
/// 使用 ExecuteAlways 在编辑器中也会运行,方便预览。
|
||||
/// <para>驱动方式:
|
||||
/// - 编辑器非运行态:EditorApplication.update → EditorTick
|
||||
/// - 运行时:MonoBehaviour.Update</para>
|
||||
@@ -45,13 +44,21 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
{
|
||||
if (_inputActions == value) return;
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
if (_inputActions is InputActionAsset oldAsset)
|
||||
BlueprintInputManager.UnregisterAsset(oldAsset);
|
||||
if (Graph != null && _inputActions is InputActionAsset oldAsset)
|
||||
{
|
||||
var oldTool = BlueprintToolProvider.GetToolByType<BlueprintInputTool_InputSystem>(Graph);
|
||||
if (oldTool != null && oldTool.InputActionAsset == oldAsset)
|
||||
oldTool.InputActionAsset = null;
|
||||
}
|
||||
#endif
|
||||
_inputActions = value;
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
if (value is InputActionAsset newAsset)
|
||||
BlueprintInputManager.RegisterAsset(newAsset);
|
||||
if (Graph != null && value is InputActionAsset newAsset)
|
||||
{
|
||||
var newTool = BlueprintToolProvider.GetToolByType<BlueprintInputTool_InputSystem>(Graph);
|
||||
if (newTool != null)
|
||||
newTool.InputActionAsset = newAsset;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -62,7 +69,7 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
|
||||
[Header("Test")]
|
||||
[Tooltip("是否自动生成测试节点")]
|
||||
[SerializeField] private bool _autoGenerateTestNodes = true;
|
||||
[SerializeField] public bool _autoGenerateTestNodes = true;
|
||||
|
||||
[Header("Debug")]
|
||||
[Tooltip("显示所有隐藏的运行时对象(HideFlags),用于调试。默认关闭。")]
|
||||
@@ -111,16 +118,36 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
}
|
||||
else
|
||||
{
|
||||
var unityCanvas = GetComponent<UnityEngine.Canvas>();
|
||||
if (unityCanvas == null)
|
||||
// 1) 找父级 Canvas(不在自身挂 Canvas)
|
||||
var parentCanvas = GetComponentInParent<UnityEngine.Canvas>();
|
||||
if (parentCanvas == null)
|
||||
{
|
||||
unityCanvas = gameObject.AddComponent<UnityEngine.Canvas>();
|
||||
unityCanvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
// 场景中无 Canvas → 创建全屏 Canvas,将自身移入
|
||||
var canvasGo = new GameObject("Blueprint Canvas",
|
||||
typeof(UnityEngine.Canvas),
|
||||
typeof(UnityEngine.UI.CanvasScaler),
|
||||
typeof(UnityEngine.UI.GraphicRaycaster));
|
||||
var cc = canvasGo.GetComponent<UnityEngine.Canvas>();
|
||||
cc.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
canvasGo.transform.SetParent(transform.parent, false);
|
||||
canvasGo.transform.SetAsLastSibling();
|
||||
transform.SetParent(canvasGo.transform, false);
|
||||
parentCanvas = cc;
|
||||
}
|
||||
if (GetComponent<UnityEngine.UI.GraphicRaycaster>() == null)
|
||||
gameObject.AddComponent<UnityEngine.UI.GraphicRaycaster>();
|
||||
|
||||
Canvas = new ScreenSpaceBlueprintCanvas(unityCanvas);
|
||||
// 2) 创建渲染根子项(所有蓝图层挂载于此,由外层的 Mask 裁剪)
|
||||
var renderRootGo = new GameObject("__Bp_RenderRoot",
|
||||
typeof(RectTransform));
|
||||
renderRootGo.hideFlags = HideFlags.DontSave;
|
||||
var renderRootRt = renderRootGo.GetComponent<RectTransform>();
|
||||
renderRootRt.SetParent(transform, false);
|
||||
renderRootRt.anchorMin = Vector2.zero;
|
||||
renderRootRt.anchorMax = Vector2.one;
|
||||
renderRootRt.offsetMin = Vector2.zero;
|
||||
renderRootRt.offsetMax = Vector2.zero;
|
||||
|
||||
// 3) 以父级 Canvas + 渲染根构建画布适配器
|
||||
Canvas = new ScreenSpaceBlueprintCanvas(parentCanvas, renderRootRt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,16 +170,15 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
private void SetupInput()
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
EnsureEventSystem(_inputActions as InputActionAsset);
|
||||
|
||||
if (_inputActions is InputActionAsset asset)
|
||||
{
|
||||
BlueprintInputManager.RegisterAsset(asset);
|
||||
EnsureEventSystem(asset);
|
||||
// 将 InputActionAsset 传递给输入工具(由工具自身管理绑定)
|
||||
var tool = BlueprintToolProvider.GetToolByType<BlueprintInputTool_InputSystem>(Graph);
|
||||
if (tool != null)
|
||||
tool.InputActionAsset = asset;
|
||||
}
|
||||
#else
|
||||
EnsureEventSystem(null);
|
||||
#endif
|
||||
BlueprintInputManager.SetFocusedGraph(Graph);
|
||||
}
|
||||
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
@@ -190,7 +216,7 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if (eventSys.currentInputModule == null)
|
||||
if (eventSys.GetComponent<StandaloneInputModule>() == null)
|
||||
{
|
||||
eventSys.gameObject.AddComponent<StandaloneInputModule>();
|
||||
}
|
||||
@@ -277,15 +303,12 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
|
||||
var node1 = new GraphTheoryNode("Node 1", Color.red);
|
||||
node1.Position = new Vector2(startX, centerY);
|
||||
node1.NodeSize = new Vector2(90, 130);
|
||||
|
||||
var node2 = new GraphTheoryNode("Node 2", Color.green);
|
||||
node2.Position = new Vector2(startX + spacing, centerY);
|
||||
node2.NodeSize = new Vector2(90, 130);
|
||||
|
||||
var node3 = new GraphTheoryNode("Node 3", Color.blue);
|
||||
node3.Position = new Vector2(startX + spacing * 2, centerY);
|
||||
node3.NodeSize = new Vector2(90, 130);
|
||||
|
||||
Graph.AddNode(node1);
|
||||
Graph.AddNode(node2);
|
||||
@@ -295,12 +318,10 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
Graph.TryConnectPorts(node2.OutputPorts[0], node3.InputPorts[0]);
|
||||
|
||||
var node4 = new GraphTheoryNode("Node 4", Color.yellow);
|
||||
node4.Position = new Vector2(startX + spacing, centerY + branchOffset);
|
||||
node4.NodeSize = new Vector2(90, 130);
|
||||
node4.Position = new Vector2(startX + spacing * 2, centerY + branchOffset);
|
||||
|
||||
var node5 = new GraphTheoryNode("Node 5", Color.cyan);
|
||||
node5.Position = new Vector2(startX + spacing, centerY - branchOffset);
|
||||
node5.NodeSize = new Vector2(90, 130);
|
||||
node5.Position = new Vector2(startX + spacing * 2, centerY - branchOffset);
|
||||
|
||||
Graph.AddNode(node4);
|
||||
Graph.AddNode(node5);
|
||||
@@ -316,12 +337,6 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
#if UNITY_EDITOR
|
||||
EditorApplication.update -= EditorTick;
|
||||
#endif
|
||||
BlueprintInputManager.SetFocusedGraph(null);
|
||||
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
if (_inputActions is InputActionAsset asset)
|
||||
BlueprintInputManager.UnregisterAsset(asset);
|
||||
#endif
|
||||
|
||||
if (Graph != null)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using XericLibrary.Runtime.Blueprint.Render;
|
||||
|
||||
namespace XericLibrary.Runtime.Blueprint
|
||||
{
|
||||
@@ -9,6 +10,10 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
/// 释放时计算框选范围内所有元素的交集并输出统计日志。
|
||||
/// <para>选框 UGUI Image 默认渲染在所有层之上(SetAsLastSibling)。</para>
|
||||
/// <para>框选结果保存在 <see cref="SelectedElements"/> 中供其他工具读取。</para>
|
||||
/// <para>
|
||||
/// 选框仅在拖拽距离超过 <c>QuickGraphInputConfig.BoxSelectThreshold</c> 后才显示,
|
||||
/// 避免误触显示闪烁。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[BlueprintTool(phase: ToolPhase.PreUpdate, order: 60)]
|
||||
[BlueprintTheme("QuickGraph")]
|
||||
@@ -19,19 +24,44 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
private Vector2 _dragStartCanvas; // 按下时的画布坐标
|
||||
private Vector2 _dragEndCanvas; // 当前的画布坐标
|
||||
|
||||
/// <summary>选框 GameObject 是否已创建并可见(超过阈值后)</summary>
|
||||
private bool _boxVisible;
|
||||
|
||||
// 选框 UI
|
||||
private GameObject _selectionBoxGo;
|
||||
private Image _selectionBoxImage;
|
||||
private RectTransform _selectionBoxRt;
|
||||
|
||||
// ---------- 配置 ----------
|
||||
|
||||
private QuickGraphInputConfig _boxConfig;
|
||||
|
||||
private QuickGraphInputConfig BoxConfig
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_boxConfig == null && Graph != null)
|
||||
{
|
||||
foreach (var c in Graph.ConfigAssets)
|
||||
{
|
||||
if (c is QuickGraphInputConfig qc) { _boxConfig = qc; break; }
|
||||
}
|
||||
if (_boxConfig == null)
|
||||
{
|
||||
_boxConfig = ScriptableObject.CreateInstance<QuickGraphInputConfig>();
|
||||
}
|
||||
}
|
||||
return _boxConfig;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 结果 ----------
|
||||
|
||||
/// <summary>本次框选选中的元素列表。</summary>
|
||||
public List<IBlueprintElement> SelectedElements { get; } = new List<IBlueprintElement>();
|
||||
/// <summary>结果:通过 Provider.CurrentSelectElements 读取框选结果。</summary>
|
||||
|
||||
// ===== 鼠标按下 =====
|
||||
|
||||
public override void OnPointerDown(Vector2 canvasPoint, int mouseButton)
|
||||
public override void OnPointerDown(GraphInputTool sender, Vector2 canvasPoint, int mouseButton)
|
||||
{
|
||||
if (Graph == null) return;
|
||||
if (mouseButton != 0) return; // 仅左键
|
||||
@@ -41,31 +71,55 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
if (hit != null) return;
|
||||
|
||||
_isBoxSelecting = true;
|
||||
_boxVisible = false;
|
||||
_dragStartCanvas = canvasPoint;
|
||||
_dragEndCanvas = canvasPoint;
|
||||
|
||||
// 创建选框 UI
|
||||
CreateSelectionBox();
|
||||
}
|
||||
|
||||
// ===== 拖拽 =====
|
||||
|
||||
public override void OnPointerDrag(Vector2 canvasPoint, Vector2 delta, int mouseButton)
|
||||
public override void OnPointerDrag(GraphInputTool sender, Vector2 canvasPoint, Vector2 delta, int mouseButton)
|
||||
{
|
||||
if (!_isBoxSelecting || mouseButton != 0) return;
|
||||
|
||||
_dragEndCanvas = canvasPoint;
|
||||
UpdateSelectionBox();
|
||||
|
||||
// 检查拖拽距离是否超过阈值
|
||||
float threshold = BoxConfig.BoxSelectThreshold;
|
||||
float dragDist = Vector2.Distance(_dragStartCanvas, _dragEndCanvas);
|
||||
|
||||
if (!_boxVisible && dragDist > threshold)
|
||||
{
|
||||
// 首次超过阈值 → 创建选框
|
||||
_boxVisible = true;
|
||||
CreateSelectionBox();
|
||||
}
|
||||
|
||||
if (_boxVisible)
|
||||
{
|
||||
UpdateSelectionBox();
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 释放 =====
|
||||
|
||||
public override void OnPointerUp(Vector2 canvasPoint, int mouseButton)
|
||||
public override void OnPointerUp(GraphInputTool sender, Vector2 canvasPoint, int mouseButton)
|
||||
{
|
||||
if (!_isBoxSelecting || mouseButton != 0) return;
|
||||
|
||||
_dragEndCanvas = canvasPoint;
|
||||
FinishSelection();
|
||||
|
||||
if (_boxVisible)
|
||||
{
|
||||
FinishSelection();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 未超过阈值 → 点击行为(框选起点处的元素)
|
||||
SelectAtPoint(_dragStartCanvas);
|
||||
}
|
||||
|
||||
_isBoxSelecting = false;
|
||||
}
|
||||
|
||||
// ===== 销毁 =====
|
||||
@@ -83,18 +137,24 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
var root = Graph.Canvas.GetRootTransform();
|
||||
if (root == null) return;
|
||||
|
||||
// 销毁已有的选框(应对重复点击)
|
||||
DestroySelectionBox();
|
||||
|
||||
_selectionBoxGo = new GameObject("__Bp_BoxSelect", typeof(RectTransform), typeof(Image));
|
||||
_selectionBoxGo.hideFlags = HideFlags.HideAndDontSave;
|
||||
|
||||
_selectionBoxRt = _selectionBoxGo.GetComponent<RectTransform>();
|
||||
_selectionBoxRt.SetParent(root, false);
|
||||
|
||||
// 枢轴设为左下角;锚点居中对齐 RenderRoot 枢轴(与 CanvasToLocal 空间一致)
|
||||
_selectionBoxRt.pivot = Vector2.zero;
|
||||
_selectionBoxRt.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
_selectionBoxRt.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
|
||||
_selectionBoxRt.SetAsLastSibling(); // 渲染在最顶层
|
||||
|
||||
_selectionBoxImage = _selectionBoxGo.GetComponent<Image>();
|
||||
_selectionBoxImage.color = new Color(0.2f, 0.5f, 1.0f, 0.15f); // 半透明蓝
|
||||
// 边框通过 Outline 或额外 Image 实现,直接使用透明填充 + 轮廓不好做,
|
||||
// 使用两个 Image:填充(当前) + 边框(另一个,1px 白色)
|
||||
// 为简化,在填充 Image 上挂一个 Outline 组件模拟边框
|
||||
_selectionBoxImage.color = new Color(0.2f, 0.5f, 1.0f, 0.15f);
|
||||
var outline = _selectionBoxGo.AddComponent<Outline>();
|
||||
outline.effectColor = Color.white;
|
||||
outline.effectDistance = new Vector2(1, -1);
|
||||
@@ -104,15 +164,15 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
{
|
||||
if (_selectionBoxRt == null || Graph?.Canvas == null) return;
|
||||
|
||||
// 将画布坐标两角转为屏幕坐标
|
||||
var startScreen = Graph.Canvas.CanvasToScreen(_dragStartCanvas);
|
||||
var endScreen = Graph.Canvas.CanvasToScreen(_dragEndCanvas);
|
||||
// 将画布坐标转为 RenderRoot 本地空间坐标(CanvasToLocal)
|
||||
var startLocal = Graph.Canvas.CanvasToLocal(_dragStartCanvas);
|
||||
var endLocal = Graph.Canvas.CanvasToLocal(_dragEndCanvas);
|
||||
|
||||
// 计算 RectTransform 位置和尺寸(屏幕空间)
|
||||
var minX = Mathf.Min(startScreen.x, endScreen.x);
|
||||
var minY = Mathf.Min(startScreen.y, endScreen.y);
|
||||
var maxX = Mathf.Max(startScreen.x, endScreen.x);
|
||||
var maxY = Mathf.Max(startScreen.y, endScreen.y);
|
||||
// 计算 RectTransform 位置和尺寸(RenderRoot 本地空间)
|
||||
var minX = Mathf.Min(startLocal.x, endLocal.x);
|
||||
var minY = Mathf.Min(startLocal.y, endLocal.y);
|
||||
var maxX = Mathf.Max(startLocal.x, endLocal.x);
|
||||
var maxY = Mathf.Max(startLocal.y, endLocal.y);
|
||||
|
||||
_selectionBoxRt.anchoredPosition = new Vector2(minX, minY);
|
||||
_selectionBoxRt.sizeDelta = new Vector2(maxX - minX, maxY - minY);
|
||||
@@ -120,11 +180,10 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
|
||||
private void FinishSelection()
|
||||
{
|
||||
_isBoxSelecting = false;
|
||||
UpdateSelectionBox();
|
||||
|
||||
// 计算选框内的元素(画布坐标空间)
|
||||
SelectedElements.Clear();
|
||||
BlueprintToolProvider.CurrentSelectElements.Clear();
|
||||
var min = new Vector2(
|
||||
Mathf.Min(_dragStartCanvas.x, _dragEndCanvas.x),
|
||||
Mathf.Min(_dragStartCanvas.y, _dragEndCanvas.y));
|
||||
@@ -138,7 +197,7 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
if (element == null) continue;
|
||||
if (element.BoundingBox.Overlaps(selectRect))
|
||||
{
|
||||
SelectedElements.Add(element);
|
||||
BlueprintToolProvider.CurrentSelectElements.Add(element);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,6 +205,16 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
DestroySelectionBox();
|
||||
}
|
||||
|
||||
private void SelectAtPoint(Vector2 canvasPoint)
|
||||
{
|
||||
BlueprintToolProvider.CurrentSelectElements.Clear();
|
||||
if (Graph == null) return;
|
||||
|
||||
var hit = Graph.HitTest(canvasPoint);
|
||||
if (hit != null)
|
||||
BlueprintToolProvider.CurrentSelectElements.Add(hit);
|
||||
}
|
||||
|
||||
private void DestroySelectionBox()
|
||||
{
|
||||
if (_selectionBoxGo != null)
|
||||
|
||||
@@ -1,47 +1,84 @@
|
||||
using XericLibrary.Runtime.MacroLibrary;
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericLibrary.Runtime.Blueprint
|
||||
{
|
||||
/// <summary>
|
||||
/// 蓝图输入系统的 Action Map / Action 名称常量。
|
||||
/// 供 Editor 的 InputAction 模板和 Runtime 的输入处理工具共同引用,
|
||||
/// 避免字符串硬编码散布各处。
|
||||
/// 蓝图输入系统的 Action Map / Action 名称常量与全局快捷键方案。
|
||||
/// <para>
|
||||
/// 快捷键通过 <see cref="MacroKey.SetShortcut(string, string)"/> 注册别名→组合键映射,
|
||||
/// 由具体输入工具(如 <c>BlueprintInputTool_InputManager</c>)在初始化时通过
|
||||
/// <see cref="ShortcutTree.RegisterAlias(string, Action)"/> 绑定实际动作后生效。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static class BlueprintInputConstants
|
||||
{
|
||||
// ── Action Map ──
|
||||
public const string MapName = "Blueprint";
|
||||
|
||||
// ── 鼠标 / 指针 Actions(名称须与 UnityEngine.InputSystem.UI.InputSystemUIInputModule 官方预设一致)──
|
||||
/// <summary>画布内指针位置(Vector2)。InputSystemUIInputModule 预设名称。</summary>
|
||||
// ── 鼠标 / 指针 Actions ──
|
||||
public const string Point = "Point";
|
||||
/// <summary>滚轮滚动(Vector2)。InputSystemUIInputModule 预设名称。</summary>
|
||||
public const string ScrollWheel = "ScrollWheel";
|
||||
/// <summary>左键点击(Button)。InputSystemUIInputModule 预设名称。</summary>
|
||||
public const string LeftClick = "LeftClick";
|
||||
/// <summary>右键点击(Button)。InputSystemUIInputModule 预设名称。</summary>
|
||||
public const string RightClick = "RightClick";
|
||||
/// <summary>中键点击(Button)。InputSystemUIInputModule 预设名称。</summary>
|
||||
public const string MiddleClick = "MiddleClick";
|
||||
|
||||
// ── 功能键 Actions ──
|
||||
/// <summary>左 Shift(Button)</summary>
|
||||
public const string Shift = "Shift";
|
||||
/// <summary>左 Ctrl(Button)</summary>
|
||||
public const string Ctrl = "Ctrl";
|
||||
/// <summary>左 Alt(Button)</summary>
|
||||
public const string Alt = "Alt";
|
||||
|
||||
// ── 快捷键组合 Actions(仅新输入系统)──
|
||||
/// <summary>平移画布(Vector2,4方向)</summary>
|
||||
// ── 快捷键组合 Actions ──
|
||||
public const string Pan = "Pan";
|
||||
/// <summary>缩放画布(float,滚轮 + Ctrl 组合)</summary>
|
||||
public const string Zoom = "Zoom";
|
||||
/// <summary>定位到核心节点</summary>
|
||||
public const string FocusHome = "FocusHome";
|
||||
/// <summary>撤销</summary>
|
||||
public const string Undo = "Undo";
|
||||
/// <summary>重做</summary>
|
||||
public const string Redo = "Redo";
|
||||
/// <summary>进入父级</summary>
|
||||
public const string NavigateParent = "NavigateParent";
|
||||
public const string DeleteSelected = "DeleteSelected";
|
||||
public const string SelectAll = "SelectAll";
|
||||
public const string Duplicate = "Duplicate";
|
||||
public const string Copy = "Copy";
|
||||
public const string Paste = "Paste";
|
||||
public const string Cut = "Cut";
|
||||
public const string Save = "Save";
|
||||
|
||||
// ===== 全局快捷键方案 =====
|
||||
//
|
||||
// 使用 MacroKey 的静态实例管理快捷键别名→组合键映射。
|
||||
// 具体工具初始化时通过 MacroKey.ShortcutTreeInstance.RegisterAlias / RebindAlias
|
||||
// 绑定实际动作委托。
|
||||
|
||||
/// <summary>全局快捷键树(只读,操作请通过 MacroKey 静态方法)。</summary>
|
||||
public static ShortcutTree GlobalShortcuts => MacroKey.ShortcutTreeInstance;
|
||||
|
||||
// 静态构造器:在类首次加载时自动注册标准快捷键方案
|
||||
static BlueprintInputConstants()
|
||||
{
|
||||
RegisterStandardShortcuts();
|
||||
}
|
||||
|
||||
private static void RegisterStandardShortcuts()
|
||||
{
|
||||
MacroKey.SetShortcut(FocusHome, MacroKey.Combo(false, false, false, (char)KeyCode.Home));
|
||||
MacroKey.SetShortcut(FocusHome, MacroKey.Combo(false, false, false, (char)KeyCode.F));
|
||||
MacroKey.SetShortcut(Undo, MacroKey.Combo(false, true, false, 'Z'));
|
||||
MacroKey.SetShortcut(Redo, MacroKey.Combo(true, true, false, 'Z'));
|
||||
MacroKey.SetShortcut(Redo, MacroKey.Combo(false, true, false, 'Y'));
|
||||
MacroKey.SetShortcut(DeleteSelected, MacroKey.Combo(false, false, false, (char)KeyCode.Delete));
|
||||
MacroKey.SetShortcut(DeleteSelected, MacroKey.Combo(false, false, false, (char)KeyCode.Backspace));
|
||||
MacroKey.SetShortcut(SelectAll, MacroKey.Combo(false, true, false, 'A'));
|
||||
MacroKey.SetShortcut(Duplicate, MacroKey.Combo(false, true, false, 'D'));
|
||||
MacroKey.SetShortcut(Copy, MacroKey.Combo(false, true, false, 'C'));
|
||||
MacroKey.SetShortcut(Paste, MacroKey.Combo(false, true, false, 'V'));
|
||||
MacroKey.SetShortcut(Cut, MacroKey.Combo(false, true, false, 'X'));
|
||||
MacroKey.SetShortcut(Save, MacroKey.Combo(false, true, false, 'S'));
|
||||
}
|
||||
|
||||
public static readonly string[] ActionNames = new[]
|
||||
{
|
||||
FocusHome, Undo, Redo, NavigateParent,
|
||||
DeleteSelected, SelectAll, Duplicate, Copy, Paste, Cut, Save
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace XericLibrary.Runtime.Blueprint
|
||||
{
|
||||
/// <summary>
|
||||
/// 蓝图输入管理器(新输入系统)—— 管理 InputActionAsset 的引用计数与启用/停用。
|
||||
/// <para>
|
||||
/// 多个蓝图可能引用同一个 InputActionAsset,管理器确保:
|
||||
/// - 首个蓝图注册时启用 asset 并绑定 Action 回调;
|
||||
/// - 后续蓝图注册仅增加计数,不重复启用;
|
||||
/// - 蓝图注销时减少计数,计数归零时才解绑并禁用 asset。
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 蓝图组件在 Awake 时调用 <see cref="RegisterAsset"/>,OnDestroy 时调用
|
||||
/// <see cref="UnregisterAsset"/>;运行时动态更换 asset 也通过这两方法。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static class BlueprintInputManager
|
||||
{
|
||||
/// <summary>asset → 引用计数</summary>
|
||||
private static Dictionary<InputActionAsset, int> _refCounts
|
||||
= new Dictionary<InputActionAsset, int>();
|
||||
|
||||
/// <summary>asset → 已绑定回调的 ActionMap</summary>
|
||||
private static Dictionary<InputActionAsset, InputActionMap> _activeMaps
|
||||
= new Dictionary<InputActionAsset, InputActionMap>();
|
||||
|
||||
// ===== 快捷键最新值(供 QuickGraphInputTool.OnUpdate 轮询) =====
|
||||
|
||||
/// <summary>Pan 动作最新的 Vector2 方向值(在 performed 中写入,canceled 中归零)。</summary>
|
||||
public static Vector2 LastPanDirection;
|
||||
|
||||
/// <summary>Zoom 动作最新的 float 轴值(在 performed 中写入,canceled 中归零)。</summary>
|
||||
public static float LastZoomAxis;
|
||||
|
||||
// 缓存的回调引用(确保 Bind/Unbind 使用同一委托实例)
|
||||
private static readonly System.Action<InputAction.CallbackContext> s_panHandler
|
||||
= ctx => LastPanDirection = ctx.ReadValue<Vector2>();
|
||||
private static readonly System.Action<InputAction.CallbackContext> s_panCanceled
|
||||
= _ => LastPanDirection = Vector2.zero;
|
||||
private static readonly System.Action<InputAction.CallbackContext> s_zoomHandler
|
||||
= ctx => LastZoomAxis = ctx.ReadValue<float>();
|
||||
private static readonly System.Action<InputAction.CallbackContext> s_zoomCanceled
|
||||
= _ => LastZoomAxis = 0f;
|
||||
private static readonly System.Action<InputAction.CallbackContext> s_homeHandler
|
||||
= _ => OnActionReceived(BlueprintInputConstants.FocusHome);
|
||||
private static readonly System.Action<InputAction.CallbackContext> s_undoHandler
|
||||
= _ => OnActionReceived(BlueprintInputConstants.Undo);
|
||||
private static readonly System.Action<InputAction.CallbackContext> s_redoHandler
|
||||
= _ => OnActionReceived(BlueprintInputConstants.Redo);
|
||||
private static readonly System.Action<InputAction.CallbackContext> s_parentHandler
|
||||
= _ => OnActionReceived(BlueprintInputConstants.NavigateParent);
|
||||
|
||||
// ===== 当前焦点蓝图(快捷键分发目标) =====
|
||||
|
||||
private static BlueprintGraph s_focusedGraph;
|
||||
|
||||
/// <summary>设置当前焦点蓝图(快捷键分发目标)。</summary>
|
||||
public static void SetFocusedGraph(BlueprintGraph graph)
|
||||
{
|
||||
s_focusedGraph = graph;
|
||||
}
|
||||
|
||||
// ===== 注册 / 注销 =====
|
||||
|
||||
/// <summary>
|
||||
/// 注册一个蓝图对 InputActionAsset 的引用。
|
||||
/// 若 asset 尚未启用,则启用并绑定快捷键回调。
|
||||
/// 允许多个蓝图共享同一 asset。
|
||||
/// </summary>
|
||||
public static void RegisterAsset(InputActionAsset asset)
|
||||
{
|
||||
if (asset == null) return;
|
||||
|
||||
if (_refCounts.TryGetValue(asset, out int count))
|
||||
{
|
||||
_refCounts[asset] = count + 1;
|
||||
return;
|
||||
}
|
||||
|
||||
_refCounts[asset] = 1;
|
||||
|
||||
var map = asset.FindActionMap(BlueprintInputConstants.MapName);
|
||||
if (map != null)
|
||||
{
|
||||
map.Enable();
|
||||
BindActions(map);
|
||||
_activeMaps[asset] = map;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注销一个蓝图对 InputActionAsset 的引用。
|
||||
/// 引用计数归零时解绑回调并禁用 asset。
|
||||
/// </summary>
|
||||
public static void UnregisterAsset(InputActionAsset asset)
|
||||
{
|
||||
if (asset == null) return;
|
||||
|
||||
if (!_refCounts.TryGetValue(asset, out int count)) return;
|
||||
|
||||
count--;
|
||||
if (count > 0)
|
||||
{
|
||||
_refCounts[asset] = count;
|
||||
return;
|
||||
}
|
||||
|
||||
_refCounts.Remove(asset);
|
||||
|
||||
if (_activeMaps.TryGetValue(asset, out var map))
|
||||
{
|
||||
UnbindActions(map);
|
||||
map.Disable();
|
||||
_activeMaps.Remove(asset);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Action 回调 =====
|
||||
|
||||
private static void BindActions(InputActionMap map)
|
||||
{
|
||||
Bind(map, BlueprintInputConstants.Pan, s_panHandler);
|
||||
BindCancelled(map, BlueprintInputConstants.Pan, s_panCanceled);
|
||||
Bind(map, BlueprintInputConstants.Zoom, s_zoomHandler);
|
||||
BindCancelled(map, BlueprintInputConstants.Zoom, s_zoomCanceled);
|
||||
Bind(map, BlueprintInputConstants.FocusHome,s_homeHandler);
|
||||
Bind(map, BlueprintInputConstants.Undo, s_undoHandler);
|
||||
Bind(map, BlueprintInputConstants.Redo, s_redoHandler);
|
||||
Bind(map, BlueprintInputConstants.NavigateParent, s_parentHandler);
|
||||
}
|
||||
|
||||
private static void UnbindActions(InputActionMap map)
|
||||
{
|
||||
Unbind(map, BlueprintInputConstants.Pan, s_panHandler);
|
||||
UnbindCancelled(map, BlueprintInputConstants.Pan, s_panCanceled);
|
||||
Unbind(map, BlueprintInputConstants.Zoom, s_zoomHandler);
|
||||
UnbindCancelled(map, BlueprintInputConstants.Zoom, s_zoomCanceled);
|
||||
Unbind(map, BlueprintInputConstants.FocusHome,s_homeHandler);
|
||||
Unbind(map, BlueprintInputConstants.Undo, s_undoHandler);
|
||||
Unbind(map, BlueprintInputConstants.Redo, s_redoHandler);
|
||||
Unbind(map, BlueprintInputConstants.NavigateParent, s_parentHandler);
|
||||
}
|
||||
|
||||
private static void Bind(InputActionMap map, string name, System.Action<InputAction.CallbackContext> handler)
|
||||
{
|
||||
var action = map.FindAction(name);
|
||||
if (action != null)
|
||||
action.performed += handler;
|
||||
}
|
||||
|
||||
private static void Unbind(InputActionMap map, string name, System.Action<InputAction.CallbackContext> handler)
|
||||
{
|
||||
var action = map.FindAction(name);
|
||||
if (action != null)
|
||||
action.performed -= handler;
|
||||
}
|
||||
|
||||
private static void BindCancelled(InputActionMap map, string name, System.Action<InputAction.CallbackContext> handler)
|
||||
{
|
||||
var action = map.FindAction(name);
|
||||
if (action != null)
|
||||
action.canceled += handler;
|
||||
}
|
||||
|
||||
private static void UnbindCancelled(InputActionMap map, string name, System.Action<InputAction.CallbackContext> handler)
|
||||
{
|
||||
var action = map.FindAction(name);
|
||||
if (action != null)
|
||||
action.canceled -= handler;
|
||||
}
|
||||
|
||||
private static void OnActionReceived(string actionName)
|
||||
{
|
||||
if (s_focusedGraph != null)
|
||||
{
|
||||
s_focusedGraph.MarkDirty();
|
||||
BlueprintToolProvider.DispatchAction(s_focusedGraph, actionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,135 +0,0 @@
|
||||
#if !ENABLE_INPUT_SYSTEM
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericLibrary.Runtime.Blueprint
|
||||
{
|
||||
/// <summary>
|
||||
/// 蓝图输入管理器(旧输入系统)—— 通过旧输入系统轮询鼠标和键盘事件。
|
||||
/// <para>
|
||||
/// 提供与 <c>BlueprintInputManager.InputSystem.cs</c> 相同的静态 API 签名,
|
||||
/// 但底层使用 <see cref="Input.GetMouseButton"/>/<see cref="Input.GetKey"/> 等旧 API,
|
||||
/// 不依赖任何新输入系统类型。
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 鼠标点击、拖拽、右键等事件通过 <see cref="PollMouseEvents"/> 轮询后
|
||||
/// 分发到 <see cref="BlueprintToolProvider"/> 的 DispatchXxx 方法。
|
||||
/// 键盘 Pan/Zoom 值通过 <see cref="PollKeyboard"/> 轮询后由
|
||||
/// <see cref="QuickGraphInputTool"/> 在 OnUpdate 中读取。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static class BlueprintInputManager
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前焦点蓝图(快捷键分发目标)。
|
||||
/// 由 <see cref="SetFocusedGraph"/> 设置。
|
||||
/// </summary>
|
||||
private static BlueprintGraph s_focusedGraph;
|
||||
|
||||
/// <summary>设置当前焦点蓝图。</summary>
|
||||
public static void SetFocusedGraph(BlueprintGraph graph)
|
||||
{
|
||||
s_focusedGraph = graph;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册 InputActionAsset(旧系统空操作)。
|
||||
/// 仅保留签名以兼容编译。
|
||||
/// </summary>
|
||||
public static void RegisterAsset(Object asset)
|
||||
{
|
||||
// 旧输入系统:不管理 InputActionAsset
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注销 InputActionAsset(旧系统空操作)。
|
||||
/// 仅保留签名以兼容编译。
|
||||
/// </summary>
|
||||
public static void UnregisterAsset(Object asset)
|
||||
{
|
||||
// 旧输入系统:不管理 InputActionAsset
|
||||
}
|
||||
|
||||
// ===== 键盘轮询值(供 QuickGraphInputTool.OnUpdate 读取) =====
|
||||
|
||||
/// <summary>
|
||||
/// 方向键 Pan 方向值(每帧由 <see cref="PollKeyboard"/> 更新)。
|
||||
/// </summary>
|
||||
public static Vector2 LastPanDirection;
|
||||
|
||||
/// <summary>
|
||||
/// +/- 键 Zoom 轴值(每帧由 <see cref="PollKeyboard"/> 更新)。
|
||||
/// </summary>
|
||||
public static float LastZoomAxis;
|
||||
|
||||
// ===== 事件轮询 =====
|
||||
|
||||
/// <summary>
|
||||
/// 轮询鼠标按键事件。
|
||||
/// 每帧在 Update 中调用,检测鼠标按下/释放并分发到 <see cref="BlueprintToolProvider"/>。
|
||||
/// </summary>
|
||||
/// <param name="mouseButton">0=左键, 1=右键, 2=中键</param>
|
||||
public static void PollMouseEvents(int mouseButton)
|
||||
{
|
||||
if (s_focusedGraph?.Canvas == null) return;
|
||||
|
||||
s_focusedGraph.BeginFrame();
|
||||
|
||||
if (Input.GetMouseButtonDown(mouseButton))
|
||||
{
|
||||
var canvasPoint = s_focusedGraph.Canvas.ScreenToCanvas(Input.mousePosition);
|
||||
BlueprintToolProvider.DispatchPointerDown(s_focusedGraph, canvasPoint, mouseButton);
|
||||
}
|
||||
|
||||
if (Input.GetMouseButtonUp(mouseButton))
|
||||
{
|
||||
var canvasPoint = s_focusedGraph.Canvas.ScreenToCanvas(Input.mousePosition);
|
||||
BlueprintToolProvider.DispatchPointerUp(s_focusedGraph, canvasPoint, mouseButton);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轮询鼠标滚轮事件。
|
||||
/// 每帧在 Update 中调用,检测滚轮增量并分发到 <see cref="BlueprintToolProvider"/>。
|
||||
/// </summary>
|
||||
public static void PollScrollWheel()
|
||||
{
|
||||
if (s_focusedGraph?.Canvas == null) return;
|
||||
|
||||
s_focusedGraph.BeginFrame();
|
||||
|
||||
float scrollY = Input.mouseScrollDelta.y;
|
||||
if (!Mathf.Approximately(scrollY, 0f))
|
||||
{
|
||||
var canvasPoint = s_focusedGraph.Canvas.ScreenToCanvas(Input.mousePosition);
|
||||
var scrollDelta = new Vector2(0f, scrollY);
|
||||
BlueprintToolProvider.DispatchScroll(s_focusedGraph, canvasPoint, scrollDelta);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轮询键盘快捷键(方向键 Pan、+/- Zoom)。
|
||||
/// 每帧在 Update 中调用,更新 <see cref="LastPanDirection"/> 和 <see cref="LastZoomAxis"/>。
|
||||
/// </summary>
|
||||
public static void PollKeyboard()
|
||||
{
|
||||
LastPanDirection = Vector2.zero;
|
||||
if (Input.GetKey(KeyCode.UpArrow) || Input.GetKey(KeyCode.W))
|
||||
LastPanDirection.y += 1f;
|
||||
if (Input.GetKey(KeyCode.DownArrow) || Input.GetKey(KeyCode.S))
|
||||
LastPanDirection.y -= 1f;
|
||||
if (Input.GetKey(KeyCode.LeftArrow) || Input.GetKey(KeyCode.A))
|
||||
LastPanDirection.x -= 1f;
|
||||
if (Input.GetKey(KeyCode.RightArrow) || Input.GetKey(KeyCode.D))
|
||||
LastPanDirection.x += 1f;
|
||||
|
||||
LastZoomAxis = 0f;
|
||||
if (Input.GetKey(KeyCode.Equals) || Input.GetKey(KeyCode.KeypadPlus))
|
||||
LastZoomAxis = 1f;
|
||||
if (Input.GetKey(KeyCode.Minus) || Input.GetKey(KeyCode.KeypadMinus))
|
||||
LastZoomAxis = -1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,110 +0,0 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericLibrary.Runtime.Blueprint
|
||||
{
|
||||
/// <summary>
|
||||
/// 蓝图输入接收工具 —— 继承自 <see cref="BlueprintTool"/>,由 BlueprintToolProvider 统一管理生命周期。
|
||||
/// <para>
|
||||
/// 职责:
|
||||
/// 1. 创建并驱动 <see cref="BlueprintUGUIInputManager"/>,绑定 UGUI EventTrigger 到背景 Image;
|
||||
/// 2. 在 <see cref="OnUpdate"/> 中统一刷新鼠标位置、检测指针进入/离开背景区域;
|
||||
/// 3. 将画布内的指针移动事件分发给 <see cref="BlueprintToolProvider"/>。
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 鼠标按键按下/释放/拖拽/滚轮等由 <see cref="BlueprintUGUIInputManager"/> 通过 EventTrigger 回调直接分发。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[BlueprintTool(phase: ToolPhase.PreUpdate, order: 0)]
|
||||
[BlueprintTheme("QuickGraph")]
|
||||
public class BlueprintInputReceiver : BlueprintTool
|
||||
{
|
||||
private BlueprintUGUIInputManager _uguiManager;
|
||||
private bool _initialized;
|
||||
|
||||
private Vector2 _lastMousePos;
|
||||
private bool _isInside;
|
||||
|
||||
// ===== 生命周期 =====
|
||||
|
||||
public override void OnUpdate(float deltaTime)
|
||||
{
|
||||
if (Graph == null) return;
|
||||
|
||||
// ── 每帧开始:刷新帧缓存 ──
|
||||
Graph.BeginFrame();
|
||||
|
||||
// ── 初始化 UGUI 管理器 ──
|
||||
if (!_initialized)
|
||||
{
|
||||
_uguiManager = new BlueprintUGUIInputManager(Graph);
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
// ── 更新鼠标位置(统一入口) ──
|
||||
BlueprintUGUIInputManager.UpdateMousePosition();
|
||||
BlueprintToolProvider.MousePosition = BlueprintUGUIInputManager.MousePosition;
|
||||
|
||||
// ── 延迟绑定 EventTrigger ──
|
||||
if (!_uguiManager.IsBound)
|
||||
{
|
||||
_uguiManager.TryBind();
|
||||
return;
|
||||
}
|
||||
|
||||
var mp = BlueprintUGUIInputManager.MousePosition;
|
||||
|
||||
// ── 检测指针进入/离开背景 ──
|
||||
bool overBg = IsScreenPointOverBackground(mp);
|
||||
if (overBg && !_isInside)
|
||||
{
|
||||
_isInside = true;
|
||||
BlueprintToolProvider.DispatchPointerEnter(Graph);
|
||||
}
|
||||
else if (!overBg && _isInside)
|
||||
{
|
||||
_isInside = false;
|
||||
BlueprintToolProvider.DispatchPointerExit(Graph);
|
||||
}
|
||||
|
||||
if (!_isInside) return;
|
||||
|
||||
// ── 指针移动(轮询,EventTrigger 无通用移动事件) ──
|
||||
if (Vector2.Distance(_lastMousePos, mp) < 0.001f) return;
|
||||
|
||||
var cur = ScreenToCanvas(mp);
|
||||
var prev = (_lastMousePos == Vector2.zero) ? cur : ScreenToCanvas(_lastMousePos);
|
||||
var delta = cur - prev;
|
||||
_lastMousePos = mp;
|
||||
|
||||
BlueprintToolProvider.DispatchPointerMove(Graph, cur, delta);
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
if (_uguiManager != null)
|
||||
{
|
||||
_uguiManager.Unbind();
|
||||
_uguiManager = null;
|
||||
}
|
||||
_initialized = false;
|
||||
_isInside = false;
|
||||
_lastMousePos = Vector2.zero;
|
||||
}
|
||||
|
||||
// ===== 辅助 =====
|
||||
|
||||
/// <summary>检查屏幕坐标是否落在背景 RectTransform 内。</summary>
|
||||
private bool IsScreenPointOverBackground(Vector2 screenPoint)
|
||||
{
|
||||
var rt = _uguiManager?.BgRectTransform;
|
||||
if (rt == null) return false;
|
||||
return RectTransformUtility.RectangleContainsScreenPoint(rt, screenPoint, null);
|
||||
}
|
||||
|
||||
/// <summary>屏幕坐标 → 画布坐标。</summary>
|
||||
private Vector2 ScreenToCanvas(Vector2 screenPos)
|
||||
{
|
||||
return Graph?.Canvas != null ? Graph.Canvas.ScreenToCanvas(screenPos) : screenPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
#if !ENABLE_INPUT_SYSTEM
|
||||
|
||||
using XericLibrary.Runtime.MacroLibrary;
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericLibrary.Runtime.Blueprint
|
||||
{
|
||||
/// <summary>
|
||||
/// 蓝图输入工具(旧输入系统)—— 自身完成全部鼠标/键盘/快捷键轮询,
|
||||
/// 通过 <c>KfsStyle</c> + <c>KeyFlippingStampSheet</c> 状态机驱动拖拽识别。
|
||||
/// </summary>
|
||||
[BlueprintTool(phase: ToolPhase.PreUpdate, order: 1)]
|
||||
[BlueprintTheme("QuickGraph")]
|
||||
public class BlueprintInputTool_InputManager : BlueprintInputTool_UGUI
|
||||
{
|
||||
// ===== 三键的 sheet 状态表 =====
|
||||
private readonly MacroKey.KeyFlippingStampSheet[] _sheets = new MacroKey.KeyFlippingStampSheet[3];
|
||||
private readonly Vector2[] _lastCanvasPos = new Vector2[3];
|
||||
|
||||
// ===== 快捷键轮询常量 =====
|
||||
private static readonly KeyCode[] s_modifierKeys = new[]
|
||||
{
|
||||
KeyCode.LeftControl, KeyCode.RightControl,
|
||||
KeyCode.LeftShift, KeyCode.RightShift,
|
||||
KeyCode.LeftAlt, KeyCode.RightAlt,
|
||||
};
|
||||
|
||||
// ===== 生命周期 =====
|
||||
|
||||
public override void OnInitialize()
|
||||
{
|
||||
UseEventTriggerForMouse = false;
|
||||
base.OnInitialize();
|
||||
|
||||
// 初始化三键的 sheet(button → KfsStyle → KeyFlippingStampSheet)
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
var style = i.AsKfsStyle();
|
||||
_sheets[i] = style.GetKeySheet();
|
||||
}
|
||||
|
||||
RegisterShortcutHandlers();
|
||||
}
|
||||
|
||||
public override void OnUpdate(float deltaTime)
|
||||
{
|
||||
// 基类:BeginFrame + 鼠标位置 + 光标上下文 + PointerMove
|
||||
base.OnUpdate(deltaTime);
|
||||
|
||||
// 鼠标按键 / 滚轮 / 键盘 Pan/Zoom 轮询
|
||||
PollAll();
|
||||
|
||||
// 快捷键轮询
|
||||
PollShortcuts();
|
||||
}
|
||||
|
||||
// ===== 统一轮询 =====
|
||||
|
||||
private void PollAll()
|
||||
{
|
||||
if (Graph?.Canvas == null) return;
|
||||
var canvas = Graph.Canvas;
|
||||
|
||||
// 三键(由 sheet 状态机驱动)
|
||||
PollMouseButtonSheet(canvas, 0);
|
||||
PollMouseButtonSheet(canvas, 1);
|
||||
PollMouseButtonSheet(canvas, 2);
|
||||
|
||||
// 滚轮
|
||||
float scrollY = Input.mouseScrollDelta.y;
|
||||
if (!Mathf.Approximately(scrollY, 0f))
|
||||
{
|
||||
BlueprintToolProvider.DispatchScroll(Graph, this,
|
||||
canvas.ScreenToCanvas(Input.mousePosition),
|
||||
new Vector2(0f, scrollY));
|
||||
}
|
||||
|
||||
// 键盘 Pan / Zoom
|
||||
GraphInputTool.LastPanDirection = Vector2.zero;
|
||||
float x = 0f, y = 0f;
|
||||
if (Input.GetKey(KeyCode.UpArrow) || Input.GetKey(KeyCode.W)) y += 1f;
|
||||
if (Input.GetKey(KeyCode.DownArrow) || Input.GetKey(KeyCode.S)) y -= 1f;
|
||||
if (Input.GetKey(KeyCode.LeftArrow) || Input.GetKey(KeyCode.A)) x -= 1f;
|
||||
if (Input.GetKey(KeyCode.RightArrow) || Input.GetKey(KeyCode.D)) x += 1f;
|
||||
GraphInputTool.LastPanDirection = new Vector2(x, y);
|
||||
GraphInputTool.LastZoomAxis = 0f;
|
||||
if (Input.GetKey(KeyCode.Equals) || Input.GetKey(KeyCode.KeypadPlus)) GraphInputTool.LastZoomAxis = 1f;
|
||||
if (Input.GetKey(KeyCode.Minus) || Input.GetKey(KeyCode.KeypadMinus)) GraphInputTool.LastZoomAxis = -1f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 sheet 状态机驱动单个鼠标按键的点击/拖拽/释放事件。
|
||||
/// 每帧调用 <c>AppendKeyState_PositionTriggle</c> 更新状态,
|
||||
/// 然后根据 <c>IsPressed / IsDragging / IsDropping / IsReleased</c> 分发到 Provider。
|
||||
/// </summary>
|
||||
private void PollMouseButtonSheet(IBlueprintCanvas canvas, int button)
|
||||
{
|
||||
var sheet = _sheets[button];
|
||||
bool pressed = Input.GetMouseButton(button);
|
||||
sheet.AppendKeyState_PositionTriggle(pressed, Input.mousePosition, false, 0.1f);
|
||||
|
||||
// ── 按下 ──
|
||||
if (sheet.IsPressed)
|
||||
{
|
||||
var pt = canvas.ScreenToCanvas(Input.mousePosition);
|
||||
_lastCanvasPos[button] = pt;
|
||||
BlueprintToolProvider.DispatchPointerDown(Graph, this, pt, button);
|
||||
}
|
||||
|
||||
// ── 拖拽(含首次拖拽帧) ──
|
||||
if (sheet.IsDragging)
|
||||
{
|
||||
var current = canvas.ScreenToCanvas(Input.mousePosition);
|
||||
var delta = current - _lastCanvasPos[button];
|
||||
_lastCanvasPos[button] = current;
|
||||
|
||||
if (delta.sqrMagnitude > 0.0001f)
|
||||
{
|
||||
BlueprintToolProvider.DispatchPointerDrag(Graph, this, current, delta, button);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 释放 ──
|
||||
if (sheet.IsDropping || sheet.IsReleased)
|
||||
{
|
||||
var pt = canvas.ScreenToCanvas(Input.mousePosition);
|
||||
bool isEmptyClick = sheet.IsReleased && Graph?.HitTest(pt) == null;
|
||||
BlueprintToolProvider.DispatchPointerUp(Graph, this, pt, button, isEmptyClick);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 快捷键动作注册 =====
|
||||
|
||||
private void RegisterShortcutHandlers()
|
||||
{
|
||||
var tree = MacroKey.ShortcutTreeInstance;
|
||||
RegisterAction(tree, BlueprintInputConstants.FocusHome);
|
||||
RegisterAction(tree, BlueprintInputConstants.Undo);
|
||||
RegisterAction(tree, BlueprintInputConstants.Redo);
|
||||
RegisterAction(tree, BlueprintInputConstants.DeleteSelected);
|
||||
RegisterAction(tree, BlueprintInputConstants.SelectAll);
|
||||
RegisterAction(tree, BlueprintInputConstants.Duplicate);
|
||||
RegisterAction(tree, BlueprintInputConstants.Copy);
|
||||
RegisterAction(tree, BlueprintInputConstants.Paste);
|
||||
RegisterAction(tree, BlueprintInputConstants.Cut);
|
||||
RegisterAction(tree, BlueprintInputConstants.Save);
|
||||
}
|
||||
|
||||
private void RegisterAction(ShortcutTree tree, string alias)
|
||||
{
|
||||
tree.RegisterAlias(alias, () => BlueprintToolProvider.DispatchAction(Graph, this, alias));
|
||||
tree.RebindAlias(alias, () => BlueprintToolProvider.DispatchAction(Graph, this, alias));
|
||||
}
|
||||
|
||||
// ===== 快捷键轮询 =====
|
||||
|
||||
private void PollShortcuts()
|
||||
{
|
||||
if (!Input.anyKeyDown) return;
|
||||
|
||||
foreach (KeyCode key in System.Enum.GetValues(typeof(KeyCode)))
|
||||
{
|
||||
if (IsModifier(key)) continue;
|
||||
if (!Input.GetKeyDown(key)) continue;
|
||||
|
||||
bool ctrl = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl);
|
||||
bool shift = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
|
||||
bool alt = Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt);
|
||||
char keyChar = KeyCodeToChar(key);
|
||||
|
||||
MacroKey.ShortcutTreeInstance.CallShortcut(shift, ctrl, alt, keyChar);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsModifier(KeyCode key)
|
||||
{
|
||||
foreach (var mod in s_modifierKeys)
|
||||
if (key == mod) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static char KeyCodeToChar(KeyCode key)
|
||||
{
|
||||
if (key >= KeyCode.A && key <= KeyCode.Z)
|
||||
return (char)('A' + (key - KeyCode.A));
|
||||
if (key >= KeyCode.Alpha0 && key <= KeyCode.Alpha9)
|
||||
return (char)('0' + (key - KeyCode.Alpha0));
|
||||
if (key >= KeyCode.Keypad0 && key <= KeyCode.Keypad9)
|
||||
return (char)('0' + (key - KeyCode.Keypad0));
|
||||
return (char)key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e1d0771b4cc2aa4692eff637fa5d5af
|
||||
guid: 53cd1e0c9567eee4a8ca246adee6510f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -0,0 +1,160 @@
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
|
||||
using XericLibrary.Runtime.MacroLibrary;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace XericLibrary.Runtime.Blueprint
|
||||
{
|
||||
/// <summary>
|
||||
/// 蓝图输入工具(新输入系统)—— 自身管理 InputActionAsset 绑定和快捷键回调,
|
||||
/// 不再依赖 <c>BlueprintInputManager</c> 管理器。
|
||||
/// </summary>
|
||||
[BlueprintTool(phase: ToolPhase.PreUpdate, order: 1)]
|
||||
[BlueprintTheme("QuickGraph")]
|
||||
public class BlueprintInputTool_InputSystem : BlueprintInputTool_UGUI
|
||||
{
|
||||
private InputActionAsset _inputActionAsset;
|
||||
private InputActionMap _activeMap;
|
||||
|
||||
// 缓存的动作回调实例
|
||||
private System.Action<InputAction.CallbackContext> _panHandler;
|
||||
private System.Action<InputAction.CallbackContext> _panCanceled;
|
||||
private System.Action<InputAction.CallbackContext> _zoomHandler;
|
||||
private System.Action<InputAction.CallbackContext> _zoomCanceled;
|
||||
private System.Action<InputAction.CallbackContext> _homeHandler;
|
||||
private System.Action<InputAction.CallbackContext> _undoHandler;
|
||||
private System.Action<InputAction.CallbackContext> _redoHandler;
|
||||
private System.Action<InputAction.CallbackContext> _parentHandler;
|
||||
|
||||
// ===== InputActionAsset 属性 =====
|
||||
|
||||
public InputActionAsset InputActionAsset
|
||||
{
|
||||
get => _inputActionAsset;
|
||||
set
|
||||
{
|
||||
if (_inputActionAsset == value) return;
|
||||
UnbindCurrent();
|
||||
_inputActionAsset = value;
|
||||
if (_inputActionAsset != null && Graph != null)
|
||||
BindCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 生命周期 =====
|
||||
|
||||
public override void OnInitialize()
|
||||
{
|
||||
// 创建回调委托(确保 Bind/Unbind 使用同一引用)
|
||||
_panHandler = ctx => GraphInputTool.LastPanDirection = ctx.ReadValue<Vector2>();
|
||||
_panCanceled = _ => GraphInputTool.LastPanDirection = Vector2.zero;
|
||||
_zoomHandler = ctx => GraphInputTool.LastZoomAxis = ctx.ReadValue<float>();
|
||||
_zoomCanceled = _ => GraphInputTool.LastZoomAxis = 0f;
|
||||
_homeHandler = _ => DispatchShortcut(BlueprintInputConstants.FocusHome);
|
||||
_undoHandler = _ => DispatchShortcut(BlueprintInputConstants.Undo);
|
||||
_redoHandler = _ => DispatchShortcut(BlueprintInputConstants.Redo);
|
||||
_parentHandler = _ => DispatchShortcut(BlueprintInputConstants.NavigateParent);
|
||||
|
||||
base.OnInitialize();
|
||||
RegisterShortcutHandlers();
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
UnbindCurrent();
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
// ===== ActionMap 绑定/解绑 =====
|
||||
|
||||
private void BindCurrent()
|
||||
{
|
||||
if (_inputActionAsset == null) return;
|
||||
var map = _inputActionAsset.FindActionMap(BlueprintInputConstants.MapName);
|
||||
if (map == null) return;
|
||||
|
||||
map.Enable();
|
||||
Bind(map, BlueprintInputConstants.Pan, _panHandler);
|
||||
BindCancel(map, BlueprintInputConstants.Pan, _panCanceled);
|
||||
Bind(map, BlueprintInputConstants.Zoom, _zoomHandler);
|
||||
BindCancel(map, BlueprintInputConstants.Zoom, _zoomCanceled);
|
||||
Bind(map, BlueprintInputConstants.FocusHome, _homeHandler);
|
||||
Bind(map, BlueprintInputConstants.Undo, _undoHandler);
|
||||
Bind(map, BlueprintInputConstants.Redo, _redoHandler);
|
||||
Bind(map, BlueprintInputConstants.NavigateParent, _parentHandler);
|
||||
_activeMap = map;
|
||||
}
|
||||
|
||||
private void UnbindCurrent()
|
||||
{
|
||||
if (_activeMap == null) return;
|
||||
Unbind(_activeMap, BlueprintInputConstants.Pan, _panHandler);
|
||||
UnbindCancel(_activeMap, BlueprintInputConstants.Pan, _panCanceled);
|
||||
Unbind(_activeMap, BlueprintInputConstants.Zoom, _zoomHandler);
|
||||
UnbindCancel(_activeMap, BlueprintInputConstants.Zoom, _zoomCanceled);
|
||||
Unbind(_activeMap, BlueprintInputConstants.FocusHome, _homeHandler);
|
||||
Unbind(_activeMap, BlueprintInputConstants.Undo, _undoHandler);
|
||||
Unbind(_activeMap, BlueprintInputConstants.Redo, _redoHandler);
|
||||
Unbind(_activeMap, BlueprintInputConstants.NavigateParent, _parentHandler);
|
||||
_activeMap.Disable();
|
||||
_activeMap = null;
|
||||
}
|
||||
|
||||
private static void Bind(InputActionMap map, string name, System.Action<InputAction.CallbackContext> handler)
|
||||
{
|
||||
var action = map.FindAction(name);
|
||||
if (action != null) action.performed += handler;
|
||||
}
|
||||
|
||||
private static void Unbind(InputActionMap map, string name, System.Action<InputAction.CallbackContext> handler)
|
||||
{
|
||||
var action = map.FindAction(name);
|
||||
if (action != null) action.performed -= handler;
|
||||
}
|
||||
|
||||
private static void BindCancel(InputActionMap map, string name, System.Action<InputAction.CallbackContext> handler)
|
||||
{
|
||||
var action = map.FindAction(name);
|
||||
if (action != null) action.canceled += handler;
|
||||
}
|
||||
|
||||
private static void UnbindCancel(InputActionMap map, string name, System.Action<InputAction.CallbackContext> handler)
|
||||
{
|
||||
var action = map.FindAction(name);
|
||||
if (action != null) action.canceled -= handler;
|
||||
}
|
||||
|
||||
private void DispatchShortcut(string actionName)
|
||||
{
|
||||
if (Graph == null) return;
|
||||
Graph.MarkDirty();
|
||||
BlueprintToolProvider.DispatchAction(Graph, this, actionName);
|
||||
}
|
||||
|
||||
// ===== 快捷键树注册 =====
|
||||
|
||||
private void RegisterShortcutHandlers()
|
||||
{
|
||||
var tree = MacroKey.ShortcutTreeInstance;
|
||||
RegisterAction(tree, BlueprintInputConstants.Undo);
|
||||
RegisterAction(tree, BlueprintInputConstants.Redo);
|
||||
RegisterAction(tree, BlueprintInputConstants.FocusHome);
|
||||
RegisterAction(tree, BlueprintInputConstants.DeleteSelected);
|
||||
RegisterAction(tree, BlueprintInputConstants.SelectAll);
|
||||
RegisterAction(tree, BlueprintInputConstants.Duplicate);
|
||||
RegisterAction(tree, BlueprintInputConstants.Copy);
|
||||
RegisterAction(tree, BlueprintInputConstants.Paste);
|
||||
RegisterAction(tree, BlueprintInputConstants.Cut);
|
||||
RegisterAction(tree, BlueprintInputConstants.Save);
|
||||
}
|
||||
|
||||
private void RegisterAction(ShortcutTree tree, string alias)
|
||||
{
|
||||
tree.RegisterAlias(alias, () => BlueprintToolProvider.DispatchAction(Graph, this, alias));
|
||||
tree.RebindAlias(alias, () => BlueprintToolProvider.DispatchAction(Graph, this, alias));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 72f3c53c414fbef4b80270adb10afe1d
|
||||
guid: 43b72153b4945b14da4fb0464d0a2e22
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -0,0 +1,278 @@
|
||||
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";
|
||||
|
||||
// 上一帧的鼠标画布坐标(用于计算 PointerMove delta)
|
||||
private Vector2 _lastCanvasPos;
|
||||
|
||||
/// <summary>
|
||||
/// 是否通过 EventTrigger 处理鼠标按键/拖拽/滚轮事件。
|
||||
/// 新输入系统路径下为 true(默认),旧输入系统路径下为 false(由 PollUpdate 轮询)。
|
||||
/// 子类可在 <see cref="OnInitialize"/> 中修改此值。
|
||||
/// </summary>
|
||||
protected bool UseEventTriggerForMouse { get; set; } = true;
|
||||
|
||||
// ===== 生命周期 =====
|
||||
|
||||
public override void OnInitialize()
|
||||
{
|
||||
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 != null
|
||||
? Graph.Canvas.ScreenToCanvas(BlueprintUGUIInputManager.MousePosition)
|
||||
: Vector2.zero;
|
||||
UpdateCursorContext(canvasPt);
|
||||
|
||||
// ── 每帧分发 PointerMove ──
|
||||
var delta = canvasPt - _lastCanvasPos;
|
||||
if (delta.sqrMagnitude > 0.0001f || _lastCanvasPos == Vector2.zero)
|
||||
{
|
||||
BlueprintToolProvider.DispatchPointerMove(Graph, this, canvasPt, delta);
|
||||
}
|
||||
_lastCanvasPos = canvasPt;
|
||||
}
|
||||
|
||||
/// <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()
|
||||
{
|
||||
UnbindEventTrigger();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保场景中存在 EventSystem 和合适的 InputModule。
|
||||
/// 旧输入系统 → StandaloneInputModule。
|
||||
/// 新输入系统 → InputSystemUIInputModule(由 GraphTheoryBlueprintComponent 创建)。
|
||||
/// 不会重复创建模块(通过 GetComponent 检查)。
|
||||
/// </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<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 Vector2 _dragStartPos;
|
||||
private bool _hadDragThisClick;
|
||||
|
||||
private void OnBeginDragEvent(BaseEventData data)
|
||||
{
|
||||
if (!(data is PointerEventData pData)) return;
|
||||
_dragStartPos = PointerCanvasPos(pData);
|
||||
_hadDragThisClick = true;
|
||||
}
|
||||
|
||||
private void OnDragEvent(BaseEventData data)
|
||||
{
|
||||
if (!(data is PointerEventData pData)) return;
|
||||
var current = PointerCanvasPos(pData);
|
||||
var delta = current - _dragStartPos;
|
||||
_dragStartPos = current;
|
||||
UpdateCursorContext(current);
|
||||
BlueprintToolProvider.DispatchPointerDrag(Graph, this, current, delta, (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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f94468ab3403984494aa0d980297093
|
||||
guid: ff8955aaf6e6c2447ab2c9c264a4fc05
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -1,45 +1,19 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericLibrary.Runtime.Blueprint
|
||||
{
|
||||
/// <summary>
|
||||
/// 蓝图 UGUI 输入管理器 —— 负责绑定 UGUI EventTrigger 到背景 Image,
|
||||
/// 将 UGUI 指针事件转发到 <see cref="BlueprintToolProvider"/> 的分发方法。
|
||||
/// <para>
|
||||
/// 该类仅处理与 UGUI EventTrigger 相关的绑定逻辑,不感知新旧输入系统的差异。
|
||||
/// 由 <see cref="BlueprintInputReceiver"/> 创建并在 OnUpdate 中驱动。
|
||||
/// </para>
|
||||
/// 蓝图 UGUI 输入工具静态辅助类。
|
||||
/// 提供跨新旧输入系统的统一鼠标位置和按钮状态查询。
|
||||
/// </summary>
|
||||
public class BlueprintUGUIInputManager
|
||||
public static class BlueprintUGUIInputManager
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前帧鼠标/指针在屏幕空间的位置。
|
||||
/// 每帧由 <see cref="UpdateMousePosition"/> 刷新。
|
||||
/// </summary>
|
||||
public static Vector2 MousePosition { get; private set; }
|
||||
/// <summary>当前帧鼠标/指针在屏幕空间的位置,每帧由 BlueprintInputTool_UGUI 更新。</summary>
|
||||
public static Vector2 MousePosition { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前帧鼠标/指针是否处于新输入系统模式。
|
||||
/// <c>true</c> = 新输入系统(<c>ENABLE_INPUT_SYSTEM</c> 已定义);
|
||||
/// <c>false</c> = 旧输入系统。
|
||||
/// </summary>
|
||||
public static bool IsNewInputSystem
|
||||
{
|
||||
get
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新静态鼠标位置。每帧由 BlueprintInputReceiver 的 OnUpdate 调用。
|
||||
/// 内部根据条件编译选择鼠标位置读取源。
|
||||
/// 更新静态鼠标位置。
|
||||
/// 由 <see cref="BlueprintInputTool_UGUI"/> 每帧在 OnUpdate 中调用。
|
||||
/// </summary>
|
||||
public static void UpdateMousePosition()
|
||||
{
|
||||
@@ -52,7 +26,6 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
|
||||
/// <summary>
|
||||
/// 返回当前是否按住指定鼠标按钮。
|
||||
/// 内部根据条件编译选择检测方式。
|
||||
/// </summary>
|
||||
/// <param name="button">0=左键, 1=右键, 2=中键</param>
|
||||
public static bool IsButtonPressed(int button)
|
||||
@@ -71,169 +44,5 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
return Input.GetMouseButton(button);
|
||||
#endif
|
||||
}
|
||||
|
||||
// ===== 实例部分 =====
|
||||
|
||||
private BlueprintGraph _graph;
|
||||
private Transform _bgTransform;
|
||||
private EventTrigger _eventTrigger;
|
||||
private bool _isBound;
|
||||
|
||||
// 背景 GameObject 名称(与 QuickUGUIGraphGridBackgroundTool 一致)
|
||||
private const string BackgroundGoName = "__Bp_GridBackground";
|
||||
|
||||
// ===== 生命周期 =====
|
||||
|
||||
/// <summary>初始化 UGUI 输入管理器并绑定到指定蓝图。</summary>
|
||||
public BlueprintUGUIInputManager(BlueprintGraph graph)
|
||||
{
|
||||
_graph = graph;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试绑定 EventTrigger 到背景 Image。
|
||||
/// 返回 <c>true</c> 表示绑定成功,<c>false</c> 表示背景尚未创建。
|
||||
/// </summary>
|
||||
public bool TryBind()
|
||||
{
|
||||
if (_isBound) return true;
|
||||
if (_graph?.Canvas == null) return false;
|
||||
|
||||
var root = _graph.Canvas.GetRootTransform();
|
||||
if (root == null) return false;
|
||||
|
||||
_bgTransform = root.Find(BackgroundGoName);
|
||||
if (_bgTransform == null) return false;
|
||||
|
||||
_eventTrigger = _bgTransform.gameObject.GetComponent<EventTrigger>();
|
||||
if (_eventTrigger == null)
|
||||
_eventTrigger = _bgTransform.gameObject.AddComponent<EventTrigger>();
|
||||
else
|
||||
_eventTrigger.triggers.Clear();
|
||||
|
||||
BindEventTrigger();
|
||||
_isBound = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>解绑并清理 EventTrigger。</summary>
|
||||
public void Unbind()
|
||||
{
|
||||
if (_eventTrigger != null)
|
||||
{
|
||||
_eventTrigger.triggers.Clear();
|
||||
_eventTrigger = null;
|
||||
}
|
||||
_bgTransform = null;
|
||||
_isBound = false;
|
||||
}
|
||||
|
||||
/// <summary>是否已绑定。</summary>
|
||||
public bool IsBound => _isBound;
|
||||
|
||||
/// <summary>背景 RectTransform(用于屏幕点包含检测)。</summary>
|
||||
public RectTransform BgRectTransform => _bgTransform as RectTransform;
|
||||
|
||||
// ===== EventTrigger 绑定 =====
|
||||
|
||||
private void BindEventTrigger()
|
||||
{
|
||||
if (_eventTrigger == null) return;
|
||||
|
||||
AddEntry(EventTriggerType.PointerEnter, OnEventPointerEnter);
|
||||
AddEntry(EventTriggerType.PointerExit, OnEventPointerExit);
|
||||
AddEntry(EventTriggerType.PointerDown, OnEventPointerDown);
|
||||
AddEntry(EventTriggerType.PointerUp, OnEventPointerUp);
|
||||
AddEntry(EventTriggerType.BeginDrag, OnEventBeginDrag);
|
||||
AddEntry(EventTriggerType.Drag, OnEventDrag);
|
||||
AddEntry(EventTriggerType.EndDrag, OnEventEndDrag);
|
||||
AddEntry(EventTriggerType.Scroll, OnEventScroll);
|
||||
}
|
||||
|
||||
private void AddEntry(EventTriggerType eventType, UnityEngine.Events.UnityAction<BaseEventData> callback)
|
||||
{
|
||||
var entry = new EventTrigger.Entry { eventID = eventType };
|
||||
entry.callback.AddListener(callback);
|
||||
_eventTrigger.triggers.Add(entry);
|
||||
}
|
||||
|
||||
// ===== 坐标转换 =====
|
||||
|
||||
private Vector2 ScreenToCanvas(Vector2 screenPos)
|
||||
{
|
||||
return _graph?.Canvas != null ? _graph.Canvas.ScreenToCanvas(screenPos) : screenPos;
|
||||
}
|
||||
|
||||
private Vector2 ScreenToCanvas(PointerEventData data)
|
||||
{
|
||||
return ScreenToCanvas(data.position);
|
||||
}
|
||||
|
||||
// ===== EventTrigger 回调 =====
|
||||
|
||||
private void OnEventPointerEnter(BaseEventData _)
|
||||
{
|
||||
if (_graph != null)
|
||||
BlueprintToolProvider.DispatchPointerEnter(_graph);
|
||||
}
|
||||
|
||||
private void OnEventPointerExit(BaseEventData _)
|
||||
{
|
||||
if (_graph != null)
|
||||
BlueprintToolProvider.DispatchPointerExit(_graph);
|
||||
}
|
||||
|
||||
private void OnEventPointerDown(BaseEventData data)
|
||||
{
|
||||
if (_graph == null || !(data is PointerEventData pData)) return;
|
||||
_graph.BeginFrame();
|
||||
var pt = ScreenToCanvas(pData);
|
||||
_graph.MarkDirty();
|
||||
BlueprintToolProvider.DispatchPointerDown(_graph, pt, (int)pData.button);
|
||||
}
|
||||
|
||||
private void OnEventPointerUp(BaseEventData data)
|
||||
{
|
||||
if (_graph == null || !(data is PointerEventData pData)) return;
|
||||
var pt = ScreenToCanvas(pData);
|
||||
BlueprintToolProvider.DispatchPointerUp(_graph, pt, (int)pData.button);
|
||||
}
|
||||
|
||||
// 拖拽状态
|
||||
private int _dragButton = -1;
|
||||
private Vector2 _dragStartPos;
|
||||
|
||||
private void OnEventBeginDrag(BaseEventData data)
|
||||
{
|
||||
if (_graph == null || !(data is PointerEventData pData)) return;
|
||||
_dragButton = (int)pData.button;
|
||||
_dragStartPos = ScreenToCanvas(pData);
|
||||
}
|
||||
|
||||
private void OnEventDrag(BaseEventData data)
|
||||
{
|
||||
if (_graph == null || !(data is PointerEventData pData)) return;
|
||||
var current = ScreenToCanvas(pData);
|
||||
var delta = current - _dragStartPos;
|
||||
_dragStartPos = current;
|
||||
BlueprintToolProvider.DispatchPointerDrag(_graph, current, delta, _dragButton);
|
||||
}
|
||||
|
||||
private void OnEventEndDrag(BaseEventData data)
|
||||
{
|
||||
if (_graph == null || !(data is PointerEventData pData)) return;
|
||||
BlueprintToolProvider.DispatchPointerUp(_graph, ScreenToCanvas(pData), (int)pData.button);
|
||||
_dragButton = -1;
|
||||
}
|
||||
|
||||
private void OnEventScroll(BaseEventData data)
|
||||
{
|
||||
if (_graph == null || !(data is PointerEventData pData)) return;
|
||||
_graph.BeginFrame();
|
||||
var pt = ScreenToCanvas(pData);
|
||||
var scroll = new Vector2(-pData.scrollDelta.x, -pData.scrollDelta.y);
|
||||
_graph.MarkDirty();
|
||||
BlueprintToolProvider.DispatchScroll(_graph, pt, scroll);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,24 +66,21 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
_viewInitialized = true;
|
||||
}
|
||||
|
||||
// ── 键盘轮询(新输入系统 Pan 动作值) ──
|
||||
_targetPanOffset += BlueprintInputManager.LastPanDirection
|
||||
// ── 键盘 Pan ──
|
||||
_targetPanOffset += GraphInputTool.LastPanDirection
|
||||
* cfg.KeyboardPanSpeed * deltaTime;
|
||||
|
||||
// ── 键盘轮询(新输入系统 Zoom 动作值) ──
|
||||
// zoomInput 对键盘按键是 ±1(按下一下),对轴是 0~1 连续值。
|
||||
// 统一按一个 scroll notch = 120 缩放,scale = 120 / ZoomDivisor
|
||||
float zoomInput = BlueprintInputManager.LastZoomAxis;
|
||||
// ── 键盘 / 滚轮 Zoom ──
|
||||
float zoomInput = GraphInputTool.LastZoomAxis;
|
||||
if (!Mathf.Approximately(zoomInput, 0f))
|
||||
{
|
||||
float oldZoom = _targetZoomLevel;
|
||||
float zoomDelta = zoomInput / cfg.ZoomDivisor;
|
||||
_targetZoomLevel = Mathf.Clamp(
|
||||
_targetZoomLevel + zoomInput / cfg.ZoomDivisor,
|
||||
_targetZoomLevel + zoomDelta,
|
||||
canvas.MinZoom, canvas.MaxZoom);
|
||||
// 以鼠标焦点为中心缩放(键盘无光标,从鼠标读取)
|
||||
ApplyZoomFocus(ref _targetPanOffset, _targetZoomLevel, oldZoom);
|
||||
// 归零,避免持续累加
|
||||
BlueprintInputManager.LastZoomAxis = 0f;
|
||||
GraphInputTool.LastZoomAxis = 0f;
|
||||
}
|
||||
|
||||
// ── 平滑插值到目标值 ──
|
||||
@@ -103,7 +100,7 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
|
||||
// ===== 鼠标移动 =====
|
||||
|
||||
public override void OnPointerMove(Vector2 canvasPoint, Vector2 delta)
|
||||
public override void OnPointerMove(GraphInputTool sender, Vector2 canvasPoint, Vector2 delta)
|
||||
{
|
||||
if (Graph == null || _isDragging) return;
|
||||
|
||||
@@ -113,22 +110,21 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
|
||||
// ===== 鼠标按下 =====
|
||||
|
||||
public override void OnPointerDown(Vector2 canvasPoint, int mouseButton)
|
||||
public override void OnPointerDown(GraphInputTool sender, Vector2 canvasPoint, int mouseButton)
|
||||
{
|
||||
if (Graph == null) return;
|
||||
|
||||
if (mouseButton == 2) // 中键 → 平移
|
||||
if (mouseButton == 2)
|
||||
{
|
||||
_isDragging = true;
|
||||
_activeDragButton = 2;
|
||||
_dragStartPan = _targetPanOffset;
|
||||
}
|
||||
// 左键点击选择由元素层的 IBlueprintEventHandler.OnPointerDown 处理
|
||||
}
|
||||
|
||||
// ===== 鼠标释放 =====
|
||||
|
||||
public override void OnPointerUp(Vector2 canvasPoint, int mouseButton)
|
||||
public override void OnPointerUp(GraphInputTool sender, Vector2 canvasPoint, int mouseButton)
|
||||
{
|
||||
if (_isDragging && _activeDragButton == mouseButton)
|
||||
{
|
||||
@@ -139,20 +135,20 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
|
||||
// ===== 拖拽 =====
|
||||
|
||||
public override void OnPointerDrag(Vector2 canvasPoint, Vector2 delta, int mouseButton)
|
||||
public override void OnPointerDrag(GraphInputTool sender, Vector2 canvasPoint, Vector2 delta, int mouseButton)
|
||||
{
|
||||
if (Graph?.Canvas == null) return;
|
||||
|
||||
if (mouseButton == 2) // 中键拖拽 = 平移
|
||||
if (mouseButton == 2)
|
||||
{
|
||||
_targetPanOffset += delta * Graph.Canvas.ZoomLevel;
|
||||
_targetPanOffset += delta * Config.DragPanSpeed;
|
||||
Graph.MarkDirty();
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 滚轮 =====
|
||||
|
||||
public override void OnScroll(Vector2 canvasPoint, Vector2 scrollDelta)
|
||||
public override void OnScroll(GraphInputTool sender, Vector2 canvasPoint, Vector2 scrollDelta)
|
||||
{
|
||||
if (Graph == null) return;
|
||||
|
||||
@@ -164,15 +160,14 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
|
||||
if (!Mathf.Approximately(oldZoom, _targetZoomLevel))
|
||||
{
|
||||
// 以鼠标光标为中心缩放
|
||||
_targetPanOffset += canvasPoint * (oldZoom - _targetZoomLevel);
|
||||
Graph.MarkDirty();
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 快捷键(仅新输入系统) =====
|
||||
// ===== 快捷键 =====
|
||||
|
||||
public override void OnAction(string actionName)
|
||||
public override void OnAction(GraphInputTool sender, string actionName)
|
||||
{
|
||||
if (Graph?.Canvas == null) return;
|
||||
|
||||
@@ -182,11 +177,11 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
ResetView();
|
||||
break;
|
||||
case BlueprintInputConstants.Undo:
|
||||
break; // TODO
|
||||
break;
|
||||
case BlueprintInputConstants.Redo:
|
||||
break; // TODO
|
||||
break;
|
||||
case BlueprintInputConstants.NavigateParent:
|
||||
break; // TODO
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,9 +31,14 @@ namespace XericLibrary.Runtime.Blueprint.Render
|
||||
[Range(1f, 30f)]
|
||||
public float ZoomLerpSpeed = 8f;
|
||||
|
||||
[Tooltip("滚轮缩放除数。公式:zoom += scrollDelta / ZoomDivisor。\nUnity Input System 的 scrollDelta.y 标准值为 120/格,\n1200 = 每格 +0.1,600 = 每格 +0.2,300 = 每格 +0.4")]
|
||||
[Range(100f, 5000f)]
|
||||
public float ZoomDivisor = 1200f;
|
||||
[Tooltip("滚轮缩放除数。公式:zoom += scrollDelta / ZoomDivisor。\n旧输入系统 scrollDelta.y 约 ±3,默认 30 = 每格 ±0.1")]
|
||||
[Range(1f, 200f)]
|
||||
public float ZoomDivisor = 30f;
|
||||
|
||||
[Header("拖拽平移")]
|
||||
[Tooltip("中键拖拽平移速度系数。1=鼠标移动 1px,画布移动 1px(经缩放校正)")]
|
||||
[Range(0.1f, 10f)]
|
||||
public float DragPanSpeed = 1f;
|
||||
|
||||
[Header("缩放范围")]
|
||||
[Tooltip("最小缩放级别")]
|
||||
@@ -48,13 +53,13 @@ namespace XericLibrary.Runtime.Blueprint.Render
|
||||
[Range(50f, 2000f)]
|
||||
public float KeyboardPanSpeed = 600f;
|
||||
|
||||
[Tooltip("键盘 +/- 缩放速度(每次帧增量,建议 0.01~0.5)")]
|
||||
[Tooltip("键盘 +/- 缩放速度(与 ZoomDivisor 配合,公式:zoom += 1 / ZoomDivisor)")]
|
||||
[Range(0.005f, 0.5f)]
|
||||
public float KeyboardZoomSpeed = 0.05f;
|
||||
|
||||
[Header("框选")]
|
||||
[Tooltip("框选拖拽触发阈值(像素)")]
|
||||
[Range(1f, 20f)]
|
||||
public float DragThreshold = 5f;
|
||||
[Tooltip("框选拖拽触发阈值(画布单位)。拖拽距离超过此值后才显示选框并开始框选")]
|
||||
[Range(1f, 100f)]
|
||||
public float BoxSelectThreshold = 10f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,21 @@ namespace XericLibrary.Runtime.Blueprint.Render
|
||||
{
|
||||
/// <summary>
|
||||
/// 快速 UGUI 图论网格背景渲染工具。
|
||||
/// 在蓝图画布最底层创建一个全屏 Image 组件绘制网格背景,
|
||||
/// 在蓝图画布最底层创建一个 Image 组件绘制网格背景,
|
||||
/// 每帧将画布的 zoom / pan 同步到 Shader 的 _Transform 属性。
|
||||
/// 全部可调参数由 QuickGraphGridBackgroundConfig 配置资产提供。
|
||||
/// <para>
|
||||
/// 背景 Image 在 <see cref="__Bp_RenderRoot"/> 下锚点撑满,
|
||||
/// 只响应画布属性和 dirty flag,不会因画布重建而销毁(见 <see cref="OnInitialize"/>)。
|
||||
/// Shader 参数(<c>_Transform</c> 的 scale/offset)在 <see cref="OnRender"/> 中随
|
||||
/// <c>ZoomLevel / PanOffset</c> 更新,与画布拖拽共享同一属性源。
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>材质引用注意:</b>
|
||||
/// 必须缓存材质实例(<see cref="_backgroundMaterial"/>)并直接写入该实例。
|
||||
/// UGUI 的 <c>Graphic.material</c> getter 可能返回 <c>materialForRendering</c>
|
||||
/// 的副本(与 setter 存入的 <c>m_Material</c> 不同),因此 <c>_backgroundImage.material</c>
|
||||
/// 不适合用于写 shader 属性。见 <c>Graphic.material</c> Unity 源码。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[BlueprintTool(phase: ToolPhase.Render, order: 0)]
|
||||
[BlueprintTheme("QuickGraph")]
|
||||
@@ -30,7 +42,13 @@ namespace XericLibrary.Runtime.Blueprint.Render
|
||||
}
|
||||
|
||||
private Image _backgroundImage;
|
||||
private Transform _renderRoot;
|
||||
|
||||
/// <summary>
|
||||
/// 缓存的背景材质实例。
|
||||
/// 由 <see cref="Config.GetOrCreateMaterial"/> 创建,终身保留。
|
||||
/// shader 属性始终写入此实例,避免 UGUI <c>materialForRendering</c> 副本问题。
|
||||
/// </summary>
|
||||
private Material _backgroundMaterial;
|
||||
|
||||
public override void OnInitialize()
|
||||
{
|
||||
@@ -44,14 +62,18 @@ namespace XericLibrary.Runtime.Blueprint.Render
|
||||
SyncMaterialProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在 RenderRoot 下查找或创建背景 Image。
|
||||
/// 创建后终身保留,不随画布重建销毁。
|
||||
/// </summary>
|
||||
private void EnsureBackground()
|
||||
{
|
||||
if (_backgroundImage != null) return;
|
||||
if (Graph?.Canvas?.GetRootTransform() == null) return;
|
||||
|
||||
EnsureRenderRoot();
|
||||
if (_renderRoot == null) return;
|
||||
var root = Graph.Canvas.GetRootTransform();
|
||||
|
||||
var existing = _renderRoot.Find(BackgroundGoName);
|
||||
var existing = root.Find(BackgroundGoName);
|
||||
if (existing != null)
|
||||
{
|
||||
_backgroundImage = existing.GetComponent<Image>();
|
||||
@@ -68,7 +90,7 @@ namespace XericLibrary.Runtime.Blueprint.Render
|
||||
|
||||
var bgGo = new GameObject(BackgroundGoName, typeof(RectTransform));
|
||||
bgGo.hideFlags = HideFlags.HideAndDontSave;
|
||||
bgGo.transform.SetParent(_renderRoot, false);
|
||||
bgGo.transform.SetParent(root, false);
|
||||
bgGo.transform.SetAsFirstSibling();
|
||||
|
||||
var rt = bgGo.GetComponent<RectTransform>();
|
||||
@@ -79,45 +101,44 @@ namespace XericLibrary.Runtime.Blueprint.Render
|
||||
|
||||
_backgroundImage = bgGo.AddComponent<Image>();
|
||||
_backgroundImage.raycastTarget = true;
|
||||
// 关闭 maskable:运行时背景 Image 不参与 UGUI Mask 裁剪,
|
||||
// 避免父级 Mask 组件触发 materialForRendering 克隆材质实例。
|
||||
// 材质写入(_Transform 等 shader 属性)直通 GPU,不受 IMaterialModifier 干扰。
|
||||
_backgroundImage.maskable = false;
|
||||
|
||||
ApplyMaterial();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从配置获取材质并设置到 Image(仅首次执行)。
|
||||
/// 从配置获取材质并缓存到 <see cref="_backgroundMaterial"/>,
|
||||
/// 然后赋值给 Image。
|
||||
/// 此方法仅执行一次,后续材质写入直接使用缓存的引用。
|
||||
/// </summary>
|
||||
private void ApplyMaterial()
|
||||
{
|
||||
if (_backgroundImage == null) return;
|
||||
var mat = Config.GetOrCreateMaterial();
|
||||
if (mat != null)
|
||||
_backgroundImage.material = mat;
|
||||
if (_backgroundMaterial != null) return;
|
||||
|
||||
_backgroundMaterial = Config.GetOrCreateMaterial();
|
||||
if (_backgroundMaterial != null)
|
||||
_backgroundImage.material = _backgroundMaterial;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 每帧将当前画布 zoom / pan 同步到 Shader,
|
||||
/// 同时写入配置中所有 Shader 属性。
|
||||
/// 使用 <see cref="_backgroundMaterial"/> 缓存写入,确保与 GPU 使用的材质实例一致。
|
||||
/// </summary>
|
||||
private void SyncMaterialProperties()
|
||||
{
|
||||
if (_backgroundImage == null) return;
|
||||
if (_backgroundMaterial == null) return;
|
||||
if (Graph == null || Graph.Canvas == null) return;
|
||||
|
||||
var mat = _backgroundImage.material;
|
||||
if (mat == null) return;
|
||||
|
||||
var rt = _backgroundImage.rectTransform;
|
||||
float zoom = Graph.Canvas.ZoomLevel;
|
||||
Vector2 pan = Graph.Canvas.PanOffset;
|
||||
|
||||
Config.ApplyToMaterial(mat, rt.rect.size, zoom, pan);
|
||||
}
|
||||
|
||||
private void EnsureRenderRoot()
|
||||
{
|
||||
if (_renderRoot != null) return;
|
||||
if (Graph == null || Graph.Canvas == null) return;
|
||||
_renderRoot = Graph.Canvas.GetRootTransform();
|
||||
Config.ApplyToMaterial(_backgroundMaterial, rt.rect.size, zoom, pan);
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
@@ -133,9 +154,9 @@ namespace XericLibrary.Runtime.Blueprint.Render
|
||||
_backgroundImage = null;
|
||||
}
|
||||
|
||||
_backgroundMaterial = null;
|
||||
Config.ReleaseMaterial();
|
||||
_defaultConfig = null;
|
||||
_renderRoot = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace XericLibrary.Runtime.Blueprint.Render
|
||||
private QuickGraphNodeRenderConfig _defaultConfig;
|
||||
|
||||
// --- 当前生效的配置 ---
|
||||
private QuickGraphNodeRenderConfig Config
|
||||
public QuickGraphNodeRenderConfig Config
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -57,12 +57,6 @@ namespace XericLibrary.Runtime.Blueprint.Render
|
||||
|
||||
var cfg = Config;
|
||||
|
||||
// ── 将配置默认值写入所有节点(config → node property) ──
|
||||
foreach (var node in Graph.Nodes)
|
||||
{
|
||||
ApplyConfigToNode(node, cfg);
|
||||
}
|
||||
|
||||
// 视口裁剪:只渲染画布可见范围内的节点
|
||||
var viewport = Graph.Canvas.GetViewportRect();
|
||||
float margin = cfg.NodeWidth + cfg.NodeHeight; // 扩展一点避免边缘抖动
|
||||
@@ -172,8 +166,8 @@ namespace XericLibrary.Runtime.Blueprint.Render
|
||||
sideCount = 4,
|
||||
chamferSize = cfg.ChamferSize,
|
||||
chamferSegments = cfg.ChamferSegments,
|
||||
bgColor = bgColor,
|
||||
centerColor = Color.white,
|
||||
bgColor = new Color32(0,0,0,0),
|
||||
centerColor = bgColor,
|
||||
borderColor = borderColor,
|
||||
borderThickness = border,
|
||||
}
|
||||
@@ -237,15 +231,6 @@ namespace XericLibrary.Runtime.Blueprint.Render
|
||||
tmp.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将配置默认值写入节点,使渲染工具直接读取节点值。
|
||||
/// config 只控制背景色,边框色和文本色由节点初始化时定义。
|
||||
/// </summary>
|
||||
private static void ApplyConfigToNode(BlueprintNode node, QuickGraphNodeRenderConfig cfg)
|
||||
{
|
||||
node.NodeBGColor = cfg.DefaultBackgroundColor;
|
||||
}
|
||||
|
||||
private void EnsureRenderRoot()
|
||||
{
|
||||
if (_renderRoot != null) return;
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user