using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
///
/// Manages the simulation by handling missiles, targets, and their assignments.
/// Implements the Singleton pattern to ensure only one instance exists.
///
public class SimManager : MonoBehaviour {
///
/// Singleton instance of SimManager.
///
public static SimManager Instance { get; private set; }
///
/// Configuration settings for the simulation.
///
[SerializeField]
public SimulationConfig simulationConfig;
private List _activeInterceptors = new List();
[SerializeField]
private List _threatTable = new List();
private Dictionary _threatDataMap = new Dictionary();
private List _interceptorObjects = new List();
private List _threatObjects = new List();
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;
///
/// Gets the elapsed simulation time.
///
/// The elapsed time in seconds.
public double GetElapsedSimulationTime() {
return _elapsedSimulationTime;
}
public List GetActiveInterceptors() {
return _activeInterceptors;
}
public List GetActiveThreats() {
return _threatTable.Where(threat => threat.Status != ThreatStatus.DESTROYED).Select(threat => threat.Threat).ToList();
}
public List GetActiveAgents() {
return _activeInterceptors.ConvertAll(interceptor => interceptor as Agent)
.Concat(GetActiveThreats().ConvertAll(threat => threat 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();
ResumeSimulation();
}
}
public void SetTimeScale(float timeScale) {
Time.timeScale = timeScale;
Time.fixedDeltaTime = Time.timeScale * 0.02f;
Time.maximumDeltaTime = Time.timeScale * 0.15f;
}
public void StartSimulation() {
InitializeSimulation();
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 missiles = new List();
// Create missiles based on config
foreach (var swarmConfig in simulationConfig.interceptor_swarm_configs) {
for (int i = 0; i < swarmConfig.num_agents; i++) {
var interceptor = CreateInterceptor(swarmConfig.agent_config);
}
}
List targets = new List();
// Create targets based on config
foreach (var swarmConfig in simulationConfig.threat_swarm_configs) {
for (int i = 0; i < swarmConfig.num_agents; i++) {
var threat = CreateThreat(swarmConfig.agent_config);
}
}
_assignmentScheme = new ThreatAssignment();
// Invoke the simulation started event to let listeners
// know to invoke their own handler behavior
OnSimulationStarted?.Invoke();
}
public void AssignInterceptorsToThreats() {
AssignInterceptorsToThreats(_interceptorObjects);
}
public void RegisterInterceptorHit(Interceptor interceptor, Threat threat) {
if (interceptor is Interceptor missileComponent) {
_activeInterceptors.Remove(missileComponent);
}
}
public void RegisterInterceptorMiss(Interceptor interceptor, Threat threat) {
if (interceptor is Interceptor missileComponent) {
_activeInterceptors.Remove(missileComponent);
}
// Remove the interceptor from the threat's assigned interceptors
_threatDataMap[threat].RemoveInterceptor(interceptor);
}
public void RegisterThreatHit(Interceptor interceptor, Threat threat) {
ThreatData threatData = _threatDataMap[threat];
threatData.RemoveInterceptor(interceptor);
if (threatData != null) {
threatData.MarkDestroyed();
}
}
public void RegisterThreatMiss(Interceptor interceptor, Threat threat) {
Debug.Log($"RegisterThreatMiss: Interceptor {interceptor.name} missed threat {threat.name}");
ThreatData threatData = _threatDataMap[threat];
threatData.RemoveInterceptor(interceptor);
}
///
/// Assigns the specified list of missiles to available targets based on the assignment scheme.
///
/// The list of missiles to assign.
public void AssignInterceptorsToThreats(List missilesToAssign) {
// Perform the assignment
IEnumerable assignments =
_assignmentScheme.Assign(missilesToAssign, _threatTable);
// Apply the assignments to the missiles
foreach (var assignment in assignments) {
Debug.LogWarning($"Assigning interceptor {assignment.Interceptor} to threat {assignment.Threat}");
assignment.Interceptor.AssignTarget(assignment.Threat);
_threatDataMap[assignment.Threat].AssignInterceptor(assignment.Interceptor);
Debug.Log($"Interceptor {assignment.Interceptor.name} assigned to threat {assignment.Threat.name}");
}
// Check if any interceptors were not assigned
List unassignedInterceptors = missilesToAssign.Where(m => !m.HasAssignedTarget()).ToList();
if (unassignedInterceptors.Count > 0)
{
string unassignedIds = string.Join(", ", unassignedInterceptors.Select(m => m.name));
int totalInterceptors = missilesToAssign.Count;
int assignedInterceptors = totalInterceptors - unassignedInterceptors.Count;
Debug.LogWarning($"Warning: {unassignedInterceptors.Count} out of {totalInterceptors} interceptors were not assigned to any threat. " +
$"Unassigned interceptor IDs: {unassignedIds}. " +
$"Total interceptors: {totalInterceptors}, Assigned: {assignedInterceptors}, Unassigned: {unassignedInterceptors.Count}");
// Log information about the assignment scheme
Debug.Log($"Current Assignment Scheme: {_assignmentScheme.GetType().Name}");
}
}
///
/// Creates a interceptor based on the provided configuration.
///
/// Configuration settings for the interceptor.
/// The created Interceptor instance, or null if creation failed.
public Interceptor CreateInterceptor(AgentConfig config) {
string prefabName = config.interceptor_type switch { InterceptorType.HYDRA_70 => "Hydra70",
InterceptorType.MICROMISSILE => "Micromissile",
_ => "Hydra70" };
GameObject interceptorObject = CreateAgent(config, prefabName);
if (interceptorObject == null)
return null;
// Interceptor-specific logic
switch (config.dynamic_config.sensor_config.type) {
case SensorType.IDEAL:
interceptorObject.AddComponent();
break;
default:
Debug.LogError($"Sensor type '{config.dynamic_config.sensor_config.type}' not found.");
break;
}
Interceptor interceptor = interceptorObject.GetComponent();
_interceptorObjects.Add(interceptor);
_activeInterceptors.Add(interceptor);
// Subscribe events
interceptor.OnInterceptHit += RegisterInterceptorHit;
interceptor.OnInterceptMiss += RegisterInterceptorMiss;
// Assign a unique and simple ID
int missileId = _interceptorObjects.Count;
interceptorObject.name = $"{config.interceptor_type}_Interceptor_{missileId}";
return interceptorObject.GetComponent();
}
///
/// Creates a threat based on the provided configuration.
///
/// Configuration settings for the threat.
/// The created Threat instance, or null if creation failed.
private Threat CreateThreat(AgentConfig config) {
string prefabName = config.threat_type switch {
ThreatType.DRONE => "Drone", ThreatType.ANTISHIP_MISSILE => "AntishipMissile",
_ => throw new System.ArgumentException($"Unsupported threat type: {config.threat_type}")
};
GameObject threatObject = CreateAgent(config, prefabName);
if (threatObject == null)
return null;
Threat threat = threatObject.GetComponent();
// Assign a unique and simple ID
int targetId = _threatTable.Count;
threatObject.name = $"{config.threat_type}_Target_{targetId}";
ThreatData threatData = new ThreatData(threat, threatObject.name);
_threatDataMap.Add(threat, threatData);
_threatTable.Add(threatData);
_threatObjects.Add(threat);
// Subscribe events
threat.OnInterceptHit += RegisterThreatHit;
threat.OnInterceptMiss += RegisterThreatMiss;
return threatObject.GetComponent();
}
///
/// Creates an agent (interceptor or threat) based on the provided configuration and prefab name.
///
/// Configuration settings for the agent.
/// Name of the prefab to instantiate.
/// The created GameObject instance, or null if creation failed.
public GameObject CreateAgent(AgentConfig config, string prefabName) {
GameObject prefab = Resources.Load($"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();
Vector3 velocityNoise = Utilities.GenerateRandomNoise(config.standard_deviation.velocity);
Vector3 noisyVelocity = config.initial_state.velocity + velocityNoise;
agentRigidbody.linearVelocity = noisyVelocity;
agentObject.GetComponent().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 = IsSimulationRunning();
// Clear existing missiles and targets
foreach (var interceptor in _interceptorObjects) {
if (interceptor != null) {
Destroy(interceptor.gameObject);
}
}
foreach (var threat in _threatObjects) {
if (threat != null) {
Destroy(threat.gameObject);
}
}
_interceptorObjects.Clear();
_activeInterceptors.Clear();
_threatObjects.Clear();
_threatTable.Clear();
StartSimulation();
}
void Update() {
// Check if all missiles have terminated
bool allInterceptorsTerminated = true;
foreach (var interceptor in _interceptorObjects) {
if (interceptor != null && !interceptor.IsHit() && !interceptor.IsMiss()) {
allInterceptorsTerminated = false;
break;
}
}
// If all missiles have terminated, restart the simulation
if (allInterceptorsTerminated) {
RestartSimulation();
}
if (simulationRunning && _elapsedSimulationTime < endTime) {
_elapsedSimulationTime += Time.deltaTime;
} else if (_elapsedSimulationTime >= endTime) {
simulationRunning = false;
Debug.Log("Simulation completed.");
}
}
}