最小样本
先看一个最小的模板组件样本,也就是这套模板系统里的 Hello World。
一个最小的模板组件样本,其实只需要三样东西:组件类、同名 UXML,以及一个负责挂载实例的宿主窗口。把这三样接起来,模板系统里最短的那条链就成立了:类 → 模板资源 → UI。本篇只看这条链,不引入状态、指令或结构更新。
对应到这个示例 app,目录结构如下:
TemplateBlogApp/
├── TemplateBlogAppWindow.cs
└── sfc/
├── TemplateBlogAppLayout.cs
└── TemplateBlogAppLayout.uxml
这里的组件资产只包含 cs + uxml。
最小模板组件
组件类本身保持在最小形态。它不手写构树,也不声明额外语义,只把自己放到模板装配链上:
using UnityEngine.UIElements;
using Zhujing.Editor.UI.Framework;
namespace Zhujing.Editor.UI.Examples.TemplateBlogApp
{
public class TemplateBlogAppLayout : TemplateElement
{
public new class UxmlFactory : UxmlFactory<TemplateBlogAppLayout, UxmlTraits> { }
public new class UxmlTraits : VisualElement.UxmlTraits { }
}
}
这个类只说明两件事:组件已经落到 TemplateElement 这一层,所以构造期会进入模板装配链;同时它自己不写任何构树代码,结构完全交给同名 UXML。
与之配对的模板文件同样很小:
<?xml version="1.0" encoding="utf-8"?>
<ui:UXML xmlns:ui="UnityEngine.UIElements">
<ui:VisualElement>
<ui:Label text="Hello World" />
</ui:VisualElement>
</ui:UXML>
这里只有一个 Label。这不是为了追求简陋,而是为了把问题收窄到最基本的判据:模板有没有被装配出来。只要窗口里能看到这句 Hello World,就说明“同名类 + 同名 UXML”的接驳已经成立。
宿主窗口只负责提供挂载位置。它不参与模板装配,只做 EditorApp.Create(this) 和 Mount(new TemplateBlogAppLayout()) 这两件事。这里的 EditorApp 是项目里的宿主脚手架,用来处理 EditorWindow 的挂载与释放;它不是模板系统本身的一部分。
using UnityEditor;
using UnityEngine;
using Zhujing.Editor.Core.Hosting;
namespace Zhujing.Editor.UI.Examples.TemplateBlogApp
{
public sealed class TemplateBlogAppWindow : EditorWindow
{
private EditorApp _app;
private TemplateBlogAppLayout _layout;
[MenuItem("Zhujing/Examples/UI/Template Blog App")]
public static void ShowWindow()
{
var window = GetWindow<TemplateBlogAppWindow>();
window.titleContent = new GUIContent("Template Blog App");
window.minSize = new Vector2(520f, 320f);
}
public void CreateGUI()
{
// Host scaffold only: mount the root element into the EditorWindow.
_app?.Dispose();
_app = EditorApp.Create(this);
_layout = new TemplateBlogAppLayout();
_app.Mount(_layout);
}
private void OnDisable()
{
_layout = null;
_app?.Dispose();
_app = null;
}
}
}
职责边界到这里已经很清楚:窗口层只拿根组件实例,真正把 Hello World 长出来的,是 TemplateElement 构造期内部那条“按类型找模板、再把模板树克隆出来”的链。
在这个版本的样本里,组件目录下还没有独立的同名 USS 资产,也没有额外加入代码侧样式定制;界面结果如下:

这个结果已经足够说明本篇的核心事实:模板组件不是靠手写 new Label() 长出来的,而是通过类与同名 UXML 自动接上的。
加上样式
结构接上之后,再看样式这一层。样式资产落在同名 USS 上。为了让样式有稳定的挂载点,UXML 这里给根节点和文字各加一个 class:
<?xml version="1.0" encoding="utf-8"?>
<ui:UXML xmlns:ui="UnityEngine.UIElements">
<ui:VisualElement class="template-blog-root">
<ui:Label text="Hello World" class="template-blog-title" />
</ui:VisualElement>
</ui:UXML>
对应的同名 USS 写法如下:
.template-blog-root {
flex-grow: 1;
align-items: center;
justify-content: center;
}
.template-blog-title {
font-size: 20px;
white-space: normal;
}
加入样式资产之后,目录结构变成:
TemplateBlogApp/
├── TemplateBlogAppWindow.cs
└── sfc/
├── TemplateBlogAppLayout.cs
├── TemplateBlogAppLayout.uxml
└── TemplateBlogAppLayout.uss
这份 USS 的作用很直接:根节点撑满可用空间,Label 被推到窗口中心,同时字号也从默认值抬高了一档。模板装配链没有任何变化,变化只发生在样式层。

小结
这个 Hello World 例子把模板组件最基本的几件事放在了一起:组件类提供类型入口,同名 UXML 提供结构,宿主窗口负责挂载,而同名 USS 只处理样式结果。模板装配与样式表达在这里已经分工清楚。
有了这个最小样本,后面的继承链、组件配方和 token 规格才有共同的落脚点。