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