Files
Seek-Battle-Evacuate/Docs/Tech/04_TurnBasedCombatDevelopmentTODO.md
T

38 KiB
Raw Blame History

回合制战斗开发 TODO

状态:进行中(M0 组件已落地,其余里程碑未开始)

创建日期:2026-08-31

相关设计:Docs/GameDesign/04_TurnBasedCombat.md、Docs/GameDesign/03_RunExploration.md、Docs/GameDesign/06_PrototypeScope.md

目标:在不新增 Battle 场景的前提下,实现覆盖在地图上的半透明多对多回合制战斗 UI,以及可独立测试的战斗规则内核。

一、完成纪律

  • 里程碑是否完成只看标题中的 [ ];第八节的复选框单独追踪 22 条设计验收,不代替里程碑完成状态。
  • 代码写完、静态检查通过或 Unity 编译通过,都不能单独把里程碑改为 [x]。
  • 只有完成该里程碑列出的 Unity 编译、EditMode 测试和 Play Mode 可见验收,并记录证据后,才能改为 [x]。
  • 每次只实现当前里程碑。本里程碑“明确不做”的内容不能因为顺手而提前并入。
  • 生成代码不能手改。Luban 结构或数据需要调整时,修改源表后运行 数据表/gen_cli.sh。
  • 战斗内核不得直接读写 SaveData,不得直接操作地图敌人物体、掉落物或局外结算。
  • 本文档当前所有里程碑均为未完成;本文档落地不代表任何代码或运行时验收已经完成。

每个里程碑完成后按以下格式补证据:

代码状态:未开始 / 进行中 / 已完成
Unity 编译:未验证 / 通过(日期、日志或截图)
EditMode:未验证 / 通过(用例数量、结果)
Play Mode:未验证 / 通过(操作步骤、可见结果、截图或录像)
提交或差异:commit / diff 路径
剩余问题:无 / 具体问题

二、总体进度

里程碑 可见结果 当前状态
M0 接口与调试入口 Main 场景可打开和关闭半透明战斗壳层,地图更新暂停 已完成
M1 1v1 攻击闭环 玩家攻击、敌人反击并产生胜负 未开始
M2 4v4 回合调度 多人队伍按实时速度和并列规则行动 未开始
M3 技能、目标与效果 数值型攻击和技能可合法选目标并结算 HP/MP 未开始
M4 状态与速度变化 眩晕跳过下一次行动,速度变化立即刷新顺序 未开始
M5 先制与确定性随机 先制首轮和普通次轮正确,同一随机输入可复现 未开始
M6 单人逃跑与结束矩阵 成功、失败、全逃跑、部分逃跑后阵亡均可见 未开始
M7 单局状态回写 HP/MP、阵亡恢复和战斗状态清理正确回到临时单局状态 未开始
M8 探索遭遇接入 碰撞进入战斗,胜利移除敌人,逃跑恢复敌人并获得保护 未开始
M9 战斗 UI 完成 最小信息、输入限制和即时反馈完整 未开始
M10 全量回归 战斗文档 22 条验收全部留有运行证据 未开始

验证记录

里程碑 代码 Unity 编译 EditMode Play Mode 证据
M0 已完成 通过(2026-08-31,dotnet build SepCore.Runtime.csproj 0 错误;Unity 编辑器内 Play Mode 实测编译通过) 不适用(M0 无 EditMode 用例) 通过(2026-08-31,见 M0 证据) 已记录(M0 小节)
M1 未开始 未验证 未验证 未验证 未记录
M2 未开始 未验证 未验证 未验证 未记录
M3 未开始 未验证 未验证 未验证 未记录
M4 未开始 未验证 未验证 未验证 未记录
M5 未开始 未验证 未验证 未验证 未记录
M6 未开始 未验证 未验证 未验证 未记录
M7 未开始 未验证 未验证 未验证 未记录
M8 未开始 未验证 未验证 未验证 未记录
M9 未开始 未验证 未验证 未验证 未记录
M10 未开始 未验证 未验证 未验证 未记录

三、已确认的范围与约定

以下决定于 2026-08-31 确认,后续实现不得自行改变:

项目 约定
单局基础 本 TODO 包含最小单局临时状态及其接口,不包含完整地图生成、搜索和警惕 AI 的实现
战斗承载 不新增 Battle 场景;在 Main 场景地图上覆盖半透明的顶级 BattleForm
地图暂停 战斗期间暂停地图角色、敌人 AI、碰撞、搜索和单局计时的更新,战斗 UI 继续响应
模块边界 战斗接收值快照和本局随机接口,返回结果;不直接读写存档、地图敌人或掉落
眩晕 DurationRounds=N 影响目标未来 N 次行动机会;Stun(1) 跳过下一次行动后失效
状态重复施加 同类状态不叠层,保留较长的剩余持续次数
友方目标 SingleAlly 包含施法者;Self 只能选择施法者自己
阵亡目标 阵亡和已逃跑单位不是合法目标;首版没有战斗内复活
战斗表现 首版使用面板式即时结算,不做站位动画、攻击动画、VFX、Timeline 或音效同步
操作方式 首版使用鼠标点击行动和目标
行动顺序 UI 显示当前行动者和本轮剩余行动者;速度变化后立即刷新,不预测下一轮

