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 资源存在,再创建 Combat、Enemies 目录、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 把攻击拆成三层:
-
PlayerInputHandler负责消费轻攻击、重攻击输入。 -
AttackConfigSO定义攻击类型、伤害、持续时间、命中窗口、命中框、目标 Layer、hitstop 和击退。 -
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 | AttackConfigSO |
这条链路的重点不是表现效果,而是战斗事实:谁打了谁、造成多少伤害、是否死亡、事件是否只触发一次。
生命、死亡和复活
生命系统集中在 unity-project/Assets/_Project/Combat/Scripts/HealthComponent.cs。
HealthComponent 不只服务玩家,也服务敌人;实体类型通过 HealthEntityKind 区分:
-
Player -
Enemy -
Generic
TakeDamage() 的守卫逻辑很明确:
-
已死亡则不再受伤。
-
伤害小于等于 0 则不处理。
-
仍在受击无敌时间内则不处理。
有效伤害会触发:
-
OnDamageTaken -
OnHealthChanged -
死亡时触发
OnPlayerDeath或OnEnemyDeath
敌人死亡后会禁用子物体中的 Collider,避免死亡敌人继续参与命中或碰撞。测试也覆盖了“死亡事件只触发一次”和“死亡后不能重复受击”。
玩家复活由 unity-project/Assets/_Project/Player/Scripts/PlayerDeathController.cs 接入。当前测试确认玩家死亡后不能攻击,复活后 HP 回到 50,并恢复攻击能力。复活输入在白盒实现中支持 R 或 Enter。
敌人配置和基础 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 | Patrol |
AI 逻辑先按距离找玩家:看见玩家进入 Approach,进入攻击距离后先 Telegraph,至少等待 0.3s,再进入 Attack。这一步是公平性底线:敌人不能一进范围就瞬间扣血。
赛博敌人目前不是正式远程 projectile,而是用更大的 attack range 做白盒范围攻击。这个限制已经在 QA 报告中接受为 M2 范围内的残余项。
难度缩放
M2 的难度公式在 unity-project/Assets/_Project/Combat/Scripts/DifficultyBalancing.cs:
1 | enemyHp = baseHp * (1 + 0.10 * upgradeCount) |
实际实现会对 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 | PlayerInputHandler |
这条链路把 M1 的“能走能跳”升级成了“能打、能受伤、能死、能复活、敌人能反击”。它仍然是白盒实现,但已经足够支撑 M3 继续做攻击链、闪避、技能、反馈调参和更复杂敌人行为。
M3 不应该另起一套战斗框架,而应该沿着 M2 的 AttackConfigSO、HealthComponent、EnemyController 和事件钩子继续扩展。