我们写一个迷你的好感度系统,给剧本提供「增加好感度」方法,给玩家一个浮起来的小面板看每个角色的好感度。 整份代码 60 行左右,一屏看完。
新建扩展默认只有可视化界面能力。开始这个例子前,先在扩展树中选中「程序」,点击「初始化程序」,再把下面代码写入 src/index.tsx

完整代码

import React from "react";
import {
  Extension, extension, settings, defineSave, method,
  useExtensionContext,
  type SettingsBuilder, type SaveAPI,
} from "@avg-studio/sdk";
import manifest from "../extension.json";

type Entry = { characterId: string; value: number };
type SaveMap = { values: readonly Entry[] };

// 界面层订阅存档字段时,变量真名是 `${manifest.id}.[字段名]`
// manifest.id 是 Studio 创建扩展时生成的(带随机后缀),不能写死
const VAR_VALUES = `${manifest.id}.values`;

@extension({ id: "affection", label: "好感度" })
export class Affection extends Extension {
  static settings = settings((s: SettingsBuilder) => ({
    title: s.string("面板标题").default("好感度"),
  }));

  static saveSchema = defineSave({
    values: { type: "list", persistence: "slot", default: [] as Entry[] },
  });

  static add = method({
    title: "增加好感度",
    schema: {
      character: { type: "character", label: "角色", required: true },
      amount: { type: "number", label: "增减量", default: 1 },
    },
    run(_ctx, params) {
      const save = this.save as unknown as SaveAPI<SaveMap>;
      const list = save.get("values");
      const idx = list.findIndex((e) => e.characterId === params.character);
      const next = idx >= 0
        ? list.map((e, i) => i === idx ? { ...e, value: e.value + params.amount } : e)
        : [...list, { characterId: params.character, value: params.amount }];
      save.set("values", next);
    },
  });

  render() {
    return { component: AffectionPanel, props: {} };
  }
}

const AffectionPanel: React.FC = () => {
  const ctx = useExtensionContext();
  const [title] = ctx.settings.useValue<string>("title");
  const [raw] = ctx.variables.useValue(VAR_VALUES);
  const entries = (raw as unknown as readonly Entry[] | undefined) ?? [];

  return (
    <div style={{
      position: "absolute", top: "50%", left: "50%",
      transform: "translate(-50%, -50%)",
      padding: "24px 36px", borderRadius: 12,
      background: "rgba(20, 20, 30, 0.92)", color: "white",
      fontFamily: "sans-serif", minWidth: 260,
    }}>
      <h2 style={{ textAlign: "center", margin: 0 }}>{title}</h2>
      <ul style={{ listStyle: "none", padding: 0, margin: "20px 0" }}>
        {entries.map((e) => {
          const ch = ctx.character.get(e.characterId);
          return (
            <li key={e.characterId} style={{ display: "flex", justifyContent: "space-between", padding: "4px 0" }}>
              <span>{ch?.name ?? e.characterId}</span>
              <span>💗 {e.value}</span>
            </li>
          );
        })}
        {entries.length === 0 && (
          <li style={{ textAlign: "center", opacity: 0.5 }}>还没有任何记录</li>
        )}
      </ul>
      <div style={{ textAlign: "center" }}>
        <button onClick={() => ctx.ui.hide("affection")}>关闭</button>
      </div>
    </div>
  );
};

这 60 行里的扩展全貌

一个扩展类,里面装了四件事:
代码段它在做什么详细
@extension({ id, label })给这个扩展起个 id 和名字Extension 基类
static settings = settings(...)声明一个可在 Studio 配置的「面板标题」设置 Schema
static saveSchema = defineSave(...)声明一个跟随存档的「好感度列表」字段存档 Schema
static add = method(...)暴露一个剧本能调用的「增加好感度」方法剧本方法
render()返回一个 React 组件作为面板Extension 基类 › render

怎么用它

剧本端:在剧本里插一个「调用扩展方法」block,选「好感度 → 增加好感度」,挑角色 + 填数值。 玩家端:在剧本里插一个「显示界面」block 指向「好感度」就能弹出面板,或者你自己加个工具栏按钮触发 ctx.ui.show("affection")

想加更多东西

这份代码故意留得最简单。常见的「加点东西」:
  • 想给玩家按一个键就能打开面板 → 加 static onRegister 注册快捷键,看 系统插槽 › 内置动作
  • 想让总开关控制一组子设置 → 用 .enabledWhen(),看 设置 Schema › enabledWhen
  • 想跨周目共享数据 → 把 persistence: "slot" 改成 "shared"
  • 想在 UI 显示前截图、关闭后清理 → 加 onInit / onClose 实例钩子
把这些都堆在一份代码里,就是 实战范例 › 好感度的进阶写法 里的样子。从这份 Hello World 开始按需增量加,比一次看完所有特性容易得多。