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