Skip to content

专业地管理游戏数据:从 JSON 开始

想象一下,你正在制作一个大型 RPG 游戏,里面有成百上千种装备、道具和怪物。

起初,你可能会图方便,把这些数据直接写在代码里:

typescript
function getItemData(itemId: string) {
  if (itemId === "item_001") {
    return { name: "治疗药水", effect: "恢复50点生命" };
  }
  if (itemId === "item_002") {
    return { name: "力量卷轴", effect: "临时增加10点攻击" };
  }
  // ... 此处省略几百个 if/else ...
}

这种“硬编码”的方式,在项目初期似乎没什么问题。但随着项目规模的扩大,这会变成一场灾难:

  • 难以修改:每次你想调整一个道具的效果,都得在成千上万行代码里找到那一小行。
  • 容易出错:当道具数量多起来时,复制粘贴很容易出错,比如把 effect 错写成 effcet
  • 无法协作:你无法让不懂编程的游戏策划帮你配置和调整数值。

要解决这个问题,我们需要引入一种专业的开发思想:数据与逻辑分离。让代码只负责“如何使用道具”,而道具的具体信息,则存放在一个专门的“数据仓库”里。

这个“数据仓库”,最常用的格式就是 JSON

阶段一:用 JSON 建立你的“数据仓库”

JSON 是一种轻量级的、专门用来存储数据的文本格式。它非常易于阅读和编写。我们的目标是:把所有道具的数据,都从代码里挪到一个 items.json 文件中。

  1. 在你的 src 目录(或者你认为合适的任何地方),创建一个新的 gamedata 文件夹。
  2. gamedata 文件夹里,新建一个 items.json 文件。

现在,我们把道具数据写进去:

json
// src/gamedata/items.json
[
  {
    "id": "item_001",
    "name": "治疗药水",
    "description": "一瓶能治愈伤口的红色药水。",
    "effect": "heal",
    "value": 50
  },
  {
    "id": "item_002",
    "name": "力量卷轴",
    "description": "使用后能感受到无穷的力量。",
    "effect": "buff_attack",
    "value": 10
  },
  {
    "id": "item_003",
    "name": "新手村地图",
    "description": "妈妈再也不用担心我迷路了。",
    "effect": "none",
    "value": 0
  }
]

看,是不是非常清晰?你可以轻松地在这里添加、修改或删除道具,而无需触碰任何一行游戏逻辑代码。

阶段二:用 Interface 为“数据仓库”上一把“安全锁”

现在我们有了数据,但还有一个至关重要的问题:如何在代码里安全地使用它呢?

  • 如果我不小心把 value 理解成了“价格”,但它实际上是“治疗量”,怎么办?
  • 如果策划在配置时不小心把 effect 写成了 effects,程序怎么发现?

这就是 TypeScript Interface 发挥作用的地方。我们可以为我们的道具数据创建一个“数据契约”,来为这个“仓库”装上一把“安全锁”。这份契约会严格规定:嘿,所有存入仓库的道具,都必须包含 id, name, description, effect, value 这几个属性,而且它们的类型也必须是规定好的!

src 目录下创建一个 types.ts 文件(如果还没有的话),然后写入以下内容:

typescript
// src/types.ts

/**
 * 代表游戏中的一个道具的数据结构。
 * 每一条 JSDoc 注释都会在后续的开发中给你带来极大的便利。
 */
export interface ItemData {
  /** 道具的唯一标识符,不允许重复。 */
  readonly id: string;

  /** 道具在游戏中显示的名字。 */
  readonly name: string;

  /** 道具的描述文本。 */
  readonly description: string;

  /**
   * 道具的效果类型。
   * 'heal': 治疗
   * 'buff_attack': 攻击力增益
   * 'none': 无特殊效果
   */
  readonly effect: "heal" | "buff_attack" | "none";

  /**
   * 与效果相关联的数值。
   * - 当 effect 为 'heal' 时,这是治疗量。
   * - 当 effect 为 'buff_attack' 时,这是增加的攻击力。
   */
  readonly value: number;
}

这份 interface 就是我们给“数据仓库”上的那把“安全锁”,它不仅定义了每个字段的类型,还通过注释和字面量联合类型 ('heal' | 'buff_attack'),清晰地定义了每个字段的含义

阶段三:在代码中,安全地打开“上了锁的仓库”

ArenaPro 的开发环境允许你直接导入 JSON 文件,就像导入一个普通的 TypeScript 模块一样。现在,我们可以把所有东西都串起来了。

typescript
// server/src/itemManager.ts

// 1. 同时导入 JSON 数据和它的“数据契约”
import allItems from "../gamedata/items.json";
import type { ItemData } from "../types";

// 2. 创建一个道具数据库,方便通过 ID 快速查找
//    我们用 Map 来存储,键是道具 ID,值是道具数据。
const itemDatabase = new Map<string, ItemData>();

// 3. 遍历导入的 JSON 数组,填充数据库
//    我们在这里使用 `as ItemData[]` 进行类型断言,
//    意思是告诉 TypeScript:“请相信我,这个 allItems 数组里的每个成员,都符合 ItemData 这个契约”。
//    如果 JSON 文件中的某个字段写错了(比如把 name 写成了 Name),TypeScript 在这里可能不会报错,
//    但在后续使用时,错误依然会被捕捉到。
for (const item of allItems as ItemData[]) {
  itemDatabase.set(item.id, item);
}

// 4. 创建一个函数来获取道具数据
export function getItem(itemId: string): ItemData | undefined {
  return itemDatabase.get(itemId);
}

// 5. 在其他地方调用它,并享受“安全锁”带来的好处
const potion = getItem("item_001");

// 当你输入 `potion.` 时,VS Code 会自动提示 name, description, effect, value 等属性!
if (potion) {
  console.log(`你使用了:${potion.name}`);

  // 你可以放心地使用 value,因为你知道在 effect 为 'heal' 时它代表治疗量
  if (potion.effect === "heal") {
    player.hp += potion.value;
  }

  // 如果你把 value 写成 potion.Value(大小写错误),
  // 或者把 effect 的判断写成 if (potion.effect === 'heall')(拼写错误),
  // TypeScript 会立刻划出红线警告你,在运行游戏前就帮你避免了一个 Bug!
}

总结

通过将游戏数据剥离到 JSON 文件中,并结合 TypeScript 的 interface,你获得了一套专业、强大且易于维护的工作流:

  • 数据与逻辑分离:游戏数值的调整变得极其简单,甚至可以交给不懂编程的策划来完成。
  • 代码更整洁:告别了代码中成堆的、无法维护的 if/else
  • 极致的类型安全:在 interface 的“安全锁”保护下,你可以在代码中放心地使用这些数据,享受 IDE 带来的自动补全和实时错误检查,大幅减少因数据不一致或拼写错误导致的 Bug。

现在,就去把你的游戏数据从代码中解放出来吧!

神岛实验室