四、模块所有权

4.1 目录与程序集

位置 所有权
Assets/GameMain/Scripts/Runtime/Battle/ 战斗接口与 DTO、纯战斗模型、调度、指令校验、效果结算、敌人策略和 BattleController
Assets/GameMain/Scripts/Runtime/CustomComponent/Run/ 最小单局临时状态、探索暂停和战斗协调
Assets/GameMain/Scripts/Runtime/CustomComponent/Random/ 本局种子与共享随机源(RandomComponent、IRunRandomSource、RunRandomSource)
Assets/GameMain/Scripts/UI/Battle/ BattleForm、子 Form/View 和手写 .Logic.cs 表现逻辑
Assets/GameMain/Scripts/Base/Event/ 只有确实需要跨模块广播的 UGF 事件;战斗内部通信不走全局事件总线
Assets/GameMain/Tests/EditMode/Battle/ 战斗规则的纯逻辑 EditMode 测试及测试程序集

不新增战斗程序集。接口、DTO 和内核均放入现有 SepCore.Runtime;SepCore.Presentation 已经单向引用 Runtime,Runtime 不得反向引用 Presentation。

4.2 责任划分

模块 负责 不负责
RandomComponent 本局 seed、共享随机源;地图生成、敌人 AI、逃跑和掉落共用 战斗行动规则、地图对象、存档写盘
TurnBattleComponent 临时角色状态、计时、战斗占用、探索暂停标志 战斗行动规则、战斗 UI、存档写盘
RunBattleCoordinator 从单局状态构建请求、打开 UI、提交结果、生成地图恢复计划 回合排序、效果计算、UI 控件状态
BattleSession 单位、轮次、行动机会、效果、状态、敌人 AI、逃跑和结束判定 地图对象、SaveData、UI GameObject
BattleController 暴露只读快照、接收玩家指令、推进自动行动、产出行动记录和最终结果 直接渲染 UI、直接控制地图
BattleForm 展示快照、收集玩家指令、即时播放行动记录、展示结束结果 修改战斗数据、读取存档、决定掉落
探索适配器 碰撞发起遭遇、冻结/恢复地图、敌人移除或复位、掉落、2 秒保护 战斗内部规则

4.3 调用链

队长与地图敌人碰撞
    -> 探索层创建 BattleEncounter
    -> RunBattleCoordinator.TryStartBattle
    -> TurnBattleComponent 预留战斗占用,拒绝重复遭遇
    -> RunBattleCoordinator 构建 BattleStartRequest
    -> 创建并校验 BattleController;失败则释放占用且不暂停地图
    -> 校验成功后提交战斗占用并暂停探索更新
    -> 打开 BattleForm,并调用 BattleController.Start
    -> BattleForm 展示 BattleSnapshot
    -> 玩家提交 BattleCommand / 敌人策略自动提交行动
    -> BattleController 返回 BattleAdvance
    -> BattleForm 即时展示 BattleActionRecord
    -> BattleSession 产生 BattleResult
    -> RunBattleCoordinator.ApplyResult
    -> 更新单局临时角色状态并生成 BattleReturnPlan
    -> 探索层执行敌人、掉落、保护或单局失败处理
    -> 关闭 BattleForm,并在允许继续单局时恢复探索更新

五、接口契约

本节固定接口语义。实现时允许按 C# 规范调整只读集合或构造函数形式,但不得改变所有权和数据流。

5.1 身份规则

标识 用途
EncounterId 地图敌人实例的单局唯一标识;不能使用敌人配置 ID 代替
BattleUnitId 本场战斗单位的唯一运行时标识;重复配置的敌人必须拥有不同 ID
CharacterId 玩家角色配置标识;同时用于把结果回写到单局角色状态
EnemyConfigId 敌人种类配置标识;同一敌人队伍内可以重复
PartyOrder 玩家取战备顺序,敌人取 EnemyPartyConfig.EnemyIds 顺序;作为同阵营同速度的最终并列规则

所有 UI 指令和目标选择都使用 BattleUnitId,不能使用可能重复的 EnemyConfigId。

5.2 遭遇输入

BattleEncounter 由探索层创建:

字段 说明
EncounterId 触发碰撞的地图敌人实例
EnemyPartyConfigId 该地图敌人代表的敌人队伍预设
IsPreemptive 碰撞时敌人警惕值未满为 true,否则为 false

地图坐标、碰撞器、GameObject 和警惕组件不进入战斗请求,由探索层继续持有。

5.3 玩家输入快照

BattlePlayerInput 是进入本场战斗时的值快照:

字段 说明
CharacterId 角色配置 ID
PartyOrder 当前战备中的顺序
CurrentHp / CurrentMp 单局临时状态中的当前值
MaxHp / MaxMp 已结算装备加成的最终上限
Atk / Mat / Speed 已结算装备加成的最终战斗属性
AttackActionId 普通攻击行动配置 ID
SkillActionId 该角色首版唯一技能行动配置 ID

