关于 build-gtk
面向 Unity Editor 开发者的技能,指导使用 Graph Toolkit 公共 API 构建图编辑器和状态机编辑工具。内容涵盖版本检查、图结构、验证及运行时资产导入流程。
主要功能
- 01基于公共 API 构建图工具
- 02构建状态机编辑工具
- 03配置节点、端口与选项
- 04验证图并报告问题
- 05通过 ScriptedImporter 编译资产
适用场景
- 01为 Unity 项目创建可视化图编辑器
- 02构建包含状态与转换的状态机
- 03将编辑器图数据导入为运行时资产
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ unity-technologies/ skills --skill build-gtk安装后在 Claude Code 中输入/build-gtk确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/graph-api.mdpitfalls.mdruntime-and-visualization.mdstate-machine-api.md详细介绍
Unity Graph Toolkit(GTK)
基于 Unity 的 Graph Toolkit 模块构建编辑器图工具和状态机工具,仅使用 Unity.GraphToolkit.Editor 命名空间中的公共 API。
重要事项
- 创建一个还原点,以便在更改失败时回滚。
- 只做要求的事情。不要更改无关的资源或文件。避免冗长的解释。
- 在开始工作流或诊断之前,打开并阅读该工作流中指定的参考文件,以及涵盖你打算处理事项的主题索引页面。请在回复中注明你阅读的文件。这是因为关于 Unity 的训练知识可能已经过时、有误,或不适用于当前 Unity 版本,而且 Graph Toolkit 在 6000.4、6000.6 和 6000.7 之间有所变化。
参考资料
按需阅读以下资料。路径相对于此技能文件夹。
references/graph-api.md— Graph API 范围:Graph、Node、端口和选项构建器、上下文和块节点、子图、变量、验证、工具栏和上下文菜单。编写任何图工具代码前请阅读。references/state-machine-api.md— State Machine API(Unity 6.7+):StateMachine、State、Condition、转换和规则、自转换、自定义视图。涉及状态、转换或条件时请阅读。references/runtime-and-visualization.md— 使用 ScriptedImporter 将图或状态机编译为运行时资源,以及在图编辑器中显示实时执行情况。用户希望在游戏中使用创作的数据或在运行时进行调试时请阅读。references/pitfalls.md— 能通过编译但仍会出错的问题。交付前以及用户报告编译错误、Add 菜单为空、节点缺失或发生异常时请阅读。
步骤 1:检查 Editor 版本并选择 API
编写代码前,读取 ProjectSettings/ProjectVersion.txt 中的 m_EditorVersion。Graph Toolkit 是一个 Editor 模块,因此无需安装包。com.unity.graphtoolkit 是已弃用的兼容层,不得将其添加到 manifest。
| Editor 版本 | 可用功能 |
|---|---|
| 早于 6000.4 | 仅有已退役的实验性包(0.x)。其 API 与本技能不同。建议升级。 |
| 6000.4 至 6000.5 | 已包含该模块,但不支持端口容量、类型转换、连线和节点可视化、USS 节点样式或元素 ID。 |
| 6000.6 | 本技能文档介绍的 Graph API。 |
| 6000.7 及更新版本 | 包含以上所有功能,此外还支持 State Machine API、NodeView<T> / StateView<T> / ConditionView<T> 自定义 UI、[GraphMenu] / [BlackboardMenu] 上下文菜单、变更增量 GraphLogger.GraphChanges、端口类型跟随和 Blackboard 类型限制。参考资料中标记为 6.7 的成员。 |
如果用户要求在低于 6000.7 的 Editor 上构建状态机工具,请明确说明该版本不提供 State Machine API。除非用户提出要求,否则不要用 Graph API 模拟实现。
心智模型
-
Graph Toolkit 仅是一个创作框架。它绘制并持久化图,但不会执行图。 用户编写运行时数据模型和运行它的代码,通常由 ScriptedImporter 提供数据。
-
每个
Graph或StateMachine子类对应一种资源类型,由传递给[Graph("ext")]或[StateMachine("ext")]的文件扩展名标识。 -
通过反射发现类型。与图类位于同一程序集中的节点、状态和条件类会自动列出。其他程序集中的类需通过
[UseWithGraph]或[UseWithStateMachine]显式加入。抽象类不会列出。 -
所有创作类都带有
[Serializable],并且位于仅限 Editor 的程序集中,可以是Editor文件夹,也可以是将平台限制为 Editor 的 asmdef。 -
只有公共 API 会为用户编译。如果草稿中出现左列中的名称,请停止并改用右列中的名称:
不要写 改为 GraphModel、GraphObject、GraphTool、Stencil、BaseGraphToolGraph和GraphDatabaseNodeModel、PortModel、WireModelNode、IPort、WireStateModel、TransitionModel、ConditionModelState、ITransition、Condition<T>GraphViewEditorWindow、GraphElementsExtensionMethodsCache、ModelView窗口已提供;在 6000.7 上使用 NodeView<T>实现自定义 UIUnityEditor.Experimental.GraphView、graphViewChanged、Port.Create没有等效项;将工具建模为 Graph和Node类清单中的 com.unity.graphtoolkit无需替代;该模块已内置
工作流 A:构建图工具
现在阅读 references/graph-api.md,再开始编写代码;成员名称和构建器链应以该文档为准,而不是凭记忆。第 5 步适用时,也请阅读 references/runtime-and-visualization.md。
-
Graph 类。
[Graph(AssetExtension)] [Serializable] class MyGraph : Graph,并添加public const string AssetExtension。添加一个调用GraphDatabase.PromptInProjectBrowserToCreateNewAsset<MyGraph>()的[MenuItem("Assets/Create/...")]静态方法。需要子图时,在特性中传入GraphOptions.SupportsSubgraphs。 -
节点类。
[Serializable] class MyNode : Node。重写OnDefinePorts,并确保每条端口构建器链都以.Build()结束。重写OnDefineOptions添加可在检查器中编辑的设置;当端口依赖这些设置时,在OnDefinePorts中使用GetNodeOptionByName(name).TryGetValue<T>(out var v)读取它们。对于类似数量的选项,调用.Delayed(),避免每次按键时都重建端口。使用[Node("Category/Path", iconPath, title, stylesheet)]设置位置、图标、默认标题和 USS。 -
验证。 重写
OnGraphChanged(GraphLogger logger),并使用logger.LogError、LogWarning或Log报告问题,同时将有问题的节点或端口作为上下文传入,使标记显示在该对象上。切勿在此回调中修改图;应将修复操作放入附加到消息的GraphLogAction、菜单项或导入器中。 -
按需构建结构。 上下文和块节点、子图、黑板变量、通过
IsConnectionAllowed进行类型转换、工具栏元素或上下文菜单。详情请参阅graph-api.md。 -
输出。 注册在相同扩展名上的
ScriptedImporter会将图编译为普通的运行时资源。结构始终如下:var graph = GraphDatabase.LoadGraphForImporter<MyGraph>(ctx.assetPath); // never LoadGraph here if (graph == null) return; // bad path or type: log and stop var start = graph.GetNodes().OfType<StartNode>().FirstOrDefault(); if (start == null) return; // OnGraphChanged already reported it var next = start.GetOutputPortByName("Next").FirstConnectedPort?.GetNode(); // null when unconnected var titlePort = next.GetInputPortByName("Title"); titlePort.TryGetValue<string>(out var title); // the value typed on the node if (titlePort.IsConnected) { /* resolve titlePort.FirstConnectedPort.GetNode() upstream instead */ } ctx.AddObjectToAsset("Runtime", runtime); ctx.SetMainObject(runtime);运行时资源及其程序集不得引用
Unity.GraphToolkit.Editor;将Hash128ID 保持为Hash128。调试视图和通过代码构建的图请参阅runtime-and-visualization.md。 -
按照下文所述进行验证。
工作流 B:构建状态机工具(Unity 6.7 及更高版本)
现在阅读 references/state-machine-api.md;此 API 目前还没有手动操作指南,因此该文件和脚本参考是关于其成员名称的唯一准确来源。
- 状态机类。
[StateMachine(AssetExtension)] [Serializable] class MySM : StateMachine并添加一个调用StateMachineDatabase.PromptInProjectBrowserToCreateNewAsset<MySM>()的菜单项。 - 状态。
[Serializable] class Patrol : State。状态没有端口;状态机拥有转换。在OnDefineOptions中声明的选项仅显示在图形检查器中。 - 条件。
[Serializable] [Condition("Health")] class HealthCondition : Condition<float>。需要比较运算符时,设置protected override bool DisplayComparisonDropdown => true。 对于没有值的触发器,直接从Condition派生。与组(And/Or)和变量相关的条件已内置;请勿重新实现。 - 可选项。 自定义自身转换(
SelfTransition+[Transition(...)])、通过StateView<T>和ConditionView<T>自定义 UI,以及[StateMachineMenu]/[ConditionMenu]条目。 - 验证。 重写
OnStateMachineChanged(StateMachineLogger logger)。规则与OnGraphChanged相同。 - 输出。
ScriptedImporter使用StateMachineDatabase.LoadStateMachineForImporter<MySM>加载,然后遍历GetStates()``、每个状态的GetOutgoingTransitions()、每个转换的GetRules(),以及每条规则的RootCondition树。该树始终包含内置类型和用户类,因此编译器必须先处理IGroupCondition(递归处理并遵循Operation)和IVariableCondition(Variable.Name、Comparison、Value),再匹配自定义Condition` 类型。
通过代码编辑图
菜单项、生成器或工具构建或编辑资源时,请将变更操作括起来:
LoadGraph/CreateGraph → UndoBeginRecordGraph("Action") → AddNode、Connect、
port.TrySetValue、CreateVariable → GraphDatabase.SaveGraph(graph) → UndoEndRecordGraph()。
状态机的对应方法是 UndoBeginRecordStateMachine、Connect(fromState, toState)、
SaveStateMachine 和 UndoEndRecordStateMachine。在 OnEnable、OnDisable、
OnGraphChanged 或 OnStateMachineChanged 中执行变更会抛出 InvalidOperationException。
决策
| 用户想要 | 操作 | 原因 |
|---|---|---|
| 限制端口可接受的连线数量 | 在端口构建器中使用 .WithCapacity(PortCapacity.Single)(或 Multi、None) | 端口默认允许多条连线,因此只有声明限制后,框架才会强制执行 |
将 int 输出连接到 float 输入 | 重写 Graph.IsConnectionAllowed(IPort output, IPort input) | 默认情况下,不同类型的端口无法连接;图只会记录连线,转换由你的导入器完成 |
| 创建一个可接受多种类型的端口,例如 Shader Graph 中的端口 | 6000.7+:在构建器上使用 .WithDataTypes(typeof(float), typeof(int), typeof(Vector3))。6000.6:使用无类型端口并实现 IsConnectionAllowed | 6000.6 中不存在 WithDataTypes |
| 创建没有数据类型的执行流端口 | 使用不带类型的 context.AddInputPort("In");其 DataType 为 Untyped。添加 .WithConnectorUI(PortConnectorUI.Arrowhead) 可呈现流端口外观 | 流端口不携带值;为其指定类型容易导致错误连接 |
| 在“添加”菜单中隐藏某种节点类型 | 将其设为 abstract,或将其移到没有 [UseWithGraph] 的其他程序集,或设置 GraphOptions.DisableAutoInclusionOfNodesFromGraphAssembly 并显式选择要加入的节点 | 图所在程序集中的所有具体节点类都会自动列出 |
| 在“添加”菜单中对节点分组 | 使用 [Node("Category/Sub")]。类名或标题名称会追加在路径后面 | 特性的第一个参数是文件夹路径,而不是节点标题 |
了解发生了哪些变化,类似旧版 graphViewChanged | 6000.7+:使用 OnGraphChanged 和 logger.GraphChanges.ChangedNodes,通过 ChangeKinds 标记获取已添加和已修改的节点。任何版本中要获取已删除的节点,以及在 6000.6 中获取所有变化:将缓存的节点 ID HashSet<Hash128> 与 GetNodes() 的结果进行比较 | GraphChanges 在 6000.7 中加入,且从不报告已删除的节点,只报告已删除的端口 |
| 创建多种子图 | 在主图上使用 GraphOptions.SupportsSubgraphs,并在每个子图 Graph 类上使用 [Subgraph(typeof(MainGraph))] | 没有该特性时,主图类型会兼作唯一的子图类型 |
| 为子图节点添加端口 | 在子图中创建类型为 VariableKind.Input 或 Output 的黑板变量 | 子图节点没有 OnDefinePorts;其端口会映射子图的变量 |
| 设置节点图标或 USS 外观 | 使用 [Node(category, iconPath, title, stylesheet)];深色主题图标文件使用 d_ 前缀 | Editor 会在深色主题下选择 d_ 文件,否则使用备用文件 |
| 在运行时显示正在运行的节点及端口中流动的数据 | 使用 GraphVisualization API,参见 runtime-and-visualization.md | Graph Toolkit 没有运行时;游戏必须将状态推送回打开的窗口 |
| 创建类似 Animator 的状态机编辑器 | 在 6000.7+ 上使用工作流 B。在 6000.6 上,说明此功能不可用 | 6000.7 之前不存在 State Machine API |
| 在节点中添加额外 UI | 在 6000.7+ 上使用 NodeView<MyNode>。在 6000.6 上,只能使用 USS、DefaultColor 和 FillAmount | NodeView<T> 在 6000.7 中加入 |
约束
- 成员名称必须与参考资料中的拼写完全一致:属性使用 PascalCase
(
FirstConnectedPort、IsConnected),特性使用位置构造函数参数,ID 使用Hash128。如果参考资料中未列出某个成员,请按下方的 URL 模式查询,不要猜测其名称或大小写,因为猜错的成员会导致编译失败,用户也无法分辨是拼写错误还是缺少功能。 - 链接到脚本参考或手册,而不是复述其中内容,这样文档更新时答案仍然准确。
- 不要在运行时程序集里引用
Unity.GraphToolkit.Editor,并将可视化代码放在#if UNITY_EDITOR下的运行时程序集中,因为该模块仅适用于 Editor,任何对它的引用都会导致 Player 构建失败。 - 保留用户现有的工具结构,不要重新设置样式或重组未提及的节点,因为节点和端口名称是导入器及已保存资源依赖的查找键。
- 不要将 Graph Toolkit 内部实现或私有类型作为变通方案,因为用户代码无法访问它们,而且它们可能随时更改。如果公共 API 无法实现某项功能,请说明这一点,并指向下方的社区讨论列表。
验证
- 阅读
references/pitfalls.md,并在向用户展示代码前对照检查。 - 项目编译无错误。如果 Unity CLI 可用,请用它进行构建或无头运行 Editor;否则请用户将 Editor 切到前台并反馈 Console 信息。
- 通过新建菜单项创建资源,双击打开,从 Add 菜单中添加每一种节点或状态类型,将它们连接起来,保存、关闭并重新打开。Console 中不得出现任何错误或警告。
- 如果有导入器,请选中资源,并确认生成的运行时对象是 Inspector 中的主资源。
3b. 如果编写了运行时代码(执行器、调试视图),请添加能够证明其行为的
Debug.Log行,例如记录进入的状态或执行的节点;进入 Play 模式并查看日志。确认行为后,将这些日志移除或加上条件编译保护。 - 重新阅读
references/pitfalls.md,并修复其中指出的所有问题。 - 如果某一步失败或 Console 报错,请返回并重读该步骤对应的参考文件和主题索引页面,然后重试;问题通常是遗漏了成员名称或版本限制。
最终报告
向用户提供一份简短清单:创建或修改了哪些文件以及它们的存放位置、如何创建并打开第一个资源、导入器会生成什么,以及代码面向哪个 Editor 版本。列出仍需用户决定或完成的事项,例如更多节点类型、运行时执行器或调试视图;如果没有可用的 Editor,也请说明哪些 Console 错误无法验证。
主题索引
优先使用 WebFetch,而不是 WebSearch;速度更快,也能直达准确页面。只获取所需内容。
获取页面前,先替换 <VERSION>:
docs.unity.com/en-us/engine/<VERSION>和docs.unity3d.com/<VERSION>/Documentation:项目的 Editor 版本,取自ProjectSettings/ProjectVersion.txt,例如6000.6或6000.7。docs.unity3d.com/Packages/com.unity.graphtoolkit-samples@<VERSION>:Package Manager 中显示的样例包 版本,例如0.6。
以下页面均位于 https://docs.unity.com/en-us/engine/<VERSION>/ 下;保留 .md
后缀时会以 Markdown 格式提供:
- 手册索引:
manual/extending-the-editor/gtk-index.md - 实现图工具:
manual/extending-the-editor/gtk-index/implementing-a-graph-tool.md,其 子页面包括该文件夹下的implement-a-graph-tool.md、implement-nodes.md、implement-node-options.md、implement-context-nodes.md、implement-block-nodes.md、type-cast-ports.md、add-custom-toolbar-actions.md、add-subgraph-support.md、graph-processing.md - 图窗口和面板:
manual/extending-the-editor/gtk-index/landing-graph-interface.md,其下有graph-window.md、blackboard.md、graph-inspector.md、minimap.md - Script Reference:
script-reference/unity/graphtoolkit/editor/<type>.md和.../<type>/<member>.md,全部使用小写,并省略泛型元数(Condition<T>对应condition.md)。 示例:script-reference/unity/graphtoolkit/editor/graph/ongraphchanged.md。状态机类型从6000.7开始提供。旧格式https://docs.unity3d.com/<VERSION>/Documentation/ScriptReference/Unity.GraphToolkit.Editor.<Type>.html也可访问,格式为 HTML。 - 样例:
https://docs.unity3d.com/Packages/com.unity.graphtoolkit-samples@<VERSION>/manual/index.html。 在 Package Manager 中按名称安装com.unity.graphtoolkit-samples,然后导入 Texture Maker (导入器)、Visual Novel Director(自定义运行时和调试视图)或 Dungeon Graph Generator(通过代码构建图)。 - 社区:https://discussions.unity.com/tag/graph-toolkit