mirror of
https://github.com/motajs/template.git
synced 2026-11-01 06:04:30 +08:00
- combat-overview/enemy-specials: 说明上下文重建时机(惰性、一般无需手动) - enemy-final/aura/query: 补「既依赖又改外部」不提供的原因(循环依赖) - enemy-specials: getSpecial<T> 泛型说明 + 特殊属性定义逐行注释与动态文案 - enemy-aura: 转换器/视图大白话,每条属性可对应不同转换器 - enemy-query: query() 必须调用 + 查询上下文改为只读 IReadonlyEnemyContext - 追加 10-11-READER-REVIEW.md 用户循环 batch1 记录
100 lines
6.8 KiB
Markdown
100 lines
6.8 KiB
Markdown
# 战斗总览
|
||
|
||
在游戏里点一下怪物,勇士走过去、挥几刀,屏幕上出现伤害和回合数。对玩家来说这只是一瞬间,但在数据端,它背后有一条固定的流水线:先准备一份属性准确的怪物,再把双方的属性换算成伤害,最后跑一遍战前与战后的规则。
|
||
|
||
本篇沿着这条流水线走一遍,让你知道每一步在做什么、该去哪篇文档看细节。
|
||
|
||
## 先准备一份「计算后怪物」
|
||
|
||
怪物数据里保存的是一份**模板**:基础属性(`hp`、`atk`、`def` 等),加上若干特殊属性(先攻、连击、光环……)。它描述的是「这只怪物本来是什么样」。
|
||
|
||
可战斗不能直接拿模板来算。光环会改周围怪物的属性,查询会根据周围情况调整自己,最终效果还会在收尾时再改一次。如果直接拿模板去算伤害,这些影响就全丢了。
|
||
|
||
所以进入战斗前,怪物上下文会把每只怪物**重建**成一份属性已经算准的副本,这就是「计算后怪物」。重建的入口是:
|
||
|
||
```ts
|
||
state.enemyContext.buildup();
|
||
```
|
||
|
||
`buildup()` 会按固定顺序处理每只怪物,把所有特殊属性的作用真正写进属性里。之后伤害计算器读到的就是最终结果,而不是模板上的原始数字。
|
||
|
||
### 什么时候重建
|
||
|
||
重建的时机由怪物上下文自己掌握,而且是**惰性**的:真正读取某只怪物的计算后属性时,上下文才会检查它需不需要重算。因此日常流程里,你一般**不需要**手动重建:
|
||
|
||
- 放置或移除怪物、注册光环转换器 / 查询效果 / 最终效果、绑定勇士,都会自动把上下文标记为「需要更新」;
|
||
- `buildup()` 是全量重建的入口,会把所有怪物的光环、查询与最终效果重新算一遍;若上下文没有变化,它会直接跳过;
|
||
- `markDirty(view)` 只把某一只怪物标记为待重算,下次读取它时做局部刷新。
|
||
|
||
也就是说,「什么时候重建」大多由样板与引擎内部处理好了。只有当你绕过正常流程、直接改动了怪物对象(例如用可写接口拿到怪物后改属性),才需要自己对那只怪物调用 `markDirty(view)`,让改动生效。
|
||
|
||
### 为什么要重建
|
||
|
||
一句话:**为了拿到准确的怪物属性**。
|
||
|
||
模板上的 `atk` 只是怪物「自己写的」攻击力。实际战斗中,它可能被旁边的光环加成,也可能因为查询看到周围有多少同类而提高,还可能被最终效果改写。只有把这些作用全部算进去,伤害计算才可能准确。
|
||
|
||
重建还有一个好处:每次重建都从模板的原始值出发,所以反复 `buildup()` 不会让加成越叠越多。光环、查询与最终效果改的始终是那份计算后副本,模板本身保持不变。
|
||
|
||
### 为什么分四步
|
||
|
||
重建不是一次算完的,而是分成四个阶段。原因很简单:**不同阶段要处理的事情不一样**。有的阶段要先决定「怪物拥有哪些特殊属性」,有的阶段才能去改基础属性,还有的阶段需要看周围的怪物。
|
||
|
||
1. **光环与特殊查询**:先把特殊属性转换成光环视图,按优先级执行会修改特殊属性的效果,确定每只怪物最终拥有哪些特殊属性。
|
||
2. **普通光环**:执行修改基础属性的光环效果。
|
||
3. **常规查询**:允许读取上下文(周围怪物、位置等),并修改自身基础属性。
|
||
4. **最终效果**:不再查询上下文,只对自身基础属性做收尾调整。
|
||
|
||
可以把它想成装配一件装备:先确认要装哪些零件(第 1 步),再装主体(第 2 步),接着根据使用环境微调(第 3 步),最后做一次收尾检查(第 4 步)。顺序不能随便换,因为后面的步骤要建立在前面步骤的结果上。
|
||
|
||
前三步都可能受优先级影响;光环与查询共用同一套优先级,见[怪物光环](./enemy-aura.md)与[怪物查询](./enemy-query.md)。最终效果见[最终属性效果](./enemy-final.md)。
|
||
|
||
## 伤害大致怎么算
|
||
|
||
属性准备完之后,伤害计算器把它们换算成两个数字。一次战斗的伤害可以写成 `f(enemy, hero)`:`enemy` 是计算后的怪物对象,`hero` 是勇士属性对象。计算器读取双方的 `hp`、`atk`、`def`、`mdef` 等属性,返回:
|
||
|
||
- `damage`:本次战斗勇士会损失的生命值;
|
||
- `turn`:战斗持续多少回合。
|
||
|
||
数据端不逐帧模拟战斗,只根据公式给出这两个值。公式的实现与替换见[伤害计算](./enemy-calculator.md)。
|
||
|
||
## 战斗流程
|
||
|
||
把上面几步连起来,就是一次完整的战斗:
|
||
|
||
```mermaid
|
||
graph TD
|
||
A[怪物基础数据] --> B[光环 / 查询 / 最终]
|
||
B --> C[伤害计算 f(enemy, hero)]
|
||
C --> D[战前脚本 before]
|
||
D -->|返回 false| X[放弃战斗]
|
||
D -->|返回 true| E[战斗结算 damage / turn]
|
||
E --> F[战后脚本 after]
|
||
F --> G[战后钩子与状态钩子]
|
||
```
|
||
|
||
- **基础数据**:怪物模板的属性与特殊属性,来自怪物数据;
|
||
- **光环 / 查询 / 最终**:由 `enemyContext.buildup()` 生成计算后怪物;
|
||
- **伤害计算**:`IDamageCalculator.calculate` 返回 `{ damage, turn }`;
|
||
- **战前脚本**:`ICombatScript.before` 返回 `false` 时放弃战斗;
|
||
- **战斗结算**:按 `damage` 扣血,按 `turn` 记录回合数;
|
||
- **战后脚本**:`ICombatScript.after` 结算经验、金币等内容;
|
||
- **战后钩子与状态钩子**:处理中毒、衰弱、重生等持续状态。
|
||
|
||
战斗流程本身由 `state.combat`(`ICombatFlow`)驱动,`battle(view)` 返回本次战斗的伤害信息。脚本见[战斗脚本](./enemy-combat-script.md),状态钩子见[状态钩子](./enemy-status-hooks.md)。
|
||
|
||
## 流程步骤与对应文档
|
||
|
||
| 步骤 | 做什么 | 对应文档 |
|
||
| --------------- | -------------------------------- | ------------------------------------ |
|
||
| 特殊属性 | 定义怪物的机制与参数 | [特殊属性](./enemy-specials.md) |
|
||
| 光环 | 影响周围怪物的基础属性或特殊属性 | [怪物光环](./enemy-aura.md) |
|
||
| 查询 | 读取上下文后调整自身 | [怪物查询](./enemy-query.md) |
|
||
| 最终效果 | 只调整自身基础属性 | [最终属性效果](./enemy-final.md) |
|
||
| 伤害计算 | 实现 `f(enemy, hero)` | [伤害计算](./enemy-calculator.md) |
|
||
| 地图伤害 | 勇士移动时按位置结算伤害 | [地图伤害](./enemy-map-damage.md) |
|
||
| 战前 / 战后脚本 | 拦截战斗、结算奖励 | [战斗脚本](./enemy-combat-script.md) |
|
||
| 状态钩子 | 处理中毒、衰弱、重生等状态 | [状态钩子](./enemy-status-hooks.md) |
|
||
|
||
勇士一侧的属性读写见[勇士属性](./hero-attribute.md)。
|