PROJECT-v M2:基础战斗闭环实现拆解

M2 的目标是把 M1 的“角色能动”推进到“基础战斗”。这一阶段不追求完整动作游戏手感,也不做技能、武器 Build、Boss 或正式表现层,而是先证明最小战斗链路成立:玩家能攻击,敌人能受伤和死亡,敌人能靠近并攻击玩家,玩家能死亡和复活,难度缩放和自动化测试能覆盖这条链路。

本文所有文件路径均以 PROJECT-V 项目根目录为基准。

关联提交和依据

M2 功能主体提交:

commit 用途
f93faa4 feat(m2): implement basic combat milestone,M2 基础战斗里程碑实现

分支状态:

分支 说明
develop M2 实施分支
origin/develop 已推送远端分支

主要参考产物:

  • reports/M2_delivery_report.html

  • qa/reports/M2_test_report.md

  • qa/test_cases/M2_basic_combat.md

  • qa/results/M2_EditMode_TestResults.xml

  • qa/results/M2_PlayMode_TestResults.xml

  • qa/results/M2_RuntimeProbe.json

M2 的目标和边界

M2 只做基础战斗闭环。它承接 M1 的移动、跳跃、相机和测试场景,不重写角色控制,而是在玩家、敌人和战斗模块之间接出一条最小可验证链路。

M2 纳入实现的内容:

  • 玩家轻攻击和重攻击。

  • 攻击配置、命中框、重复命中保护、击退和 hitstop。

  • 玩家 HP、敌人 HP、死亡事件、死亡后防重复处理。

  • 玩家死亡后的输入锁定和复活。

  • 三类普通敌人:废土、赛博、生化各一个。

  • 敌人 Patrol / Approach / Telegraph / Attack / Recover / Dead 基础 AI。

  • 难度缩放公式。

  • M2_BasicCombat.unity 测试场景。

  • EditMode、PlayMode、Windows Development Build 和 Runtime Probe 验证。

M2 明确不做的内容:

  • 连段树、闪避无敌、技能、武器池、符文和 Build 协同。

  • Boss、程序化关卡、正式关卡流程。

  • 正式伤害数字、正式死亡 UI、最终音频和正式特效。

  • 赛博敌人的完整 projectile 系统。

  • 30 分钟 1080p 长时 soak。

这个边界决定了 M2 的实现方式:系统要能打通战斗因果链,但仍保持白盒、可测、可替换。

工程生成入口

M2 的工程落地入口是 unity-project/Assets/_Project/Editor/M2ProjectBuilder.cs

它的职责比 M1 更重:不仅生成场景,还要把战斗资产、玩家 Prefab、敌人 Prefab 和验证入口全部拉齐。

它会生成或更新这些内容:

类别 路径
轻攻击配置 unity-project/Assets/_Project/Combat/SO/AttackConfig_Light.asset
重攻击配置 unity-project/Assets/_Project/Combat/SO/AttackConfig_Heavy.asset
玩家生命配置 unity-project/Assets/_Project/Combat/SO/Health_Player.asset
难度配置 unity-project/Assets/_Project/Combat/SO/Difficulty_M2.asset
三类敌人配置 unity-project/Assets/_Project/Enemies/SO/
三类敌人 Prefab unity-project/Assets/_Project/Enemies/Prefabs/
M2 测试场景 unity-project/Assets/Scenes/TestScenes/M2_BasicCombat.unity
Runtime Probe unity-project/Assets/_Project/Player/Scripts/M2RuntimeProbe.cs

SetupM2() 会先确保 M1 资源存在,再创建 CombatEnemies 目录、Enemy Layer、Player Tag、敌人占位 Sprite、SO 配置、Prefab 和测试场景。VerifyM2Setup() 则检查配置参数、输入绑定、玩家 Prefab、敌人配置、场景内容和 runtime probe。

这让 M2 仍保持一个重要原则:不要依赖某次 Unity Editor 里的手工摆放。战斗场景、敌人 Prefab、SO 参数和验证入口都能由同一套 Editor tooling 重建。

玩家攻击系统

玩家攻击核心在 unity-project/Assets/_Project/Player/Scripts/PlayerAttack.cs

