using UnityEngine; using UnityEngine.EventSystems; #if ENABLE_INPUT_SYSTEM using UnityEngine.InputSystem; #endif using XericLibrary.Runtime.Blueprint.Canvas; using XericLibrary.Runtime.Blueprint.Element; namespace XericLibrary.Runtime.Blueprint { /// /// 图论蓝图组件:Unity Inspector 配置、画布创建及调试入口。 /// 帧驱动完全由 承担。 /// [DisallowMultipleComponent] [ExecuteAlways] public class GraphTheoryBlueprintComponent : MonoBehaviour { [Header("Canvas Settings")] [Tooltip("勾选则为世界空间画布,否则为屏幕空间画布")] [SerializeField] private bool _useWorldSpaceCanvas = false; [Tooltip("世界空间画布的相机引用")] [SerializeField] private Camera _worldCamera; [Tooltip("屏幕空间模式实际可见区域;留空时使用蓝图宿主 RectTransform。")] [SerializeField] private RectTransform _viewportRectTransform; [Header("Blueprint Context")] [Tooltip("嵌入式图配置:主题与按 TargetToolType 解析的工具配置资产。")] [SerializeField] private BlueprintGraphContext _context = new BlueprintGraphContext(); [Header("Input")] [Tooltip("新输入系统 InputActionAsset。留空则仅支持鼠标基础操作(中键拖动平移、滚轮缩放)。")] [SerializeField] private Object _inputActions; public Object InputActions { get { return _inputActions; } set { if (_inputActions == value) return; #if ENABLE_INPUT_SYSTEM 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 (Graph != null) { var newAsset = value as InputActionAsset; EnsureEventSystem(newAsset); var newTool = BlueprintToolProvider.GetToolByType(Graph); if (newTool != null) newTool.InputActionAsset = newAsset; } #endif } } [Header("Test")] [Tooltip("是否自动生成测试节点")] [SerializeField] public bool _autoGenerateTestNodes = true; [Header("Debug")] [Tooltip("显示所有隐藏的运行时对象(HideFlags),用于调试。默认关闭。")] [SerializeField] private bool _showDebugObjects = false; /// 当前蓝图实例 public BlueprintGraph Graph { get; private set; } /// 当前画布 public IBlueprintCanvas Canvas { get; private set; } public BlueprintGraphContext Context => _context; public string ThemeName { get { return _context?.ThemeName ?? string.Empty; } set { if (_context == null) _context = new BlueprintGraphContext(); _context.ThemeName = value ?? string.Empty; Graph?.RequestContextRefresh(_context); } } // ===== 初始化 ===== private void Awake() { if (_context == null) _context = new BlueprintGraphContext(); CreateCanvas(); CreateGraph(); SetupInput(); } private void CreateCanvas() { if (_useWorldSpaceCanvas) { Canvas = new WorldSpaceBlueprintCanvas(transform, _worldCamera); } else { // 1) 找父级 Canvas(不在自身挂 Canvas) var parentCanvas = GetComponentInParent(); if (parentCanvas == null) { // 场景中无 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); // 将组件自身的 RectTransform 撑满整个 Canvas, // 确保 __Bp_RenderRoot 的尺寸与屏幕一致, // 避免 GetViewportRect() 基于默认 (100x100) 尺寸错误裁剪节点。 var selfRt = GetComponent(); if (selfRt == null) selfRt = gameObject.AddComponent(); selfRt.anchorMin = Vector2.zero; selfRt.anchorMax = Vector2.one; selfRt.offsetMin = Vector2.zero; selfRt.offsetMax = Vector2.zero; parentCanvas = cc; } // 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 + 渲染根构建画布适配器;实际视口默认是宿主自身而非 RenderRoot。 var hostRect = GetComponent(); Canvas = new ScreenSpaceBlueprintCanvas(parentCanvas, renderRootRt, _viewportRectTransform != null ? _viewportRectTransform : hostRect); } } private void CreateGraph() { // System 是唯一帧驱动;Context 在 Initialize/工具构建之前绑定。 Graph = BlueprintGraphSystem.GlobalInstance.CreateGraph(Canvas, _context); } private void SetupInput() { #if ENABLE_INPUT_SYSTEM var asset = _inputActions as InputActionAsset; EnsureEventSystem(asset); // 将 InputActionAsset 传递给输入工具(由工具自身管理绑定)。 var tool = BlueprintToolProvider.GetToolByType(Graph); if (tool != null) tool.InputActionAsset = asset; #endif } #if ENABLE_INPUT_SYSTEM private void EnsureEventSystem(InputActionAsset actionsAsset) #else private void EnsureEventSystem(Object _) #endif { var es = FindObjectOfType(); EventSystem eventSys; if (es != null) { eventSys = es; } else { var esGo = new GameObject("EventSystem"); eventSys = esGo.AddComponent(); } #if ENABLE_INPUT_SYSTEM var uiModule = eventSys.GetComponent(); if (uiModule == null) { var standalone = eventSys.GetComponent(); if (standalone != null) DestroyImmediate(standalone); uiModule = eventSys.gameObject.AddComponent(); } if (actionsAsset != null) ConfigureBlueprintUiActions(uiModule, actionsAsset); else { // 未提供蓝图资产时使用 Input System 内置 UI 动作,仍可产生指针事件。 uiModule.actionsAsset = null; uiModule.AssignDefaultActions(); } #else if (eventSys.GetComponent() == null) { eventSys.gameObject.AddComponent(); } #endif } #if ENABLE_INPUT_SYSTEM /// /// 将 Blueprint map 的鼠标动作显式映射到 UI 模块。 /// InputSystemUIInputModule 不会仅凭 actionsAsset 自动推断这些引用。 /// private static void ConfigureBlueprintUiActions( UnityEngine.InputSystem.UI.InputSystemUIInputModule uiModule, InputActionAsset actionsAsset) { var map = actionsAsset.FindActionMap(BlueprintInputConstants.MapName); var point = map?.FindAction("Point"); var leftClick = map?.FindAction("LeftClick"); var rightClick = map?.FindAction("RightClick"); var middleClick = map?.FindAction("MiddleClick"); var scrollWheel = map?.FindAction("ScrollWheel"); if (point == null || leftClick == null || rightClick == null || middleClick == null || scrollWheel == null) { uiModule.actionsAsset = null; uiModule.AssignDefaultActions(); return; } uiModule.actionsAsset = actionsAsset; uiModule.point = InputActionReference.Create(point); uiModule.leftClick = InputActionReference.Create(leftClick); uiModule.rightClick = InputActionReference.Create(rightClick); uiModule.middleClick = InputActionReference.Create(middleClick); uiModule.scrollWheel = InputActionReference.Create(scrollWheel); } #endif // ===== 调试可见性 ===== private void SyncDebugVisibility() { if (Graph == null) return; var targetFlags = _showDebugObjects ? HideFlags.None | HideFlags.DontSave : HideFlags.HideAndDontSave; var root = Graph.Canvas?.GetRootTransform(); if (root == null) return; for (int i = 0; i < root.childCount; i++) { var child = root.GetChild(i); if (child.name.StartsWith("__BpLayer_") || child.name == "__Bp_GridBackground") child.gameObject.hideFlags = targetFlags; } } // ===== 测试节点 ===== private void Start() { if (_autoGenerateTestNodes) GenerateTestNodes(); } // 组件仅承担 Inspector 配置与调试入口;图的 Update/LateUpdate 只由 BlueprintGraphSystem 驱动。 private void OnValidate() { if (_context == null) _context = new BlueprintGraphContext(); Graph?.RequestContextRefresh(_context); SyncDebugVisibility(); } // ===== 测试节点生成 ===== public void GenerateTestNodes() { if (Graph == null) return; if (Graph.Nodes.Count > 0) return; float startX = 200f; float centerY = 300f; float spacing = 200f; float branchOffset = 200f; var node1 = new GraphTheoryNode("Node 1", Color.red); node1.Position = new Vector2(startX, centerY); var node2 = new GraphTheoryNode("Node 2", Color.green); node2.Position = new Vector2(startX + spacing, centerY); var node3 = new GraphTheoryNode("Node 3", Color.blue); node3.Position = new Vector2(startX + spacing * 2, centerY); Graph.AddNode(node1); Graph.AddNode(node2); Graph.AddNode(node3); Graph.TryConnectPorts(node1.OutputPorts[0], node2.InputPorts[0]); Graph.TryConnectPorts(node2.OutputPorts[0], node3.InputPorts[0]); var node4 = new GraphTheoryNode("Node 4", Color.yellow); node4.Position = new Vector2(startX + spacing * 2, centerY + branchOffset); var node5 = new GraphTheoryNode("Node 5", Color.cyan); node5.Position = new Vector2(startX + spacing * 2, centerY - branchOffset); Graph.AddNode(node4); Graph.AddNode(node5); Graph.TryConnectPorts(node2.OutputPorts[0], node4.InputPorts[0]); Graph.TryConnectPorts(node2.OutputPorts[0], node5.InputPorts[0]); } // ===== 清理 ===== private void OnDestroy() { if (Graph != null) { var system = BlueprintGraphSystem.LazyInstance; system?.DestroyGraph(Graph); Graph = null; } } } }