Assignment system overhaul to fix many bugs
parent
d5ae6f97f1
commit
e15b70fafc
|
@ -33,12 +33,12 @@ public abstract class Agent : MonoBehaviour {
|
|||
protected StaticConfig _staticConfig;
|
||||
|
||||
// Define delegates
|
||||
public delegate void AgentHitEventHandler(Agent agent);
|
||||
public delegate void AgentMissEventHandler(Agent agent);
|
||||
public delegate void InterceptHitEventHandler(Interceptor interceptor, Threat target);
|
||||
public delegate void InterceptMissEventHandler(Interceptor interceptor, Threat target);
|
||||
|
||||
// Define events
|
||||
public event AgentHitEventHandler OnAgentHit;
|
||||
public event AgentMissEventHandler OnAgentMiss;
|
||||
public event InterceptHitEventHandler OnInterceptHit;
|
||||
public event InterceptMissEventHandler OnInterceptMiss;
|
||||
|
||||
public void SetFlightPhase(FlightPhase flightPhase) {
|
||||
Debug.Log(
|
||||
|
@ -105,16 +105,24 @@ public abstract class Agent : MonoBehaviour {
|
|||
}
|
||||
|
||||
// Mark the agent as having hit the target or been hit.
|
||||
public void MarkAsHit() {
|
||||
public void HandleInterceptHit() {
|
||||
_isHit = true;
|
||||
OnAgentHit?.Invoke(this);
|
||||
if (this is Interceptor interceptor && _target is Threat threat) {
|
||||
OnInterceptHit?.Invoke(interceptor, threat);
|
||||
} else if (this is Threat threatAgent && _target is Interceptor interceptorTarget) {
|
||||
OnInterceptHit?.Invoke(interceptorTarget, threatAgent);
|
||||
}
|
||||
TerminateAgent();
|
||||
}
|
||||
|
||||
public void MarkAsMiss() {
|
||||
public void HandleInterceptMiss() {
|
||||
_isMiss = true;
|
||||
if (_target != null) {
|
||||
OnAgentMiss?.Invoke(this);
|
||||
if (this is Interceptor interceptor && _target is Threat threat) {
|
||||
OnInterceptMiss?.Invoke(interceptor, threat);
|
||||
} else if (this is Threat threatAgent && _target is Interceptor interceptorTarget) {
|
||||
OnInterceptMiss?.Invoke(interceptorTarget, threatAgent);
|
||||
}
|
||||
_target = null;
|
||||
}
|
||||
TerminateAgent();
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
using System.Diagnostics.Contracts;
|
||||
|
||||
// The assignment class is an interface for assigning a threat to each interceptor.
|
||||
public interface IAssignment {
|
||||
|
@ -8,39 +11,30 @@ public interface IAssignment {
|
|||
// The first element corresponds to the interceptor index, and the second element
|
||||
// corresponds to the threat index.
|
||||
public struct AssignmentItem {
|
||||
public int InterceptorIndex;
|
||||
public int ThreatIndex;
|
||||
public Interceptor Interceptor;
|
||||
public Threat Threat;
|
||||
|
||||
public AssignmentItem(int missileIndex, int threatIndex) {
|
||||
InterceptorIndex = missileIndex;
|
||||
ThreatIndex = threatIndex;
|
||||
public AssignmentItem(Interceptor interceptor, Threat threat) {
|
||||
Interceptor = interceptor;
|
||||
Threat = threat;
|
||||
}
|
||||
}
|
||||
|
||||
// A list containing the interceptor-target assignments.
|
||||
|
||||
// Assign a target to each interceptor that has not been assigned a target yet.
|
||||
public abstract IEnumerable<AssignmentItem> Assign(List<Agent> missiles, List<Agent> targets);
|
||||
[Pure]
|
||||
public abstract IEnumerable<AssignmentItem> Assign(in IReadOnlyList<Interceptor> interceptors, in IReadOnlyList<ThreatData> threatTable);
|
||||
|
||||
// Get the list of assignable interceptor indices.
|
||||
protected static List<int> GetAssignableInterceptorIndices(List<Agent> missiles) {
|
||||
List<int> assignableInterceptorIndices = new List<int>();
|
||||
for (int missileIndex = 0; missileIndex < missiles.Count; missileIndex++) {
|
||||
if (missiles[missileIndex].IsAssignable()) {
|
||||
assignableInterceptorIndices.Add(missileIndex);
|
||||
}
|
||||
}
|
||||
return assignableInterceptorIndices;
|
||||
[Pure]
|
||||
protected static List<Interceptor> GetAssignableInterceptors(in IReadOnlyList<Interceptor> interceptors) {
|
||||
return interceptors.Where(interceptor => interceptor.IsAssignable()).ToList();
|
||||
}
|
||||
|
||||
// Get the list of active target indices.
|
||||
protected static List<int> GetActiveThreatIndices(List<Agent> threats) {
|
||||
List<int> activeThreatIndices = new List<int>();
|
||||
for (int threatIndex = 0; threatIndex < threats.Count; threatIndex++) {
|
||||
if (!threats[threatIndex].IsHit()) {
|
||||
activeThreatIndices.Add(threatIndex);
|
||||
}
|
||||
}
|
||||
return activeThreatIndices;
|
||||
// Get the list of active threats.
|
||||
[Pure]
|
||||
protected static List<ThreatData> GetActiveThreats(in IReadOnlyList<ThreatData> threats) {
|
||||
return threats.Where(t => t.Status != ThreatStatus.DESTROYED).ToList();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,39 +1,43 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
// The round-robin assignment class assigns missiles to the targets in a
|
||||
// round-robin order.
|
||||
using System.Diagnostics.Contracts;
|
||||
// The round-robin assignment class assigns interceptors to the targets in a
|
||||
// round-robin order using the new paradigm.
|
||||
public class RoundRobinAssignment : IAssignment {
|
||||
// Previous target index that was assigned.
|
||||
private int prevTargetIndex = -1;
|
||||
// Previous target index that was assigned.
|
||||
private int prevTargetIndex = -1;
|
||||
|
||||
// Assign a target to each interceptor that has not been assigned a target yet.
|
||||
public IEnumerable<IAssignment.AssignmentItem> Assign(List<Agent> missiles, List<Agent> targets) {
|
||||
List<IAssignment.AssignmentItem> assignments = new List<IAssignment.AssignmentItem>();
|
||||
List<int> assignableInterceptorIndices = IAssignment.GetAssignableInterceptorIndices(missiles);
|
||||
if (assignableInterceptorIndices.Count == 0) {
|
||||
return assignments;
|
||||
// Assign a target to each interceptor that has not been assigned a target yet.
|
||||
[Pure]
|
||||
public IEnumerable<IAssignment.AssignmentItem> Assign(in IReadOnlyList<Interceptor> interceptors, in IReadOnlyList<ThreatData> targets) {
|
||||
List<IAssignment.AssignmentItem> assignments = new List<IAssignment.AssignmentItem>();
|
||||
|
||||
// Get the list of interceptors that are available for assignment.
|
||||
List<Interceptor> assignableInterceptors = IAssignment.GetAssignableInterceptors(interceptors);
|
||||
if (assignableInterceptors.Count == 0) {
|
||||
return assignments;
|
||||
}
|
||||
|
||||
// Get the list of active threats that need to be addressed.
|
||||
List<ThreatData> activeThreats = IAssignment.GetActiveThreats(targets);
|
||||
if (activeThreats.Count == 0) {
|
||||
return assignments;
|
||||
}
|
||||
|
||||
// Perform round-robin assignment.
|
||||
foreach (Interceptor interceptor in assignableInterceptors) {
|
||||
// Determine the next target index in a round-robin fashion.
|
||||
int nextTargetIndex = (prevTargetIndex + 1) % activeThreats.Count;
|
||||
ThreatData selectedThreat = activeThreats[nextTargetIndex];
|
||||
|
||||
// Assign the interceptor to the selected threat.
|
||||
assignments.Add(new IAssignment.AssignmentItem(interceptor, selectedThreat.Threat));
|
||||
|
||||
// Update the previous target index.
|
||||
prevTargetIndex = nextTargetIndex;
|
||||
}
|
||||
|
||||
return assignments;
|
||||
}
|
||||
|
||||
List<int> activeThreatIndices = IAssignment.GetActiveThreatIndices(targets);
|
||||
if (activeThreatIndices.Count == 0) {
|
||||
return assignments;
|
||||
}
|
||||
|
||||
foreach (int missileIndex in assignableInterceptorIndices) {
|
||||
int nextActiveTargetIndex = activeThreatIndices.FindIndex(index => index > prevTargetIndex);
|
||||
|
||||
if (nextActiveTargetIndex == -1) {
|
||||
nextActiveTargetIndex = 0;
|
||||
}
|
||||
|
||||
int nextTargetIndex = activeThreatIndices[nextActiveTargetIndex];
|
||||
assignments.Add(new IAssignment.AssignmentItem(missileIndex, nextTargetIndex));
|
||||
prevTargetIndex = nextTargetIndex;
|
||||
}
|
||||
|
||||
return assignments;
|
||||
}
|
||||
}
|
|
@ -3,70 +3,64 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using Unity.VisualScripting;
|
||||
using UnityEngine;
|
||||
|
||||
// The threat assignment class assigns missiles to the targets based
|
||||
using System.Diagnostics.Contracts;
|
||||
// The threat assignment class assigns interceptors to the targets based
|
||||
// on the threat level of the targets.
|
||||
public class ThreatAssignment : IAssignment {
|
||||
// Assign a target to each interceptor that has not been assigned a target yet.
|
||||
public IEnumerable<IAssignment.AssignmentItem> Assign(List<Agent> missiles, List<Agent> targets) {
|
||||
[Pure]
|
||||
public IEnumerable<IAssignment.AssignmentItem> Assign(in IReadOnlyList<Interceptor> interceptors, in IReadOnlyList<ThreatData> targets) {
|
||||
List<IAssignment.AssignmentItem> assignments = new List<IAssignment.AssignmentItem>();
|
||||
|
||||
List<int> assignableInterceptorIndices = IAssignment.GetAssignableInterceptorIndices(missiles);
|
||||
if (assignableInterceptorIndices.Count == 0) {
|
||||
List<Interceptor> assignableInterceptors = IAssignment.GetAssignableInterceptors(interceptors);
|
||||
if (assignableInterceptors.Count == 0) {
|
||||
Debug.LogWarning("No assignable interceptors found");
|
||||
return assignments;
|
||||
}
|
||||
|
||||
List<int> activeThreatIndices = IAssignment.GetActiveThreatIndices(targets);
|
||||
if (activeThreatIndices.Count == 0) {
|
||||
List<ThreatData> activeThreats = IAssignment.GetActiveThreats(targets);
|
||||
if (activeThreats.Count == 0) {
|
||||
Debug.LogWarning("No active threats found");
|
||||
return assignments;
|
||||
}
|
||||
|
||||
Vector3 positionToDefend = Vector3.zero;
|
||||
List<ThreatInfo> threatInfos =
|
||||
CalculateThreatLevels(targets, activeThreatIndices, positionToDefend);
|
||||
CalculateThreatLevels(activeThreats, positionToDefend);
|
||||
|
||||
foreach (int missileIndex in assignableInterceptorIndices) {
|
||||
if (missiles[missileIndex].HasAssignedTarget())
|
||||
continue;
|
||||
if (threatInfos.Count == 0)
|
||||
break;
|
||||
// Sort ThreatInfo first by ThreatData.Status (UNASSIGNED first, then ASSIGNED)
|
||||
// Within each group, order by ThreatLevel descending
|
||||
threatInfos = threatInfos.OrderByDescending(t => t.ThreatData.Status == ThreatStatus.UNASSIGNED)
|
||||
.ThenByDescending(t => t.ThreatLevel)
|
||||
.ToList();
|
||||
|
||||
// Find the optimal target for this interceptor based on distance and threat
|
||||
ThreatInfo optimalTarget = null;
|
||||
float optimalScore = float.MinValue;
|
||||
|
||||
foreach (ThreatInfo threat in threatInfos) {
|
||||
float distance = Vector3.Distance(missiles[missileIndex].transform.position,
|
||||
targets[threat.TargetIndex].transform.position);
|
||||
float score = threat.ThreatLevel / distance; // Balance threat level with proximity
|
||||
|
||||
if (score > optimalScore) {
|
||||
optimalScore = score;
|
||||
optimalTarget = threat;
|
||||
var assignableInterceptorsEnumerator = assignableInterceptors.GetEnumerator();
|
||||
if (assignableInterceptorsEnumerator.MoveNext()) // Move to the first element
|
||||
{
|
||||
foreach (ThreatInfo threatInfo in threatInfos) {
|
||||
assignments.Add(new IAssignment.AssignmentItem(assignableInterceptorsEnumerator.Current, threatInfo.ThreatData.Threat));
|
||||
if (!assignableInterceptorsEnumerator.MoveNext()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (optimalTarget != null) {
|
||||
assignments.Add(new IAssignment.AssignmentItem(missileIndex, optimalTarget.TargetIndex));
|
||||
threatInfos.Remove(optimalTarget);
|
||||
}
|
||||
}
|
||||
return assignments;
|
||||
}
|
||||
|
||||
private List<ThreatInfo> CalculateThreatLevels(List<Agent> targets, List<int> activeThreatIndices,
|
||||
Vector3 missilesMeanPosition) {
|
||||
|
||||
private List<ThreatInfo> CalculateThreatLevels(List<ThreatData> threatTable,
|
||||
Vector3 defensePosition) {
|
||||
List<ThreatInfo> threatInfos = new List<ThreatInfo>();
|
||||
|
||||
foreach (int targetIndex in activeThreatIndices) {
|
||||
Agent target = targets[targetIndex];
|
||||
float distanceToMean = Vector3.Distance(target.transform.position, missilesMeanPosition);
|
||||
float velocityMagnitude = target.GetVelocity().magnitude;
|
||||
foreach (ThreatData threatData in threatTable) {
|
||||
Threat threat = threatData.Threat;
|
||||
float distanceToMean = Vector3.Distance(threat.transform.position, defensePosition);
|
||||
float velocityMagnitude = threat.GetVelocity().magnitude;
|
||||
|
||||
// Calculate threat level based on proximity and velocity
|
||||
float threatLevel = (1 / distanceToMean) * velocityMagnitude;
|
||||
|
||||
threatInfos.Add(new ThreatInfo(targetIndex, threatLevel));
|
||||
threatInfos.Add(new ThreatInfo(threatData, threatLevel));
|
||||
}
|
||||
|
||||
// Sort threats in descending order
|
||||
|
@ -74,11 +68,11 @@ public class ThreatAssignment : IAssignment {
|
|||
}
|
||||
|
||||
private class ThreatInfo {
|
||||
public int TargetIndex { get; }
|
||||
public ThreatData ThreatData { get; }
|
||||
public float ThreatLevel { get; }
|
||||
|
||||
public ThreatInfo(int targetIndex, float threatLevel) {
|
||||
TargetIndex = targetIndex;
|
||||
public ThreatInfo(ThreatData threatData, float threatLevel) {
|
||||
ThreatData = threatData;
|
||||
ThreatLevel = threatLevel;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
[System.Serializable]
|
||||
public enum ThreatStatus {
|
||||
UNASSIGNED,
|
||||
ASSIGNED,
|
||||
DESTROYED
|
||||
}
|
||||
[System.Serializable]
|
||||
public class ThreatData
|
||||
{
|
||||
public Threat Threat;
|
||||
[SerializeField]
|
||||
private ThreatStatus _status;
|
||||
public ThreatStatus Status { get { return _status; } }
|
||||
public string ThreatID;
|
||||
[SerializeField]
|
||||
private List<Interceptor> _assignedInterceptors; // Changed from property to field
|
||||
|
||||
public void AssignInterceptor(Interceptor interceptor) {
|
||||
if(Status == ThreatStatus.DESTROYED) {
|
||||
Debug.LogError($"AssignInterceptor: Threat {ThreatID} is destroyed, cannot assign interceptor");
|
||||
return;
|
||||
}
|
||||
_status = ThreatStatus.ASSIGNED;
|
||||
_assignedInterceptors.Add(interceptor);
|
||||
}
|
||||
|
||||
public void RemoveInterceptor(Interceptor interceptor) {
|
||||
_assignedInterceptors.Remove(interceptor);
|
||||
if(_assignedInterceptors.Count == 0) {
|
||||
_status = ThreatStatus.UNASSIGNED;
|
||||
}
|
||||
}
|
||||
|
||||
public void MarkDestroyed() {
|
||||
_status = ThreatStatus.DESTROYED;
|
||||
}
|
||||
// Constructor remains the same
|
||||
public ThreatData(Threat threat, string threatID)
|
||||
{
|
||||
Threat = threat;
|
||||
_status = ThreatStatus.UNASSIGNED;
|
||||
ThreatID = threatID;
|
||||
_assignedInterceptors = new List<Interceptor>(); // Initialize the list
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: adc0c5dbdb9dc7d498b50cf9a15c2db5
|
|
@ -74,7 +74,7 @@ public class Interceptor : Agent {
|
|||
|
||||
private void OnTriggerEnter(Collider other) {
|
||||
if (other.gameObject.name == "Floor") {
|
||||
this.MarkAsMiss();
|
||||
this.HandleInterceptMiss();
|
||||
}
|
||||
// Check if the collision is with another Agent
|
||||
Agent otherAgent = other.gameObject.GetComponentInParent<Agent>();
|
||||
|
@ -87,13 +87,13 @@ public class Interceptor : Agent {
|
|||
// Set green for hit
|
||||
markerObject.GetComponent<Renderer>().material.color = new Color(0, 1, 0, 0.15f);
|
||||
// Mark both this agent and the other agent as hit
|
||||
this.MarkAsHit();
|
||||
otherAgent.MarkAsHit();
|
||||
this.HandleInterceptHit();
|
||||
otherAgent.HandleInterceptHit();
|
||||
|
||||
} else {
|
||||
// Set red for miss
|
||||
markerObject.GetComponent<Renderer>().material.color = new Color(1, 0, 0, 0.15f);
|
||||
this.MarkAsMiss();
|
||||
this.HandleInterceptMiss();
|
||||
// otherAgent.MarkAsMiss();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ public class Micromissile : Interceptor {
|
|||
// Check whether the threat should be considered a miss
|
||||
SensorOutput sensorOutput = GetComponent<Sensor>().Sense(_target);
|
||||
if (sensorOutput.velocity.range > 1000f) {
|
||||
this.MarkAsMiss();
|
||||
this.HandleInterceptMiss();
|
||||
}
|
||||
|
||||
// Calculate the acceleration input
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "MicromissileAssembly",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:6055be8ebefd69e48b49212b09b47b2f"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: af8bba08a36038347823e2f46bdc9857
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -41,7 +41,7 @@ public class SimMonitor : MonoBehaviour
|
|||
private void ExportTelemetry()
|
||||
{
|
||||
float time = (float)SimManager.Instance.GetElapsedSimulationTime();
|
||||
foreach (var agent in SimManager.Instance.GetActiveThreats().Cast<Agent>().Concat(SimManager.Instance.GetActiveInterceptors().Cast<Agent>()))
|
||||
foreach (var agent in SimManager.Instance.GetActiveAgents())
|
||||
{
|
||||
Vector3 pos = agent.transform.position;
|
||||
if(pos == Vector3.zero) {
|
||||
|
|
|
@ -19,11 +19,15 @@ public class SimManager : MonoBehaviour {
|
|||
[SerializeField]
|
||||
public SimulationConfig simulationConfig;
|
||||
|
||||
private List<Interceptor> _interceptors = new List<Interceptor>();
|
||||
|
||||
private List<Interceptor> _activeInterceptors = new List<Interceptor>();
|
||||
private List<Threat> _unassignedThreats = new List<Threat>();
|
||||
private List<Threat> _threats = new List<Threat>();
|
||||
private List<Threat> _activeThreats = new List<Threat>();
|
||||
[SerializeField]
|
||||
private List<ThreatData> _threatTable = new List<ThreatData>();
|
||||
private Dictionary<Threat, ThreatData> _threatDataMap = new Dictionary<Threat, ThreatData>();
|
||||
|
||||
private List<Interceptor> _interceptorObjects = new List<Interceptor>();
|
||||
private List<Threat> _threatObjects = new List<Threat>();
|
||||
|
||||
private float _elapsedSimulationTime = 0f;
|
||||
private float endTime = 100f; // Set an appropriate end time
|
||||
private bool simulationRunning = false;
|
||||
|
@ -47,12 +51,12 @@ public class SimManager : MonoBehaviour {
|
|||
}
|
||||
|
||||
public List<Threat> GetActiveThreats() {
|
||||
return _activeThreats;
|
||||
return _threatTable.Where(threat => threat.Status != ThreatStatus.DESTROYED).Select(threat => threat.Threat).ToList();
|
||||
}
|
||||
|
||||
public List<Agent> GetActiveAgents() {
|
||||
return _activeInterceptors.ConvertAll(interceptor => interceptor as Agent)
|
||||
.Concat(_activeThreats.ConvertAll(threat => threat as Agent))
|
||||
.Concat(GetActiveThreats().ConvertAll(threat => threat as Agent))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
@ -108,8 +112,6 @@ public class SimManager : MonoBehaviour {
|
|||
foreach (var swarmConfig in simulationConfig.interceptor_swarm_configs) {
|
||||
for (int i = 0; i < swarmConfig.num_agents; i++) {
|
||||
var interceptor = CreateInterceptor(swarmConfig.agent_config);
|
||||
interceptor.OnAgentHit += RegisterInterceptorHit;
|
||||
interceptor.OnAgentMiss += RegisterInterceptorMiss;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,8 +120,6 @@ public class SimManager : MonoBehaviour {
|
|||
foreach (var swarmConfig in simulationConfig.threat_swarm_configs) {
|
||||
for (int i = 0; i < swarmConfig.num_agents; i++) {
|
||||
var threat = CreateThreat(swarmConfig.agent_config);
|
||||
threat.OnAgentHit += RegisterThreatHit;
|
||||
threat.OnAgentMiss += RegisterThreatMiss;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,31 +131,35 @@ public class SimManager : MonoBehaviour {
|
|||
}
|
||||
|
||||
public void AssignInterceptorsToThreats() {
|
||||
AssignInterceptorsToThreats(_interceptors);
|
||||
AssignInterceptorsToThreats(_interceptorObjects);
|
||||
}
|
||||
|
||||
public void RegisterInterceptorHit(Agent interceptor) {
|
||||
public void RegisterInterceptorHit(Interceptor interceptor, Threat threat) {
|
||||
if (interceptor is Interceptor missileComponent) {
|
||||
_activeInterceptors.Remove(missileComponent);
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterInterceptorMiss(Agent interceptor) {
|
||||
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(Agent threat) {
|
||||
if (threat is Threat targetComponent) {
|
||||
_activeThreats.Remove(targetComponent);
|
||||
public void RegisterThreatHit(Interceptor interceptor, Threat threat) {
|
||||
ThreatData threatData = _threatDataMap[threat];
|
||||
threatData.RemoveInterceptor(interceptor);
|
||||
if (threatData != null) {
|
||||
threatData.MarkDestroyed();
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterThreatMiss(Agent threat) {
|
||||
if (threat is Threat targetComponent) {
|
||||
_unassignedThreats.Add(targetComponent);
|
||||
}
|
||||
public void RegisterThreatMiss(Interceptor interceptor, Threat threat) {
|
||||
Debug.Log($"RegisterThreatMiss: Interceptor {interceptor.name} missed threat {threat.name}");
|
||||
ThreatData threatData = _threatDataMap[threat];
|
||||
threatData.RemoveInterceptor(interceptor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -163,27 +167,34 @@ public class SimManager : MonoBehaviour {
|
|||
/// </summary>
|
||||
/// <param name="missilesToAssign">The list of missiles to assign.</param>
|
||||
public void AssignInterceptorsToThreats(List<Interceptor> missilesToAssign) {
|
||||
// Convert Interceptor and Threat lists to Agent lists
|
||||
List<Agent> missileAgents = new List<Agent>(missilesToAssign.ConvertAll(m => m as Agent));
|
||||
// Convert Threat list to Agent list, excluding already assigned targets
|
||||
List<Agent> targetAgents = _unassignedThreats.ToList<Agent>();
|
||||
|
||||
// Perform the assignment
|
||||
IEnumerable<IAssignment.AssignmentItem> assignments =
|
||||
_assignmentScheme.Assign(missileAgents, targetAgents);
|
||||
_assignmentScheme.Assign(missilesToAssign, _threatTable);
|
||||
|
||||
// Apply the assignments to the missiles
|
||||
foreach (var assignment in assignments) {
|
||||
if (assignment.InterceptorIndex < missilesToAssign.Count) {
|
||||
Interceptor interceptor = missilesToAssign[assignment.InterceptorIndex];
|
||||
Threat threat = _unassignedThreats[assignment.ThreatIndex];
|
||||
interceptor.AssignTarget(threat);
|
||||
Debug.Log($"Interceptor {interceptor.name} assigned to threat {threat.name}");
|
||||
}
|
||||
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<Interceptor> 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}");
|
||||
}
|
||||
// TODO this whole function should be optimized
|
||||
_unassignedThreats.RemoveAll(
|
||||
threat => missilesToAssign.Any(interceptor => interceptor.GetAssignedTarget() == threat));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -196,27 +207,32 @@ public class SimManager : MonoBehaviour {
|
|||
InterceptorType.MICROMISSILE => "Micromissile",
|
||||
_ => "Hydra70" };
|
||||
|
||||
GameObject missileObject = CreateAgent(config, prefabName);
|
||||
if (missileObject == null)
|
||||
GameObject interceptorObject = CreateAgent(config, prefabName);
|
||||
if (interceptorObject == null)
|
||||
return null;
|
||||
|
||||
// Interceptor-specific logic
|
||||
switch (config.dynamic_config.sensor_config.type) {
|
||||
case SensorType.IDEAL:
|
||||
missileObject.AddComponent<IdealSensor>();
|
||||
interceptorObject.AddComponent<IdealSensor>();
|
||||
break;
|
||||
default:
|
||||
Debug.LogError($"Sensor type '{config.dynamic_config.sensor_config.type}' not found.");
|
||||
break;
|
||||
}
|
||||
|
||||
_interceptors.Add(missileObject.GetComponent<Interceptor>());
|
||||
_activeInterceptors.Add(missileObject.GetComponent<Interceptor>());
|
||||
Interceptor interceptor = interceptorObject.GetComponent<Interceptor>();
|
||||
_interceptorObjects.Add(interceptor);
|
||||
_activeInterceptors.Add(interceptor);
|
||||
|
||||
// Subscribe events
|
||||
interceptor.OnInterceptHit += RegisterInterceptorHit;
|
||||
interceptor.OnInterceptMiss += RegisterInterceptorMiss;
|
||||
|
||||
// Assign a unique and simple ID
|
||||
int missileId = _interceptors.Count;
|
||||
missileObject.name = $"{config.interceptor_type}_Interceptor_{missileId}";
|
||||
return missileObject.GetComponent<Interceptor>();
|
||||
int missileId = _interceptorObjects.Count;
|
||||
interceptorObject.name = $"{config.interceptor_type}_Interceptor_{missileId}";
|
||||
return interceptorObject.GetComponent<Interceptor>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -233,13 +249,20 @@ public class SimManager : MonoBehaviour {
|
|||
if (threatObject == null)
|
||||
return null;
|
||||
|
||||
_threats.Add(threatObject.GetComponent<Threat>());
|
||||
_activeThreats.Add(threatObject.GetComponent<Threat>());
|
||||
_unassignedThreats.Add(threatObject.GetComponent<Threat>());
|
||||
|
||||
Threat threat = threatObject.GetComponent<Threat>();
|
||||
// Assign a unique and simple ID
|
||||
int targetId = _threats.Count;
|
||||
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<Threat>();
|
||||
}
|
||||
|
||||
|
@ -290,21 +313,24 @@ public class SimManager : MonoBehaviour {
|
|||
simulationRunning = IsSimulationRunning();
|
||||
|
||||
// Clear existing missiles and targets
|
||||
foreach (var interceptor in _interceptors) {
|
||||
foreach (var interceptor in _interceptorObjects) {
|
||||
if (interceptor != null) {
|
||||
Destroy(interceptor.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var threat in _threats) {
|
||||
foreach (var threat in _threatObjects) {
|
||||
if (threat != null) {
|
||||
Destroy(threat.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
_interceptors.Clear();
|
||||
_threats.Clear();
|
||||
_unassignedThreats.Clear();
|
||||
_interceptorObjects.Clear();
|
||||
_activeInterceptors.Clear();
|
||||
_threatObjects.Clear();
|
||||
_threatTable.Clear();
|
||||
|
||||
|
||||
|
||||
StartSimulation();
|
||||
}
|
||||
|
@ -312,7 +338,7 @@ public class SimManager : MonoBehaviour {
|
|||
void Update() {
|
||||
// Check if all missiles have terminated
|
||||
bool allInterceptorsTerminated = true;
|
||||
foreach (var interceptor in _interceptors) {
|
||||
foreach (var interceptor in _interceptorObjects) {
|
||||
if (interceptor != null && !interceptor.IsHit() && !interceptor.IsMiss()) {
|
||||
allInterceptorsTerminated = false;
|
||||
break;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f034d27b4aab67a47865af3d624c4375
|
||||
guid: 5ab47dc725c65cb4cb1fa6948fb0c9a7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
|
@ -0,0 +1,25 @@
|
|||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
using System.Collections;
|
||||
|
||||
public class SanityTest
|
||||
{
|
||||
[Test]
|
||||
public void SanityTestSimplePasses()
|
||||
{
|
||||
// Use the Assert class to test conditions
|
||||
Assert.Pass("This test passes.");
|
||||
}
|
||||
|
||||
// A UnityTest behaves like a coroutine in Play Mode. In Edit Mode you can use
|
||||
// `yield return null;` to skip a frame.
|
||||
[UnityTest]
|
||||
public IEnumerator SanityTestWithEnumeratorPasses()
|
||||
{
|
||||
// Use the Assert class to test conditions.
|
||||
// Use yield to skip a frame.
|
||||
yield return null;
|
||||
Assert.Pass("This test passes after skipping a frame.");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b48cf6e434723a449a49186eeda32d3f
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "Tests",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"UnityEngine.TestRunner",
|
||||
"UnityEditor.TestRunner",
|
||||
"MicromissileAssembly"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": true,
|
||||
"precompiledReferences": [
|
||||
"nunit.framework.dll"
|
||||
],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [
|
||||
"UNITY_INCLUDE_TESTS"
|
||||
],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d15c92e585e721749b63d85007276dbe
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,138 @@
|
|||
using NUnit.Framework;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
using System.Linq;
|
||||
|
||||
public class ThreatAssignmentTests
|
||||
{
|
||||
[Test]
|
||||
public void Assign_Should_Assign_All_Interceptors_And_Threats()
|
||||
{
|
||||
// Arrange
|
||||
ThreatAssignment threatAssignment = new ThreatAssignment();
|
||||
|
||||
// Create interceptors
|
||||
List<Interceptor> interceptors = new List<Interceptor>
|
||||
{
|
||||
new GameObject("Interceptor 1").AddComponent<Micromissile>(),
|
||||
new GameObject("Interceptor 2").AddComponent<Micromissile>(),
|
||||
new GameObject("Interceptor 3").AddComponent<Micromissile>()
|
||||
};
|
||||
|
||||
// Create threats
|
||||
Threat threat1 = new GameObject("Threat 1").AddComponent<DroneTarget>();
|
||||
Threat threat2 = new GameObject("Threat 2").AddComponent<DroneTarget>();
|
||||
Threat threat3 = new GameObject("Threat 3").AddComponent<DroneTarget>();
|
||||
|
||||
// Add Rigidbody components to threats to set velocities
|
||||
Rigidbody rb1 = threat1.gameObject.AddComponent<Rigidbody>();
|
||||
Rigidbody rb2 = threat2.gameObject.AddComponent<Rigidbody>();
|
||||
Rigidbody rb3 = threat3.gameObject.AddComponent<Rigidbody>();
|
||||
|
||||
// Set positions and velocities
|
||||
threat1.transform.position = Vector3.forward * -20f;
|
||||
threat2.transform.position = Vector3.forward * -20f;
|
||||
threat3.transform.position = Vector3.forward * -20f;
|
||||
|
||||
rb1.linearVelocity = Vector3.forward * 5f;
|
||||
rb2.linearVelocity = Vector3.forward * 10f;
|
||||
rb3.linearVelocity = Vector3.forward * 15f;
|
||||
|
||||
// Create threat data
|
||||
List<ThreatData> threats = new List<ThreatData>
|
||||
{
|
||||
new ThreatData(threat1, "Threat1ID"),
|
||||
new ThreatData(threat2, "Threat2ID"),
|
||||
new ThreatData(threat3, "Threat3ID")
|
||||
};
|
||||
|
||||
// Act
|
||||
IEnumerable<IAssignment.AssignmentItem> assignments = threatAssignment.Assign(interceptors, threats);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(3, assignments.Count(), "All interceptors should be assigned");
|
||||
|
||||
HashSet<Interceptor> assignedInterceptors = new HashSet<Interceptor>();
|
||||
HashSet<Threat> assignedThreats = new HashSet<Threat>();
|
||||
|
||||
foreach (var assignment in assignments)
|
||||
{
|
||||
Assert.IsNotNull(assignment.Interceptor, "Interceptor should not be null");
|
||||
Assert.IsNotNull(assignment.Threat, "Threat should not be null");
|
||||
assignedInterceptors.Add(assignment.Interceptor);
|
||||
assignedThreats.Add(assignment.Threat);
|
||||
}
|
||||
|
||||
Assert.AreEqual(3, assignedInterceptors.Count, "All interceptors should be unique");
|
||||
Assert.AreEqual(3, assignedThreats.Count, "All threats should be assigned");
|
||||
|
||||
// Verify that threats are assigned in order of their threat level (based on velocity and distance)
|
||||
var orderedAssignments = assignments.OrderByDescending(a => a.Threat.GetVelocity().magnitude / Vector3.Distance(a.Threat.transform.position, Vector3.zero)).ToList();
|
||||
Assert.AreEqual(threat3, orderedAssignments[0].Threat, "Highest threat should be assigned first");
|
||||
Assert.AreEqual(threat2, orderedAssignments[1].Threat, "Second highest threat should be assigned second");
|
||||
Assert.AreEqual(threat1, orderedAssignments[2].Threat, "Lowest threat should be assigned last");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Assign_Should_Handle_More_Interceptors_Than_Threats()
|
||||
{
|
||||
// Arrange
|
||||
ThreatAssignment threatAssignment = new ThreatAssignment();
|
||||
|
||||
// Create interceptors
|
||||
List<Interceptor> interceptors = new List<Interceptor>
|
||||
{
|
||||
new GameObject("Interceptor 1").AddComponent<Micromissile>(),
|
||||
new GameObject("Interceptor 2").AddComponent<Micromissile>(),
|
||||
new GameObject("Interceptor 3").AddComponent<Micromissile>()
|
||||
};
|
||||
|
||||
// Create threats
|
||||
Threat threat1 = new GameObject("Threat 1").AddComponent<DroneTarget>();
|
||||
Threat threat2 = new GameObject("Threat 2").AddComponent<DroneTarget>();
|
||||
|
||||
// Add Rigidbody components to threats to set velocities
|
||||
Rigidbody rb1 = threat1.gameObject.AddComponent<Rigidbody>();
|
||||
Rigidbody rb2 = threat2.gameObject.AddComponent<Rigidbody>();
|
||||
|
||||
// Set positions and velocities
|
||||
threat1.transform.position = Vector3.up * 10f;
|
||||
threat2.transform.position = Vector3.right * 5f;
|
||||
|
||||
rb1.linearVelocity = Vector3.forward * 10f;
|
||||
rb2.linearVelocity = Vector3.forward * 15f;
|
||||
|
||||
// Create threat data
|
||||
List<ThreatData> threats = new List<ThreatData>
|
||||
{
|
||||
new ThreatData(threat1, "Threat1ID"),
|
||||
new ThreatData(threat2, "Threat2ID")
|
||||
};
|
||||
|
||||
// Act
|
||||
IEnumerable<IAssignment.AssignmentItem> assignments = threatAssignment.Assign(interceptors, threats);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(2, assignments.Count(), "All threats should be assigned");
|
||||
|
||||
HashSet<Interceptor> assignedInterceptors = new HashSet<Interceptor>();
|
||||
HashSet<Threat> assignedThreats = new HashSet<Threat>();
|
||||
|
||||
foreach (var assignment in assignments)
|
||||
{
|
||||
Assert.IsNotNull(assignment.Interceptor, "Interceptor should not be null");
|
||||
Assert.IsNotNull(assignment.Threat, "Threat should not be null");
|
||||
assignedInterceptors.Add(assignment.Interceptor);
|
||||
assignedThreats.Add(assignment.Threat);
|
||||
}
|
||||
|
||||
Assert.AreEqual(2, assignedInterceptors.Count, "Two interceptors should be assigned");
|
||||
Assert.AreEqual(2, assignedThreats.Count, "Both threats should be assigned");
|
||||
|
||||
// Verify that threats are assigned in order of their threat level (based on velocity and distance)
|
||||
var orderedAssignments = assignments.OrderByDescending(a => a.Threat.GetVelocity().magnitude / Vector3.Distance(a.Threat.transform.position, Vector3.zero)).ToList();
|
||||
Assert.AreEqual(threat2, orderedAssignments[0].Threat, "Higher threat should be assigned first");
|
||||
Assert.AreEqual(threat1, orderedAssignments[1].Threat, "Lower threat should be assigned second");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7ddf271569a78ee4e995192d4df0ef3f
|
|
@ -61,6 +61,11 @@
|
|||
"type": "UnityEngine.PhysicMaterial",
|
||||
"defaultInstantiationMode": 0
|
||||
},
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEngine.PhysicsMaterial",
|
||||
"defaultInstantiationMode": 0
|
||||
},
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEngine.PhysicsMaterial2D",
|
||||
|
|
Loading…
Reference in New Issue