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