剧本方法(method())让你的扩展暴露一组可在剧本里调用的逻辑。创作者在剧本里通过「调用扩展方法」action block 选中你的方法、填好参数,剧本执行到那里时引擎就会调用你定义的 run

声明方法

Extension 子类上用 static 方法名 = method({ ... }) 声明:
import { Extension, extension, method } from "@avg-studio/sdk";

@extension({ id: "rewards", label: "奖励系统" })
export class RewardSystem extends Extension {
  static giveGold = method({
    title: "发放金币",
    description: "给玩家加金币并弹出提示",
    schema: {
      amount: { type: "number", label: "金币数", default: 100 },
    },
    run(ctx, params) {
      const cur = ctx.variables.get<number>("gold") ?? 0;
      ctx.variables.set("gold", cur + params.amount);
    },
  });
}
一个类可以声明多个方法。声明好后,创作者在剧本里按 Tab 选「调用扩展方法」,就能在选择器里找到「奖励系统 → 发放金币」并填参数。

method() 字段

字段必填说明
title方法选择器里的显示名
description简要说明
idkebab-case 方法 id,缺省时从属性名 kebabize 推导(addItem"add-item"
schema参数定义,Studio 自动生成填参表单
run执行体,剧本走到这里时调用
runImmediately立即生效版本,详见下文
skip玩家快进时的简化行为,详见下文

参数 schema

schema 定义方法接受的参数。Studio 根据 schema 在「调用扩展方法」action block 的属性面板里自动生成对应的编辑控件:
类型编辑器控件字段配置
string文本输入框可配 multiline default required suggestions
number数字输入框可配 min max step default required
boolean开关可配 default required
enum下拉选择options: [{ label, value }] 必填,可配 default
asset素材选择器通过 assetType 限定:"image" "audio" "video" "any"
character角色选择器选项来自项目里的角色
characterPortrait立绘选择器值为角色的立绘 ID;可配 characterField 关联同 schema 里的角色字段并筛选立绘
scene场景选择器选项来自项目里的场景
fragmentFragment 选择器跳转目标用
variable变量选择器选已声明的变量
uiExtensionUI 扩展选择器选已注册的扩展模块
这套 schema 类型只用在 method().schema不要static settings = settings((s) => ({ ... })) 的 builder API 混淆——那是另一套 fluent 接口,类型集也不同(详见 设置 Schema)。
static playEffect = method({
  title: "播放特效",
  schema: {
    targetCharacter: {
      type: "character",
      label: "目标角色",
      required: true,
    },
    effectType: {
      type: "enum",
      label: "效果类型",
      options: [
        { label: "闪烁", value: "blink" },
        { label: "抖动", value: "shake" },
        { label: "渐隐", value: "fade" },
      ],
      default: "shake",
    },
    backgroundImage: {
      type: "asset",
      label: "可选背景图",
      assetType: "image",
    },
  },
  run(ctx, params) {
    // params.targetCharacter: string
    // params.effectType: "blink" | "shake" | "fade"
    // params.backgroundImage: string
  },
});
enum 字段的 params 类型会被推导成选项值的字面量联合,其他字段(含 asset、character、scene 等)的 params 类型都是 string

文本参数候选值

v1.9.0 新增
单行 string 参数可以声明 suggestions,让检查器显示「可自由输入 + 候选下拉」控件。同一扩展中使用相同 key 的参数会共享候选池,Studio 会从当前项目剧本里收集这些参数已经填写过的值。
static sendMessage = method({
  title: "发送消息",
  schema: {
    contact: {
      type: "string",
      label: "联系人",
      required: true,
      suggestions: {
        key: "contact",
        includeCharacterNames: true,
      },
    },
    content: {
      type: "string",
      label: "内容",
      multiline: true,
    },
  },
  run(ctx, params) {
    // params.contact 仍是普通 string
  },
});
字段说明
key扩展内的候选池名称。多个方法使用相同 key 时会复用彼此的历史值
includeCharacterNames设为 true 后,把项目角色名一并加入候选
候选值只改善编辑体验,不限制输入内容,也不改变运行时参数类型。多行文本不建议声明候选值。

run、runImmediately、skip

方法何时被调用
run剧本正常播放到该方法。可以是 async,引擎会等它返回再走下一个块
runImmediately该方法的副作用需要”立即”发生、不等待动画
skip玩家按住 Ctrl 快进剧本时调用。一般写”放结果但不放动画”的简化版
runImmediatelyskip 都是可选的,不提供时引擎会 fallback 到 run
static fadeToBlack = method({
  title: "渐黑",
  schema: { duration: { type: "number", label: "时长(ms)", default: 800 } },

  // 正常播放:跑完淡入动画
  async run(ctx, params) {
    await ctx.curtain.fadeIn({ duration: params.duration });
  },

  // 玩家快进:直接黑屏,不等动画
  skip(ctx) {
    ctx.curtain.show();
  },
});

在 run 里访问 this.save

method()run 回调里,this 类型是 ExtensionBase——TypeScript 没办法把方法定义和外层类的 saveSchema 关联起来,this.save 拿到的是 EmptySaveAPI,任何 key 都报错。 正确做法是显式声明存档形状然后收窄:
import type { SaveAPI } from "@avg-studio/sdk";

type InventorySaveMap = {
  items: readonly string[];
  gold: number;
};

@extension({ id: "inventory", label: "背包" })
export class Inventory extends Extension {
  static saveSchema = defineSave({
    items: { type: "list", persistence: "slot", default: [] as string[] },
    gold:  { type: "number", persistence: "slot", default: 0 },
  });

  static addItem = method({
    title: "增加物品",
    schema: { itemId: { type: "string", label: "物品 ID", required: true } },
    run(_ctx, params) {
      const save = this.save as unknown as SaveAPI<InventorySaveMap>;
      save.set("items", [...save.get("items"), params.itemId]);
    },
  });

  static addGold = method({
    title: "增加金币",
    schema: { amount: { type: "number", label: "数量", default: 100 } },
    run(_ctx, params) {
      const save = this.save as unknown as SaveAPI<InventorySaveMap>;
      save.set("gold", save.get("gold") + params.amount);
    },
  });
}
引擎内置的 gallery-screen 就用这个模式。详见 存档 Schema

方法跟界面联动

方法改了状态、界面要实时刷新——首选 React Hook 路径,不要绕 ctx.subscribe
// 界面里订阅存档/变量
function GalleryComponent() {
  const ctx = useExtensionContext();
  const [items] = ctx.variables.useValue("gold");
  // gold 变化时组件自动重渲染
  return <div>金币:{items}</div>;
}
ctx.variables.useValuethis.save.useValue 在底层都订阅了对应的 zustand store,比手动 ctx.subscribe("variable:changed", ...) 更地道。命令式 subscribe 主要给非 React 上下文(比如 static onRegister)用,并且它的 handler 签名是 () => void——不接收参数,需要数据时主动 ctx.variables.get() 读。
方法都是 static——它们附在类上,但每次调用引擎会 new 一个新实例来跑,this 上的临时字段不持久。要持久状态只能放 this.save(随存档)或 ctx.variables(剧本变量)。