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
+2
View File
@@ -0,0 +1,2 @@
Runtime/DrawDebugLibrary/README_DXXL_API.md
Runtime/DrawDebugLibrary/demo scene scripts/
+3 -1
View File
@@ -2,7 +2,9 @@
## [Unrealse]
* 添加运行时蓝图框架
## [0.6.2] 2026-07-13
* 添加运行时图论框架(v2
## [0.6.1] 2026-07-09
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9b93e95735924284ba5561cdcb1af3dc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+73
View File
@@ -0,0 +1,73 @@
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
using XericLibrary.Runtime.Blueprint;
namespace XericLibraryEditor.Bluprint.Editor
{
/// <summary>
/// 蓝图右键菜单项 —— 通过 GameObject 菜单快捷创建蓝图示例。
/// </summary>
public static class BlueprintMenuItems
{
private const string MenuRoot = "GameObject/Xeric Library/Blueprint/";
[MenuItem(MenuRoot + "UI Graph Bp", false, 10)]
private static void CreateUIGraphBlueprint()
{
var go = CreateBlueprintGameObject("Blueprint Graph (UI)");
var canvas = go.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
go.AddComponent<CanvasScaler>();
go.AddComponent<GraphicRaycaster>();
go.AddComponent<GraphTheoryBlueprintComponent>();
Selection.activeGameObject = go;
Undo.RegisterCreatedObjectUndo(go, "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>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
go.AddComponent<CanvasScaler>();
go.AddComponent<GraphicRaycaster>();
var comp = go.AddComponent<GraphTheoryBlueprintComponent>();
comp.ThemeName = "QuickGraph";
Selection.activeGameObject = go;
Undo.RegisterCreatedObjectUndo(go, "Create UI QuickGraph Blueprint");
}
[MenuItem(MenuRoot + "World Space Graph Bp", false, 12)]
private static void CreateWorldSpaceGraphBlueprint()
{
var go = CreateBlueprintGameObject("Blueprint Graph (World)");
go.AddComponent<GraphTheoryBlueprintComponent>();
Selection.activeGameObject = go;
Undo.RegisterCreatedObjectUndo(go, "Create World Space Graph Blueprint");
}
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;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ef23c61b0362b8b4e99796655c109d78
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9a618e02e848483d82f62c25637f3719
timeCreated: 1783824464
@@ -0,0 +1,236 @@
#if UNITY_EDITOR && ENABLE_INPUT_SYSTEM
using System.IO;
using UnityEditor;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
namespace XericLibrary.Runtime.Blueprint.Editor
{
/// <summary>
/// 蓝图 InputAction 模板生成器。
/// 右键 Project 窗口 → Xeric Library / Input Action Template / BlueprintAction
/// 即可创建预设 InputActionAsset,内含蓝图所有的 Actions 和绑定。
/// </summary>
public static class BlueprintInputActionTemplate
{
private const string MenuPath = "Assets/Create/Xeric Library/Input Action Template/BlueprintAction";
private const string DefaultFileName = "BlueprintActions";
[MenuItem(MenuPath, priority = 1100)]
public static void CreateBlueprintInputActionAsset()
{
var asset = ScriptableObject.CreateInstance<InputActionAsset>();
var map = asset.AddActionMap(BlueprintInputConstants.MapName);
var binding = InputBinding.MaskByGroup("Keyboard&Mouse");
var gamepadBinding = InputBinding.MaskByGroup("Gamepad");
// ── 指针位置 ──
map.AddAction(
BlueprintInputConstants.Point,
type: InputActionType.Value,
binding: "<Mouse>/position",
interactions: null,
processors: null,
groups: binding.ToString());
// ── 滚轮 ──
map.AddAction(
BlueprintInputConstants.ScrollWheel,
type: InputActionType.Value,
binding: "<Mouse>/scroll",
interactions: null,
processors: null,
groups: binding.ToString());
// ── 左键 ──
map.AddAction(
BlueprintInputConstants.LeftClick,
type: InputActionType.Button,
binding: "<Mouse>/leftButton",
interactions: null,
processors: null,
groups: binding.ToString());
// ── 右键 ──
map.AddAction(
BlueprintInputConstants.RightClick,
type: InputActionType.Button,
binding: "<Mouse>/rightButton",
interactions: null,
processors: null,
groups: binding.ToString());
// ── 中键 ──
map.AddAction(
BlueprintInputConstants.MiddleClick,
type: InputActionType.Button,
binding: "<Mouse>/middleButton",
interactions: null,
processors: null,
groups: binding.ToString());
// ── Shift ──
map.AddAction(
BlueprintInputConstants.Shift,
type: InputActionType.Button,
binding: "<Keyboard>/leftShift",
interactions: null,
processors: null,
groups: binding.ToString());
// ── Ctrl ──
map.AddAction(
BlueprintInputConstants.Ctrl,
type: InputActionType.Button,
binding: "<Keyboard>/leftCtrl",
interactions: null,
processors: null,
groups: binding.ToString());
// ── Alt ──
map.AddAction(
BlueprintInputConstants.Alt,
type: InputActionType.Button,
binding: "<Keyboard>/leftAlt",
interactions: null,
processors: null,
groups: binding.ToString());
// ── Pan(平移:方向键 + Shift+滚轮 → 水平, Alt+滚轮 → 垂直)──
var panAction = map.AddAction(
BlueprintInputConstants.Pan,
type: InputActionType.Value,
binding: null); // 多个 composite,需手动绑定
// 键盘方向键 Vector2
var kbCompositeIndex = panAction.AddCompositeBinding("2DVector")
.With("Up", "<Keyboard>/upArrow")
.With("Down", "<Keyboard>/downArrow")
.With("Left", "<Keyboard>/leftArrow")
.With("Right", "<Keyboard>/rightArrow");
ApplyGroupToLastBindings(panAction, binding.ToString());
// 手柄左摇杆
panAction.AddCompositeBinding("2DVector")
.With("Up", "<Gamepad>/leftStick/up")
.With("Down", "<Gamepad>/leftStick/down")
.With("Left", "<Gamepad>/leftStick/left")
.With("Right", "<Gamepad>/leftStick/right");
ApplyGroupToLastBindings(panAction, gamepadBinding.ToString());
// ── Zoom(缩放:滚轮 + Ctrl热键)──
var zoomAction = map.AddAction(
BlueprintInputConstants.Zoom,
type: InputActionType.Value);
zoomAction.AddCompositeBinding("1DAxis")
.With("Positive", "<Mouse>/scroll/up")
.With("Negative", "<Mouse>/scroll/down");
ApplyGroupToLastBindings(zoomAction, binding.ToString());
// Ctrl 组合(多一层)
zoomAction.AddCompositeBinding("1DAxis")
.With("Positive", "<Keyboard>/ctrl")
.With("Negative", "<Keyboard>/ctrl");
ApplyGroupToLastBindings(zoomAction, binding.ToString());
// ── FocusHomeCtrl + H)──
map.AddAction(
BlueprintInputConstants.FocusHome,
type: InputActionType.Button,
binding: "<Keyboard>/h",
interactions: null,
processors: null,
groups: binding.ToString());
// ── UndoCtrl + Z)──
map.AddAction(
BlueprintInputConstants.Undo,
type: InputActionType.Button,
binding: "<Keyboard>/z",
interactions: null,
processors: null,
groups: binding.ToString());
// ── RedoCtrl + Y)──
map.AddAction(
BlueprintInputConstants.Redo,
type: InputActionType.Button,
binding: "<Keyboard>/y",
interactions: null,
processors: null,
groups: binding.ToString());
// ── NavigateParentTab)──
map.AddAction(
BlueprintInputConstants.NavigateParent,
type: InputActionType.Button,
binding: "<Keyboard>/tab",
interactions: null,
processors: null,
groups: binding.ToString());
// 保存资产 —— .inputactions 文件需以 JSON 文本形式写入,
// 再由 AssetDatabase.ImportAsset 触发 InputActionImporter 解析。
string folderPath = GetSelectedFolderPath();
string assetPath = AssetDatabase.GenerateUniqueAssetPath(
Path.Combine(folderPath, $"{DefaultFileName}.inputactions"));
string json = asset.ToJson();
System.IO.File.WriteAllText(assetPath, json);
AssetDatabase.ImportAsset(assetPath);
AssetDatabase.SaveAssets();
var imported = AssetDatabase.LoadAssetAtPath<InputActionAsset>(assetPath);
EditorGUIUtility.PingObject(imported);
Debug.Log($"[Blueprint] InputAction 模板已创建: {assetPath}");
}
/// <summary>
/// 将 action 最后一个 binding(及所有 composite part)的 groups 设为指定值。
/// </summary>
private static void ApplyGroupToLastBindings(InputAction action, string groups)
{
int count = action.bindings.Count;
int compositeStart = -1;
for (int i = count - 1; i >= 0; i--)
{
if (action.bindings[i].isComposite)
{
compositeStart = i;
break;
}
}
if (compositeStart < 0) return;
for (int i = compositeStart; i < count; i++)
action.ChangeBinding(i).WithGroup(groups);
}
/// <summary>
/// 获取当前选中的 Project 窗口文件夹路径。
/// </summary>
private static string GetSelectedFolderPath()
{
string path = "Assets";
var selected = Selection.activeObject;
if (selected != null)
{
string assetPath = AssetDatabase.GetAssetPath(selected);
if (!string.IsNullOrEmpty(assetPath))
{
if (AssetDatabase.IsValidFolder(assetPath))
return assetPath;
return Path.GetDirectoryName(assetPath);
}
}
return path;
}
}
}
#endif
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: be6718b37ded78141820ec5058fba150
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+2 -2
View File
@@ -2,8 +2,8 @@
"name": "Lrss3.SesothoLine.Editor",
"rootNamespace": "SesothoLineEditor",
"references": [
"Lrss3.SesothoLine",
"Lrss3.Deconstruction"
"Lrss3.Deconstruction",
"Unity.InputSystem"
],
"includePlatforms": [
"Editor"
Binary file not shown.
+5
View File
@@ -43,6 +43,11 @@ Xeric Library 是一个专注代码的 Unity 扩展库。
---
## 蓝图 (Blueprint)
蓝图系统,用于创建运行时轻量蓝图渲染框架。
通过内置的渲染工具实现分层绘制。
## 样式表 (XSSS)
自研 Xeric Super Style Sheet (XSSS) 系统,使用 `.xsss` 自定义文本格式,类 CSS/USS 语法,专为 Unity 组件样式设计。基于字典树进行样式路径查找与匹配,支持命名空间隔离、动态监听热重载。提供完整的编辑器导入、Inspector 编辑及 PropertyDrawer 支持。
+4 -1
View File
@@ -8,7 +8,9 @@ Material:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: SceneGround
m_Shader: {fileID: -6465566751694194690, guid: c508188bc2ab04746ba729aded8434fa, type: 3}
m_Shader: {fileID: -6465566751694194690, guid: 10fb48704e4629d40a97198ec7047bf2, type: 3}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
@@ -17,6 +19,7 @@ Material:
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ea72d510e84667c46831c1387dd88327
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,183 @@
Shader "XericLibrary/BluePrint/BlueprintBackgound_GridLine"
{
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
_StencilComp ("Stencil Comparison", Float) = 8
_Stencil ("Stencil ID", Float) = 0
_StencilOp ("Stencil Operation", Float) = 0
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_StencilReadMask ("Stencil Read Mask", Float) = 255
_ColorMask ("Color Mask", Float) = 15
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
_Transform( "Transform", Vector ) = ( 10, 10, 0, 0 )
_GridOverlayPower( "Grid Overlay Power", Float ) = 5
_GridColor( "Grid Color", Color ) = ( 1, 1, 1, 1 )
_GridLineThreshold( "Grid Line Threshold ", Range( 0, 1 ) ) = 0.9888518
_GridBackgroundColor( "Grid Background Color", Color ) = ( 0, 0, 0, 1 )
_GridExp( "Grid Exp", Range( 0.001, 1 ) ) = 0.001
_LodTransthd( "Lod Trans thd", Float ) = 30
}
SubShader
{
LOD 0
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" }
Stencil
{
Ref [_Stencil]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
Comp [_StencilComp]
Pass [_StencilOp]
}
Cull Off
Lighting Off
ZWrite Off
ZTest [unity_GUIZTestMode]
Blend One OneMinusSrcAlpha
ColorMask [_ColorMask]
Pass
{
Name "Default"
CGPROGRAM
#define ASE_VERSION 19908
#pragma vertex vert
#pragma fragment frag
#pragma target 3.5
#include "UnityCG.cginc"
#include "UnityUI.cginc"
#pragma multi_compile_local _ UNITY_UI_CLIP_RECT
#pragma multi_compile_local _ UNITY_UI_ALPHACLIP
#include "UnityShaderVariables.cginc"
#define ASE_NEEDS_TEXTURE_COORDINATES0
#define ASE_NEEDS_FRAG_TEXTURE_COORDINATES0
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
float4 worldPosition : TEXCOORD1;
float4 mask : TEXCOORD2;
UNITY_VERTEX_OUTPUT_STEREO
};
sampler2D _MainTex;
fixed4 _Color;
fixed4 _TextureSampleAdd;
float4 _ClipRect;
float4 _MainTex_ST;
float _UIMaskSoftnessX;
float _UIMaskSoftnessY;
uniform float4 _GridBackgroundColor;
uniform float4 _GridColor;
uniform float _GridLineThreshold;
uniform float4 _Transform;
uniform float _LodTransthd;
uniform float _GridOverlayPower;
uniform float _GridExp;
v2f vert(appdata_t v )
{
v2f OUT;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
v.vertex.xyz += float3( 0, 0, 0 ) ;
float4 vPosition = UnityObjectToClipPos(v.vertex);
OUT.worldPosition = v.vertex;
OUT.vertex = vPosition;
float2 pixelSize = vPosition.w;
pixelSize /= float2(1, 1) * abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy));
float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
float2 maskUV = (v.vertex.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy);
OUT.texcoord = v.texcoord;
OUT.mask = float4(v.vertex.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_UIMaskSoftnessX, _UIMaskSoftnessY) + abs(pixelSize.xy)));
OUT.color = v.color * _Color;
return OUT;
}
fixed4 frag(v2f IN ) : SV_Target
{
//Round up the alpha color coming from the interpolator (to 1.0/256.0 steps)
//The incoming alpha could have numerical instability, which makes it very sensible to
//HDR color transparency blend, when it blends with the world's texture.
const half alphaPrecision = half(0xff);
const half invAlphaPrecision = half(1.0/alphaPrecision);
IN.color.a = round(IN.color.a * alphaPrecision)*invAlphaPrecision;
float4 temp_cast_0 = (_GridLineThreshold).xxxx;
float2 texCoord2 = IN.texcoord.xy * (_Transform).xy + (_Transform).zw;
float2 temp_output_62_0 = fwidth( texCoord2 );
float4 appendResult73 = (float4(temp_output_62_0 , temp_output_62_0));
float4 temp_cast_1 = (1.0).xxxx;
float2 temp_cast_2 = (0.5).xx;
float smoothstepResult95 = smoothstep( -0.8 , 1.0 , ( ( max( _Transform.x, _Transform.y ) - _LodTransthd ) / _LodTransthd ));
float2 lerpResult94 = lerp( frac( texCoord2 ) , temp_cast_2 , smoothstepResult95);
float4 appendResult5 = (float4(lerpResult94 , frac( ( texCoord2 / _GridOverlayPower ) )));
float temp_output_85_0 = ( 0.5 * 1.1 );
float4 appendResult83 = (float4(0.5 , 0.5 , temp_output_85_0 , temp_output_85_0));
float4 smoothstepResult33 = smoothstep( ( _ScreenParams.z * ( temp_cast_0 - appendResult73 ) ) , temp_cast_1 , ( abs( ( appendResult5 + -0.5 ) ) / appendResult83 ));
float4 break35 = smoothstepResult33;
float4 lerpResult46 = lerp( _GridBackgroundColor , _GridColor , saturate( pow( ( break35.x + break35.y + break35.z + break35.w ) , _GridExp ) ));
half4 color = lerpResult46;
#ifdef UNITY_UI_CLIP_RECT
half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(IN.mask.xy)) * IN.mask.zw);
color.a *= m.x * m.y;
#endif
#ifdef UNITY_UI_ALPHACLIP
clip (color.a - 0.001);
#endif
color.rgb *= color.a;
return color;
}
ENDCG
}
}
CustomEditor "AmplifyShaderEditor.MaterialInspector"
Fallback Off
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 2571bd49141e8ec429a8c3ec86b5da5b
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,51 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: BlueprintBackgound_GridLine
m_Shader: {fileID: 4800000, guid: 2571bd49141e8ec429a8c3ec86b5da5b, type: 3}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _ColorMask: 15
- _Float2: 0.95
- _Float3: 1
- _GridExp: 0.001
- _GridLineThreshold: 0.98
- _GridOverlayPower: 5
- _OverlayerMult: 1
- _Stencil: 0
- _StencilComp: 8
- _StencilOp: 0
- _StencilReadMask: 255
- _StencilWriteMask: 255
- _UseUIAlphaClip: 0
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _GridBackgroundColor: {r: 0, g: 0, b: 0, a: 1}
- _GridColor: {r: 1, g: 1, b: 1, a: 1}
- _Transform: {r: 19.2, g: 10.8, b: -0, a: -0}
- _Vector0: {r: 0.95, g: 1, b: 0, a: 0}
m_BuildTextureStacks: []
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c16d74b277ae17c479319990fca9477b
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:
@@ -8,7 +8,9 @@ Material:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: UISelectionBoxDraw
m_Shader: {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
m_Shader: {fileID: -6465566751694194690, guid: 7da14d69552d5a64791f8abd5e98dc5b, type: 3}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords: []
m_InvalidKeywords:
- _BUILTIN_SURFACE_TYPE_TRANSPARENT
@@ -18,6 +20,7 @@ Material:
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
@@ -1,4 +1,3 @@
Shader "XUIDotBasic"
{
Properties
@@ -128,9 +127,9 @@ Shader "XUIDotBasic"
fixed4 frag(v2f IN ) : SV_Target
{
//Round up the alpha color coming from the interpolator (to 1.0/256.0 steps)
//The incoming alpha could have numerical instability, which makes it very sensible to
//HDR color transparency blend, when it blends with the world's texture.
const half alphaPrecision = half(0xff);
const half invAlphaPrecision = half(1.0/alphaPrecision);
IN.color.a = round(IN.color.a * alphaPrecision)*invAlphaPrecision;
@@ -1,4 +1,5 @@
Shader "XUIBasic"
{
Properties
@@ -143,9 +144,9 @@ Shader "XUIBasic"
fixed4 frag(v2f IN ) : SV_Target
{
//Round up the alpha color coming from the interpolator (to 1.0/256.0 steps)
//The incoming alpha could have numerical instability, which makes it very sensible to
//HDR color transparency blend, when it blends with the world's texture.
const half alphaPrecision = half(0xff);
const half invAlphaPrecision = half(1.0/alphaPrecision);
IN.color.a = round(IN.color.a * alphaPrecision)*invAlphaPrecision;
@@ -1,4 +1,5 @@
Shader "XericLibrary/UIGraph/XericUICurve"
{
Properties
@@ -129,9 +130,9 @@ Shader "XericLibrary/UIGraph/XericUICurve"
fixed4 frag(v2f IN ) : SV_Target
{
//Round up the alpha color coming from the interpolator (to 1.0/256.0 steps)
//The incoming alpha could have numerical instability, which makes it very sensible to
//HDR color transparency blend, when it blends with the world's texture.
const half alphaPrecision = half(0xff);
const half invAlphaPrecision = half(1.0/alphaPrecision);
IN.color.a = round(IN.color.a * alphaPrecision)*invAlphaPrecision;
@@ -1,4 +1,5 @@
Shader "XericLibrary/UIGraph/UILineRender"
{
Properties
@@ -121,9 +122,9 @@ Shader "XericLibrary/UIGraph/UILineRender"
fixed4 frag(v2f IN ) : SV_Target
{
//Round up the alpha color coming from the interpolator (to 1.0/256.0 steps)
//The incoming alpha could have numerical instability, which makes it very sensible to
//HDR color transparency blend, when it blends with the world's texture.
const half alphaPrecision = half(0xff);
const half invAlphaPrecision = half(1.0/alphaPrecision);
IN.color.a = round(IN.color.a * alphaPrecision)*invAlphaPrecision;
@@ -1,4 +1,5 @@
Shader "XericLibrary/UIGraph/UIPattern"
{
Properties
@@ -121,7 +122,7 @@ Shader "XericLibrary/UIGraph/UIPattern"
OUT.ase_texcoord3.xy = v.ase_texcoord2.xy;
OUT.ase_tangent = v.ase_tangent;
//setting value to unused interpolator channels and avoid initialization warnings
OUT.ase_texcoord3.zw = 0;
v.vertex.xyz += float3( 0, 0, 0 ) ;
@@ -144,9 +145,9 @@ Shader "XericLibrary/UIGraph/UIPattern"
fixed4 frag(v2f IN ) : SV_Target
{
//Round up the alpha color coming from the interpolator (to 1.0/256.0 steps)
//The incoming alpha could have numerical instability, which makes it very sensible to
//HDR color transparency blend, when it blends with the world's texture.
const half alphaPrecision = half(0xff);
const half invAlphaPrecision = half(1.0/alphaPrecision);
IN.color.a = round(IN.color.a * alphaPrecision)*invAlphaPrecision;
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c60a425d5c8e87345b4f0909350d6861
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,449 @@
{
"version": 1,
"name": "BlueprintActions",
"maps": [
{
"name": "Blueprint",
"id": "57e48183-533c-4d6d-b131-8ad65e183c27",
"actions": [
{
"name": "Point",
"type": "Value",
"id": "3fecd77e-e6b0-4c35-923f-4b75c319c835",
"expectedControlType": "",
"processors": "",
"interactions": "",
"initialStateCheck": true
},
{
"name": "ScrollWheel",
"type": "Value",
"id": "026f04b5-3585-4a14-b362-905a03b9cd6c",
"expectedControlType": "",
"processors": "",
"interactions": "",
"initialStateCheck": true
},
{
"name": "LeftClick",
"type": "Button",
"id": "a64e638b-6923-45f3-ae92-a4c2c93b9404",
"expectedControlType": "",
"processors": "",
"interactions": "",
"initialStateCheck": false
},
{
"name": "RightClick",
"type": "Button",
"id": "6d27d898-d133-4b87-84f4-f4d3ffb30036",
"expectedControlType": "",
"processors": "",
"interactions": "",
"initialStateCheck": false
},
{
"name": "MiddleClick",
"type": "Button",
"id": "a62871ba-e098-4311-a013-031040604a8c",
"expectedControlType": "",
"processors": "",
"interactions": "",
"initialStateCheck": false
},
{
"name": "Shift",
"type": "Button",
"id": "82515a87-cc55-4c1c-bf55-8b22eb994e51",
"expectedControlType": "",
"processors": "",
"interactions": "",
"initialStateCheck": false
},
{
"name": "Ctrl",
"type": "Button",
"id": "29b51781-8763-4d20-8e79-6c49c81ed45e",
"expectedControlType": "",
"processors": "",
"interactions": "",
"initialStateCheck": false
},
{
"name": "Alt",
"type": "Button",
"id": "5d3332a3-b320-4e6b-b877-5b7cc2f2e15f",
"expectedControlType": "",
"processors": "",
"interactions": "",
"initialStateCheck": false
},
{
"name": "Pan",
"type": "Value",
"id": "437af76f-a22f-4e97-9454-c8ad4a04ea7b",
"expectedControlType": "",
"processors": "",
"interactions": "",
"initialStateCheck": true
},
{
"name": "Zoom",
"type": "Value",
"id": "fb9c164e-b854-49e2-8e65-3f89ac15ba0e",
"expectedControlType": "",
"processors": "",
"interactions": "",
"initialStateCheck": true
},
{
"name": "FocusHome",
"type": "Button",
"id": "1fb4d951-0fe4-4a0c-8231-aecb2940501f",
"expectedControlType": "",
"processors": "",
"interactions": "",
"initialStateCheck": false
},
{
"name": "Undo",
"type": "Button",
"id": "15c7f65a-293d-47d5-a911-3165ac581985",
"expectedControlType": "",
"processors": "",
"interactions": "",
"initialStateCheck": false
},
{
"name": "Redo",
"type": "Button",
"id": "2924dab5-49f4-42fc-97a7-78fdcb63e0f7",
"expectedControlType": "",
"processors": "",
"interactions": "",
"initialStateCheck": false
},
{
"name": "NavigateParent",
"type": "Button",
"id": "f285f303-6045-42a4-87b5-ce32f3174551",
"expectedControlType": "",
"processors": "",
"interactions": "",
"initialStateCheck": false
}
],
"bindings": [
{
"name": "",
"id": "0c5d3758-e653-4a4b-b5bf-f5cb387c26af",
"path": "<Mouse>/position",
"interactions": "",
"processors": "",
"groups": "[Keyboard&Mouse]",
"action": "Point",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "6b2eed90-22cf-4698-b6c3-fffe60b0be27",
"path": "<Mouse>/scroll",
"interactions": "",
"processors": "",
"groups": "[Keyboard&Mouse]",
"action": "ScrollWheel",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "5de88c78-b8d1-4116-a489-2fcddc16ac00",
"path": "<Mouse>/leftButton",
"interactions": "",
"processors": "",
"groups": "[Keyboard&Mouse]",
"action": "LeftClick",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "90ca46b6-838c-4b33-b823-2b8be1e1cd7c",
"path": "<Mouse>/rightButton",
"interactions": "",
"processors": "",
"groups": "[Keyboard&Mouse]",
"action": "RightClick",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "87e46bb3-fc2c-4d1d-bdfd-92104cbf8585",
"path": "<Mouse>/middleButton",
"interactions": "",
"processors": "",
"groups": "[Keyboard&Mouse]",
"action": "MiddleClick",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "b5c6968e-6302-4312-8e92-b975f1369e91",
"path": "<Keyboard>/leftShift",
"interactions": "",
"processors": "",
"groups": "[Keyboard&Mouse]",
"action": "Shift",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "42a36e22-d17a-46f8-8a2c-c5f5fe9976b1",
"path": "<Keyboard>/leftCtrl",
"interactions": "",
"processors": "",
"groups": "[Keyboard&Mouse]",
"action": "Ctrl",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "752ad893-e912-4206-93eb-c476910b49c0",
"path": "<Keyboard>/leftAlt",
"interactions": "",
"processors": "",
"groups": "[Keyboard&Mouse]",
"action": "Alt",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "2DVector",
"id": "809dd943-9bc5-441b-96c5-88408f588ba5",
"path": "2DVector",
"interactions": "",
"processors": "",
"groups": "[Keyboard&Mouse]",
"action": "Pan",
"isComposite": true,
"isPartOfComposite": false
},
{
"name": "Up",
"id": "51ceee12-44fc-43aa-b953-3bca4a2c648a",
"path": "<Keyboard>/upArrow",
"interactions": "",
"processors": "",
"groups": "[Keyboard&Mouse]",
"action": "Pan",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "Down",
"id": "1f81dec0-886b-4adc-86ce-8b17fd471dee",
"path": "<Keyboard>/downArrow",
"interactions": "",
"processors": "",
"groups": "[Keyboard&Mouse]",
"action": "Pan",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "Left",
"id": "9b2865f0-450c-41f3-87dc-f75de30757d9",
"path": "<Keyboard>/leftArrow",
"interactions": "",
"processors": "",
"groups": "[Keyboard&Mouse]",
"action": "Pan",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "Right",
"id": "3cbbb040-2bcf-4856-b030-eadffce1d864",
"path": "<Keyboard>/rightArrow",
"interactions": "",
"processors": "",
"groups": "[Keyboard&Mouse]",
"action": "Pan",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "2DVector",
"id": "8c6b2980-4b14-4e3d-beba-43a04dd40294",
"path": "2DVector",
"interactions": "",
"processors": "",
"groups": "[Gamepad]",
"action": "Pan",
"isComposite": true,
"isPartOfComposite": false
},
{
"name": "Up",
"id": "03370fa0-29c6-4569-9619-628eaacfb3c6",
"path": "<Gamepad>/leftStick/up",
"interactions": "",
"processors": "",
"groups": "[Gamepad]",
"action": "Pan",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "Down",
"id": "b85caf61-0f41-4255-a80a-c402df5b2a20",
"path": "<Gamepad>/leftStick/down",
"interactions": "",
"processors": "",
"groups": "[Gamepad]",
"action": "Pan",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "Left",
"id": "85d70aff-19dd-444f-a477-26f86a047341",
"path": "<Gamepad>/leftStick/left",
"interactions": "",
"processors": "",
"groups": "[Gamepad]",
"action": "Pan",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "Right",
"id": "eda1e128-379a-4c87-ad82-951f8a69e294",
"path": "<Gamepad>/leftStick/right",
"interactions": "",
"processors": "",
"groups": "[Gamepad]",
"action": "Pan",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "1DAxis",
"id": "dca71d91-5b59-41ed-a4d3-868f19753361",
"path": "1DAxis",
"interactions": "",
"processors": "",
"groups": "[Keyboard&Mouse]",
"action": "Zoom",
"isComposite": true,
"isPartOfComposite": false
},
{
"name": "Positive",
"id": "56e24cf8-5c62-4da9-a54e-71282d26ea66",
"path": "<Mouse>/scroll/up",
"interactions": "",
"processors": "",
"groups": "[Keyboard&Mouse]",
"action": "Zoom",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "Negative",
"id": "e5d6e5fb-6450-4124-a961-ff22bf6cfcbd",
"path": "<Mouse>/scroll/down",
"interactions": "",
"processors": "",
"groups": "[Keyboard&Mouse]",
"action": "Zoom",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "1DAxis",
"id": "391d3207-a469-4240-9b49-8cb67f385af2",
"path": "1DAxis",
"interactions": "",
"processors": "",
"groups": "[Keyboard&Mouse]",
"action": "Zoom",
"isComposite": true,
"isPartOfComposite": false
},
{
"name": "Positive",
"id": "add5f511-0cdc-427f-a2dc-743fe7bce6f4",
"path": "<Keyboard>/ctrl",
"interactions": "",
"processors": "",
"groups": "[Keyboard&Mouse]",
"action": "Zoom",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "Negative",
"id": "1378e62c-5fc0-4e8b-8680-4a61c5088529",
"path": "<Keyboard>/ctrl",
"interactions": "",
"processors": "",
"groups": "[Keyboard&Mouse]",
"action": "Zoom",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "",
"id": "9a024cd3-ef65-4421-91a3-1963c7e3c422",
"path": "<Keyboard>/h",
"interactions": "",
"processors": "",
"groups": "[Keyboard&Mouse]",
"action": "FocusHome",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "3b26b918-d6c9-4995-b180-c6c47ef8cf38",
"path": "<Keyboard>/z",
"interactions": "",
"processors": "",
"groups": "[Keyboard&Mouse]",
"action": "Undo",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "9da8a358-d6f6-48ee-bbb8-e064ee1806cd",
"path": "<Keyboard>/y",
"interactions": "",
"processors": "",
"groups": "[Keyboard&Mouse]",
"action": "Redo",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "1c3cf3be-12c2-440d-8294-5dc04ec61abb",
"path": "<Keyboard>/tab",
"interactions": "",
"processors": "",
"groups": "[Keyboard&Mouse]",
"action": "NavigateParent",
"isComposite": false,
"isPartOfComposite": false
}
]
}
],
"controlSchemes": []
}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 58e605f9253ce864ab140170b0d7c467
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 8404be70184654265930450def6a9037, type: 3}
generateWrapperCode: 0
wrapperCodePath:
wrapperClassName:
wrapperCodeNamespace:
@@ -0,0 +1,22 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 32753aff001677a4b81e10dfae39b4cb, type: 3}
m_Name: QuickGraphGridBackgroundConfig
m_EditorClassIdentifier:
ShaderName: XericLibrary/BluePrint/BlueprintBackgound_GridLine
OverrideMaterial: {fileID: 0}
GridSize: 100
GridOverlayPower: 5
GridLineThreshold: 0.95
GridExp: 1
GridColor: {r: 0.6156863, g: 0.6156863, b: 0.6156863, a: 1}
GridBackgroundColor: {r: 0.1764706, g: 0.1764706, b: 0.1764706, a: 1}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 092ad6102a8bc354bb081e3ee26e5ed6
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,22 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: cfce52570fac4f5488becd85ce4f023b, type: 3}
m_Name: QuickGraphInputConfig
m_EditorClassIdentifier:
PanLerpSpeed: 8
ZoomLerpSpeed: 8
ZoomDivisor: 1100
MinZoom: 0.1
MaxZoom: 3
KeyboardPanSpeed: 600
KeyboardZoomSpeed: 0.05
DragThreshold: 5
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3f441c61e0e252146a114c471a77c507
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,25 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8ab3d3829c73815468ed8c5ae306a6ee, type: 3}
m_Name: QuickGraphNodeRenderConfig
m_EditorClassIdentifier:
NodeWidth: 120
NodeHeight: 80
BorderThickness: 5
ChamferSize: 0.5
ChamferSegments: 6
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
TitleFont: {fileID: 0}
Lod0Threshold: 0.1
Lod1Threshold: 0.15
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cdebd6b7f25eb334093a428b6414b0ce
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,24 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 21208cffd6108e04d815bb385f0af3fc, type: 3}
m_Name: QuickGraphWireRenderConfig
m_EditorClassIdentifier:
WireWidth: 5
SourceHandleLength: 120
TargetHandleLength: 120
ArrowShape: 0
ArrowReversed: 0
ArrowProgress: 1
ArrowWidth: 20
ArrowHeight: 30
ArrowDepthCompensation: -1
TessellationSegments: 24
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: aad533644ae5c8b4bb61786c413d4844
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1566851acf6847a88b28779b8778be7d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a62ba5ace01c4084b9c4e8cb553cf3b0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,114 @@
using UnityEngine;
namespace XericLibrary.Runtime.Blueprint.Canvas
{
/// <summary>
/// 屏幕空间蓝图画布实现,基于 UGUI Canvas。
/// <code>
/// 使用 RectTransformUtility 处理屏幕坐标和画布坐标之间的转换。
/// 缩放范围:0.1x ~ 3x。
/// </code>
/// </summary>
public class ScreenSpaceBlueprintCanvas : BlueprintCanvasBase
{
private readonly UnityEngine.Canvas _unityCanvas;
private readonly RectTransform _rectTransform;
/// <summary>
/// 构造屏幕空间蓝图画布
/// </summary>
/// <param name="unityCanvas">Unity UGUI Canvas 组件</param>
public ScreenSpaceBlueprintCanvas(UnityEngine.Canvas unityCanvas)
{
_unityCanvas = unityCanvas;
_rectTransform = unityCanvas.GetComponent<RectTransform>();
}
/// <summary>
/// 将屏幕坐标转换为画布坐标
/// </summary>
/// <param name="screenPoint">屏幕空间的坐标点</param>
/// <returns>画布空间的坐标点</returns>
public override Vector2 ScreenToCanvas(Vector2 screenPoint)
{
RectTransformUtility.ScreenPointToLocalPointInRectangle(
_rectTransform, screenPoint, _unityCanvas.worldCamera, out Vector2 localPoint);
return (localPoint - _panOffset) / _zoomLevel;
}
/// <summary>
/// 将画布坐标转换为屏幕坐标
/// </summary>
/// <param name="canvasPoint">画布空间的坐标点</param>
/// <returns>屏幕空间的坐标点</returns>
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;
}
/// <summary>
/// 将画布坐标转换为世界坐标
/// <code>
/// 屏幕空间画布的世界空间转换依赖于 Canvas 的渲染模式:
/// Screen Space - Overlay 时直接使用 localPoint 作为屏幕坐标;
/// Screen Space - Camera 时通过 camera 进行坐标转换。
/// </code>
/// </summary>
/// <param name="canvasPoint">画布空间的坐标点</param>
/// <returns>世界空间的坐标点</returns>
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;
}
/// <summary>
/// 将世界坐标转换为画布坐标
/// </summary>
/// <param name="worldPoint">世界空间的坐标点</param>
/// <returns>画布空间的坐标点</returns>
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;
}
/// <summary>
/// 获取渲染根 TransformUGUI Canvas 的 RectTransform
/// </summary>
public override Transform GetRootTransform()
{
return _rectTransform;
}
/// <summary>
/// 底层 Unity Canvas 引用
/// </summary>
public UnityEngine.Canvas UnityCanvas
{
get { return _unityCanvas; }
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2df8866bfc0672749a3ea7ebb87aa708
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,104 @@
using UnityEngine;
namespace XericLibrary.Runtime.Blueprint.Canvas
{
/// <summary>
/// 世界空间蓝图画布实现,用于 Sprite 渲染等世界空间场景。
/// <code>
/// 通过相机射线与画布平面的交点实现屏幕坐标到画布坐标的转换。
/// 缩放范围:0.05x ~ 5x。
/// </code>
/// </summary>
public class WorldSpaceBlueprintCanvas : BlueprintCanvasBase
{
private readonly Transform _canvasTransform;
/// <summary>
/// 构造世界空间蓝图画布
/// </summary>
/// <param name="canvasTransform">画布的 Transform 组件</param>
/// <param name="camera">使用的摄像机,如果为 null 则使用 Camera.main</param>
public WorldSpaceBlueprintCanvas(Transform canvasTransform, Camera camera = null)
{
_canvasTransform = canvasTransform;
SetCamera(camera != null ? camera : Camera.main);
MinZoom = 0.05f;
MaxZoom = 5f;
}
/// <summary>
/// 将屏幕坐标转换为画布坐标
/// <code>
/// 通过相机的屏幕射线与画布平面的交点计算画布坐标。
/// 画布平面由 Transform 的前向方向和位置定义。
/// </code>
/// </summary>
/// <param name="screenPoint">屏幕空间的坐标点</param>
/// <returns>画布空间的坐标点</returns>
public override Vector2 ScreenToCanvas(Vector2 screenPoint)
{
if (_camera == null)
{
return Vector2.zero;
}
Ray ray = _camera.ScreenPointToRay(screenPoint);
Plane plane = new Plane(-_canvasTransform.forward, _canvasTransform.position);
if (plane.Raycast(ray, out float dist))
{
Vector3 worldPoint = ray.GetPoint(dist);
Vector3 local = _canvasTransform.InverseTransformPoint(worldPoint);
return (new Vector2(local.x, local.y) - _panOffset) / _zoomLevel;
}
return Vector2.zero;
}
/// <summary>
/// 将画布坐标转换为屏幕坐标
/// </summary>
/// <param name="canvasPoint">画布空间的坐标点</param>
/// <returns>屏幕空间的坐标点</returns>
public override Vector2 CanvasToScreen(Vector2 canvasPoint)
{
if (_camera == null)
{
return Vector2.zero;
}
Vector2 local = canvasPoint * _zoomLevel + _panOffset;
Vector3 world = _canvasTransform.TransformPoint(new Vector3(local.x, local.y, 0f));
return _camera.WorldToScreenPoint(world);
}
/// <summary>
/// 将画布坐标转换为世界坐标
/// </summary>
/// <param name="canvasPoint">画布空间的坐标点</param>
/// <returns>世界空间的坐标点</returns>
public override Vector3 CanvasToWorld(Vector2 canvasPoint)
{
Vector2 local = canvasPoint * _zoomLevel + _panOffset;
return _canvasTransform.TransformPoint(new Vector3(local.x, local.y, 0f));
}
/// <summary>
/// 将世界坐标转换为画布坐标
/// </summary>
/// <param name="worldPoint">世界空间的坐标点</param>
/// <returns>画布空间的坐标点</returns>
public override Vector2 WorldToCanvas(Vector3 worldPoint)
{
Vector3 local = _canvasTransform.InverseTransformPoint(worldPoint);
return (new Vector2(local.x, local.y) - _panOffset) / _zoomLevel;
}
/// <summary>
/// 获取渲染根 Transform
/// </summary>
public override Transform GetRootTransform()
{
return _canvasTransform;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 386905a638369a64c9cc64ec70d51620
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 38246c6c75b0cf94f983bff80ccd471e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,70 @@
using UnityEngine;
using XericLibrary.Runtime.Blueprint;
namespace XericLibrary.Runtime.Blueprint.Element
{
/// <summary>
/// 图论测试节点 —— 用于图论蓝图测试的简单节点。
/// 包含一个输入端口和一个输出端口,节点颜色由外部指定。
/// </summary>
public class GraphTheoryNode : BlueprintNode
{
private readonly string _title;
private readonly string _category;
public override string NodeTitle
{
get { return _title; }
}
public override string Category
{
get { return _category; }
}
/// <summary>
/// 创建一个图论测试节点。
/// </summary>
/// <param name="title">节点标题</param>
/// <param name="color">节点边框颜色</param>
public GraphTheoryNode(string title, Color color)
{
_title = title;
_category = "Test";
NodeColor = color;
NodeBGColor = new Color(0.15f, 0.15f, 0.15f, 1f);
NodeTextColor = Color.gray;
AddInputPort("In", "any");
AddOutputPort("Out", "any");
}
/// <summary>
/// 创建一个带指定颜色的图论测试节点。
/// </summary>
/// <param name="title">节点标题</param>
/// <param name="color">节点边框颜色</param>
/// <param name="bgColor">节点背景颜色</param>
public GraphTheoryNode(string title, Color color, Color bgColor)
{
_title = title;
_category = "Test";
NodeColor = color;
NodeBGColor = bgColor;
NodeTextColor = Color.gray;
AddInputPort("In", "any");
AddOutputPort("Out", "any");
}
public override void Execute(BlueprintExecutionContext context)
{
// 测试节点,无执行逻辑
}
public override System.Collections.Generic.IReadOnlyList<System.Type> GetRequiredToolTypes()
{
return System.Array.Empty<System.Type>();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1866118873770184ba4c48f8c8d2ead6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,334 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
#if ENABLE_INPUT_SYSTEM
using UnityEngine.InputSystem;
#endif
#if UNITY_EDITOR
using UnityEditor;
#endif
using XericLibrary.Runtime.Blueprint.Canvas;
using XericLibrary.Runtime.Blueprint.Element;
namespace XericLibrary.Runtime.Blueprint
{
/// <summary>
/// 图论蓝图组件 —— 在一个 UGUI Canvas 上挂载蓝图系统。
/// 使用 ExecuteAlways 在编辑器中也会运行,方便预览。
/// <para>驱动方式:
/// - 编辑器非运行态:EditorApplication.update → EditorTick
/// - 运行时:MonoBehaviour.Update</para>
/// </summary>
[DisallowMultipleComponent]
[ExecuteAlways]
public class GraphTheoryBlueprintComponent : MonoBehaviour
{
[Header("Canvas Settings")]
[Tooltip("勾选则为世界空间画布,否则为屏幕空间画布")]
[SerializeField] private bool _useWorldSpaceCanvas = false;
[Tooltip("世界空间画布的相机引用")]
[SerializeField] private Camera _worldCamera;
[Header("Theme")]
[Tooltip("蓝图主题名称。留空使用默认(所有 QuickGraph 工具可见)。")]
[SerializeField] private string _themeName = string.Empty;
[Header("Input")]
[Tooltip("新输入系统 InputActionAsset。留空则仅支持鼠标基础操作(中键拖动平移、滚轮缩放)。")]
[SerializeField] private Object _inputActions;
public Object InputActions
{
get { return _inputActions; }
set
{
if (_inputActions == value) return;
#if ENABLE_INPUT_SYSTEM
if (_inputActions is InputActionAsset oldAsset)
BlueprintInputManager.UnregisterAsset(oldAsset);
#endif
_inputActions = value;
#if ENABLE_INPUT_SYSTEM
if (value is InputActionAsset newAsset)
BlueprintInputManager.RegisterAsset(newAsset);
#endif
}
}
[Header("Tool Config Overrides")]
[Tooltip("拖拽配置资产到此,渲染时自动按工具类型+主题匹配。")]
[SerializeField] private List<BlueprintToolConfigBase> _toolConfigAssets = new List<BlueprintToolConfigBase>();
[Header("Test")]
[Tooltip("是否自动生成测试节点")]
[SerializeField] private bool _autoGenerateTestNodes = true;
[Header("Debug")]
[Tooltip("显示所有隐藏的运行时对象(HideFlags),用于调试。默认关闭。")]
[SerializeField] private bool _showDebugObjects = false;
/// <summary>当前蓝图实例</summary>
public BlueprintGraph Graph { get; private set; }
/// <summary>当前画布</summary>
public IBlueprintCanvas Canvas { get; private set; }
public string ThemeName
{
get { return _themeName; }
set
{
_themeName = value ?? string.Empty;
if (Graph != null)
{
Graph.ThemeName = _themeName;
Graph.RefreshTheme();
}
}
}
// ===== 初始化 =====
private void Awake()
{
#if UNITY_EDITOR
// 编辑器下提前注册 tick
if (!Application.isPlaying)
EditorApplication.update += EditorTick;
#endif
CreateCanvas();
CreateGraph();
SetupInput();
}
private void CreateCanvas()
{
if (_useWorldSpaceCanvas)
{
Canvas = new WorldSpaceBlueprintCanvas(transform, _worldCamera);
}
else
{
var unityCanvas = GetComponent<UnityEngine.Canvas>();
if (unityCanvas == null)
{
unityCanvas = gameObject.AddComponent<UnityEngine.Canvas>();
unityCanvas.renderMode = RenderMode.ScreenSpaceOverlay;
}
if (GetComponent<UnityEngine.UI.GraphicRaycaster>() == null)
gameObject.AddComponent<UnityEngine.UI.GraphicRaycaster>();
Canvas = new ScreenSpaceBlueprintCanvas(unityCanvas);
}
}
private void CreateGraph()
{
var system = BlueprintGraphSystem.GlobalInstance;
Graph = system.CreateGraph(Canvas);
Graph.ConfigAssets = _toolConfigAssets;
if (!string.IsNullOrEmpty(_themeName))
{
Graph.ThemeName = _themeName;
Graph.RefreshTheme();
}
// 立即执行一次渲染,确保背景等基础设施在首帧 OnUpdate 前就绪
Graph.LateUpdate();
}
private void SetupInput()
{
#if ENABLE_INPUT_SYSTEM
EnsureEventSystem(_inputActions as InputActionAsset);
if (_inputActions is InputActionAsset asset)
{
BlueprintInputManager.RegisterAsset(asset);
}
#else
EnsureEventSystem(null);
#endif
BlueprintInputManager.SetFocusedGraph(Graph);
}
#if ENABLE_INPUT_SYSTEM
private void EnsureEventSystem(InputActionAsset actionsAsset)
#else
private void EnsureEventSystem(Object _)
#endif
{
var es = FindObjectOfType<EventSystem>();
EventSystem eventSys;
if (es != null)
{
eventSys = es;
}
else
{
var esGo = new GameObject("EventSystem");
eventSys = esGo.AddComponent<EventSystem>();
}
#if ENABLE_INPUT_SYSTEM
if (actionsAsset != null)
{
var uiModule = eventSys.GetComponent<UnityEngine.InputSystem.UI.InputSystemUIInputModule>();
if (uiModule == null)
{
var standalone = eventSys.GetComponent<StandaloneInputModule>();
if (standalone != null)
DestroyImmediate(standalone);
uiModule = eventSys.gameObject.AddComponent<UnityEngine.InputSystem.UI.InputSystemUIInputModule>();
}
uiModule.actionsAsset = actionsAsset;
}
else
#endif
{
if (eventSys.currentInputModule == null)
{
eventSys.gameObject.AddComponent<StandaloneInputModule>();
}
}
}
// ===== 调试可见性 =====
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();
}
// ===== 驱动:Update(运行时)=====
private void Update()
{
#if UNITY_EDITOR
if (!Application.isPlaying) return;
#endif
if (Graph == null) return;
Graph.Update();
Graph.LateUpdate();
SyncDebugVisibility();
}
#if UNITY_EDITOR
// ===== 驱动:EditorTick(编辑器非运行态)=====
private void EditorTick()
{
if (this == null || Graph == null) return;
Graph.ConfigAssets = _toolConfigAssets;
Graph.InvalidateToolConfigCache();
Graph.Update();
Graph.LateUpdate();
SyncDebugVisibility();
}
private void OnValidate()
{
if (Graph == null || Application.isPlaying) return;
Graph.ConfigAssets = _toolConfigAssets;
Graph.InvalidateToolConfigCache();
}
#endif
// ===== 测试节点生成 =====
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);
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);
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, centerY + branchOffset);
node4.NodeSize = new Vector2(90, 130);
var node5 = new GraphTheoryNode("Node 5", Color.cyan);
node5.Position = new Vector2(startX + spacing, centerY - branchOffset);
node5.NodeSize = new Vector2(90, 130);
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 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)
{
var system = BlueprintGraphSystem.LazyInstance;
system?.DestroyGraph(Graph);
Graph = null;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b0187d970265b514c98d142a8b6c34e9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8d9272dc004d78b409ea3890a6be3ba0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,165 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace XericLibrary.Runtime.Blueprint
{
/// <summary>
/// 蓝图框选工具 —— 鼠标左键在空白区域拖拽,绘制矩形选框,
/// 释放时计算框选范围内所有元素的交集并输出统计日志。
/// <para>选框 UGUI Image 默认渲染在所有层之上(SetAsLastSibling)。</para>
/// <para>框选结果保存在 <see cref="SelectedElements"/> 中供其他工具读取。</para>
/// </summary>
[BlueprintTool(phase: ToolPhase.PreUpdate, order: 60)]
[BlueprintTheme("QuickGraph")]
public class BlueprintBoxSelectTool : BlueprintTool
{
// ---------- 状态 ----------
private bool _isBoxSelecting;
private Vector2 _dragStartCanvas; // 按下时的画布坐标
private Vector2 _dragEndCanvas; // 当前的画布坐标
// 选框 UI
private GameObject _selectionBoxGo;
private Image _selectionBoxImage;
private RectTransform _selectionBoxRt;
// ---------- 结果 ----------
/// <summary>本次框选选中的元素列表。</summary>
public List<IBlueprintElement> SelectedElements { get; } = new List<IBlueprintElement>();
// ===== 鼠标按下 =====
public override void OnPointerDown(Vector2 canvasPoint, int mouseButton)
{
if (Graph == null) return;
if (mouseButton != 0) return; // 仅左键
// 仅在空白区域开始框选(HitTest 无结果)
var hit = Graph.HitTest(canvasPoint);
if (hit != null) return;
_isBoxSelecting = true;
_dragStartCanvas = canvasPoint;
_dragEndCanvas = canvasPoint;
// 创建选框 UI
CreateSelectionBox();
}
// ===== 拖拽 =====
public override void OnPointerDrag(Vector2 canvasPoint, Vector2 delta, int mouseButton)
{
if (!_isBoxSelecting || mouseButton != 0) return;
_dragEndCanvas = canvasPoint;
UpdateSelectionBox();
}
// ===== 释放 =====
public override void OnPointerUp(Vector2 canvasPoint, int mouseButton)
{
if (!_isBoxSelecting || mouseButton != 0) return;
_dragEndCanvas = canvasPoint;
FinishSelection();
}
// ===== 销毁 =====
public override void OnDestroy()
{
DestroySelectionBox();
}
// ===== 选框 UI =====
private void CreateSelectionBox()
{
if (Graph?.Canvas == null) return;
var root = Graph.Canvas.GetRootTransform();
if (root == null) return;
_selectionBoxGo = new GameObject("__Bp_BoxSelect", typeof(RectTransform), typeof(Image));
_selectionBoxGo.hideFlags = HideFlags.HideAndDontSave;
_selectionBoxRt = _selectionBoxGo.GetComponent<RectTransform>();
_selectionBoxRt.SetParent(root, false);
_selectionBoxRt.SetAsLastSibling(); // 渲染在最顶层
_selectionBoxImage = _selectionBoxGo.GetComponent<Image>();
_selectionBoxImage.color = new Color(0.2f, 0.5f, 1.0f, 0.15f); // 半透明蓝
// 边框通过 Outline 或额外 Image 实现,直接使用透明填充 + 轮廓不好做,
// 使用两个 Image:填充(当前) + 边框(另一个,1px 白色)
// 为简化,在填充 Image 上挂一个 Outline 组件模拟边框
var outline = _selectionBoxGo.AddComponent<Outline>();
outline.effectColor = Color.white;
outline.effectDistance = new Vector2(1, -1);
}
private void UpdateSelectionBox()
{
if (_selectionBoxRt == null || Graph?.Canvas == null) return;
// 将画布坐标两角转为屏幕坐标
var startScreen = Graph.Canvas.CanvasToScreen(_dragStartCanvas);
var endScreen = Graph.Canvas.CanvasToScreen(_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);
_selectionBoxRt.anchoredPosition = new Vector2(minX, minY);
_selectionBoxRt.sizeDelta = new Vector2(maxX - minX, maxY - minY);
}
private void FinishSelection()
{
_isBoxSelecting = false;
UpdateSelectionBox();
// 计算选框内的元素(画布坐标空间)
SelectedElements.Clear();
var min = new Vector2(
Mathf.Min(_dragStartCanvas.x, _dragEndCanvas.x),
Mathf.Min(_dragStartCanvas.y, _dragEndCanvas.y));
var max = new Vector2(
Mathf.Max(_dragStartCanvas.x, _dragEndCanvas.x),
Mathf.Max(_dragStartCanvas.y, _dragEndCanvas.y));
var selectRect = new Rect(min, max - min);
foreach (var element in Graph.Elements)
{
if (element == null) continue;
if (element.BoundingBox.Overlaps(selectRect))
{
SelectedElements.Add(element);
}
}
// 销毁选框
DestroySelectionBox();
}
private void DestroySelectionBox()
{
if (_selectionBoxGo != null)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
Object.DestroyImmediate(_selectionBoxGo);
else
#endif
Object.Destroy(_selectionBoxGo);
_selectionBoxGo = null;
_selectionBoxRt = null;
_selectionBoxImage = null;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e9d5cdc3f1b8c9840b274a6db73a9fb1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,47 @@
namespace XericLibrary.Runtime.Blueprint
{
/// <summary>
/// 蓝图输入系统的 Action Map / Action 名称常量。
/// 供 Editor 的 InputAction 模板和 Runtime 的输入处理工具共同引用,
/// 避免字符串硬编码散布各处。
/// </summary>
public static class BlueprintInputConstants
{
// ── Action Map ──
public const string MapName = "Blueprint";
// ── 鼠标 / 指针 Actions(名称须与 UnityEngine.InputSystem.UI.InputSystemUIInputModule 官方预设一致)──
/// <summary>画布内指针位置(Vector2)。InputSystemUIInputModule 预设名称。</summary>
public const string Point = "Point";
/// <summary>滚轮滚动(Vector2)。InputSystemUIInputModule 预设名称。</summary>
public const string ScrollWheel = "ScrollWheel";
/// <summary>左键点击(Button)。InputSystemUIInputModule 预设名称。</summary>
public const string LeftClick = "LeftClick";
/// <summary>右键点击(Button)。InputSystemUIInputModule 预设名称。</summary>
public const string RightClick = "RightClick";
/// <summary>中键点击(Button)。InputSystemUIInputModule 预设名称。</summary>
public const string MiddleClick = "MiddleClick";
// ── 功能键 Actions ──
/// <summary>左 ShiftButton</summary>
public const string Shift = "Shift";
/// <summary>左 CtrlButton</summary>
public const string Ctrl = "Ctrl";
/// <summary>左 AltButton</summary>
public const string Alt = "Alt";
// ── 快捷键组合 Actions(仅新输入系统)──
/// <summary>平移画布(Vector24方向)</summary>
public const string Pan = "Pan";
/// <summary>缩放画布(float,滚轮 + Ctrl 组合)</summary>
public const string Zoom = "Zoom";
/// <summary>定位到核心节点</summary>
public const string FocusHome = "FocusHome";
/// <summary>撤销</summary>
public const string Undo = "Undo";
/// <summary>重做</summary>
public const string Redo = "Redo";
/// <summary>进入父级</summary>
public const string NavigateParent = "NavigateParent";
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3e718c439f4fa3d449427433ed729467
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,188 @@
#if ENABLE_INPUT_SYSTEM
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
namespace XericLibrary.Runtime.Blueprint
{
/// <summary>
/// 蓝图输入管理器(新输入系统)—— 管理 InputActionAsset 的引用计数与启用/停用。
/// <para>
/// 多个蓝图可能引用同一个 InputActionAsset,管理器确保:
/// - 首个蓝图注册时启用 asset 并绑定 Action 回调;
/// - 后续蓝图注册仅增加计数,不重复启用;
/// - 蓝图注销时减少计数,计数归零时才解绑并禁用 asset。
/// </para>
/// <para>
/// 蓝图组件在 Awake 时调用 <see cref="RegisterAsset"/>OnDestroy 时调用
/// <see cref="UnregisterAsset"/>;运行时动态更换 asset 也通过这两方法。
/// </para>
/// </summary>
public static class BlueprintInputManager
{
/// <summary>asset → 引用计数</summary>
private static Dictionary<InputActionAsset, int> _refCounts
= new Dictionary<InputActionAsset, int>();
/// <summary>asset → 已绑定回调的 ActionMap</summary>
private static Dictionary<InputActionAsset, InputActionMap> _activeMaps
= new Dictionary<InputActionAsset, InputActionMap>();
// ===== 快捷键最新值(供 QuickGraphInputTool.OnUpdate 轮询) =====
/// <summary>Pan 动作最新的 Vector2 方向值(在 performed 中写入,canceled 中归零)。</summary>
public static Vector2 LastPanDirection;
/// <summary>Zoom 动作最新的 float 轴值(在 performed 中写入,canceled 中归零)。</summary>
public static float LastZoomAxis;
// 缓存的回调引用(确保 Bind/Unbind 使用同一委托实例)
private static readonly System.Action<InputAction.CallbackContext> s_panHandler
= ctx => LastPanDirection = ctx.ReadValue<Vector2>();
private static readonly System.Action<InputAction.CallbackContext> s_panCanceled
= _ => LastPanDirection = Vector2.zero;
private static readonly System.Action<InputAction.CallbackContext> s_zoomHandler
= ctx => LastZoomAxis = ctx.ReadValue<float>();
private static readonly System.Action<InputAction.CallbackContext> s_zoomCanceled
= _ => LastZoomAxis = 0f;
private static readonly System.Action<InputAction.CallbackContext> s_homeHandler
= _ => OnActionReceived(BlueprintInputConstants.FocusHome);
private static readonly System.Action<InputAction.CallbackContext> s_undoHandler
= _ => OnActionReceived(BlueprintInputConstants.Undo);
private static readonly System.Action<InputAction.CallbackContext> s_redoHandler
= _ => OnActionReceived(BlueprintInputConstants.Redo);
private static readonly System.Action<InputAction.CallbackContext> s_parentHandler
= _ => OnActionReceived(BlueprintInputConstants.NavigateParent);
// ===== 当前焦点蓝图(快捷键分发目标) =====
private static BlueprintGraph s_focusedGraph;
/// <summary>设置当前焦点蓝图(快捷键分发目标)。</summary>
public static void SetFocusedGraph(BlueprintGraph graph)
{
s_focusedGraph = graph;
}
// ===== 注册 / 注销 =====
/// <summary>
/// 注册一个蓝图对 InputActionAsset 的引用。
/// 若 asset 尚未启用,则启用并绑定快捷键回调。
/// 允许多个蓝图共享同一 asset。
/// </summary>
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;
}
}
/// <summary>
/// 注销一个蓝图对 InputActionAsset 的引用。
/// 引用计数归零时解绑回调并禁用 asset。
/// </summary>
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<InputAction.CallbackContext> handler)
{
var action = map.FindAction(name);
if (action != null)
action.performed += handler;
}
private static void Unbind(InputActionMap map, string name, System.Action<InputAction.CallbackContext> handler)
{
var action = map.FindAction(name);
if (action != null)
action.performed -= handler;
}
private static void BindCancelled(InputActionMap map, string name, System.Action<InputAction.CallbackContext> handler)
{
var action = map.FindAction(name);
if (action != null)
action.canceled += handler;
}
private static void UnbindCancelled(InputActionMap map, string name, System.Action<InputAction.CallbackContext> 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
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 72f3c53c414fbef4b80270adb10afe1d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,135 @@
#if !ENABLE_INPUT_SYSTEM
using UnityEngine;
namespace XericLibrary.Runtime.Blueprint
{
/// <summary>
/// 蓝图输入管理器(旧输入系统)—— 通过旧输入系统轮询鼠标和键盘事件。
/// <para>
/// 提供与 <c>BlueprintInputManager.InputSystem.cs</c> 相同的静态 API 签名,
/// 但底层使用 <see cref="Input.GetMouseButton"/>/<see cref="Input.GetKey"/> 等旧 API
/// 不依赖任何新输入系统类型。
/// </para>
/// <para>
/// 鼠标点击、拖拽、右键等事件通过 <see cref="PollMouseEvents"/> 轮询后
/// 分发到 <see cref="BlueprintToolProvider"/> 的 DispatchXxx 方法。
/// 键盘 Pan/Zoom 值通过 <see cref="PollKeyboard"/> 轮询后由
/// <see cref="QuickGraphInputTool"/> 在 OnUpdate 中读取。
/// </para>
/// </summary>
public static class BlueprintInputManager
{
/// <summary>
/// 当前焦点蓝图(快捷键分发目标)。
/// 由 <see cref="SetFocusedGraph"/> 设置。
/// </summary>
private static BlueprintGraph s_focusedGraph;
/// <summary>设置当前焦点蓝图。</summary>
public static void SetFocusedGraph(BlueprintGraph graph)
{
s_focusedGraph = graph;
}
/// <summary>
/// 注册 InputActionAsset(旧系统空操作)。
/// 仅保留签名以兼容编译。
/// </summary>
public static void RegisterAsset(Object asset)
{
// 旧输入系统:不管理 InputActionAsset
}
/// <summary>
/// 注销 InputActionAsset(旧系统空操作)。
/// 仅保留签名以兼容编译。
/// </summary>
public static void UnregisterAsset(Object asset)
{
// 旧输入系统:不管理 InputActionAsset
}
// ===== 键盘轮询值(供 QuickGraphInputTool.OnUpdate 读取) =====
/// <summary>
/// 方向键 Pan 方向值(每帧由 <see cref="PollKeyboard"/> 更新)。
/// </summary>
public static Vector2 LastPanDirection;
/// <summary>
/// +/- 键 Zoom 轴值(每帧由 <see cref="PollKeyboard"/> 更新)。
/// </summary>
public static float LastZoomAxis;
// ===== 事件轮询 =====
/// <summary>
/// 轮询鼠标按键事件。
/// 每帧在 Update 中调用,检测鼠标按下/释放并分发到 <see cref="BlueprintToolProvider"/>。
/// </summary>
/// <param name="mouseButton">0=左键, 1=右键, 2=中键</param>
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);
}
}
/// <summary>
/// 轮询鼠标滚轮事件。
/// 每帧在 Update 中调用,检测滚轮增量并分发到 <see cref="BlueprintToolProvider"/>。
/// </summary>
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);
}
}
/// <summary>
/// 轮询键盘快捷键(方向键 Pan、+/- Zoom)。
/// 每帧在 Update 中调用,更新 <see cref="LastPanDirection"/> 和 <see cref="LastZoomAxis"/>。
/// </summary>
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
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1f94468ab3403984494aa0d980297093
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,110 @@
using UnityEngine;
namespace XericLibrary.Runtime.Blueprint
{
/// <summary>
/// 蓝图输入接收工具 —— 继承自 <see cref="BlueprintTool"/>,由 BlueprintToolProvider 统一管理生命周期。
/// <para>
/// 职责:
/// 1. 创建并驱动 <see cref="BlueprintUGUIInputManager"/>,绑定 UGUI EventTrigger 到背景 Image
/// 2. 在 <see cref="OnUpdate"/> 中统一刷新鼠标位置、检测指针进入/离开背景区域;
/// 3. 将画布内的指针移动事件分发给 <see cref="BlueprintToolProvider"/>。
/// </para>
/// <para>
/// 鼠标按键按下/释放/拖拽/滚轮等由 <see cref="BlueprintUGUIInputManager"/> 通过 EventTrigger 回调直接分发。
/// </para>
/// </summary>
[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;
}
// ===== 辅助 =====
/// <summary>检查屏幕坐标是否落在背景 RectTransform 内。</summary>
private bool IsScreenPointOverBackground(Vector2 screenPoint)
{
var rt = _uguiManager?.BgRectTransform;
if (rt == null) return false;
return RectTransformUtility.RectangleContainsScreenPoint(rt, screenPoint, null);
}
/// <summary>屏幕坐标 → 画布坐标。</summary>
private Vector2 ScreenToCanvas(Vector2 screenPos)
{
return Graph?.Canvas != null ? Graph.Canvas.ScreenToCanvas(screenPos) : screenPos;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7e1d0771b4cc2aa4692eff637fa5d5af
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,239 @@
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace XericLibrary.Runtime.Blueprint
{
/// <summary>
/// 蓝图 UGUI 输入管理器 —— 负责绑定 UGUI EventTrigger 到背景 Image
/// 将 UGUI 指针事件转发到 <see cref="BlueprintToolProvider"/> 的分发方法。
/// <para>
/// 该类仅处理与 UGUI EventTrigger 相关的绑定逻辑,不感知新旧输入系统的差异。
/// 由 <see cref="BlueprintInputReceiver"/> 创建并在 OnUpdate 中驱动。
/// </para>
/// </summary>
public class BlueprintUGUIInputManager
{
/// <summary>
/// 当前帧鼠标/指针在屏幕空间的位置。
/// 每帧由 <see cref="UpdateMousePosition"/> 刷新。
/// </summary>
public static Vector2 MousePosition { get; private set; }
/// <summary>
/// 当前帧鼠标/指针是否处于新输入系统模式。
/// <c>true</c> = 新输入系统(<c>ENABLE_INPUT_SYSTEM</c> 已定义);
/// <c>false</c> = 旧输入系统。
/// </summary>
public static bool IsNewInputSystem
{
get
{
#if ENABLE_INPUT_SYSTEM
return true;
#else
return false;
#endif
}
}
/// <summary>
/// 更新静态鼠标位置。每帧由 BlueprintInputReceiver 的 OnUpdate 调用。
/// 内部根据条件编译选择鼠标位置读取源。
/// </summary>
public static void UpdateMousePosition()
{
#if ENABLE_INPUT_SYSTEM
MousePosition = UnityEngine.InputSystem.Mouse.current?.position.ReadValue() ?? Vector2.zero;
#else
MousePosition = (Vector2)Input.mousePosition;
#endif
}
/// <summary>
/// 返回当前是否按住指定鼠标按钮。
/// 内部根据条件编译选择检测方式。
/// </summary>
/// <param name="button">0=左键, 1=右键, 2=中键</param>
public static bool IsButtonPressed(int button)
{
#if ENABLE_INPUT_SYSTEM
var mouse = UnityEngine.InputSystem.Mouse.current;
if (mouse == null) return false;
return button switch
{
0 => mouse.leftButton.isPressed,
1 => mouse.rightButton.isPressed,
2 => mouse.middleButton.isPressed,
_ => false,
};
#else
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";
// ===== 生命周期 =====
/// <summary>初始化 UGUI 输入管理器并绑定到指定蓝图。</summary>
public BlueprintUGUIInputManager(BlueprintGraph graph)
{
_graph = graph;
}
/// <summary>
/// 尝试绑定 EventTrigger 到背景 Image。
/// 返回 <c>true</c> 表示绑定成功,<c>false</c> 表示背景尚未创建。
/// </summary>
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<EventTrigger>();
if (_eventTrigger == null)
_eventTrigger = _bgTransform.gameObject.AddComponent<EventTrigger>();
else
_eventTrigger.triggers.Clear();
BindEventTrigger();
_isBound = true;
return true;
}
/// <summary>解绑并清理 EventTrigger。</summary>
public void Unbind()
{
if (_eventTrigger != null)
{
_eventTrigger.triggers.Clear();
_eventTrigger = null;
}
_bgTransform = null;
_isBound = false;
}
/// <summary>是否已绑定。</summary>
public bool IsBound => _isBound;
/// <summary>背景 RectTransform(用于屏幕点包含检测)。</summary>
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<BaseEventData> 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);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 00688a047bee7ee4b837f07886b45d44
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,226 @@
using UnityEngine;
using XericLibrary.Runtime.Blueprint.Render;
namespace XericLibrary.Runtime.Blueprint
{
/// <summary>
/// QuickGraph 输入交互工具 —— 处理画布平移、缩放和节点选择。
/// <para>中键拖拽 = 平移;左键点击 = 选择;滚轮 = 缩放。</para>
/// <para>快捷键(新输入系统):方向键 = 平移,Ctrl+± = 缩放,Ctrl+H = 归位。</para>
/// </summary>
[BlueprintTool(phase: ToolPhase.PreUpdate, order: 50)]
[BlueprintTheme("QuickGraph")]
public class QuickGraphInputTool : BlueprintTool
{
// ---------- 配置(从 ToolConfigAssets 读取) ----------
private QuickGraphInputConfig Config
{
get
{
if (ToolConfig is QuickGraphInputConfig external)
return external;
if (_defaultConfig == null)
_defaultConfig = ScriptableObject.CreateInstance<QuickGraphInputConfig>();
return _defaultConfig;
}
}
private static QuickGraphInputConfig _defaultConfig;
// ---------- 视图状态 ----------
private Vector2 _targetPanOffset;
private float _targetZoomLevel = 1f;
private bool _viewInitialized;
// 用于检测帧间变化
private Vector2 _prevPanOffset;
private float _prevZoomLevel;
// ---------- 拖拽状态 ----------
private bool _isDragging;
private int _activeDragButton = -1;
private Vector2 _dragStartPan;
// ---------- 选择状态 ----------
private BlueprintElementBase _hoveredElement;
private BlueprintElementBase _selectedElement;
// ===== 更新(平滑插值 + 键盘轮询) =====
public override void OnUpdate(float deltaTime)
{
if (Graph?.Canvas == null) return;
var cfg = Config;
var canvas = Graph.Canvas;
// ── 同步缩放范围 ──
canvas.MinZoom = cfg.MinZoom;
canvas.MaxZoom = cfg.MaxZoom;
if (!_viewInitialized)
{
_targetPanOffset = canvas.PanOffset;
_targetZoomLevel = canvas.ZoomLevel;
_prevPanOffset = _targetPanOffset;
_prevZoomLevel = _targetZoomLevel;
_viewInitialized = true;
}
// ── 键盘轮询(新输入系统 Pan 动作值) ──
_targetPanOffset += BlueprintInputManager.LastPanDirection
* cfg.KeyboardPanSpeed * deltaTime;
// ── 键盘轮询(新输入系统 Zoom 动作值) ──
// zoomInput 对键盘按键是 ±1(按下一下),对轴是 0~1 连续值。
// 统一按一个 scroll notch = 120 缩放,scale = 120 / ZoomDivisor
float zoomInput = BlueprintInputManager.LastZoomAxis;
if (!Mathf.Approximately(zoomInput, 0f))
{
float oldZoom = _targetZoomLevel;
_targetZoomLevel = Mathf.Clamp(
_targetZoomLevel + zoomInput / cfg.ZoomDivisor,
canvas.MinZoom, canvas.MaxZoom);
// 以鼠标焦点为中心缩放(键盘无光标,从鼠标读取)
ApplyZoomFocus(ref _targetPanOffset, _targetZoomLevel, oldZoom);
// 归零,避免持续累加
BlueprintInputManager.LastZoomAxis = 0f;
}
// ── 平滑插值到目标值 ──
canvas.PanOffset = Vector2.Lerp(
canvas.PanOffset, _targetPanOffset, cfg.PanLerpSpeed * deltaTime);
canvas.ZoomLevel = Mathf.Lerp(
canvas.ZoomLevel, _targetZoomLevel, cfg.ZoomLerpSpeed * deltaTime);
// ── 标记脏 ──
if (canvas.PanOffset != _prevPanOffset || !Mathf.Approximately(canvas.ZoomLevel, _prevZoomLevel))
{
Graph.MarkDirty();
_prevPanOffset = canvas.PanOffset;
_prevZoomLevel = canvas.ZoomLevel;
}
}
// ===== 鼠标移动 =====
public override void OnPointerMove(Vector2 canvasPoint, Vector2 delta)
{
if (Graph == null || _isDragging) return;
var hit = Graph.HitTest(canvasPoint);
_hoveredElement = hit as BlueprintElementBase;
}
// ===== 鼠标按下 =====
public override void OnPointerDown(Vector2 canvasPoint, int mouseButton)
{
if (Graph == null) return;
if (mouseButton == 2) // 中键 → 平移
{
_isDragging = true;
_activeDragButton = 2;
_dragStartPan = _targetPanOffset;
}
// 左键点击选择由元素层的 IBlueprintEventHandler.OnPointerDown 处理
}
// ===== 鼠标释放 =====
public override void OnPointerUp(Vector2 canvasPoint, int mouseButton)
{
if (_isDragging && _activeDragButton == mouseButton)
{
_isDragging = false;
_activeDragButton = -1;
}
}
// ===== 拖拽 =====
public override void OnPointerDrag(Vector2 canvasPoint, Vector2 delta, int mouseButton)
{
if (Graph?.Canvas == null) return;
if (mouseButton == 2) // 中键拖拽 = 平移
{
_targetPanOffset += delta * Graph.Canvas.ZoomLevel;
Graph.MarkDirty();
}
}
// ===== 滚轮 =====
public override void OnScroll(Vector2 canvasPoint, Vector2 scrollDelta)
{
if (Graph == null) return;
float oldZoom = _targetZoomLevel;
_targetZoomLevel = Mathf.Clamp(
_targetZoomLevel + scrollDelta.y / Config.ZoomDivisor,
Graph.Canvas.MinZoom, Graph.Canvas.MaxZoom);
if (!Mathf.Approximately(oldZoom, _targetZoomLevel))
{
// 以鼠标光标为中心缩放
_targetPanOffset += canvasPoint * (oldZoom - _targetZoomLevel);
Graph.MarkDirty();
}
}
// ===== 快捷键(仅新输入系统) =====
public override void OnAction(string actionName)
{
if (Graph?.Canvas == null) return;
switch (actionName)
{
case BlueprintInputConstants.FocusHome:
ResetView();
break;
case BlueprintInputConstants.Undo:
break; // TODO
case BlueprintInputConstants.Redo:
break; // TODO
case BlueprintInputConstants.NavigateParent:
break; // TODO
}
}
// ===== 辅助 =====
public void ResetView()
{
_targetPanOffset = Vector2.zero;
_targetZoomLevel = 1f;
Graph?.MarkDirty();
}
private void SelectElement(BlueprintElementBase element)
{
_selectedElement = element;
}
/// <summary>当前悬停的元素(可能为 null</summary>
public BlueprintElementBase HoveredElement => _hoveredElement;
/// <summary>当前选中的元素(可能为 null</summary>
public BlueprintElementBase SelectedElement => _selectedElement;
// ===== 以鼠标焦点为中心缩放(键盘调用 — 自行读取鼠标位置) =====
private void ApplyZoomFocus(ref Vector2 panOffset, float newZoom, float oldZoom)
{
if (Graph?.Canvas == null) return;
// 通过 BlueprintUGUIInputManager 获取统一鼠标位置(已被 BlueprintInputReceiver 每帧刷新)
Vector2 canvasCursor = Graph.Canvas.ScreenToCanvas(BlueprintUGUIInputManager.MousePosition);
panOffset += canvasCursor * (oldZoom - newZoom);
}
// =====(旧 ZoomFocusAdjust 已删除 — 替代为 ApplyZoomFocusOnScroll 直接内联 canvasPoint 计算)=====
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1fe1086d7e9750541a9a5de047e05ce6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 87791decaa857844d8b3d8d266733abb
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: af33ee557aaf7d44182e195524832a41
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -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>包括:
/// - _TransformscaleX, 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.1600 = 每格 +0.2300 = 每格 +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:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 708403450e119784988882f777746911
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+2 -1
View File
@@ -2,7 +2,8 @@
"name": "Lrss3.Deconstruction",
"rootNamespace": "Deconstruction",
"references": [
"Unity.TextMeshPro"
"Unity.TextMeshPro",
"Unity.InputSystem"
],
"includePlatforms": [],
"excludePlatforms": [],
Binary file not shown.
+1 -1
View File
File diff suppressed because one or more lines are too long