7.7 KiB
Simulation Development Skill(VampireLike)
目标
本文件是 Simulation 分层的开发规范与速查手册。
后续调整敌人移动、补齐投射物/掉落物逻辑、推进 Job/Burst 改造时,优先按本文档执行,避免反复通读全部代码。
当前架构总览(P1.5 已落地)
- Simulation 主目录:
Assets/GameMain/Scripts/Simulation/ - 核心组件:
SimulationWorld(GameFrameworkComponent) - 数据容器:
List<EnemySimData> _enemiesList<ProjectileSimData> _projectilesList<PickupSimData> _pickups
- Tick 临时缓冲:
List<EnemyTickWorkItem> _enemyTickWorkItemsList<EnemySeparationAgent> _enemySeparationAgents
- 索引绑定:
EntityBinding(EntityId <-> SimulationIndex双向映射) - 生命周期同步:
SimulationWorld.EntitySync(监听实体 Show/Hide 事件) - 表现层回写:
SimulationWorld.Presentation(LateUpdate写回Transform) - Tick 上下文:
SimulationTickContext(DeltaTime、RealDeltaTime、PlayerPosition)
运行时主链路(按帧)
GameEntry.InitCustomComponents()获取或自动挂载SimulationWorld
文件:Assets/GameMain/Scripts/Base/GameEntry.Custom.csProcedureGame.OnEnter()清理旧 Simulation 数据
文件:Assets/GameMain/Scripts/Procedure/Game/ProcedureGame.csGameStateBattle.OnUpdate()中先执行刷怪,再执行SimulationWorld.Tick(...)
文件:Assets/GameMain/Scripts/Procedure/Game/GameStateBattle.csSimulationWorld.Tick()仅在UseSimulationMovement == true时执行敌人 TickSimulationWorld.LateUpdate()执行Presentation.OnLateUpdate(),将仿真结果写回敌人Transform
生命周期与数据同步设计
EntitySync 通过事件驱动保持 Simulation 容器与实体生命周期一致:
| 事件 | 组名 | 行为 |
|---|---|---|
ShowEntitySuccessEventArgs |
Enemy |
RegisterEnemyLifecycle + UpsertEnemy |
HideEntityCompleteEventArgs |
Enemy |
UnregisterEnemyLifecycle + RemoveEnemyByEntityId |
ShowEntitySuccessEventArgs |
Drop |
RegisterPickupLifecycle + UpsertPickup |
HideEntityCompleteEventArgs |
Drop |
UnregisterPickupLifecycle + RemovePickupByEntityId |
ShowEntitySuccessEventArgs |
Bullet / Projectile |
RegisterProjectileLifecycle + UpsertProjectile |
HideEntityCompleteEventArgs |
Bullet / Projectile |
UnregisterProjectileLifecycle + RemoveProjectileByEntityId |
关键规则:
- 删除容器元素统一使用“末尾覆盖 +
RemoveAt(lastIndex)+EntityBinding.RemapIndex”。 Upsert语义:EntityId已存在则覆盖,不存在则追加。
EnemySimData 合约(当前实现)
文件:Assets/GameMain/Scripts/Simulation/SimData/EnemySimData.cs
EntityId:实体唯一标识Position / Forward / Rotation:逻辑输出与表现层写回字段Speed:来自EnemyData.SpeedBaseAttackRange:当前固定初始化为1fAvoidEnemyOverlap / EnemyBodyRadius / SeparationIterations:从MovementComponent读取TargetType / State:状态扩展预留
当前状态值(SimulationWorld 常量):
0:Idle1:Chasing2:InAttackRange
TickEnemies 当前算法(P1.5 分阶段)
文件:Assets/GameMain/Scripts/Simulation/SimulationWorld.cs
TickEnemies 入口保持纯数据热路径,不直接读写 Transform,按四阶段执行:
BuildInput- 计算到玩家平面距离
- 产出
EnemyTickWorkItem - 生成分离输入
EnemySeparationAgent
Move/Separation- 计算追踪位移与朝向
- 通过
EnemySeparationSolverProvider.ResolveSimulation(...)做互斥求解
StateUpdate- 按距离与可追逐状态更新
Idle/Chasing/InAttackRange
- 按距离与可追逐状态更新
WriteBack- 回写
EnemySimData(Position/Forward/Rotation/State)
- 回写
互斥求解器双通道(Legacy + Simulation)
文件:
Assets/GameMain/Scripts/Utility/EnemySeperator/IEnemySeparationSolver.csAssets/GameMain/Scripts/Utility/EnemySeperator/EnemySeparationSolverProvider.csAssets/GameMain/Scripts/Utility/EnemySeperator/GridBucketEnemySeparationSolver.cs
说明:
SetSimulationAgents/ResolveSimulation:供 Simulation 纯数据路径调用。Register/Unregister/Resolve(Transform, ...):保留旧路径兼容与回滚能力。GridBucketEnemySeparationSolver已加入桶列表复用池(_bucketListPool)以降低 GC。
表现层回写(Presentation)规则
文件:Assets/GameMain/Scripts/Simulation/SimulationWorld.Presentation.cs
- 仅在
UseSimulationMovement == true时执行 - 遍历
EnemyManager.Enemies,按EntityId查找EnemySimData - 写回顺序:
- 始终写回
position - 优先使用
rotation - 若
rotation无效,则回退到forward
- 始终写回
与旧移动系统的关系(重要)
MeleeEnemy/RemoteEnemy在OnUpdate开头门控:- 开启 Simulation:直接
return - 关闭 Simulation:走旧
MovementComponent
- 开启 Simulation:直接
- 回滚能力来自同一构建内的
UseSimulationMovementA/B 开关。
Projectile / Pickup 现状
ProjectileSimData、PickupSimData已具备容器、绑定与生命周期同步通道- 当前仍未接入独立 Tick 行为,仅完成“创建/回收/索引同步”占位目标
P1.5 实测基线(P2 输入)
基线文档:docs/P1.5 Simulation-Supplement.md
关键结论:
TickEnemies GC在500/1000/1500/2000敌人数下均为0 KBGC Allocated In Frame从 P1 的29.5~109.7 KB降至2.1 KBTickEnemies热路径耗时(四阶段合计)对比 P1 降幅约22.8%~26.8%- Android 端评估以 CPU
ms为主,fps受 60 上限影响
自动化回归(P1.5 已补)
目录:
- Assets/Tests/Simulation/EditMode/SimulationWorldTickTests.cs
- Assets/Tests/Simulation/PlayMode/SimulationWorldPlayModeTests.cs
覆盖点:
- 敌人追踪玩家
- 进入攻击距离后停止移动
- 实体移除后的索引 remap 稳定性
后续扩展规范(必须遵守)
-
先扩数据,再扩行为
先在SimData增字段,再改EntitySync初始化与Tick逻辑,最后改表现层消费。 -
保留 A/B 路径
任何迁移都必须可在同一构建内通过开关回退到旧路径。 -
生命周期只走 EntitySync
禁止在敌人业务代码中手动改写 Simulation 容器,避免双写导致索引错乱。 -
维持“逻辑输出 / 表现消费”边界
Simulation 只产出逻辑结果,不直接触发 UI、特效、音频事件。 -
删除策略统一用 swap-back
所有 Simulation 容器删除都必须 remap 索引,严禁RemoveAt(i)直接删中间项。 -
热路径禁用托管分配
TickEnemies、互斥求解、阶段化循环里禁止 LINQ/临时集合扩张。
P2 前的已知技术债
AttackRange目前固定值1f,尚未由配置化数值驱动EnemySimData.TargetType/State语义仍偏轻量,未形成完整状态机合约- Projectile/Pickup 尚未迁移真实 Tick 行为
提交前检查清单
- 是否保持了
UseSimulationMovement关闭时行为不变 - 是否保持了
EntityId <-> SimulationIndex一致性(含移除 remap) - 是否避免在 Tick 热路径引入新 GC
- 是否将新字段接入了
EntitySync -> Tick -> Presentation全链路 - 是否补充了最小回归验证(至少 Battle 循环、敌人移除、索引稳定性)
- 是否同步更新本 Skill 文档与
docs/P1.5 Simulation-Supplement.md