装备计算由单局角色快照构建器完成。BattleSession 不读取 CharacterSave、装备栏或背包。

5.4 战斗启动请求

BattleStartRequest 至少包含:

字段 说明
EncounterId 用于将结果关联回地图敌人实例
Players 1 到 4 个 BattlePlayerInput,保持战备顺序
EnemyPartyConfigId 用于按队伍预设创建 1 到 4 个敌人运行时单位
IsPreemptive 是否启用第一轮玩家先制
Random 当前单局唯一的 IRunRandomSource 实例,来自 RandomComponent,不创建战斗私有随机源

请求在创建后视为只读。战斗开始失败时不得消耗随机数、修改单局状态或暂停状态计数。

5.5 随机数接口

public interface IRunRandomSource
{
    int NextInt(int minInclusive, int maxExclusive);
    bool RollPermille(int successPermille);
}
  • 本局种子由局外(战备界面玩家输入)带入,RandomComponent.BeginRun(seed) 初始化共享随机源。
  • 同一单局的地图生成、敌人 AI、逃跑和敌人掉落依次消费同一个实例;RandomComponent 是唯一持有者。
  • 战斗不能根据 EncounterId、回合数或当前时间重新播种。
  • RollPermille(0) 必须失败,RollPermille(1000) 必须成功。
  • EditMode 测试使用可注入的序列随机源,不依赖 Unity 全局随机状态。

5.6 配置访问接口

IBattleConfigProvider 隔离 BattleSession 与 GameEntry.Luban。运行时适配器读取现有 Luban 表,测试使用内存配置。

public interface IBattleConfigProvider
{
    EnemyPartyConfig GetEnemyParty(int id);
    EnemyConfig GetEnemy(int id);
    BattleActionConfig GetAction(int id);
    GlobalConfig GetGlobal();
}

配置缺失属于战斗启动失败,不允许用零值单位继续战斗。普通消费路径信任已经导出的合法配表,不在每次行动中重复做全表完整性扫描。

5.7 玩家指令

BattleCommand 至少包含:

字段 说明
ActorUnitId 当前获得行动机会的玩家单位
CommandType Attack、Skill、Item 或 Escape;复用配表 BattleActionType 枚举,不单独定义
ActionConfigId 攻击或技能对应的配置 ID;逃跑为 0
TargetUnitIds 按目标类型解析后的唯一运行时目标列表
  • Item 按钮可见但首版禁用,不产生 BattleCommand。
  • 只有 BattleSnapshot 指示的当前玩家单位能提交指令。
  • MP 不足时技能按钮禁用;控制器仍需拒绝伪造的非法指令。
  • SingleAlly 包含自己;Self 必须且只能以自己为目标。
  • AllAllies 包含自己;全体目标由控制器根据当前合法单位展开,UI 不自行拼装。
  • 阵亡、逃跑和敌对阵营错误的目标均非法。
  • 非法指令不得消耗 MP、行动机会或随机数。

5.8 只读战斗快照

BattleSnapshot 是 UI 和敌人策略唯一允许读取的战斗状态。每次推进都创建逻辑上的只读快照,不能把可修改的内部列表或 BattleUnit 引用暴露给调用方。

字段 说明
RoundNumber 当前轮次,从 1 开始
IsPreemptiveRound 当前是否为先制战斗第一轮
CurrentActorUnitId 当前行动者;战斗完成时为空
Units 全部单位的只读视图,保持稳定显示顺序
RemainingTurnOrder 本轮从当前行动者开始的剩余运行时单位 ID;速度变化后重建
FlowState AwaitingPlayerCommand 或 Completed

每个单位视图至少包含 BattleUnitId、阵营、配置 ID、PartyOrder、当前及上限 HP/MP、ATK、MAT、Speed、是否阵亡、是否逃跑、剩余状态和可用行动 ID。UI 不根据这些字段自行推演下一轮或判定胜负。

5.9 战斗推进返回值

BattleController.Start() 和 SubmitCommand() 返回 BattleAdvance:

字段 说明
Records 本次推进产生的有序 BattleActionRecord 列表
Snapshot 推进结束后的只读战斗快照
FlowState AwaitingPlayerCommand 或 Completed
Result 仅在 Completed 时存在

BattleActionRecord 记录行动者、行动、目标,以及每个效果前后的数值或状态变化。首版 UI 立即消费并显示;未来动画系统可以逐条等待播放,不需要修改战斗规则内核。

5.10 敌人策略接口

public interface IEnemyBattlePolicy
{
    BattleCommand Decide(
        BattleSnapshot snapshot,
        int actorUnitId,
        IRunRandomSource random);
}
  • EnemyAiType.Random 对当前可用行动做等概率选择,再对合法的单体目标做等概率选择。
  • MP 不足的行动不进入候选集。
  • 全体目标不额外随机目标。
  • 当前没有可执行行动时,本次行动机会记为跳过,不能形成无限循环。
  • 策略只读取快照并返回指令,不直接修改 BattleSession。

