333 lines
11 KiB
C#
333 lines
11 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
|
|
/// <summary>
|
|
/// Manages the simulation by handling missiles, targets, and their assignments.
|
|
/// Implements the Singleton pattern to ensure only one instance exists.
|
|
/// </summary>
|
|
public class SimManager : MonoBehaviour {
|
|
/// <summary>
|
|
/// Singleton instance of SimManager.
|
|
/// </summary>
|
|
public static SimManager Instance { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Configuration settings for the simulation.
|
|
/// </summary>
|
|
[SerializeField]
|
|
public SimulationConfig simulationConfig;
|
|
|
|
private List<Missile> _missiles = new List<Missile>();
|
|
private List<Missile> _activeMissiles = new List<Missile>();
|
|
private List<Target> _unassignedTargets = new List<Target>();
|
|
private List<Target> _targets = new List<Target>();
|
|
private List<Target> _activeTargets = new List<Target>();
|
|
private float _elapsedSimulationTime = 0f;
|
|
private float endTime = 100f; // Set an appropriate end time
|
|
private bool simulationRunning = false;
|
|
|
|
private IAssignment _assignmentScheme;
|
|
|
|
public delegate void SimulationEventHandler();
|
|
public event SimulationEventHandler OnSimulationEnded;
|
|
public event SimulationEventHandler OnSimulationStarted;
|
|
|
|
/// <summary>
|
|
/// Gets the elapsed simulation time.
|
|
/// </summary>
|
|
/// <returns>The elapsed time in seconds.</returns>
|
|
public double GetElapsedSimulationTime() {
|
|
return _elapsedSimulationTime;
|
|
}
|
|
|
|
public List<Missile> GetActiveMissiles() {
|
|
return _activeMissiles;
|
|
}
|
|
|
|
public List<Target> GetActiveTargets() {
|
|
return _activeTargets;
|
|
}
|
|
|
|
public List<Agent> GetActiveAgents() {
|
|
return _activeMissiles.ConvertAll(missile => missile as Agent).Concat(_activeTargets.ConvertAll(target => target as Agent)).ToList();
|
|
}
|
|
|
|
void Awake() {
|
|
// Ensure only one instance of SimManager exists
|
|
if (Instance == null) {
|
|
Instance = this;
|
|
DontDestroyOnLoad(gameObject);
|
|
} else {
|
|
Destroy(gameObject);
|
|
}
|
|
simulationConfig = ConfigLoader.LoadSimulationConfig("1_salvo_1_hydra_7_drones.json");
|
|
Debug.Log(simulationConfig);
|
|
}
|
|
|
|
void Start() {
|
|
// Slow down time by simulationConfig.timeScale
|
|
if (Instance == this) {
|
|
StartSimulation();
|
|
}
|
|
}
|
|
|
|
public void SetTimeScale(float timeScale) {
|
|
Time.timeScale = timeScale;
|
|
Time.fixedDeltaTime = Time.timeScale * 0.02f;
|
|
Time.maximumDeltaTime = Time.timeScale * 0.15f;
|
|
}
|
|
|
|
public void StartSimulation() {
|
|
InitializeSimulation();
|
|
simulationRunning = true;
|
|
OnSimulationStarted?.Invoke();
|
|
}
|
|
|
|
public void PauseSimulation() {
|
|
SetTimeScale(0);
|
|
simulationRunning = false;
|
|
}
|
|
|
|
public void ResumeSimulation() {
|
|
SetTimeScale(simulationConfig.timeScale);
|
|
simulationRunning = true;
|
|
}
|
|
|
|
public bool IsSimulationRunning() {
|
|
return simulationRunning;
|
|
}
|
|
|
|
private void InitializeSimulation() {
|
|
List<Missile> missiles = new List<Missile>();
|
|
// Create missiles based on config
|
|
foreach (var swarmConfig in simulationConfig.missile_swarm_configs) {
|
|
for (int i = 0; i < swarmConfig.num_agents; i++) {
|
|
var missile = CreateMissile(swarmConfig.agent_config);
|
|
missile.OnAgentHit += RegisterMissileHit;
|
|
missile.OnAgentMiss += RegisterMissileMiss;
|
|
}
|
|
}
|
|
|
|
List<Target> targets = new List<Target>();
|
|
// Create targets based on config
|
|
foreach (var swarmConfig in simulationConfig.target_swarm_configs) {
|
|
for (int i = 0; i < swarmConfig.num_agents; i++) {
|
|
var target = CreateTarget(swarmConfig.agent_config);
|
|
target.OnAgentHit += RegisterTargetHit;
|
|
target.OnAgentMiss += RegisterTargetMiss;
|
|
}
|
|
}
|
|
|
|
_assignmentScheme = new ThreatAssignment();
|
|
OnSimulationStarted?.Invoke();
|
|
}
|
|
|
|
public void AssignMissilesToTargets() {
|
|
AssignMissilesToTargets(_missiles);
|
|
}
|
|
|
|
public void RegisterMissileHit(Agent missile) {
|
|
if (missile is Missile missileComponent) {
|
|
_activeMissiles.Remove(missileComponent);
|
|
}
|
|
}
|
|
|
|
public void RegisterMissileMiss(Agent missile) {
|
|
if (missile is Missile missileComponent) {
|
|
_activeMissiles.Remove(missileComponent);
|
|
}
|
|
}
|
|
|
|
public void RegisterTargetHit(Agent target) {
|
|
if (target is Target targetComponent) {
|
|
_activeTargets.Remove(targetComponent);
|
|
}
|
|
}
|
|
|
|
public void RegisterTargetMiss(Agent target) {
|
|
if (target is Target targetComponent) {
|
|
_unassignedTargets.Add(targetComponent);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Assigns the specified list of missiles to available targets based on the assignment scheme.
|
|
/// </summary>
|
|
/// <param name="missilesToAssign">The list of missiles to assign.</param>
|
|
public void AssignMissilesToTargets(List<Missile> missilesToAssign) {
|
|
// Convert Missile and Target lists to Agent lists
|
|
List<Agent> missileAgents = new List<Agent>(missilesToAssign.ConvertAll(m => m as Agent));
|
|
// Convert Target list to Agent list, excluding already assigned targets
|
|
List<Agent> targetAgents = _unassignedTargets.ToList<Agent>();
|
|
|
|
// Perform the assignment
|
|
IEnumerable<IAssignment.AssignmentItem> assignments =
|
|
_assignmentScheme.Assign(missileAgents, targetAgents);
|
|
|
|
// Apply the assignments to the missiles
|
|
foreach (var assignment in assignments) {
|
|
if (assignment.MissileIndex < missilesToAssign.Count) {
|
|
Missile missile = missilesToAssign[assignment.MissileIndex];
|
|
Target target = _unassignedTargets[assignment.TargetIndex];
|
|
missile.AssignTarget(target);
|
|
Debug.Log($"Missile {missile.name} assigned to target {target.name}");
|
|
}
|
|
}
|
|
// TODO this whole function should be optimized
|
|
_unassignedTargets.RemoveAll(
|
|
target => missilesToAssign.Any(missile => missile.GetAssignedTarget() == target));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a missile based on the provided configuration.
|
|
/// </summary>
|
|
/// <param name="config">Configuration settings for the missile.</param>
|
|
/// <returns>The created Missile instance, or null if creation failed.</returns>
|
|
public Missile CreateMissile(AgentConfig config) {
|
|
string prefabName = config.missile_type switch { MissileType.HYDRA_70 => "Hydra70",
|
|
MissileType.MICROMISSILE => "Micromissile",
|
|
_ => "Hydra70" };
|
|
|
|
GameObject missileObject = CreateAgent(config, prefabName);
|
|
if (missileObject == null)
|
|
return null;
|
|
|
|
// Missile-specific logic
|
|
switch (config.dynamic_config.sensor_config.type) {
|
|
case SensorType.IDEAL:
|
|
missileObject.AddComponent<IdealSensor>();
|
|
break;
|
|
default:
|
|
Debug.LogError($"Sensor type '{config.dynamic_config.sensor_config.type}' not found.");
|
|
break;
|
|
}
|
|
|
|
_missiles.Add(missileObject.GetComponent<Missile>());
|
|
_activeMissiles.Add(missileObject.GetComponent<Missile>());
|
|
|
|
// Assign a unique and simple target ID
|
|
int missileId = _missiles.Count;
|
|
missileObject.name = $"{config.missile_type}_Missile_{missileId}";
|
|
return missileObject.GetComponent<Missile>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a target based on the provided configuration.
|
|
/// </summary>
|
|
/// <param name="config">Configuration settings for the target.</param>
|
|
/// <returns>The created Target instance, or null if creation failed.</returns>
|
|
private Target CreateTarget(AgentConfig config) {
|
|
string prefabName = config.target_type switch {
|
|
TargetType.DRONE => "DroneTarget", TargetType.MISSILE => "MissileTarget",
|
|
_ => throw new System.ArgumentException($"Unsupported target type: {config.target_type}")
|
|
};
|
|
GameObject targetObject = CreateAgent(config, prefabName);
|
|
if (targetObject == null)
|
|
return null;
|
|
|
|
_targets.Add(targetObject.GetComponent<Target>());
|
|
_activeTargets.Add(targetObject.GetComponent<Target>());
|
|
_unassignedTargets.Add(targetObject.GetComponent<Target>());
|
|
|
|
// Assign a unique and simple target ID
|
|
int targetId = _targets.Count;
|
|
targetObject.name = $"{config.target_type}_Target_{targetId}";
|
|
return targetObject.GetComponent<Target>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an agent (missile or target) based on the provided configuration and prefab name.
|
|
/// </summary>
|
|
/// <param name="config">Configuration settings for the agent.</param>
|
|
/// <param name="prefabName">Name of the prefab to instantiate.</param>
|
|
/// <returns>The created GameObject instance, or null if creation failed.</returns>
|
|
public GameObject CreateAgent(AgentConfig config, string prefabName) {
|
|
GameObject prefab = Resources.Load<GameObject>($"Prefabs/{prefabName}");
|
|
if (prefab == null) {
|
|
Debug.LogError($"Prefab '{prefabName}' not found in Resources/Prefabs folder.");
|
|
return null;
|
|
}
|
|
|
|
Vector3 noiseOffset = Utilities.GenerateRandomNoise(config.standard_deviation.position);
|
|
Vector3 noisyPosition = config.initial_state.position + noiseOffset;
|
|
|
|
GameObject agentObject =
|
|
Instantiate(prefab, noisyPosition, Quaternion.Euler(config.initial_state.rotation));
|
|
|
|
Rigidbody agentRigidbody = agentObject.GetComponent<Rigidbody>();
|
|
Vector3 velocityNoise = Utilities.GenerateRandomNoise(config.standard_deviation.velocity);
|
|
Vector3 noisyVelocity = config.initial_state.velocity + velocityNoise;
|
|
agentRigidbody.velocity = noisyVelocity;
|
|
|
|
agentObject.GetComponent<Agent>().SetAgentConfig(config);
|
|
|
|
return agentObject;
|
|
}
|
|
|
|
public void LoadNewConfig(string configFileName)
|
|
{
|
|
simulationConfig = ConfigLoader.LoadSimulationConfig(configFileName);
|
|
if (simulationConfig != null)
|
|
{
|
|
Debug.Log($"Loaded new configuration: {configFileName}");
|
|
RestartSimulation();
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError($"Failed to load configuration: {configFileName}");
|
|
}
|
|
}
|
|
|
|
|
|
public void RestartSimulation() {
|
|
OnSimulationEnded?.Invoke();
|
|
Debug.Log("Simulation ended");
|
|
// Reset simulation time
|
|
_elapsedSimulationTime = 0f;
|
|
simulationRunning = true;
|
|
|
|
// Clear existing missiles and targets
|
|
foreach (var missile in _missiles) {
|
|
if (missile != null) {
|
|
Destroy(missile.gameObject);
|
|
}
|
|
}
|
|
|
|
foreach (var target in _targets) {
|
|
if (target != null) {
|
|
Destroy(target.gameObject);
|
|
}
|
|
}
|
|
|
|
_missiles.Clear();
|
|
_targets.Clear();
|
|
_unassignedTargets.Clear();
|
|
|
|
StartSimulation();
|
|
}
|
|
|
|
void Update() {
|
|
// Check if all missiles have terminated
|
|
bool allMissilesTerminated = true;
|
|
foreach (var missile in _missiles) {
|
|
if (missile != null && !missile.IsHit() && !missile.IsMiss()) {
|
|
allMissilesTerminated = false;
|
|
break;
|
|
}
|
|
}
|
|
// If all missiles have terminated, restart the simulation
|
|
if (allMissilesTerminated) {
|
|
RestartSimulation();
|
|
}
|
|
|
|
if (simulationRunning && _elapsedSimulationTime < endTime) {
|
|
_elapsedSimulationTime += Time.deltaTime;
|
|
} else if (_elapsedSimulationTime >= endTime) {
|
|
simulationRunning = false;
|
|
Debug.Log("Simulation completed.");
|
|
}
|
|
}
|
|
}
|