Assignment system overhaul to fix many bugs
parent
d5ae6f97f1
commit
e15b70fafc
|
@ -33,12 +33,12 @@ public abstract class Agent : MonoBehaviour {
|
||||||
protected StaticConfig _staticConfig;
|
protected StaticConfig _staticConfig;
|
||||||
|
|
||||||
// Define delegates
|
// Define delegates
|
||||||
public delegate void AgentHitEventHandler(Agent agent);
|
public delegate void InterceptHitEventHandler(Interceptor interceptor, Threat target);
|
||||||
public delegate void AgentMissEventHandler(Agent agent);
|
public delegate void InterceptMissEventHandler(Interceptor interceptor, Threat target);
|
||||||
|
|
||||||
// Define events
|
// Define events
|
||||||
public event AgentHitEventHandler OnAgentHit;
|
public event InterceptHitEventHandler OnInterceptHit;
|
||||||
public event AgentMissEventHandler OnAgentMiss;
|
public event InterceptMissEventHandler OnInterceptMiss;
|
||||||
|
|
||||||
public void SetFlightPhase(FlightPhase flightPhase) {
|
public void SetFlightPhase(FlightPhase flightPhase) {
|
||||||
Debug.Log(
|
Debug.Log(
|
||||||
|
@ -105,16 +105,24 @@ public abstract class Agent : MonoBehaviour {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark the agent as having hit the target or been hit.
|
// Mark the agent as having hit the target or been hit.
|
||||||
public void MarkAsHit() {
|
public void HandleInterceptHit() {
|
||||||
_isHit = true;
|
_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();
|
TerminateAgent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MarkAsMiss() {
|
public void HandleInterceptMiss() {
|
||||||
_isMiss = true;
|
_isMiss = true;
|
||||||
if (_target != null) {
|
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;
|
_target = null;
|
||||||
}
|
}
|
||||||
TerminateAgent();
|
TerminateAgent();
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Diagnostics.Contracts;
|
||||||
|
|
||||||
// The assignment class is an interface for assigning a threat to each interceptor.
|
// The assignment class is an interface for assigning a threat to each interceptor.
|
||||||
public interface IAssignment {
|
public interface IAssignment {
|
||||||
|
@ -8,39 +11,30 @@ public interface IAssignment {
|
||||||
// The first element corresponds to the interceptor index, and the second element
|
// The first element corresponds to the interceptor index, and the second element
|
||||||
// corresponds to the threat index.
|
// corresponds to the threat index.
|
||||||
public struct AssignmentItem {
|
public struct AssignmentItem {
|
||||||
public int InterceptorIndex;
|
public Interceptor Interceptor;
|
||||||
public int ThreatIndex;
|
public Threat Threat;
|
||||||
|
|
||||||
public AssignmentItem(int missileIndex, int threatIndex) {
|
public AssignmentItem(Interceptor interceptor, Threat threat) {
|
||||||
InterceptorIndex = missileIndex;
|
Interceptor = interceptor;
|
||||||
ThreatIndex = threatIndex;
|
Threat = threat;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A list containing the interceptor-target assignments.
|
// A list containing the interceptor-target assignments.
|
||||||
|
|
||||||
// Assign a target to each interceptor that has not been assigned a target yet.
|
// 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.
|
// Get the list of assignable interceptor indices.
|
||||||
protected static List<int> GetAssignableInterceptorIndices(List<Agent> missiles) {
|
[Pure]
|
||||||
List<int> assignableInterceptorIndices = new List<int>();
|
protected static List<Interceptor> GetAssignableInterceptors(in IReadOnlyList<Interceptor> interceptors) {
|
||||||
for (int missileIndex = 0; missileIndex < missiles.Count; missileIndex++) {
|
return interceptors.Where(interceptor => interceptor.IsAssignable()).ToList();
|
||||||
if (missiles[missileIndex].IsAssignable()) {
|
|
||||||
assignableInterceptorIndices.Add(missileIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return assignableInterceptorIndices;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the list of active target indices.
|
// Get the list of active threats.
|
||||||
protected static List<int> GetActiveThreatIndices(List<Agent> threats) {
|
[Pure]
|
||||||
List<int> activeThreatIndices = new List<int>();
|
protected static List<ThreatData> GetActiveThreats(in IReadOnlyList<ThreatData> threats) {
|
||||||
for (int threatIndex = 0; threatIndex < threats.Count; threatIndex++) {
|
return threats.Where(t => t.Status != ThreatStatus.DESTROYED).ToList();
|
||||||
if (!threats[threatIndex].IsHit()) {
|
|
||||||
activeThreatIndices.Add(threatIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return activeThreatIndices;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +1,43 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using System.Diagnostics.Contracts;
|
||||||
// The round-robin assignment class assigns missiles to the targets in a
|
// The round-robin assignment class assigns interceptors to the targets in a
|
||||||
// round-robin order.
|
// round-robin order using the new paradigm.
|
||||||
public class RoundRobinAssignment : IAssignment {
|
public class RoundRobinAssignment : IAssignment {
|
||||||
// Previous target index that was assigned.
|
// Previous target index that was assigned.
|
||||||
private int prevTargetIndex = -1;
|
private int prevTargetIndex = -1;
|
||||||
|
|
||||||
// Assign a target to each interceptor that has not been assigned a target yet.
|
// 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]
|
||||||
List<IAssignment.AssignmentItem> assignments = new List<IAssignment.AssignmentItem>();
|
public IEnumerable<IAssignment.AssignmentItem> Assign(in IReadOnlyList<Interceptor> interceptors, in IReadOnlyList<ThreatData> targets) {
|
||||||
List<int> assignableInterceptorIndices = IAssignment.GetAssignableInterceptorIndices(missiles);
|
List<IAssignment.AssignmentItem> assignments = new List<IAssignment.AssignmentItem>();
|
||||||
if (assignableInterceptorIndices.Count == 0) {
|
|
||||||
return assignments;
|
// 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 System.Linq;
|
||||||
using Unity.VisualScripting;
|
using Unity.VisualScripting;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using System.Diagnostics.Contracts;
|
||||||
// The threat assignment class assigns missiles to the targets based
|
// The threat assignment class assigns interceptors to the targets based
|
||||||
// on the threat level of the targets.
|
// on the threat level of the targets.
|
||||||
public class ThreatAssignment : IAssignment {
|
public class ThreatAssignment : IAssignment {
|
||||||
// Assign a target to each interceptor that has not been assigned a target yet.
|
// 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<IAssignment.AssignmentItem> assignments = new List<IAssignment.AssignmentItem>();
|
||||||
|
|
||||||
List<int> assignableInterceptorIndices = IAssignment.GetAssignableInterceptorIndices(missiles);
|
List<Interceptor> assignableInterceptors = IAssignment.GetAssignableInterceptors(interceptors);
|
||||||
if (assignableInterceptorIndices.Count == 0) {
|
if (assignableInterceptors.Count == 0) {
|
||||||
|
Debug.LogWarning("No assignable interceptors found");
|
||||||
return assignments;
|
return assignments;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<int> activeThreatIndices = IAssignment.GetActiveThreatIndices(targets);
|
List<ThreatData> activeThreats = IAssignment.GetActiveThreats(targets);
|
||||||
if (activeThreatIndices.Count == 0) {
|
if (activeThreats.Count == 0) {
|
||||||
|
Debug.LogWarning("No active threats found");
|
||||||
return assignments;
|
return assignments;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3 positionToDefend = Vector3.zero;
|
Vector3 positionToDefend = Vector3.zero;
|
||||||
List<ThreatInfo> threatInfos =
|
List<ThreatInfo> threatInfos =
|
||||||
CalculateThreatLevels(targets, activeThreatIndices, positionToDefend);
|
CalculateThreatLevels(activeThreats, positionToDefend);
|
||||||
|
|
||||||
foreach (int missileIndex in assignableInterceptorIndices) {
|
// Sort ThreatInfo first by ThreatData.Status (UNASSIGNED first, then ASSIGNED)
|
||||||
if (missiles[missileIndex].HasAssignedTarget())
|
// Within each group, order by ThreatLevel descending
|
||||||
continue;
|
threatInfos = threatInfos.OrderByDescending(t => t.ThreatData.Status == ThreatStatus.UNASSIGNED)
|
||||||
if (threatInfos.Count == 0)
|
.ThenByDescending(t => t.ThreatLevel)
|
||||||
break;
|
.ToList();
|
||||||
|
|
||||||
// Find the optimal target for this interceptor based on distance and threat
|
var assignableInterceptorsEnumerator = assignableInterceptors.GetEnumerator();
|
||||||
ThreatInfo optimalTarget = null;
|
if (assignableInterceptorsEnumerator.MoveNext()) // Move to the first element
|
||||||
float optimalScore = float.MinValue;
|
{
|
||||||
|
foreach (ThreatInfo threatInfo in threatInfos) {
|
||||||
foreach (ThreatInfo threat in threatInfos) {
|
assignments.Add(new IAssignment.AssignmentItem(assignableInterceptorsEnumerator.Current, threatInfo.ThreatData.Threat));
|
||||||
float distance = Vector3.Distance(missiles[missileIndex].transform.position,
|
if (!assignableInterceptorsEnumerator.MoveNext()) {
|
||||||
targets[threat.TargetIndex].transform.position);
|
break;
|
||||||
float score = threat.ThreatLevel / distance; // Balance threat level with proximity
|
|
||||||
|
|
||||||
if (score > optimalScore) {
|
|
||||||
optimalScore = score;
|
|
||||||
optimalTarget = threat;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (optimalTarget != null) {
|
|
||||||
assignments.Add(new IAssignment.AssignmentItem(missileIndex, optimalTarget.TargetIndex));
|
|
||||||
threatInfos.Remove(optimalTarget);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return assignments;
|
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>();
|
List<ThreatInfo> threatInfos = new List<ThreatInfo>();
|
||||||
|
|
||||||
foreach (int targetIndex in activeThreatIndices) {
|
foreach (ThreatData threatData in threatTable) {
|
||||||
Agent target = targets[targetIndex];
|
Threat threat = threatData.Threat;
|
||||||
float distanceToMean = Vector3.Distance(target.transform.position, missilesMeanPosition);
|
float distanceToMean = Vector3.Distance(threat.transform.position, defensePosition);
|
||||||
float velocityMagnitude = target.GetVelocity().magnitude;
|
float velocityMagnitude = threat.GetVelocity().magnitude;
|
||||||
|
|
||||||
// Calculate threat level based on proximity and velocity
|
// Calculate threat level based on proximity and velocity
|
||||||
float threatLevel = (1 / distanceToMean) * velocityMagnitude;
|
float threatLevel = (1 / distanceToMean) * velocityMagnitude;
|
||||||
|
|
||||||
threatInfos.Add(new ThreatInfo(targetIndex, threatLevel));
|
threatInfos.Add(new ThreatInfo(threatData, threatLevel));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort threats in descending order
|
// Sort threats in descending order
|
||||||
|
@ -74,11 +68,11 @@ public class ThreatAssignment : IAssignment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ThreatInfo {
|
private class ThreatInfo {
|
||||||
public int TargetIndex { get; }
|
public ThreatData ThreatData { get; }
|
||||||
public float ThreatLevel { get; }
|
public float ThreatLevel { get; }
|
||||||
|
|
||||||
public ThreatInfo(int targetIndex, float threatLevel) {
|
public ThreatInfo(ThreatData threatData, float threatLevel) {
|
||||||
TargetIndex = targetIndex;
|
ThreatData = threatData;
|
||||||
ThreatLevel = threatLevel;
|
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) {
|
private void OnTriggerEnter(Collider other) {
|
||||||
if (other.gameObject.name == "Floor") {
|
if (other.gameObject.name == "Floor") {
|
||||||
this.MarkAsMiss();
|
this.HandleInterceptMiss();
|
||||||
}
|
}
|
||||||
// Check if the collision is with another Agent
|
// Check if the collision is with another Agent
|
||||||
Agent otherAgent = other.gameObject.GetComponentInParent<Agent>();
|
Agent otherAgent = other.gameObject.GetComponentInParent<Agent>();
|
||||||
|
@ -87,13 +87,13 @@ public class Interceptor : Agent {
|
||||||
// Set green for hit
|
// Set green for hit
|
||||||
markerObject.GetComponent<Renderer>().material.color = new Color(0, 1, 0, 0.15f);
|
markerObject.GetComponent<Renderer>().material.color = new Color(0, 1, 0, 0.15f);
|
||||||
// Mark both this agent and the other agent as hit
|
// Mark both this agent and the other agent as hit
|
||||||
this.MarkAsHit();
|
this.HandleInterceptHit();
|
||||||
otherAgent.MarkAsHit();
|
otherAgent.HandleInterceptHit();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Set red for miss
|
// Set red for miss
|
||||||
markerObject.GetComponent<Renderer>().material.color = new Color(1, 0, 0, 0.15f);
|
markerObject.GetComponent<Renderer>().material.color = new Color(1, 0, 0, 0.15f);
|
||||||
this.MarkAsMiss();
|
this.HandleInterceptMiss();
|
||||||
// otherAgent.MarkAsMiss();
|
// otherAgent.MarkAsMiss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ public class Micromissile : Interceptor {
|
||||||
// Check whether the threat should be considered a miss
|
// Check whether the threat should be considered a miss
|
||||||
SensorOutput sensorOutput = GetComponent<Sensor>().Sense(_target);
|
SensorOutput sensorOutput = GetComponent<Sensor>().Sense(_target);
|
||||||
if (sensorOutput.velocity.range > 1000f) {
|
if (sensorOutput.velocity.range > 1000f) {
|
||||||
this.MarkAsMiss();
|
this.HandleInterceptMiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the acceleration input
|
// 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()
|
private void ExportTelemetry()
|
||||||
{
|
{
|
||||||
float time = (float)SimManager.Instance.GetElapsedSimulationTime();
|
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;
|
Vector3 pos = agent.transform.position;
|
||||||
if(pos == Vector3.zero) {
|
if(pos == Vector3.zero) {
|
||||||
|
|
|
@ -19,11 +19,15 @@ public class SimManager : MonoBehaviour {
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
public SimulationConfig simulationConfig;
|
public SimulationConfig simulationConfig;
|
||||||
|
|
||||||
private List<Interceptor> _interceptors = new List<Interceptor>();
|
|
||||||
private List<Interceptor> _activeInterceptors = new List<Interceptor>();
|
private List<Interceptor> _activeInterceptors = new List<Interceptor>();
|
||||||
private List<Threat> _unassignedThreats = new List<Threat>();
|
[SerializeField]
|
||||||
private List<Threat> _threats = new List<Threat>();
|
private List<ThreatData> _threatTable = new List<ThreatData>();
|
||||||
private List<Threat> _activeThreats = new List<Threat>();
|
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 _elapsedSimulationTime = 0f;
|
||||||
private float endTime = 100f; // Set an appropriate end time
|
private float endTime = 100f; // Set an appropriate end time
|
||||||
private bool simulationRunning = false;
|
private bool simulationRunning = false;
|
||||||
|
@ -47,12 +51,12 @@ public class SimManager : MonoBehaviour {
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Threat> GetActiveThreats() {
|
public List<Threat> GetActiveThreats() {
|
||||||
return _activeThreats;
|
return _threatTable.Where(threat => threat.Status != ThreatStatus.DESTROYED).Select(threat => threat.Threat).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Agent> GetActiveAgents() {
|
public List<Agent> GetActiveAgents() {
|
||||||
return _activeInterceptors.ConvertAll(interceptor => interceptor as Agent)
|
return _activeInterceptors.ConvertAll(interceptor => interceptor as Agent)
|
||||||
.Concat(_activeThreats.ConvertAll(threat => threat as Agent))
|
.Concat(GetActiveThreats().ConvertAll(threat => threat as Agent))
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,8 +112,6 @@ public class SimManager : MonoBehaviour {
|
||||||
foreach (var swarmConfig in simulationConfig.interceptor_swarm_configs) {
|
foreach (var swarmConfig in simulationConfig.interceptor_swarm_configs) {
|
||||||
for (int i = 0; i < swarmConfig.num_agents; i++) {
|
for (int i = 0; i < swarmConfig.num_agents; i++) {
|
||||||
var interceptor = CreateInterceptor(swarmConfig.agent_config);
|
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) {
|
foreach (var swarmConfig in simulationConfig.threat_swarm_configs) {
|
||||||
for (int i = 0; i < swarmConfig.num_agents; i++) {
|
for (int i = 0; i < swarmConfig.num_agents; i++) {
|
||||||
var threat = CreateThreat(swarmConfig.agent_config);
|
var threat = CreateThreat(swarmConfig.agent_config);
|
||||||
threat.OnAgentHit += RegisterThreatHit;
|
|
||||||
threat.OnAgentMiss += RegisterThreatMiss;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,31 +131,35 @@ public class SimManager : MonoBehaviour {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AssignInterceptorsToThreats() {
|
public void AssignInterceptorsToThreats() {
|
||||||
AssignInterceptorsToThreats(_interceptors);
|
AssignInterceptorsToThreats(_interceptorObjects);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RegisterInterceptorHit(Agent interceptor) {
|
public void RegisterInterceptorHit(Interceptor interceptor, Threat threat) {
|
||||||
if (interceptor is Interceptor missileComponent) {
|
if (interceptor is Interceptor missileComponent) {
|
||||||
_activeInterceptors.Remove(missileComponent);
|
_activeInterceptors.Remove(missileComponent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RegisterInterceptorMiss(Agent interceptor) {
|
public void RegisterInterceptorMiss(Interceptor interceptor, Threat threat) {
|
||||||
if (interceptor is Interceptor missileComponent) {
|
if (interceptor is Interceptor missileComponent) {
|
||||||
_activeInterceptors.Remove(missileComponent);
|
_activeInterceptors.Remove(missileComponent);
|
||||||
}
|
}
|
||||||
|
// Remove the interceptor from the threat's assigned interceptors
|
||||||
|
_threatDataMap[threat].RemoveInterceptor(interceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RegisterThreatHit(Agent threat) {
|
public void RegisterThreatHit(Interceptor interceptor, Threat threat) {
|
||||||
if (threat is Threat targetComponent) {
|
ThreatData threatData = _threatDataMap[threat];
|
||||||
_activeThreats.Remove(targetComponent);
|
threatData.RemoveInterceptor(interceptor);
|
||||||
|
if (threatData != null) {
|
||||||
|
threatData.MarkDestroyed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RegisterThreatMiss(Agent threat) {
|
public void RegisterThreatMiss(Interceptor interceptor, Threat threat) {
|
||||||
if (threat is Threat targetComponent) {
|
Debug.Log($"RegisterThreatMiss: Interceptor {interceptor.name} missed threat {threat.name}");
|
||||||
_unassignedThreats.Add(targetComponent);
|
ThreatData threatData = _threatDataMap[threat];
|
||||||
}
|
threatData.RemoveInterceptor(interceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -163,27 +167,34 @@ public class SimManager : MonoBehaviour {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="missilesToAssign">The list of missiles to assign.</param>
|
/// <param name="missilesToAssign">The list of missiles to assign.</param>
|
||||||
public void AssignInterceptorsToThreats(List<Interceptor> missilesToAssign) {
|
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
|
// Perform the assignment
|
||||||
IEnumerable<IAssignment.AssignmentItem> assignments =
|
IEnumerable<IAssignment.AssignmentItem> assignments =
|
||||||
_assignmentScheme.Assign(missileAgents, targetAgents);
|
_assignmentScheme.Assign(missilesToAssign, _threatTable);
|
||||||
|
|
||||||
// Apply the assignments to the missiles
|
// Apply the assignments to the missiles
|
||||||
foreach (var assignment in assignments) {
|
foreach (var assignment in assignments) {
|
||||||
if (assignment.InterceptorIndex < missilesToAssign.Count) {
|
Debug.LogWarning($"Assigning interceptor {assignment.Interceptor} to threat {assignment.Threat}");
|
||||||
Interceptor interceptor = missilesToAssign[assignment.InterceptorIndex];
|
assignment.Interceptor.AssignTarget(assignment.Threat);
|
||||||
Threat threat = _unassignedThreats[assignment.ThreatIndex];
|
_threatDataMap[assignment.Threat].AssignInterceptor(assignment.Interceptor);
|
||||||
interceptor.AssignTarget(threat);
|
Debug.Log($"Interceptor {assignment.Interceptor.name} assigned to threat {assignment.Threat.name}");
|
||||||
Debug.Log($"Interceptor {interceptor.name} assigned to threat {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>
|
/// <summary>
|
||||||
|
@ -196,27 +207,32 @@ public class SimManager : MonoBehaviour {
|
||||||
InterceptorType.MICROMISSILE => "Micromissile",
|
InterceptorType.MICROMISSILE => "Micromissile",
|
||||||
_ => "Hydra70" };
|
_ => "Hydra70" };
|
||||||
|
|
||||||
GameObject missileObject = CreateAgent(config, prefabName);
|
GameObject interceptorObject = CreateAgent(config, prefabName);
|
||||||
if (missileObject == null)
|
if (interceptorObject == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// Interceptor-specific logic
|
// Interceptor-specific logic
|
||||||
switch (config.dynamic_config.sensor_config.type) {
|
switch (config.dynamic_config.sensor_config.type) {
|
||||||
case SensorType.IDEAL:
|
case SensorType.IDEAL:
|
||||||
missileObject.AddComponent<IdealSensor>();
|
interceptorObject.AddComponent<IdealSensor>();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Debug.LogError($"Sensor type '{config.dynamic_config.sensor_config.type}' not found.");
|
Debug.LogError($"Sensor type '{config.dynamic_config.sensor_config.type}' not found.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
_interceptors.Add(missileObject.GetComponent<Interceptor>());
|
Interceptor interceptor = interceptorObject.GetComponent<Interceptor>();
|
||||||
_activeInterceptors.Add(missileObject.GetComponent<Interceptor>());
|
_interceptorObjects.Add(interceptor);
|
||||||
|
_activeInterceptors.Add(interceptor);
|
||||||
|
|
||||||
|
// Subscribe events
|
||||||
|
interceptor.OnInterceptHit += RegisterInterceptorHit;
|
||||||
|
interceptor.OnInterceptMiss += RegisterInterceptorMiss;
|
||||||
|
|
||||||
// Assign a unique and simple ID
|
// Assign a unique and simple ID
|
||||||
int missileId = _interceptors.Count;
|
int missileId = _interceptorObjects.Count;
|
||||||
missileObject.name = $"{config.interceptor_type}_Interceptor_{missileId}";
|
interceptorObject.name = $"{config.interceptor_type}_Interceptor_{missileId}";
|
||||||
return missileObject.GetComponent<Interceptor>();
|
return interceptorObject.GetComponent<Interceptor>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -233,13 +249,20 @@ public class SimManager : MonoBehaviour {
|
||||||
if (threatObject == null)
|
if (threatObject == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
_threats.Add(threatObject.GetComponent<Threat>());
|
Threat threat = threatObject.GetComponent<Threat>();
|
||||||
_activeThreats.Add(threatObject.GetComponent<Threat>());
|
|
||||||
_unassignedThreats.Add(threatObject.GetComponent<Threat>());
|
|
||||||
|
|
||||||
// Assign a unique and simple ID
|
// Assign a unique and simple ID
|
||||||
int targetId = _threats.Count;
|
int targetId = _threatTable.Count;
|
||||||
threatObject.name = $"{config.threat_type}_Target_{targetId}";
|
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>();
|
return threatObject.GetComponent<Threat>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,21 +313,24 @@ public class SimManager : MonoBehaviour {
|
||||||
simulationRunning = IsSimulationRunning();
|
simulationRunning = IsSimulationRunning();
|
||||||
|
|
||||||
// Clear existing missiles and targets
|
// Clear existing missiles and targets
|
||||||
foreach (var interceptor in _interceptors) {
|
foreach (var interceptor in _interceptorObjects) {
|
||||||
if (interceptor != null) {
|
if (interceptor != null) {
|
||||||
Destroy(interceptor.gameObject);
|
Destroy(interceptor.gameObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var threat in _threats) {
|
foreach (var threat in _threatObjects) {
|
||||||
if (threat != null) {
|
if (threat != null) {
|
||||||
Destroy(threat.gameObject);
|
Destroy(threat.gameObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_interceptors.Clear();
|
_interceptorObjects.Clear();
|
||||||
_threats.Clear();
|
_activeInterceptors.Clear();
|
||||||
_unassignedThreats.Clear();
|
_threatObjects.Clear();
|
||||||
|
_threatTable.Clear();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
StartSimulation();
|
StartSimulation();
|
||||||
}
|
}
|
||||||
|
@ -312,7 +338,7 @@ public class SimManager : MonoBehaviour {
|
||||||
void Update() {
|
void Update() {
|
||||||
// Check if all missiles have terminated
|
// Check if all missiles have terminated
|
||||||
bool allInterceptorsTerminated = true;
|
bool allInterceptorsTerminated = true;
|
||||||
foreach (var interceptor in _interceptors) {
|
foreach (var interceptor in _interceptorObjects) {
|
||||||
if (interceptor != null && !interceptor.IsHit() && !interceptor.IsMiss()) {
|
if (interceptor != null && !interceptor.IsHit() && !interceptor.IsMiss()) {
|
||||||
allInterceptorsTerminated = false;
|
allInterceptorsTerminated = false;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: f034d27b4aab67a47865af3d624c4375
|
guid: 5ab47dc725c65cb4cb1fa6948fb0c9a7
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
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",
|
"type": "UnityEngine.PhysicMaterial",
|
||||||
"defaultInstantiationMode": 0
|
"defaultInstantiationMode": 0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"userAdded": false,
|
||||||
|
"type": "UnityEngine.PhysicsMaterial",
|
||||||
|
"defaultInstantiationMode": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"userAdded": false,
|
"userAdded": false,
|
||||||
"type": "UnityEngine.PhysicsMaterial2D",
|
"type": "UnityEngine.PhysicsMaterial2D",
|
||||||
|
|
Loading…
Reference in New Issue