Extension 是写扩展的统一基类。一个 Extension 子类 = 一个完整的游戏系统——它可以同时拥有界面、剧本可调用的方法、自己的项目设置和需要持久化的存档数据。
按需声明四种能力:
| 你想要 | 怎么写 |
|---|---|
| 一个界面(背包面板、HUD、标题画面…) | 实现 render(),返回 { component, props } |
| 一个可被剧本调用的方法 | static 方法名 = method({ ... }) |
| 扩展自己的项目设置 | static settings = settings((s) => ({ ... })) |
| 玩家进度或本次启动的数据 | Extension.withSave(defineSave({ ... })) |
Extension 子类(标题、存读档、历史、设置、工具栏、鉴赏),是这套写法的完整范例。
最小例子
@extension 装饰器
@extension({ ... }) 声明扩展模块的身份信息。等价于写 static meta = meta({ ... }),前者更简洁。
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 模块标识符(kebab-case),在 ui-ref 路径里被引用,缺省时由 SDK 从 class 名推导 |
label | string | 显示名,Studio 在「显示界面」选择器和扩展设置面板里用 |
description | string | 简要说明 |
category | string | 分类(暂未使用) |
autonomous | boolean | 启动期是否自动注册——见下文「Autonomous 模式」 |
supportsSlot | string | string[] | 声明该模块实现哪个系统插槽,详见 系统插槽 |
静态钩子 vs 实例钩子
Extension 上的钩子分两类,性质完全不同:
static onRegister(模块级,启动期跑一次)
引擎在启动时对每个Extension 模块调用一次 static onRegister(ctx)——无论 autonomous 是否为 true。适合「整个游戏期间只需要做一次」的事:注册全局快捷键、订阅引擎事件、声明语义动作。
onInit / onShow / onClose(实例级,每次显示界面跑)
每次ctx.ui.show(uiId) 都会 new 一个实例。这三个钩子定义在 ExtensionBase 上,都是普通实例方法,按需实现:
| 钩子 | 时机 |
|---|---|
onInit() | 实例化、attach 完 host 之后,render 之前 |
onShow() | UI 变为可见 |
onClose() | UI 被关闭、实例销毁前 |
static onRegister 拿的是 ctx 参数,实例钩子里通过 this.context 访问 ctx——它们的作用域不同。
render 方法
render() 是实例方法,返回 { component, props }:
render() 的扩展模块会出现在「显示界面」action block 的选择器里。不实现 render() 就是一个纯方法/纯订阅型模块(比如只声明 method() 和 onRegister)。
实例属性
下面这些属性只在类的方法/钩子内部使用(this.xxx),不要从外部访问实例。
| 属性 | 说明 |
|---|---|
this.context | 当前扩展的 ExtensionContext(protected)。所有引擎 API 的入口 |
this.data | 从「显示界面」block 传入的 props 数据,由 schema 解析得到 |
this.save | 运行时数据读写代理。需继承 Extension.withSave(schema),详见下文 |
this.id | 该实例的运行时 id |
this.close() | 主动关闭这个 UI 实例(等价于外部 ctx.ui.hide(uiId)) |
this.context:
this.save 的强类型访问
用defineSave() 定义 Schema,再继承 Extension.withSave(schema)。同一份 Schema 会同时注册运行时字段,并为 this.save.get/set/useValue 提供 key 和 value 类型。
在类内部的钩子(onInit、render 等)可以直接访问 this.save。静态剧本方法使用 this.method(),也会保留当前扩展的实例类型,不需要手写类型或强制转换:
method()。需要读写 this.save、访问实例辅助方法或自定义 props 时,使用类上的 this.method()。详见 存档 Schema。
Autonomous 模式
@extension({ autonomous: true }) 的扩展模块在引擎启动后自动加载并显示,不需要剧本调用 ctx.ui.show()。适合常驻 UI:
- 对话工具栏(跟着对话显隐的底部按钮栏)
- 全局快捷键监听器
- HUD 元素(生命值、好感度提示等)
autonomous 改变的是加载策略——非 autonomous 模块也会跑 static onRegister,只是不会自动显示界面。
跟系统插槽配合
通过supportsSlot 声明该模块实现了哪个引擎内置 UI 槽位(标题、存档、设置等),就能替换 Default Shell:
完整范例:CG 鉴赏
这是引擎内置gallery-screen 的精简版——一个 Extension 子类同时包含界面、3 个剧本方法、1 个设置、两种存档作用域并存。当前完整实现还会声明 supportsSlot: INTERNAL_SYSTEM_SLOT.Gallery,因此可以在「游戏系统」中替换鉴赏界面。