☰
WarriorJS 自定义塔楼(Tower)开发指南:从零构建关卡与地牢
2026/9/25 16:07:00 网站建设 项目流程
  • 教育
  • CLI

【免费下载链接】warriorjs

🏰 An exciting game of programming and Artificial Intelligence

项目地址:https://gitcode.com/gh_mirrors/wa/warriorjs
点击查看免费下载

导读:本篇指南以 WarriorJS 官方 Maker 文档为主线,讲解如何用纯 JavaScript/TypeScript 对象定义一座可游玩的塔楼(Tower),包括塔楼骨架、关卡(Level)结构、地板(floor)布局、楼梯与 Warrior 摆放、敌人单位布置,以及能力配置与加载运行机制。读完本文,你将能独立编写出一座结构完整、可被 CLI 自动发现并加载的自定义塔楼,并理解其底层数据模型与运行原理。


1. Tower 的本质:一个模块,一个导出

在 WarriorJS 中,一座塔楼(Tower)就是一个普通的 JavaScript 模块,唯一的要求是导出一个塔楼定义对象:

module.exports = { // Tower definition. };

如果使用 TypeScript(如仓库内官方塔楼的做法),则对应导出一个类型为TowerDefinition的对象。从 libs/core/src/types.ts 可以看到该类型的完整结构:

export interface TowerDefinition { name: string; description: string; warrior: WarriorDefinition; // { maxHealth: number } levels: LevelDefinition[]; }

也就是说,一座塔楼由名称、简介、Warrior 默认属性和关卡列表四部分组成。先来定义名称和简介:

module.exports = { name: 'Game of Thrones', description: 'There is only one war that matters: the Great War. And it is here.', };

塔楼级 Warrior 默认值

注意上面的warrior: WarriorDefinition字段:它用于声明这座塔楼中 Warrior 的基础属性(目前只有maxHealth)。每个关卡里 Warrior 的最终属性,会在运行期由塔楼级默认值与关卡级覆盖值合并而成(详见第 8 节)。例如官方塔楼 towers/the-narrow-path/src/index.ts 中定义:

const tower: TowerDefinition = { name: 'The Narrow Path', description: 'A corridor of stone where the only way out is forward', warrior: { maxHealth: 20, }, levels: [ /* ... */ ], };

2. 关卡(Level):塔楼的每一层

关卡同样是一个 JavaScript 对象:

const Level1 = { // Level definition. };

对应源码中的LevelDefinition接口(libs/core/src/types.ts):

export interface LevelDefinition { description: string; tip: string; clue?: string; timeBonus: number; aceScore: number; floor: { size: Size; // { width, height } stairs: LocationConfig; // { x, y } warrior: WarriorOverrides; // { position, abilities?, maxHealth? } units: UnitConfig[]; // [{ unit, position, effects? }] }; }

先写上一段剧情描述(description)和一条通关提示(tip),帮助玩家理解目标:

const Level1 = { description: "You've entered the ancient castle of Eastwatch to escape from a blizzard. But it's deadly cold inside too.", tip: "Call `warrior.walk()` to walk forward in the Player's `playTurn` method.", };

3. 两个关键数值:timeBonus 与 aceScore

每个关卡还必须定义两个数字:

  • timeBonus(时间奖励):玩家通关越快,获得越多。它会在游戏过程中逐回合递减直到归零,是鼓励速通的核心参数。
  • aceScore(完美分数线):用于在epic 模式下计算关卡评级(grade)。得分大于或等于aceScore的玩家即可拿到S评级。
const Level1 = { description: "You've entered the ancient castle of Eastwatch to escape from a blizzard. But it's deadly cold inside too.", tip: "Call `warrior.walk()` to walk forward in the Player's `playTurn` method.", timeBonus: 15, aceScore: 10, };

这两个数值需要在试玩(play testing)时反复调优。本教程中给出的数值已经过调优,可直接使用。

从源码实现看,timeBonus与aceScore在 libs/scoring 包中被消费:getLevelScore负责把时间奖励与清场奖励合并成关卡总分,getGradeLetter依据aceScore计算评级。因此它们直接决定玩家的最终得分体验,是关卡设计的重要杠杆。

4. 定义地板(floor):尺寸、楼梯与 Warrior

4.1 地板尺寸

floor.size用width和height指定网格大小:

const Level1 = { description: "You've entered the ancient castle of Eastwatch to escape from a blizzard. But it's deadly cold inside too.", tip: "Call `warrior.walk()` to walk forward in the Player's `playTurn` method.", timeBonus: 15, aceScore: 10, floor: { size: { width: 8, height: 1, }, }, };

官方塔楼 towers/the-narrow-path/src/index.ts 前两关也采用了8×1的走廊布局——单行走廊是最适合新手关卡的地形。

4.2 摆放楼梯(stairs)

玩家需要站上楼梯才能进入下一层,因此必须给楼梯一个坐标:

const Level1 = { description: "You've entered the ancient castle of Eastwatch to escape from a blizzard. But it's deadly cold inside too.", tip: "Call `warrior.walk()` to walk forward in the Player's `playTurn` method.", timeBonus: 15, aceScore: 10, floor: { size: { width: 8, height: 1, }, stairs: { x: 7, y: 0, }, }, };

楼梯坐标使用x(列)与y(行)表示,原点(0, 0)在左上角。关卡是否通关,正是由 Warrior 是否踏上楼梯决定的——见 libs/core/src/Level.ts 中的判定逻辑:

wasPassed(): boolean { const stairsSpace = this.floor.getStairsSpace(); return stairsSpace.getUnit() === this.floor.warrior; }

4.3 定义 Warrior

接下来为关卡放置 Warrior。floor.warrior需要三个关键信息:角色字符(character)、最大生命(maxHealth)和初始位置(position):

const Level1 = { description: "You've entered the ancient castle of Eastwatch to escape from a blizzard. But it's deadly cold inside too.", tip: "Call `warrior.walk()` to walk forward in the Player's `playTurn` method.", timeBonus: 15, aceScore: 10, floor: { size: { width: 8, height: 1, }, stairs: { x: 7, y: 0, }, warrior: { character: '@', maxHealth: 20, position: { x: 0, y: 0, facing: 'east', }, }, }, };

position中的facing表示朝向,来自 libs/spatial 包的绝对方向常量(north/south/east/west)。官方塔楼中直接复用常量EAST、WEST以保证类型安全。

值得注意:文档示例把character和maxHealth写在每个关卡里,而源码中的WarriorOverrides类型则允许在塔楼级统一声明maxHealth、只在关卡级覆盖个别属性。两种写法皆可,推荐优先使用塔楼级默认值。

5. 布置敌人与单位(units)

要形成真正的关卡,还需要往地板上放敌人。第二个关卡加入了一只 Sludge(史莱姆),并让 Warrior 具备攻击与感知能力:

const Level2 = { description: 'The cold became more intense. In the distance, you see a pair of deep and blue eyes, a blue that burns like ice.', tip: "Use `warrior.feel().isEmpty()` to see if there's anything in front of you, and `warrior.attack()` to fight it. Remember, you can only do one action per turn.", clue: 'Add an if/else condition using `warrior.feel().isEmpty()` to decide whether to attack or walk.', timeBonus: 20, aceScore: 26, floor: { size: { width: 8, height: 1, }, stairs: { x: 7, y: 0, }, warrior: { character: '@', maxHealth: 20, position: { x: 0, y: 0, facing: 'east', }, }, }, };

这一关难度上升,我们引入了clue(线索)。线索是可选字段,玩家在需要时才会主动查看,用于给出更具体的解题思路。

units 数组的写法

在真实的仓库实现(towers/the-narrow-path/src/index.ts)中,敌人通过floor.units数组布置,每个元素由单位类与位置组成:

floor: { size: { width: 8, height: 1 }, stairs: { x: 7, y: 0 }, warrior: { abilities: { attack: Attack.with({ power: 5 }), feel: Feel, }, position: { x: 0, y: 0, facing: EAST }, }, units: [ { unit: Sludge, position: { x: 4, y: 0, facing: WEST }, }, ], }

units的每个元素对应UnitConfig:

export interface UnitConfig { unit: UnitClass; // 来自 @warriorjs/units 的单位类 position: PositionConfig; // { x, y, facing } effects?: Record<string, EffectEntry>; // 可选:绑定效果(如 Ticking 倒计时) }

敌方单位的facing决定了它的攻击方向——官方塔楼中敌人通常facing: WEST(面向 Warrior 来袭方向)。可用的内置单位包括 Sludge、ThickSludge、Archer、Captive、Wizard 等,详见 libs/units/src/index.ts。

6. 能力(abilities)的配置方式:Ability.with()

文档在第二关开始要求玩家使用warrior.attack()、warrior.feel()、warrior.walk(),此时塔楼作者必须在 Warrior 上显式注册这些能力,否则玩家无法调用。能力有两种写法(见 libs/core/src/Ability.ts 的AbilityEntry类型):

export type AbilityBinding = [AbilityClass, object]; export type AbilityEntry = AbilityBinding | AbilityClass;
  • 无参数能力:直接写类本身,如feel: Feel、walk: Walk、think: Think;
  • 带参数能力:用静态方法Ability.with(config)生成绑定,如attack: Attack.with({ power: 5 })。

参数化能力示例

以攻击为例,libs/abilities/src/Attack.ts 中:

static with(config: AttackConfig): AbilityBinding { return [Attack, config]; }

AttackConfig的power决定单次伤害;源码还展示了一个值得设计的细节——背身攻击只有一半伤害(Math.ceil(power / 2)),这为"后退迎敌"的策略关卡提供了惩罚机制。

再如回复能力 libs/abilities/src/Rest.ts,healthGain是 0~1 之间的小数,表示每次rest()恢复最大生命值的比例,实际恢复量按Math.round(maxHealth * healthGain)计算:

rest: Rest.with({ healthGain: 0.1 }), // 每次回复 10% 最大生命

射箭与视野类能力同样支持参数化(towers/the-narrow-path/src/index.ts):

look: Look.with({ range: 3 }), shoot: Shoot.with({ power: 3, range: 3 }),

更多能力的完整清单与参数说明,可参考 docs/maker/defining-abilities.md 与 docs/player/abilities.md。

能力随关卡渐进解锁的机制

从 libs/core/src/getLevelConfig.ts 的实现看,Warrior 的最终能力表会把从第 1 关到当前关所有关卡中声明的abilities逐层合并(epic 模式下则合并全部关卡):

const levels = epic ? tower.levels : tower.levels.slice(0, levelNumber); const warriorAbilities = Object.assign( {}, ...levels.map(({ floor: { warrior: { abilities } } }) => abilities || {}), );

这意味着:你不需要在每个关卡重复声明旧能力,只需在引入新能力的关卡中追加注册。这正是官方塔楼第 1 关只有think/walk、第 2 关才加入attack/feel的原因——能力是塔楼教学节奏的核心工具。

7. 把关卡挂载到塔楼

最后,将定义好的关卡放进塔楼的levels数组:

module.exports = { name: 'Game of Thrones', description: 'There is only one war that matters: the Great War. And it is here.', levels: [Level1, Level2], };

至此,一座最简单的双关卡塔楼就完成了!CLI 端会通过 apps/cli/src/loadTowers.ts 自动发现并加载塔楼:内置塔楼包名以@warriorjs/tower-开头,社区塔楼包名以warriorjs-tower-开头,两者都会从node_modules中被扫描,最终包装成 apps/cli/src/Tower.ts 中的Tower实例(提供getLevel(levelNumber)、hasLevel(levelNumber)等接口)。

8. 从定义到运行:配置合并与回合制执行

理解底层运行机制,有助于设计更严谨的关卡:

  1. 配置合并:运行关卡前,libs/core/src/getLevelConfig.ts 会deepClone塔楼定义,将tower.warrior(如maxHealth: 20)与关卡级floor.warrior合并,并注入玩家名称与累积能力表,生成一份不可篡改的LevelConfig。
  2. 回合制循环:libs/core/src/Level.ts 的play()默认最多执行200 回合,每回合先让所有单位prepareTurn()(准备),再依次performTurn()(行动),直到 Warrior 踏上楼梯(wasPassed)或死亡(wasFailed)。
  3. 边界判定:libs/core/src/Floor.ts 负责坐标越界检查(isOutOfBounds)与单位查询,楼梯、墙体和单位共同构成玩家眼中的地板地图。

9. 参考实现:官方塔楼 The Narrow Path

仓库自带的官方塔楼 towers/the-narrow-path/src/index.ts 是极佳的完整参考,它用 9 个关卡演示了完整的难度曲线设计:

关卡新能力新单位/机制关键布局
1think、walk空走廊8×1,楼梯在右端
2attack、feelSludge敌人facing: WEST
3health、maxHealth、rest多只 Sludge4 个敌人分散布置
4(无新能力)ThickSludge、Archer引入远程单位与"受伤检测"
5rescueCaptive引入营救机制与isBound()
6(无新能力)前后双向受敌教玩家'backward'方向参数
7pivot死胡同地形教玩家isWall()与转身
8look、shootWizard远程脆皮敌人,考验先手
9(无新能力)多单位混战最终综合考验

其package.json(towers/the-narrow-path/package.json)展示了塔楼包的标准依赖:@warriorjs/abilities、@warriorjs/core、@warriorjs/spatial、@warriorjs/units,并且keywords中包含warriorjs-tower——这正是 CLI 识别社区塔楼的关键标记。

10. 设计建议与试玩调优

  • 循序渐进的能力教学:利用"能力随关卡累积"的机制,把新能力当作新关卡的教学主题(参照第 9 节表格)。
  • 反复调优timeBonus与aceScore:文档明确指出这两个数字需要配合试玩反复微调——timeBonus太低会让新手挫败,太高则失去速通挑战;aceScore决定 S 评级的门槛,建议依据"熟练玩家最优解"的回合数反推。
  • 善用clue:线索只在玩家主动查看时显示,是给卡关玩家的第二层提示,不影响不求助玩家的体验。
  • 用facing控制难度:敌人朝向、Warrior 出生点与楼梯的相对位置,共同决定了玩家的行动顺序与受击面,这是不写一行"代码逻辑"就能改变关卡难度的手段。

11. 延伸阅读

  • docs/maker/creating-tower.md —— 本文主体来源:塔楼定义
  • docs/maker/adding-levels.md —— 本文主体来源:关卡与地板配置
  • docs/maker/defining-abilities.md —— 自定义能力的完整指南
  • docs/maker/defining-units.md —— 自定义单位的完整指南
  • towers/the-narrow-path/src/index.ts —— 官方塔楼完整实现
  • libs/core/src/types.ts —— Tower/Level/Unit 全部配置类型定义
  • 教育
  • CLI

【免费下载链接】warriorjs

🏰 An exciting game of programming and Artificial Intelligence

项目地址:https://gitcode.com/gh_mirrors/wa/warriorjs
点击查看免费下载
上一篇:Blender VRM 插件新手指南:4 步做出你的第一个 VRM 虚拟角色
下一篇:3步完成NCM文件解密:ncmdump免费开源工具把网易云音乐还原成MP3

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询