M2 把攻击拆成三层:

  1. PlayerInputHandler 负责消费轻攻击、重攻击输入。

  2. AttackConfigSO 定义攻击类型、伤害、持续时间、命中窗口、命中框、目标 Layer、hitstop 和击退。

  3. PlayerAttack 用计时器进入命中窗口,并把命中结果转换成 DamageInfo

轻重攻击参数如下:

攻击 输入 伤害 总时长 命中开始 命中持续 hitstop
Light J 12 0.40s 0.25s 0.10s 6 frames
Heavy L 30 0.80s 0.50s 0.15s 12 frames

输入资产还配置了鼠标和手柄:

行为 键盘 鼠标 手柄
轻攻击 J 左键 buttonWest
重攻击 L 右键 buttonNorth

需要注意的是,M2 报告只确认这些绑定存在;鼠标和手柄路径没有做真实外设人工测试。

命中框和重复命中保护

命中检测由 unity-project/Assets/_Project/Combat/Scripts/Hitbox2D.cs 封装,底层使用 Physics2D.OverlapBoxNonAlloc()。这符合 M2 的白盒目标:先用简单、可控、无 GC 压力的盒形命中框跑通战斗。

PlayerAttack 中有一个关键结构:

1
HashSet<HealthComponent> hitTargets

每次攻击开始时清空它;攻击命中窗口内,如果目标已经被当前攻击实例命中过,就跳过。这样可以保证“同一次攻击同一个目标只受伤一次”,但同一次攻击仍可以打到多个不同敌人。

命中成功后,链路是:

1
2
3
4
5
6
AttackConfigSO
-> PlayerAttack.ApplyHitbox()
-> Hitbox2D.Overlap()
-> HealthComponent.TakeDamage()
-> OnAttackHit / OnDamageTaken / OnHealthChanged
-> HitFeedback / Knockback / Hitstop

这条链路的重点不是表现效果,而是战斗事实:谁打了谁、造成多少伤害、是否死亡、事件是否只触发一次。

生命、死亡和复活

生命系统集中在 unity-project/Assets/_Project/Combat/Scripts/HealthComponent.cs

HealthComponent 不只服务玩家,也服务敌人;实体类型通过 HealthEntityKind 区分:

  • Player

  • Enemy

  • Generic

TakeDamage() 的守卫逻辑很明确:

  • 已死亡则不再受伤。

  • 伤害小于等于 0 则不处理。

  • 仍在受击无敌时间内则不处理。

有效伤害会触发:

  • OnDamageTaken

  • OnHealthChanged

  • 死亡时触发 OnPlayerDeathOnEnemyDeath

敌人死亡后会禁用子物体中的 Collider,避免死亡敌人继续参与命中或碰撞。测试也覆盖了“死亡事件只触发一次”和“死亡后不能重复受击”。

玩家复活由 unity-project/Assets/_Project/Player/Scripts/PlayerDeathController.cs 接入。当前测试确认玩家死亡后不能攻击,复活后 HP 回到 50,并恢复攻击能力。复活输入在白盒实现中支持 REnter

敌人配置和基础 AI

M2 新增 Enemies 模块,核心配置文件是 unity-project/Assets/_Project/Enemies/Scripts/EnemyConfigSO.cs,核心控制器是 unity-project/Assets/_Project/Enemies/Scripts/EnemyController.cs

三类普通敌人对应三个世界观:

敌人 世界 基础 HP 基础伤害 特点
Wasteland Raider Wasteland 30 8 近战基础敌人
Cyber Infantry Cyberpunk 25 10 M2 用范围攻击近似远程
Bio Parasite Biohazard 40 12 更厚、更贴近

敌人状态机是白盒版:

1
2
3
4
5
6
7
Patrol
-> Approach
-> Telegraph
-> Attack
-> Recover
-> Patrol / Approach
-> Dead

AI 逻辑先按距离找玩家:看见玩家进入 Approach,进入攻击距离后先 Telegraph,至少等待 0.3s,再进入 Attack。这一步是公平性底线:敌人不能一进范围就瞬间扣血。

