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."); } } }