5.11 战斗结果

BattleOutcome 固定为以下四种:

结果 条件 单局是否继续
Victory 所有敌人阵亡 是
AllEscaped 所有玩家成功逃跑,且没有玩家阵亡 是
PartialEscapeDefeat 至少一名玩家逃跑,其余仍在战斗中的玩家全部阵亡 是
TotalDefeat 所有玩家阵亡,没有任何玩家成功逃跑 否

BattleResult 至少包含:

字段 说明
EncounterId 关联原地图敌人实例
Outcome 上述四种战斗结果之一
Players 每名参战玩家的原始战后结果

每个玩家结果包含 CharacterId、CurrentHp、CurrentMp、WasDefeated、Escaped。战斗结果保留原始战斗值:阵亡者 HP 为 0;恢复到 1 HP/1 MP 的规则由 RunBattleCoordinator 在非全员阵亡结果中统一应用。

5.12 探索恢复计划

RunBattleCoordinator 根据 BattleResult 生成 BattleReturnPlan,探索层只执行计划:

BattleReturnPlan 至少包含 EncounterId、Outcome、回写后的玩家临时状态、RemoveEncounterEnemy、ResetEncounterEnemy、ShouldRollDrops、ProtectionMs、ShouldResumeExploration 和 EndsRunAsDefeated。这些布尔值由协调器按下表统一产生,探索层不得再次根据玩家 HP 或逃跑数量重判战斗结果。

战斗结果 玩家状态 触发敌人 掉落 保护时间 单局计时
Victory 存活者保留 HP/MP,阵亡者恢复配置的 1/1,清除战斗状态 移除 使用本局随机源结算 无 恢复
AllEscaped 逃跑时 HP/MP 保留,清除战斗状态 保留并恢复地图行为,下次按完整预设重建 无 配置的 2 秒 恢复
PartialEscapeDefeat 逃跑者保留 HP/MP,阵亡者恢复配置的 1/1,清除状态 保留并恢复地图行为,下次按完整预设重建 无 配置的 2 秒 恢复
TotalDefeat 不返回探索 保持到单局失败清理 无 无 不恢复,进入失败结算

其他地图敌人在战斗期间被冻结;战斗结束后恢复战前的警惕和追击状态,不重新初始化。

探索层通过以下最小接口接收恢复计划:

public interface IExplorationBattleBridge
{
    void ApplyBattleReturn(BattleReturnPlan plan);
}

碰撞方主动把 BattleEncounter 交给 RunBattleCoordinator.TryStartBattle;协调器不扫描场景寻找敌人。ApplyBattleReturn 必须按 EncounterId 精确操作触发敌人,不能按 EnemyConfigId 批量处理同类敌人。

5.13 探索暂停接口

  • TurnBattleComponent.IsExplorationPaused 是地图更新的统一门禁。
  • 玩家移动、队员跟随、敌人巡逻/警惕/追击、搜索进度、地图碰撞触发和单局计时都必须遵守该门禁。
  • 战斗 UI、战斗内核和使用 unscaled time 的 UI 反馈不受该门禁影响。
  • 首版不使用切换场景实现暂停。
  • 不把 Time.timeScale = 0 作为战斗模块的唯一暂停机制,避免 UI 和后续表现链被一同停止。
  • 战斗重复进入由 TurnBattleComponent 的战斗占用状态拒绝;同一碰撞帧不得创建多场战斗。

六、固定规则

6.1 下一行动者

每次行动或跳过结束后,重新从本轮尚未获得行动机会且仍在战斗中的单位中选择:

  1. 先制战斗第一轮仍有玩家未行动时,只在玩家中选择;玩家全部行动后才允许选择敌人。
  2. 当前速度高者优先。
  3. 当前速度相同时,玩家优先于敌人。
  4. 同阵营同速度时,PartyOrder 小者优先。
  5. 已经获得过本轮行动机会的单位不会因速度变化再次行动。
  6. 候选集为空时开启下一轮;先制限制只适用于第一轮。

阵亡和逃跑单位不再进入候选集。眩晕单位仍会被选为下一行动者,随后跳过这次机会、标记本轮已行动并消耗一次眩晕持续次数。

6.2 数值与效果

  • 每个 BattleEffect 按配置列表顺序结算。
  • 目标变化值为 FlatValue + SourceStat * SourceScalePermille / 1000。
  • 使用整数运算;首版不加入暴击、防御、命中、浮动伤害或元素克制。
  • HP 和 MP 始终钳制到 0 与对应上限之间。
  • HP 降到 0 时立即标记阵亡,之后不能成为合法目标或行动者。
  • MP 不足的行动不可执行,不扣除行动机会。
  • 战斗中的属性和状态变化只属于本场战斗;除最终 HP/MP 外不写回探索。

6.3 眩晕

  • Stun(1) 表示跳过目标未来一次行动机会。
  • 目标本轮尚未行动时被施加眩晕,会跳过本轮即将到来的行动。
  • 目标本轮已经行动时被施加眩晕,会跳过下一轮的行动。
  • 重复施加时,剩余次数取当前值与新值中的较大值,不相加。
  • 战斗结束时清除全部战斗状态。

