udpate 0.6.2

This commit is contained in:
2026-07-13 11:44:25 +08:00
parent 923ce1f60d
commit 155641f2ef
87 changed files with 4491 additions and 24 deletions
@@ -0,0 +1,14 @@
using UnityEngine;
using XericLibrary.Runtime.UIGraph;
namespace XericLibrary.Runtime.Blueprint.Render
{
/// <summary>
/// 最简曲线渲染器 —— 继承 CurveCacheRendererBase,无任何额外设定。
/// 供蓝图渲染工具使用,避免 UICurveRenderer 等自带的默认配置干扰。
/// </summary>
[DisallowMultipleComponent]
public sealed class BlueprintCurveRenderer : CurveCacheRendererBase
{
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 53a46255c1675484489c772b9cd65277
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,14 @@
using UnityEngine;
using XericLibrary.Runtime.UIGraph;
namespace XericLibrary.Runtime.Blueprint.Render
{
/// <summary>
/// 最简图元渲染器 —— 继承 PrimitiveCacheRendererBase,无任何额外设定。
/// 供蓝图渲染工具使用,避免 UICurveRenderer 等自带的默认配置干扰。
/// </summary>
[DisallowMultipleComponent]
public sealed class BlueprintPrimitiveRenderer : PrimitiveCacheRendererBase
{
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6292bb3966ffc0049b2f7546debd0a73
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c7cfe95b8a3355c4e8e6585d2266fb5b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,145 @@
using System;
using UnityEngine;
namespace XericLibrary.Runtime.Blueprint.Render
{
/// <summary>
/// QuickGraph 网格背景配置表 —— 控制 Shader 全部可调参数。
/// 材质实例由此配置统一管理,所有调用者通过 <see cref="GetOrCreateMaterial"/> 获取同一实例。
/// <see cref="ApplyToMaterial"/> 封装所有材质属性写入(含 _Transform 同步画布平移/缩放)。
/// 右键 Project 窗口 → Create/Blueprint/QuickGraph/Grid Background Config 创建资产。
/// </summary>
[CreateAssetMenu(
fileName = "QuickGraphGridBackgroundConfig",
menuName = "Xeric Library/Blueprint/QuickGraph/Grid Background Config",
order = 3)]
[BlueprintTheme("QuickGraph")]
public class QuickGraphGridBackgroundConfig : BlueprintToolConfigBase
{
/// <summary>目标工具类型</summary>
public override System.Type TargetToolType
{
get { return typeof(QuickUGUIGraphGridBackgroundTool); }
}
[Header("材质来源")]
[Tooltip("网格 Shader 名称")]
public string ShaderName = "XericLibrary/BluePrint/BlueprintBackgound_GridLine";
[Tooltip("可选:直接指定背景材质。设置后将忽略 ShaderName。")]
public Material OverrideMaterial = null;
[Header("网格参数")]
[Tooltip("网格间距(画布单位)。值越小网格越密。")]
public float GridSize = 100f;
[Tooltip("叠加网格密度")]
public float GridOverlayPower = 5f;
[Tooltip("线条阈值。0.5=极粗, 1=极细")]
[Range(0.5f, 1f)]
public float GridLineThreshold = 0.99f;
[Tooltip("线条扩展/柔和度。越大线条越宽。")]
[Range(0.001f, 1f)]
public float GridExp = 0.001f;
[Header("颜色")]
[Tooltip("网格线颜色")]
public Color GridColor = Color.white;
[Tooltip("背景底色")]
public Color GridBackgroundColor = Color.black;
// ─── 运行时缓存的材质实例 ───
[NonSerialized]
private Material _cachedMaterial;
/// <summary>
/// 获取或创建材质实例。
/// - 若 OverrideMaterial 不为空,直接返回。
/// - 否则按 ShaderName 查找 Shader,创建材质并缓存。
/// - 同一配置实例上多次调用返回同一材质对象。
/// </summary>
public Material GetOrCreateMaterial()
{
if (OverrideMaterial != null)
return OverrideMaterial;
if (_cachedMaterial != null)
return _cachedMaterial;
if (!string.IsNullOrEmpty(ShaderName))
{
var shader = Shader.Find(ShaderName);
if (shader != null)
{
_cachedMaterial = new Material(shader);
_cachedMaterial.name = "BpGridBackground_Mat";
}
}
return _cachedMaterial;
}
/// <summary>
/// 将当前配置的所有参数写入材质。
/// <para>包括:
/// - _Transform(scaleX, scaleY, offsetX, offsetY):随画布 zoom / pan 同步更新;
/// - _GridOverlayPower、_GridLineThreshold、_GridExp;
/// - _GridColor、_GridBackgroundColor。</para>
/// 每次渲染时调用以保持与蓝图画布的缩放/平移同步。
/// </summary>
/// <param name="mat">目标材质实例</param>
/// <param name="rectSize">背景板 RectTransform 的像素尺寸 (width, height)</param>
/// <param name="zoom">当前画布缩放级别</param>
/// <param name="panOffset">当前画布平移偏移量</param>
public void ApplyToMaterial(Material mat, Vector2 rectSize, float zoom, Vector2 panOffset)
{
if (mat == null) return;
// ── _Transform: 基于画布 zoom / pan 同步 ──
// Shader 中: texCoord2 = uv * _Transform.xy + _Transform.zw
// uv (0,0) = Image 左下角, uv (1,1) = Image 右上角
// CanvasToLocal: localPoint = canvasPoint * zoom + panOffset
// UV ↔ localPoint: uv = localPoint / rectSize + 0.5f
// ^^^^^ pivot(0.5,0.5) 的 UV 偏移
// 代入: uv = (canvasPoint * zoom + panOffset) / rectSize + 0.5f
// 目标: gridPos = canvasPoint / GridSize = uv * scale + offset
// 解出: scale = rectSize / (zoom * GridSize)
// offset = -(panOffset + 0.5 * rectSize) / (zoom * GridSize)
float sx = rectSize.x / (zoom * GridSize);
float sy = rectSize.y / (zoom * GridSize);
float ox = -(panOffset.x + 0.5f * rectSize.x) / (zoom * GridSize);
float oy = -(panOffset.y + 0.5f * rectSize.y) / (zoom * GridSize);
mat.SetVector("_Transform", new Vector4(sx, sy, ox, oy));
// ── 网格线参数 ──
mat.SetFloat("_GridOverlayPower", GridOverlayPower);
mat.SetFloat("_GridLineThreshold", GridLineThreshold);
mat.SetFloat("_GridExp", GridExp);
// ── 颜色 ──
mat.SetColor("_GridColor", GridColor);
mat.SetColor("_GridBackgroundColor", GridBackgroundColor);
}
/// <summary>
/// 释放此配置创建的材质实例(不释放 OverrideMaterial)。
/// </summary>
public void ReleaseMaterial()
{
if (_cachedMaterial != null)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
DestroyImmediate(_cachedMaterial);
else
#endif
Destroy(_cachedMaterial);
_cachedMaterial = null;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 32753aff001677a4b81e10dfae39b4cb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,60 @@
using System;
using UnityEngine;
namespace XericLibrary.Runtime.Blueprint.Render
{
/// <summary>
/// QuickGraph 输入工具配置表 —— 控制平移/缩放插值速率、灵敏度等可调参数。
/// 右键 Project 窗口 → Create/Blueprint/QuickGraph/Input Config 创建资产。
/// 拖入蓝图组件的 ToolConfigAssets 列表即可覆盖默认值。
/// </summary>
[CreateAssetMenu(
fileName = "QuickGraphInputConfig",
menuName = "Xeric Library/Blueprint/QuickGraph/Input Config",
order = 4)]
[BlueprintTheme("QuickGraph")]
public class QuickGraphInputConfig : BlueprintToolConfigBase
{
/// <summary>目标工具类型</summary>
public override System.Type TargetToolType
{
get { return typeof(QuickGraphInputTool); }
}
[Header("平移")]
[Tooltip("平移动画插值速率(越大跟随越快,3=适中,10=几乎瞬移)")]
[Range(1f, 20f)]
public float PanLerpSpeed = 3f;
[Header("缩放")]
[Tooltip("缩放动画插值速率(越大跟随更快)")]
[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;
[Header("缩放范围")]
[Tooltip("最小缩放级别")]
[Range(0.01f, 1f)]
public float MinZoom = 0.1f;
[Tooltip("最大缩放级别")]
[Range(1f, 10f)]
public float MaxZoom = 3f;
[Header("键盘")]
[Tooltip("键盘方向键平移速度(画布单位/秒)")]
[Range(50f, 2000f)]
public float KeyboardPanSpeed = 600f;
[Tooltip("键盘 +/- 缩放速度(每次帧增量,建议 0.01~0.5)")]
[Range(0.005f, 0.5f)]
public float KeyboardZoomSpeed = 0.05f;
[Header("框选")]
[Tooltip("框选拖拽触发阈值(像素)")]
[Range(1f, 20f)]
public float DragThreshold = 5f;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cfce52570fac4f5488becd85ce4f023b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,64 @@
using System;
using UnityEngine;
namespace XericLibrary.Runtime.Blueprint.Render
{
/// <summary>
/// QuickGraph 节点渲染配置表 —— 控制节点矩形、边框、圆角等视觉参数。
/// 右键 Project 窗口 → Create/Blueprint/QuickGraph/Node Render Config 创建资产。
/// 拖入蓝图组件的 ToolConfigAssets 列表即可覆盖默认值。
/// </summary>
[CreateAssetMenu(
fileName = "QuickGraphNodeRenderConfig",
menuName = "Xeric Library/Blueprint/QuickGraph/Node Render Config",
order = 1)]
[BlueprintTheme("QuickGraph")]
public class QuickGraphNodeRenderConfig : BlueprintToolConfigBase
{
/// <summary>目标工具类型</summary>
public override System.Type TargetToolType
{
get { return typeof(QuickUGUIGraphNodeRenderTool); }
}
[Header("尺寸")]
[Tooltip("节点宽度(像素)")]
public float NodeWidth = 90f;
[Tooltip("节点高度(像素)")]
public float NodeHeight = 130f;
[Header("边框")]
[Tooltip("边框厚度(像素)")]
public float BorderThickness = 3f;
[Header("圆角")]
[Tooltip("圆角大小(0~1 归一化)")]
[Range(0f, 0.5f)]
public float ChamferSize = 0.2f;
[Tooltip("圆角细分段数")]
[Range(1, 16)]
public int ChamferSegments = 4;
[Header("颜色")]
[Tooltip("节点默认背景色")]
public Color DefaultBackgroundColor = new Color(0.16f, 0.16f, 0.16f);
[Tooltip("节点默认文字色")]
public Color DefaultTextColor = Color.white;
[Header("文本")]
[Tooltip("节点标题字号")]
public float TitleFontSize = 14f;
[Tooltip("节点标题字体(留空使用默认 TMP 字体)")]
public TMPro.TMP_FontAsset TitleFont;
[Header("LOD(缩放等级细节)")]
[Tooltip("归一化缩放值低于此阈值时触发 LOD 0(极简:纯色节点,无文本)。0~1。默认 0.3。")]
[Range(0f, 1f)]
public float Lod0Threshold = 0.3f;
[Tooltip("归一化缩放值低于此阈值时触发 LOD 1(简化:节点+边框,无文本);高于此值为 LOD 2(完整渲染)。0~1。默认 0.7。")]
[Range(0f, 1f)]
public float Lod1Threshold = 0.7f;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8ab3d3829c73815468ed8c5ae306a6ee
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,66 @@
using System;
using UnityEngine;
using XericLibrary.Runtime.UIGraph;
namespace XericLibrary.Runtime.Blueprint.Render
{
/// <summary>
/// QuickGraph 连线渲染配置表 —— 控制贝塞尔曲线宽度、箭头形状等全部视觉参数。
/// 右键 Project 窗口 → Create/Blueprint/QuickGraph/Wire Render Config 创建资产。
/// 拖入蓝图组件的 ToolConfigAssets 列表即可覆盖默认值。
/// </summary>
[CreateAssetMenu(
fileName = "QuickGraphWireRenderConfig",
menuName = "Xeric Library/Blueprint/QuickGraph/Wire Render Config",
order = 2)]
[BlueprintTheme("QuickGraph")]
public class QuickGraphWireRenderConfig : BlueprintToolConfigBase
{
/// <summary>目标工具类型</summary>
public override System.Type TargetToolType
{
get { return typeof(QuickUGUIGraphWireRenderTool); }
}
[Header("连线")]
[Tooltip("连线宽度(像素)")]
[Range(1f, 20f)]
public float WireWidth = 5f;
[Header("贝塞尔手柄")]
[Tooltip("源端手柄从端口伸出的距离(画布单位)。数值越大曲线越平缓。")]
[Range(10f, 500f)]
public float SourceHandleLength = 80f;
[Tooltip("目标端手柄从端口伸出的距离(画布单位)。")]
[Range(10f, 500f)]
public float TargetHandleLength = 80f;
[Header("箭头")]
[Tooltip("箭头形状")]
public ArrowShape ArrowShape = ArrowShape.Triangle;
[Tooltip("箭头是否反向")]
public bool ArrowReversed = false;
[Tooltip("箭头在曲线上的位置(0=起点, 1=终点)")]
[Range(0f, 1f)]
public float ArrowProgress = 1f;
[Tooltip("箭头宽度(垂直切线方向,像素)")]
[Range(4f, 40f)]
public float ArrowWidth = 12f;
[Tooltip("箭头高度(沿切线方向,像素)")]
[Range(4f, 40f)]
public float ArrowHeight = 10f;
[Tooltip("深度补偿:-1=尾部对齐曲线点,0=中心对齐,1=头部对齐曲线点")]
[Range(-1f, 1f)]
public float ArrowDepthCompensation = -1f;
[Header("曲线质量")]
[Tooltip("贝塞尔曲线细分段数")]
[Range(8, 64)]
public int TessellationSegments = 32;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 21208cffd6108e04d815bb385f0af3fc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,275 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace XericLibrary.Runtime.Blueprint.Render
{
/// <summary>
/// 层容器条目 —— 一层中挂载的所有渲染组件
/// </summary>
public sealed class LayerContainerEntry
{
/// <summary>容器根 GameObject,锚点撑满蓝图</summary>
public GameObject Container;
/// <summary>图元渲染器(节点矩形)</summary>
public BlueprintPrimitiveRenderer PrimitiveRenderer;
/// <summary>曲线渲染器(连线)</summary>
public BlueprintCurveRenderer CurveRenderer;
/// <summary>节点 → TMP Text 映射(挂在此容器下)</summary>
public Dictionary<BlueprintNode, TMPro.TMP_Text> NodeTexts
= new Dictionary<BlueprintNode, TMPro.TMP_Text>();
}
/// <summary>
/// 共享层容器管理器 —— 管理桶排序中每一层的容器 GameObject。
/// 一层一个容器,内部按 sibling 顺序挂载:
/// 1. Image 背景(由 GridBackgroundTool 处理,挂 root 下)
/// 2. __Curve(连线)
/// 3. __Primitive(节点矩形)
/// 4. TMP Text(节点文字)
/// 各渲染工具通过此管理器共享同一组层容器。
///
/// 所有创建的 GameObject 默认 HideFlags.HideAndDontSave,
/// 避免在编辑器→运行/运行→编辑器切换时残留场景。
/// 调试时可通过 GraphTheoryBlueprintComponent._showDebugObjects 强制显示。
/// </summary>
public class LayerContainerManager
{
/// <summary>所有蓝图实例共享的层管理器(蓝图实例 → 层管理器)</summary>
private static Dictionary<BlueprintGraph, LayerContainerManager> s_Instances
= new Dictionary<BlueprintGraph, LayerContainerManager>();
/// <summary>获取或创建指定蓝图实例的层容器管理器</summary>
public static LayerContainerManager GetForGraph(BlueprintGraph graph, Transform renderRoot)
{
if (s_Instances.TryGetValue(graph, out var mgr) && mgr != null)
return mgr;
mgr = new LayerContainerManager(renderRoot);
s_Instances[graph] = mgr;
return mgr;
}
/// <summary>释放蓝图实例的层容器</summary>
public static void ReleaseForGraph(BlueprintGraph graph)
{
if (s_Instances.TryGetValue(graph, out var mgr))
{
mgr.DestroyAll();
s_Instances.Remove(graph);
}
}
private readonly Transform _renderRoot;
private readonly Dictionary<int, LayerContainerEntry> _layers
= new Dictionary<int, LayerContainerEntry>();
private LayerContainerManager(Transform renderRoot)
{
_renderRoot = renderRoot;
}
/// <summary>
/// 获取指定层级的容器条目。不存在则创建。
/// </summary>
public LayerContainerEntry GetOrCreateLayer(int layer)
{
if (_layers.TryGetValue(layer, out var entry) && entry != null)
return entry;
// --- 创建容器 ---
var container = new GameObject($"__BpLayer_{layer:D2}", typeof(RectTransform));
container.hideFlags = HideFlags.HideAndDontSave;
container.transform.SetParent(_renderRoot, false);
var ctRt = container.GetComponent<RectTransform>();
ctRt.anchorMin = Vector2.zero;
ctRt.anchorMax = Vector2.one;
ctRt.offsetMin = Vector2.zero;
ctRt.offsetMax = Vector2.zero;
entry = new LayerContainerEntry { Container = container };
// --- 创建连线渲染器(底层) ---
var curveGo = new GameObject("__Curve", typeof(RectTransform));
curveGo.hideFlags = HideFlags.HideAndDontSave;
curveGo.transform.SetParent(container.transform, false);
var crRt = curveGo.GetComponent<RectTransform>();
crRt.anchorMin = Vector2.zero;
crRt.anchorMax = Vector2.one;
crRt.offsetMin = Vector2.zero;
crRt.offsetMax = Vector2.zero;
entry.CurveRenderer = curveGo.AddComponent<BlueprintCurveRenderer>();
// --- 创建图元渲染器(中层) ---
var primGo = new GameObject("__Primitive", typeof(RectTransform));
primGo.hideFlags = HideFlags.HideAndDontSave;
primGo.transform.SetParent(container.transform, false);
var prRt = primGo.GetComponent<RectTransform>();
prRt.anchorMin = Vector2.zero;
prRt.anchorMax = Vector2.one;
prRt.offsetMin = Vector2.zero;
prRt.offsetMax = Vector2.zero;
entry.PrimitiveRenderer = primGo.AddComponent<BlueprintPrimitiveRenderer>();
_layers[layer] = entry;
ReorderAllContainers();
return entry;
}
/// <summary>
/// 尝试获取已存在的层容器(不创建)
/// </summary>
public LayerContainerEntry TryGetLayer(int layer)
{
_layers.TryGetValue(layer, out var entry);
return entry;
}
/// <summary>
/// 获取或创建节点文本(挂到指定层容器下)
/// </summary>
public TMPro.TMP_Text GetOrCreateNodeText(BlueprintNode node, int layer)
{
var entry = GetOrCreateLayer(layer);
if (entry.NodeTexts.TryGetValue(node, out var text) && text != null)
return text;
var textGo = new GameObject($"__Text_{node.ElementId}", typeof(RectTransform));
textGo.hideFlags = HideFlags.HideAndDontSave;
textGo.transform.SetParent(entry.Container.transform, false);
var tmp = textGo.AddComponent<TMPro.TextMeshProUGUI>();
tmp.alignment = TMPro.TextAlignmentOptions.Center;
tmp.fontSize = 12;
tmp.enableAutoSizing = false;
var rt = tmp.rectTransform;
rt.pivot = new Vector2(0.5f, 0.5f);
rt.anchorMin = new Vector2(0.5f, 0.5f);
rt.anchorMax = new Vector2(0.5f, 0.5f);
// 放到该层最末尾(覆盖矩形之上)
tmp.rectTransform.SetAsLastSibling();
entry.NodeTexts[node] = tmp;
return tmp;
}
/// <summary>
/// 尝试获取已存在的节点文本对象,不存在则返回 null。
/// </summary>
public TMPro.TMP_Text TryGetNodeText(BlueprintNode node, int layer)
{
if (!_layers.TryGetValue(layer, out var entry) || entry == null) return null;
entry.NodeTexts.TryGetValue(node, out var text);
return text;
}
/// <summary>
/// 清除指定层容器的所有渲染内容(不清除容器结构)
/// </summary>
public void ClearLayer(int layer)
{
if (!_layers.TryGetValue(layer, out var entry) || entry == null) return;
entry.PrimitiveRenderer?.ClearAll();
entry.CurveRenderer?.ClearAll();
foreach (var kvp in entry.NodeTexts)
{
if (kvp.Value != null)
kvp.Value.gameObject.SetActive(false);
}
}
/// <summary>
/// 清理指定层中已不存在的节点的文本对象
/// </summary>
public void PruneDeadTexts(int layer, HashSet<BlueprintNode> aliveNodes)
{
if (!_layers.TryGetValue(layer, out var entry) || entry == null) return;
var dead = new List<BlueprintNode>();
foreach (var kvp in entry.NodeTexts)
{
if (!aliveNodes.Contains(kvp.Key))
dead.Add(kvp.Key);
}
foreach (var node in dead)
{
if (entry.NodeTexts.TryGetValue(node, out var text) && text != null)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
Object.DestroyImmediate(text.gameObject);
else
#endif
Object.Destroy(text.gameObject);
}
entry.NodeTexts.Remove(node);
}
}
/// <summary>
/// 移除超出活跃范围的所有层容器
/// </summary>
public void PruneLayersAbove(int highestActiveLayer)
{
var toRemove = new List<int>();
foreach (var kvp in _layers)
{
if (kvp.Key > highestActiveLayer)
toRemove.Add(kvp.Key);
}
foreach (int layer in toRemove)
{
if (_layers.TryGetValue(layer, out var entry) && entry != null)
{
if (entry.Container != null)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
Object.DestroyImmediate(entry.Container);
else
#endif
Object.Destroy(entry.Container);
}
}
_layers.Remove(layer);
}
}
/// <summary>
/// 销毁所有层容器
/// </summary>
public void DestroyAll()
{
foreach (var kvp in _layers)
{
if (kvp.Value?.Container != null)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
Object.DestroyImmediate(kvp.Value.Container);
else
#endif
Object.Destroy(kvp.Value.Container);
}
}
_layers.Clear();
}
private void ReorderAllContainers()
{
var sorted = new List<KeyValuePair<int, LayerContainerEntry>>(_layers);
sorted.Sort((a, b) => a.Key.CompareTo(b.Key));
foreach (var kvp in sorted)
kvp.Value.Container.transform.SetAsLastSibling();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 159fb4400ff6e5e4b859ddfd37f60a6c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,141 @@
using UnityEngine;
using UnityEngine.UI;
namespace XericLibrary.Runtime.Blueprint.Render
{
/// <summary>
/// 快速 UGUI 图论网格背景渲染工具。
/// 在蓝图画布最底层创建一个全屏 Image 组件绘制网格背景,
/// 每帧将画布的 zoom / pan 同步到 Shader 的 _Transform 属性。
/// 全部可调参数由 QuickGraphGridBackgroundConfig 配置资产提供。
/// </summary>
[BlueprintTool(phase: ToolPhase.Render, order: 0)]
[BlueprintTheme("QuickGraph")]
public class QuickUGUIGraphGridBackgroundTool : BlueprintTool
{
private const string BackgroundGoName = "__Bp_GridBackground";
private QuickGraphGridBackgroundConfig _defaultConfig;
private QuickGraphGridBackgroundConfig Config
{
get
{
if (ToolConfig is QuickGraphGridBackgroundConfig external)
return external;
if (_defaultConfig == null)
_defaultConfig = ScriptableObject.CreateInstance<QuickGraphGridBackgroundConfig>();
return _defaultConfig;
}
}
private Image _backgroundImage;
private Transform _renderRoot;
public override void OnInitialize()
{
EnsureBackground();
}
public override void OnRender()
{
if (Graph == null) return;
EnsureBackground();
SyncMaterialProperties();
}
private void EnsureBackground()
{
if (_backgroundImage != null) return;
EnsureRenderRoot();
if (_renderRoot == null) return;
var existing = _renderRoot.Find(BackgroundGoName);
if (existing != null)
{
_backgroundImage = existing.GetComponent<Image>();
if (_backgroundImage == null)
{
Object.DestroyImmediate(existing.gameObject);
}
else
{
ApplyMaterial();
return;
}
}
var bgGo = new GameObject(BackgroundGoName, typeof(RectTransform));
bgGo.hideFlags = HideFlags.HideAndDontSave;
bgGo.transform.SetParent(_renderRoot, false);
bgGo.transform.SetAsFirstSibling();
var rt = bgGo.GetComponent<RectTransform>();
rt.anchorMin = Vector2.zero;
rt.anchorMax = Vector2.one;
rt.offsetMin = Vector2.zero;
rt.offsetMax = Vector2.zero;
_backgroundImage = bgGo.AddComponent<Image>();
_backgroundImage.raycastTarget = true;
ApplyMaterial();
}
/// <summary>
/// 从配置获取材质并设置到 Image(仅首次执行)。
/// </summary>
private void ApplyMaterial()
{
if (_backgroundImage == null) return;
var mat = Config.GetOrCreateMaterial();
if (mat != null)
_backgroundImage.material = mat;
}
/// <summary>
/// 每帧将当前画布 zoom / pan 同步到 Shader,
/// 同时写入配置中所有 Shader 属性。
/// </summary>
private void SyncMaterialProperties()
{
if (_backgroundImage == 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();
}
public override void OnDestroy()
{
if (_backgroundImage != null && _backgroundImage.gameObject != null)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
Object.DestroyImmediate(_backgroundImage.gameObject);
else
#endif
Object.Destroy(_backgroundImage.gameObject);
_backgroundImage = null;
}
Config.ReleaseMaterial();
_defaultConfig = null;
_renderRoot = null;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: da293e1f23cfca24b9ca4d40f91d2d8c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,264 @@
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using XericLibrary.Runtime.UIGraph;
using PrimitiveType = XericLibrary.Runtime.UIGraph.PrimitiveType;
namespace XericLibrary.Runtime.Blueprint.Render
{
/// <summary>
/// 快速 UGUI 图论节点样式渲染工具。
/// 通过共享的 LayerContainerManager 管理每层容器,
/// 计算端口相对位置并更新 TMP 文本显示。
/// 可调参数由 QuickGraphNodeRenderConfig 配置资产提供,
/// 拖入蓝图组件即可覆盖默认值。
///
/// <para>LOD 模式(由 zoom 决定):
/// - LOD 0:节点纯色填充(bg=borderColor),无边框无文本
/// - LOD 1:正常节点+边框,无文本
/// - LOD 2:完整渲染(含文本)
/// </para>
/// </summary>
[BlueprintTool(phase: ToolPhase.Render, order: 100)]
[BlueprintTheme("QuickGraph")]
public class QuickUGUIGraphNodeRenderTool : BlueprintTool
{
private enum LodLevel { Minimal, Simplified, Full }
// --- 默认配置(代码内建,ScriptableObject.CreateInstance 创建) ---
private QuickGraphNodeRenderConfig _defaultConfig;
// --- 当前生效的配置 ---
private QuickGraphNodeRenderConfig Config
{
get
{
if (ToolConfig is QuickGraphNodeRenderConfig external)
return external;
if (_defaultConfig == null)
_defaultConfig = ScriptableObject.CreateInstance<QuickGraphNodeRenderConfig>();
return _defaultConfig;
}
}
private Transform _renderRoot;
private LayerContainerManager _layerMgr;
public override void OnRender()
{
if (Graph == null) return;
EnsureRenderRoot();
if (_renderRoot == null) return;
var buckets = Graph.RenderLayers;
if (buckets == null) return;
_layerMgr = LayerContainerManager.GetForGraph(Graph, _renderRoot);
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; // 扩展一点避免边缘抖动
viewport.xMin -= margin;
viewport.xMax += margin;
viewport.yMin -= margin;
viewport.yMax += margin;
int bucketCount = buckets.BucketCount;
var layerNodes = new List<BlueprintNode>[bucketCount];
for (int i = 0; i < bucketCount; i++)
layerNodes[i] = new List<BlueprintNode>();
foreach (var node in Graph.Nodes)
{
int layer = node.RenderLayer;
if (layer < 0 || layer >= bucketCount) continue;
// 视口裁剪过滤
if (!viewport.Contains(node.Position)) continue;
layerNodes[layer].Add(node);
}
int highestActiveLayer = -1;
for (int i = 0; i < bucketCount; i++)
{
if (layerNodes[i].Count > 0)
{
highestActiveLayer = i;
var entry = _layerMgr.GetOrCreateLayer(i);
RenderLayerNodes(entry, layerNodes[i], i, cfg);
}
else
{
var entry = _layerMgr.TryGetLayer(i);
entry?.PrimitiveRenderer?.ClearAll();
// 空层也要回收残留的 TMP 文本(被视口裁剪掉的节点)
_layerMgr.PruneDeadTexts(i, new HashSet<BlueprintNode>());
}
}
_layerMgr.PruneLayersAbove(highestActiveLayer);
}
/// <summary>
/// 根据归一化缩放值计算 LOD 等级。
/// normalizedZoom = (zoom - MinZoom) / (MaxZoom - MinZoom),将 zoom 映射到 [0, 1]。
/// </summary>
private LodLevel GetLodLevel(QuickGraphNodeRenderConfig cfg)
{
float min = Graph.Canvas.MinZoom;
float max = Graph.Canvas.MaxZoom;
float range = max - min;
float normalized = range > 0.001f ? (Graph.Canvas.ZoomLevel - min) / range : 1f;
if (normalized < cfg.Lod0Threshold) return LodLevel.Minimal;
if (normalized < cfg.Lod1Threshold) return LodLevel.Simplified;
return LodLevel.Full;
}
private void RenderLayerNodes(LayerContainerEntry entry, List<BlueprintNode> nodes, int layer,
QuickGraphNodeRenderConfig cfg)
{
if (entry.PrimitiveRenderer != null)
entry.PrimitiveRenderer.ClearAll();
var aliveNodes = new HashSet<BlueprintNode>(nodes);
float zoom = Graph.Canvas.ZoomLevel;
LodLevel lod = GetLodLevel(cfg);
float nw = cfg.NodeWidth * zoom;
float nh = cfg.NodeHeight * zoom;
float border = (lod == LodLevel.Minimal) ? 0f : cfg.BorderThickness * zoom;
foreach (var node in nodes)
{
node.NodeSize = new Vector2(nw, nh);
// 端口始终用未缩放尺寸计算(连线系统的坐标基准)
CalculatePortPositions(node, cfg.NodeWidth, cfg.NodeHeight);
var localPos = Graph.Canvas.CanvasToLocal(node.Position);
Color bgColor, borderColor;
if (lod == LodLevel.Minimal)
{
// LOD 0:纯色块,bg = 边框色, 无边框
bgColor = node.NodeColor;
borderColor = node.NodeColor;
}
else
{
bgColor = node.NodeBGColor;
borderColor = node.NodeColor;
}
var primEntry = new PrimitiveCacheEntry
{
type = PrimitiveType.Rectangle,
sizeMode = SizeMode.InscribedEllipse,
center = localPos,
size = new Vector2(nw, nh),
angle = 0f,
@params = new PrimitiveParams
{
axisScaleX = 1f,
axisScaleY = 1f,
sideCount = 4,
chamferSize = cfg.ChamferSize,
chamferSegments = cfg.ChamferSegments,
bgColor = bgColor,
centerColor = Color.white,
borderColor = borderColor,
borderThickness = border,
}
};
entry.PrimitiveRenderer.AddPrimitive(primEntry);
// LOD >= Full 时才显示节点文本
if (lod == LodLevel.Full)
UpdateNodeText(node, layer, nw, nh, border, cfg);
else
HideNodeText(node, layer);
}
entry.PrimitiveRenderer.SetPrimitiveDirty(0);
_layerMgr.PruneDeadTexts(layer, aliveNodes);
}
private static void CalculatePortPositions(BlueprintNode node, float nw, float nh)
{
int inCount = node.InputPorts.Count;
int outCount = node.OutputPorts.Count;
for (int i = 0; i < inCount; i++)
{
float y = nh * (0.5f - (float)(i + 1) / (inCount + 1));
node.InputPorts[i].RelativePosition = new Vector2(-nw * 0.5f, y);
}
for (int i = 0; i < outCount; i++)
{
float y = nh * (0.5f - (float)(i + 1) / (outCount + 1));
node.OutputPorts[i].RelativePosition = new Vector2(nw * 0.5f, y);
}
}
private void UpdateNodeText(BlueprintNode node, int layer, float nw, float nh, float border,
QuickGraphNodeRenderConfig cfg)
{
var tmp = _layerMgr.GetOrCreateNodeText(node, layer);
float zoom = Graph.Canvas.ZoomLevel;
tmp.text = node.NodeTitle;
tmp.color = node.NodeTextColor;
tmp.fontSize = cfg.TitleFontSize * Mathf.Max(zoom, 0.5f);
if (cfg.TitleFont != null)
tmp.font = cfg.TitleFont;
var rt = tmp.rectTransform;
rt.anchoredPosition = Graph.Canvas.CanvasToLocal(node.Position);
rt.sizeDelta = new Vector2(nw - border * 2f, nh - border * 2f);
rt.SetAsLastSibling();
tmp.gameObject.SetActive(true);
}
/// <summary>隐藏节点的 TMP 文本(不销毁,复用)。</summary>
private void HideNodeText(BlueprintNode node, int layer)
{
var tmp = _layerMgr.TryGetNodeText(node, layer);
if (tmp != null)
tmp.gameObject.SetActive(false);
}
/// <summary>
/// 将配置默认值写入节点,使渲染工具直接读取节点值。
/// config 只控制背景色,边框色和文本色由节点初始化时定义。
/// </summary>
private static void ApplyConfigToNode(BlueprintNode node, QuickGraphNodeRenderConfig cfg)
{
node.NodeBGColor = cfg.DefaultBackgroundColor;
}
private void EnsureRenderRoot()
{
if (_renderRoot != null) return;
if (Graph.Canvas == null) return;
_renderRoot = Graph.Canvas.GetRootTransform();
}
public override void OnDestroy()
{
if (Graph != null)
LayerContainerManager.ReleaseForGraph(Graph);
_layerMgr = null;
_renderRoot = null;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 552d9e16260f6704f8a3da72908af509
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,181 @@
using System.Collections.Generic;
using UnityEngine;
using XericLibrary.Runtime.UIGraph;
namespace XericLibrary.Runtime.Blueprint.Render
{
/// <summary>
/// 快速 UGUI 图论连线渲染工具。
/// 通过共享的 LayerContainerManager 使用与节点相同的层容器,
/// 连线绘制在两端节点所在层的较低层中(不遮挡节点)。
/// 可调参数由 QuickGraphWireRenderConfig 配置资产提供。
/// </summary>
[BlueprintTool(phase: ToolPhase.Render, order: 110)]
[BlueprintTheme("QuickGraph")]
public class QuickUGUIGraphWireRenderTool : BlueprintTool
{
private QuickGraphWireRenderConfig _defaultConfig;
private QuickGraphWireRenderConfig Config
{
get
{
if (ToolConfig is QuickGraphWireRenderConfig external)
return external;
if (_defaultConfig == null)
_defaultConfig = ScriptableObject.CreateInstance<QuickGraphWireRenderConfig>();
return _defaultConfig;
}
}
private Transform _renderRoot;
private LayerContainerManager _layerMgr;
public override void OnRender()
{
if (Graph == null) return;
EnsureRenderRoot();
if (_renderRoot == null) return;
var buckets = Graph.RenderLayers;
if (buckets == null) return;
_layerMgr = LayerContainerManager.GetForGraph(Graph, _renderRoot);
var cfg = Config;
int bucketCount = buckets.BucketCount;
// 视口裁剪
var viewport = Graph.Canvas.GetViewportRect();
float margin = cfg.WireWidth * 20f;
viewport.xMin -= margin;
viewport.xMax += margin;
viewport.yMin -= margin;
viewport.yMax += margin;
var layerWires = new List<BlueprintWire>[bucketCount];
for (int i = 0; i < bucketCount; i++)
layerWires[i] = new List<BlueprintWire>();
foreach (var wire in Graph.Wires)
{
int srcLayer = wire.SourcePort?.OwnerNode?.RenderLayer ?? 0;
int tgtLayer = wire.TargetPort?.OwnerNode?.RenderLayer ?? 0;
int wireLayer = Mathf.Min(srcLayer, tgtLayer);
if (wireLayer < 0 || wireLayer >= bucketCount) wireLayer = 0;
// 两端都不在视口内则跳过
if (wire.SourcePort?.OwnerNode != null && wire.TargetPort?.OwnerNode != null)
{
if (!viewport.Contains(wire.SourcePort.OwnerNode.Position) &&
!viewport.Contains(wire.TargetPort.OwnerNode.Position))
continue;
}
layerWires[wireLayer].Add(wire);
}
for (int i = 0; i < bucketCount; i++)
{
if (layerWires[i].Count > 0)
{
var entry = _layerMgr.GetOrCreateLayer(i);
RenderLayerWires(entry, layerWires[i], cfg, Graph.Canvas);
}
else
{
var entry = _layerMgr.TryGetLayer(i);
entry?.CurveRenderer?.ClearAll();
}
}
}
private static void RenderLayerWires(LayerContainerEntry entry, List<BlueprintWire> wires,
QuickGraphWireRenderConfig cfg, IBlueprintCanvas canvas)
{
entry.CurveRenderer.ClearAll();
float zoom = canvas.ZoomLevel;
foreach (var wire in wires)
{
if (wire.SourcePort == null || wire.TargetPort == null) continue;
if (wire.SourcePort.OwnerNode == null || wire.TargetPort.OwnerNode == null) continue;
Vector2 startPos = canvas.CanvasToLocal(wire.SourcePort.GetWorldPosition());
Vector2 endPos = canvas.CanvasToLocal(wire.TargetPort.GetWorldPosition());
// 手柄方向 = 端口在节点上的朝向
// 端口在节点右侧 (RelativePosition.x > 0) → 手柄朝右 (+X)
// 端口在节点左侧 (RelativePosition.x < 0) → 手柄朝左 (-X)
float srcDirX = Mathf.Sign(wire.SourcePort.RelativePosition.x);
float tgtDirX = Mathf.Sign(wire.TargetPort.RelativePosition.x);
// 同一侧(如两个端口都在右侧)则方向取反
if (srcDirX == tgtDirX) srcDirX *= -1f;
// 手柄距离 = 配置值 × zoom(画布单位 → local 坐标,与 CanvasToLocal 一致)
Vector2 handle1 = startPos + new Vector2(srcDirX * cfg.SourceHandleLength * zoom, 0f);
Vector2 handle2 = endPos + new Vector2(tgtDirX * cfg.TargetHandleLength * zoom, 0f);
float ww = cfg.WireWidth * zoom;
var ctrlPts = new Vector3[]
{
new Vector3(startPos.x, startPos.y, ww),
new Vector3(handle1.x, handle1.y, ww),
new Vector3(handle2.x, handle2.y, ww),
new Vector3(endPos.x, endPos.y, ww),
};
Color32 srcColor = wire.SourcePort.OwnerNode.NodeColor;
Color32 tgtColor = wire.TargetPort.OwnerNode.NodeColor;
var arrow = new ArrowHeadData
{
shape = cfg.ArrowShape,
reversed = cfg.ArrowReversed,
progress = cfg.ArrowProgress,
width = cfg.ArrowWidth * zoom,
height = cfg.ArrowHeight * zoom,
depthCompensation = cfg.ArrowDepthCompensation,
color = tgtColor,
};
int count = ctrlPts.Length;
var ctrlPts2 = new Vector2[count];
var widths = new float[count];
for (int i = 0; i < count; i++)
{
ctrlPts2[i] = ctrlPts[i];
widths[i] = ctrlPts[i].z;
}
var curveEntry = new CurveCacheEntry
{
startColor = srcColor,
endColor = tgtColor,
tessellationSegments = cfg.TessellationSegments,
};
entry.CurveRenderer.AddCurve(curveEntry, ctrlPts2, widths, new[] { arrow });
}
if (wires.Count > 0)
entry.CurveRenderer.RebuildAll();
}
private void EnsureRenderRoot()
{
if (_renderRoot != null) return;
if (Graph.Canvas == null) return;
_renderRoot = Graph.Canvas.GetRootTransform();
}
public override void OnDestroy()
{
_layerMgr = null;
_renderRoot = null;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b98af5cb1bdbbcf4d8b60e620f6b2fdb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: