剧本方法(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"
enabledWhen按扩展项目设置控制该方法是否出现在新建候选中
schema参数定义,Studio 自动生成填参表单
returns返回值契约;声明后可在 If 中作为条件来源,支持 booleannumberstring
run执行体,剧本走到这里时调用
runImmediately立即生效版本,详见下文
skip玩家快进时的简化行为,详见下文

按项目配置启用方法

方法不是每个项目都需要时,可以先声明一个扩展设置,再用 enabledWhen 引用它:
import { Extension, extension, method, settings } from "@avg-studio/sdk";

@extension({ id: "rewards", label: "奖励系统" })
export class RewardSystem extends Extension {
  static settings = settings((s) => ({
    enableAchievementReward: s
      .boolean("启用成就奖励方法")
      .default(false),
  }));

  static grantAchievementReward = method({
    title: "发放成就奖励",
    enabledWhen: "enableAchievementReward",
    schema: {
      achievementId: { type: "string", label: "成就 ID", required: true },
    },
    run(ctx, params) {
      // 发放奖励……
    },
  });
}
字符串写法读取当前 Extension 子模块里的同名设置。未显式保存配置时,Studio 会使用该设置的 default;切换开关后,方法选择器和 Tab 菜单会立即更新。 需要按枚举等非布尔值启用时,使用对象写法:
enabledWhen: { setting: "rewardMode", equals: "achievement" }
对象还支持 scope: "extension",用于读取扩展级设置。setting 包含 . 时会被视为扩展内的完整设置路径,可引用其他子模块,例如:
enabledWhen: { setting: "common.enableAdvancedMethods" }
enabledWhen 只控制这个方法是否能被新选择。已经写入剧本的方法仍然保留参数 Schema、可以编辑并正常执行;关闭开关不会让已有剧本断链。

参数 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

声明返回值

方法需要参与剧情条件判断时,用 returns 显式声明返回类型,并在 run 中返回对应的值:
static hasItem = method({
  title: "是否持有道具",
  description: "检查玩家是否至少持有一个指定道具",
  returns: { type: "boolean", label: "是否持有" },
  schema: {
    itemName: { type: "string", label: "道具名称", required: true },
  },
  run(ctx, params) {
    const items = ctx.variables.get<string[]>("inventoryItems") ?? [];
    return items.includes(params.itemName);
  },
});
支持的类型如下:
returns.typeIf 中的编辑方式
boolean选择「结果为真」或「结果为假」
number使用等于、大于、小于等数字算子
string使用等于、包含、开头是等文本算子
声明了返回值的方法会在方法列表中显示「返回 · 布尔 / 数字 / 文本」标记。只有这类方法会出现在 If 的扩展方法候选中;没有 returns 的动作方法不会混入条件列表。
If 与普通「调用扩展方法」使用完全相同的参数 Schema 和变量解引用规则。普通调用在右侧检查器中内联填写参数;If 为避免条件卡过长,会弹出参数窗口。两处保存的是同一种参数数据。
运行时返回值必须与 returns.type 一致。类型不匹配、方法缺失、扩展停用或方法抛错时,If 会把该条条件安全地视为不满足,并在控制台记录警告。

文本参数候选值

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 对于有返回值的方法,runImmediatelyskip 若存在,也应返回相同类型和语义的结果。普通「调用扩展方法」Block 会忽略返回值;If 会读取返回值完成比较。
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(剧本变量)。