6.4 逃跑

  • 逃跑只允许当前玩家行动者选择。
  • 每次尝试使用 GlobalConfig.EscapeSuccessPermille 和本局随机源独立判定。
  • 成功或失败都消耗当前角色本轮行动机会。
  • 成功后角色立即从当前战斗候选集和合法目标中移除,但不视为阵亡。
  • 只要仍有玩家留在战斗中,单个角色逃跑不结束战斗。

七、开发里程碑

[x] M0 接口、最小单局状态与战斗壳层

目标:建立不会被后续规则推翻的数据流,并在 Main 场景得到第一个可见结果。

实现范围:

  • 建立第五节定义的 DTO、枚举和接口。
  • 新增 RandomComponent:本局 seed 与共享随机源,种子由局外带入,地图生成、敌人 AI、逃跑和掉落共用。
  • 新增 TurnBattleComponent 最小状态:临时玩家列表、计时、IsExplorationPaused 和战斗占用标志。
  • 将 RandomComponent、TurnBattleComponent 挂入 Launcher 的自定义组件节点,并在 GameEntry.Custom 暴露统一访问入口。
  • 新增 RunBattleCoordinator 空流程,能用固定调试遭遇构建 BattleStartRequest。
  • 新增半透明顶级 BattleForm、UIFormType 与 UIFormConfig,沿用现有 Form/View/.Logic.cs 生成和维护规则。
  • 增加仅供 Editor/Development Build 使用的调试入口,不依赖尚未实现的地图敌人。
  • 打开战斗 UI 时暂停调试地图计时,关闭调试壳层后恢复。

