diff --git a/docs/对接文档_Unity前端.md b/docs/对接文档_Unity前端.md index 8288382..63578ff 100644 --- a/docs/对接文档_Unity前端.md +++ b/docs/对接文档_Unity前端.md @@ -21,14 +21,15 @@ src/Unity/Assets/Scripts/Managers/ ← 桥接脚本 --- -## 二、快速验证 +## 二、快速开始 -1. 将上述文件拖入 Unity 项目,等编译 -2. 创建空 GameObject,挂 `ManagerVerification` -3. Inspector 右键 → `Run Full Verification` -4. Console 看到 7 行 OK +1. 将 `defaults.json` 和 `planner_config.json` 放到 `Assets/StreamingAssets/` +2. 创建空 GameObject,挂 `SimulationBootstrap` +3. 运行 — 自动种子数据库,选一个 `[Demo]` 想定,启动仿真 -> 首次运行自动种子 6 个 `[Demo]` 预设想定到数据库,含完整配置。 +> `SimulationBootstrap` 是前端开发的参考模板,展示预设模式(一行代码)和自定义模式(用 DefaultData 预设组装)两种路径。 + +> 如需验证全部模块,挂 `ManagerVerification` → Inspector 右键 `Run Full Verification`。 --- diff --git a/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll b/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll index 3a16729..0d3bf12 100644 Binary files a/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll and b/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll differ diff --git a/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.pdb b/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.pdb index 0959f3e..88c1caf 100644 Binary files a/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.pdb and b/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.pdb differ diff --git a/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs b/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs index 5bd66ef..5456498 100644 --- a/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs +++ b/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using System.IO; +using System.Linq; using CounterDrone.Core; using CounterDrone.Core.Algorithms; using CounterDrone.Core.Models; @@ -11,12 +11,19 @@ using AVector3 = CounterDrone.Core.Algorithms.Vector3; namespace CounterDrone.Unity { - /// 一键启动:创建想定 → 推荐方案 → 运行仿真 + /// + /// 前端参考模板 — 展示两种运行方式: + /// 1. 预设模式:直接运行数据库中的 [Demo] 想定(最简单) + /// 2. 自定义模式:用 DefaultData 预设快速组装新想定 → Planner → 仿真 + /// public class SimulationBootstrap : MonoBehaviour { - [Header("Simulation Settings")] + [Header("Mode")] + [SerializeField] private bool _usePreset = true; // true=预设想定, false=自定义 + + [Header("Custom Settings")] [SerializeField] private float _droneSpeed = 200f; - [SerializeField] private float _routeLength = 20000f; + [SerializeField] private float _routeLength = 5000f; [SerializeField] private float _timeScale = 4f; public SimulationRunner Runner { get; private set; } @@ -24,117 +31,97 @@ namespace CounterDrone.Unity void Start() { var paths = new UnityPathProvider(); - - // 清理上次残留数据 - if (System.IO.File.Exists(paths.GetMainDbPath())) - System.IO.File.Delete(paths.GetMainDbPath()); - if (System.IO.Directory.Exists(paths.GetFramesDir())) - System.IO.Directory.Delete(paths.GetFramesDir(), true); - var dbm = new DatabaseManager(paths); - var db = dbm.OpenMainDb(); + var db = dbm.OpenMainDb(); // 自动种子弹药 + 6个[Demo]想定 SqliteConnectionTracker.Track(db); - foreach (var a in DefaultData.Load(paths).Ammunition) db.Insert(a); - // 创建想定 var scenario = new ScenarioService( new SimTaskRepository(db), new CombatSceneRepository(db), new ControlZoneRepository(db), new TargetConfigRepository(db), new EquipmentDeploymentRepository(db), new CloudDispersalRepository(db), new RoutePlanRepository(db), new WaypointRepository(db)); - var task = scenario.CreateTask("Unity Demo", ""); - var taskId = task.Id; + string taskId; - scenario.SaveScene(taskId, new CombatScene + if (_usePreset) { - SceneType = (int)SceneType.Plain, WindSpeed = 3, - WindDirection = (int)WindDirection.W, Temperature = 22, - }); - scenario.SaveTarget(taskId, new TargetConfig - { - WaveId = "default", - TargetType = (int)TargetType.Piston, PowerType = (int)PowerType.Piston, - Quantity = 1, TypicalSpeed = _droneSpeed, TypicalAltitude = 500, - }); - scenario.SaveRoute(taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single }, - new List + // 方式1:直接使用预设想定(最简单) + var demos = scenario.SearchTasks("[Demo]", null, null, 1, 1); + if (demos.TotalCount == 0) { - new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = _droneSpeed }, - new Waypoint { PosX = _routeLength, PosY = 500, PosZ = 0, Speed = _droneSpeed }, - }); + Debug.LogError("无预设想定,请确认 defaults.json 已放入 StreamingAssets"); + return; + } + taskId = demos.Items[0].Id; + Debug.Log($"使用预设想定: {demos.Items[0].Name}"); + } + else + { + // 方式2:用 DefaultData 预设组装自定义想定 + var defaults = DefaultData.Load(paths); - // 推荐方案 - var detail = scenario.GetTaskDetail(taskId); - var ammoCatalog = db.Table().ToList(); - var planner = new DefensePlanner(ammoCatalog, PlannerConfig.Load(paths)); - var droneGroup = new DroneWave - { - WaveId = "default", - Target = detail.Targets[0], - Route = detail.Routes[0], - Waypoints = detail.WaypointGroups["default"], - }; - var fireUnits = new List(); - for (int i = 0; i < 8; i++) - fireUnits.Add(new FireUnit - { - Id = $"u{i}", - Type = PlatformType.GroundBased, - Position = new AVector3(5000 + i * 50, 0, 50), - MuzzleVelocity = 800, - TotalMunitions = 3, - AmmoTypes = new() { AerosolType.InertGas, AerosolType.ActiveMaterial, AerosolType.ActiveFuel }, - }); - var result = planner.Plan(fireUnits, new List { droneGroup }, detail.Scene, new List()); - scenario.SaveCloudDispersal(taskId, new CloudDispersal - { - PositionX = (droneGroup.Waypoints[0].PosX + droneGroup.Waypoints[^1].PosX) / 2, - PositionY = (droneGroup.Waypoints[0].PosY + droneGroup.Waypoints[^1].PosY) / 2, - PositionZ = (droneGroup.Waypoints[0].PosZ + droneGroup.Waypoints[^1].PosZ) / 2, - DisperseHeight = droneGroup.Target.TypicalAltitude, - Source = "Algorithm", - }); + var task = scenario.CreateTask("Unity Demo", ""); + taskId = task.Id; - var equips = new List(); - foreach (var u in fireUnits) - equips.Add(new EquipmentDeployment + scenario.SaveScene(taskId, defaults.Weather.First(w => w.Id == "sunny-calm").ToCombatScene()); + scenario.SaveTarget(taskId, defaults.Targets.First(p => p.Id == "shahed").ToTargetConfig()); + scenario.SaveRoute(taskId, "default", + defaults.Formations.First(f => f.Id == "single").ToRoutePlan(), + defaults.Routes.First(r => _routeLength > 6000 ? r.Id == "10km-h500" : r.Id == "5km-h500") + .ToWaypoints(_droneSpeed)); + + // 从火力单元预设构建 FireUnit → Planner 自动生成方案 → 引擎执行 + var detail = scenario.GetTaskDetail(taskId); + var ammoCatalog = db.Table().ToList(); + var planner = new DefensePlanner(ammoCatalog, PlannerConfig.Load(paths)); + var threats = new List { - EquipmentRole = (int)EquipmentRole.LaunchPlatform, - PlatformType = (int)u.Type, Quantity = 1, - PositionX = u.Position.X, PositionY = u.Position.Y, PositionZ = u.Position.Z, - AerosolType = (int)u.AmmoTypes[0], - MunitionCount = u.TotalMunitions, Cooldown = u.Cooldown, MuzzleVelocity = u.MuzzleVelocity, - CruiseSpeed = u.CruiseSpeed > 0 ? u.CruiseSpeed : null, - ReleaseAltitude = u.ReleaseAltitude > 0 ? u.ReleaseAltitude : null, + new DroneWave { WaveId = "default", Target = detail.Targets[0], Route = detail.Routes[0], Waypoints = detail.WaypointGroups["default"] } + }; + var fireUnits = new List(); + for (int i = 0; i < 4; i++) + fireUnits.Add(new FireUnit + { + Id = $"fu{i}", + Type = PlatformType.GroundBased, + Position = new AVector3(_routeLength / 2f + i * 50, 0, 50), + MuzzleVelocity = 800, TotalMunitions = 3, + AmmoTypes = new() { AerosolType.InertGas, AerosolType.ActiveMaterial, AerosolType.ActiveFuel }, + }); + var plan = planner.Plan(fireUnits, threats, detail.Scene, new List()); + + scenario.SaveCloudDispersal(taskId, new CloudDispersal + { + AerosolType = (int)AerosolType.InertGas, + DisperseHeight = detail.Targets[0].TypicalAltitude, + Source = "Algorithm", }); - scenario.SaveDeployment(taskId, equips); + scenario.SaveDeployment(taskId, new List + { + defaults.FireUnits.First(f => f.Id == "ground-light") + .ToEquipmentDeployment(AerosolType.InertGas, 1, _routeLength / 2f, 0, 50), + }); + scenario.UpdateStep(taskId, 5); + + Debug.Log($"自定义想定: {plan.Best.ThreatsEngaged} 威胁已分配, {plan.Best.MergedSchedule.Count} 发, 概率 {plan.Best.OverallProbability:P0}"); + } SqliteConnectionTracker.Untrack(db); db.Dispose(); - var plan = result.Best; - Debug.Log($"规划方案: {plan.ThreatsEngaged} 威胁被分配, {plan.MergedSchedule.Count} 发, 概率 {plan.OverallProbability:P0}"); - // 运行仿真 Runner = GetComponent(); if (Runner == null) Runner = gameObject.AddComponent(); else Runner.enabled = true; Runner.LoadAndStart(taskId); + Runner.Engine.TimeScale = _timeScale; - // 设置倍速 - if (Runner.Engine != null) - { - Runner.Engine.TimeScale = _timeScale; - Debug.Log($"Engine state: {Runner.Engine.State}, drones: {Runner.Engine.Drones.Count}"); - } - - // 移动 Game 视图相机 + // 定位相机 var cam = Camera.main; - if (cam == null) { var cgo = new GameObject("MainCamera"); cgo.AddComponent(); cam = cgo.GetComponent(); cam.tag = "MainCamera"; } - cam.transform.position = new UVector3(_routeLength / 2f, 2000, -3000); - cam.transform.LookAt(new UVector3(_routeLength / 2f, 500, 0)); - Debug.Log("Camera positioned. In Scene view, double-click 'Simulation' in Hierarchy to focus."); + if (cam == null) { cam = new GameObject("MainCamera").AddComponent(); cam.tag = "MainCamera"; } + float midX = _usePreset ? 2500f : _routeLength / 2f; + cam.transform.position = new UVector3(midX, 2000, -3000); + cam.transform.LookAt(new UVector3(midX, 500, 0)); } } }