update 0.6.3.7

This commit is contained in:
2026-07-30 09:18:26 +08:00
parent 54644a9f15
commit 46560ea78f
49 changed files with 1126 additions and 1325 deletions
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8aa6308c2cd1dd845a8b4810310dce87
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,133 @@
using System;
using UnityEngine;
namespace XericLibrary.Runtime.Blueprint.QuickGraph
{
/// <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;
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: 38115e5721db6514bb8b8be6be10f26a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,51 @@
using System;
using UnityEngine;
namespace XericLibrary.Runtime.Blueprint.QuickGraph
{
/// <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("滚轮缩放除数。公式: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("最小缩放级别")]
[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("键盘 +/- 缩放速度(与 ZoomDivisor 配合,公式:zoom += 1 / ZoomDivisor)")]
[Range(0.005f, 0.5f)]
public float KeyboardZoomSpeed = 0.05f;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4735b93342f4fde4a8ed46dd28b46d4c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,59 @@
using System;
using UnityEngine;
namespace XericLibrary.Runtime.Blueprint.QuickGraph
{
/// <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 => typeof(NodeCanvasRenderTool);
[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 兼容字段")]
[Obsolete("节点 LOD 已迁移至 BlueprintRenderLodConfig(绝对 Zoom + 滞回),此字段不再参与渲染。")]
[HideInInspector] public float Lod0Threshold = 0.3f;
[Obsolete("节点 LOD 已迁移至 BlueprintRenderLodConfig(绝对 Zoom + 滞回),此字段不再参与渲染。")]
[HideInInspector] public float Lod1Threshold = 0.7f;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8038bf45323375b46a3a2820ddfe212e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,67 @@
using System;
using UnityEngine;
using XericLibrary.Runtime.UIGraph;
namespace XericLibrary.Runtime.Blueprint.QuickGraph
{
/// <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 => typeof(WireCanvasRenderTool);
[Header("连线")]
[Tooltip("连线宽度(像素)")]
[Range(1f, 20f)]
public float WireWidth = 5f;
[Tooltip("线条额外点击命中半径(画布单位);实际半径至少为线宽的一半)。")]
[Range(0f, 40f)]
public float HitTestRadius = 8f;
[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: c95f6e1f1ece40444839f0a29ac371e5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,118 @@
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using XericLibrary.Runtime.Blueprint.Render;
using XericLibrary.Runtime.UIGraph;
using PrimitiveType = XericLibrary.Runtime.UIGraph.PrimitiveType;
namespace XericLibrary.Runtime.Blueprint.QuickGraph
{
[BlueprintTool(phase: ToolPhase.Render, order: 100)]
[BlueprintTheme("QuickGraph")]
public class NodeCanvasRenderTool : BlueprintCanvasRenderTool<BlueprintPrimitiveRenderer>
{
private QuickGraphNodeRenderConfig _defaultConfig;
private QuickGraphRenderStyleResolver _styleResolver;
private readonly Dictionary<string, (ulong chunkID, TMP_Text text)> _nodeTexts = new Dictionary<string, (ulong, TMP_Text)>();
private readonly HashSet<string> _staleTextCandidates = new HashSet<string>();
private QuickGraphNodeRenderConfig Config
{
get
{
if (ToolConfig is QuickGraphNodeRenderConfig external) return external;
if (_defaultConfig == null) _defaultConfig = ScriptableObject.CreateInstance<QuickGraphNodeRenderConfig>();
return _defaultConfig;
}
}
public override void OnInitialize() { base.OnInitialize(); _styleResolver = new QuickGraphRenderStyleResolver(Graph, nodeFallback: ConfigStyle); SyncAllNodeGeometry(); }
public override void OnConfigChanged()
{
_styleResolver?.Refresh(); SyncAllNodeGeometry();
base.OnConfigChanged();
}
private QuickGraphNodeRenderStyle ConfigStyle()
{
var cfg = Config;
return new QuickGraphNodeRenderStyle { Width = cfg.NodeWidth, Height = cfg.NodeHeight, Border = cfg.BorderThickness, Chamfer = cfg.ChamferSize, ChamferSegments = cfg.ChamferSegments, TitleFontSize = cfg.TitleFontSize };
}
protected override BlueprintPrimitiveRenderer BuildChunkRenderer(GameObject go) => go.AddComponent<BlueprintPrimitiveRenderer>();
protected override void ClearChunkData(BlueprintPrimitiveRenderer renderer) => renderer.ClearAll();
protected override void SetChunkDirty(BlueprintPrimitiveRenderer renderer) => renderer.SetPrimitiveDirty(0);
protected override bool AcceptsElement(IBlueprintElement element) => element is BlueprintNode;
protected override void AppendElementData(BlueprintPrimitiveRenderer renderer, IBlueprintElement element, ulong chunkID)
{
if (!(element is BlueprintNode node)) return;
var cfg = Config; var style = _styleResolver != null ? _styleResolver.Snapshot.Node : new QuickGraphNodeRenderStyle { Width=cfg.NodeWidth, Height=cfg.NodeHeight, Border=cfg.BorderThickness, Chamfer=cfg.ChamferSize, ChamferSegments=cfg.ChamferSegments, TitleFontSize=cfg.TitleFontSize }; var lod = CurrentElementSubmission.LOD;
CalculatePortPositions(node, style.Width, style.Height);
var local = CurrentElementSubmission.ChunkLocalPosition;
float border = lod == BlueprintLodLevel.Minimal ? 0f : style.Border;
Color bg = lod == BlueprintLodLevel.Minimal ? node.NodeColor : node.NodeBGColor;
Color edge = node.NodeColor;
renderer.AddPrimitive(new PrimitiveCacheEntry { type = PrimitiveType.Rectangle, sizeMode = SizeMode.InscribedEllipse, center = local, size = new Vector2(style.Width, style.Height), @params = new PrimitiveParams { axisScaleX = 1f, axisScaleY = 1f, sideCount = 4, chamferSize = style.Chamfer, chamferSegments = style.ChamferSegments, bgColor = new Color32(0,0,0,0), centerColor = bg, borderColor = edge, borderThickness = border } });
if (lod == BlueprintLodLevel.Full) UpdateNodeText(node, chunkID, local, style.Width, style.Height, border, style.TitleFontSize, cfg); else HideNodeText(node.ElementId);
}
private void SyncAllNodeGeometry()
{
if (Graph == null) return;
var cfg = Config; var style = _styleResolver != null ? _styleResolver.Snapshot.Node : new QuickGraphNodeRenderStyle { Width=cfg.NodeWidth, Height=cfg.NodeHeight };
foreach (var node in Graph.Nodes)
{
var oldSize = node.NodeSize;
CalculatePortPositions(node, style.Width, style.Height);
if (oldSize != node.NodeSize) Graph.UpdateNodeGeometry(node);
}
}
private static void CalculatePortPositions(BlueprintNode node, float width, float height)
{
// Position 语义为视觉中心;渲染配置尺寸必须同步给数据模型,供 BoundingBox / RTree 使用。
node.NodeSize = new Vector2(width, height);
for (int i=0;i<node.InputPorts.Count;i++) node.InputPorts[i].RelativePosition = new Vector2(-width*.5f, height*(.5f-(float)(i+1)/(node.InputPorts.Count+1)));
for (int i=0;i<node.OutputPorts.Count;i++) node.OutputPorts[i].RelativePosition = new Vector2(width*.5f, height*(.5f-(float)(i+1)/(node.OutputPorts.Count+1)));
}
protected override void BeginChunkSubmission(ulong chunk, BlueprintPrimitiveRenderer renderer)
{
foreach (var pair in _nodeTexts)
if (pair.Value.chunkID == chunk) _staleTextCandidates.Add(pair.Key);
}
protected override void EndChunkSubmission(ulong chunk, BlueprintPrimitiveRenderer renderer)
{
var remove = new List<string>();
foreach (var id in _staleTextCandidates)
if (_nodeTexts.TryGetValue(id, out var item) && item.chunkID == chunk) remove.Add(id);
foreach (var id in remove)
{
if (_nodeTexts[id].text != null) Object.Destroy(_nodeTexts[id].text.gameObject);
_nodeTexts.Remove(id);
}
_staleTextCandidates.Clear();
}
private void UpdateNodeText(BlueprintNode node, ulong chunk, Vector2 local, float width, float height, float border, float titleFontSize, QuickGraphNodeRenderConfig cfg)
{
_staleTextCandidates.Remove(node.ElementId);
TMP_Text text;
if (_nodeTexts.TryGetValue(node.ElementId, out var old) && old.chunkID == chunk && old.text != null) text = old.text;
else
{
if (old.text != null) Object.Destroy(old.text.gameObject);
var go = new GameObject($"__Text_{node.ElementId}", typeof(RectTransform)); go.hideFlags = HideFlags.HideAndDontSave; go.transform.SetParent(rendererTransform(chunk), false);
text = go.AddComponent<TextMeshProUGUI>(); text.alignment = TextAlignmentOptions.Center; _nodeTexts[node.ElementId] = (chunk, text);
}
text.text=node.NodeTitle; text.color=node.NodeTextColor; text.fontSize=titleFontSize; if(cfg.TitleFont!=null) text.font=cfg.TitleFont;
var rt=text.rectTransform; var chunkPivot = text.transform.parent is RectTransform parent ? parent.pivot : new Vector2(.5f, .5f);
rt.anchorMin=chunkPivot;rt.anchorMax=chunkPivot;rt.pivot=new Vector2(.5f,.5f);rt.anchoredPosition=local;rt.sizeDelta=new Vector2(width-border*2,height-border*2);text.gameObject.SetActive(true);rt.SetAsLastSibling();
}
private Transform rendererTransform(ulong chunk) => GetOrCreateChunk(chunk).transform;
private void HideNodeText(string id) { if (_nodeTexts.TryGetValue(id,out var item) && item.text != null) item.text.gameObject.SetActive(false); }
protected override void OnChunkRecycled(ulong chunk, BlueprintPrimitiveRenderer renderer)
{
_staleTextCandidates.Clear();
var remove=new List<string>(); foreach(var pair in _nodeTexts) if(pair.Value.chunkID==chunk) remove.Add(pair.Key);
foreach(var id in remove) { if(_nodeTexts[id].text!=null) Object.Destroy(_nodeTexts[id].text.gameObject); _nodeTexts.Remove(id); }
}
protected override void DestroyAllChunks() { foreach(var pair in _nodeTexts) if(pair.Value.text!=null) Object.Destroy(pair.Value.text.gameObject); _nodeTexts.Clear(); base.DestroyAllChunks(); }
public override void OnDestroy() { _styleResolver?.Dispose(); _styleResolver = null; base.OnDestroy(); }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 76eb4633e3b7b1b47ae35a2a4435663c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,69 @@
using System;
using UnityEngine;
using XericLibrary.Runtime.Blueprint;
using XericLibrary.Runtime.SuperStyleSheet;
using XericLibrary.Runtime.UIGraph;
namespace XericLibrary.Runtime.Blueprint.QuickGraph
{
[Flags] public enum QuickGraphRenderStyleChange { None = 0, NodeGeometry = 1, NodeAppearance = 2, WireGeometry = 4, WireAppearance = 8, GridAppearance = 16, All = NodeGeometry | NodeAppearance | WireGeometry | WireAppearance | GridAppearance }
public struct QuickGraphNodeRenderStyle { public float Width, Height, Border, Chamfer; public int ChamferSegments; public float TitleFontSize; }
public struct QuickGraphWireRenderStyle { public float Width, HitRadius, SourceHandle, TargetHandle, ArrowWidth, ArrowHeight, ArrowProgress, ArrowDepth; public int Tessellation; public ArrowShape ArrowShape; public bool ArrowReversed; }
public struct QuickGraphGridRenderStyle { public Material Material; public string ShaderName; public float Size, OverlayPower, Threshold, Exp; public Color Color, Background; }
public sealed class QuickGraphRenderStyleSnapshot { public int Revision; public QuickGraphRenderStyleChange Changes; public QuickGraphNodeRenderStyle Node; public QuickGraphWireRenderStyle Wire; public QuickGraphGridRenderStyle Grid; }
/// <summary>每次样式变更只解析一次;渲染热路径只消费强类型快照。</summary>
public sealed class QuickGraphRenderStyleResolver : IDisposable
{
private readonly BlueprintGraph _graph;
private readonly Func<QuickGraphNodeRenderStyle> _nodeFallback;
private readonly Func<QuickGraphWireRenderStyle> _wireFallback;
private readonly Func<QuickGraphGridRenderStyle> _gridFallback;
public QuickGraphRenderStyleSnapshot Snapshot { get; private set; }
public event Action<QuickGraphRenderStyleSnapshot> Changed;
public QuickGraphRenderStyleResolver(BlueprintGraph graph, Func<QuickGraphNodeRenderStyle> nodeFallback = null, Func<QuickGraphWireRenderStyle> wireFallback = null, Func<QuickGraphGridRenderStyle> gridFallback = null)
{
_graph = graph;
_nodeFallback = nodeFallback ?? DefaultNode;
_wireFallback = wireFallback ?? DefaultWire;
_gridFallback = gridFallback ?? DefaultGrid;
_graph.RenderStyleChanged += Refresh;
StyleManager.OnStylesReloaded += Refresh;
Refresh();
}
private static QuickGraphNodeRenderStyle DefaultNode() => new QuickGraphNodeRenderStyle { Width = 90f, Height = 130f, Border = 3f, Chamfer = .2f, ChamferSegments = 4, TitleFontSize = 14f };
private static QuickGraphWireRenderStyle DefaultWire() => new QuickGraphWireRenderStyle { Width = 5f, HitRadius = 8f, SourceHandle = 80f, TargetHandle = 80f, ArrowWidth = 12f, ArrowHeight = 10f, ArrowProgress = 1f, ArrowDepth = -1f, Tessellation = 32, ArrowShape = ArrowShape.Triangle };
private static QuickGraphGridRenderStyle DefaultGrid() => new QuickGraphGridRenderStyle { ShaderName = "XericLibrary/BluePrint/BlueprintBackgound_GridLine", Size = 100f, OverlayPower = 5f, Threshold = .99f, Exp = .001f, Color = Color.white, Background = Color.black };
// 样式项存在即优先使用;只有项缺失时才使用旧 ToolConfig 回退值。
private T Value<T>(string path, T fallback) { var value = _graph.GetRenderStyle(path); return value == null ? fallback : value.GetValueAs<T>(); }
public void Refresh()
{
var node = _nodeFallback();
node = new QuickGraphNodeRenderStyle { Width = Value("/quickgraph/node/width", node.Width), Height = Value("/quickgraph/node/height", node.Height), Border = Value("/quickgraph/node/border", node.Border), Chamfer = Value("/quickgraph/node/chamfer", node.Chamfer), ChamferSegments = Value("/quickgraph/node/chamferSegments", node.ChamferSegments), TitleFontSize = Value("/quickgraph/node/titleFontSize", node.TitleFontSize) };
var wire = _wireFallback();
wire = new QuickGraphWireRenderStyle { Width = Value("/quickgraph/wire/width", wire.Width), HitRadius = Value("/quickgraph/wire/hitRadius", wire.HitRadius), SourceHandle = Value("/quickgraph/wire/sourceHandle", wire.SourceHandle), TargetHandle = Value("/quickgraph/wire/targetHandle", wire.TargetHandle), ArrowWidth = Value("/quickgraph/wire/arrowWidth", wire.ArrowWidth), ArrowHeight = Value("/quickgraph/wire/arrowHeight", wire.ArrowHeight), ArrowProgress = Value("/quickgraph/wire/arrowProgress", wire.ArrowProgress), ArrowDepth = Value("/quickgraph/wire/arrowDepth", wire.ArrowDepth), Tessellation = Value("/quickgraph/wire/tessellation", wire.Tessellation), ArrowShape = Value("/quickgraph/wire/arrowShape", wire.ArrowShape), ArrowReversed = Value("/quickgraph/wire/arrowReversed", wire.ArrowReversed) };
var grid = _gridFallback();
grid = new QuickGraphGridRenderStyle { Material = Value("/quickgraph/grid/material", grid.Material), ShaderName = Value("/quickgraph/grid/shader", grid.ShaderName), Size = Value("/quickgraph/grid/size", grid.Size), OverlayPower = Value("/quickgraph/grid/overlayPower", grid.OverlayPower), Threshold = Value("/quickgraph/grid/threshold", grid.Threshold), Exp = Value("/quickgraph/grid/exp", grid.Exp), Color = Value("/quickgraph/grid/color", grid.Color), Background = Value("/quickgraph/grid/background", grid.Background) };
var old = Snapshot;
var changes = old == null ? QuickGraphRenderStyleChange.All : Classify(old, node, wire, grid);
Snapshot = new QuickGraphRenderStyleSnapshot { Revision = old == null ? 1 : old.Revision + 1, Changes = changes, Node = node, Wire = wire, Grid = grid };
if (changes != QuickGraphRenderStyleChange.None) Changed?.Invoke(Snapshot);
}
private static QuickGraphRenderStyleChange Classify(QuickGraphRenderStyleSnapshot old, QuickGraphNodeRenderStyle node, QuickGraphWireRenderStyle wire, QuickGraphGridRenderStyle grid)
{
QuickGraphRenderStyleChange changes = QuickGraphRenderStyleChange.None;
if (old.Node.Width != node.Width || old.Node.Height != node.Height) changes |= QuickGraphRenderStyleChange.NodeGeometry;
if (old.Node.Border != node.Border || old.Node.Chamfer != node.Chamfer || old.Node.ChamferSegments != node.ChamferSegments || old.Node.TitleFontSize != node.TitleFontSize) changes |= QuickGraphRenderStyleChange.NodeAppearance;
if (old.Wire.Width != wire.Width || old.Wire.HitRadius != wire.HitRadius || old.Wire.SourceHandle != wire.SourceHandle || old.Wire.TargetHandle != wire.TargetHandle) changes |= QuickGraphRenderStyleChange.WireGeometry;
if (old.Wire.ArrowWidth != wire.ArrowWidth || old.Wire.ArrowHeight != wire.ArrowHeight || old.Wire.ArrowProgress != wire.ArrowProgress || old.Wire.ArrowDepth != wire.ArrowDepth || old.Wire.Tessellation != wire.Tessellation || old.Wire.ArrowShape != wire.ArrowShape || old.Wire.ArrowReversed != wire.ArrowReversed) changes |= QuickGraphRenderStyleChange.WireAppearance;
if (old.Grid.Material != grid.Material || old.Grid.ShaderName != grid.ShaderName || old.Grid.Size != grid.Size || old.Grid.OverlayPower != grid.OverlayPower || old.Grid.Threshold != grid.Threshold || old.Grid.Exp != grid.Exp || old.Grid.Color != grid.Color || old.Grid.Background != grid.Background) changes |= QuickGraphRenderStyleChange.GridAppearance;
return changes;
}
public void Dispose() { _graph.RenderStyleChanged -= Refresh; StyleManager.OnStylesReloaded -= Refresh; Changed = null; }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a2abe7122e63ad946ba485026296788a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,113 @@
using UnityEngine;
using UnityEngine.UI;
namespace XericLibrary.Runtime.Blueprint.QuickGraph
{
/// <summary>QuickGraph 网格背景;样式资源材质只绑定不拥有,运行时创建材质才由本工具释放。</summary>
[BlueprintTool(phase: ToolPhase.Render, order: 0)]
[BlueprintTheme("QuickGraph")]
public class QuickUGUIGraphGridBackgroundTool : BlueprintTool
{
private const string BackgroundGoName = "__Bp_GridBackground";
private QuickGraphGridBackgroundConfig _defaultConfig;
private QuickGraphRenderStyleResolver _styleResolver;
private Image _backgroundImage;
private Material _backgroundMaterial;
private QuickGraphGridBackgroundConfig _ownedMaterialConfig;
private string _ownedShaderName;
private QuickGraphGridBackgroundConfig Config
{
get
{
if (ToolConfig is QuickGraphGridBackgroundConfig external) return external;
if (_defaultConfig == null) _defaultConfig = ScriptableObject.CreateInstance<QuickGraphGridBackgroundConfig>();
return _defaultConfig;
}
}
public override void OnInitialize()
{
_styleResolver = new QuickGraphRenderStyleResolver(Graph, gridFallback: ConfigStyle);
_styleResolver.Changed += OnStyleChanged;
EnsureBackground(); RebindMaterial(_styleResolver.Snapshot.Grid, true);
}
public override void OnRender()
{
if (Graph == null) return;
EnsureBackground();
var style = _styleResolver != null ? _styleResolver.Snapshot.Grid : ConfigStyle();
RebindMaterial(style, false);
ApplyStyle(style);
}
public override void OnConfigChanged()
{
_styleResolver?.Refresh();
if (_styleResolver == null) RebindMaterial(ConfigStyle(), true);
Graph?.MarkDirty();
}
private QuickGraphGridRenderStyle ConfigStyle()
{
var cfg = Config;
return new QuickGraphGridRenderStyle { Material = cfg.OverrideMaterial, ShaderName = cfg.ShaderName, Size = cfg.GridSize, OverlayPower = cfg.GridOverlayPower, Threshold = cfg.GridLineThreshold, Exp = cfg.GridExp, Color = cfg.GridColor, Background = cfg.GridBackgroundColor };
}
private void OnStyleChanged(QuickGraphRenderStyleSnapshot snapshot)
{
if ((snapshot.Changes & QuickGraphRenderStyleChange.GridAppearance) == 0) return;
RebindMaterial(snapshot.Grid, true);
Graph?.MarkDirty();
}
private void EnsureBackground()
{
if (_backgroundImage != null || Graph?.Canvas?.GetRootTransform() == null) return;
var root = Graph.Canvas.GetRootTransform(); var existing = root.Find(BackgroundGoName);
if (existing != null) _backgroundImage = existing.GetComponent<Image>();
if (_backgroundImage != null) return;
if (existing != null) Object.DestroyImmediate(existing.gameObject);
var go = new GameObject(BackgroundGoName, typeof(RectTransform)); go.hideFlags = HideFlags.HideAndDontSave; go.transform.SetParent(root, false); go.transform.SetAsFirstSibling();
var rect = go.GetComponent<RectTransform>(); rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = Vector2.zero; rect.offsetMax = Vector2.zero;
_backgroundImage = go.AddComponent<Image>(); _backgroundImage.raycastTarget = true; _backgroundImage.maskable = false;
}
private void RebindMaterial(QuickGraphGridRenderStyle style, bool force)
{
if (_backgroundImage == null) return;
Material target = style.Material;
QuickGraphGridBackgroundConfig owner = null;
string ownedShader = null;
if (target == null)
{
// 旧 Config 仅在 style 未提供共享材质时参与回退;其缓存材质为唯一可释放对象。
if (style.ShaderName == Config.ShaderName) { target = Config.GetOrCreateMaterial(); owner = Config.OverrideMaterial == null ? Config : null; }
else if (!string.IsNullOrEmpty(style.ShaderName))
{
if (_backgroundMaterial != null && _ownedShaderName == style.ShaderName) return;
var shader = Shader.Find(style.ShaderName); if (shader != null) { target = new Material(shader) { name = "BpGridBackground_StyleMat" }; ownedShader = style.ShaderName; }
}
}
if (target == _backgroundMaterial) return;
ReleaseOwnedMaterial();
_backgroundMaterial = target; _ownedMaterialConfig = owner; _ownedShaderName = ownedShader;
if (_backgroundImage != null) _backgroundImage.material = _backgroundMaterial;
}
private void ApplyStyle(QuickGraphGridRenderStyle style)
{
if (_backgroundMaterial == null || Graph?.Canvas == null || _backgroundImage == null) return;
var size = _backgroundImage.rectTransform.rect.size; var zoom = Graph.Canvas.ZoomLevel; var pan = Graph.Canvas.PanOffset; var gridSize = Mathf.Max(.0001f, style.Size);
_backgroundMaterial.SetVector("_Transform", new Vector4(size.x / (zoom * gridSize), size.y / (zoom * gridSize), -(pan.x + .5f * size.x) / (zoom * gridSize), -(pan.y + .5f * size.y) / (zoom * gridSize)));
_backgroundMaterial.SetFloat("_GridOverlayPower", style.OverlayPower); _backgroundMaterial.SetFloat("_GridLineThreshold", style.Threshold); _backgroundMaterial.SetFloat("_GridExp", style.Exp);
_backgroundMaterial.SetColor("_GridColor", style.Color); _backgroundMaterial.SetColor("_GridBackgroundColor", style.Background);
}
private void ReleaseOwnedMaterial()
{
if (_backgroundImage != null) _backgroundImage.material = null;
if (_ownedMaterialConfig != null) _ownedMaterialConfig.ReleaseMaterial();
else if (_backgroundMaterial != null && !string.IsNullOrEmpty(_ownedShaderName)) { if (Application.isPlaying) Object.Destroy(_backgroundMaterial); else Object.DestroyImmediate(_backgroundMaterial); }
_backgroundMaterial = null; _ownedMaterialConfig = null; _ownedShaderName = null;
}
public override void OnDestroy()
{
if (_styleResolver != null) _styleResolver.Changed -= OnStyleChanged;
_styleResolver?.Dispose(); _styleResolver = null; ReleaseOwnedMaterial();
if (_backgroundImage != null) { if (Application.isPlaying) Object.Destroy(_backgroundImage.gameObject); else Object.DestroyImmediate(_backgroundImage.gameObject); _backgroundImage = null; }
_defaultConfig = null;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1ba1fe1f333ee914b962da638a630281
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,82 @@
using UnityEngine;
using XericLibrary.Runtime.Blueprint.Render;
using XericLibrary.Runtime.UIGraph;
namespace XericLibrary.Runtime.Blueprint.QuickGraph
{
/// <summary>按源节点 Chunk 提交连线的渲染工具;所有局部坐标由基类 submission 提供。</summary>
[BlueprintTool(phase: ToolPhase.Render, order: 110)]
[BlueprintTheme("QuickGraph")]
public class WireCanvasRenderTool : BlueprintCanvasRenderTool<BlueprintCurveRenderer>
{
private QuickGraphWireRenderConfig _defaultConfig;
private QuickGraphRenderStyleResolver _styleResolver;
private int _appliedStyleRevision = -1;
private QuickGraphWireRenderConfig Config
{
get
{
if (ToolConfig is QuickGraphWireRenderConfig external) return external;
if (_defaultConfig == null) _defaultConfig = ScriptableObject.CreateInstance<QuickGraphWireRenderConfig>();
return _defaultConfig;
}
}
public override void OnInitialize()
{
base.OnInitialize();
_styleResolver = new QuickGraphRenderStyleResolver(Graph, wireFallback: ConfigStyle);
_styleResolver.Changed += OnStyleChanged;
ApplyWireGeometry(_styleResolver.Snapshot.Wire, true);
}
public override void OnConfigChanged()
{
_styleResolver?.Refresh();
if (_styleResolver == null) ApplyWireGeometry(ConfigStyle(), true);
base.OnConfigChanged();
}
private QuickGraphWireRenderStyle ConfigStyle()
{
var cfg = Config;
return new QuickGraphWireRenderStyle { Width = cfg.WireWidth, HitRadius = cfg.HitTestRadius, SourceHandle = cfg.SourceHandleLength, TargetHandle = cfg.TargetHandleLength, ArrowShape = cfg.ArrowShape, ArrowReversed = cfg.ArrowReversed, ArrowProgress = cfg.ArrowProgress, ArrowWidth = cfg.ArrowWidth, ArrowHeight = cfg.ArrowHeight, ArrowDepth = cfg.ArrowDepthCompensation, Tessellation = cfg.TessellationSegments };
}
private void OnStyleChanged(QuickGraphRenderStyleSnapshot snapshot)
{
if ((snapshot.Changes & (QuickGraphRenderStyleChange.WireGeometry | QuickGraphRenderStyleChange.WireAppearance)) == 0) return;
ApplyWireGeometry(snapshot.Wire, (snapshot.Changes & QuickGraphRenderStyleChange.WireGeometry) != 0);
Graph?.MarkAllElementsDirty();
}
private void ApplyWireGeometry(QuickGraphWireRenderStyle style, bool force)
{
if (Graph == null || (!force && _appliedStyleRevision == _styleResolver.Snapshot.Revision)) return;
Graph.SetWireGeometrySettings(new BlueprintWireGeometrySettings { SourceHandleLength = style.SourceHandle, TargetHandleLength = style.TargetHandle, WireWidth = style.Width, HitTestRadius = style.HitRadius });
foreach (var wire in Graph.Wires) Graph.UpdateWireGeometry(wire);
_appliedStyleRevision = _styleResolver != null ? _styleResolver.Snapshot.Revision : _appliedStyleRevision + 1;
}
protected override BlueprintCurveRenderer BuildChunkRenderer(GameObject chunkGo) => chunkGo.AddComponent<BlueprintCurveRenderer>();
protected override void ClearChunkData(BlueprintCurveRenderer renderer) => renderer.ClearAll();
protected override void SetChunkDirty(BlueprintCurveRenderer renderer) => renderer.RebuildAll();
protected override bool AcceptsElement(IBlueprintElement element) => element is BlueprintWire;
protected override void AppendElementData(BlueprintCurveRenderer renderer, IBlueprintElement element, ulong chunkID)
{
if (!(element is BlueprintWire wire) || wire.SourcePort == null || wire.TargetPort == null || wire.SourcePort.OwnerNode == null || wire.TargetPort.OwnerNode == null) return;
var style = _styleResolver != null ? _styleResolver.Snapshot.Wire : ConfigStyle();
wire.GetControlPoints(out var startCanvas, out var handle1Canvas, out var handle2Canvas, out var endCanvas);
var submission = CurrentElementSubmission;
var ctrlPts = new[] { submission.ToChunkLocal(startCanvas), submission.ToChunkLocal(handle1Canvas), submission.ToChunkLocal(handle2Canvas), submission.ToChunkLocal(endCanvas) };
var widths = new[] { style.Width, style.Width, style.Width, style.Width };
Color32 srcColor = wire.SourcePort.OwnerNode.NodeColor;
Color32 tgtColor = wire.TargetPort.OwnerNode.NodeColor;
var arrow = new ArrowHeadData { shape = style.ArrowShape, reversed = style.ArrowReversed, progress = style.ArrowProgress, width = style.ArrowWidth, height = style.ArrowHeight, depthCompensation = style.ArrowDepth, color = tgtColor };
var entry = new CurveCacheEntry { startColor = srcColor, endColor = tgtColor, tessellationSegments = Mathf.Max(2, Mathf.RoundToInt(style.Tessellation * Graph.CurrentLodSettings.WireTessellationMultiplier)) };
renderer.AddCurve(entry, ctrlPts, widths, new[] { arrow });
}
public override void OnDestroy()
{
if (_styleResolver != null) _styleResolver.Changed -= OnStyleChanged;
_styleResolver?.Dispose(); _styleResolver = null;
base.OnDestroy();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e6d0420274cb4d9469b505ea464a1671
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: