refactor: SimulationBootstrap 更新为前端参考模板
两种模式: 预设(选[Demo]想定) / 自定义(DefaultData预设组装) 移除手动插弹药(已自动种子), 使用 DefaultData 天气/目标/航线/火力单元预设 UnityPathProvider 从 StreamingAssets 自动复制数据文件 前端文档: 快速开始改为挂 SimulationBootstrap 即运行
This commit is contained in:
parent
18f7b79401
commit
5f589c9644
@ -21,14 +21,15 @@ src/Unity/Assets/Scripts/Managers/ ← 桥接脚本
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 二、快速验证
|
## 二、快速开始
|
||||||
|
|
||||||
1. 将上述文件拖入 Unity 项目,等编译
|
1. 将 `defaults.json` 和 `planner_config.json` 放到 `Assets/StreamingAssets/`
|
||||||
2. 创建空 GameObject,挂 `ManagerVerification`
|
2. 创建空 GameObject,挂 `SimulationBootstrap`
|
||||||
3. Inspector 右键 → `Run Full Verification`
|
3. 运行 — 自动种子数据库,选一个 `[Demo]` 想定,启动仿真
|
||||||
4. Console 看到 7 行 OK
|
|
||||||
|
|
||||||
> 首次运行自动种子 6 个 `[Demo]` 预设想定到数据库,含完整配置。
|
> `SimulationBootstrap` 是前端开发的参考模板,展示预设模式(一行代码)和自定义模式(用 DefaultData 预设组装)两种路径。
|
||||||
|
|
||||||
|
> 如需验证全部模块,挂 `ManagerVerification` → Inspector 右键 `Run Full Verification`。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -1,5 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.Linq;
|
||||||
using CounterDrone.Core;
|
using CounterDrone.Core;
|
||||||
using CounterDrone.Core.Algorithms;
|
using CounterDrone.Core.Algorithms;
|
||||||
using CounterDrone.Core.Models;
|
using CounterDrone.Core.Models;
|
||||||
@ -11,12 +11,19 @@ using AVector3 = CounterDrone.Core.Algorithms.Vector3;
|
|||||||
|
|
||||||
namespace CounterDrone.Unity
|
namespace CounterDrone.Unity
|
||||||
{
|
{
|
||||||
/// <summary>一键启动:创建想定 → 推荐方案 → 运行仿真</summary>
|
/// <summary>
|
||||||
|
/// 前端参考模板 — 展示两种运行方式:
|
||||||
|
/// 1. 预设模式:直接运行数据库中的 [Demo] 想定(最简单)
|
||||||
|
/// 2. 自定义模式:用 DefaultData 预设快速组装新想定 → Planner → 仿真
|
||||||
|
/// </summary>
|
||||||
public class SimulationBootstrap : MonoBehaviour
|
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 _droneSpeed = 200f;
|
||||||
[SerializeField] private float _routeLength = 20000f;
|
[SerializeField] private float _routeLength = 5000f;
|
||||||
[SerializeField] private float _timeScale = 4f;
|
[SerializeField] private float _timeScale = 4f;
|
||||||
|
|
||||||
public SimulationRunner Runner { get; private set; }
|
public SimulationRunner Runner { get; private set; }
|
||||||
@ -24,117 +31,97 @@ namespace CounterDrone.Unity
|
|||||||
void Start()
|
void Start()
|
||||||
{
|
{
|
||||||
var paths = new UnityPathProvider();
|
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 dbm = new DatabaseManager(paths);
|
||||||
var db = dbm.OpenMainDb();
|
var db = dbm.OpenMainDb(); // 自动种子弹药 + 6个[Demo]想定
|
||||||
SqliteConnectionTracker.Track(db);
|
SqliteConnectionTracker.Track(db);
|
||||||
foreach (var a in DefaultData.Load(paths).Ammunition) db.Insert(a);
|
|
||||||
|
|
||||||
// 创建想定
|
|
||||||
var scenario = new ScenarioService(
|
var scenario = new ScenarioService(
|
||||||
new SimTaskRepository(db), new CombatSceneRepository(db),
|
new SimTaskRepository(db), new CombatSceneRepository(db),
|
||||||
new ControlZoneRepository(db), new TargetConfigRepository(db),
|
new ControlZoneRepository(db), new TargetConfigRepository(db),
|
||||||
new EquipmentDeploymentRepository(db), new CloudDispersalRepository(db),
|
new EquipmentDeploymentRepository(db), new CloudDispersalRepository(db),
|
||||||
new RoutePlanRepository(db), new WaypointRepository(db));
|
new RoutePlanRepository(db), new WaypointRepository(db));
|
||||||
|
|
||||||
var task = scenario.CreateTask("Unity Demo", "");
|
string taskId;
|
||||||
var taskId = task.Id;
|
|
||||||
|
|
||||||
scenario.SaveScene(taskId, new CombatScene
|
if (_usePreset)
|
||||||
{
|
{
|
||||||
SceneType = (int)SceneType.Plain, WindSpeed = 3,
|
// 方式1:直接使用预设想定(最简单)
|
||||||
WindDirection = (int)WindDirection.W, Temperature = 22,
|
var demos = scenario.SearchTasks("[Demo]", null, null, 1, 1);
|
||||||
});
|
if (demos.TotalCount == 0)
|
||||||
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<Waypoint>
|
|
||||||
{
|
{
|
||||||
new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = _droneSpeed },
|
Debug.LogError("无预设想定,请确认 defaults.json 已放入 StreamingAssets");
|
||||||
new Waypoint { PosX = _routeLength, PosY = 500, PosZ = 0, Speed = _droneSpeed },
|
return;
|
||||||
});
|
}
|
||||||
|
taskId = demos.Items[0].Id;
|
||||||
|
Debug.Log($"使用预设想定: {demos.Items[0].Name}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 方式2:用 DefaultData 预设组装自定义想定
|
||||||
|
var defaults = DefaultData.Load(paths);
|
||||||
|
|
||||||
// 推荐方案
|
var task = scenario.CreateTask("Unity Demo", "");
|
||||||
var detail = scenario.GetTaskDetail(taskId);
|
taskId = task.Id;
|
||||||
var ammoCatalog = db.Table<AmmunitionSpec>().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<FireUnit>();
|
|
||||||
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<DroneWave> { droneGroup }, detail.Scene, new List<DetectionSource>());
|
|
||||||
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 equips = new List<EquipmentDeployment>();
|
scenario.SaveScene(taskId, defaults.Weather.First(w => w.Id == "sunny-calm").ToCombatScene());
|
||||||
foreach (var u in fireUnits)
|
scenario.SaveTarget(taskId, defaults.Targets.First(p => p.Id == "shahed").ToTargetConfig());
|
||||||
equips.Add(new EquipmentDeployment
|
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<AmmunitionSpec>().ToList();
|
||||||
|
var planner = new DefensePlanner(ammoCatalog, PlannerConfig.Load(paths));
|
||||||
|
var threats = new List<DroneWave>
|
||||||
{
|
{
|
||||||
EquipmentRole = (int)EquipmentRole.LaunchPlatform,
|
new DroneWave { WaveId = "default", Target = detail.Targets[0], Route = detail.Routes[0], Waypoints = detail.WaypointGroups["default"] }
|
||||||
PlatformType = (int)u.Type, Quantity = 1,
|
};
|
||||||
PositionX = u.Position.X, PositionY = u.Position.Y, PositionZ = u.Position.Z,
|
var fireUnits = new List<FireUnit>();
|
||||||
AerosolType = (int)u.AmmoTypes[0],
|
for (int i = 0; i < 4; i++)
|
||||||
MunitionCount = u.TotalMunitions, Cooldown = u.Cooldown, MuzzleVelocity = u.MuzzleVelocity,
|
fireUnits.Add(new FireUnit
|
||||||
CruiseSpeed = u.CruiseSpeed > 0 ? u.CruiseSpeed : null,
|
{
|
||||||
ReleaseAltitude = u.ReleaseAltitude > 0 ? u.ReleaseAltitude : null,
|
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<DetectionSource>());
|
||||||
|
|
||||||
|
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<EquipmentDeployment>
|
||||||
|
{
|
||||||
|
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);
|
SqliteConnectionTracker.Untrack(db);
|
||||||
db.Dispose();
|
db.Dispose();
|
||||||
|
|
||||||
var plan = result.Best;
|
|
||||||
Debug.Log($"规划方案: {plan.ThreatsEngaged} 威胁被分配, {plan.MergedSchedule.Count} 发, 概率 {plan.OverallProbability:P0}");
|
|
||||||
|
|
||||||
// 运行仿真
|
// 运行仿真
|
||||||
Runner = GetComponent<SimulationRunner>();
|
Runner = GetComponent<SimulationRunner>();
|
||||||
if (Runner == null) Runner = gameObject.AddComponent<SimulationRunner>();
|
if (Runner == null) Runner = gameObject.AddComponent<SimulationRunner>();
|
||||||
else Runner.enabled = true;
|
else Runner.enabled = true;
|
||||||
Runner.LoadAndStart(taskId);
|
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;
|
var cam = Camera.main;
|
||||||
if (cam == null) { var cgo = new GameObject("MainCamera"); cgo.AddComponent<Camera>(); cam = cgo.GetComponent<Camera>(); cam.tag = "MainCamera"; }
|
if (cam == null) { cam = new GameObject("MainCamera").AddComponent<Camera>(); cam.tag = "MainCamera"; }
|
||||||
cam.transform.position = new UVector3(_routeLength / 2f, 2000, -3000);
|
float midX = _usePreset ? 2500f : _routeLength / 2f;
|
||||||
cam.transform.LookAt(new UVector3(_routeLength / 2f, 500, 0));
|
cam.transform.position = new UVector3(midX, 2000, -3000);
|
||||||
Debug.Log("Camera positioned. In Scene view, double-click 'Simulation' in Hierarchy to focus.");
|
cam.transform.LookAt(new UVector3(midX, 500, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user