diff --git a/CHANGELOG.md b/CHANGELOG.md index f4defa8..d1d7ad0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## [Unrealse] +* 图论框架支持蓝图模式 +* 蓝图添加了工具,节点,端口,连接线,ugui渲染工具等等 + ## [0.6.2] 2026-07-13 * 添加运行时图论框架(v2) diff --git a/Editor/Blueprint/BlueprintMenuItems.cs b/Editor/Blueprint/BlueprintMenuItems.cs index 2a48297..1b6c612 100644 --- a/Editor/Blueprint/BlueprintMenuItems.cs +++ b/Editor/Blueprint/BlueprintMenuItems.cs @@ -6,45 +6,44 @@ using XericLibrary.Runtime.Blueprint; namespace XericLibraryEditor.Bluprint.Editor { /// - /// 蓝图右键菜单项 —— 通过 GameObject 菜单快捷创建蓝图示例。 + /// 蓝图右键菜单项 —— 通过 GameObject 菜单/Gizmo 快捷创建蓝图场景对象。 + /// + /// 创建逻辑: + /// + /// 检查选中节点是否已在 Canvas 子级下;若否则新建 Canvas(1024×720)。 + /// 在 Canvas 下创建背景 Image 子项(1024×720,中心对齐)。 + /// 在 Canvas 下创建蓝图组件子项。 + /// + /// /// public static class BlueprintMenuItems { private const string MenuRoot = "GameObject/Xeric Library/Blueprint/"; + private const string BackgroundGoName = "__Bp_GridBackground"; + + // ========== 菜单项 ========== [MenuItem(MenuRoot + "UI Graph Bp", false, 10)] private static void CreateUIGraphBlueprint() { - var go = CreateBlueprintGameObject("Blueprint Graph (UI)"); - - var canvas = go.AddComponent(); - canvas.renderMode = RenderMode.ScreenSpaceOverlay; - - go.AddComponent(); - go.AddComponent(); - - go.AddComponent(); - - Selection.activeGameObject = go; - Undo.RegisterCreatedObjectUndo(go, "Create UI Graph Blueprint"); + var parent = GetOrCreateCanvasParent(); + var bg = CreateBackgroundImage(parent); + var bpGo = CreateChild(parent, "Blueprint Graph (UI)"); + bpGo.transform.SetParent(bg.transform, false); + bpGo.AddComponent(); + Finish(bpGo, "Create UI Graph Blueprint"); } [MenuItem(MenuRoot + "UI Graph Bp (QuickGraph)", false, 11)] private static void CreateUIQuickGraphBlueprint() { - var go = CreateBlueprintGameObject("Blueprint Graph - QuickGraph (UI)"); - - var canvas = go.AddComponent(); - canvas.renderMode = RenderMode.ScreenSpaceOverlay; - - go.AddComponent(); - go.AddComponent(); - - var comp = go.AddComponent(); + var parent = GetOrCreateCanvasParent(); + var bg = CreateBackgroundImage(parent); + var bpGo = CreateChild(parent, "Blueprint Graph - QuickGraph (UI)"); + bpGo.transform.SetParent(bg.transform, false); + var comp = bpGo.AddComponent(); comp.ThemeName = "QuickGraph"; - - Selection.activeGameObject = go; - Undo.RegisterCreatedObjectUndo(go, "Create UI QuickGraph Blueprint"); + Finish(bpGo, "Create UI QuickGraph Blueprint"); } [MenuItem(MenuRoot + "World Space Graph Bp", false, 12)] @@ -52,22 +51,115 @@ namespace XericLibraryEditor.Bluprint.Editor { var go = CreateBlueprintGameObject("Blueprint Graph (World)"); go.AddComponent(); + Finish(go, "Create World Space Graph Blueprint"); + } - Selection.activeGameObject = go; - Undo.RegisterCreatedObjectUndo(go, "Create World Space Graph Blueprint"); + // ========== 工具方法 ========== + + /// + /// 确保存在 Canvas 父节点。 + /// 当前选中节点已在 Canvas 子级下 → 直接返回根 Canvas。 + /// 否则 → 新建 Canvas 并设为父级。 + /// + private static Transform GetOrCreateCanvasParent() + { + var active = Selection.activeTransform; + if (active != null) + { + var existingCanvas = active.GetComponentInParent(); + if (existingCanvas != null) + return existingCanvas.transform; + + // 如果选中对象不是 Canvas 但已有 Canvas 同级或父级 + existingCanvas = Object.FindObjectOfType(); + if (existingCanvas != null) + return existingCanvas.transform; + } + else + { + // 场景中没有任何选中对象,找现有的 Canvas + var existingCanvas = Object.FindObjectOfType(); + if (existingCanvas != null) + return existingCanvas.transform; + } + + // 真的没有 Canvas → 新建一个 + var canvasGo = new GameObject("Blueprint Canvas", typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster)); + var canvas = canvasGo.GetComponent(); + canvas.renderMode = RenderMode.ScreenSpaceOverlay; + + var rt = canvasGo.GetComponent(); + rt.sizeDelta = new Vector2(1024, 720); + rt.anchorMin = new Vector2(0.5f, 0.5f); + rt.anchorMax = new Vector2(0.5f, 0.5f); + rt.pivot = new Vector2(0.5f, 0.5f); + + if (Selection.activeTransform != null) + canvasGo.transform.SetParent(Selection.activeTransform, false); + + canvasGo.transform.SetAsLastSibling(); + return canvasGo.transform; + } + + /// + /// 创建背景 Image 子项,1024×720,中心对齐,启用射线检测。 + /// 如果同层级下已有同名的背景对象,跳过创建。 + /// + private static GameObject CreateBackgroundImage(Transform parent) + { + // 不重复创建 + var existing = parent.Find(BackgroundGoName); + if (existing != null) + return existing.gameObject; + + var bgGo = new GameObject(BackgroundGoName, typeof(Image), typeof(Mask)); + var bgRt = bgGo.GetComponent(); + bgRt.SetParent(parent, false); + + // 撑满父节点 + bgRt.anchorMin = Vector2.zero; + bgRt.anchorMax = Vector2.one; + bgRt.offsetMin = Vector2.zero; + bgRt.offsetMax = Vector2.zero; + bgRt.pivot = new Vector2(0.5f, 0.5f); + + var image = bgGo.GetComponent(); + image.raycastTarget = true; + image.color = new Color(0.15f, 0.15f, 0.17f, 1f); + bgGo.transform.SetAsFirstSibling(); + return bgGo; + } + + private static GameObject CreateChild(Transform parent, string name) + { + var go = new GameObject(name, typeof(RectTransform)); + var rt = go.GetComponent(); + rt.SetParent(parent, false); + rt.anchorMin = Vector2.zero; + rt.anchorMax = Vector2.one; + rt.offsetMin = Vector2.zero; + rt.offsetMax = Vector2.zero; + rt.SetAsLastSibling(); + return go; } private static GameObject CreateBlueprintGameObject(string name) { var go = new GameObject(name); - if (Selection.activeTransform != null) - { go.transform.SetParent(Selection.activeTransform, false); - } - go.transform.SetAsLastSibling(); return go; } + + private static void Finish(GameObject go, string undoName) + { + Selection.activeGameObject = go; + Undo.RegisterCreatedObjectUndo(go, undoName); + // 也注册父级 Canvas 的撤销 + var canvas = go.GetComponentInParent(); + if (canvas != null) + Undo.RegisterCreatedObjectUndo(canvas.gameObject, undoName); + } } } diff --git a/Resources/Material/Graph/UI/UI Graph/UILineRender.mat b/Resources/Material/Graph/UI/UI Graph/UILineRender.mat index 65bf9e0..ae82c65 100644 --- a/Resources/Material/Graph/UI/UI Graph/UILineRender.mat +++ b/Resources/Material/Graph/UI/UI Graph/UILineRender.mat @@ -8,9 +8,7 @@ Material: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_Name: UILineRender - m_Shader: {fileID: 4800000, guid: 753025f901250db45b87f87c632c0bb0, type: 3} - m_Parent: {fileID: 0} - m_ModifiedSerializedProperties: 0 + m_Shader: {fileID: 4800000, guid: 9dacf142d8c5aee44bbbd2bd080fa3f5, type: 3} m_ValidKeywords: [] m_InvalidKeywords: [] m_LightmapFlags: 4 @@ -19,7 +17,6 @@ Material: m_CustomRenderQueue: -1 stringTagMap: {} disabledShaderPasses: [] - m_LockedProperties: m_SavedProperties: serializedVersion: 3 m_TexEnvs: diff --git a/Resources/ScriptableObject/QuickGraphInputConfig.asset b/Resources/ScriptableObject/QuickGraphInputConfig.asset index db21638..2236bb1 100644 --- a/Resources/ScriptableObject/QuickGraphInputConfig.asset +++ b/Resources/ScriptableObject/QuickGraphInputConfig.asset @@ -13,10 +13,11 @@ MonoBehaviour: m_Name: QuickGraphInputConfig m_EditorClassIdentifier: PanLerpSpeed: 8 - ZoomLerpSpeed: 8 - ZoomDivisor: 1100 + ZoomLerpSpeed: 8.2 + ZoomDivisor: 53 + DragPanSpeed: 1 MinZoom: 0.1 MaxZoom: 3 KeyboardPanSpeed: 600 KeyboardZoomSpeed: 0.05 - DragThreshold: 5 + BoxSelectThreshold: 10 diff --git a/Resources/ScriptableObject/QuickGraphNodeRenderConfig.asset b/Resources/ScriptableObject/QuickGraphNodeRenderConfig.asset index 3f48ea4..4e6c43e 100644 --- a/Resources/ScriptableObject/QuickGraphNodeRenderConfig.asset +++ b/Resources/ScriptableObject/QuickGraphNodeRenderConfig.asset @@ -15,8 +15,8 @@ MonoBehaviour: NodeWidth: 120 NodeHeight: 80 BorderThickness: 5 - ChamferSize: 0.5 - ChamferSegments: 6 + ChamferSize: 0.4 + ChamferSegments: 7 DefaultBackgroundColor: {r: 0.16037738, g: 0.16037738, b: 0.16037738, a: 1} DefaultTextColor: {r: 0.6156863, g: 0.6156863, b: 0.6156863, a: 1} TitleFontSize: 20 diff --git a/Runtime/Blueprint/Canvas/ScreenSpaceBlueprintCanvas.cs b/Runtime/Blueprint/Canvas/ScreenSpaceBlueprintCanvas.cs index 854c504..1ee9522 100644 --- a/Runtime/Blueprint/Canvas/ScreenSpaceBlueprintCanvas.cs +++ b/Runtime/Blueprint/Canvas/ScreenSpaceBlueprintCanvas.cs @@ -4,103 +4,88 @@ namespace XericLibrary.Runtime.Blueprint.Canvas { /// /// 屏幕空间蓝图画布实现,基于 UGUI Canvas。 - /// - /// 使用 RectTransformUtility 处理屏幕坐标和画布坐标之间的转换。 + /// + /// 所有坐标转换统一通过 完成, + /// 以 RectTransform 为坐标基准, + /// 不依赖蓝图自身的 RectTransform 是否撑满全屏。 + /// + /// + /// 支持 ScreenSpaceOverlay / ScreenSpaceCamera / WorldSpace 三种渲染模式。 + /// 由外层的 Mask(__Bp_GridBackground)裁剪,确保蓝图节点不溢出视图区域。 /// 缩放范围:0.1x ~ 3x。 - /// + /// /// public class ScreenSpaceBlueprintCanvas : BlueprintCanvasBase { private readonly UnityEngine.Canvas _unityCanvas; - private readonly RectTransform _rectTransform; + private readonly RectTransform _renderRoot; + + /// 动态获取摄像机引用(不缓存), + /// 自动适应 Canvas.renderMode 运行时的变化。 + private Camera WorldCamera => _unityCanvas != null ? _unityCanvas.worldCamera : null; /// /// 构造屏幕空间蓝图画布 /// - /// Unity UGUI Canvas 组件 - public ScreenSpaceBlueprintCanvas(UnityEngine.Canvas unityCanvas) + /// 所属的 Unity UGUI Canvas 组件 + /// 蓝图渲染根 RectTransform,所有蓝图层容器和背景挂载于此 + public ScreenSpaceBlueprintCanvas(UnityEngine.Canvas unityCanvas, RectTransform renderRoot) { _unityCanvas = unityCanvas; - _rectTransform = unityCanvas.GetComponent(); + _renderRoot = renderRoot; } /// - /// 将屏幕坐标转换为画布坐标 + /// 将屏幕坐标转换为画布坐标。 + /// 通过 + /// 以 为参照转换, + /// 自动处理 Canvas 渲染模式和 RenderRoot 的偏移。 /// - /// 屏幕空间的坐标点 - /// 画布空间的坐标点 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; } /// - /// 将画布坐标转换为屏幕坐标 + /// 将画布坐标转换为屏幕坐标。 + /// 先计算 RenderRoot 本地空间位置,再通过 RectTransformUtility 转换为屏幕坐标。 /// - /// 画布空间的坐标点 - /// 屏幕空间的坐标点 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); } /// - /// 将画布坐标转换为世界坐标 - /// - /// 屏幕空间画布的世界空间转换依赖于 Canvas 的渲染模式: - /// Screen Space - Overlay 时直接使用 localPoint 作为屏幕坐标; - /// Screen Space - Camera 时通过 camera 进行坐标转换。 - /// + /// 将画布坐标转换为世界坐标。 /// - /// 画布空间的坐标点 - /// 世界空间的坐标点 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); } /// - /// 将世界坐标转换为画布坐标 + /// 将世界坐标转换为画布坐标。 + /// 先转换为屏幕坐标,再通过 RectTransformUtility 转换为 RenderRoot 本地坐标。 /// - /// 世界空间的坐标点 - /// 画布空间的坐标点 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; } /// - /// 获取渲染根 Transform(UGUI Canvas 的 RectTransform) + /// 获取渲染根 Transform(蓝图层容器和背景的父级) /// public override Transform GetRootTransform() { - return _rectTransform; + return _renderRoot; } /// @@ -110,5 +95,10 @@ namespace XericLibrary.Runtime.Blueprint.Canvas { get { return _unityCanvas; } } + + /// + /// Canvas 组件未被销毁,画布可正常使用。 + /// + public override bool IsAlive => _unityCanvas != null; } } diff --git a/Runtime/Blueprint/Canvas/WorldSpaceBlueprintCanvas.cs b/Runtime/Blueprint/Canvas/WorldSpaceBlueprintCanvas.cs index b571bea..76b0a82 100644 --- a/Runtime/Blueprint/Canvas/WorldSpaceBlueprintCanvas.cs +++ b/Runtime/Blueprint/Canvas/WorldSpaceBlueprintCanvas.cs @@ -100,5 +100,10 @@ namespace XericLibrary.Runtime.Blueprint.Canvas { return _canvasTransform; } + + /// + /// Transform 组件未被销毁,画布可正常使用。 + /// + public override bool IsAlive => _canvasTransform != null; } } diff --git a/Runtime/Blueprint/GraphTheoryBlueprintComponent.cs b/Runtime/Blueprint/GraphTheoryBlueprintComponent.cs index 118ba30..d1a68b7 100644 --- a/Runtime/Blueprint/GraphTheoryBlueprintComponent.cs +++ b/Runtime/Blueprint/GraphTheoryBlueprintComponent.cs @@ -14,7 +14,6 @@ namespace XericLibrary.Runtime.Blueprint { /// /// 图论蓝图组件 —— 在一个 UGUI Canvas 上挂载蓝图系统。 - /// 使用 ExecuteAlways 在编辑器中也会运行,方便预览。 /// 驱动方式: /// - 编辑器非运行态:EditorApplication.update → EditorTick /// - 运行时:MonoBehaviour.Update @@ -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(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(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(); - if (unityCanvas == null) + // 1) 找父级 Canvas(不在自身挂 Canvas) + var parentCanvas = GetComponentInParent(); + if (parentCanvas == null) { - unityCanvas = gameObject.AddComponent(); - 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(); + cc.renderMode = RenderMode.ScreenSpaceOverlay; + canvasGo.transform.SetParent(transform.parent, false); + canvasGo.transform.SetAsLastSibling(); + transform.SetParent(canvasGo.transform, false); + parentCanvas = cc; } - if (GetComponent() == null) - gameObject.AddComponent(); - Canvas = new ScreenSpaceBlueprintCanvas(unityCanvas); + // 2) 创建渲染根子项(所有蓝图层挂载于此,由外层的 Mask 裁剪) + var renderRootGo = new GameObject("__Bp_RenderRoot", + typeof(RectTransform)); + renderRootGo.hideFlags = HideFlags.DontSave; + var renderRootRt = renderRootGo.GetComponent(); + 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(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() == null) { eventSys.gameObject.AddComponent(); } @@ -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) { diff --git a/Runtime/Blueprint/Input/BlueprintBoxSelectTool.cs b/Runtime/Blueprint/Input/BlueprintBoxSelectTool.cs index ae8d5cf..75db439 100644 --- a/Runtime/Blueprint/Input/BlueprintBoxSelectTool.cs +++ b/Runtime/Blueprint/Input/BlueprintBoxSelectTool.cs @@ -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 /// 释放时计算框选范围内所有元素的交集并输出统计日志。 /// 选框 UGUI Image 默认渲染在所有层之上(SetAsLastSibling)。 /// 框选结果保存在 中供其他工具读取。 + /// + /// 选框仅在拖拽距离超过 QuickGraphInputConfig.BoxSelectThreshold 后才显示, + /// 避免误触显示闪烁。 + /// /// [BlueprintTool(phase: ToolPhase.PreUpdate, order: 60)] [BlueprintTheme("QuickGraph")] @@ -19,19 +24,44 @@ namespace XericLibrary.Runtime.Blueprint private Vector2 _dragStartCanvas; // 按下时的画布坐标 private Vector2 _dragEndCanvas; // 当前的画布坐标 + /// 选框 GameObject 是否已创建并可见(超过阈值后) + 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(); + } + } + return _boxConfig; + } + } + // ---------- 结果 ---------- - /// 本次框选选中的元素列表。 - public List SelectedElements { get; } = new List(); + /// 结果:通过 Provider.CurrentSelectElements 读取框选结果。 // ===== 鼠标按下 ===== - 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(); _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(); - _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.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) diff --git a/Runtime/Blueprint/Input/BlueprintInputConstants.cs b/Runtime/Blueprint/Input/BlueprintInputConstants.cs index ec71d07..1142c02 100644 --- a/Runtime/Blueprint/Input/BlueprintInputConstants.cs +++ b/Runtime/Blueprint/Input/BlueprintInputConstants.cs @@ -1,47 +1,84 @@ +using XericLibrary.Runtime.MacroLibrary; +using UnityEngine; + namespace XericLibrary.Runtime.Blueprint { /// - /// 蓝图输入系统的 Action Map / Action 名称常量。 - /// 供 Editor 的 InputAction 模板和 Runtime 的输入处理工具共同引用, - /// 避免字符串硬编码散布各处。 + /// 蓝图输入系统的 Action Map / Action 名称常量与全局快捷键方案。 + /// + /// 快捷键通过 注册别名→组合键映射, + /// 由具体输入工具(如 BlueprintInputTool_InputManager)在初始化时通过 + /// 绑定实际动作后生效。 + /// /// public static class BlueprintInputConstants { // ── Action Map ── public const string MapName = "Blueprint"; - // ── 鼠标 / 指针 Actions(名称须与 UnityEngine.InputSystem.UI.InputSystemUIInputModule 官方预设一致)── - /// 画布内指针位置(Vector2)。InputSystemUIInputModule 预设名称。 + // ── 鼠标 / 指针 Actions ── public const string Point = "Point"; - /// 滚轮滚动(Vector2)。InputSystemUIInputModule 预设名称。 public const string ScrollWheel = "ScrollWheel"; - /// 左键点击(Button)。InputSystemUIInputModule 预设名称。 public const string LeftClick = "LeftClick"; - /// 右键点击(Button)。InputSystemUIInputModule 预设名称。 public const string RightClick = "RightClick"; - /// 中键点击(Button)。InputSystemUIInputModule 预设名称。 public const string MiddleClick = "MiddleClick"; // ── 功能键 Actions ── - /// 左 Shift(Button) public const string Shift = "Shift"; - /// 左 Ctrl(Button) public const string Ctrl = "Ctrl"; - /// 左 Alt(Button) public const string Alt = "Alt"; - // ── 快捷键组合 Actions(仅新输入系统)── - /// 平移画布(Vector2,4方向) + // ── 快捷键组合 Actions ── public const string Pan = "Pan"; - /// 缩放画布(float,滚轮 + Ctrl 组合) public const string Zoom = "Zoom"; - /// 定位到核心节点 public const string FocusHome = "FocusHome"; - /// 撤销 public const string Undo = "Undo"; - /// 重做 public const string Redo = "Redo"; - /// 进入父级 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 + // 绑定实际动作委托。 + + /// 全局快捷键树(只读,操作请通过 MacroKey 静态方法)。 + 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 + }; } } diff --git a/Runtime/Blueprint/Input/BlueprintInputManager.InputSystem.cs b/Runtime/Blueprint/Input/BlueprintInputManager.InputSystem.cs deleted file mode 100644 index 9c410c4..0000000 --- a/Runtime/Blueprint/Input/BlueprintInputManager.InputSystem.cs +++ /dev/null @@ -1,188 +0,0 @@ -#if ENABLE_INPUT_SYSTEM - -using System.Collections.Generic; -using UnityEngine; -using UnityEngine.InputSystem; - -namespace XericLibrary.Runtime.Blueprint -{ - /// - /// 蓝图输入管理器(新输入系统)—— 管理 InputActionAsset 的引用计数与启用/停用。 - /// - /// 多个蓝图可能引用同一个 InputActionAsset,管理器确保: - /// - 首个蓝图注册时启用 asset 并绑定 Action 回调; - /// - 后续蓝图注册仅增加计数,不重复启用; - /// - 蓝图注销时减少计数,计数归零时才解绑并禁用 asset。 - /// - /// - /// 蓝图组件在 Awake 时调用 ,OnDestroy 时调用 - /// ;运行时动态更换 asset 也通过这两方法。 - /// - /// - public static class BlueprintInputManager - { - /// asset → 引用计数 - private static Dictionary _refCounts - = new Dictionary(); - - /// asset → 已绑定回调的 ActionMap - private static Dictionary _activeMaps - = new Dictionary(); - - // ===== 快捷键最新值(供 QuickGraphInputTool.OnUpdate 轮询) ===== - - /// Pan 动作最新的 Vector2 方向值(在 performed 中写入,canceled 中归零)。 - public static Vector2 LastPanDirection; - - /// Zoom 动作最新的 float 轴值(在 performed 中写入,canceled 中归零)。 - public static float LastZoomAxis; - - // 缓存的回调引用(确保 Bind/Unbind 使用同一委托实例) - private static readonly System.Action s_panHandler - = ctx => LastPanDirection = ctx.ReadValue(); - private static readonly System.Action s_panCanceled - = _ => LastPanDirection = Vector2.zero; - private static readonly System.Action s_zoomHandler - = ctx => LastZoomAxis = ctx.ReadValue(); - private static readonly System.Action s_zoomCanceled - = _ => LastZoomAxis = 0f; - private static readonly System.Action s_homeHandler - = _ => OnActionReceived(BlueprintInputConstants.FocusHome); - private static readonly System.Action s_undoHandler - = _ => OnActionReceived(BlueprintInputConstants.Undo); - private static readonly System.Action s_redoHandler - = _ => OnActionReceived(BlueprintInputConstants.Redo); - private static readonly System.Action s_parentHandler - = _ => OnActionReceived(BlueprintInputConstants.NavigateParent); - - // ===== 当前焦点蓝图(快捷键分发目标) ===== - - private static BlueprintGraph s_focusedGraph; - - /// 设置当前焦点蓝图(快捷键分发目标)。 - public static void SetFocusedGraph(BlueprintGraph graph) - { - s_focusedGraph = graph; - } - - // ===== 注册 / 注销 ===== - - /// - /// 注册一个蓝图对 InputActionAsset 的引用。 - /// 若 asset 尚未启用,则启用并绑定快捷键回调。 - /// 允许多个蓝图共享同一 asset。 - /// - 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; - } - } - - /// - /// 注销一个蓝图对 InputActionAsset 的引用。 - /// 引用计数归零时解绑回调并禁用 asset。 - /// - 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 handler) - { - var action = map.FindAction(name); - if (action != null) - action.performed += handler; - } - - private static void Unbind(InputActionMap map, string name, System.Action handler) - { - var action = map.FindAction(name); - if (action != null) - action.performed -= handler; - } - - private static void BindCancelled(InputActionMap map, string name, System.Action handler) - { - var action = map.FindAction(name); - if (action != null) - action.canceled += handler; - } - - private static void UnbindCancelled(InputActionMap map, string name, System.Action 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 diff --git a/Runtime/Blueprint/Input/BlueprintInputManager.oldInputSys.cs b/Runtime/Blueprint/Input/BlueprintInputManager.oldInputSys.cs deleted file mode 100644 index dd202ae..0000000 --- a/Runtime/Blueprint/Input/BlueprintInputManager.oldInputSys.cs +++ /dev/null @@ -1,135 +0,0 @@ -#if !ENABLE_INPUT_SYSTEM - -using UnityEngine; - -namespace XericLibrary.Runtime.Blueprint -{ - /// - /// 蓝图输入管理器(旧输入系统)—— 通过旧输入系统轮询鼠标和键盘事件。 - /// - /// 提供与 BlueprintInputManager.InputSystem.cs 相同的静态 API 签名, - /// 但底层使用 / 等旧 API, - /// 不依赖任何新输入系统类型。 - /// - /// - /// 鼠标点击、拖拽、右键等事件通过 轮询后 - /// 分发到 的 DispatchXxx 方法。 - /// 键盘 Pan/Zoom 值通过 轮询后由 - /// 在 OnUpdate 中读取。 - /// - /// - public static class BlueprintInputManager - { - /// - /// 当前焦点蓝图(快捷键分发目标)。 - /// 由 设置。 - /// - private static BlueprintGraph s_focusedGraph; - - /// 设置当前焦点蓝图。 - public static void SetFocusedGraph(BlueprintGraph graph) - { - s_focusedGraph = graph; - } - - /// - /// 注册 InputActionAsset(旧系统空操作)。 - /// 仅保留签名以兼容编译。 - /// - public static void RegisterAsset(Object asset) - { - // 旧输入系统:不管理 InputActionAsset - } - - /// - /// 注销 InputActionAsset(旧系统空操作)。 - /// 仅保留签名以兼容编译。 - /// - public static void UnregisterAsset(Object asset) - { - // 旧输入系统:不管理 InputActionAsset - } - - // ===== 键盘轮询值(供 QuickGraphInputTool.OnUpdate 读取) ===== - - /// - /// 方向键 Pan 方向值(每帧由 更新)。 - /// - public static Vector2 LastPanDirection; - - /// - /// +/- 键 Zoom 轴值(每帧由 更新)。 - /// - public static float LastZoomAxis; - - // ===== 事件轮询 ===== - - /// - /// 轮询鼠标按键事件。 - /// 每帧在 Update 中调用,检测鼠标按下/释放并分发到 。 - /// - /// 0=左键, 1=右键, 2=中键 - 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); - } - } - - /// - /// 轮询鼠标滚轮事件。 - /// 每帧在 Update 中调用,检测滚轮增量并分发到 。 - /// - 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); - } - } - - /// - /// 轮询键盘快捷键(方向键 Pan、+/- Zoom)。 - /// 每帧在 Update 中调用,更新 和 。 - /// - 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 diff --git a/Runtime/Blueprint/Input/BlueprintInputReceiver.cs b/Runtime/Blueprint/Input/BlueprintInputReceiver.cs deleted file mode 100644 index c75ea69..0000000 --- a/Runtime/Blueprint/Input/BlueprintInputReceiver.cs +++ /dev/null @@ -1,110 +0,0 @@ -using UnityEngine; - -namespace XericLibrary.Runtime.Blueprint -{ - /// - /// 蓝图输入接收工具 —— 继承自 ,由 BlueprintToolProvider 统一管理生命周期。 - /// - /// 职责: - /// 1. 创建并驱动 ,绑定 UGUI EventTrigger 到背景 Image; - /// 2. 在 中统一刷新鼠标位置、检测指针进入/离开背景区域; - /// 3. 将画布内的指针移动事件分发给 。 - /// - /// - /// 鼠标按键按下/释放/拖拽/滚轮等由 通过 EventTrigger 回调直接分发。 - /// - /// - [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; - } - - // ===== 辅助 ===== - - /// 检查屏幕坐标是否落在背景 RectTransform 内。 - private bool IsScreenPointOverBackground(Vector2 screenPoint) - { - var rt = _uguiManager?.BgRectTransform; - if (rt == null) return false; - return RectTransformUtility.RectangleContainsScreenPoint(rt, screenPoint, null); - } - - /// 屏幕坐标 → 画布坐标。 - private Vector2 ScreenToCanvas(Vector2 screenPos) - { - return Graph?.Canvas != null ? Graph.Canvas.ScreenToCanvas(screenPos) : screenPos; - } - } -} diff --git a/Runtime/Blueprint/Input/BlueprintInputTool_InputManager.cs b/Runtime/Blueprint/Input/BlueprintInputTool_InputManager.cs new file mode 100644 index 0000000..2adc9de --- /dev/null +++ b/Runtime/Blueprint/Input/BlueprintInputTool_InputManager.cs @@ -0,0 +1,196 @@ +#if !ENABLE_INPUT_SYSTEM + +using XericLibrary.Runtime.MacroLibrary; +using UnityEngine; + +namespace XericLibrary.Runtime.Blueprint +{ + /// + /// 蓝图输入工具(旧输入系统)—— 自身完成全部鼠标/键盘/快捷键轮询, + /// 通过 KfsStyle + KeyFlippingStampSheet 状态机驱动拖拽识别。 + /// + [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; + } + + /// + /// 使用 sheet 状态机驱动单个鼠标按键的点击/拖拽/释放事件。 + /// 每帧调用 AppendKeyState_PositionTriggle 更新状态, + /// 然后根据 IsPressed / IsDragging / IsDropping / IsReleased 分发到 Provider。 + /// + 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 diff --git a/Runtime/Blueprint/Input/BlueprintInputReceiver.cs.meta b/Runtime/Blueprint/Input/BlueprintInputTool_InputManager.cs.meta similarity index 83% rename from Runtime/Blueprint/Input/BlueprintInputReceiver.cs.meta rename to Runtime/Blueprint/Input/BlueprintInputTool_InputManager.cs.meta index 46563a5..e508356 100644 --- a/Runtime/Blueprint/Input/BlueprintInputReceiver.cs.meta +++ b/Runtime/Blueprint/Input/BlueprintInputTool_InputManager.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 7e1d0771b4cc2aa4692eff637fa5d5af +guid: 53cd1e0c9567eee4a8ca246adee6510f MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Runtime/Blueprint/Input/BlueprintInputTool_InputSystem.cs b/Runtime/Blueprint/Input/BlueprintInputTool_InputSystem.cs new file mode 100644 index 0000000..c8c0da6 --- /dev/null +++ b/Runtime/Blueprint/Input/BlueprintInputTool_InputSystem.cs @@ -0,0 +1,160 @@ +#if ENABLE_INPUT_SYSTEM + +using XericLibrary.Runtime.MacroLibrary; +using UnityEngine; +using UnityEngine.InputSystem; + +namespace XericLibrary.Runtime.Blueprint +{ + /// + /// 蓝图输入工具(新输入系统)—— 自身管理 InputActionAsset 绑定和快捷键回调, + /// 不再依赖 BlueprintInputManager 管理器。 + /// + [BlueprintTool(phase: ToolPhase.PreUpdate, order: 1)] + [BlueprintTheme("QuickGraph")] + public class BlueprintInputTool_InputSystem : BlueprintInputTool_UGUI + { + private InputActionAsset _inputActionAsset; + private InputActionMap _activeMap; + + // 缓存的动作回调实例 + private System.Action _panHandler; + private System.Action _panCanceled; + private System.Action _zoomHandler; + private System.Action _zoomCanceled; + private System.Action _homeHandler; + private System.Action _undoHandler; + private System.Action _redoHandler; + private System.Action _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(); + _panCanceled = _ => GraphInputTool.LastPanDirection = Vector2.zero; + _zoomHandler = ctx => GraphInputTool.LastZoomAxis = ctx.ReadValue(); + _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 handler) + { + var action = map.FindAction(name); + if (action != null) action.performed += handler; + } + + private static void Unbind(InputActionMap map, string name, System.Action handler) + { + var action = map.FindAction(name); + if (action != null) action.performed -= handler; + } + + private static void BindCancel(InputActionMap map, string name, System.Action handler) + { + var action = map.FindAction(name); + if (action != null) action.canceled += handler; + } + + private static void UnbindCancel(InputActionMap map, string name, System.Action 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 diff --git a/Runtime/Blueprint/Input/BlueprintInputManager.InputSystem.cs.meta b/Runtime/Blueprint/Input/BlueprintInputTool_InputSystem.cs.meta similarity index 83% rename from Runtime/Blueprint/Input/BlueprintInputManager.InputSystem.cs.meta rename to Runtime/Blueprint/Input/BlueprintInputTool_InputSystem.cs.meta index 2de743b..8c8698e 100644 --- a/Runtime/Blueprint/Input/BlueprintInputManager.InputSystem.cs.meta +++ b/Runtime/Blueprint/Input/BlueprintInputTool_InputSystem.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 72f3c53c414fbef4b80270adb10afe1d +guid: 43b72153b4945b14da4fb0464d0a2e22 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Runtime/Blueprint/Input/BlueprintInputTool_UGUI.cs b/Runtime/Blueprint/Input/BlueprintInputTool_UGUI.cs new file mode 100644 index 0000000..92e6400 --- /dev/null +++ b/Runtime/Blueprint/Input/BlueprintInputTool_UGUI.cs @@ -0,0 +1,278 @@ +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; + +namespace XericLibrary.Runtime.Blueprint +{ + /// + /// 蓝图输入工具基类(UGUI)—— 注册 EventTrigger 到蓝图背景上监听鼠标事件, + /// 并将事件发布到 ,同时记录光标上下文。 + /// + /// 注意:此基类本身不被注册为独立工具(无 [BlueprintTool] 属性)。 + /// 由子类 BlueprintInputTool_InputSystem 和 BlueprintInputTool_InputManager + /// 通过继承获得 UGUI 绑定能力。 + /// + /// + 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; + + /// + /// 是否通过 EventTrigger 处理鼠标按键/拖拽/滚轮事件。 + /// 新输入系统路径下为 true(默认),旧输入系统路径下为 false(由 PollUpdate 轮询)。 + /// 子类可在 中修改此值。 + /// + 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; + } + + /// + /// 检查已绑定的 EventTrigger 实例是否仍然有效(回调仍指向本实例)。 + /// + 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(); + } + + /// + /// 确保场景中存在 EventSystem 和合适的 InputModule。 + /// 旧输入系统 → StandaloneInputModule。 + /// 新输入系统 → InputSystemUIInputModule(由 GraphTheoryBlueprintComponent 创建)。 + /// 不会重复创建模块(通过 GetComponent 检查)。 + /// + private static void EnsureEventSystemExists() + { + var es = Object.FindObjectOfType(); + if (es == null) + { + var go = new GameObject("EventSystem"); + es = go.AddComponent(); + } + +#if !ENABLE_INPUT_SYSTEM + if (es.GetComponent() == null) + { + es.gameObject.AddComponent(); + } +#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(); + if (_eventTrigger == null) + { + _eventTrigger = _bgTransform.gameObject.AddComponent(); + } + 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 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); + } + } +} diff --git a/Runtime/Blueprint/Input/BlueprintInputManager.oldInputSys.cs.meta b/Runtime/Blueprint/Input/BlueprintInputTool_UGUI.cs.meta similarity index 83% rename from Runtime/Blueprint/Input/BlueprintInputManager.oldInputSys.cs.meta rename to Runtime/Blueprint/Input/BlueprintInputTool_UGUI.cs.meta index 98a91fa..0bb1b49 100644 --- a/Runtime/Blueprint/Input/BlueprintInputManager.oldInputSys.cs.meta +++ b/Runtime/Blueprint/Input/BlueprintInputTool_UGUI.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 1f94468ab3403984494aa0d980297093 +guid: ff8955aaf6e6c2447ab2c9c264a4fc05 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Runtime/Blueprint/Input/BlueprintUGUIInputManager.cs b/Runtime/Blueprint/Input/BlueprintUGUIInputManager.cs index 16d0f52..138c99a 100644 --- a/Runtime/Blueprint/Input/BlueprintUGUIInputManager.cs +++ b/Runtime/Blueprint/Input/BlueprintUGUIInputManager.cs @@ -1,45 +1,19 @@ using UnityEngine; -using UnityEngine.EventSystems; -using UnityEngine.UI; namespace XericLibrary.Runtime.Blueprint { /// - /// 蓝图 UGUI 输入管理器 —— 负责绑定 UGUI EventTrigger 到背景 Image, - /// 将 UGUI 指针事件转发到 的分发方法。 - /// - /// 该类仅处理与 UGUI EventTrigger 相关的绑定逻辑,不感知新旧输入系统的差异。 - /// 由 创建并在 OnUpdate 中驱动。 - /// + /// 蓝图 UGUI 输入工具静态辅助类。 + /// 提供跨新旧输入系统的统一鼠标位置和按钮状态查询。 /// - public class BlueprintUGUIInputManager + public static class BlueprintUGUIInputManager { - /// - /// 当前帧鼠标/指针在屏幕空间的位置。 - /// 每帧由 刷新。 - /// - public static Vector2 MousePosition { get; private set; } + /// 当前帧鼠标/指针在屏幕空间的位置,每帧由 BlueprintInputTool_UGUI 更新。 + public static Vector2 MousePosition { get; internal set; } /// - /// 当前帧鼠标/指针是否处于新输入系统模式。 - /// true = 新输入系统(ENABLE_INPUT_SYSTEM 已定义); - /// false = 旧输入系统。 - /// - public static bool IsNewInputSystem - { - get - { -#if ENABLE_INPUT_SYSTEM - return true; -#else - return false; -#endif - } - } - - /// - /// 更新静态鼠标位置。每帧由 BlueprintInputReceiver 的 OnUpdate 调用。 - /// 内部根据条件编译选择鼠标位置读取源。 + /// 更新静态鼠标位置。 + /// 由 每帧在 OnUpdate 中调用。 /// public static void UpdateMousePosition() { @@ -52,7 +26,6 @@ namespace XericLibrary.Runtime.Blueprint /// /// 返回当前是否按住指定鼠标按钮。 - /// 内部根据条件编译选择检测方式。 /// /// 0=左键, 1=右键, 2=中键 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"; - - // ===== 生命周期 ===== - - /// 初始化 UGUI 输入管理器并绑定到指定蓝图。 - public BlueprintUGUIInputManager(BlueprintGraph graph) - { - _graph = graph; - } - - /// - /// 尝试绑定 EventTrigger 到背景 Image。 - /// 返回 true 表示绑定成功,false 表示背景尚未创建。 - /// - 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(); - if (_eventTrigger == null) - _eventTrigger = _bgTransform.gameObject.AddComponent(); - else - _eventTrigger.triggers.Clear(); - - BindEventTrigger(); - _isBound = true; - return true; - } - - /// 解绑并清理 EventTrigger。 - public void Unbind() - { - if (_eventTrigger != null) - { - _eventTrigger.triggers.Clear(); - _eventTrigger = null; - } - _bgTransform = null; - _isBound = false; - } - - /// 是否已绑定。 - public bool IsBound => _isBound; - - /// 背景 RectTransform(用于屏幕点包含检测)。 - 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 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); - } } } diff --git a/Runtime/Blueprint/Input/QuickGraphInputTool.cs b/Runtime/Blueprint/Input/QuickGraphInputTool.cs index 32f883f..f17ff90 100644 --- a/Runtime/Blueprint/Input/QuickGraphInputTool.cs +++ b/Runtime/Blueprint/Input/QuickGraphInputTool.cs @@ -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; } } diff --git a/Runtime/Blueprint/Render/Config/QuickGraphInputConfig.cs b/Runtime/Blueprint/Render/Config/QuickGraphInputConfig.cs index 0dd2b6a..40630d1 100644 --- a/Runtime/Blueprint/Render/Config/QuickGraphInputConfig.cs +++ b/Runtime/Blueprint/Render/Config/QuickGraphInputConfig.cs @@ -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; } } diff --git a/Runtime/Blueprint/Render/QuickUGUIGraphGridBackgroundTool.cs b/Runtime/Blueprint/Render/QuickUGUIGraphGridBackgroundTool.cs index 4c714d3..32d7ebb 100644 --- a/Runtime/Blueprint/Render/QuickUGUIGraphGridBackgroundTool.cs +++ b/Runtime/Blueprint/Render/QuickUGUIGraphGridBackgroundTool.cs @@ -5,9 +5,21 @@ namespace XericLibrary.Runtime.Blueprint.Render { /// /// 快速 UGUI 图论网格背景渲染工具。 - /// 在蓝图画布最底层创建一个全屏 Image 组件绘制网格背景, + /// 在蓝图画布最底层创建一个 Image 组件绘制网格背景, /// 每帧将画布的 zoom / pan 同步到 Shader 的 _Transform 属性。 - /// 全部可调参数由 QuickGraphGridBackgroundConfig 配置资产提供。 + /// + /// 背景 Image 在 下锚点撑满, + /// 只响应画布属性和 dirty flag,不会因画布重建而销毁(见 )。 + /// Shader 参数(_Transform 的 scale/offset)在 中随 + /// ZoomLevel / PanOffset 更新,与画布拖拽共享同一属性源。 + /// + /// + /// 材质引用注意: + /// 必须缓存材质实例()并直接写入该实例。 + /// UGUI 的 Graphic.material getter 可能返回 materialForRendering + /// 的副本(与 setter 存入的 m_Material 不同),因此 _backgroundImage.material + /// 不适合用于写 shader 属性。见 Graphic.material Unity 源码。 + /// /// [BlueprintTool(phase: ToolPhase.Render, order: 0)] [BlueprintTheme("QuickGraph")] @@ -30,7 +42,13 @@ namespace XericLibrary.Runtime.Blueprint.Render } private Image _backgroundImage; - private Transform _renderRoot; + + /// + /// 缓存的背景材质实例。 + /// 由 创建,终身保留。 + /// shader 属性始终写入此实例,避免 UGUI materialForRendering 副本问题。 + /// + private Material _backgroundMaterial; public override void OnInitialize() { @@ -44,14 +62,18 @@ namespace XericLibrary.Runtime.Blueprint.Render SyncMaterialProperties(); } + /// + /// 在 RenderRoot 下查找或创建背景 Image。 + /// 创建后终身保留,不随画布重建销毁。 + /// 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(); @@ -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(); @@ -79,45 +101,44 @@ namespace XericLibrary.Runtime.Blueprint.Render _backgroundImage = bgGo.AddComponent(); _backgroundImage.raycastTarget = true; + // 关闭 maskable:运行时背景 Image 不参与 UGUI Mask 裁剪, + // 避免父级 Mask 组件触发 materialForRendering 克隆材质实例。 + // 材质写入(_Transform 等 shader 属性)直通 GPU,不受 IMaterialModifier 干扰。 + _backgroundImage.maskable = false; ApplyMaterial(); } /// - /// 从配置获取材质并设置到 Image(仅首次执行)。 + /// 从配置获取材质并缓存到 , + /// 然后赋值给 Image。 + /// 此方法仅执行一次,后续材质写入直接使用缓存的引用。 /// 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; } /// /// 每帧将当前画布 zoom / pan 同步到 Shader, - /// 同时写入配置中所有 Shader 属性。 + /// 使用 缓存写入,确保与 GPU 使用的材质实例一致。 /// 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; } } } diff --git a/Runtime/Blueprint/Render/QuickUGUIGraphNodeRenderTool.cs b/Runtime/Blueprint/Render/QuickUGUIGraphNodeRenderTool.cs index c9c6aa2..f6d8368 100644 --- a/Runtime/Blueprint/Render/QuickUGUIGraphNodeRenderTool.cs +++ b/Runtime/Blueprint/Render/QuickUGUIGraphNodeRenderTool.cs @@ -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); } - /// - /// 将配置默认值写入节点,使渲染工具直接读取节点值。 - /// config 只控制背景色,边框色和文本色由节点初始化时定义。 - /// - private static void ApplyConfigToNode(BlueprintNode node, QuickGraphNodeRenderConfig cfg) - { - node.NodeBGColor = cfg.DefaultBackgroundColor; - } - private void EnsureRenderRoot() { if (_renderRoot != null) return; diff --git a/Runtime/XericLibrary.dll b/Runtime/XericLibrary.dll index 0687eb6..c109fd3 100644 Binary files a/Runtime/XericLibrary.dll and b/Runtime/XericLibrary.dll differ