vampire-like/skills/simulation-development/references/SimulationDevelopmentSkill.md

11 KiB
Raw Permalink Blame History

SimulationWorld Architecture Specification

文档定位

本文件是 SimulationWorld 的架构规范与扩展开发约束。

用途分为两部分:

  • 作为当前 SimulationWorld 实现的架构总览,说明模块职责、依赖边界、运行链路和数据所有权。
  • 作为后续扩展、重构、性能优化和回归修复时的约束文档,防止破坏核心不变量。

文档与实现冲突时,当前分支源码优先;提交前必须同步修正文档。

适用范围

  • Assets/GameMain/Scripts/Simulation/*
  • Assets/GameMain/Scripts/Procedure/Game/GameStateBattle.cs
  • Assets/GameMain/Scripts/Procedure/Game/ProcedureGame.cs
  • Assets/GameMain/Scripts/Utility/AIUtility.cs
  • Assets/Tests/Simulation/EditMode/*
  • Assets/Tests/Simulation/PlayMode/*

架构目标

  • 将战斗中的敌人、投射物、掉落物运行时状态收口到统一仿真容器。
  • 将热路径逻辑与 Unity 表现层解耦,避免在仿真阶段直接读写 Transform
  • 为 Job/Burst 提供稳定的数据通道、生命周期管理和主线程结算收口点。
  • 保持 SimulationWorld 对外是单一战斗调度入口,而不是分散的业务入口集合。
  • 保证扩展新仿真对象或新碰撞规则时,能够沿着既有管线接入,而不是旁路修改。

非目标

  • 不负责完整战斗规则定义。伤害公式、碰撞业务语义仍由 AIUtility 和实体逻辑承担。
  • 不负责实体创建策略。实体创建与隐藏仍由外部流程和 Entity 系统负责。
  • 不追求全局 ECS 化。本模块仍以 SimulationWorld + partial + Native 容器 为中心组织。
  • 不在 Job 中直接驱动表现层、事件系统或 Unity 对象生命周期。

外部依赖与系统边界

SimulationWorld 处于战斗流程中层,位于 GameStateBattle 和具体实体逻辑之间。

上游依赖:

  • GameStateBattle.OnUpdate 驱动每帧 Tick
  • GameEntry.Event 提供实体显示/隐藏事件,用于同步仿真容器生命周期。
  • GameEntry.Entity 提供实体查询、隐藏和表现事件消费。

下游协作:

  • AIUtility 负责伤害与碰撞业务结算。
  • Enemy/Projectile/Drop 实体提供初始化所需运行时数据。
  • Presentation 子模块负责把仿真结果写回表现层。

边界要求:

  • 外部业务代码不得直接增删 _enemies_projectiles_pickups
  • 外部业务代码不得绕过 SimulationWorld 直接维护仿真索引。
  • SimulationWorld 不直接拥有实体生成权,只消费实体生命周期事件。

模块结构

SimulationWorld 使用 partial 拆分,职责按以下边界划分:

  • SimulationWorld.cs
    • 核心组件入口、主状态容器、基础依赖、Unity 生命周期入口。
  • SimulationWorld.SimEntityState.cs
    • 敌人、投射物、掉落物的仿真态创建、更新、删除和清空。
  • SimulationWorld.EntitySync.cs
    • 监听实体 show/hide 事件,将实体生命周期映射到仿真容器。
  • DataChannel/SimulationWorld.JobDataChannel.cs
    • Native 容器持有、初始化、清理、容量准备、仿真数据到 Job 数据的转换。
  • Jobs/SimulationWorld.EnemyJobs.cs
    • 每帧仿真主编排、敌人移动与互斥分离 Job 调度。
  • Jobs/SimulationWorld.ProjectileJobs.cs
    • 投射物移动、寿命处理、越界回收。
  • Jobs/SimulationWorld.CollisionPipeline.cs
    • 投射物与区域碰撞查询构建、候选筛选、主线程命中结算。
  • SimulationWorld.TargetSelectionSpatialIndex.cs
    • 敌人目标选择空间索引。
  • Presentation/SimulationWorld.TransformSync.cs
    • LateUpdate 表现写回。
  • Presentation/SimulationWorld.HitPresentation.cs
    • 命中事件的表现消费桥。

核心数据所有权

主容器

  • _enemies
  • _projectiles
  • _pickups

这些容器是真实仿真态所有者。Job 输入输出缓冲只是当前帧的镜像通道,不是持久源数据。

绑定关系

  • EntityBinding 维护 EntityId <-> SimulationIndex 双向映射。
  • 容器删除使用 swap-back
  • 发生尾元素覆盖时,必须同步 RemapIndex
  • 删除完成后再 Unbind,避免索引悬挂。

Native 容器

  • Job 通道一律使用 Allocator.Persistent
  • 生命周期由 InitializeJobDataChannels / DisposeJobDataChannels 集中管理。
  • 帧间复用时使用 Clear,不允许用临时重建替代正常复用。
  • Job 数据与主容器数据之间的转换必须集中在 JobDataChannel 侧完成。

生命周期模型

实体进入仿真

统一由 EntitySync 监听实体显示事件后触发:

  • Enemy group -> RegisterEnemyLifecycle
  • Drop group -> RegisterPickupLifecycle
  • Bullet / Projectile / EnemyProjectile group -> RegisterProjectileLifecycle

实体退出仿真

统一由 EntitySync 监听实体隐藏事件后触发:

  • Enemy -> UnregisterEnemyLifecycle
  • Drop -> UnregisterPickupLifecycle
  • Projectile 相关 group -> UnregisterProjectileLifecycle

清场

ClearSimulationState 负责:

  • 清空主容器
  • 清空投射物回收与结算缓存
  • 清空区域碰撞请求与命中缓存
  • 清空 Job 通道
  • 清空全部 EntityBinding

运行时执行链路

帧级入口

  1. GameStateBattle.OnUpdate
  2. _enemyManager.OnUpdate(...)
  3. SimulationWorld.Tick(...)
  4. SimulationWorld.LateUpdate()

Tick 总流程

SimulationWorld.Tick 是战斗仿真的唯一主入口。

约束:

  • UseSimulationMovement == false 时,直接返回。
  • Tick 只负责逻辑仿真与结算,不直接写 Transform

每帧仿真管线

当前实现的标准顺序为:

  1. Early Return
    • DeltaTime <= 0 时只清理碰撞临时通道和统计。
  2. BuildInput
    • _enemies / _projectiles 同步为 Job 输入。
    • 准备敌人输出、投射物输出、碰撞查询缓冲。
  3. StateUpdate
    • 调度敌人移动 Job。
    • 调度投射物移动 Job。
  4. Schedule
    • 按需调度敌人互斥分离 Job。
    • 合并敌人与投射物 Job 依赖。
  5. Complete
    • 等待本帧仿真 Job 完成。
  6. Collision
    • 构建碰撞查询。
    • 构建敌人碰撞桶。
    • 生成候选并统计。
  7. WriteBack
    • 把输出写回主容器。
    • 在主线程结算碰撞与伤害。
    • 回收失效投射物。

LateUpdate

LateUpdate 只做表现写回,不做逻辑判定:

  • 敌人位置/朝向写回
  • 投射物位置/朝向写回

线程模型与边界

Job/Burst 允许做的事

  • 读取 Job 输入缓冲
  • 写入 Job 输出缓冲
  • 写入 NativeHashMap / NativeList 等碰撞与分桶数据
  • 执行纯数据计算

Job/Burst 禁止做的事

  • 读写 Transform
  • 操作 GameObject / Entity 生命周期
  • 调用事件系统
  • 直接调用 AIUtility.PerformCollision
  • 进行托管分配、LINQ、装箱

主线程必须做的事

  • 应用输出到仿真主容器
  • 命中结算与伤害计算
  • 投射物失效回收
  • 命中表现事件派发
  • LateUpdate 表现写回

子系统约束

敌人仿真

固定接入点:

  • BuildInput
  • Movement
  • Separation
  • WriteBack

约束:

  • 敌人状态必须以 EnemySimData 为中心流动。
  • 互斥与移动结果必须先写入输出缓冲,再统一提交。
  • 与目标选择相关的空间索引脏标记必须在主容器变更时维护。

投射物仿真

固定接入点:

  • BuildInput
  • Movement
  • Collision Query
  • Resolve
  • Recycle

约束:

  • 投射物生命周期状态必须由 ProjectileSimData.ActiveState 共同表达。
  • 投射物实际隐藏与移除只能在主线程回收阶段完成。

碰撞管线

职责:

  • 构建投射物查询和区域查询
  • 生成 broad-phase 候选
  • 在主线程做最终业务结算

约束:

  • Broad-phase 只能筛候选,不能替代最终命中判定。
  • Area Query 必须保留 SourceWasActiveAtQueryTime 快照语义。
  • 候选去重与区域命中去重只能在主线程收口。

目标选择空间索引

职责:

  • 提供按位置查询最近敌人的能力。

约束:

  • 仅在仿真启用时对外提供结果。
  • 敌人主容器变更后必须标记脏。
  • 索引是缓存,不是源数据;源数据仍是 _enemies

Presentation

职责:

  • 将仿真层结果写回表现层。
  • 消费命中表现事件。

约束:

  • Presentation 只消费仿真结果,不反向修改仿真逻辑状态。
  • 任何新增表现都应接在 Presentation 子模块,而不是接回 Job 或业务结算热路径。

不可破坏的不变量

生命周期单入口

仿真容器的增删必须只经过 EntitySyncSimEntityState

数据单一事实来源

主容器是持续态事实来源Job 缓冲只是帧级副本。

逻辑与表现分离

逻辑阶段不写 Transform,表现阶段不做业务结算。

索引一致性

任何 swap-back 删除都必须同步 remap否则视为架构级错误。

主线程结算收口

伤害、事件派发、实体隐藏和回收必须回到主线程。

空间索引与碰撞桶是缓存

它们可以重建,不可被外部业务当作持久数据依赖。

扩展开发流程

Step 0判定接入位置

先判断新需求属于:

  • 新仿真态字段
  • 新执行阶段逻辑
  • 新碰撞查询类型
  • 新表现桥接

不要一开始就直接改 Tick 主流程。

Step 1扩状态

先补 SimData、必要的 Job 输入输出结构和转换逻辑。

Step 2接生命周期

如果是新实体类型,先定义 show/hide 到仿真态的映射,再进入执行阶段。

Step 3接执行管线

优先复用已有阶段;只有确实无法收纳时才新增阶段,并补可观测的 profiler 标记。

Step 4接主线程结算

需要业务判定、伤害、事件派发、实体隐藏时,一律回主线程收口。

Step 5接表现

视觉写回、命中反馈、临时特效都放在 Presentation 侧。

Step 6补测试

至少覆盖:

  • 正常行为
  • 空容器和边界条件
  • 删除后的索引稳定性
  • 碰撞去重或快照语义
  • 与旧路径一致的关键行为

Step 7更新文档

修改模块边界、数据契约、不变量或执行阶段时,必须同步更新本文件。

回归关注点

  • ClearSimulationState 是否把主容器、缓存和 binding 一并清干净。
  • 删除路径是否保持 swap-back + remap 一致。
  • Job Native 容器是否有泄漏或容量管理回退。
  • 是否在热路径引入托管分配。
  • 是否让表现逻辑重新侵入仿真逻辑。
  • 是否破坏 Area Query 快照语义。
  • 是否破坏碰撞候选与命中去重。

测试建议

至少保留并持续扩展以下类型的测试:

  • Tick 行为正确性
  • 主线程与 Job 管线一致性
  • 最近敌查询正确性
  • 投射物候选上限与玩家候选覆盖
  • Area Query 快照语义
  • 清场和 Battle 循环稳定性

维护原则

如果未来需要继续扩展为多模式仿真开关、更多 Job 管线层级或新的仿真对象类型,应优先维护以下三点:

  • Tick 仍然只有一个主入口
  • 仿真态生命周期仍然只有一个注册/反注册入口
  • 主线程结算与表现写回边界不被打穿