当前进度(2026-08-31):

  • 已完成:RandomComponent(GameEntry.Random,Launcher.unity "Random" GameObject);TurnBattleComponent(GameEntry.TurnBattle,Launcher.unity "Turn Battle" GameObject);IRunRandomSource、RunRandomSource、RunPlayerState;第五节全部 DTO/枚举/接口(Battle/ 目录,命名空间 SepCore.Battle);RunBattleCoordinator(GameEntry.RunBattle,空流程:预留占用、构建 BattleStartRequest、暂停/恢复探索与计时、开/关 BattleForm,EndDebugBattle 为 M0 专用,M7 由 ApplyResult 替代);BattleForm.Logic.cs 壳层逻辑(按钮绑定、道具禁用、占位反馈,不含暂停职责);BattleDebuggerWindow 调试入口(#if UNITY_EDITOR || DEVELOPMENT_BUILD 注册于 ProcedureLaunch,路径 Battle/Shell,固定遭遇 EncounterId=1/队伍预设 1,缺单局状态时用配表角色与固定种子初始化);配表 UIFormType.BattleForm = 103 与 UIFormConfig 行已导出。
  • 未完成:BattleController 创建与校验(M1 起);暂停联动已由协调器承担,等待 M0 Play Mode 验收。
  • 已知注意点:单局计时暂停来源不止战斗(暂停菜单也会暂停),后续需要多来源组合;RunRandomSource 使用 System.Random,跨平台同 seed 序列不保证一致,仅需同构建内复现;BattleView 的回合槽与敌人槽为模板 + 运行时实例化(turnSlotsRoot/enemySlotsRoot + template),数据接入时由 Logic 负责实例化。

验证证据:

代码状态:已完成
Unity 编译:通过(2026-08-31,dotnet build SepCore.Runtime.csproj 0 错误;Unity 编辑器内编译与 Play Mode 实测通过)
EditMode:不适用(M0 无 EditMode 用例)
Play Mode:通过(2026-08-31,Debugger -> Battle/Shell 实测:
  1. Main 场景未切换、未卸载,半透明 BattleForm 覆盖在地图上;
  2. 调试入口 Open 打开战斗壳层,按钮可点击响应;
  3. Run State 读数:打开后 RunElapsedMs 停止增长、IsTimerPaused=true、IsExplorationPaused=true、IsBattleActive=true,Close 后计时恢复增长、标志复位;
  4. 壳层打开时重复点击 Open 被拒绝(TryStartBattle 占用检查 + allowMultiInstance=false),不会打开第二场。
  截图:未提供)
提交或差异:未提交(工作区包含全部 M0 代码、配表导出与 Launcher.unity 挂载)
剩余问题:无

明确不做:回合调度、攻击、敌人 AI、胜负和真实碰撞接入。

Play Mode 验收:

  • Main 场景不切换、不卸载。
  • 调试入口能打开覆盖地图的半透明战斗 UI。
  • 战斗 UI 打开时地图计时或可观察的地图更新停止,UI 按钮仍可响应。
  • 重复触发调试入口不会打开第二场战斗。

[ ] M1 1v1 攻击闭环

目标:完成第一场能亲手打完的战斗。

实现范围:

  • 创建 1 名玩家和 1 名敌人的 BattleSession。
  • 支持轮次、当前行动者和每轮一次行动机会。
  • 支持普通攻击、简单 HP 伤害、MP/HP 钳制和阵亡。
  • 实现 EnemyAiType.Random 的免费攻击选择和合法目标选择。
  • 实现 Victory 与 TotalDefeat。
  • UI 显示双方 HP、当前行动者和攻击按钮。

明确不做:多人队伍、技能、状态、先制和逃跑。

Play Mode 验收:

  • 玩家点击攻击后敌人 HP 立即变化。
  • 未结束时敌人能自动反击,玩家 HP 立即变化。
  • 任一方 HP 归零后战斗停止接受指令并显示对应结果。
  • 行动记录的顺序和最终数值与 UI 一致。

[ ] M2 1 至 4 人多对多与实时回合顺序

目标:完成设计要求的多对多调度骨架。

实现范围:

  • 从 BattleStartRequest.Players 和 EnemyPartyConfig 创建最多 4v4 的单位。
  • 为重复敌人配置生成不同 BattleUnitId。
  • 实现当前速度、阵营优先和 PartyOrder 并列规则。
  • 每次行动后重新选择本轮下一行动者。
  • 阵亡单位在轮到前死亡时不再行动。
  • UI 显示当前行动者和本轮剩余顺序。

明确不做:速度修改效果、眩晕、技能和先制。

Play Mode 验收:

  • 调试场景可分别启动 1v1、2v2 和 4v4。
  • 同速度时玩家先于敌人,同阵营按队伍顺序行动。
  • 每个仍在战斗中的单位每轮最多行动一次。
  • 两个相同 EnemyConfig 的敌人可以分别选择、受伤和死亡。

[ ] M3 技能、目标选择、MP 与通用效果

目标:让当前配表中的数值型玩家和敌人行动可用。

实现范围:

  • 支持 Attack 和 Skill 行动配置。
  • 本阶段只结算数值型 BattleEffect;状态型效果由 M4 接入。
  • 支持 Self、SingleAlly、AllAllies、SingleEnemy 和 AllEnemies。
  • 实现 MP 校验和成功执行后的 MP 扣除。
  • 实现 HP 治疗、单体伤害和全体伤害。
  • UI 根据行动配置进入或跳过目标选择,并禁用 MP 不足的技能。
  • 控制器拒绝错误行动者、错误阵营、错误目标数量、阵亡目标和逃跑目标。

明确不做:角色 4 的眩晕效果、速度修改、逃跑和道具效果。

Play Mode 验收:

  • 角色 1 至 3 的单体伤害、单体治疗和全体伤害技能能够完整结算。
  • SingleAlly 可以选择施法者自己。
  • 全体技能一次结算全部合法目标。
  • MP 不足时技能不可点击,伪造非法指令也不会消耗行动机会。
  • 治疗不会超过 MaxHP,伤害不会使 HP 低于 0。

[ ] M4 眩晕、无法行动与实时速度变化

目标:补齐会改变行动机会或下一行动者的战斗内变化。

实现范围:

  • 实现 BattleStatusType.Stun 及已确认的持续次数语义。
  • 实现同类状态刷新而非叠层。
  • 实现对 Speed 的简单数值修改。
  • 速度改变后立即重算本轮剩余顺序。
  • 为 EditMode 和开发调试场景提供可复现的速度变化用例;不要求修改四名正式角色的技能定位。

明确不做:复杂状态、每轮伤害、状态图标动画和永久属性写回。

Play Mode 验收:

  • 角色 4 的眩晕技能能使尚未行动的目标跳过本次行动。
  • 已经行动的目标被眩晕后,会跳过下一轮的行动。
  • 重复眩晕不会叠加总次数,只保留较长持续次数。
  • 调试行动改变未行动单位速度后,本轮剩余顺序立即更新。
  • 已在本轮行动过的单位不会因速度提高再次行动。

[ ] M5 先制首轮与确定性随机

目标:接通普通遭遇和主动接敌的规则差异,并证明战斗随机来自单局。

实现范围:

  • 实现 IsPreemptive。
  • 第一轮所有尚未行动的玩家先于所有敌人,玩家内部仍按当前速度和队伍顺序选择。
  • 第二轮恢复双方共同按速度选择。
  • 敌人行动和目标选择只消费注入的本局随机源。
  • 增加固定随机序列的 EditMode 回归用例。

明确不做:真实警惕碰撞接入和敌人掉落。

Play Mode 验收:

  • 同一队伍可分别以普通和先制模式启动。
  • 先制第一轮中,即使敌人速度更高,也要等所有玩家行动后再行动。
  • 第二轮高速敌人恢复正常优先级。
  • 相同初始状态与固定随机序列产生相同敌人行动和目标记录。

[ ] M6 单角色逃跑与结束结果矩阵

目标:完成所有战斗结束分支,但暂不操作地图。

实现范围:

  • 增加逃跑按钮和 Escape 指令。
  • 使用 GlobalConfig.EscapeSuccessPermille 与本局随机源判定。
  • 成功或失败均消耗当前行动机会。
  • 成功逃跑单位立即离开候选集和合法目标集合。
  • 实现 AllEscaped 与 PartialEscapeDefeat。
  • 固定四种 BattleOutcome 的互斥判定和结果数据。

明确不做:2 秒地图保护、地图敌人恢复和 HP/MP 回写。

Play Mode 验收:

  • 逃跑失败后角色仍在战斗中,随后敌人正常行动。
  • 单人逃跑成功不会在仍有队友战斗时结束战斗。
  • 所有人成功逃跑得到 AllEscaped。
  • 至少一人逃跑、其余人阵亡得到 PartialEscapeDefeat,而不是 TotalDefeat。
  • 没有人逃跑且全部阵亡得到 TotalDefeat。

[ ] M7 单局临时状态回写与战斗关闭

目标:让调试战斗能正确返回同一局临时状态。

实现范围:

  • RunBattleCoordinator 消费 BattleResult,不得由 UI 自行回写。
  • 胜利时存活者保留 HP/MP,阵亡者按全局配置恢复到 1/1。
  • 全逃跑时所有角色保留逃跑时 HP/MP。
  • 部分逃跑失败时逃跑者保留值,阵亡者恢复到 1/1。
  • 清除所有战斗状态,不把 Speed 修改或眩晕带回探索。
  • TotalDefeat 进入单局失败入口,不恢复探索更新。
  • 非全员阵亡时关闭 BattleForm 并恢复探索更新。

明确不做:真实地图敌人、掉落和保护碰撞门禁。

Play Mode 验收:

  • 连续打开两场调试战斗时,第二场使用第一场返回的玩家 HP/MP。
  • 第一场阵亡但非全员阵亡的角色,第二场以 1 HP/1 MP 参战。
  • 上一场眩晕或速度修改不会进入下一场。
  • 全员阵亡后不能返回可操作地图状态。
  • 本阶段全过程不修改 GameEntry.Save.Data 或写入 save.json。

[ ] M8 真实探索遭遇、敌人恢复、掉落与保护

目标:通过既定接口接入探索层,形成碰撞到返回地图的完整垂直流程。

前置依赖:探索层必须提供唯一 EncounterId、敌人队伍预设 ID、警惕是否已满、地图暂停门禁和敌人实例生命周期接口。

实现范围:

  • 队长碰撞调用探索适配器创建 BattleEncounter。
  • 搜索被战斗中断时保留搜索进度和已掉落物。
  • 战斗期间冻结所有地图更新,其他敌人保持战前警惕和追击状态。
  • Victory 移除触发敌人,并通过掉落服务使用本局随机源结算掉落。
  • AllEscaped 和 PartialEscapeDefeat 保留触发敌人,恢复地图行为;下次进入战斗重新按完整队伍预设创建敌方。
  • 逃跑类结果开启 GlobalConfig.EscapeProtectionMs 保护。
  • 保护期间禁止警惕增长、追击和战斗触发;结束后仍重叠可立即再次触发。

明确不做:地图生成、敌人巡逻算法、警惕值算法、物品背包实现和掉落表设计。

Play Mode 验收:

  • 警惕未满碰撞进入先制战斗,警惕已满碰撞进入普通战斗。
  • 附近其他地图敌人不会加入战斗,战斗后状态保持不变。
  • 胜利后只有触发敌人消失并可能产生掉落。
  • 逃跑后触发敌人仍在;再次碰撞面对完整初始敌人队伍。
  • 2 秒保护内不能立刻重入战斗,保护结束仍重叠时可以重入。

[ ] M9 战斗 UI、输入限制与最小表现完成

目标:达到设计文档要求的最小可读、可操作战斗界面。

实现范围:

  • 我方所有角色显示名称、HP、MP、阵亡和逃跑状态。
  • 所有敌人显示名称、HP 条和不可选状态。
  • 显示当前行动者和本轮剩余行动顺序。
  • 行动菜单只在等待当前玩家指令时出现。
  • 攻击、技能、逃跑可用;道具占位可见但禁用。
  • 单体目标提供明确选中态;取消目标选择返回行动菜单且不消耗行动。
  • 战斗期间不能打开共享背包、保险箱或调整保险箱内容。
  • UI 文案走现有本地化/格式化文本流程,不在逻辑代码中硬编码。
  • 保持半透明覆盖效果,让玩家能够确认仍在原地图场景。

明确不做:角色站位、骨骼或序列帧动画、VFX、Timeline、镜头演出和音效同步。

Play Mode 验收:

  • 1 至 4 名玩家和敌人的最长名称、HP/MP 数值不会溢出或遮挡。
  • 速度变化后顺序显示与下一实际行动者一致。
  • 非当前玩家不能操作,敌人自动行动期间不能提交玩家指令。
  • 背包和保险箱入口在战斗中不可用。
  • 取消目标、MP 不足、阵亡目标和逃跑目标均不会误消耗行动。

[ ] M10 全量回归与原型完成

目标:关闭战斗模块全部设计验收,清理调试入口和遗留临时代码。

实现范围:

  • 完成第八节的全部验收映射。
  • 覆盖 EditMode 规则测试和实际探索 Play Mode 流程。
  • 调试入口只在 Editor/Development Build 可用,正式构建不可见。
  • 清理临时日志、硬编码测试数据和无用调试按钮。
  • 检查生成文件、资源收集、UIForm 配表和 Prefab 引用。
  • 更新本文档进度、证据和剩余问题。

Play Mode 验收:

  • 从地图碰撞进入战斗、完成任一结果并返回或进入失败结算的流程可重复执行。
  • 普通、先制、胜利、全逃跑、部分逃跑失败和全员阵亡均留有证据。
  • 连续多场战斗没有重复事件订阅、重复 UI、随机源重置或地图暂停泄漏。
  • Unity Console 没有由战斗流程产生的新异常或错误。

八、设计验收映射

以下 22 项对应 Docs/GameDesign/04_TurnBasedCombat.md。只有对应里程碑完成运行验收后才能勾选。

  • 多个玩家和多个敌人可以同时参战。对应 M2。
  • 玩家与敌人上限均为 4,并按战备和敌人队伍预设创建。对应 M2。
  • 普通轮次每个单位只行动一次,每次行动后按当前速度决定下一位。对应 M2、M4。
  • 同速玩家优先,速度变化立即影响尚未行动单位。对应 M2、M4。
  • 同阵营同速按队伍顺序行动。对应 M2。
  • 死亡、眩晕或无法行动单位跳过本轮。对应 M2、M4。
  • 轮到玩家角色时才显示并处理该角色的行动选择。对应 M1、M9。
  • 先制第一轮所有玩家排在所有敌人之前。对应 M5。
  • 先制第二轮恢复普通速度规则。对应 M5。
  • 玩家可选择攻击、技能和逃跑,道具保留但不可用。对应 M3、M6、M9。
  • 战斗中不能打开共享背包或保险箱。对应 M9。
  • 敌人随机选择可用行动和合法目标。对应 M1、M5。
  • 敌人全灭时胜利,玩家全灭时战斗和单局失败。对应 M1、M6、M7。
  • 单个角色可逃跑;只要有人逃跑,其他人阵亡不导致单局失败。对应 M6。
  • 逃跑使用配置成功率,失败后继续留在战斗。对应 M6。
  • 逃跑成功或失败均消耗本轮行动。对应 M6。
  • 逃跑返回战前位置并获得 2 秒保护。对应 M7、M8。
  • 逃跑者保留 HP/MP,部分失败中的阵亡者恢复 1/1。对应 M7。
  • 任何非全员阵亡结果中的阵亡角色恢复 1/1。对应 M7。
  • 逃跑后地图敌人恢复,重战按完整预设创建。对应 M8。
  • 保护期间不警惕、不追击、不触发战斗,结束仍重叠时可重入。对应 M8。
  • 战斗 UI 显示我方 HP/MP、行动顺序和敌人 HP。对应 M9。

九、最低测试矩阵

EditMode

类别 必测情况
单位创建 1v1、4v4、重复 EnemyConfig、不合法队伍人数
普通排序 速度不同、跨阵营同速、同阵营同速、每轮只行动一次
动态排序 未行动单位加速/减速、已行动单位加速、死亡后移出候选集
先制 第一轮玩家全先行、第二轮恢复普通规则
目标 Self、SingleAlly 含自己、全体目标、阵营错误、阵亡和逃跑目标
MP 与效果 MP 不足、伤害、治疗、全体效果、HP/MP 钳制、顺序效果
眩晕 行动前施加、行动后施加、重复施加、跳过后失效
敌人 AI 过滤不可用行动、合法目标、无行动时跳过、固定随机序列
逃跑 0/1000 边界、成功/失败消耗行动、四种结束结果
回写 胜利阵亡恢复、全逃跑保留、部分失败恢复、全灭不恢复探索

Play Mode

流程 必测情况
UI 生命周期 打开、关闭、重复进入、非当前行动者不可输入
地图暂停 玩家、敌人、搜索、计时停止;战斗 UI 仍响应
遭遇 普通碰撞、先制碰撞、附近敌人不参战
返回 胜利移除敌人、逃跑保留敌人、完整队伍重建
保护 保护期不重入,结束仍重叠可重入
连战 HP/MP 延续、状态清除、共享随机序列不重置
失败 全员阵亡进入单局失败,不短暂恢复地图操作

十、原型完成条件

只有同时满足以下条件,本文档状态才能改为“完成”:

  • M0 至 M10 全部标记为 [x],且每个里程碑都有独立的 Unity/Play Mode 证据。
  • 第八节 22 条设计验收全部通过。
  • EditMode 最低测试矩阵全部通过。
  • 战斗过程中没有直接写 SaveData、切换 Battle 场景或重建单局随机源。
  • 胜利、逃跑和失败对地图敌人、玩家临时状态、计时和保护期的处理符合本文档。
  • 正式构建中没有开发调试入口和测试数据。
  • 静态检查、Unity 编译、EditMode 和 Play Mode 结果分别记录,不用静态检查代替运行验收。