- DroneType 枚举清理:移除 Electric/Piston,重编号 HighSpeed=2 - planner_config.json / defaults.json 同步迁移 - 238 单元测试全部通过
93 KiB
反无人机仿真系统 — 总体架构设计
版本:V14
日期:2026-06-17
状态:已实现
变更:V14 新增 DataService 基础数据 CRUD + FrameDataStore 内存回放架构
Unity 版本:22.3.62
.NET 版本:.NET Standard 2.1
变更:V13 探测行为决策固化——5 项规则确认(回退 Undetected / 融合取最早-同刻取精度高 / 不影响 FireSchedule / 盲区仅可视化 / 3D 球冠);新增 14.2.1 三维球冠几何(IsInCoverage)+ 14.7 数据模型变更清单;EquipmentDeployment 表加 4 个 3D 探测字段;DetectionSource 同步扩展
历史变更:V12 探测双链路设计(静态规划+实时探测待实现);V11 文档校准 + 第十三章性能设计;V10 Group 表移除,批次+火力单元概念升级
一、系统概述
本系统对新型气溶胶武器的三种防空毁伤机制(吸入式灭火、爆燃式、吸入式爆炸)进行数字化仿真。通过参数化配置与可视化推演,量化评估新型气溶胶武器对不同类型空中威胁源目标(动力系统)的毁伤效能。
系统分为三大模块:
| 模块 | 核心功能 |
|---|---|
| 模型管理 | 3D 模型导入/预览/删除 |
| 想定管理 | 仿真任务 CRUD,步骤化配置向导,管控区域设置,搜索分页 |
| 仿真报告 | 报告列表,含时序图的完整报告预览,导出 PDF/Word,删除 |
要求来源对照
本设计融合两份需求文档:
| 文档 | 侧重 |
|---|---|
| V1.0 反无人机仿真系统功能需求 | UI 交互、业务流程、业务字段 |
| 技术要求终版 | 毁伤机制、装备体系、可视化效果、性能指标、第三方算法集成 |
二、架构分层
┌──────────────────────────────────────────────────────────┐
│ Unity UI Layer │
│ 模型管理 │ 想定管理(含管控区域) │ 仿真运行&回放 │ 仿真报告 │
├──────────────────────────────────────────────────────────┤
│ Unity Application Layer │
│ ModelPanel | ScenarioWizard | SimulationRunner | │
│ ReplayController | ReportPanel │
├──────────────────────────────────────────────────────────┤
│ Service Layer (Pure C#) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ ModelService │ │ScenarioService│ │ SimulationEngine │ │
│ │ │ │ DefensePlanner │ │ ├ DroneEntity │ │
│ └──────────────┘ └──────────────┘ │ ├ PlatformEntity │ │
│ ┌──────────────┐ ┌──────────────┐ │ ├ MunitionEntity │ │
│ │FrameDataStore│ │ReportService │ │ ├ CloudEntity │ │
│ │ │ │ ├ ReportGen │ │ │ └ GaussianPuff│ │
│ └──────────────┘ │ └ Markdown │ │ ├ ControlZone │ │
│ └──────────────┘ │ ├ IDamageModel×3 │ │
│ │ └ FireSchedule │ │
│ ┌──────────────────────────────────────────────────────┐│
│ │ Algorithm Layer (自主实现) ││
│ │ GaussianPuffDispersion | DamageCalculator ││
│ │ DefaultDefensePlanner | Kinematics(PG稳定度) ││
│ └──────────────────────────────────────────────────────┘│
├──────────────────────────────────────────────────────────┤
│ Data Layer │
│ ┌────────────────────────────┐ ┌────────────────────┐ │
│ │ main.db (SQLite) │ │ frames/ (SQLite) │ │
│ │ 11张业务表 + SimEvent │ │ 每任务一个 .db │ │
│ │ + SimulationReport │ │ SimFrameRecord │ │
│ │ < 100MB,永久保留 │ │ 6个月 TTL │ │
│ └────────────────────────────┘ └────────────────────┘ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ FileSystem │ │
│ │ 模型文件(.fbx/.obj/.stl/.glb/.gltf) + 预览图 │ │
│ └────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
各层职责
| 层 | 依赖 | 职责 |
|---|---|---|
| Data Layer | 主库 SQLite + 任务分库 SQLite + 文件系统 | 主库存元数据/事件/报告(<100MB,永久保留);分库按任务存帧数据(6月TTL);文件系统存模型文件 |
| Algorithm Layer | 纯 C#,零外部依赖 | 气溶胶扩散模型、毁伤计算、防御推荐算法(全部自主实现) |
| Service Layer | Data Layer + Algorithm Layer | 纯业务逻辑、仿真引擎、录制、报告生成 |
| Application Layer | Service Layer + Unity | MonoBehaviour 桥接,生命周期协调,驱动仿真 |
| UI Layer | Application Layer | UGUI 页面、弹窗、3D 预览、回放、粒子参数传递 |
2.1 程序集拆分:Core 独立于 Unity
后端代码拆分为两个程序集,确保 Core 可脱离 Unity 独立编译和测试:
CounterDrone.Core.dll ← .NET Standard 2.1 类库
零 UnityEngine 引用
可在任何 .NET 环境运行
├── /Models 数据模型(POCO)
├── /Repository 数据访问(SQLite + 文件存储)
├── /Services 业务服务(ModelService, ScenarioService 等)
├── /Simulation 仿真引擎 + 实体(SimulationEngine, DroneEntity, ...)
└── /Algorithms 扩散模型、毁伤模型、防御推荐
CounterDrone.Core.Tests.dll ← xUnit 测试项目
直接引用 Core.dll
dotnet test 即可运行
├── /Unit 单元测试(每个类独立测)
└── /Integration 集成测试(完整仿真流程)
Unity 项目/Assets/Scripts/ ← Unity 侧
├── /Managers MonoBehaviours 桥接层(薄)
└── /UI UGUI 绑定
引用 → CounterDrone.Core.dll(放在 Assets/Plugins/)
2.2 Unity 依赖隔离:关键设计
| 隔离点 | 做法 |
|---|---|
| 时间 | engine.Tick(float deltaTime) — Unity 传 Time.deltaTime,测试传固定值 0.05f |
| 文件路径 | Core 使用接口 IPathProvider → Unity 实现返回 Application.persistentDataPath,测试返回 ./testdata/ |
| 日志 | Core 使用 Microsoft.Extensions.Logging 抽象 → Unity 桥接到 Debug.Log,测试用控制台 |
| 模型加载 | Core 不涉及 3D 渲染,只管理文件路径和元数据 |
| 粒子参数 | Core 输出 ParticleParams 数据对象 → Unity 读值设置 ParticleSystem |
2.3 Manager 桥接层示例(极薄)
// Unity 侧 SimulationRunner.cs — 仅负责桥接
public class SimulationRunner : MonoBehaviour
{
private ISimulationEngine _engine;
private IRecordService _recorder;
void Start()
{
// 通过 DI 或工厂获取实例(Core 创建)
_engine = SimulationEngineFactory.Create(scenarioId);
_recorder = ServiceFactory.CreateRecordService();
}
void Update()
{
if (_engine.State != SimulationState.Running) return;
// 核心只有这一行:把 Unity 时间传入纯 C# 引擎
var result = _engine.Tick(Time.deltaTime);
// 更新 Unity 侧的 3D 物体位置(根据 result 中的实体快照)
UpdateVisuals(result.EntitySnapshots);
}
void UpdateVisuals(List<EntitySnapshot> snapshots)
{
foreach (var s in snapshots)
{
// 找到对应 GameObject,设置 transform
_entityMap[s.EntityId].transform.position = s.Position;
}
}
}
2.4 测试策略
| 测试类型 | 工具 | 内容 |
|---|---|---|
| 单元测试 | xUnit + Moq | Repository 增删改查、Service 业务逻辑、Algorithm 计算正确性 |
| 仿真确定性测试 | xUnit | 固定输入 + 固定 deltaTime → 断言帧 N 的实体位置 = 预期值 |
| 集成测试 | xUnit + SQLite 文件 | 完整想定配置 → 启动仿真 → 跑 1000 帧 → 断言事件序列和对抗结果 |
| 性能验证 | 实测帧率 | Unity 实测:满载场景渲染帧率从优化前 <20 FPS 提升至 200+ FPS(详见第十四章) |
Core 测试无需 Unity,
dotnet test即可运行全部测试,适合 CI 流水线。
三、枚举定义
// === 任务 ===
enum TaskStatus { Draft = 0, Configuring = 1, Completed = 2 }
// === 场景 ===
enum SceneType { Urban = 0, Plain = 1, Mountain = 2, Coast = 3 }
enum WeatherType { Sunny = 0, Overcast = 1, Fog = 2, Rain = 3, Night = 4 }
enum WindDirection { N = 0, NE = 1, E = 2, SE = 3, S = 4, SW = 5, W = 6, NW = 7 }
// === 目标 ===
enum DroneType { Rotor = 0, FixedWing = 1, Electric = 2, Piston = 3, HighSpeed = 4 }
enum PowerType { Electric = 0, Piston = 1, Jet = 2 }
// === 装备 & 搭载平台 ===
enum PlatformType { AirBased = 0, GroundBased = 1 } // 空基(大型无人机) / 地基(炮弹)
enum EquipmentRole { Detection = 0, LaunchPlatform = 1 } // 装备角色:探测 / 发射平台
// === 气溶胶 & 毁伤 ===
enum AerosolType { InertGas = 0, ActiveMaterial = 1, ActiveFuel = 2 }
// 对应的毁伤机制名称:吸入式灭火 / 爆燃式 / 吸入式爆炸
enum ReleaseMode { Single = 0, Continuous = 1, Pulse = 2 }
enum TriggerMode { Time = 0, Area = 1, Manual = 2 }
// === 毁伤状态阶段(EntitySnapshot.DamageStage,前端直读;入库时进 StateData JSON)===
enum DamageStage { Normal = 0, EngineAnomaly = 1, AttitudeLoss = 2, Destroyed = 3 }
// === 编队 ===
enum FormationMode { Single = 0, Formation = 1, Swarm = 2 }
// === 核心作战概念 ===
// 火力单元(FireUnit):能独立完成搜索、跟踪、瞄准并实施打击的最小作战实体。
// 包含探测(雷达/光电/红外)+ 打击(发射架/火炮)的完整闭环系统。
// 配置层 = EquipmentDeployment(LaunchPlatform),含探测字段 + 打击字段。
// 独立探测节点(DetectionNode):只探测不打击的侦查节点(如前沿警戒雷达)。
// 配置层 = EquipmentDeployment(Detection),仅含探测字段。
// 两者都往统一信息网络送探测信息,planner 基于融合后的探测边界规划。
// 无人机批次(DroneWave):有共同航路的一组无人机(攻击方)。
// 批次关联航路但不独占(多个批次可共享同一条航路)。
// 配置层 = DroneProfile(含 WaveId) + RoutePlan(ScenarioId, WaveId) + Waypoints。
// === 运行时 ===
enum EntityType { Drone = 0, Platform = 1, DetectionEquip = 2, Cloud = 3, Munition = 4 }
enum SimEventType
{
TargetDetected = 0, // 探测发现目标【待实现,当前未触发,详见第十四章】
MunitionLaunched = 1,
CloudGenerated = 2,
DroneEnteredCloud = 3,
DroneDestroyed = 4,
DroneReachedTarget = 5,
ZoneIntruded = 6, // 无人机侵入管控区域
WaypointReached = 7,
SimulationEnd = 8
}
// === 报告 ===
enum InterceptResult { Success = 0, Partial = 1, Failed = 2 }
enum PositionMode { PointSelect = 0, ManualInput = 1 }
四、数据模型设计
4.1 存储架构:主库 + 任务分库
CounterDroneBackend_Data/
├── main.db ← 主库(< 100MB,永久保留)
│ ├── 11 张业务配置表
│ ├── SimEvent ← 事件表(~30 条/任务)
│ └── SimulationReport
│
├── frames/ ← 帧数据分库(6 个月 TTL)
│ ├── {scenarioId_001}.db ← 单任务 ~161 MB(30min 仿真)
│ └── {scenarioId_002}.db
│
└── models/ ← 模型文件 + 预览图
4.2 数据表总览
共 11 张业务表(主库) + 1 张运行时表(主库) + 1 张运行时表(任务分库):
主库(main.db)— 永久保留:
ModelInfo — 模型元数据
AmmunitionSpec — 弹药基础参数(第三方数据,NEW)
Scenario — 仿真任务主表
CombatScene — 步骤1:作战场景(含24h时间、天气)
ControlZone — 步骤1+:管控区域/电子围栏(NEW)
DroneProfile — 步骤2:目标配置(含 WaveId 关联批次)
EquipmentDeployment — 步骤3:装备部署(火力单元含探测+打击,或独立探测节点)
CloudDispersal — 步骤4:云团抛撒配置
RoutePlan — 步骤5:航路规划(1:N Scenario,多批次;批次关联但不独占航路,FK 关联 DroneProfile.WaveId)
Waypoint — 航路点
SimulationReport — 仿真报告
SimEvent — 仿真事件(时序图数据源)
任务分库(frames/{scenarioId}.db)— 6 个月 TTL:
SimFrameRecord — 逐帧位置记录(回放用)
核心作战概念:
- 火力单元(FireUnit):能独立完成搜索、跟踪、瞄准并实施打击的最小作战实体。包含探测(雷达/光电/红外)+ 打击(发射架/火炮)的完整闭环。配置层 =
EquipmentDeployment(LaunchPlatform)。- 独立探测节点(DetectionNode):只探测不打击的侦查节点(如前沿警戒雷达)。配置层 =
EquipmentDeployment(Detection)。- 无人机批次(DroneWave):有共同航路的一组无人机(攻击方)。批次关联航路但不独占(多个批次可共享同一条航路)。配置层 =
DroneProfile(WaveId)+RoutePlan(ScenarioId, WaveId)+Waypoints。- 原来的
Group(编组)表已移除:无人机编队概念升级为"批次",装备编组概念不再需要(火力单元是独立作战单位)。
4.3 业务表详细设计
ModelInfo — 模型元数据
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| Id | TEXT | PK | GUID |
| Name | TEXT | NOT NULL | 模型名称 |
| ModelType | TEXT | 分类标签 | |
| FilePath | TEXT | persistentDataPath 下的相对路径 | |
| FileSize | REAL | MB | |
| PreviewPath | TEXT | 预览图路径 | |
| HasHighResTexture | INTEGER | DEFAULT 0 | 是否含 2K+ 贴图 |
| CreatedAt | TEXT | 导入时间 ISO8601 |
支持格式:.fbx / .obj / .stl / .glb / .gltf,单文件 ≤ 500MB,支持 2K 及以上分辨率贴图。
Scenario — 仿真任务主表
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| Id | TEXT | PK | GUID |
| Name | TEXT | NOT NULL | 任务名称 |
| TaskNumber | TEXT | UNIQUE | SIM-yyyyMMdd-xxx |
| Status | INTEGER | DEFAULT 0 | 0=草稿 1=配置中 2=已完成 |
| CurrentStep | INTEGER | DEFAULT 1 | 当前配置步骤 1-5 |
| TickRate | INTEGER | DEFAULT 20 | 仿真 Tick 频率(Hz) |
| CreatedAt | TEXT | ||
| UpdatedAt | TEXT |
CombatScene — 步骤1:作战场景
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| ScenarioId | TEXT | PK, FK | 1:1 Scenario |
| SceneType | INTEGER | 0城市 1平原 2山区 3海岸 | |
| TimeOfDay | TEXT | DEFAULT "12:00" | NEW 24h 制时间,如 "14:30" |
| WeatherType | INTEGER | 0晴 1阴 2雾 3雨 4夜 | |
| WindSpeed | REAL | DEFAULT 5 | m/s,范围 0-30 |
| WindDirection | INTEGER | DEFAULT 0 | 八方位 0-7 |
| Temperature | REAL | DEFAULT 20 | ℃ |
| Humidity | REAL | DEFAULT 60 | %RH |
| Pressure | REAL | DEFAULT 1013 | hPa |
| Visibility | REAL | DEFAULT 5000 | m,范围 100-10000 |
| SceneWidth | REAL | DEFAULT 4000 | NEW 场景宽 m(≥4m) |
| SceneLength | REAL | DEFAULT 10000 | NEW 场景纵深 m(≥100m) |
| SceneHeight | REAL | DEFAULT 1000 | NEW 场景高 m(≥10m) |
场景空间范围最低要求:4m × 100m × 10m。默认值 4000m × 10000m × 1000m 可配置。
ControlZone — 步骤1扩展:管控区域 / 电子围栏(NEW)
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| Id | TEXT | PK | GUID |
| ScenarioId | TEXT | FK | |
| Name | TEXT | 区域名称 | |
| VerticesJson | TEXT | NOT NULL | 多边形顶点 JSON:[{"x":0,"y":0,"z":0},...] |
| MinAltitude | REAL | 区域底高 m | |
| MaxAltitude | REAL | 区域顶高 m | |
| OrderIndex | INTEGER | 多个区域的顺序 |
管控区域在场景设置(地形+天气)之后配置。无人机进入管控区域 → 触发 ZoneIntruded 事件 → 该无人机任务失败。
DroneProfile — 步骤2:目标配置
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| Id | TEXT | PK | GUID |
| ScenarioId | TEXT | FK | |
| WaveId | TEXT | FK | 关联无人机批次(同一 WaveId 的无人机共享航路) |
| DroneType | INTEGER | 0旋翼 1固定翼 2电推 3活塞 4高速 | |
| Quantity | INTEGER | DEFAULT 1 | 目标数量 |
| PowerType | INTEGER | 0电推 1活塞 2喷吸气 | |
| Wingspan | REAL | m | |
| TypicalSpeed | REAL | km/h | |
| TypicalAltitude | REAL | m |
目标类型默认值:
| 目标类型 | 翼展(m) | 典型速度(km/h) | 典型飞行高度(m) |
|---|---|---|---|
| 旋翼 | 1.2 | 60 | 300 |
| 固定翼 | 3.5 | 150 | 1000 |
| 电推 | 1.8 | 100 | 500 |
| 活塞 | 2.5 | 120 | 800 |
| 高速目标 | 1.5 | 300 | 2000 |
EquipmentDeployment — 步骤3:装备部署(火力单元 + 独立探测节点)
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| Id | TEXT | PK | GUID |
| ScenarioId | TEXT | FK | |
| EquipmentRole | INTEGER | 0=独立探测节点 1=火力单元(发射平台) | |
| Quantity | INTEGER | DEFAULT 1 | 部署数量 |
| WaveId | TEXT | NULLABLE | 可选关联批次(用于 UI 分组,火力单元是独立作战单位,不强依赖批次) |
| // 以下为火力单元(发射平台)专用字段 | |||
| PlatformType | INTEGER | NULLABLE | 0=空基(大型无人机) 1=地基(炮弹),独立探测节点为 NULL |
| PositionX | REAL | 部署位置 | |
| PositionY | REAL | ||
| PositionZ | REAL | ||
| AerosolType | INTEGER | NULLABLE | 挂载气溶胶类型,独立探测节点为 NULL |
| MunitionCount | INTEGER | NULLABLE | 挂载弹药数量 |
| MuzzleVelocity | REAL | NULLABLE | NEW 弹药初速 m/s(地基炮弹用) |
| ReleaseAltitude | REAL | NULLABLE | NEW 弹药释放高度 m(空基平台用) |
| Cooldown | REAL | DEFAULT 5 | 发射冷却时间 s(同弹种连发间隔) |
| AmmoChangeTime | REAL | DEFAULT 300 | NEW 更换弹种所需时间 s(默认 5 分钟) |
| // 探测能力(火力单元自带 + 独立探测节点均有) | |||
| RadarRange | REAL | NULLABLE | 雷达探测距离 m(雨雾不衰减) |
| EORange | REAL | NULLABLE | 光电探测距离 m(受 Visibility 衰减) |
| IRRange | REAL | NULLABLE | 红外探测距离 m(不受 Visibility 影响) |
| DetectionAccuracy | REAL | NULLABLE | 探测精度 m(位置误差,影响抛撒散布范围) |
| // 三维球冠几何(决策 5,待新增字段;详见第十四章) | |||
| MinElevation | REAL | NULLABLE | 俯仰角下限(度) |
| MaxElevation | REAL | NULLABLE | 俯仰角上限(度) |
| MinDetectAlt | REAL | NULLABLE | 可探测高度下界 m |
| MaxDetectAlt | REAL | NULLABLE | 可探测高度上界 m |
装备体系说明:
- 搭载平台(2 种):
- 空基(大型无人机):飞到释放点投放弹药 → 弹药自由落体/滑翔至云团生成点
- 地基(炮弹):地面发射 → 抛物线弹道 → 到达释放点生成云团
- 探测设备:目标搜索发现与跟踪,输出目标坐标给发射平台
- 弹药类型决定生成云团的 AerosolType 和覆盖体积
CloudDispersal — 步骤4:云团抛撒配置
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| ScenarioId | TEXT | PK, FK | 1:1 Scenario |
| AerosolType | INTEGER | 0吸入式灭火 1爆燃式 2吸入式爆炸 | |
| DisperseHeight | REAL | DEFAULT 300 | m |
| TriggerMode | INTEGER | 0时间 1区域 2手动 | |
| SpreadAngle | REAL | DEFAULT 0 | 度 |
| ReleaseMode | INTEGER | 0单次 1连续 2脉冲 | |
| InitialScale | REAL | DEFAULT 1000 | m³ |
| Duration | REAL | DEFAULT 60 | s |
| ReleaseInterval | REAL | DEFAULT 5 | s |
| TotalAmount | REAL | DEFAULT 50 | kg |
| PositionX | REAL | 抛撒坐标 | |
| PositionY | REAL | ||
| PositionZ | REAL | ||
| PositionMode | INTEGER | 0三维点选 1手动输入 2算法推荐(NEW) | |
| // 算法推荐结果(NEW) | |||
| RecommendedPosX | REAL | NULLABLE | 算法推荐最佳抛撒位置 |
| RecommendedPosY | REAL | NULLABLE | |
| RecommendedPosZ | REAL | NULLABLE | |
| RecommendedTiming | REAL | NULLABLE | 算法推荐最佳触发时机(仿真秒) |
| CriticalPosX | REAL | NULLABLE | 最危险值—抛撒位置 |
| CriticalPosY | REAL | NULLABLE | |
| CriticalPosZ | REAL | NULLABLE | |
| CriticalTiming | REAL | NULLABLE | 最危险值—触发时机 |
RoutePlan — 步骤5:航路规划
支持多批次:通过
ScenarioId + WaveId唯一标识一条航路,一个任务可有多个批次各自独立航路。
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| ScenarioId | TEXT | PK(复合) | |
| WaveId | TEXT | PK(复合) | 关联批次:同一任务不同批次可有独立航路(多个批次可共享同一条航路) |
| FormationMode | INTEGER | 0单机 1编队 2蜂群 | |
| FormationSpacing | REAL | DEFAULT 50 | m |
| ETA | TEXT | 预计到达时间 |
Waypoint — 航路点
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| Id | TEXT | PK | GUID |
| ScenarioId | TEXT | FK | |
| OrderIndex | INTEGER | NOT NULL | 航路顺序 |
| PosX | REAL | 坐标(支持三维点选) | |
| PosY | REAL | ||
| PosZ | REAL | ||
| Altitude | REAL | 该航点飞行高度 m | |
| Speed | REAL | 该段速度 km/h |
Group — 编组(已移除)
概念升级:原 Group 表的两种类型已分别升级:
DroneFleet(无人机编队)→ 无人机批次(DroneWave):由DroneProfile(WaveId)+RoutePlan(ScenarioId, WaveId)+Waypoints隐式表达。批次关联航路但不独占(多个批次可共享同一条航路)。EquipmentGroup(装备编组)→ 不再需要。火力单元是独立作战的最小单位,不需要上层编组。数据层:
Group表保留但不再主动使用(向后兼容)。DroneProfile.WaveId语义为"批次 ID"。
4.4 运行时表
注意:SimEvent 和 SimulationReport 存储在主库 main.db;SimFrameRecord 存储在各任务的独立分库 frames/{scenarioId}.db 中。
SimFrameRecord — 逐帧记录
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| Id | INTEGER | PK AUTOINCREMENT | |
| ScenarioId | TEXT | FK | |
| FrameIndex | INTEGER | 帧序号 | |
| Timestamp | REAL | 仿真时间(秒) | |
| EntityId | TEXT | ||
| EntityType | INTEGER | 实体类型枚举 | |
| PosX | REAL | ||
| PosY | REAL | ||
| PosZ | REAL | ||
| RotX | REAL | ||
| RotY | REAL | ||
| RotZ | REAL | ||
| StateData | TEXT | JSON,由 EntityType 决定结构。仅 Flush 入库时生成,仿真期间保持空 |
StateData 双轨设计:仿真热路径上
EntitySnapshot携带强类型字段(Hp/CloudRadius/CloudOpacity等),前端直接读取,零 JSON 解析;仅FrameDataStore.Flush入库时按EntityType序列化为 JSON 字符串写入SimFrameRecord.StateData,供历史回放解析。StateData JSON 按实体类型差异化(字段名与 Flush 中模板严格一致):
// Drone: { "damageStage": 0, "hp": 0.75 } // DamageStage 枚举;剩余血量 0~1 // Platform(仅空基平台记录快照): { "state": "Flying" } // 平台状态字符串 // Cloud: { "radius": 150.0, "opacity": 0.8, "phase": 2, "elapsed": 35.0 } // radius=当前半径m, opacity=透明度, phase=扩散阶段(1/2/3), elapsed=已存在秒数
SimEvent — 仿真事件(时序图数据源)
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| Id | TEXT | PK | GUID |
| ScenarioId | TEXT | FK | |
| OccurredAt | REAL | 仿真时间(秒) | |
| EventType | INTEGER | 事件类型枚举 | |
| SourceId | TEXT | 源实体 ID | |
| TargetId | TEXT | 目标实体 ID | |
| DataJson | TEXT | 事件携带的结构化数据 | |
| Description | TEXT | 事件描述 |
五、仿真引擎设计
5.1 核心实体关系
SimulationEngine
│
├── SimulationState ← 状态(运行/暂停/结束)
├── SceneConfig ← 配置快照(含场景边界、管控区域)
├── List<DroneEntity> ← 无人机(目标方)
│ └── 按批次组织,共享航路
├── List<PlatformEntity> ← 搭载平台(发射方)★重构
│ ├── AirBasedPlatform ← 空基(大型无人机)
│ └── GroundBasedPlatform ← 地基(火炮)
├── List<DetectionEntity> ← 探测设备(统一信息网络)★实时探测【待实现】
│ ├── 持有 DetectionSource(位置/雷达/光电/红外距离/精度)
│ └── 每个威胁的探测状态机:Undetected → Detected → Tracked
├── List<MunitionEntity> ← 飞行中的弹药
│ ├── 空基投放:短程自由落体/滑翔
│ └── 地基发射:抛物线弹道
├── List<CloudEntity> ← 气溶胶云团 ★核心毁伤载体★
│ └── GaussianPuffDispersion(三阶段扩散)
├── List<ControlZone> ← 管控区域(电子围栏)
├── FireSchedule ← 发射计划(推荐算法生成)
└── FrameDataStore ← 帧数据写入(任务分库)
探测设备的两阶段角色(详见第十五章):
- 事前规划阶段(已实现):
DefensePlanner调用DetectionCalculator.EarliestDetection静态估算每个威胁的最早发现弧长,反推拦截时机。- 运行时实时探测(待实现):
DetectionEntity在 Tick 内扫描飞行无人机,首次进入探测范围 → 触发TargetDetected事件。两阶段共用同一EffectiveRange物理公式保证一致。
5.2 拦截链路(完整流程)
系统存在两条并行的探测链路,分别服务于"事前规划"和"运行时观测":
═══ 链路 A:事前规划(已实现,开环)═════════════════════════════
仿真启动前 DefensePlanner.Plan():
DetectionCalculator.EarliestDetection(threatRoute, sources, visibility)
│ 静态计算每个威胁航路进入探测网的最早弧长
▼
拦截时机 = 最早发现弧长 + 弹药飞行时间反推
│ 一次性生成 FireSchedule(开环,不依赖运行时观测)
▼
SimulationEngine 按 FireSchedule 时间表执行发射
═══ 链路 B:运行时实时探测(待实现,闭环观测)═════════════════
Tick 内 DetectionEntity 扫描飞行无人机:
│ 首次进入 EffectiveRange → 状态 Undetected→Detected
▼
触发 OnTargetDetected(TargetDetected 事件 → 写 SimEvent)
│ 输出目标当前坐标(含探测精度散布)
▼
持续跟踪(Detected→Tracked)→ 坐标更新
│ (未来:驱动动态火控重分配 / 自适应拦截)
▼
与链路 A 的静态计划协同:观测印证规划 / 补救漏网目标
═══ 公共后段(两条链路最终汇入)═══════════════════════════════
火控分配(分配给可用搭载平台)
│
├── 空基平台:固定阵位发射(水平初速 + 重力下落)
│ │
│ ▼
│ 弹药飞行至目标高度
│
└── 地基平台:计算射击诸元
│
▼
发射炮弹(抛物线弹道飞向目标区域)
│
▼
弹药到达释放点 → 生成 CloudEntity
MunitionEntity 销毁
│
▼
云团扩散膨胀(三阶段模型:爆轰→湍流→高斯)
│ 持续膨胀 + 消散计时
▼
无人机进入云团范围
│ 施加毁伤(IDamageModel 接口,三种机制自主实现)
│ 吸入式灭火 / 爆燃式 / 吸入式爆炸
▼
无人机被摧毁 / 存活
│ 毁伤阶段状态输出到 StateData
▼
Unity 渲染毁伤效果(尾焰异常、姿态失控、坠落)
当前实现状态:链路 A(静态规划)已完整实现,是拦截时机的唯一来源;链路 B(实时探测)尚未接入 Tick,
TargetDetected事件已定义但未触发。详见第十五章探测设备设计。
5.3 仿真 Tick 流程
1. 【发射阶段】
按 FireSchedule 执行:
├─ SimulationTime ≥ FireTime → 平台直接发射弹药
├─ 空基发射:弹药以巡航速度水平射出,重力下落到目标高度
└─ 触发 OnMunitionLaunched 事件
2. 【弹药飞行 → 云团生成】
FOR EACH MunitionEntity:
├─ 抛物线弹道(地基)/ 自由落体+水平惯性(空基)
└─ 到达释放高度 → AlgorithmFactory.Create<ICloudDispersionModel>() → 生成 CloudEntity
→ 触发 OnCloudGenerated,弹药移除(倒序索引)
3. 【云团演化】
FOR EACH CloudEntity:
├─ GaussianPuffDispersion.Tick():三阶段扩散(爆轰→湍流→高斯)
├─ 更新粒子参数(半径、透明度)
└─ 消散条件满足 → 移除(倒序索引)
4. 【无人机移动】
├─ 预过滤 Flying 状态无人机到复用缓冲 _flyingDrones(避免多次遍历)
├─ SavePreviousPosition(为毁伤路径积分保存上一帧位置)
└─ FOR EACH 飞行 DroneEntity:Update(deltaTime)(弧长驱动,详见 5.4)
到达终点 → ReachedTarget → 触发 OnDroneReachedTarget
5. 【实时探测扫描】★待实现(见第十五章)
FOR EACH DetectionEntity × 飞行无人机:
├─ effRange = DetectionCalculator.EffectiveRange(radar, eo, ir, visibility)
├─ ‖drone - detector‖ ≤ effRange?
├─ 是 且 drone 探测状态 == Undetected → 状态置 Detected
│ ├─ 触发 OnTargetDetected(TargetDetected 事件,SourceId=探测设备, TargetId=无人机)
│ └─ 输出含精度散布的目标坐标(散布半径 = DetectionCalculator.SpreadRadius(accuracy))
├─ 持续在范围内 → 状态升 Tracked(坐标持续刷新)
└─ 离开范围 → (策略待定:保持 Tracked 或回退)
> 当前此步缺失:探测只在事前 planner 静态计算(链路 A),Tick 内无实时探测。
6. 【毁伤判定】 FOR EACH 飞行无人机 × CloudEntity:
├─ 快速排斥 1:cloud.CoreDensity < 有效浓度阈值 → 跳过
├─ 快速排斥 2:‖drone - cloudCenter‖ > 2×EffectiveRadius → 跳过(不可能穿云)
├─ DamageAssessment.PathInSphere:积分无人机本帧路径段在球体内的长度 inCloudDistance
│ (云团参考系修正:扣除本帧风偏位移 cloudD×dt,使无人机表现为"穿云")
├─ exposureIncrement = inCloudDistance / (TypicalSpeed/3.6)
├─ drone.ExposureTime += exposureIncrement
├─ dmg = IDamageModel.CalculateDamage(...);drone.ApplyDamage(dmg)
└─ HP ≤ 0 → Destroyed → 触发 OnDroneDestroyed
7. 【平台冷却】
FOR EACH PlatformEntity:Update(deltaTime)
8. 【管控区域判定】
FOR EACH 飞行无人机 × ControlZone:
├─ 高度区间检查 + Point-in-Polygon(顶点 2D 数组构造时缓存,每帧零分配)
└─ 侵入 → MarkZoneIntruded → 触发 OnZoneIntruded
9. 【结束判定】
所有无人机 Status ≠ Flying → SimulationState.Completed → 触发 OnSimulationEnded
10. 【录制】(若 RecordFrames)
├─ CollectSnapshots:填充强类型字段(位置/Hp/云团半径等)到复用池,零字符串分配
└─ FrameDataStore.RecordFrame:仅追加内存缓冲 List<FrameRecord>
5.4 实体行为模型
DroneEntity(无人机)
属性:
- 批次 WaveId、航路点队列 Waypoint[]
- 当前位置、速度、朝向
- HP(0~1 归一化)
- DamageStage(Normal → EngineAnomaly → AttitudeLoss → Destroyed)
- 目标类型 DroneType、动力类型 PowerType
- 存活状态(飞行中/抵达目标/侵入管控区/被摧毁)
运动模型(弧长驱动,统一在 RouteGeometry):
- 构造时一次性预计算航路几何缓存:_totalArc(总弧长)、_segLen[](各段段长)、_cumArc[](累积弧长)
- 每 Tick:_traveledArc += (TypicalSpeed/3.6) × deltaTime
- 在 _cumArc[] 中定位所在段,分段线性插值得到 3D 位置
- 无人机严格按航路匀速飞行,**不受风偏影响**(真实无人机有飞控修正航向)
- 云团受风偏影响 → 毁伤判定中对云团做参考系修正(详见 5.3 第4步)
- 到达判定:_traveledArc ≥ _totalArc → ReachedTarget
结束条件(任一触发即该无人机结束):
- 被毁伤(HP ≤ 0)→ Destroyed
- 到达攻击目标 → ReachedTarget
- 侵入管控区域 → ZoneIntruded
PlatformEntity(搭载平台)★重构
空基平台(AirBasedPlatform):
- 固定阵位
- 挂载弹药类型 AerosolType、数量 MunitionCount
- 巡航速度 CruiseSpeed
- 冷却计时器
- 行为:按发射计划从固定位置水平射出弹药,弹药重力下落到目标高度
地基平台(GroundBasedPlatform):
- 固定位置
- 弹药初速 MuzzleVelocity
- 挂载弹药类型 AerosolType、数量 MunitionCount
- 冷却计时器
- 行为:接收目标坐标 → 计算射击诸元 → 发射炮弹
MunitionEntity(飞行中弹药)★重构
属性:
- 弹药类型 AerosolType
- 发射方式 LaunchMode(AirDrop / GroundArtillery)
- 当前位置、速度
- 目标坐标(云团生成点)
行为(统一抛物模型):
- 地基:初速 MuzzleVelocity + 发射角 → 抛物线弹道(Kinematics.ParabolicPosition)
- 空基:初速 CruiseSpeed + 水平发射(θ=0°)→ 重力下落(Kinematics.ParabolicPosition)
- 到达条件:Y 坐标到达释放高度
CloudEntity(气溶胶云团)
属性:
- 气溶胶类型 AerosolType
- 中心坐标
- 当前半径(调用第三方 DLL 扩散模型计算)
- 最大半径
- 持续时间、剩余时间
- 粒子系统参数(半径、透明度、颜色 → 输出到 StateData)
行为(委托第三方 DLL):
- 扩散膨胀:GaussianPuffDispersion.Tick()(三阶段)
- 消散计时
- 毁伤判定:IDamageModel.CalculateDamage()(三种机制自主实现)
六、算法设计 — 接口驱动 & 可替换架构 ★关键
设计原则
所有算法组件通过接口隔离,SimulationEngine 只依赖接口,不感知具体实现。每个算法组件可独立替换为第三方实现,无需修改引擎代码。
SimulationEngine
│
┌─────┼─────┐
▼ ▼ ▼
ICloudDispersionModel ← 扩散模型接口
IDamageModel ← 毁伤模型接口
IDefensePlanner ← 防御规划接口
│
┌───────┼───────┐
▼ ▼ ▼
自主实现 第三方实现 混合(部分自研+部分外购)
(Default) (Future) (Future)
6.1 算法工厂与注册
/// <summary>算法工厂 — 统一管理所有算法组件的创建与替换</summary>
public static class AlgorithmFactory
{
// 注册表:每个接口 → 创建委托(Func<object>)
private static readonly Dictionary<Type, Func<object>> _registry = new()
{
[typeof(ICloudDispersionModel)] = () => new GaussianPuffDispersion(),
[typeof(IDamageModel)] = () => new DamageModelRouter(),
};
/// <summary>替换某个算法实现(第三方接入点)</summary>
public static void Register<TInterface>(Func<object> factory)
{
_registry[typeof(TInterface)] = factory;
}
/// <summary>创建实例,SimulationEngine 统一调用此方法</summary>
public static T Create<T>() where T : class
{
if (_registry.TryGetValue(typeof(T), out var factory))
return (T)factory();
throw new InvalidOperationException($"No implementation for {typeof(T).Name}");
}
}
注:
IDefensePlanner由 DI 直接注入SimulationEngine(不经过 AlgorithmFactory,因为它是单例策略对象,不是每云团/每帧 new)。AlgorithmFactory 仅管理每实例的算法组件(云团扩散、毁伤模型)。替换示例:将来第三方提供
ThirdPartyDispersion.dll,只需一行:AlgorithmFactory.Register<ICloudDispersionModel>(() => new ThirdPartyDispersion());无需改动 SimulationEngine 任何代码。
6.2 第三方提供的数据
/// <summary>第三方提供的弹药基础参数(数据配置,非运行时 DLL)</summary>
public class AmmunitionSpec
{
public AerosolType AerosolType; // 气溶胶类型
public string Name; // 弹药名称
// 初始扩散状态
public float InitialRadius; // 初始云团半径(m)
public float InitialVolume; // 初始云团体积(m³)
public float CoreDensity; // 核心密度(kg/m³)
public float EdgeDensity; // 边缘密度(kg/m³)
public float InitialTemperature; // 初始温度(K)
public float BuoyancyFactor; // 浮力系数
// 弹药基础参数
public float EffectiveConcentration; // 有效毁伤浓度阈值(kg/m³)
public float DispersionRateBase; // 基础扩散速率(m/s)
public float MaxRadius; // 最大有效半径(m)
public float MaxDuration; // 最大持续时间(s)
public ParticleRenderParams Particles; // 粒子渲染参数
}
第三方以 JSON 配置文件形式交付
AmmunitionSpec数组,系统启动时加载到 SQLiteAmmunitionSpec表。
6.2.1 系统内置默认值(第三方提供前使用)
基于 155mm 炮弹载荷的物理合理估计:
| 参数 | 惰性气体弹 | 活性材料弹 | 活性燃料弹 |
|---|---|---|---|
| 药剂质量 (SourceStrength) | 10 kg | 12 kg | 10 kg |
| 爆发药 TNT 当量 (BurstChargeKg) | 1.5 kg | 4.0 kg | 1.5 kg |
| 有效浓度阈值 (EffectiveConcentration) | 0.0001 kg/m³ | 0.0002 kg/m³ | 0.0001 kg/m³ |
| 湍流扩散系数 (TurbulentExpansionK) | 3.0 | 4.0 | 3.0 |
| 最大半径 (MaxRadius) | 100 m | 80 m | 90 m |
| 最大持续时间 (MaxDuration) | 120 s | 90 s | 100 s |
| Phase 1 初始半径 (R₀) | 3.75 m | 5.14 m | 3.75 m |
数据来源:10kg烟幕剂,抛射药/烟幕剂质量比 5%-20%,TNT当量取 1.5kg(典型值)。活性材料弹取爆炸分散型上限 4kg。
6.3 扩散模型设计 — 高斯烟团模型(Gaussian Puff)V2
6.3.1 模型选择
采用高斯烟团扩散模型,使用Pasquill-Gifford 稳定度分类计算扩散系数 σ。
6.3.2 大气稳定度映射
从天气类型和风速推导稳定度等级,无需额外参数:
| 天气 | 风速 | 稳定度 | 太阳辐射 | 湍流 |
|---|---|---|---|---|
| 晴天 | ≤ 5 m/s | A(极不稳定) | 强 | 强 |
| 晴天 | > 5 m/s | B(不稳定) | 中 | 中 |
| 阴天 | 任意 | D(中性) | 弱 | 中 |
| 雨天 | 任意 | D(中性) | 弱 | 中 |
| 雾天 | 任意 | E(稳定) | 无 | 弱 |
| 夜间 | 任意 | F(极稳定) | 无 | 最弱 |
6.3.3 σ 扩散系数(Pasquill-Gifford)
扩散距离 x 米后,水平扩散系数 σ_y、垂直扩散系数 σ_z(单位 m):
| 稳定度 | σ_y | σ_z |
|---|---|---|
| A | 0.22x / (1+0.0001x)^0.5 | 0.20x |
| B | 0.16x / (1+0.0001x)^0.5 | 0.12x |
| C | 0.11x / (1+0.0001x)^0.5 | 0.08x / (1+0.0002x)^0.5 |
| D | 0.08x / (1+0.0001x)^0.5 | 0.06x / (1+0.0015x)^0.5 |
| E | 0.06x / (1+0.0001x)^0.5 | 0.03x / (1+0.0003x)^0.5 |
| F | 0.04x / (1+0.0001x)^0.5 | 0.016x / (1+0.0003x)^0.5 |
对于瞬时释放的烟团(Puff),扩散距离 x 取云团中心随风漂移的距离:x = u × t,其中 u 为风速,t 为经过时间。若风速为 0,取 x ≈ 1m(纯湍流扩散)。
6.3.4 浓度场公式
云团内任意点 (x, y, z) 在时刻 t 的浓度:
C(x, y, z, t) = ────────────── × exp[ -½( (x-ut)/σy )² ]
(2π)^(3/2) σy σy σz
× exp[ -½( y/σy )² ]
× { exp[ -½( (z-H)/σz )² ] + exp[ -½( (z+H)/σz )² ] }
其中:
Q = 源强(释放物质总量,kg),来自 AmmunitionSpec
u = 风速(m/s)
H = 有效释放高度(m)
σy, σz = 扩散系数(由 6.3.2 和 6.3.3 确定)
6.3.5 可判定指标
| 指标 | 计算 | 说明 |
|---|---|---|
| 中心浓度 | C_max = Q / [(2π)^(3/2) σy² σz] | 云团最浓处 |
| 有效半径 | R_eff = min{ r | C(中心, r) < C_threshold } |
| 消散条件 | C_max < C_threshold / 10 或 经过时间 > MaxDuration | 浓度可忽略 |
简化:实时判定时,无人机位于云团内当且仅当 C(无人机坐标) ≥ EffectiveConcentration。不需要球体假设,直接用高斯公式计算。
6.3.6 AmmunitionSpec 调整
| 字段 | 变更 | 说明 |
|---|---|---|
DispersionRateBase |
❌ 删除 | 由 PG 稳定度代替 |
InitialRadius |
保留 | 仍作初始 σ 参考 |
SourceStrength |
✅ 新增 | 源强 Q(kg),取代线性模型 |
浓度 C 的单位为 kg/m³,无人机是否受损取决于 C ≥ EffectiveConcentration。
6.3.7 接口(不变)
public interface ICloudDispersionModel
{
void Initialize(AmmunitionSpec ammo, CombatScene env, Vector3 releasePos, float releaseTime);
void Tick(float deltaTime, float windSpeed, WindDirection windDir);
Vector3 Center { get; }
float Radius { get; }
float CoreDensity { get; }
float EffectiveRadius { get; }
ParticleParams Particles { get; }
bool IsDissipated { get; }
}
6.4 毁伤模型设计
三种气溶胶对应三种毁伤机制,我们自主实现:
6.4.1 共同接口
public interface IDamageModel
{
/// <summary>每 Tick 计算毁伤值</summary>
/// <param name="droneType">目标类型</param>
/// <param name="powerType">动力类型(影响毁伤效率)</param>
/// <param name="aerosolType">气溶胶类型</param>
/// <param name="cloudDensity">当前位置云团密度(kg/m³)</param>
/// <param name="exposureTime">累计暴露时间(s)</param>
/// <param name="deltaTime">本次增量暴露时长(s)</param>
/// <returns>本次毁伤值(0~1 归一化)</returns>
float CalculateDamage(DroneType droneType, PowerType powerType,
AerosolType aerosolType, float cloudDensity,
float exposureTime, float deltaTime);
/// <summary>达到 100% 毁伤所需的连续暴露时间 (s),供 planner 估算拦截可行性</summary>
float RequiredExposureSeconds(DroneType droneType, PowerType powerType, AerosolType aerosolType);
DamageStage GetDamageStage(float accumulatedDamage);
}
6.4.2 三种机制的差异
| 机制 | 气溶胶类型 | 毁伤原理 | 毁伤曲线特征 |
|---|---|---|---|
| 吸入式灭火 | 惰性气体 | 发动机吸入惰性气体 → 氧气浓度下降 → 熄火 | 阈值型:密度达标后快速生效,延迟短 |
| 爆燃式 | 活性材料 | 材料接触高温发动机表面 → 化学反应爆燃 | 触发型:密度 + 温度双条件,瞬间高伤害 |
| 吸入式爆炸 | 活性燃料 | 燃料被吸入进气道 → 内部爆炸 | 累积型:随暴露时间指数增长 |
// 实现示例伪代码
// 吸入式灭火:阈值判定
class InertGasDamageModel : IDamageModel
{
float CalculateDamage(...) {
if (cloudDensity < effectiveThreshold) return 0; // 浓度不够,无伤害
// 超过阈值后,线性累积毁伤
return damageRate * deltaTime;
}
}
// 爆燃式:瞬时触发
class ActiveMaterialDamageModel : IDamageModel
{
float CalculateDamage(...) {
if (cloudDensity < triggerThreshold) return 0;
if (!triggered) { triggered = true; return burstDamage; } // 一次性爆发
return residualDamage * deltaTime; // 后续余伤
}
}
// 吸入式爆炸:指数累积
class ActiveFuelDamageModel : IDamageModel
{
float CalculateDamage(...) {
if (cloudDensity < effectiveThreshold) return 0;
// 累积暴露 → 指数增长
return baseRate * Mathf.Exp(exposureTime * factor) * deltaTime;
}
}
6.4.3 毁伤阶段判定
accumulatedDamage: 0.0 ─────── 0.25 ─────── 0.6 ─────── 1.0
DamageStage: Normal EngineAnomaly AttitudeLoss Destroyed
Normal: 正常运行
EngineAnomaly: 发动机异常(Unity: 尾焰变色/不稳定)
AttitudeLoss: 姿态失控(Unity: 偏航/滚转)
Destroyed: 被摧毁(Unity: 坠落动画)
6.5 防御推荐算法设计
6.5.1 Step A:气溶胶选型
基于规则匹配,根据目标动力类型推荐最佳毁伤机制:
| 目标动力 | 推荐气溶胶 | 理由 |
|---|---|---|
| 电推式 | 惰性气体(吸入式灭火) | 电动机无高温,爆燃/爆炸不适用;惰性气体使电调/散热失效 |
| 活塞式 | 惰性气体(吸入式灭火) | 活塞发动机依赖氧气,窒息最有效 |
| 喷吸气式 | 活性材料(爆燃式) | 喷气发动机高温表面触发爆燃 |
| — | 活性燃料(吸入式爆炸) | 适用于高价值高速目标,追求一击必杀 |
// 选型规则表(可配置)
static readonly Dictionary<PowerType, AerosolType> MatchTable = new()
{
{ PowerType.Electric, AerosolType.InertGas },
{ PowerType.Piston, AerosolType.InertGas },
{ PowerType.Jet, AerosolType.ActiveMaterial },
};
6.5.2 Step B:云团参数推荐 — 时空交汇优化
问题:
输入 = 目标航路 R(t)、风速 Vw、搭载平台位置 Pp、弹药飞行时间 Tf
输出 = 最优抛撒位置 Ps 和触发时机 Ts
方法:
1. 将目标航路离散为时间点 t₁, t₂, ..., tₙ
2. 对每个 tᵢ:
a. 无人机位置 = R(tᵢ)
b. 考虑炮弹飞行时间 Tf,抛撒时刻 = tᵢ - Tf
c. 云团在 [tᵢ - Tf, tᵢ - Tf + Duration] 内的扩散范围
d. 计算交汇窗口 = 无人机航路 ∩ 云团扩散范围的时空重叠
3. 选择交汇窗口最大的 (Ps, Ts) 作为最佳值
4. 选择交汇窗口刚好有效的边界作为最危险值
简化实现:
- 云团近似为匀速膨胀球体
- 无人机匀速直线飞行(取平均速度)
- 解析解:计算无人机航迹与云团膨胀球面的两个交点
- 最佳 = 无人机在云团中时间最长
- 最危险 = 无人机刚好擦边
风偏补偿(天气纳入规划)
云团生成后会被风吹偏。为使云团中心在无人机到达穿越点时正好漂移回航路,抛撒点须逆风预置:
穿越点 (tx, tz) = 无人机航路上的目标点(不变,用于 txArrival 计算)
抛撒点 (cloudGenX, cloudGenZ) = (tx, tz) − windVec × expansionTime
windVec = Kinematics.WindToVector(WindDirection, WindSpeed) // 只影响 X/Z
expansionTime ≈ 30s(Phase 2 湍流膨胀时长)
- 弹药瞄准抛撒点:地基
dist = |cloudGen − platform|;空基distToCloud = |cloudGen − platform| - FireEvent.TargetX/Z = cloudGen:引擎据此生成云团,云团被风吹
windVec × expansionTime后回到穿越点 - 天气(稳定度)间接影响:
CloudExpansionModel.RadiusAt已根据env.WeatherType推导 Pasquill 稳定度,影响expansionTime与有效半径估算;修复GaussianPuffDispersion写死 Sunny 的 bug 后,预估与运行时一致
tx, tz(穿越点)与cloudGenX, cloudGenZ(抛撒点)在有风时不同,无风时相同。原代码混淆二者,导致有风场景规划错误。
物理模型统一原则(planner 与引擎共用)
planner 禁止写任何本地运动学/几何/毁伤公式,全部调用与引擎共享的独立工具类。每个物理量只有一个实现:
共享工具类(唯一实例) planner 用途 引擎用途
─────────────────────────────────────────────────────────────────
Kinematics(质点运动学) 弹道/风矢量/到达时间 MunitionEntity/DroneEntity
RouteGeometry(航路几何) 穿越点弧长/位置/切向 DroneEntity 位置更新
CloudExpansionModel(云团膨胀) 有效半径/膨胀时间/弹药数 CloudEntity 半径演化
DamageAssessment(毁伤几何) (暂未直接用,估算用模型) PathInSphere 穿云路径
- RouteGeometry:纯静态工具类。
TotalLength/PositionAt(arcLen)/ArcLengthNearestTo(x,z)/TangentAt(arcLen)/TravelTimeTo(arcLen,speed)。planner 用它定位穿越点、沿航路分布多发、算到达时间;DroneEntity 用它做弧长驱动的位置更新(_traveledArc += speed×dt→PositionAt)。 - 云团重叠:间距
= 2R × (1 − CloudOverlapRatio),重叠比例由planner_config.json驱动(默认 20%)。CloudExpansionModel.RoundsNeeded接受spacing参数,公式仍在共享模块,planner 只传配置值。
配置外置(planner_config.json)
planner 所有策略参数从 data/planner_config.json 读取,代码零默认值:
{
"CloudOverlapRatio": 0.2,
"CriticalProbabilityThreshold": 0.5,
"MaxInterceptProbability": 0.95,
"TypeCoefficient": { "HighSpeed": 4.0, "FixedWing": 2.0, ... },
"AmmoMatch": { "Electric": "InertGas", "Piston": "InertGas", "Jet": "ActiveMaterial" }
}
PlannerConfig.Load(IPathProvider)从 dataRoot 加载;文件缺失或字段非法即抛异常DefaultDefensePlanner构造函数必传PlannerConfig
6.5.3 Step C:反推平台部署
根据云团方案反推:
Q: 需要覆盖多大面积?
→ 根据目标数量、编队间距、航路宽度 → 需要的云团数
Q: 每个云团需要多少弹药?
→ 云团体积 / 单发覆盖体积 → 弹药数
Q: 用什么平台?
→ 抛撒点距目标 > 平台航程?→ 地基
→ 抛撒点距目标 ≤ 平台航程?→ 空基
→ 根据弹药初速 / 飞行时间校验可行性
Q: 探测设备需要多大半径?
→ 探测半径 ≥ 从平台发现目标到完成拦截期间目标飞过的距离
6.5.4 接口
// === 防御规划 ===
public interface IDefensePlanner
{
/// <param name="fireUnits">可用的火力单元(空基/地基统一表达)</param>
/// <param name="threats">威胁批次列表</param>
/// <param name="environment">作战环境(含天气,影响光电探测距离)</param>
/// <param name="detectionSources">统一信息网络的探测源列表(独立探测设备 + 火力单元自带探测)。可为空列表。</param>
PlannerResult Plan(List<FireUnit> fireUnits, List<DroneWave> threats,
CombatScene environment, List<DetectionSource> detectionSources);
}
// ThreatProfile / DefenseRecommendation / DefenseSolution 等模型见本节上文
6.6 新增数据表:AmmunitionSpec
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| Id | TEXT | PK | GUID |
| AerosolType | INTEGER | 气溶胶类型 | |
| Name | TEXT | 弹药名称 | |
| InitialRadius | REAL | 初始云团半径 m | |
| InitialVolume | REAL | 初始云团体积 m³ | |
| CoreDensity | REAL | 核心密度 kg/m³ | |
| EdgeDensity | REAL | 边缘密度 kg/m³ | |
| InitialTemperature | REAL | 初始温度 K | |
| BuoyancyFactor | REAL | 浮力系数 | |
| EffectiveConcentration | REAL | 有效毁伤浓度阈值 | |
| DispersionRateBase | REAL | 基础扩散速率 m/s | |
| MaxRadius | REAL | 最大有效半径 m | |
| MaxDuration | REAL | 最大持续时间 s | |
| ParticlesJson | TEXT | 粒子渲染参数 JSON |
第三方以 JSON 文件交付,导入到此表。系统启动时若表为空则从默认配置文件加载。
6.7 算法替换完整示例
以下是将来用第三方 DLL 替换扩散模型的完整流程,演示接口驱动架构的灵活性:
┌─ 步骤 1:第三方提供实现 ─────────────────────────────┐
│ │
│ ThirdPartyDispersion.dll │
│ └─ class ThirdPartyPuff : ICloudDispersionModel │
│ { │
│ void Initialize(AmmunitionSpec, ...) → P/Invoke → native_init()
│ void Tick(float, float, WindDirection) → P/Invoke → native_tick()
│ Vector3 Center → P/Invoke → native_center()
│ float Radius → P/Invoke → native_radius()
│ ... │
│ } │
│ │
└────────────────────────────────────────────────────────┘
┌─ 步骤 2:一行注册 ──────────────────────────────────┐
│ │
│ AlgorithmFactory.Register │
│ <ICloudDispersionModel, ThirdPartyPuff>(); │
│ │
└────────────────────────────────────────────────────────┘
┌─ 步骤 3:SimulationEngine 无感知切换 ───────────────┐
│ │
│ var dispersion = AlgorithmFactory │
│ .Create<ICloudDispersionModel>(); │
│ dispersion.Initialize(ammo, env, pos, t); │
│ │
│ // 后续代码完全不变,无论底层是自主实现还是第三方 DLL │
│ dispersion.Tick(deltaTime, windSpeed, windDir); │
│ if (dispersion.IsDissipated) { ... } │
│ │
└────────────────────────────────────────────────────────┘
替换粒度:每个算法组件独立替换,可以实现如下组合:
组合 A(当前):
ICloudDispersionModel → GaussianPuffDispersion(自主)
IDamageModel → DamageModelRouter(自主)
IDefensePlanner → DefaultDefensePlanner(自主)
组合 B(将来可能):
ICloudDispersionModel → ThirdPartyPuff(第三方 DLL)
IDamageModel → DamageModelRouter(自主保留)
IDefensePlanner → DefaultDefensePlanner(自主保留)
组合 C(将来可能):
ICloudDispersionModel → ThirdPartyPuff(第三方 DLL)
IDamageModel → ThirdPartyDamage(第三方 DLL)
IDefensePlanner → DefaultDefensePlanner(自主保留)
七、数据录制与回放
7.1 FrameDataStore 实现
FrameDataStore 采用双层存储架构:仿真期间内存缓冲 → 仿真结束保留 LiveFrames 副本 + 批量写入 SQLite:
public class FrameDataStore
{
// 内存回放:Flush 后保留副本,供回放直接读取(零磁盘 IO)
public List<SimFrameRecord> LiveFrames { get; private set; }
void BeginRecording(string scenarioId); // 清空 buffer + LiveFrames
void RecordFrame(...); // 仅写内存 List<FrameRecord>
void Flush(); // 序列化 → LiveFrames 副本 + SQLite 入库
void Discard(); // 清空 buffer + LiveFrames
List<SimFrameRecord> ReadFrames(...); // 从 SQLite 读(历史仿真回放)
}
LiveFrames 生命周期:
仿真运行 → Flush() → LiveFrames = 当前仿真数据(同时写 SQLite)
↓
回放:优先取 LiveFrames(内存,快)
↓
新仿真开始 → BeginRecording() → LiveFrames.Clear()
↓
如需回放旧仿真 → 回退读 SQLite
7.2 录制
- 仿真启动时
BeginRecording清空内存缓冲区和 LiveFrames - 录制帧率 = 仿真 TickRate(每想定可配置,默认 20Hz)
- 每 Tick 调用
RecordFrame(),仅追加到内存List<FrameRecord>,零磁盘 IO、零字符串分配 - 仿真结束
Flush():序列化为SimFrameRecord→ 保留LiveFrames副本 → 单事务InsertAll写入 SQLite - 仿真事件
SimEvent直接写入主库main.db
7.3 回放
ReplayController.LoadReplay(scenarioId, frameStore)双路径:- 传入
frameStore且LiveFrames非空 → 直接使用内存数据(仿真刚结束,零延迟) - 否则 → 从
frames/{scenarioId}.db读取(历史仿真)
- 传入
- WHERE FrameIndex BETWEEN a AND b 命中索引,毫秒级
- 回放帧率由前端控制(可与录制不同)
- 支持暂停 / 快进 / 后退 / 跳转
7.4 容量估算
以默认 20Hz TickRate,16 实体为例:
| 仿真时长 | 帧数 | 行数(16实体) | 单任务大小 |
|---|---|---|---|
| 5 min | 6,000 | 96,000 | ~27 MB |
| 15 min | 18,000 | 288,000 | ~80 MB |
| 30 min | 36,000 | 576,000 | ~161 MB |
| 60 min | 72,000 | 1,152,000 | ~322 MB |
6 个月累计(假设日均 5 次仿真,每次 30min):
| 项目 | 数据 |
|---|---|
| 仿真总次数 | 5 × 180 = 900 次 |
| 帧数据总大小 | 900 × 161 MB ≈ 145 GB |
| main.db | < 100 MB |
| 全部合计 | ~145 GB |
7.5 TTL 清理策略
仿真完成 → 生成 frames/{scenarioId}.db
│
▼
CleanupExpired() 每天执行一次:
├─ 扫描 frames/ 目录
├─ 检查 {scenarioId}.db 的创建时间
└─ 超过 6 个月 → 删除文件
可选扩展:
- 归档:移到 archive/ 目录并压缩(.db.gz)
- 降采样:删除前先保留每 N 帧 1 帧的精简版
7.6 为什么分库而非单库
| 对比 | 单库(全部在 main.db) | 分库(每任务一个 .db) |
|---|---|---|
| 帧表行数(6月) | 5.18 亿行 | 每库 57.6 万行 |
| 查询延迟 | 秒级(需精细索引) | 毫秒级 |
| 单文件体积 | > 100 GB | 每文件 ~161 MB |
| 备份/VACUUM | 灾难级 | 无痛 |
| 数据损坏影响 | 全部丢失 | 仅一个任务 |
| 运维(删除/归档) | 复杂 SQL | 直接删文件 |
八、报告生成
8.1 报告结构
技术要求终版要求报告包含时序图和三个分析维度:
| 阶段 | 内容 | 数据来源 |
|---|---|---|
| 仿真前 | 我方配置(装备类型/数量、火力单元部署、弹药类型/数量、部署位置、管控区域) | Scenario + 5 步配置 |
| 仿真中 | 关键事件时序表、各实体轨迹概要、云团演化数据 | SimEvent + SimFrameRecord |
| 仿真后 | 拦截成功率、各阶段耗时、毁伤效果评估、对抗结果判定 | SimEvent 汇总统计 |
8.2 对抗结果判定逻辑
成功拦截:所有无人机 被摧毁(未抵达目标、未侵入管控区)
部分拦截:部分无人机被摧毁,其余抵达目标或侵入管控区
拦截失败:所有无人机抵达目标或侵入管控区(无摧毁)
8.3 导出
- 格式:PDF / Word(默认 PDF)
- 模板占位符填充后输出
- 技术选型:QuestPDF 或其他 .NET Standard 2.1 兼容方案(待调研)
九、服务接口概要
// === 模型管理 ===
interface IModelService
{
ModelInfo ImportModel(string filePath, string name);
void DeleteModel(string id);
List<ModelInfo> GetAllModels();
ModelInfo GetModel(string id);
}
// === 想定管理 ===
interface IScenarioService
{
Scenario CreateTask(string name, string taskNumber);
void DeleteTask(string id);
PagedResult<Scenario> SearchTasks(string kw, DateTime? from, DateTime? to, int page, int size);
ScenarioConfig GetTaskDetail(string id);
// 步骤保存
void SaveScene(string scenarioId, CombatScene scene);
void SaveControlZones(string scenarioId, List<ControlZone> zones); // NEW
void SaveTarget(string scenarioId, DroneProfile target);
void SaveDeployment(string scenarioId, List<EquipmentDeployment> equips); // 重构
void SaveCloudDispersal(string scenarioId, CloudDispersal cloud);
void SaveRoute(string scenarioId, RoutePlan route, List<Waypoint> wps);
void UpdateStep(string scenarioId, int step);
// 综合防御推荐(NEW — 多编队版)
DefenseRecommendation GetDefenseRecommendation(string scenarioId);
}
// === 仿真引擎 ===
interface ISimulationEngine
{
void Initialize(string scenarioId);
SimulationFrameResult Tick(float deltaTime); // 返回本帧快照+新事件(复用对象池)
void Pause();
void Resume();
void Stop();
SimulationState State { get; }
}
// === 数据录制 ===
// 说明:录制职责由具体类 FrameDataStore 承担(无 IRecordService 接口)。
// SimulationEngine 内部持有 FrameDataStore,每 Tick 调 RecordFrame(仅内存),
// Stop/完成时调 Flush 批量落库。事件 SimEvent 由引擎直接写入主库。
class FrameDataStore
{
void BeginRecording(string scenarioId);
void RecordFrame(int frameIdx, float timestamp, List<EntitySnapshot> entities);
void Flush(); // 批量落库
void Discard();
List<SimFrameRecord> ReadFrames(string scenarioId, int fromFrame, int toFrame);
void DeleteTaskDb(string scenarioId);
int CleanupExpired(); // TTL 清理(RetentionDays,默认 180)
}
// === 仿真报告 ===
interface IReportService
{
SimulationReport Generate(string scenarioId);
SimulationReport GetReport(string id);
void DeleteReport(string id);
byte[] ExportReport(string id, string format); // "pdf" | "word"
PagedResult<SimulationReport> SearchReports(string kw, DateTime? from, DateTime? to, int page, int size);
}
九-A、多批次多编队设计
数据模型
一个仿真任务可包含多种类型、多批次的无人机,每批次有独立的航路:
Scenario "城市防御演习"
├── CombatScene(共享)
├── ControlZone[](共享)
├── CloudDispersal(共享:云团参数,具体拦截方案由算法确定)
│
├── 批次 A(活塞×3) ← WaveId
│ ├── DroneProfile(WaveId=A,类型=活塞,数量=3)
│ ├── RoutePlan(ScenarioId + WaveId=A,航路 A:北→南)
│ └── Waypoint[]
│
├── 批次 B(喷气×2) ← WaveId
│ ├── DroneProfile(WaveId=B,类型=高速,数量=2)
│ ├── RoutePlan(ScenarioId + WaveId=B,航路 B:西→东)
│ └── Waypoint[]
│
└── 批次 C(旋翼×5) ← WaveId
├── DroneProfile(WaveId=C,类型=旋翼,数量=5)
├── RoutePlan(ScenarioId + WaveId=C,航路 C)
└── Waypoint[]
关键变更:RoutePlan 主键为 (ScenarioId, WaveId) 复合键。Waypoint 通过 (ScenarioId, WaveId) 关联航路。
推荐算法
算法需要同时考虑所有无人机批次和可用火力单元,自动匹配分配:
// 输入:所有无人机编队 + 已部署的火力单元
// 输出:每个编队的拦截方案 + 合并的发射计划
var allSolutions = new List<DefenseSolution>();
var usedFireUnits = new HashSet<string>();
foreach (var droneWave in threat.DroneWaves)
{
// 从可用火力单元中选择合适的(弹药类型匹配目标动力类型)
var availableUnits = fireUnits.Where(u => !usedFireUnits.Contains(u.Id)).ToList();
var solution = advisor.RecommendForWave(droneWave, environment, availableUnits);
// 标记占用的火力单元
foreach (var idx in solution.AssignedFireUnitIds)
usedFireUnits.Add(idx);
allSolutions.Add(solution);
}
// 复用判定(不写死"一对一"或"一对多"):
// 同种弹药 → 只需冷却时间(秒级),可直接复用
// 异种弹药 → 需要 AmmoChangeTime(默认 5 分钟),检查批次间隔是否够
// 弹药耗尽 → 不可复用
var merged = MergeFireSchedules(allSolutions);
engine.SetFireSchedule(merged);
关键原则:
- 算法决定分配,而非写死"一对一"或"一对多"
- 决策依据:弹药类型匹配、时间窗口重叠、火力单元能力
- 人工可调整算法推荐结果
- 引擎不感知分组——只看合并后的
FireSchedule
数据表变更
| 表 | 变更 |
|---|---|
RoutePlan |
PK 为 (ScenarioId, WaveId) 复合键 |
DroneProfile |
不变(已有 WaveId) |
Waypoint |
关联 (ScenarioId, WaveId) 定位航路 |
IScenarioService 的 SaveRoute 签名:
void SaveRoute(string scenarioId, string waveId, RoutePlan route, List<Waypoint> wps); // waveId 参数
报告
报告新增多编队汇总:
## 编队汇总
| 批次 | 目标 | 数量 | 拦截方案 | 结果 |
|------|------|------|------|------|
| 批次A | 活塞 | 3 | 惰性气体 12发 | ✅ 全毁 |
| 批次B | 喷气 | 2 | 活性材料 8发 | ⚠️ 1逃脱 |
十、配置向导步骤(整合后)
整合需求 V1.0 和技术要求终版后,配置向导保持 5 步但内容调整:
┌────────────────────────────────────────────────────────┐
│ 步骤 1:作战场景设置 │
│ ├─ 场景类型(城市/平原/山区/海岸) │
│ ├─ 时间段(24h 制 NEW) │
│ ├─ 天气 + 风速/风向/温度/湿度/气压/能见度 │
│ ├─ 场景空间范围(宽/纵深/高 NEW) │
│ └─ 管控区域(电子围栏 NEW) │
│ └─ 多边形绘制 / 编辑 / 删除 │
├────────────────────────────────────────────────────────┤
│ 步骤 2:目标配置 │
│ ├─ 批次选择(已有批次/新建批次) │
│ ├─ 目标类型选择(自动填充默认参数) │
│ ├─ 数量 / 动力类型 / 翼展 / 速度 / 高度 │
│ └─ 预设典型目标库(NEW) │
├────────────────────────────────────────────────────────┤
│ 步骤 3:装备部署(重构) │
│ ├─ 装备角色(探测设备 / 发射平台) │
│ ├─ 发射平台类型(空基 / 地基 NEW) │
│ ├─ 数量 / 部署位置 │
│ ├─ 挂载气溶胶类型 / 弹药数量 │
│ ├─ 弹药初速 / 释放高度 / 冷却时间 │
│ └─ 探测半径(探测设备) │
├────────────────────────────────────────────────────────┤
│ 步骤 4:云团抛撒配置 │
│ ├─ 气溶胶类型(吸入式灭火/爆燃式/吸入式爆炸) │
│ ├─ 释放参数(高度/触发模式/扩散方向/规模/持续等) │
│ ├─ 抛撒位置(三维点选 / 手动输入 / 算法推荐 NEW) │
│ ├─ 算法推荐按钮 → 显示最佳值 & 最危险值(NEW) │
│ └─ 可点选采纳推荐值 │
├────────────────────────────────────────────────────────┤
│ 步骤 5:航路规划 │
│ ├─ 编队模式(单机/编队/蜂群) │
│ ├─ 编队间距 / 预计到达时间 │
│ ├─ 航路点画布(三维点选添加/调整/删除 NEW) │
│ └─ 航路点列表(坐标/高度/速度) │
└────────────────────────────────────────────────────────┘
十一、关键技术决策
| # | 决策 | 选择 | 理由 |
|---|---|---|---|
| 1 | 数据持久化 | SQLite (sqlite-net) | 支持复杂查询、筛选、分页 |
| 2 | 模型文件格式 | .fbx / .obj / .stl / .glb / .gltf | 合并两份需求,≤500MB,2K+贴图 |
| 3 | 模型文件存储 | Application.persistentDataPath | 不与 Unity Assets 耦合 |
| 4 | 序列化方案 | System.Text.Json | Unity 2022+ 内置支持 |
| 5 | Service/Data 层 | 零 UnityEngine 依赖 | 可独立单元测试 |
| 6 | 仿真驱动方式 | 固定 TickRate 帧驱动 | 每想定可配置(默认 20Hz),保证确定性 |
| 7 | 仿真并发 | 单仿真 | 简化状态管理 |
| 8 | 拦截机制 | 多阶段链路 | 探测→火控→发射→弹药飞行→云团→毁伤 |
| 9 | 云团扩散模型 | 高斯烟团模型(自主实现) | 简化球体膨胀 + 风速漂移 + 密度衰减,适合实时 20Hz |
| 10 | 毁伤模型 | 三种机制自主实现 | 吸入式灭火(阈值型)、爆燃式(触发型)、吸入式爆炸(指数累积型) |
| 11 | 防御方案推荐 | IDefensePlanner(自主实现) | 五步规划:威胁排序 → 弹药匹配 → 候选生成 → 贪心分配 → 时序生成 |
| 12 | 第三方数据 | 静态配置(JSON → SQLite) | 仅提供弹药基础参数和初始扩散状态,不提供运行时算法 |
| 13 | 运动学 | 弧长驱动匀速运动 | 无人机严格按航路匀速飞行(不受风偏,飞控修正);云团受风偏,毁伤判定做参考系修正 |
| 14 | 弹药弹道 | 抛物线(地基)/ 自由落体(空基) | 基于初速、角度、释放高度计算 |
| 15 | 批次 / 火力单元 | WaveId + 火力单元独立作战 | 批次关联航路但不独占;火力单元是最小独立作战单位,不需要上层编组 |
| 16 | 管控区域 | ControlZone 表 + Tick 判定 | 电子围栏,侵入即任务失败 |
| 17 | 配置向导流程 | 威胁驱动三阶段 | Phase1 定义威胁 → Phase2 算法推荐 → Phase3 审阅确认 |
| 18 | 报告导出 | 模板填充 → Markdown | PDF/Word 待调研 |
| 19 | 坐标系统 | 右手系 Y-up,米为单位 | 与 Unity 一致 |
| 20 | 空间范围 | 最低 4×100×10m,默认更大 | 可配置 |
| 21 | 帧数据存储 | 主库 + 任务分库 | 主库存元数据+事件(<100MB永久);分库每任务存帧数据(6月TTL),单文件~161MB,直接删文件运维 |
| 22 | 程序集拆分 | Core 独立于 Unity | Core 为 .NET Standard 2.1 纯类库,零 UnityEngine 依赖;Unity 仅作桥接;dotnet test 即可全量测试 |
十二、性能指标(来自技术要求终版)
| 指标 | 要求 | 说明 |
|---|---|---|
| 最大目标数 | ≥ 5 独立运动实体(3D 显示) | 显示层面,非逻辑上限 |
| 渲染帧率 | ≥ 30 FPS(满载 + 粒子特效) | Unity 前端职责 |
| 仿真空间 | ≥ 4m × 100m × 10m | 默认更大,可配置 |
| 粒子系统 | 云团扩散/流动/色彩 | Unity 根据后端参数渲染 |
| 毁伤效果 | 尾焰异常、姿态失控、坠落 | Unity 根据 DamageStage 渲染 |
十三、性能设计(Tick 热路径优化)
本节记录仿真引擎
SimulationEngine.Tick()热路径上的性能优化。实测:Unity 端满载场景渲染帧率从优化前 <20 FPS(卡顿) 提升至 200+ FPS。
13.1 设计原则:零分配热路径
Tick 在仿真期间以 TickRate(默认 20Hz)被 Unity Update 反复调用,且每帧需向 Unity 传回所有实体快照。优化的核心原则是仿真热路径上零(或极少)托管堆分配,把所有可复用的对象、数组、字符串构造移出热路径:
| 原则 | 手段 |
|---|---|
| 对象池复用 | 快照列表、事件列表、帧结果、移除索引缓冲、飞行无人机缓冲均 Clear() 复用,不每帧 new |
| 延迟序列化 | 强类型字段在仿真期流转,JSON/字符串只在 Flush 一次性生成 |
| 几何预计算 | 航路、多边形顶点等静态数据构造时算一次,缓存供每帧查询 |
| 批量落库 | 帧数据先全量驻留内存,仿真结束单事务写入,杜绝每帧磁盘 IO |
13.2 关键优化点
A. DroneEntity 航路几何缓存(消除每帧重复几何运算)
问题:原实现每帧对每架无人机调用 RouteGeometry.TotalLength(Route)(O(N) 全扫求和)+ PositionAt(O(N) 重扫定位),航路静态却每帧重算。
优化:构造时一次性预计算 _totalArc(总弧长)、_segLen[](各段段长)、_cumArc[](累积弧长)。Update() 内 _traveledArc += speed×dt,在 _cumArc[] 线性定位所在段(航点数通常 ≤10)后插值。
收益:每架无人机每帧从 O(N) ×2 降为 O(N) ×1(仅定位,无重算总长)。无人机数 × 航点数 × FPS 规模下收益显著。
B. 仿真期零字符串分配 + JSON 推迟到 Flush(最大收益项)
问题:原实现每帧每实体在 CollectSnapshots 拼 JSON 字符串(StateData),Unity 端 SimulationRunner 又对每个云团 JsonDocument.Parse 解析 —— 拼装 + 解析两端分配,是帧率杀手。
优化:
EntitySnapshot新增强类型字段(DamageStage/Hp/CloudRadius/CloudOpacity/CloudPhase/CloudElapsed/PlatformStateStr),CollectSnapshots直接填标量,零字符串分配。- Unity 端直读
snap.CloudRadius/snap.CloudOpacity,移除JsonDocument.Parse。 - JSON 序列化推迟到
FrameDataStore.Flush:按EntityTypeswitch 拼装(模板与历史格式逐字节一致),单次完成。
收益:热路径上彻底消除字符串分配与 JSON 解析。这是帧率从 <20 提升到 200+ 的主因。
C. ControlZoneEntity 顶点 2D 缓存(消除每帧数组分配)
问题:ContainsPoint 每 tick 被 O(飞行无人机 × 管控区) 调用,原实现每次 new (float X, float Z)[] 并逐元素拷贝。
优化:构造时缓存 _vertices2D(水平投影顶点数组),ContainsPoint 直接传给 Kinematics.PointInPolygon(数组隐式转 ReadOnlySpan,零分配)。
D. 帧数据内存缓存 + 批量落库
问题:每帧写 SQLite 会产生磁盘 IO 与事务开销。
优化:FrameDataStore 用 List<FrameRecord>(struct,连续存储)做内存缓冲,RecordFrame 仅追加;Flush 时序列化 + 单事务 InsertAll 批量入库。仿真期间零磁盘 IO。
E. 毁伤判定快速排斥
O(无人机 × 云团) 嵌套循环前两层裁剪:
cloud.CoreDensity < 有效浓度阈值→ 跳过(浓度不足不可能造成毁伤)。‖drone − cloudCenter‖² > (2R)²→ 跳过(距离超 2 倍有效半径,本帧不可能穿云),避免进入PathInSphere解析计算。
F. 移除索引复用 + 预过滤
- 弹药/云团移除用倒序索引缓冲
_removalIndices,避免ToList()。 - 飞行无人机预过滤到复用缓冲
_flyingDrones,避免后续多步重复Where。
13.3 优化前后对照
| 场景 | 优化前 | 优化后 |
|---|---|---|
| Unity 渲染帧率(满载) | <20 FPS(卡顿) | 200+ FPS |
| 每帧 GC 分配 | 每实体 1 个 JSON 字符串 + 解析端 DOM | 零字符串分配 |
| 每帧无人机几何运算 | O(无人机×航点) ×2 重算 | O(无人机×航点) ×1 定位 |
| 每帧管控区数组分配 | 每次调用 new 数组 | 构造时缓存,零分配 |
| 帧数据磁盘 IO | 每帧写库 | 仿真结束单次批量 |
13.4 后续可扩展方向(当前规模无需)
当实体规模大幅增长(如数百架无人机 + 数十云团)时,可能出现的新瓶颈与应对:
- 毁伤判定
O(drone × cloud)嵌套 → 空间分区(均匀网格 / AABB 树)。 - 回放
ReplayController.GetFrame用Where().ToList()线性扫描 → 改为按 FrameIndex 建索引或分组字典。 - 极长回放全量加载内存 → 分段/流式读取。
按 YAGNI,上述在当前规模(<30 FPS 需求、200+ FPS 实测)下不做,仅记录备查。
十四、探测设备设计(统一信息网络)
探测设备是"统一信息网络"的感知层,负责对来袭威胁的搜索、发现与跟踪。本系统支持三种探测手段,统一抽象为
DetectionSource,由独立探测节点(EquipmentRole.Detection)和火力单元自带的探测能力共同构成探测网。
14.1 设计现状:两阶段角色
探测设备在系统中承担两个阶段的角色,两者共用同一套物理公式(DetectionCalculator)以保证一致:
| 阶段 | 角色 | 状态 | 输入 | 输出 |
|---|---|---|---|---|
| 事前规划 | 静态估算最早发现点 | ✅ 已实现 | 威胁航路 + 探测网 + 能见度 | 每个威胁的最早发现弧长 DetectArc + 精度 |
| 运行时实时 | 扫描→发现→跟踪 | ⏳ 待实现 | 飞行无人机实时位置 + 探测网 | TargetDetected 事件 + 跟踪坐标流 |
- 事前规划(链路 A):
DefensePlanner.Plan调DetectionCalculator.EarliestDetection,沿威胁航路求其进入探测网范围的最早点,反推拦截时机。这是当前拦截时机的唯一来源(开环)。 - 运行时实时(链路 B):
DetectionEntity在 Tick 内扫描,产生"何时真正发现"的事件流与可视化。当前未接入 Tick,TargetDetected事件已定义(SimEventType.TargetDetected = 0)但全代码库未触发。
14.2 三种探测手段
| 手段 | 字段 | 特性 | 受能见度影响 |
|---|---|---|---|
| 雷达 | RadarRange |
全天候,三维波束 | 否 |
| 光电(EO) | EORange |
可见光成像 | 是:有效 = 基准 × min(1, Visibility/基准) |
| 红外(IR) | IRRange |
热源探测,夜间可用 | 否 |
综合有效距离取三者最大(任一手段发现即算发现):
// DetectionCalculator.EffectiveRange —— planner 与运行时实时共用
// 注:返回的是"最大水平距离基准",3D 球冠判定在其上层(见 14.2.1)
float EffectiveRange(float radarRange, float eoRange, float irRange, float visibility)
{
float effRadar = radarRange;
float effEO = eoRange > 0 ? eoRange * Math.Min(1f, visibility / eoRange) : 0f; // 光电衰减
float effIR = irRange;
return Math.Max(effRadar, Math.Max(effEO, effIR));
}
统一原则(正确性硬约束):planner 的事前规划与运行时实时探测必须用同一个 3D 球冠判定模型(
IsInCoverage)。原因:仿真是按 planner 规划执行的,planner 的探测判定就是仿真的事实依据——若 planner 用比真实探测能力更宽松的几何(如 2D 圆),会规划出"未探测却拦截"的物理错误结局(详见 14.2.1)。
14.2.1 三维球冠探测几何(决策 5)
当前实现:EffectiveRange 仅做水平面距离判定(EarliestEntryArc 求线段-圆交点),忽略高度与俯仰。这是简化模型。
目标态:探测范围为以设备为中心的三维球冠——由最大水平距离、俯仰角范围、高度门限共同界定,反映真实雷达/光电/红外的波束形状。
MaxDetectAlt ─────────────── 高度上界
╱ ╲
╱ ╲ ← 俯仰角上限 MaxElevation
╱ 球冠 ╲
设备 ●──────╱ ◌ ╲────── EffectiveRange(最大水平距离)
╲ 区域 ╱
╲ ╱ ← 俯仰角下限 MinElevation
╲ ╱
MinDetectAlt ─────────────── 高度下界
目标判定函数(待实现,planner 与实时探测共用):
// DetectionCalculator.IsInCoverage —— 三维球冠判定
// ① 水平距离 ≤ EffectiveRange(含能见度衰减后的综合距离)
// ② 俯仰角 ∈ [MinElevation, MaxElevation](设备指向目标的高低角)
// ③ 目标高度 ∈ [MinDetectAlt, MaxDetectAlt]
bool IsInCoverage(
Vector3 target, Vector3 detector,
float effectiveRange,
float minElevation, float maxElevation,
float minDetectAlt, float maxDetectAlt)
{
float dx = target.X - detector.X;
float dy = target.Y - detector.Y; // 高度差
float dz = target.Z - detector.Z;
float horizDistSq = dx * dx + dz * dz;
if (horizDistSq > effectiveRange * effectiveRange) return false; // ①
if (target.Y < minDetectAlt || target.Y > maxDetectAlt) return false; // ③
float horizDist = MathF.Sqrt(horizDistSq);
if (horizDist < 0.0001f) return true; // 正顶/正下,按角度门限放行
float elevation = MathF.Atan2(dy, horizDist) * (180f / MathF.PI); // 俯仰角(度)
return elevation >= minElevation && elevation <= maxElevation; // ②
}
planner 必须升级到 3D(正确性硬要求,非精度优化):
EarliestDetection/EarliestEntryArc当前用线段-圆求交(纯 2D),必须改为"沿航路逐采样点调用IsInCoverage"。 原因:仿真是按 planner 规划执行的——planner 判定"威胁能被发现"→ 据此规划拦截→ 仿真照此执行→ 威胁被摧毁。若 planner 用 2D 判定"能发现",但威胁实际高度超出 3D 球冠范围(探测设备真实能力达不到),就会出现**"根本没探测到、却被规划拦截并摧毁"的物理错误结局**——防空系统不可能拦截它没发现的目标。因此 planner 与实时探测必须共用同一个 3D 球冠判定(
IsInCoverage),这不是为了"好看的统一",而是正确性硬约束。采样法 vs 解析法:三维球冠-折线解析求交复杂度高,改用沿航路采样(步长 ≤ 50m)。采样引入的弧长误差 ≤ 一个步长,换算到推荐抛撒时机 ≤ ~1.5s(典型 120km/h),远小于云团膨胀窗口(~30s)和云团重叠冗余,不影响火力计划结果。所以采样误差可接受,但 2D→3D 的几何升级不可省略。
14.3 探测源构成
DetectionSource 来自两类装备(SimulationEngine.BuildDetectionSources):
统一信息网络探测源 = 独立探测节点 + 火力单元自带探测
(EquipmentRole.Detection) (LaunchPlatform 的 RadarRange/EO/IR 字段)
判定规则:任一探测字段(RadarRange/EORange/IRRange)非 null 且 > 0,即为有效探测源。
14.4 探测精度与抛撒散布
DetectionAccuracy(m):探测设备的位置误差。- 精度越差 → planner 增大横向覆盖范围,对冲定位不确定性:
// DetectionCalculator.SpreadRadius float SpreadRadius(float accuracy) => Math.Max(0f, accuracy); // 散布半径 ≈ 精度值 - 运行时实时探测输出目标坐标时,同样按此散布半径叠加位置扰动(待实现)。
14.5 实时探测设计(待实现)
14.5.1 DetectionSource / DetectionEntity
DetectionSource 需扩展三维几何参数(决策 5):
public class DetectionSource
{
public Vector3 Position { get; set; }
public float RadarRange { get; set; } // 雷达水平距离 m
public float EORange { get; set; } // 光电水平距离 m(受 Visibility 衰减)
public float IRRange { get; set; } // 红外水平距离 m
public float Accuracy { get; set; } // 探测精度 m(位置误差)
// ── 三维球冠几何参数(决策 5,待新增)──
public float MinElevation { get; set; } // 俯仰角下限(度)
public float MaxElevation { get; set; } // 俯仰角上限(度)
public float MinDetectAlt { get; set; } // 可探测高度下界 m
public float MaxDetectAlt { get; set; } // 可探测高度上界 m
}
// 待新增:仿真运行时的探测设备实体
public class DetectionEntity
{
public string Id { get; }
public DetectionSource Source { get; }
// 每个威胁的探测状态机(按无人机 Id 索引)
// Undetected ⇄ Detected(决策 1:离开范围回退 Undetected,再次进入重新触发事件)
}
14.5.2 Tick 内扫描逻辑(接入 5.3 第5步)
FOR EACH 飞行无人机(维护"本帧已发现该目标的最优探测设备"暂存):
bestDetector = null; bestTime = +∞; bestAccuracy = +∞ // 决策 2:最早发现,同时则精度高
FOR EACH DetectionEntity:
effRange = DetectionCalculator.EffectiveRange(雷达, 光电, 红外, 场景能见度)
inCover = DetectionCalculator.IsInCoverage( // 三维球冠判定(14.2.1)
drone.Pos, detector.Pos, effRange,
MinElevation, MaxElevation, MinDetectAlt, MaxDetectAlt)
IF inCover:
IF 该无人机对此设备状态 == Undetected:
标记"候选首次发现",按决策 2 比较更新 bestDetector(最早;同 tick 取精度高)
该无人机对此设备状态 → Detected
ELSE:
该无人机对此设备状态 → Undetected // 决策 1:离开即回退
// 本帧所有设备遍历完后,统一触发至多一次 TargetDetected(决策 2 融合)
IF bestDetector != null:
触发 OnTargetDetected(
SimEvent { Type = TargetDetected,
SourceId = bestDetector.Id, // 最早/精度最高者
TargetId = drone.Id,
OccurredAt = SimulationTime })
> 决策 3/4:实时探测只产生事件与可视化,不回调 planner、不改 FireSchedule、不触发额外拦截
14.5.3 TargetDetected 事件契约
| 字段 | 取值 |
|---|---|
Type |
SimEventType.TargetDetected |
SourceId |
探测设备 Id |
TargetId |
被发现的无人机 Id |
OccurredAt |
发现时刻(仿真秒) |
DataJson |
发现点坐标 + 探测精度(待定义结构) |
Description |
如"雷达 R1 发现 drone_3 @(x,y,z)" |
事件写入主库 SimEvent 表,是时序图的核心数据源之一(与 MunitionLaunched/DroneDestroyed 等并列)。
14.6 探测行为决策(已确认)
下列探测行为规则已确认,实现时严格遵循:
| # | 议题 | 决策 | 说明 |
|---|---|---|---|
| 1 | 目标离开探测范围后状态 | 回退 Undetected | 失去接触即认为丢失目标。若再次进入(同一设备或另一设备)→ 重新置 Detected 并再次触发 TargetDetected(对应真实防空"失跟→重新捕获",事件流允许多次发现记录) |
| 2 | 多设备发现同一目标的融合 | 取最早发现者;同时发现取精度最高者 | TargetDetected.SourceId 取最早发现该目标的设备;若同一 tick 内多设备同时首次发现,取 DetectionAccuracy 值最小(精度最高)的设备 |
| 3 | 实时探测是否影响 FireSchedule | 不影响 | FireSchedule 保持 planner 一次性生成(链路 A)。实时探测是纯只读观测层,不回调 planner、不改发射计划 |
| 4 | 探测盲区/漏网目标 | 仅可视化提示,不触发额外行为 | 配合决策 3:实时探测整体定位为观测+展示(时序图/告警文字/UI 标记),不驱动任何拦截逻辑 |
| 5 | 高度与角度范围 | 支持,3D 球冠几何 | 探测范围从纯水平面圆升级为以设备为中心的三维球冠(详见 14.2.1);几何参数随装备配置录入(详见 14.7) |
14.7 数据模型变更(实现清单)
实现实时探测 + 3D 球冠所需的数据模型改动,集中在此便于落地追踪:
| 改动项 | 位置 | 内容 | 状态 |
|---|---|---|---|
| EquipmentDeployment 表加字段 | 4.3 | MinElevation/MaxElevation(度)、MinDetectAlt/MaxDetectAlt(m),均可空 |
待加列 |
| DetectionSource 类加字段 | 14.5.1 | 同上 4 个三维几何字段 | 待加属性 |
| DetectionCalculator.IsInCoverage | 14.2.1 | 三维球冠判定(水平距离 + 俯仰角 + 高度门限),planner 与实时探测共用 | 待实现 |
| DetectionCalculator.EffectiveRange | 14.2 | 现有签名不变(返回综合水平距离基准),3D 判定在其上层 | 已实现(基础) |
| planner 适配 3D | 14.2.1 | EarliestDetection 由"线段-圆求交"改为"沿航路采样点调 IsInCoverage"。正确性硬要求:planner 判定=仿真事实,必须与真实 3D 探测能力一致,否则"未探测却拦截" |
待改造 |
| SimulationEngine 加 _detectionEntities | 5.1 | Initialize 时从配置构建 List<DetectionEntity>,Tick 第5步遍历 |
待实现 |
| Tick 第5步实时探测 | 5.3 / 14.5.2 | 扫描 + 状态机 + 决策1/2/3/4 落地,触发 TargetDetected |
待实现 |
数据库迁移:
EquipmentDeployment新增 4 列均为 NULLABLE,不影响存量数据;SimulationEngine.BuildDetectionSources读取新字段构造DetectionSource(缺失时按"无角度/高度限制"的退化球冠处理,等价当前 2D 行为,保证平滑过渡)。
十五、待后续讨论
| # | 议题 | 说明 |
|---|---|---|
| 1 | 第三方 DLL 的具体接口规范 | 需与供应商对齐函数签名、数据结构、调用约定 |
| 2 | 3 种毁伤机制的差异化细节 | 吸入式灭火 vs 爆燃式 vs 吸入式爆炸的毁伤曲线差异 |
| 3 | 探测设备融合逻辑 | 多设备发现同一目标时的数据融合策略(→ 见 14.6 待决策 #2) |
| 4 | 火控分配策略 | 多目标时的优先级分配规则(→ 实时探测接入后见 14.6 待决策 #3) |
| 5 | 蜂群模式运动模型 | 随机扰动范围、个体行为差异 |
| 6 | 报告模板具体格式 | 统计维度、图表需求 |
| 7 | 场景类型对仿真的影响 | 山区是否影响探测/运动 |
| 8 | PDF/Word 导出库选型 | QuestPDF 或其他 .NET Standard 2.1 方案 |
| 9 | 预置典型目标库内容 | 具体包含哪些无人机型号及参数 |
| 10 | 扩散模型的大气稳定度参数 | 不同天气条件下 σ 的增长速率如何量化 |
| 11 | 毁伤曲线的具体数值 | 三种机制的 damageRate、threshold、burstDamage 等参数取值 |
| 12 | 时空交汇优化的精度 | 离散采样步长 vs 解析解的选择 |