赛博敌人目前不是正式远程 projectile,而是用更大的 attack range 做白盒范围攻击。这个限制已经在 QA 报告中接受为 M2 范围内的残余项。

难度缩放

M2 的难度公式在 unity-project/Assets/_Project/Combat/Scripts/DifficultyBalancing.cs

1
2
3
enemyHp     = baseHp     * (1 + 0.10 * upgradeCount)
enemyDamage = baseDamage * (1 + 0.05 * upgradeCount)
enemyCount = baseCount + floor(upgradeCount / 3)

实际实现会对 HP 和伤害做 Mathf.Round(),并保证 HP 至少为 1、敌人数至少为 1。DifficultyBalancing.Calculate() 还会发布 OnDifficultyScaled,方便后续 QA 或 UI 记录缩放结果。

EditMode 测试覆盖了 upgradeCount = 0 / 3 / 6 / 10,证明公式在关键台阶上没有偏离。

M2 测试场景

M2 场景是 unity-project/Assets/Scenes/TestScenes/M2_BasicCombat.unity

场景结构很直接:

  • 一个玩家。

  • 三个敌人,分别对应废土、赛博、生化。

  • 主战斗平台、赛博远程测试平台和安全地板。

  • 一个 M2_RuntimeProbe

M2 没有做完整关卡,而是做一个可控的白盒战斗试验场。这个选择是对的:基础战斗还没稳定时,复杂关卡只会放大变量,让问题更难定位。

验证结果

M2 当前验证结果如下:

验证项 证据 结果
M2ProjectBuilder.VerifyM2Setup unity-project/Logs/M2_Verify.log Passed
EditMode qa/results/M2_EditMode_TestResults.xml 13 passed / 0 failed
PlayMode qa/results/M2_PlayMode_TestResults.xml 4 passed / 0 failed
Windows Development Build unity-project/Logs/M2_Build.log Success
Runtime Probe qa/results/M2_RuntimeProbe.json Avg 60.165 FPS, Min 59.817 FPS

EditMode 的 13 个用例包含 M1 回归 5 个和 M2 战斗 8 个,覆盖配置、HP/死亡/复活、死亡幂等、难度公式、Prefab、场景和输入绑定。

PlayMode 的 4 个用例覆盖:

  • 轻攻击造成 12 点伤害。

  • 重攻击造成 30 点伤害且同目标只命中一次。

  • 玩家死亡后不能攻击,复活到半血。

  • 敌人 telegraph 后再对玩家造成伤害。

Runtime Probe 结果如下:

指标 数值
Warmup 1s
测量窗口 5.003s
帧数 301
平均 FPS 60.165
最低 FPS 59.817
最高 FPS 367.245
敌人数 3
玩家 HP 38
玩家死亡 false

这说明 M2 短程 smoke 达到当前 QA floor,但它仍然不能替代长时 release-grade 性能验证。

已知限制

M2 已通过当前范围验收,但仍有明确限制:

  • 未执行 30 分钟 1080p soak;这仍是后续 release-grade QA gate。

  • Cyber Infantry 使用白盒范围攻击近似远程行为,还不是正式 projectile 系统。

  • 伤害数字、正式死亡 UI、正式音频不在 M2 P0 范围内。

  • 鼠标和手柄攻击路径已配置,但本轮未做真实外设人工测试。

  • 当前测试场景是白盒战斗试验场,不代表正式关卡体验。

M2 留下的工程主线

M2 之后,PROJECT-V 的局内主链路变成:

1
2
3
4
5
6
7
8
PlayerInputHandler
-> PlayerAttack
-> AttackConfigSO
-> Hitbox2D
-> HealthComponent
-> EventBus
-> EnemyController / PlayerDeathController
-> M2CombatTests / M2CombatPlayModeTests / M2RuntimeProbe

这条链路把 M1 的“能走能跳”升级成了“能打、能受伤、能死、能复活、敌人能反击”。它仍然是白盒实现,但已经足够支撑 M3 继续做攻击链、闪避、技能、反馈调参和更复杂敌人行为。

M3 不应该另起一套战斗框架,而应该沿着 M2 的 AttackConfigSOHealthComponentEnemyController 和事件钩子继续扩展。