| name | yokiframe-editor |
| description | YokiFrame 编辑器工具开发 Skill。当用户需要在 YokiFrame Tools Panel 中新增编辑器工具页、 创建 EditorWindow、开发 Unity 编辑器扩展、使用 EditorEventCenter/EditorDataBridge/ReactiveProperty、 或遵循 YokiFrame 编辑器 UI 规范(UI Toolkit + USS BEM + ToolPage 模式)时触发。 编辑器代码禁止使用运行时 EventKit,必须使用 EditorEventCenter。 |
YokiFrame 编辑器开发 Skill
首要原则 [MANDATORY]
编辑器代码 ≠ 运行时代码
编辑器事件用 EditorEventCenter(非 EventKit)
UI 用 UI Toolkit + USS(非 IMGUI)
数据驱动用响应式订阅(非 OnUpdate 轮询)
依赖: 编辑器层无需外部包(UI Toolkit 内置)。若编辑器页面需访问运行时数据,
注意运行时的软依赖宏(YOKIFRAME_UNITASK_SUPPORT 等),参考 yokiframe Skill 的依赖表。
一、编辑器事件系统
编辑器禁止使用运行时 EventKit(PlayMode 切换时事件残留)。全部改用 EditorEventCenter。
EditorEventCenter — 编辑器专用事件
using YokiFrame.EditorTools;
EditorEventCenter.Register<RefreshEvent>(this, OnRefresh);
EditorEventCenter.Send(new RefreshEvent { Data = value });
EditorEventCenter.Unregister<RefreshEvent>(this);
EditorEventCenter.Register<EditorEventType, string>(this, EditorEventType.DataChanged, OnDataChanged);
EditorEventCenter.Send(EditorEventType.DataChanged, "new data");
EditorEventCenter.Unregister<EditorEventType>(this);
EditorDataBridge — 数据通道订阅
用于跨页面/跨窗口的数据推送,替代轮询。
EditorDataBridge.Publish(DataChannels.CHANNEL_POOL_LIST_CHANGED, poolList);
protected override void OnActivate()
{
base.OnActivate();
Subscriptions.Add(
EditorDataBridge.Subscribe<List<PoolDebugInfo>>(
DataChannels.CHANNEL_POOL_LIST_CHANGED,
OnPoolListChanged));
}
ReactiveProperty / ReactiveCollection
响应式属性 — 值变化自动触发 UI 更新。
var count = new ReactiveProperty<int>(0);
count.Subscribe(v => label.text = $"Count: {v}");
count.Value = 10;
var items = new ReactiveCollection<string>();
items.OnItemAdded += item => AddRow(item);
items.OnItemRemoved += item => RemoveRow(item);
items.Add("new entry");
private readonly CompositeDisposable mSubscriptions = new();
mSubscriptions.Add(count.Subscribe(OnCountChanged));
二、ToolPage 系统
注册页面
通过 [YokiToolPage] 特性声明,TypeCache 自动发现,无需手动注册。
using YokiFrame.EditorTools;
namespace YokiFrame
{
[YokiToolPage(
kit: "MyKit", // 所属 Kit 名
name: "我的工具", // 侧边栏显示名
icon: KitIcons.CODE, // 图标 ID
priority: 50, // 排序(越小越前)
category: YokiPageCategory.Tool)]
public partial class MyToolPage : YokiToolPageBase
{
protected override void BuildUI(VisualElement root)
{
var scaffold = CreateKitPageScaffold(
title: "我的工具",
summary: "工具描述",
iconId: KitIcons.CODE);
scaffold.Toolbar.Add(CreateToolbarPrimaryButton("操作", DoAction));
scaffold.Toolbar.Add(CreateToolbarButton("刷新", Refresh));
var (card, body) = CreateCard("数据面板");
body.Add(new Label("内容..."));
scaffold.Content.Add(card);
root.Add(scaffold.Root);
}
protected override void OnActivate()
{
base.OnActivate();
Subscriptions.Add(EditorDataBridge.Subscribe<...>(...));
}
}
}
生命周期
BuildUI(VisualElement root) → 构造 UI 树(仅一次)
OnActivate() → 页面激活,订阅数据
OnUpdate() → 可选轮询(尽量避免,用响应式替代)
OnDeactivate() → 页面失活,自动清理 Subscriptions
YokiPageCategory
| 值 | 分组 | 使用场景 |
|---|
Documentation = 0 | 侧边栏「文档」 | 帮助文档、使用指南 |
Tool = 1 | 侧边栏「工具」 | 运行时调试、监控面板 |
System = 2 | 侧边栏「系统」 | 框架配置、全局功能 |
基类提供的 UI 组件
var scaffold = CreateKitPageScaffold(title, summary, iconId);
scaffold.Hero
scaffold.Toolbar
scaffold.StatusBar
scaffold.Content
var toolbar = CreateToolbar();
CreateToolbarPrimaryButton("主操作", onClick);
CreateToolbarButton("次操作", onClick);
CreateToolbarSpacer();
CreateToolbarToggle("开关", value, onChanged);
var (card, body) = CreateCard("标题");
var (card, valueLabel) = CreateKitMetricCard("标题", "数值", "提示");
var (panel, body) = CreateKitSectionPanel("标题", "描述");
var banner = CreateKitStatusBanner("标题", "消息", HelpBoxType.Info);
var strip = CreateKitMetricStrip();
CreateToolbarButtonWithIcon(KitIcons.REFRESH, "刷新", onClick);
CreateActionButtonWithIcon(KitIcons.DELETE, "删除", onClick, isDanger: true);
KitIcons 常用图标常量
CATEGORY_CORE, CATEGORY_TOOLS, EVENTKIT, POOLKIT, FSMKIT, SINGLETON, RESKIT, KITLOGGER,
ARCHITECTURE, FLUENTAPI, TOOLCLASS, UIKIT, AUDIOKIT, ACTIONKIT, SAVEKIT, SCENEKIT,
BUFFKIT, SPATIALKIT, INPUTKIT, LOCALIZATIONKIT, TABLEKIT
SETTINGS, CODE, DOCUMENT, FOLDER, REFRESH, COPY, DELETE, PLAY, PAUSE, STOP,
SUCCESS, WARNING, ERROR, INFO, SEND, RECEIVE, CLOCK, CHECK, GAMEPAD, KEYBOARD, TOUCH
三、独立 EditorWindow
如果页面需要独立窗口(不在 Tools Panel 内),继承 YokiMonitorWindowBase:
public class MyMonitorWindow : YokiMonitorWindowBase
{
protected override float RefreshIntervalSeconds => 1f;
protected override string MonitorKitName => "MyKit";
protected override void BuildMonitorUI(VisualElement root)
{
root.Add(new Label("监控内容"));
}
protected override void RefreshMonitorData() { }
protected override void OnMonitorEnabled() { }
protected override void OnMonitorDisabled() { }
}
[MenuItem("YokiFrame/MyKit/Monitor")]
public static void Open() => GetWindow<MyMonitorWindow>("MyKit Monitor");
简单 EditorWindow(不继承基类)直接使用 EditorWindow.GetWindow<T>() + CreateGUI()。
四、USS 样式规范 (BEM)
.yoki-{kit}-{block} → .yoki-pool-card
.yoki-{kit}-{block}__{element} → .yoki-pool-card__header
.yoki-{kit}-{block}--{modifier} → .yoki-pool-card--warning
文件位置: Core/Editor/UISystem/Styling/Kits/{KitName}/{KitName}.uss
UNITY_EDITOR 宏包裹所有编辑器代码。
五、禁止模式清单
| 禁止 | 替代 | 原因 |
|---|
编辑器用 EventKit.Type / EventKit.Enum | EditorEventCenter | PlayMode 切换事件残留 |
OnUpdate() 轮询刷新 UI | EditorDataBridge.Subscribe() / ReactiveProperty | 性能浪费 |
style.xxx = new StyleColor(...) | AddToClassList("class-name") | USS 样式类复用 |
Q<T>() 每次调用 | QueryCached<T>() | 缓存查询结果 |
IMGUI (OnGUI) | UI Toolkit (CreateGUI / BuildUI) | YF3 规范 |
| 硬编码色值/字号 | USS 样式类 | YF4 声明式分离 |
| Emoji 字符 | KitIcons 图标常量 | YF6 禁用 Emoji |
六、目录结构约定
Core/Editor/ToolsWindow/Pages/Kits/{KitName}/
├── Page/
│ ├── {KitName}ToolPage.cs ← 主入口(partial class)
│ ├── {KitName}ToolPage.UI.cs ← UI 构建细节(可选)
│ └── {KitName}ViewModel.cs ← 响应式 ViewModel(可选)
├── Bridge/
│ └── {KitName}EditorChannelProvider.cs ← 数据通道发布
└── Diagnostics/
└── {KitName}Debugger.cs ← 运行时数据采集
Core/Editor/UISystem/Styling/Kits/{KitName}/
└── {KitName}.uss ← Kit 独用样式