自定义检查器用 React 替换某个扩展方法的自动参数表单,比如在文本框下面加实时预览,或者把列表参数做成一行一张卡片。
方法的 schema 仍决定参数清单,检查器只决定怎么编辑,存进剧本的内容和游戏拿到的参数都不变。
写检查器前,先读 Studio 组件包里的「两个入口」和「构建」。
这一页讲的是替换方法的参数表单。想把扩展界面里的内容放进检查器面板,见动态扩展检查器。
显示在哪里
同一个检查器组件会出现在三处:
| 位置 | placement | 宽度 | 能否用变量 |
|---|
| 剧本「调用扩展方法」指令的检查器面板 | "inspector" | 窄 | 能 |
条件判断里扩展方法的参数对话框(只能选到声明了 returns 的方法) | "dialog" | 宽 | 能 |
| 界面元素「点击时 → 调用扩展方法」,在界面编辑器里 | "ui-editor" | 窄 | 不能,界面动作只收固定值 |
没有参数的方法不显示检查器。
声明检查器
src/studio.tsx 导出 inspectors,键是方法 id,值是组件:
export const inspectors = {
"show-banner": ShowBannerInspector,
"grant-rewards": GrantRewardsInspector,
};
方法 id 和剧本里用的一致:method() 写了 id 就用它,否则是静态属性名转成短横线写法,addItem 对应 add-item。
对不上任何方法的键会在扩展日志里提示。值不是组件时(比如 lazy() 或元素),Studio 继续显示自动表单,并在扩展日志里说明原因。
完整示例
运行时入口声明方法:
// src/index.tsx
static showBanner = this.method({
title: "显示横幅",
schema: {
text: { type: "string", label: "文字" },
tone: {
type: "enum",
label: "语气",
default: "info",
options: [
{ label: "普通", value: "info" },
{ label: "警告", value: "warning" },
],
},
seconds: { type: "number", label: "秒数", default: 3, min: 1, max: 10 },
},
run(ctx, params) {},
});
编辑期入口换一套编辑界面:两行现成字段,加一行预览,其余参数仍交给自动表单。
// src/studio.tsx
import {
AutoParams,
FieldRow,
ParamField,
Section,
useInspectorEnvironment,
useParam,
} from "@avg-studio/studio-ui";
/** 实时预览:读两个参数。文字绑定变量时,运行时才知道内容。 */
function PreviewRow() {
const { placement } = useInspectorEnvironment();
const [message, , messageInfo] = useParam<string>("text");
const [tone] = useParam<string>("tone");
return (
<FieldRow label="预览">
<div
style={{
padding: placement === "dialog" ? "10px 14px" : "6px 10px",
borderRadius: 6,
border: "1px solid var(--border-subtle)",
borderLeft: tone === "warning" ? "3px solid var(--accent)" : "1px solid var(--border-subtle)",
background: "var(--bg-subtle)",
color: messageInfo.isVariable ? "var(--fg-tertiary)" : "var(--fg-primary)",
fontSize: 12,
}}
>
{messageInfo.isVariable ? "游戏运行时由变量决定" : message || "(还没有文字)"}
</div>
</FieldRow>
);
}
function ShowBannerInspector() {
return (
<>
<ParamField name="text" />
<ParamField name="tone" />
<PreviewRow />
<Section title="更多">
<AutoParams exclude={["text", "tone"]} />
</Section>
</>
);
}
export const inspectors = { "show-banner": ShowBannerInspector };
FieldRow 和 ParamField 自带留白,直接一行接一行排列即可。给卡片和其他块留间距时才用 Panel 或 Stack。
studio-ui/templates/ 里还有两份可以直接参考的模板:simple-inspector.tsx(现成行、一键填写和实时预览)和 list-param-inspector.tsx(列表参数,每行一张卡片,可以排序)。
方法参数上下文
下面这些工具读写的是正在编辑的这条调用,只能在检查器组件里使用。参数名就是 schema 的键,不加前缀;写错时扩展日志会提示。
useParam(name)
const [count, setCount, info] = useParam<number>("count");
| 返回值 | 说明 |
|---|
count | 当前值。没填时是默认值;绑定变量时为空,看 info.isVariable |
setCount(next) | 检查后作为固定值写入(会替换变量绑定)。通过返回 true,被拒返回 false,原因写进扩展日志。不传值表示清空,让默认值生效;null 不算清空 |
info.isSet | 这条调用是否写了这个参数 |
info.defaultValue | 声明的默认值;列表没写默认值时是空列表 |
info.isVariable / info.variable | 是否绑定了变量、变量名 |
info.isInvalid / info.reason | 存的值不符合清单,或参数声明本身写坏了;此时读到默认值,reason 说明原因 |
info.rawValue | 存储里的原值 |
info.reset() | 清空参数,等同 setCount() 不传值 |
读到的对象和数组是冻结的,原地修改会直接报错。要改就构造新值再写入。
写入前会按参数清单检查类型、选项、数字范围、列表行结构与行数、任意数据的 validate,并且只能是 JSON 能保存的值。撤销和重做照常工作。
要把没填或不符合清单的参数改成默认值,用 reset()。原样写回读出的值不会改变它们。
和自动表单口径相同的细节:
- 数字、开关、下拉在自动表单里清空时存成空字符串。此时读到默认值,
rawValue 是空字符串,方法的 run 收到的也是空字符串。
- 位置参数读写的都是画面上的坐标。选了内置位置预设时存的是相对预设的偏移,原值看
info.rawValue。
- 场景参数在自动表单里选场景时,还会记下快照字段
_sceneLayers、_sceneLighting、_sceneName。useParam 只写场景 id,方法要读这些字段时,请用 ParamField 编辑场景参数。
useParams()
const [values, setParams] = useParams<{ count: number; bonus: number }>();
// 只算一步撤销
setParams({ count: 10, bonus: 2 });
// 同一次写入里清空一个参数
setParams({ count: 10, bonus: void 0 });
values 的读取口径和 useParam 相同,同样是冻结的。绑定了变量的参数,以及没填又没有默认值的参数,不在 values 里,判断变量绑定请用 useParam 的 info.isVariable。
setParams(patch) 只算一步撤销。任何一个值没通过检查就整体拒绝,一个都不写,并返回 false。
只把要改的参数放进 setParams,不要写成 setParams({ ...values, count: 5 })。唯一的例外是移动位置参数时,要带上共用同一预设的其他位置参数。
ParamField
放入某个参数的现成一行,和自动表单完全一样:标签、能用变量的地方自带「值 / 变量」切换、同款控件(列表和任意数据也一样)。它遵守参数的 visibleWhen,条件不满足时不显示。
<ParamField name="contact" />
以下名字画不出单独一行,会在原位显示一行红字:
| 名字 | 改法 |
|---|
| 清单里没有的名字 | 日志里列出了可用的参数名 |
位置参数的锚点(anchorField 指向的参数) | 改放那个位置参数 |
清单没声明、由 Studio 顺带填写的辅助参数(chapterField、displayNameField、mirrorFields 指向的名字) | 放填写它的那个参数 |
AutoParams
整段放入自动参数表单,保留方法声明的分组和页签。
| 参数 | 说明 |
|---|
include | 只放这些参数,不写就是全部 |
exclude | 排除这些参数 |
只有不写 include,且 exclude 不写或为空数组时,才会带上方法的参数预设工具条。
useInspectorEnvironment()
const { placement, canUseVariables, readOnly } = useInspectorEnvironment();
| 字段 | 说明 |
|---|
placement | "inspector"、"dialog" 或 "ui-editor" |
canUseVariables | 这里的参数能否绑定变量,界面点击动作里是 false |
readOnly | 只读:这一句正被其他协作成员编辑、剧本编辑器处于只读,或界面正被别人编辑 |
MethodParams
只用于类型标注:从方法声明推导参数类型,和 run 拿到的一致。用 import type 引入扩展类,编辑期入口就不会把游戏运行时代码打包进来。
import { Button, useParam, type MethodParams } from "@avg-studio/studio-ui";
import type { Shop } from "./shop";
type GrantParams = MethodParams<typeof Shop.grant>;
function RewardsInspector() {
const [rewards, setRewards] = useParam<GrantParams["rewards"]>("rewards");
return (
<Button onClick={() => setRewards([...(rewards ?? []), { name: "金币", count: 1 }])}>
添加奖励
</Button>
);
}
联动写参数
写入时会像自动表单一样带上联动,自己在同一次写入里写了的参数以写入的值为准:
| 写入的参数 | 顺带更新 |
|---|
| 数字参数 | 复制到 mirrorFields 指向的参数 |
| 片段参数 | 填写 chapterField 指向的章节 |
| 变量参数 | 填写 displayNameField 指向的显示名 |
| 带预设的位置参数 | 移动后把预设切到自定义 |
几个位置参数共用同一个预设时,移动其中一个会把预设切成自定义,没写的那些坐标可能跳动。要一起移动时用 setParams,并原样带上没改的位置:
const [values, setParams] = useParams<{ from: string; to: string }>();
setParams({ from: nextFrom, to: values.to });
写入规则
-
只响应用户操作:不要在挂载时写参数。
-
只读时写不进去:
readOnly 为 true 时写入一律被拒,建议把控件置灰。
function QuickTextRow() {
const { readOnly } = useInspectorEnvironment();
const [, setMessage] = useParam<string>("text");
return (
<FieldRow label="快捷文字" disabled={readOnly}>
<Button onClick={() => setMessage("已保存")}>已保存</Button>
</FieldRow>
);
}
-
列表和任意数据只收固定值:它们没有「值 / 变量」切换。
-
useSetting 也能用:它读写的是扩展的项目设置,不是这条调用的参数。
-
useDataCollection 也能用:它维护的资料由整个工程共用,保存后影响所有读取它的指令,剧本的撤销也不会撤回它,详见项目数据库 API。
界面点击动作
在界面编辑器里,参数区标题行右侧可以切换「表单」和「JSON」:
- 参数里有
{{item.id}} 这类数据列表占位符,或 JSON 写坏、不是对象时,默认显示 JSON。
- JSON 写坏时要先修好,才能切到表单。
- 找不到方法或方法没有参数时,没有切换按钮,只有 JSON。
出错与刷新
| 情况 | Studio 怎么处理 |
|---|
| 还没构建或正在加载 | 显示自动表单 |
| 编辑期入口加载失败 | 参数区上方提示「这个扩展的自定义检查器没能加载,先显示自动表单。」,右侧有「重试」。把鼠标停在提示上可以看到原因,ext_reload 的 studioEntry.error 里也有,但不写进扩展日志 |
| 检查器渲染时抛错 | 参数区显示「自定义检查器出错了」,提供「显示自动表单」和「重试」。错误写进扩展日志 |
| 选了「显示自动表单」 | 这个方法先显示自动表单,直到加载了扩展的新构建、打开别的工程,或点击「重新加载自定义检查器」 |
| 重新构建 | Studio 自动重新加载检查器 |