通用节点编辑器01-xNode对比
为什么需要 NodeGraphView项目的需求之前写过一篇引导节点编辑器的文档,但随着项目需求的不断复杂,越发地发现需要的不只是仅支持单一业务,和仅将节点显示出来并允许连线的编辑器。每种业务图都应具备从编辑、保存、导出到运行时加载和执行的完整链路。 具体包括: 在 Unity 中编辑节点、端口和连线。 将编辑器图转换为独立的运行时数据,并由可替换的序列化器保存。 在运行时反序列化数据,创建对应的 RuntimeNode 并驱动节点跳转。 支持 Task、对话、剧情、AI 行为等不同业务。 每种业务可以拥有自己的窗口、工具栏、节点内容、端口规则、导出位置及调试工具。 xNode 能做什么xNode 是一个轻量级 Unity 节点图框架。它提供 NodeGraph、Node、端口和默认编辑器,适合快速制作状态机、对话、决策等节点图。 xNode 的 Node 和 NodeGraph 都继承自 ScriptableObject,因此可以直接作为 Unity 资产保存;它并不是完全没有序列化。节点也可以在运行时通过业务代码调用 GetValue 等接口求值。官方将它定位为节点图框架...
通用节点编辑器02-编辑器部分
编辑器基类与扩展点编辑器层由图资产、Controller、窗口、图 View、节点外壳和节点内容 View 组成。 123456NodeGraphAsset -> NodeGraphController -> NodeEditorWindowBase -> NodeGraphViewBase -> NodeBase<TData> -> NodeViewBase<TData> 图资产NodeGraphAsset 是图配置的 ScriptableObject,保存以下内容: graphId:图的稳定标识。 graphName:创建时生成的图名称。 serializedGraph:编辑器图序列化结果。 新建图资产默认保存到: 1Assets/NodeGraphView/Editor Default Resources/GraphData/{GraphName}.asset ControllerNodeGraphController 是业务图的编辑器策略层,常见覆盖点包括: 1...
通用节点编辑器03-运行时部分
通用运行时与序列化接口运行时部分负责加载导出的图数据、创建 RuntimeNode、执行节点生命周期并完成节点跳转。 运行时结构12345GraphRuntimeManager -> GraphRunner -> INodeGraphRuntimeNode -> NodeGraphRuntimeNodeBase<TData> -> 业务 RuntimeNode GraphRuntimeManagerGraphRuntimeManager 是单例入口,负责: 持有多个 GraphRunner。 从字符串、TextAsset 或 Resources 加载运行时图。 统一序列化和反序列化入口。 更新、卸载和清理 Runner。 12GraphRunner runner = GraphRuntimeManager.Instance .LoadFromResources("TaskGraphs/NewGraph0"); GraphRunnerGrap...
通用节点编辑器04-创建具体业务
创建新的业务节点图新增业务时,优先使用脚手架面板: 1Tools/Node Graph/Create Graph Scripts 该面板可以生成 Graph Asset、Controller、Window,以及可选的 Node、NodeView、RuntimeNode 和 Debugger。 生成后的职责 类型 作用 {Business}GraphAsset 业务图资产类型 {Business}GraphController 导出路径、节点创建与编辑器策略 {Business}GraphWindow 业务图编辑窗口与工具栏 {Business}NodeData 节点业务数据 {Business}Node 编辑器节点和端口定义 {Business}NodeView 节点内容区 UI {Business}RuntimeNode 运行时进入、更新、退出行为 新业务的最小实现 创建业务 Graph Asset...
通用节点编辑器05-任务系统实例
Task 节点图示例Task 示例展示了一个完整的业务接入流程:编辑任务节点、导出 Runtime 图、显示任务面板,并根据完成或失败结果继续执行后续节点。 编辑器部分Task 图包含: TaskGraphAsset:Task 图资产。 TaskGraphController:指定 Runtime 导出目录 Assets/Resources/TaskGraphs。 TaskGraphWindow:注册 Tools/Node Graph/Task/Create Task Graph 创建入口。 TaskNode:定义 In、complete 和 failed 端口。 TaskNodeView:编辑 TaskNodeData 的任务名称和描述。 任务节点的出口含义: 端口 key 含义 complete 任务完成后的下一节点 failed 任务失败后的下一节点 运行时入口TaskGraphExampleRunner 只负责: 创建 TaskPanel。 从 Resources 加载 Task 图。 将 TaskPanel 注入每一个 TaskRuntimeN...
Unity的TMP顶点数据优化
上一篇写了 Unity的TMP的纹理优化,核心是把 TMP Font Asset 内部的 SDF Atlas 提取出来,再按平台设置合适的纹理压缩格式。这篇继续写另一个方向:把描边、阴影这类每个文字可能不同的效果参数放进顶点数据里,避免为了不同颜色或不同宽度频繁创建材质实例。 优化目标TMP 默认的 SDF Shader 已经支持描边、阴影、Glow 等效果,但这些参数大多来自 Material。只要某个文本的描边颜色、宽度不一样,就很容易产生新的材质实例。UI 里如果存在大量名字、伤害数字、状态文字,这部分材质数量和 Canvas 渲染批次都会变得不好控制。 我的做法是让材质只负责通用渲染逻辑,把每个文本独有的数据写入 Mesh 顶点通道。Shader 渲染每个字时,从顶点里读取自己的描边或阴影参数。这样多个文字可以共用同一个材质,同时又能显示不同效果。 为什么可以把数据放进顶点Unity 的 UI Mesh 顶点不只有 position、color、uv0。Canvas 还可以通过 additionalShaderChannels 额外传递 uv1、uv2、normal、t...
UniTask 0GC实现原理
UniTask 为什么能做到低 GC:从 async 状态机到 PlayerLoop 的实现原理在 Unity 里写异步逻辑,最常见的方案有三种:Coroutine、Task、UniTask。 Coroutine 写起来简单,但 IEnumerator、yield return、各种等待对象容易带来分配。标准 Task 更适合通用 .NET 异步模型,但它的线程池、同步上下文、Task 对象模型并不是为 Unity 主线程每帧调度设计的。 UniTask 的目标不是让所有代码“绝对 0GC”,而是让高频异步路径,尤其是每帧等待、Unity PlayerLoop 等场景,尽量避免持续分配。它的核心思路可以概括为: 用值类型表示 Task,用自定义 async method builder 接管编译器状态机,用可池化的 source/runner 承载挂起状态,用 Unity PlayerLoop 驱动 continuation。 下面从源码角度拆开看。 1. 先看 async/await 的两套语法糖分析 UniTask 的 0GC 之前,最好先把 C# 的...
UniTask学习与使用
UniTask 学习文档 基于UniTask 源码整理,版本适配 Unity 2019.3+ 目录 什么是 UniTask 核心类型与基本用法 时间与帧等待 PlayerLoopTiming — Unity 生命周期时序 取消机制 (CancellationToken) Unity 对象操作 Unity 生命周期异步触发器 (AsyncTrigger) 条件等待 并发控制 WhenAll / WhenAny 线程切换 UniTaskCompletionSource 手动控制任务 错误处理与 Forget UniTaskVoid — 即发即弃 TimeoutController 超时控制 AsyncLazy 懒加载异步 UniTask Tracker 调试工具 最佳实践与常见陷阱 1. 什么是 UniTaskUniTask 是由 Cysharp 开源的、专为 Unity 设计的高性能异步库,命名空间为 Cysharp.Threading.Tasks。 要理解 UniTask 为什么有存在的必要,得先看清楚为什么 .NET 原生的 Task 在 Unity 里不够好...
Unity 异步等待方案选型:Mono 轮询 vs 协程 vs UniTask
Unity 异步等待方案选型:Mono 轮询 vs 协程 vs UniTask 在 Unity 项目里,”等一段时间再做某件事”、”等加载完再回调”、”等动画播完再切场景” 这类需求几乎天天遇到。选型不当,轻则代码难读,重则 GC 卡顿、生命周期错乱、回调地狱。本文把目前主流的三种方案放在一起做一次彻底的对比。 引子:我们到底在解决什么问题Unity 的核心循环是单线程帧驱动的:所有游戏逻辑都跑在主线程,按 PlayerLoop 的节拍一帧一帧推进。这就意味着 “异步等待” 在 Unity 里本质上是一个时间/事件维度的问题,而不是一个 “线程切换” 的问题。 我们需要解决的典型场景有: 等待 N 秒后执行某件事(技能 CD、UI 提示自动消失) 等待某个资源加载完成(AssetBundle、Addressables、网络请求) 等待某个条件成立(敌人进入视野、玩家按下按键、动画播放到某一帧) 串行/并行编排多个上述等待(连招、剧情演出、新手引导) 围绕这些需求,社区演化出了三种主流方案,下面逐一拆解。 方案一:自己编写基于 Mono 的管理器,在 ...
ZString-Unity的0GC字符串操作
Unity 性能优化:用 ZString 实现真正的 0GC 字符串操作 你是否在 Unity Profiler 里看到过一帧里几百个 GC.Alloc,追查下去全是字符串拼接?本文从 Unity 的字符串内存模型出发,深入讲解 ZString 的零分配原理,并与传统 StringBuilder 做全面对比。 一、Unity 的字符串是什么在深入 ZString 之前,必须先搞清楚 Unity(以及 C#)里字符串的本质。 string 是引用类型,活在托管堆上C# 的 string 是引用类型,这意味着每一个字符串实例都分配在 托管堆(Managed Heap)。堆上的对象由 GC(Garbage Collector)负责管理和回收。 123string a = "Hello"; // 堆上的对象string b = "World"; // 又一个堆上的对象string c = a + ", " + b; // 又一个堆上的对象,a 和 b 变成垃圾 每一次拼接都会产生...




