CounterDroneBackend/src/Unity/Assets/Scripts/Managers/SimulationRunner.cs

235 lines
9.3 KiB
C#

using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using CounterDrone.Core;
using CounterDrone.Core.Algorithms;
using CounterDrone.Core.Models;
using CounterDrone.Core.Repository;
using CounterDrone.Core.Services;
using CounterDrone.Core.Simulation;
using UnityEngine;
using UVector3 = UnityEngine.Vector3;
using AVector3 = CounterDrone.Core.Algorithms.Vector3;
namespace CounterDrone.Unity
{
public class SimulationRunner : MonoBehaviour
{
private bool _loggedStart;
[SerializeField] private GameObject _dronePrefab;
[SerializeField] private GameObject _cloudPrefab;
private SimulationEngine _engine;
private FrameDataStore _frameStore;
private IScenarioService _scenario;
private IPathProvider _paths;
private string _taskId;
private readonly Dictionary<string, GameObject> _entityVisuals = new();
private readonly Dictionary<string, GameObject> _clouds = new();
private readonly HashSet<string> _knownMunitions = new();
public SimulationEngine Engine => _engine;
void Awake()
{
_paths = new UnityPathProvider();
var db = new DatabaseManager(_paths).OpenMainDb();
_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));
_frameStore = new FrameDataStore(_paths);
_engine = new SimulationEngine(_scenario, _frameStore, new DamageModelRouter(), _paths);
}
public void LoadAndStart(string taskId)
{
_taskId = taskId;
var detail = _scenario.GetTaskDetail(taskId);
if (detail == null) { Debug.LogError("Task not found"); return; }
var cloud = detail.Cloud;
if (cloud.RecommendedTiming > 0)
{
var db = new DatabaseManager(_paths).OpenMainDb();
var ammoCatalog = db.Table<AmmunitionSpec>().ToList();
db.Close();
var schedule = BuildFireSchedule(detail, ammoCatalog);
_engine.SetFireSchedule(schedule);
}
_engine.Initialize(taskId);
foreach (var drone in _engine.Drones)
SpawnDroneVisual(drone);
}
void Update()
{
if (_engine == null || _engine.State != SimulationState.Running) return;
if (!_loggedStart) { Debug.Log($"Ticking: drones={_engine.Drones.Count}, timescale={_engine.TimeScale}"); _loggedStart = true; }
var result = _engine.Tick(Time.deltaTime);
// 每秒输出无人机位置
if (Time.frameCount % 60 == 0 && _engine.Drones.Count > 0)
{
var d = _engine.Drones[0];
Debug.Log($"Drone: ({d.PosX:F0}, {d.PosY:F0}, {d.PosZ:F0}) Status={d.Status}");
}
foreach (var snap in result.EntitySnapshots)
{
if (!_entityVisuals.TryGetValue(snap.EntityId, out var go) || go == null) continue;
go.transform.position = new UVector3(snap.PosX, snap.PosY, snap.PosZ);
if (snap.EntityType == EntityType.Cloud)
{
float r = ParseCloudRadius(snap.StateData);
float opacity = ParseCloudOpacity(snap.StateData);
go.transform.localScale = UVector3.one * r;
var mat = go.GetComponent<Renderer>().material;
mat.color = new Color(1, 0, 0, opacity);
}
}
foreach (var cloud in _engine.Clouds)
{
if (!_clouds.ContainsKey(cloud.Id))
{
var pos = new UVector3(cloud.Dispersion.Center.X, cloud.Dispersion.Center.Y, cloud.Dispersion.Center.Z);
var prefab = _cloudPrefab != null ? _cloudPrefab : GameObject.CreatePrimitive(PrimitiveType.Sphere);
var go = Instantiate(prefab, pos, Quaternion.identity);
go.name = cloud.Id;
float r = cloud.Dispersion.Radius;
go.transform.localScale = UVector3.one * r;
var mat = go.GetComponent<Renderer>().material;
mat.color = new Color(1, 0, 0, 0.3f);
_clouds[cloud.Id] = go;
_entityVisuals[cloud.Id] = go;
}
}
// 炮弹飞行轨迹
foreach (var m in _engine.Munitions)
{
if (!_knownMunitions.Contains(m.Id))
{
var go = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
go.name = m.Id;
go.transform.localScale = new UVector3(2, 10, 2);
_knownMunitions.Add(m.Id);
_entityVisuals[m.Id] = go;
}
if (_entityVisuals.TryGetValue(m.Id, out var mgo))
mgo.transform.position = new UVector3(m.PosX, m.PosY, m.PosZ);
}
// 清理已到达的炮弹(已转为云团)
foreach (var id in _knownMunitions.ToArray())
{
if (!_engine.Munitions.Any(m => m.Id == id) && _entityVisuals.TryGetValue(id, out var go))
{
Destroy(go);
_knownMunitions.Remove(id);
_entityVisuals.Remove(id);
}
}
if (result.State == SimulationState.Completed)
Debug.Log($"Simulation done. Drone: {_engine.Drones[0].Status}, HP: {_engine.Drones[0].Hp:F2}");
}
public void Stop() => _engine?.Stop();
void OnDestroy() => Stop();
private void SpawnDroneVisual(DroneEntity drone)
{
var prefab = _dronePrefab != null ? _dronePrefab : GameObject.CreatePrimitive(PrimitiveType.Cube);
var go = Instantiate(prefab, new UVector3(drone.PosX, drone.PosY, drone.PosZ), Quaternion.identity);
go.name = drone.Id;
go.transform.localScale = UVector3.one * 50f;
_entityVisuals[drone.Id] = go;
}
private float ParseCloudOpacity(string stateData)
{
try
{
using var doc = JsonDocument.Parse(stateData);
if (doc.RootElement.TryGetProperty("opacity", out var o))
return o.GetSingle();
}
catch { }
return 0.5f;
}
{
try
{
using var doc = JsonDocument.Parse(stateData);
if (doc.RootElement.TryGetProperty("radius", out var r))
return r.GetSingle();
}
catch { }
return 1f;
}
private List<FireEvent> BuildFireSchedule(TaskFullConfig detail, List<AmmunitionSpec> ammoCatalog)
{
var schedule = new List<FireEvent>();
var cloud = detail.Cloud;
if (!cloud.RecommendedTiming.HasValue || cloud.RecommendedTiming <= 0) return schedule;
var platforms = detail.Equipment
.FindAll(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform);
if (platforms.Count == 0) return schedule;
var ammo = ammoCatalog.Find(a => a.AerosolType == cloud.AerosolType);
if (ammo == null) return schedule;
var target = detail.Targets[0];
var avgSpeed = (float)target.TypicalSpeed / 3.6f;
var neededExposure = cloud.AerosolType == (int)AerosolType.ActiveMaterial ? 2f : 6f;
var spacing = (float)ammo.InitialRadius * 1.5f;
var diameter = 2f * (float)ammo.InitialRadius;
var roundsNeeded = Mathf.Max(1, Mathf.CeilToInt((neededExposure * avgSpeed - diameter) / spacing) + 1);
var p0 = platforms[0];
var mv = (float)(p0.MuzzleVelocity ?? 800);
var releaseAlt = (float)cloud.DisperseHeight;
for (int r = 0; r < roundsNeeded; r++)
{
var offset = (r - (roundsNeeded - 1) / 2f) * spacing;
var tx = (float)cloud.PositionX + offset; var ty = (float)cloud.PositionY; var tz = (float)cloud.PositionZ;
var dx = tx - (float)p0.PositionX; var dz = tz - (float)p0.PositionZ;
var dist = Mathf.Sqrt(dx * dx + dz * dz);
var angle = Kinematics.CalculateLaunchAngle(dist, mv, releaseAlt);
var sinA = Mathf.Sin(angle);
var tUp = mv * sinA / 9.81f;
var peakH = mv * mv * sinA * sinA / (2f * 9.81f);
var dropH = Mathf.Max(0, peakH - releaseAlt);
var flightTime = tUp + Mathf.Sqrt(2f * dropH / 9.81f);
var fireTime = (float)cloud.RecommendedTiming.Value - flightTime;
if (fireTime < 0) fireTime = 0.1f;
schedule.Add(new FireEvent
{
FireTime = fireTime, PlatformIndex = r % platforms.Count,
TargetX = tx, TargetY = ty, TargetZ = tz, MuzzleVelocity = mv,
});
}
return schedule;
}
}
}