This commit is contained in:
turtlebasket
2024-10-04 02:51:19 +00:00
parent be34e325d6
commit 7805acc392
387 changed files with 95157 additions and 0 deletions

View File

@@ -0,0 +1,208 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class Agent : MonoBehaviour {
public enum FlightPhase { INITIALIZED, READY, BOOST, MIDCOURSE, TERMINAL, TERMINATED }
[SerializeField]
private FlightPhase _flightPhase = FlightPhase.INITIALIZED;
[SerializeField]
protected Vector3 _velocity;
[SerializeField]
protected Vector3 _acceleration;
[SerializeField]
protected Vector3 _dragAcceleration;
[SerializeField]
protected Agent _target;
protected bool _isHit = false;
protected bool _isMiss = false;
protected AgentConfig _agentConfig;
protected double _timeSinceLaunch = 0;
protected double _timeInPhase = 0;
[SerializeField]
public string staticConfigFile = "generic_static_config.json";
protected StaticConfig _staticConfig;
// Define delegates
public delegate void InterceptHitEventHandler(Interceptor interceptor, Threat target);
public delegate void InterceptMissEventHandler(Interceptor interceptor, Threat target);
// Define events
public event InterceptHitEventHandler OnInterceptHit;
public event InterceptMissEventHandler OnInterceptMiss;
public void SetFlightPhase(FlightPhase flightPhase) {
Debug.Log(
$"Setting flight phase to {flightPhase} at time {SimManager.Instance.GetElapsedSimulationTime()}");
_timeInPhase = 0;
_flightPhase = flightPhase;
}
public FlightPhase GetFlightPhase() {
return _flightPhase;
}
public bool HasLaunched() {
return (_flightPhase != FlightPhase.INITIALIZED) && (_flightPhase != FlightPhase.READY);
}
public bool HasTerminated() {
return _flightPhase == FlightPhase.TERMINATED;
}
public virtual void SetAgentConfig(AgentConfig config) {
_agentConfig = config;
}
public virtual bool IsAssignable() {
return true;
}
public virtual void AssignTarget(Agent target) {
_target = target;
}
public Agent GetAssignedTarget() {
return _target;
}
public bool HasAssignedTarget() {
return _target != null;
}
public void CheckTargetHit() {
if (HasAssignedTarget() && _target.IsHit()) {
UnassignTarget();
}
}
public virtual void UnassignTarget() {
_target = null;
}
// Return whether the agent has hit or been hit.
public bool IsHit() {
return _isHit;
}
public void TerminateAgent() {
_flightPhase = FlightPhase.TERMINATED;
transform.position = new Vector3(0, 0, 0);
gameObject.SetActive(false);
}
// Mark the agent as having hit the target or been hit.
public void HandleInterceptHit(Agent otherAgent) {
_isHit = true;
if (this is Interceptor interceptor && otherAgent is Threat threat) {
OnInterceptHit?.Invoke(interceptor, threat);
} else if (this is Threat threatAgent && otherAgent is Interceptor interceptorTarget) {
OnInterceptHit?.Invoke(interceptorTarget, threatAgent);
}
TerminateAgent();
}
public void HandleInterceptMiss() {
if (_target != null) {
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();
}
public double GetSpeed() {
return GetComponent<Rigidbody>().linearVelocity.magnitude;
}
public Vector3 GetVelocity() {
return GetComponent<Rigidbody>().linearVelocity;
}
public double GetDynamicPressure() {
var airDensity = Constants.CalculateAirDensityAtAltitude(transform.position.y);
var flowSpeed = GetSpeed();
return 0.5 * airDensity * (flowSpeed * flowSpeed);
}
protected abstract void UpdateReady(double deltaTime);
protected abstract void UpdateBoost(double deltaTime);
protected abstract void UpdateMidCourse(double deltaTime);
protected virtual void Awake() {
_staticConfig = ConfigLoader.LoadStaticConfig(staticConfigFile);
GetComponent<Rigidbody>().mass = _staticConfig.bodyConfig.mass;
}
// Start is called before the first frame update
protected virtual void Start() {
_flightPhase = FlightPhase.READY;
}
// Update is called once per frame
protected virtual void FixedUpdate() {
_timeSinceLaunch += Time.fixedDeltaTime;
_timeInPhase += Time.fixedDeltaTime;
var launch_time = _agentConfig.dynamic_config.launch_config.launch_time;
var boost_time = launch_time + _staticConfig.boostConfig.boostTime;
double elapsedSimulationTime = SimManager.Instance.GetElapsedSimulationTime();
if (_flightPhase == FlightPhase.TERMINATED) {
return;
}
if (elapsedSimulationTime >= launch_time && _flightPhase == FlightPhase.READY) {
SetFlightPhase(FlightPhase.BOOST);
}
if (_timeSinceLaunch > boost_time && _flightPhase == FlightPhase.BOOST) {
SetFlightPhase(FlightPhase.MIDCOURSE);
}
AlignWithVelocity();
switch (_flightPhase) {
case FlightPhase.INITIALIZED:
break;
case FlightPhase.READY:
UpdateReady(Time.fixedDeltaTime);
break;
case FlightPhase.BOOST:
UpdateBoost(Time.fixedDeltaTime);
break;
case FlightPhase.MIDCOURSE:
case FlightPhase.TERMINAL:
UpdateMidCourse(Time.fixedDeltaTime);
break;
case FlightPhase.TERMINATED:
break;
}
_velocity = GetComponent<Rigidbody>().linearVelocity;
_acceleration =
GetComponent<Rigidbody>().GetAccumulatedForce() / GetComponent<Rigidbody>().mass;
}
protected virtual void AlignWithVelocity() {
Vector3 velocity = GetVelocity();
if (velocity.magnitude > 0.1f) // Only align if we have significant velocity
{
// Create a rotation with forward along velocity and up along world up
Quaternion targetRotation = Quaternion.LookRotation(velocity, Vector3.up);
// Smoothly rotate towards the target rotation
transform.rotation =
Quaternion.RotateTowards(transform.rotation, targetRotation, 1000f * Time.deltaTime);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a86fc0084ee738845a451b91dd4960d9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 06e870818cf329e49a0d0c5a0d024f21
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Collections;
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 {
// Assignment item type.
// The first element corresponds to the interceptor index, and the second element
// corresponds to the threat index.
public struct AssignmentItem {
public Interceptor Interceptor;
public Threat Threat;
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.
[Pure]
public abstract IEnumerable<AssignmentItem> Assign(in IReadOnlyList<Interceptor> interceptors, in IReadOnlyList<ThreatData> threatTable);
// Get the list of assignable interceptor indices.
[Pure]
protected static List<Interceptor> GetAssignableInterceptors(in IReadOnlyList<Interceptor> interceptors) {
return interceptors.Where(interceptor => interceptor.IsAssignable()).ToList();
}
// 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();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 95c1ee01149331443bb91460f4d1e3a2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,43 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
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;
// 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;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f668f6d7413495c4093550e492ce36bd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.VisualScripting;
using UnityEngine;
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.
[Pure]
public IEnumerable<IAssignment.AssignmentItem> Assign(in IReadOnlyList<Interceptor> interceptors, in IReadOnlyList<ThreatData> targets) {
List<IAssignment.AssignmentItem> assignments = new List<IAssignment.AssignmentItem>();
List<Interceptor> assignableInterceptors = IAssignment.GetAssignableInterceptors(interceptors);
if (assignableInterceptors.Count == 0) {
Debug.LogWarning("No assignable interceptors found");
return assignments;
}
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(activeThreats, positionToDefend);
// 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();
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;
}
}
}
return assignments;
}
private List<ThreatInfo> CalculateThreatLevels(List<ThreatData> threatTable,
Vector3 defensePosition) {
List<ThreatInfo> threatInfos = new List<ThreatInfo>();
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(threatData, threatLevel));
}
// Sort threats in descending order
return threatInfos.OrderByDescending(t => t.ThreatLevel).ToList();
}
private class ThreatInfo {
public ThreatData ThreatData { get; }
public float ThreatLevel { get; }
public ThreatInfo(ThreatData threatData, float threatLevel) {
ThreatData = threatData;
ThreatLevel = threatLevel;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8e9829915a9eb41409ea03fb46910432
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 068fab6561d36f445b1d765a9d3af005
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,168 @@
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
using Newtonsoft.Json;
public static class ConfigLoader {
private static string LoadFromStreamingAssets(string relativePath)
{
string filePath = Path.Combine(Application.streamingAssetsPath, relativePath);
#if UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX || UNITY_STANDALONE_LINUX || UNITY_EDITOR_LINUX || UNITY_IOS
if (!filePath.StartsWith("file://"))
{
filePath = "file://" + filePath;
}
#endif
UnityWebRequest www = UnityWebRequest.Get(filePath);
www.SendWebRequest();
// Wait for the request to complete
while (!www.isDone)
{
// You might want to yield return null here if this is called from a coroutine
}
if (www.result != UnityWebRequest.Result.Success)
{
Debug.LogError($"Error loading file at {filePath}: {www.error}");
return null;
}
return www.downloadHandler.text;
}
public static SimulationConfig LoadSimulationConfig(string configFileName)
{
string relativePath = Path.Combine("Configs", configFileName);
string fileContent = LoadFromStreamingAssets(relativePath);
if (string.IsNullOrEmpty(fileContent))
{
Debug.LogError($"Failed to load SimulationConfig from {relativePath}");
return null;
}
return JsonConvert.DeserializeObject<SimulationConfig>(fileContent, new JsonSerializerSettings
{
Converters = { new Newtonsoft.Json.Converters.StringEnumConverter() }
});
}
public static StaticConfig LoadStaticConfig(string configFileName)
{
string relativePath = Path.Combine("Configs/Models", configFileName);
string fileContent = LoadFromStreamingAssets(relativePath);
if (string.IsNullOrEmpty(fileContent))
{
Debug.LogError($"Failed to load StaticConfig from {relativePath}");
return null;
}
return JsonConvert.DeserializeObject<StaticConfig>(fileContent, new JsonSerializerSettings
{
Converters = { new Newtonsoft.Json.Converters.StringEnumConverter() }
});
}
public static void PrintSimulationConfig(SimulationConfig config)
{
if (config == null)
{
Debug.Log("SimulationConfig is null");
return;
}
Debug.Log("SimulationConfig:");
Debug.Log($"Time Scale: {config.timeScale}");
Debug.Log("Interceptor Swarm Configurations:");
for (int i = 0; i < config.interceptor_swarm_configs.Count; i++)
{
PrintSwarmConfig(config.interceptor_swarm_configs[i], $"Interceptor Swarm {i + 1}");
}
Debug.Log("Threat Swarm Configurations:");
for (int i = 0; i < config.threat_swarm_configs.Count; i++)
{
PrintSwarmConfig(config.threat_swarm_configs[i], $"Threat Swarm {i + 1}");
}
}
private static void PrintSwarmConfig(SwarmConfig swarmConfig, string swarmName)
{
Debug.Log($"{swarmName}:");
Debug.Log($" Number of Agents: {swarmConfig.num_agents}");
PrintAgentConfig(swarmConfig.agent_config);
}
private static void PrintAgentConfig(AgentConfig agentConfig)
{
Debug.Log(" Agent Configuration:");
Debug.Log($" Interceptor Type: {agentConfig.interceptor_type}");
Debug.Log($" Threat Type: {agentConfig.threat_type}");
PrintInitialState(agentConfig.initial_state);
PrintStandardDeviation(agentConfig.standard_deviation);
PrintDynamicConfig(agentConfig.dynamic_config);
PrintPlottingConfig(agentConfig.plotting_config);
PrintSubmunitionsConfig(agentConfig.submunitions_config);
}
private static void PrintInitialState(InitialState initialState)
{
Debug.Log(" Initial State:");
Debug.Log($" Position: {initialState.position}");
Debug.Log($" Rotation: {initialState.rotation}");
Debug.Log($" Velocity: {initialState.velocity}");
}
private static void PrintStandardDeviation(StandardDeviation standardDeviation)
{
Debug.Log(" Standard Deviation:");
Debug.Log($" Position: {standardDeviation.position}");
Debug.Log($" Velocity: {standardDeviation.velocity}");
}
private static void PrintDynamicConfig(DynamicConfig dynamicConfig)
{
Debug.Log(" Dynamic Configuration:");
Debug.Log($" Launch Time: {dynamicConfig.launch_config.launch_time}");
Debug.Log($" Sensor Type: {dynamicConfig.sensor_config.type}");
Debug.Log($" Sensor Frequency: {dynamicConfig.sensor_config.frequency}");
}
private static void PrintPlottingConfig(PlottingConfig plottingConfig)
{
Debug.Log(" Plotting Configuration:");
Debug.Log($" Color: {plottingConfig.color}");
Debug.Log($" Line Style: {plottingConfig.linestyle}");
Debug.Log($" Marker: {plottingConfig.marker}");
}
private static void PrintSubmunitionsConfig(SubmunitionsConfig submunitionsConfig)
{
if (submunitionsConfig == null)
{
Debug.Log(" Submunitions Configuration: None");
return;
}
Debug.Log(" Submunitions Configuration:");
Debug.Log($" Number of Submunitions: {submunitionsConfig.num_submunitions}");
Debug.Log($" Launch Time: {submunitionsConfig.launch_config.launch_time}");
PrintSubmunitionAgentConfig(submunitionsConfig.agent_config);
}
private static void PrintSubmunitionAgentConfig(SubmunitionAgentConfig agentConfig)
{
Debug.Log(" Submunition Agent Configuration:");
Debug.Log($" Interceptor Type: {agentConfig.interceptor_type}");
PrintInitialState(agentConfig.initial_state);
PrintStandardDeviation(agentConfig.standard_deviation);
PrintDynamicConfig(agentConfig.dynamic_config);
PrintPlottingConfig(agentConfig.plotting_config);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: da3b9eead33b94a4f8df7a4af7a79d49
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
[Serializable]
public class SimulationConfig {
[Header("Simulation Settings")]
public float timeScale = 0.05f;
[Header("Interceptor Swarm Configurations")]
public List<SwarmConfig> interceptor_swarm_configs = new List<SwarmConfig>();
[Header("Threat Swarm Configurations")]
public List<SwarmConfig> threat_swarm_configs = new List<SwarmConfig>();
}
[Serializable]
public class DynamicConfig {
public LaunchConfig launch_config;
public SensorConfig sensor_config;
}
[Serializable]
public class SwarmConfig {
public int num_agents;
public AgentConfig agent_config;
}
[Serializable]
public class AgentConfig {
public InterceptorType interceptor_type;
public ThreatType threat_type;
public InitialState initial_state;
public StandardDeviation standard_deviation;
public DynamicConfig dynamic_config;
public PlottingConfig plotting_config;
public SubmunitionsConfig submunitions_config;
public static AgentConfig FromSubmunitionAgentConfig(SubmunitionAgentConfig submunitionConfig) {
return new AgentConfig {
interceptor_type = submunitionConfig.interceptor_type,
initial_state = submunitionConfig.initial_state,
standard_deviation = submunitionConfig.standard_deviation,
dynamic_config = submunitionConfig.dynamic_config,
plotting_config = submunitionConfig.plotting_config,
// Set other fields as needed, using default values if not present in SubmunitionAgentConfig
threat_type = ThreatType.DRONE, // Or another default value
submunitions_config = null // Or a default value if needed
};
}
}
[Serializable]
public class InitialState {
public Vector3 position;
public Vector3 rotation;
public Vector3 velocity;
}
[Serializable]
public class StandardDeviation {
public Vector3 position;
public Vector3 velocity;
}
[Serializable]
public class LaunchConfig {
public float launch_time;
}
[Serializable]
public class PlottingConfig {
public ConfigColor color;
public LineStyle linestyle;
public Marker marker;
}
[Serializable]
public class SubmunitionsConfig {
public int num_submunitions;
public LaunchConfig launch_config;
public SubmunitionAgentConfig agent_config;
}
[Serializable]
public class SubmunitionAgentConfig {
public InterceptorType interceptor_type;
public InitialState initial_state;
public StandardDeviation standard_deviation;
public DynamicConfig dynamic_config;
public PlottingConfig plotting_config;
}
[Serializable]
public class SensorConfig {
public SensorType type;
public float frequency;
}
[Serializable]
public class TargetConfig {
public ThreatType threat_type;
public InitialState initial_state;
public PlottingConfig plotting_config;
public string prefabName;
}
// Enums
[JsonConverter(typeof(StringEnumConverter))]
public enum InterceptorType { HYDRA_70, MICROMISSILE }
[JsonConverter(typeof(StringEnumConverter))]
public enum ThreatType { DRONE, ANTISHIP_MISSILE }
[JsonConverter(typeof(StringEnumConverter))]
public enum ConfigColor { BLUE, GREEN, RED }
[JsonConverter(typeof(StringEnumConverter))]
public enum LineStyle { DOTTED, SOLID }
[JsonConverter(typeof(StringEnumConverter))]
public enum Marker { TRIANGLE_UP, TRIANGLE_DOWN, SQUARE }
[JsonConverter(typeof(StringEnumConverter))]
public enum SensorType { IDEAL }

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 79f1fe138866d6a40b209a4edcf2ee06
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,43 @@
using System;
[Serializable]
public class StaticConfig {
[Serializable]
public class AccelerationConfig {
public float maxReferenceAcceleration = 300f;
public float referenceSpeed = 1000f;
}
[Serializable]
public class BoostConfig {
public float boostTime = 0.3f;
public float boostAcceleration = 350f;
}
[Serializable]
public class LiftDragConfig {
public float liftCoefficient = 0.2f;
public float dragCoefficient = 0.7f;
public float liftDragRatio = 5f;
}
[Serializable]
public class BodyConfig {
public float mass = 0.37f;
public float crossSectionalArea = 3e-4f;
public float finArea = 6e-4f;
public float bodyArea = 1e-2f;
}
[Serializable]
public class HitConfig {
public float hitRadius = 1f;
public float killProbability = 0.9f;
}
public AccelerationConfig accelerationConfig;
public BoostConfig boostConfig;
public LiftDragConfig liftDragConfig;
public BodyConfig bodyConfig;
public HitConfig hitConfig;
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 35d9cdcf93cb04b40a7538fc87071e3b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,17 @@
using System;
public static class Constants {
// Constants (these should be defined with appropriate values)
public const double kAirDensity = 1.204; // Sea level air density in kg/m^3
public const double kAirDensityScaleHeight = 10.4; // Scale height in km
public const double kGravity = 9.80665; // Standard gravity in m/s^2
public const double kEarthMeanRadius = 6378137; // Earth's mean radius in meters
public static double CalculateAirDensityAtAltitude(double altitude) {
return kAirDensity * Math.Exp(-altitude / (kAirDensityScaleHeight * 1000));
}
public static double CalculateGravityAtAltitude(double altitude) {
return kGravity * Math.Pow(kEarthMeanRadius / (kEarthMeanRadius + altitude), 2);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 52d6188ab9d8da8498db11ceddd027bf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 91594f3f9a39e6746879216d6dd0d4ec
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,144 @@
using UnityEngine;
using UnityEditor;
using System;
using System.Collections.Generic;
#if UNITY_EDITOR
public class GenerateCone : EditorWindow {
private int sides = 16;
private float baseRadius = 1f;
private float height = 2f;
[MenuItem("GameObject/3D Object/Cone", false, 10)]
static void CreateCone() {
GameObject cone;
GameObject selectedObject = Selection.activeGameObject;
if (selectedObject != null) {
// Create as child of selected object
cone = new GameObject("Cone");
cone.transform.SetParent(selectedObject.transform, false);
} else {
// Create as new root object
cone = new GameObject("Cone");
}
cone.AddComponent<MeshFilter>();
cone.AddComponent<MeshRenderer>();
Undo.RegisterCreatedObjectUndo(cone, "Create Cone");
var window = ScriptableObject.CreateInstance<GenerateCone>();
window.GenerateConeObject(cone);
Selection.activeGameObject = cone;
}
void GenerateConeObject(GameObject cone) {
Mesh mesh =
CreateConeMesh("ConeMesh", sides, Vector3.zero, Quaternion.identity, baseRadius, height);
// Save the mesh as an asset
string path = "Assets/Meshes";
if (!AssetDatabase.IsValidFolder(path)) {
AssetDatabase.CreateFolder("Assets", "Meshes");
}
string assetPath = AssetDatabase.GenerateUniqueAssetPath(path + "/ConeMesh.asset");
AssetDatabase.CreateAsset(mesh, assetPath);
AssetDatabase.SaveAssets();
// Assign the mesh to the MeshFilter
cone.GetComponent<MeshFilter>().sharedMesh = mesh;
cone.GetComponent<MeshRenderer>().material = new Material(Shader.Find("Standard"));
}
Vector2[] GetBasePoints(int vertices, float radius) {
const float TAU = 2f * Mathf.PI;
var pts = new Vector2[vertices];
var step = TAU / vertices; // angular step between two vertices
for (int i = 0; i < vertices; i++) {
pts[i] = radius * Trig(i * step); // convert polar coordinate to cartesian space
}
return pts;
}
static Vector2 Trig(float rad) => new Vector2(Mathf.Cos(rad), Mathf.Sin(rad));
Vector3[] BuildConeVertices(Vector2[] baseVerts, float coneHeight) {
if (baseVerts == null || baseVerts.Length < 3)
throw new InvalidOperationException("Requires at least 3 base vertices.");
var verts = new Vector3[baseVerts.Length + 1];
verts[0] = new Vector3(0f, coneHeight, 0f);
for (int i = 0; i < baseVerts.Length; i++) {
verts[i + 1] = new Vector3(baseVerts[i].x, 0f, baseVerts[i].y);
}
return verts;
}
void ConstructCone(Vector3[] coneVerts, List<Vector3> finalVerts, List<int> triangles) {
if (coneVerts == null || coneVerts.Length < 4)
throw new InvalidOperationException("Requires at least 4 vertices.");
if (finalVerts == null || triangles == null)
throw new ArgumentNullException();
finalVerts.Clear();
triangles.Clear();
var rimVertices = coneVerts.Length - 1;
// Side faces
for (int i = 1; i <= rimVertices; i++) {
int a = i, b = i < rimVertices ? i + 1 : 1;
AddTriangle(coneVerts[0], coneVerts[b], coneVerts[a]);
}
// Base face
for (int i = 1; i < rimVertices - 1; i++) {
AddTriangle(coneVerts[1], coneVerts[i + 1], coneVerts[i + 2]);
}
void AddTriangle(Vector3 t1, Vector3 t2, Vector3 t3) {
finalVerts.Add(t1);
finalVerts.Add(t2);
finalVerts.Add(t3);
triangles.Add(finalVerts.Count - 3);
triangles.Add(finalVerts.Count - 2);
triangles.Add(finalVerts.Count - 1);
}
}
Mesh CreateConeMesh(string name, int sides, Vector3 apex, Quaternion rotation, float baseRadius,
float height) {
var baseVerts = GetBasePoints(sides, baseRadius);
var coneVerts = BuildConeVertices(baseVerts, height);
var verts = new List<Vector3>();
var tris = new List<int>();
ConstructCone(coneVerts, verts, tris);
for (int i = 0; i < verts.Count; i++) {
verts[i] = rotation * (verts[i] - coneVerts[0]);
}
// Recenter the cone
Vector3 center = CalculateCenter(verts);
for (int i = 0; i < verts.Count; i++) {
verts[i] = verts[i] - center + apex;
}
Mesh mesh = new Mesh();
mesh.name = name;
mesh.SetVertices(verts);
mesh.SetTriangles(tris.ToArray(), 0);
mesh.RecalculateNormals();
return mesh;
}
Vector3 CalculateCenter(List<Vector3> vertices) {
Vector3 sum = Vector3.zero;
foreach (Vector3 vert in vertices) {
sum += vert;
}
return sum / vertices.Count;
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 27925dcc8d7b5dd4ab28ed7de5b806a4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9c6197c2ed2c6bc49b0667188a72a29d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,146 @@
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
// Integrated Air Defense System
public class IADS : MonoBehaviour {
public enum ThreatAssignmentStyle {
ONE_TIME,
CONTINUOUS
}
public static IADS Instance { get; private set; }
private IAssignment _assignmentScheme;
[SerializeField]
private List<ThreatData> _threatTable = new List<ThreatData>();
private Dictionary<Threat, ThreatData> _threatDataMap = new Dictionary<Threat, ThreatData>();
private List<Interceptor> _assignmentQueue = new List<Interceptor>();
private void Awake() {
if (Instance == null) {
Instance = this;
} else {
Destroy(gameObject);
}
}
private void Start() {
SimManager.Instance.OnSimulationEnded += RegisterSimulationEnded;
SimManager.Instance.OnNewThreat += RegisterNewThreat;
SimManager.Instance.OnNewInterceptor += RegisterNewInterceptor;
_assignmentScheme = new ThreatAssignment();
}
public void LateUpdate() {
if (_assignmentQueue.Count > 0) {
AssignInterceptorsToThreats(_assignmentQueue);
_assignmentQueue.Clear();
}
}
public void RequestThreatAssignment(List<Interceptor> interceptors) {
_assignmentQueue.AddRange(interceptors);
}
public void RequestThreatAssignment(Interceptor interceptor) {
_assignmentQueue.Add(interceptor);
}
/// <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 AssignInterceptorsToThreats(List<Interceptor> missilesToAssign) {
// Perform the assignment
IEnumerable<IAssignment.AssignmentItem> assignments =
_assignmentScheme.Assign(missilesToAssign, _threatTable);
// Apply the assignments to the missiles
foreach (var assignment in assignments) {
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}");
}
}
public void RegisterNewThreat(Threat threat) {
ThreatData threatData = new ThreatData(threat, threat.gameObject.name);
_threatTable.Add(threatData);
_threatDataMap.Add(threat, threatData);
// Subscribe to the threat's events
// TODO: If we do not want omniscient IADS, we
// need to model the IADS's sensors here.
threat.OnInterceptHit += RegisterThreatHit;
threat.OnInterceptMiss += RegisterThreatMiss;
}
public void RegisterNewInterceptor(Interceptor interceptor) {
// Placeholder
interceptor.OnInterceptMiss += RegisterInterceptorMiss;
interceptor.OnInterceptHit += RegisterInterceptorHit;
}
private void RegisterInterceptorHit(Interceptor interceptor, Threat threat) {
ThreatData threatData = _threatDataMap[threat];
if (threatData != null) {
threatData.RemoveInterceptor(interceptor);
MarkThreatDestroyed(threatData);
}
}
private void RegisterInterceptorMiss(Interceptor interceptor, Threat threat) {
// Remove the interceptor from the threat's assigned interceptors
_threatDataMap[threat].RemoveInterceptor(interceptor);
}
private void RegisterThreatHit(Interceptor interceptor, Threat threat) {
ThreatData threatData = _threatDataMap[threat];
if (threatData != null) {
threatData.RemoveInterceptor(interceptor);
MarkThreatDestroyed(threatData);
}
}
private void MarkThreatDestroyed(ThreatData threatData) {
if (threatData != null) {
threatData.MarkDestroyed();
}
}
private void RegisterThreatMiss(Interceptor interceptor, Threat threat) {
ThreatData threatData = _threatDataMap[threat];
threatData.RemoveInterceptor(interceptor);
}
private void RegisterSimulationEnded() {
_threatTable.Clear();
_threatDataMap.Clear();
_assignmentQueue.Clear();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a5899f1049cf3d64e8c06c1db772c879
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: adc0c5dbdb9dc7d498b50cf9a15c2db5

View File

@@ -0,0 +1,27 @@
using UnityEngine;
using System.Collections.Generic;
public class Vessel : MonoBehaviour {
[SerializeField]
private List<Interceptor> missileInventory = new List<Interceptor>();
public void AddInterceptor(Interceptor interceptor) {
if (interceptor != null) {
missileInventory.Add(interceptor);
}
}
public void RemoveInterceptor(Interceptor interceptor) {
missileInventory.Remove(interceptor);
}
public List<Interceptor> GetInterceptorInventory() {
return new List<Interceptor>(missileInventory);
}
public int GetInterceptorCount() {
return missileInventory.Count;
}
// Additional methods can be added here as needed
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 607b5f69ae2775c4ab0089839f65fd61
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,157 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Interceptor : Agent {
[SerializeField]
protected bool _showDebugVectors = true;
// Return whether a target can be assigned to the interceptor.
public override bool IsAssignable() {
bool assignable = !HasLaunched() && !HasAssignedTarget();
return assignable;
}
// Assign the given target to the interceptor.
public override void AssignTarget(Agent target) {
base.AssignTarget(target);
}
// Unassign the target from the interceptor.
public override void UnassignTarget() {
base.UnassignTarget();
}
protected override void UpdateReady(double deltaTime) {
Vector3 accelerationInput = Vector3.zero;
Vector3 acceleration = CalculateAcceleration(accelerationInput);
// GetComponent<Rigidbody>().AddForce(acceleration, ForceMode.Acceleration);
}
protected override void FixedUpdate() {
base.FixedUpdate();
if (_showDebugVectors) {
DrawDebugVectors();
}
}
protected override void UpdateBoost(double deltaTime) {
// The interceptor only accelerates along its roll axis (forward in Unity)
Vector3 rollAxis = transform.forward;
// Calculate boost acceleration
float boostAcceleration =
(float)(_staticConfig.boostConfig.boostAcceleration * Constants.kGravity);
Vector3 accelerationInput = boostAcceleration * rollAxis;
// Calculate the total acceleration
Vector3 acceleration = CalculateAcceleration(accelerationInput);
// Apply the acceleration force
GetComponent<Rigidbody>().AddForce(acceleration, ForceMode.Acceleration);
}
protected override void UpdateMidCourse(double deltaTime) {}
protected Vector3 CalculateAcceleration(Vector3 accelerationInput,
bool compensateForGravity = false) {
Vector3 gravity = Physics.gravity;
if (compensateForGravity) {
Vector3 gravityProjection = CalculateGravityProjectionOnPitchAndYaw();
accelerationInput -= gravityProjection;
}
float airDrag = CalculateDrag();
float liftInducedDrag = CalculateLiftInducedDrag(accelerationInput + gravity);
float dragAcceleration = -(airDrag + liftInducedDrag);
// Project the drag acceleration onto the forward direction
Vector3 dragAccelerationAlongRoll = dragAcceleration * transform.forward;
_dragAcceleration = dragAccelerationAlongRoll;
return accelerationInput + gravity + dragAccelerationAlongRoll;
}
private void OnTriggerEnter(Collider other) {
if (other.gameObject.name == "Floor") {
this.HandleInterceptMiss();
}
// Check if the collision is with another Agent
Agent otherAgent = other.gameObject.GetComponentInParent<Agent>();
if (otherAgent != null && otherAgent.GetComponent<Threat>() != null) {
// Check kill probability before marking as hit
float killProbability = _staticConfig.hitConfig.killProbability;
GameObject markerObject = Instantiate(Resources.Load<GameObject>("Prefabs/HitMarkerPrefab"),
transform.position, Quaternion.identity);
if (Random.value <= killProbability) {
// 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.HandleInterceptHit(otherAgent);
otherAgent.HandleInterceptHit(otherAgent);
} else {
// Set red for miss
markerObject.GetComponent<Renderer>().material.color = new Color(1, 0, 0, 0.15f);
this.HandleInterceptMiss();
// otherAgent.MarkAsMiss();
}
}
}
protected float CalculateMaxAcceleration() {
float maxReferenceAcceleration =
(float)(_staticConfig.accelerationConfig.maxReferenceAcceleration * Constants.kGravity);
float referenceSpeed = _staticConfig.accelerationConfig.referenceSpeed;
return Mathf.Pow(GetComponent<Rigidbody>().linearVelocity.magnitude / referenceSpeed, 2) *
maxReferenceAcceleration;
}
protected Vector3 CalculateGravityProjectionOnPitchAndYaw() {
Vector3 gravity = Physics.gravity;
Vector3 pitchAxis = transform.right;
Vector3 yawAxis = transform.up;
// Project the gravity onto the pitch and yaw axes
float gravityProjectionPitchCoefficient = Vector3.Dot(gravity, pitchAxis);
float gravityProjectionYawCoefficient = Vector3.Dot(gravity, yawAxis);
// Return the sum of the projections
return gravityProjectionPitchCoefficient * pitchAxis +
gravityProjectionYawCoefficient * yawAxis;
}
private float CalculateDrag() {
float dragCoefficient = _staticConfig.liftDragConfig.dragCoefficient;
float crossSectionalArea = _staticConfig.bodyConfig.crossSectionalArea;
float mass = _staticConfig.bodyConfig.mass;
float dynamicPressure = (float)GetDynamicPressure();
float dragForce = dragCoefficient * dynamicPressure * crossSectionalArea;
return dragForce / mass;
}
private float CalculateLiftInducedDrag(Vector3 accelerationInput) {
float liftAcceleration =
(accelerationInput - Vector3.Dot(accelerationInput, transform.up) * transform.up).magnitude;
float liftDragRatio = _staticConfig.liftDragConfig.liftDragRatio;
return Mathf.Abs(liftAcceleration / liftDragRatio);
}
protected virtual void DrawDebugVectors() {
if (_target != null) {
// Line of sight
Debug.DrawLine(transform.position, _target.transform.position, new Color(1, 1, 1, 0.15f));
// Velocity vector
Debug.DrawRay(transform.position, GetVelocity() * 0.01f, new Color(0, 0, 1, 0.15f));
// Current forward direction
Debug.DrawRay(transform.position, transform.forward * 5f, Color.yellow);
// Pitch axis (right)
Debug.DrawRay(transform.position, transform.right * 5f, Color.red);
// Yaw axis (up)
Debug.DrawRay(transform.position, transform.up * 5f, Color.magenta);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 932fd95d676a82349ab18bddf929d4bb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6f05ad8af46b8df4c9720b5deb691295
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,53 @@
using System.Collections;
using System.Collections.Generic;
using JetBrains.Annotations;
using UnityEngine;
public class Hydra70 : Interceptor {
private bool _submunitionsLaunched = false;
protected override void FixedUpdate() {
base.FixedUpdate();
// Check if it's time to launch submunitions
if (!_submunitionsLaunched &&
(GetFlightPhase() == FlightPhase.MIDCOURSE || GetFlightPhase() == FlightPhase.BOOST) &&
SimManager.Instance.GetElapsedSimulationTime() >=
_agentConfig.submunitions_config.launch_config.launch_time) {
SpawnSubmunitions();
_submunitionsLaunched = true;
}
}
protected override void UpdateMidCourse(double deltaTime) {
Vector3 accelerationInput = Vector3.zero;
// Calculate and set the total acceleration
Vector3 acceleration = CalculateAcceleration(accelerationInput);
GetComponent<Rigidbody>().AddForce(acceleration, ForceMode.Acceleration);
}
protected override void DrawDebugVectors() {
base.DrawDebugVectors();
if (_acceleration != null) {
Debug.DrawRay(transform.position, _acceleration * 1f, Color.green);
}
}
public void SpawnSubmunitions() {
List<Interceptor> submunitions = new List<Interceptor>();
switch (_agentConfig.submunitions_config.agent_config.interceptor_type) {
case InterceptorType.MICROMISSILE:
for (int i = 0; i < _agentConfig.submunitions_config.num_submunitions; i++) {
AgentConfig convertedConfig =
AgentConfig.FromSubmunitionAgentConfig(_agentConfig.submunitions_config.agent_config);
convertedConfig.initial_state.position = transform.position;
convertedConfig.initial_state.velocity = GetComponent<Rigidbody>().linearVelocity;
Interceptor submunition = SimManager.Instance.CreateInterceptor(convertedConfig);
submunitions.Add(submunition);
}
break;
}
IADS.Instance.RequestThreatAssignment(submunitions);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 403a04456de34694a8946c6a5084a9cc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,78 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Micromissile : Interceptor {
[SerializeField]
private float _navigationGain = 3f; // Typically 3-5
private SensorOutput _sensorOutput;
private Vector3 _accelerationCommand;
private double _elapsedTime = 0;
protected override void UpdateMidCourse(double deltaTime) {
_elapsedTime += deltaTime;
Vector3 accelerationInput = Vector3.zero;
if (HasAssignedTarget()) {
// Update the threat model (assuming we have a threat model)
// TODO: Implement threat model update logic
// Correct the state of the threat model at the sensor frequency
float sensorUpdatePeriod = 1f / _agentConfig.dynamic_config.sensor_config.frequency;
if (_elapsedTime >= sensorUpdatePeriod) {
// TODO: Implement guidance filter to estimate state from sensor output
// For now, we'll use the threat's actual state
_sensorOutput = GetComponent<Sensor>().Sense(_target);
_elapsedTime = 0;
}
// Check whether the threat should be considered a miss
SensorOutput sensorOutput = GetComponent<Sensor>().Sense(_target);
if (sensorOutput.velocity.range > 1000f) {
this.HandleInterceptMiss();
}
// Calculate the acceleration input
accelerationInput = CalculateAccelerationCommand(_sensorOutput);
}
// Calculate and set the total acceleration
Vector3 acceleration = CalculateAcceleration(accelerationInput, compensateForGravity: true);
GetComponent<Rigidbody>().AddForce(acceleration, ForceMode.Acceleration);
}
private Vector3 CalculateAccelerationCommand(SensorOutput sensorOutput) {
// Implement Proportional Navigation guidance law
Vector3 accelerationCommand = Vector3.zero;
// Extract relevant information from sensor output
float los_rate_az = sensorOutput.velocity.azimuth;
float los_rate_el = sensorOutput.velocity.elevation;
float closing_velocity =
-sensorOutput.velocity
.range; // Negative because closing velocity is opposite to range rate
// Navigation gain (adjust as needed)
float N = _navigationGain;
// Calculate acceleration commands in azimuth and elevation planes
float acc_az = N * closing_velocity * los_rate_az;
float acc_el = N * closing_velocity * los_rate_el;
// Convert acceleration commands to craft body frame
accelerationCommand = transform.right * acc_az + transform.up * acc_el;
// Clamp the acceleration command to the maximum acceleration
float maxAcceleration = CalculateMaxAcceleration();
accelerationCommand = Vector3.ClampMagnitude(accelerationCommand, maxAcceleration);
// Update the stored acceleration command for debugging
_accelerationCommand = accelerationCommand;
return accelerationCommand;
}
protected override void DrawDebugVectors() {
base.DrawDebugVectors();
if (_accelerationCommand != null) {
Debug.DrawRay(transform.position, _accelerationCommand * 1f, Color.green);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 864f4855030b29d44a944391ce02342a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9411e01e9e0fecb45ac53c53b65fa917
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,194 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
public class InputManager : MonoBehaviour
{
public static InputManager Instance { get; private set; }
public bool mouseActive = true;
[System.Serializable]
public struct CameraPreset
{
public Vector3 position;
public Quaternion rotation;
}
public bool lockUserInput = false;
private void Awake()
{
if (Instance == null) {
Instance = this;
DontDestroyOnLoad(gameObject);
} else {
Destroy(gameObject);
}
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
HandleInput();
}
private void HandleMouseInput()
{
if (Input.GetMouseButton(0))
{
CameraController.Instance.OrbitCamera(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"));
}
else if (Input.GetMouseButton(1))
{
CameraController.Instance.RotateCamera(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"));
}
}
private void HandleInput()
{
if (!lockUserInput)
{
HandleLockableInput();
}
HandleNonLockableInput();
}
void HandleScrollWheelInput()
{
if (Input.GetAxis("Mouse ScrollWheel") != 0)
{
CameraController.Instance.ZoomCamera(Input.GetAxis("Mouse ScrollWheel") * 500);
}
}
void HandleLockableInput()
{
if(mouseActive)
{
HandleMouseInput();
}
if (Input.GetKey(KeyCode.LeftShift))
{
CameraController.Instance.SetCameraSpeed(CameraController.Instance.GetCameraSpeedMax());
}
else
{
CameraController.Instance.SetCameraSpeed(CameraController.Instance.GetCameraSpeedNormal());
}
// TRANSLATIONAL MOVEMENT
if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow))
{
CameraController.Instance.TranslateCamera(CameraController.TranslationInput.Forward);
}
if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow))
{
CameraController.Instance.TranslateCamera(CameraController.TranslationInput.Left);
}
if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow))
{
CameraController.Instance.TranslateCamera(CameraController.TranslationInput.Back);
}
if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow))
{
CameraController.Instance.TranslateCamera(CameraController.TranslationInput.Right);
}
if (Input.GetKey(KeyCode.Q))
{
CameraController.Instance.TranslateCamera(CameraController.TranslationInput.Up);
}
if (Input.GetKey(KeyCode.E))
{
CameraController.Instance.TranslateCamera(CameraController.TranslationInput.Down);
}
}
void HandleNonLockableInput()
{
HandleScrollWheelInput();
if(Input.GetKeyDown(KeyCode.Escape))
{
Application.Quit();
}
if (Input.GetKeyDown(KeyCode.C))
{
}
if(Input.GetKeyDown(KeyCode.R))
{
SimManager.Instance.RestartSimulation();
}
if (Input.GetKeyDown(KeyCode.L)) // 'L' for Load
{
UIManager.Instance.ToggleConfigSelectorPanel();
}
if (Input.GetKeyDown(KeyCode.Space))
{
// Pause the time
if (SimManager.Instance.IsSimulationRunning()) {
SimManager.Instance.PauseSimulation();
} else {
SimManager.Instance.ResumeSimulation();
}
}
if (Input.GetKeyDown(KeyCode.P))
{
CameraController.Instance.SetAutoRotate(!CameraController.Instance.IsAutoRotate());
}
if (Input.GetKeyDown(KeyCode.Alpha1))
{
// ORIGIN
// Set pre-set view 1
}
if (Input.GetKeyDown(KeyCode.Alpha2))
{
// Set pre-set view 2
}
if (Input.GetKeyDown(KeyCode.Alpha4))
{
// Set pre-set view 4
}
if (Input.GetKeyDown(KeyCode.Alpha5))
{
// Set pre-set view 5
}
if (Input.GetKeyDown(KeyCode.Alpha6))
{
// Set pre-set view 6
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6a028bd12cca23f45a9eeb9fb0c4d1f1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,263 @@
using UnityEngine;
using System.Collections;
using System.IO;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading;
public class SimMonitor : MonoBehaviour
{
private const float _updateRate = 0.1f; // 100 Hz
private string _telemetryBinPath;
private string _eventLogPath;
private Coroutine _monitorRoutine;
private string _sessionDirectory;
private FileStream _telemetryFileStream;
private BinaryWriter _telemetryBinaryWriter;
[SerializeField]
private List<EventRecord> _eventLogCache;
[System.Serializable]
private class EventRecord
{
public float Time;
public float PositionX;
public float PositionY;
public float PositionZ;
public string EventType;
public string Details;
}
private void Awake() {
InitializeSessionDirectory();
}
private void Start()
{
SimManager.Instance.OnSimulationStarted += RegisterSimulationStarted;
SimManager.Instance.OnSimulationEnded += RegisterSimulationEnded;
SimManager.Instance.OnNewThreat += RegisterNewThreat;
SimManager.Instance.OnNewInterceptor += RegisterNewInterceptor;
}
private void InitializeSessionDirectory() {
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
_sessionDirectory = Application.persistentDataPath + $"\\Telemetry\\Logs\\{timestamp}";
Directory.CreateDirectory(_sessionDirectory);
Debug.Log($"Monitoring simulation logs to {_sessionDirectory}");
}
private void InitializeLogFiles()
{
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
_eventLogPath = Path.Combine(_sessionDirectory, $"sim_events_{timestamp}.csv");
// Initialize the event log cache
_eventLogCache = new List<EventRecord>();
_telemetryBinPath = Path.Combine(_sessionDirectory, $"sim_telemetry_{timestamp}.bin");
// Open the file stream and binary writer for telemetry data
_telemetryFileStream = new FileStream(_telemetryBinPath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
_telemetryBinaryWriter = new BinaryWriter(_telemetryFileStream);
Debug.Log("Log files initialized successfully.");
}
private void CloseLogFiles() {
if (_telemetryBinaryWriter != null)
{
_telemetryBinaryWriter.Flush();
_telemetryBinaryWriter.Close();
_telemetryBinaryWriter = null;
}
if (_telemetryFileStream != null)
{
_telemetryFileStream.Close();
_telemetryFileStream = null;
}
}
private IEnumerator MonitorRoutine()
{
while (true)
{
RecordTelemetry();
yield return new WaitForSeconds(_updateRate);
}
}
private void RecordTelemetry()
{
float time = (float)SimManager.Instance.GetElapsedSimulationTime();
var agents = SimManager.Instance.GetActiveAgents();
if(_telemetryBinaryWriter == null) {
Debug.LogWarning("Telemetry binary writer is null");
return;
}
for (int i = 0; i < agents.Count; i++)
{
var agent = agents[i];
if (!agent.gameObject.activeInHierarchy)
continue;
Vector3 pos = agent.transform.position;
if (pos == Vector3.zero)
continue;
Vector3 vel = agent.GetVelocity(); // Ensure GetVelocity() doesn't allocate
int agentID = agent.GetInstanceID();
int flightPhase = (int)agent.GetFlightPhase();
byte agentType = (byte)(agent is Threat ? 0 : 1);
// Write telemetry data directly to the binary file
_telemetryBinaryWriter.Write(time);
_telemetryBinaryWriter.Write(agentID);
_telemetryBinaryWriter.Write(pos.x);
_telemetryBinaryWriter.Write(pos.y);
_telemetryBinaryWriter.Write(pos.z);
_telemetryBinaryWriter.Write(vel.x);
_telemetryBinaryWriter.Write(vel.y);
_telemetryBinaryWriter.Write(vel.z);
_telemetryBinaryWriter.Write(flightPhase);
_telemetryBinaryWriter.Write(agentType);
}
}
public void ConvertBinaryTelemetryToCsv(string binaryFilePath, string csvFilePath)
{
try
{
using FileStream fs = new FileStream(binaryFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using BinaryReader reader = new BinaryReader(fs);
using StreamWriter writer = new StreamWriter(csvFilePath, false);
{
// Write CSV header
writer.WriteLine("Time,AgentID,AgentX,AgentY,AgentZ,AgentVX,AgentVY,AgentVZ,AgentState,AgentType");
while (reader.BaseStream.Position != reader.BaseStream.Length)
{
float time = reader.ReadSingle();
int agentID = reader.ReadInt32();
float posX = reader.ReadSingle();
float posY = reader.ReadSingle();
float posZ = reader.ReadSingle();
float velX = reader.ReadSingle();
float velY = reader.ReadSingle();
float velZ = reader.ReadSingle();
int flightPhase = reader.ReadInt32();
byte agentTypeByte = reader.ReadByte();
string agentType = agentTypeByte == 0 ? "T" : "M";
// Write the data to CSV
writer.WriteLine($"{time:F2},{agentID},{posX:F2},{posY:F2},{posZ:F2},{velX:F2},{velY:F2},{velZ:F2},{flightPhase},{agentType}");
}
}
}
catch (IOException e)
{
Debug.LogWarning($"An IO error occurred while converting binary telemetry to CSV: {e.Message}");
}
}
private void WriteEventsToFile()
{
using (StreamWriter writer = new StreamWriter(_eventLogPath, false))
{
// Write CSV header
writer.WriteLine("Time,PositionX,PositionY,PositionZ,Event,Details");
foreach (var record in _eventLogCache)
{
writer.WriteLine($"{record.Time:F2},{record.PositionX:F2},{record.PositionY:F2},{record.PositionZ:F2},{record.EventType},{record.Details}");
}
}
}
private void RegisterSimulationStarted()
{
InitializeLogFiles();
_monitorRoutine = StartCoroutine(MonitorRoutine());
}
private void RegisterSimulationEnded()
{
StopCoroutine(_monitorRoutine);
CloseLogFiles();
WriteEventsToFile();
StartCoroutine(ConvertBinaryTelemetryToCsvCoroutine(_telemetryBinPath, Path.ChangeExtension(_telemetryBinPath, ".csv")));
}
private IEnumerator ConvertBinaryTelemetryToCsvCoroutine(string binaryFilePath, string csvFilePath)
{
yield return null; // Wait for the next frame to ensure RecordTelemetry() has finished
ConvertBinaryTelemetryToCsv(binaryFilePath, csvFilePath);
}
public void RegisterNewThreat(Threat threat) {
RegisterNewAgent(threat, "NEW_THREAT");
}
public void RegisterNewInterceptor(Interceptor interceptor) {
RegisterNewAgent(interceptor, "NEW_INTERCEPTOR");
interceptor.OnInterceptMiss += RegisterInterceptorMiss;
interceptor.OnInterceptHit += RegisterInterceptorHit;
}
private void RegisterNewAgent(Agent agent, string eventType)
{
float time = (float)SimManager.Instance.GetElapsedSimulationTime();
Vector3 pos = agent.transform.position;
var record = new EventRecord
{
Time = time,
PositionX = pos.x,
PositionY = pos.y,
PositionZ = pos.z,
EventType = eventType,
Details = agent.name
};
_eventLogCache.Add(record);
}
public void RegisterInterceptorHit(Interceptor interceptor, Threat threat) {
RegisterInterceptEvent(interceptor, threat, true);
}
public void RegisterInterceptorMiss(Interceptor interceptor, Threat threat) {
RegisterInterceptEvent(interceptor, threat, false);
}
public void RegisterInterceptEvent(Interceptor interceptor, Threat threat, bool hit)
{
float time = (float)SimManager.Instance.GetElapsedSimulationTime();
Vector3 pos = interceptor.transform.position;
string eventType = hit ? "HIT" : "MISS";
var record = new EventRecord
{
Time = time,
PositionX = pos.x,
PositionY = pos.y,
PositionZ = pos.z,
EventType = eventType,
Details = $"{interceptor.name} and {threat.name}"
};
_eventLogCache.Add(record);
}
private void OnDestroy()
{
CloseLogFiles();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d822a6be5cb96f54ca63228eeb5ebf23
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: -5
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8c45dceac2c26594d8ac013a8f72e042
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,71 @@
using UnityEngine;
public class IdealSensor : Sensor {
protected override void Start() {
base.Start();
}
public override SensorOutput Sense(Agent target) {
SensorOutput targetSensorOutput = new SensorOutput();
// Sense the target's position
PositionOutput targetPositionSensorOutput = SensePosition(target);
targetSensorOutput.position = targetPositionSensorOutput;
// Sense the target's velocity
VelocityOutput targetVelocitySensorOutput = SenseVelocity(target);
targetSensorOutput.velocity = targetVelocitySensorOutput;
return targetSensorOutput;
}
protected override PositionOutput SensePosition(Agent target) {
PositionOutput positionSensorOutput = new PositionOutput();
// Calculate the relative position of the target
Vector3 relativePosition = target.transform.position - transform.position;
// Calculate the distance (range) to the target
positionSensorOutput.range = relativePosition.magnitude;
// Calculate azimuth (horizontal angle relative to forward)
positionSensorOutput.azimuth =
Vector3.SignedAngle(transform.forward, relativePosition, transform.up);
// Calculate elevation (vertical angle relative to forward)
Vector3 flatRelativePosition = Vector3.ProjectOnPlane(relativePosition, transform.up);
positionSensorOutput.elevation =
Vector3.SignedAngle(flatRelativePosition, relativePosition, transform.right);
return positionSensorOutput;
}
protected override VelocityOutput SenseVelocity(Agent target) {
VelocityOutput velocitySensorOutput = new VelocityOutput();
// Calculate relative position and velocity
Vector3 relativePosition = target.transform.position - transform.position;
Vector3 relativeVelocity = target.GetVelocity() - GetComponent<Rigidbody>().linearVelocity;
// Calculate range rate (radial velocity)
velocitySensorOutput.range = Vector3.Dot(relativeVelocity, relativePosition.normalized);
// Project relative velocity onto a plane perpendicular to relative position
Vector3 tangentialVelocity =
Vector3.ProjectOnPlane(relativeVelocity, relativePosition.normalized);
// Calculate azimuth rate
Vector3 horizontalVelocity = Vector3.ProjectOnPlane(tangentialVelocity, transform.up);
velocitySensorOutput.azimuth =
Vector3.Dot(horizontalVelocity, transform.right) / relativePosition.magnitude;
// Calculate elevation rate
Vector3 verticalVelocity = Vector3.Project(tangentialVelocity, transform.up);
velocitySensorOutput.elevation = verticalVelocity.magnitude / relativePosition.magnitude;
if (Vector3.Dot(verticalVelocity, transform.up) < 0) {
velocitySensorOutput.elevation *= -1;
}
return velocitySensorOutput;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f9b285afc8c96ee4f9ca22836fd105d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,70 @@
using UnityEngine;
public abstract class Sensor : MonoBehaviour {
protected Agent _agent;
protected virtual void Start() {
_agent = GetComponent<Agent>();
}
/// <summary>
/// Main sensing method to gather information about a target agent.
/// </summary>
/// <param name="target">The agent to sense.</param>
/// <returns>SensorOutput containing position and velocity data.</returns>
/// <remarks>
/// Implementers should:
/// 1. Call SensePosition to get position data.
/// 2. Call SenseVelocity to get velocity data.
/// 3. Combine results into a SensorOutput struct.
/// </remarks>
public abstract SensorOutput Sense(Agent target);
/// <summary>
/// Calculates the relative position of the target agent.
/// </summary>
/// <param name="target">The agent to sense.</param>
/// <returns>PositionOutput containing range, azimuth, and elevation.</returns>
/// <remarks>
/// Implementers should calculate:
/// - range: Distance to the target (in unity units).
/// - azimuth: Horizontal angle to the target (in degrees).
/// Positive is clockwise from the forward direction.
/// - elevation: Vertical angle to the target (in degrees).
/// Positive is above the horizontal plane.
/// </remarks>
protected abstract PositionOutput SensePosition(Agent target);
/// <summary>
/// Calculates the relative velocity of the target agent.
/// </summary>
/// <param name="target">The agent to sense.</param>
/// <returns>VelocityOutput containing range rate, azimuth rate, and elevation rate.</returns>
/// <remarks>
/// Implementers should calculate:
/// - range: Radial velocity (closing speed) in units/second.
/// Positive means the target is moving away.
/// - azimuth: Rate of change of azimuth in degrees/second.
/// Positive means the target is moving clockwise.
/// - elevation: Rate of change of elevation in degrees/second.
/// Positive means the target is moving upwards.
/// </remarks>
protected abstract VelocityOutput SenseVelocity(Agent target);
}
public struct SensorOutput {
public PositionOutput position;
public VelocityOutput velocity;
}
public struct PositionOutput {
public float range;
public float azimuth;
public float elevation;
}
public struct VelocityOutput {
public float range;
public float azimuth;
public float elevation;
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 285bc14e5585a804b9bc44b10b8c3cb1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,326 @@
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<Interceptor> _activeInterceptors = new List<Interceptor>();
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;
public delegate void SimulationEventHandler();
public event SimulationEventHandler OnSimulationEnded;
public event SimulationEventHandler OnSimulationStarted;
public delegate void NewThreatEventHandler(Threat threat);
public event NewThreatEventHandler OnNewThreat;
public delegate void NewInterceptorEventHandler(Interceptor interceptor);
public event NewInterceptorEventHandler OnNewInterceptor;
/// <summary>
/// Gets the elapsed simulation time.
/// </summary>
/// <returns>The elapsed time in seconds.</returns>
public double GetElapsedSimulationTime() {
return _elapsedSimulationTime;
}
public List<Interceptor> GetActiveInterceptors() {
return _activeInterceptors;
}
public List<Threat> GetActiveThreats() {
return _threatObjects.Where(threat => !threat.IsHit()).ToList();
}
public List<Agent> GetActiveAgents() {
return _activeInterceptors.ConvertAll(interceptor => interceptor as Agent)
.Concat(GetActiveThreats().ConvertAll(threat => threat as Agent))
.ToList();
}
void Awake() {
// Ensure only one instance of SimManager exists
if (Instance == null) {
Instance = this;
DontDestroyOnLoad(gameObject);
} else {
Destroy(gameObject);
}
simulationConfig = ConfigLoader.LoadSimulationConfig("1_salvo_1_hydra_7_drones.json");
Debug.Log(simulationConfig);
}
void Start() {
// Slow down time by simulationConfig.timeScale
if (Instance == this) {
StartSimulation();
ResumeSimulation();
}
}
public void SetTimeScale(float timeScale) {
Time.timeScale = timeScale;
Time.fixedDeltaTime = Time.timeScale * 0.02f;
Time.maximumDeltaTime = Time.timeScale * 0.15f;
}
public void StartSimulation() {
OnSimulationStarted?.Invoke();
InitializeSimulation();
}
public void PauseSimulation() {
SetTimeScale(0);
simulationRunning = false;
}
public void ResumeSimulation() {
SetTimeScale(simulationConfig.timeScale);
simulationRunning = true;
}
public bool IsSimulationRunning() {
return simulationRunning;
}
private void InitializeSimulation() {
// Invoke the simulation started event to let listeners
// know to invoke their own handler behavior
OnSimulationStarted?.Invoke();
List<Interceptor> missiles = new List<Interceptor>();
// Create missiles based on config
foreach (var swarmConfig in simulationConfig.interceptor_swarm_configs) {
for (int i = 0; i < swarmConfig.num_agents; i++) {
CreateInterceptor(swarmConfig.agent_config);
}
}
List<Threat> targets = new List<Threat>();
// Create targets based on config
foreach (var swarmConfig in simulationConfig.threat_swarm_configs) {
for (int i = 0; i < swarmConfig.num_agents; i++) {
CreateThreat(swarmConfig.agent_config);
}
}
}
public void AssignInterceptorsToThreats() {
IADS.Instance.AssignInterceptorsToThreats(_interceptorObjects);
}
public void RegisterInterceptorHit(Interceptor interceptor, Threat threat) {
if (interceptor is Interceptor missileComponent) {
_activeInterceptors.Remove(missileComponent);
}
}
public void RegisterInterceptorMiss(Interceptor interceptor, Threat threat) {
if (interceptor is Interceptor missileComponent) {
_activeInterceptors.Remove(missileComponent);
}
}
public void RegisterThreatHit(Interceptor interceptor, Threat threat) {
// Placeholder
}
public void RegisterThreatMiss(Interceptor interceptor, Threat threat) {
Debug.Log($"RegisterThreatMiss: Interceptor {interceptor.name} missed threat {threat.name}");
// Placeholder
}
/// <summary>
/// Creates a interceptor based on the provided configuration.
/// </summary>
/// <param name="config">Configuration settings for the interceptor.</param>
/// <returns>The created Interceptor instance, or null if creation failed.</returns>
public Interceptor CreateInterceptor(AgentConfig config) {
string prefabName = config.interceptor_type switch { InterceptorType.HYDRA_70 => "Hydra70",
InterceptorType.MICROMISSILE => "Micromissile",
_ => "Hydra70" };
GameObject interceptorObject = CreateAgent(config, prefabName);
if (interceptorObject == null)
return null;
// Interceptor-specific logic
switch (config.dynamic_config.sensor_config.type) {
case SensorType.IDEAL:
interceptorObject.AddComponent<IdealSensor>();
break;
default:
Debug.LogError($"Sensor type '{config.dynamic_config.sensor_config.type}' not found.");
break;
}
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 interceptorId = _interceptorObjects.Count;
interceptorObject.name = $"{config.interceptor_type}_Interceptor_{interceptorId}";
// Let listeners know a new interceptor has been created
OnNewInterceptor?.Invoke(interceptor);
return interceptorObject.GetComponent<Interceptor>();
}
/// <summary>
/// Creates a threat based on the provided configuration.
/// </summary>
/// <param name="config">Configuration settings for the threat.</param>
/// <returns>The created Threat instance, or null if creation failed.</returns>
private Threat CreateThreat(AgentConfig config) {
string prefabName = config.threat_type switch {
ThreatType.DRONE => "Drone", ThreatType.ANTISHIP_MISSILE => "AntishipMissile",
_ => throw new System.ArgumentException($"Unsupported threat type: {config.threat_type}")
};
GameObject threatObject = CreateAgent(config, prefabName);
if (threatObject == null)
return null;
Threat threat = threatObject.GetComponent<Threat>();
// Assign a unique and simple ID
int targetId = _threatObjects.Count;
threatObject.name = $"{config.threat_type}_Target_{targetId}";
ThreatData threatData = new ThreatData(threat, threatObject.name);
_threatObjects.Add(threat);
// Subscribe events
threat.OnInterceptHit += RegisterThreatHit;
threat.OnInterceptMiss += RegisterThreatMiss;
// Let listeners know a new threat has been created
OnNewThreat?.Invoke(threat);
return threatObject.GetComponent<Threat>();
}
/// <summary>
/// Creates an agent (interceptor or threat) 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.linearVelocity = 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 = IsSimulationRunning();
// Clear existing interceptors and threats
foreach (var interceptor in _interceptorObjects) {
if (interceptor != null) {
Destroy(interceptor.gameObject);
}
}
foreach (var threat in _threatObjects) {
if (threat != null) {
Destroy(threat.gameObject);
}
}
_interceptorObjects.Clear();
_activeInterceptors.Clear();
_threatObjects.Clear();
StartSimulation();
}
void Update() {
// Check if all missiles have terminated
bool allInterceptorsTerminated = true;
foreach (var interceptor in _interceptorObjects) {
if (interceptor != null && interceptor.GetFlightPhase() != Agent.FlightPhase.TERMINATED) {
allInterceptorsTerminated = false;
break;
}
}
// If all missiles have terminated, restart the simulation
if (allInterceptorsTerminated) {
RestartSimulation();
}
if (simulationRunning && _elapsedSimulationTime < endTime) {
_elapsedSimulationTime += Time.deltaTime;
} else if (_elapsedSimulationTime >= endTime) {
simulationRunning = false;
Debug.Log("Simulation completed.");
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 84f2990fd69b0284ca96912cbe968b62
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 100
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2342a881813cdd645962533af3f6f755
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AntishipMissile : MonoBehaviour {
// Start is called before the first frame update
void Start() {}
// Update is called once per frame
void Update() {}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 16e40584b2154ef4a95c84e85a321999
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DroneTarget : Threat {
// Start is called before the first frame update
protected override void Start() {
base.Start();
}
// Update is called once per frame
protected override void FixedUpdate() {
base.FixedUpdate();
}
protected override void UpdateReady(double deltaTime) {}
protected override void UpdateBoost(double deltaTime) {}
protected override void UpdateMidCourse(double deltaTime) {}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f532f6ee629e87643b7b75a50e83083f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,17 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class Threat : Agent {
public override bool IsAssignable() {
return false;
}
protected override void Start() {
base.Start();
}
protected override void FixedUpdate() {
base.FixedUpdate();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ad412917d9c68a144b3124e83ca083c1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c17270294c7dd6d45b8731a468423a83
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,490 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraController : MonoBehaviour
{
#region Singleton
/// <summary>
/// Singleton instance of the CameraController.
/// </summary>
public static CameraController Instance { get; private set; }
#endregion
#region Camera Settings
/// <summary>
/// Determines if mouse input is active for camera control.
/// </summary>
public bool mouseActive = true;
/// <summary>
/// Locks user input for camera control.
/// </summary>
public bool lockUserInput = false;
/// <summary>
/// Normal speed of camera movement.
/// </summary>
[SerializeField] private float _cameraSpeedNormal = 100.0f;
/// <summary>
/// Maximum speed of camera movement.
/// </summary>
[SerializeField] private float _cameraSpeedMax = 1000.0f;
/// <summary>
/// Current speed of camera movement.
/// </summary>
private float _cameraSpeed;
/// <summary>
/// Horizontal rotation speed.
/// </summary>
public float _speedH = 2.0f;
/// <summary>
/// Vertical rotation speed.
/// </summary>
public float _speedV = 2.0f;
/// <summary>
/// Current yaw angle of the camera.
/// </summary>
private float _yaw = 0.0f;
/// <summary>
/// Current pitch angle of the camera.
/// </summary>
private float _pitch = 0.0f;
#endregion
#region Orbit Settings
/// <summary>
/// Determines if the camera should auto-rotate.
/// </summary>
public bool _autoRotate = false;
/// <summary>
/// Threat transform for orbit rotation.
/// </summary>
public Transform target;
/// <summary>
/// Distance from the camera to the orbit target.
/// </summary>
[SerializeField] private float _orbitDistance = 5.0f;
/// <summary>
/// Horizontal orbit rotation speed.
/// </summary>
[SerializeField] private float _orbitXSpeed = 120.0f;
/// <summary>
/// Vertical orbit rotation speed.
/// </summary>
[SerializeField] private float _orbitYSpeed = 120.0f;
/// <summary>
/// Speed of camera zoom.
/// </summary>
[SerializeField] private float _zoomSpeed = 500.0f;
/// <summary>
/// Minimum vertical angle limit for orbit.
/// </summary>
public float orbitYMinLimit = -20f;
/// <summary>
/// Maximum vertical angle limit for orbit.
/// </summary>
public float orbitYMaxLimit = 80f;
/// <summary>
/// Minimum distance for orbit.
/// </summary>
private float _orbitDistanceMin = 10f;
/// <summary>
/// Maximum distance for orbit.
/// </summary>
[SerializeField]
private float _orbitDistanceMax = 20000f;
/// <summary>
/// Current horizontal orbit angle.
/// </summary>
private float _orbitX = 0.0f;
/// <summary>
/// Current vertical orbit angle.
/// </summary>
private float _orbitY = 0.0f;
#endregion
#region Rendering
/// <summary>
/// Renderer for the orbit target.
/// </summary>
public Renderer targetRenderer;
/// <summary>
/// Renderer for the floor.
/// </summary>
public Renderer floorRenderer;
/// <summary>
/// Alpha value for material transparency.
/// </summary>
public float matAlpha;
#endregion
#region Autoplay Settings
/// <summary>
/// Speed of camera movement during autoplay.
/// </summary>
public float autoplayCamSpeed = 2f;
/// <summary>
/// Duration of horizontal auto-rotation.
/// </summary>
public float xAutoRotateTime = 5f;
/// <summary>
/// Duration of vertical auto-rotation.
/// </summary>
public float yAutoRotateTime = 5f;
/// <summary>
/// Coroutine for autoplay functionality.
/// </summary>
private Coroutine autoplayRoutine;
#endregion
#region Camera Presets
/// <summary>
/// Represents a preset camera position and rotation.
/// </summary>
[System.Serializable]
public struct CameraPreset
{
public Vector3 position;
public Quaternion rotation;
}
/// <summary>
/// Preset camera position for key 4.
/// </summary>
CameraPreset fourPos = new CameraPreset();
/// <summary>
/// Preset camera position for key 5.
/// </summary>
CameraPreset fivePos = new CameraPreset();
/// <summary>
/// Preset camera position for key 6.
/// </summary>
CameraPreset sixPos = new CameraPreset();
#endregion
#region Movement
/// <summary>
/// Mapping of translation inputs to movement vectors.
/// </summary>
private Dictionary<TranslationInput, Vector3> _translationInputToVectorMap;
/// <summary>
/// Forward movement vector.
/// </summary>
Vector3 wVector = Vector3.forward;
/// <summary>
/// Left movement vector.
/// </summary>
Vector3 aVector = Vector3.left;
/// <summary>
/// Backward movement vector.
/// </summary>
Vector3 sVector = Vector3.back;
/// <summary>
/// Right movement vector.
/// </summary>
Vector3 dVector = Vector3.right;
/// <summary>
/// Angle between forward vector and camera direction.
/// </summary>
public float forwardToCameraAngle;
#endregion
void SetCameraRotation(Quaternion rotation)
{
transform.rotation = rotation;
_pitch = rotation.eulerAngles.x;
_yaw = rotation.eulerAngles.y;
}
public void SetCameraSpeed(float speed)
{
_cameraSpeed = speed;
}
public float GetCameraSpeedMax()
{
return _cameraSpeedMax;
}
public float GetCameraSpeedNormal()
{
return _cameraSpeedNormal;
}
public bool IsAutoRotate()
{
return _autoRotate;
}
public void SetAutoRotate(bool autoRotate)
{
if (autoRotate && !_autoRotate) {
_autoRotate = true;
autoplayRoutine = StartCoroutine(AutoPlayRoutine());
} else if (!autoRotate && _autoRotate) {
_autoRotate = false;
StopCoroutine(autoplayRoutine);
}
}
public static float ClampAngle(float angle, float min, float max)
{
if (angle < -360F)
angle += 360F;
if (angle > 360F)
angle -= 360F;
return Mathf.Clamp(angle, min, max);
}
private void Awake()
{
if (Instance == null) {
Instance = this;
DontDestroyOnLoad(gameObject);
} else {
Destroy(gameObject);
}
_translationInputToVectorMap = new Dictionary<TranslationInput, Vector3> {
{TranslationInput.Forward, wVector},
{TranslationInput.Left, aVector},
{TranslationInput.Back, sVector},
{TranslationInput.Right, dVector},
{TranslationInput.Up, Vector3.up},
{TranslationInput.Down, Vector3.down}
};
}
// Start is called before the first frame update
void Start()
{
fourPos.position = new Vector3(0, 0, 0);
fourPos.rotation = Quaternion.Euler(0,0,0);
fivePos.position = new Vector3(0, 0, 0);
fivePos.rotation = Quaternion.Euler(0, 0, 0);
sixPos.position = new Vector3(0, 0, 0);
sixPos.rotation = Quaternion.Euler(0, 0, 0);
Vector3 angles = transform.eulerAngles;
_orbitX = angles.y;
_orbitY = angles.x;
UpdateTargetAlpha();
ResetCameraTarget();
}
IEnumerator AutoPlayRoutine()
{
while (true)
{
float elapsedTime = 0f;
while (elapsedTime <= xAutoRotateTime)
{
_orbitX += Time.unscaledDeltaTime * autoplayCamSpeed * _orbitDistance * 0.02f;
UpdateCamPosition(_orbitX, _orbitY);
elapsedTime += Time.unscaledDeltaTime;
yield return null;
}
elapsedTime = 0f;
while (elapsedTime <= yAutoRotateTime)
{
_orbitY -= Time.unscaledDeltaTime * autoplayCamSpeed * _orbitDistance * 0.02f;
UpdateCamPosition(_orbitX, _orbitY);
elapsedTime += Time.unscaledDeltaTime;
yield return null;
}
elapsedTime = 0f;
while (elapsedTime <= xAutoRotateTime)
{
_orbitX -= Time.unscaledDeltaTime * autoplayCamSpeed * _orbitDistance * 0.02f;
UpdateCamPosition(_orbitX, _orbitY);
elapsedTime += Time.unscaledDeltaTime;
yield return null;
}
elapsedTime = 0f;
while (elapsedTime <= yAutoRotateTime)
{
_orbitY += Time.unscaledDeltaTime * autoplayCamSpeed * _orbitDistance * 0.02f;
UpdateCamPosition(_orbitX, _orbitY);
elapsedTime += Time.unscaledDeltaTime;
yield return null;
}
yield return null;
}
}
void ResetCameraTarget()
{
RaycastHit hit;
if(Physics.Raycast(transform.position, transform.forward, out hit, float.MaxValue, LayerMask.GetMask("Floor"), QueryTriggerInteraction.Ignore))
{
target.transform.position = hit.point;
_orbitDistance = hit.distance;
Vector3 angles = transform.eulerAngles;
_orbitX = angles.y;
_orbitY = angles.x;
UpdateCamPosition(_orbitX, _orbitY);
}
else
{
target.transform.position = transform.position + (transform.forward * 100);
_orbitDistance = 100;
Vector3 angles = transform.eulerAngles;
_orbitX = angles.y;
_orbitY = angles.x;
//UpdateCamPosition();
}
}
public void EnableTargetRenderer(bool enable) { targetRenderer.enabled = enable; }
public void EnableFloorGridRenderer(bool enable) { floorRenderer.enabled = enable; }
public void OrbitCamera(float xOrbit, float yOrbit) {
if (target)
{
_orbitX += xOrbit * _orbitXSpeed * _orbitDistance * 0.02f;
_orbitY -= yOrbit * _orbitYSpeed * _orbitDistance * 0.02f;
_orbitY = ClampAngle(_orbitY, orbitYMinLimit, orbitYMaxLimit);
UpdateCamPosition(_orbitX, _orbitY);
}
}
public void RotateCamera(float xRotate, float yRotate) {
_yaw += xRotate * _speedH;
_pitch -= yRotate * _speedV;
transform.eulerAngles = new Vector3(_pitch, _yaw, 0.0f);
}
private void UpdateCamPosition(float x, float y)
{
Quaternion rotation = Quaternion.Euler(y, x, 0);
RaycastHit hit;
//Debug.DrawLine(target.position, transform.position, Color.red);
if (Physics.Linecast(target.position, transform.position, out hit, ~LayerMask.GetMask("Floor"), QueryTriggerInteraction.Ignore))
{
_orbitDistance -= hit.distance;
}
Vector3 negDistance = new Vector3(0.0f, 0.0f, -_orbitDistance);
Vector3 position = rotation * negDistance + target.position;
UpdateTargetAlpha();
SetCameraRotation(rotation);
transform.position = position;
}
public void ZoomCamera(float zoom)
{
_orbitDistance = Mathf.Clamp(_orbitDistance - zoom * _zoomSpeed, _orbitDistanceMin, _orbitDistanceMax);
UpdateCamPosition(_orbitX, _orbitY);
}
void UpdateTargetAlpha()
{
matAlpha = (_orbitDistance - _orbitDistanceMin) / (_orbitDistanceMax - _orbitDistanceMin);
matAlpha = Mathf.Max(Mathf.Sqrt(matAlpha) - 0.5f, 0);
Color matColor = targetRenderer.material.color;
matColor.a = matAlpha;
targetRenderer.material.color = matColor;
}
void UpdateDirectionVectors()
{
Vector3 cameraToTarget = target.position - transform.position;
cameraToTarget.y = 0;
forwardToCameraAngle = Vector3.SignedAngle(Vector3.forward, cameraToTarget, Vector3.down);
if(forwardToCameraAngle >-45f && forwardToCameraAngle <= 45f)
{
_translationInputToVectorMap[TranslationInput.Forward] = Vector3.forward;
_translationInputToVectorMap[TranslationInput.Left] = Vector3.left;
_translationInputToVectorMap[TranslationInput.Back] = Vector3.back;
_translationInputToVectorMap[TranslationInput.Right] = Vector3.right;
}
else if(forwardToCameraAngle > 45f && forwardToCameraAngle <= 135f)
{
_translationInputToVectorMap[TranslationInput.Forward] = Vector3.left;
_translationInputToVectorMap[TranslationInput.Left] = Vector3.back;
_translationInputToVectorMap[TranslationInput.Back] = Vector3.right;
_translationInputToVectorMap[TranslationInput.Right] = Vector3.forward;
}
else if(forwardToCameraAngle > 135f || forwardToCameraAngle <= -135f)
{
_translationInputToVectorMap[TranslationInput.Forward] = Vector3.back;
_translationInputToVectorMap[TranslationInput.Left] = Vector3.right;
_translationInputToVectorMap[TranslationInput.Back] = Vector3.forward;
_translationInputToVectorMap[TranslationInput.Right] = Vector3.left;
}
else if(forwardToCameraAngle > -135f && forwardToCameraAngle <= -45f)
{
_translationInputToVectorMap[TranslationInput.Forward] = Vector3.right;
_translationInputToVectorMap[TranslationInput.Left] = Vector3.forward;
_translationInputToVectorMap[TranslationInput.Back] = Vector3.left;
_translationInputToVectorMap[TranslationInput.Right] = Vector3.back;
}
}
public enum TranslationInput {
Forward,
Left,
Back,
Right,
Up,
Down
}
public void TranslateCamera(TranslationInput input) {
UpdateDirectionVectors();
target.Translate(_translationInputToVectorMap[input] * Time.unscaledDeltaTime * _cameraSpeed);
UpdateCamPosition(_orbitX, _orbitY);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b9e222cf9df32df4196f08036d68d740
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 27829404610ae9a479fd6f44c6da81ca
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,36 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BotStatusDialog : UIDialog
{
// Start is called before the first frame update
public override void Start()
{
base.Start();
UISelectableEntry missiles = CreateSelectableEntry();
missiles.SetTextContent(new List<string>(new string[] { "Interceptors" }));
missiles.SetIsSelectable(false);
UISelectableEntry submunitions = CreateSelectableEntry();
submunitions.SetTextContent(new List<string>(new string[] { "Submunitions" }));
submunitions.SetIsSelectable(false);
UISelectableEntry targets = CreateSelectableEntry();
targets.SetTextContent(new List<string>(new string[] { "Threats" }));
targets.SetIsSelectable(false);
SetDialogEntries(new List<UISelectableEntry>(new UISelectableEntry[] { missiles, submunitions, targets }));
AddDialogTab("All", () => { });
}
// Update is called once per frame
void Update()
{
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a404f59a5c2ba814a9c1ca121b596df9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
using UnityEngine;
public static class RectTransformExtensions
{
public static void SetLeft(this RectTransform rt, float left)
{
rt.offsetMin = new Vector2(left, rt.offsetMin.y);
}
public static void SetRight(this RectTransform rt, float right)
{
rt.offsetMax = new Vector2(-right, rt.offsetMax.y);
}
public static void SetTop(this RectTransform rt, float top)
{
rt.offsetMax = new Vector2(rt.offsetMax.x, -top);
}
public static void SetBottom(this RectTransform rt, float bottom)
{
rt.offsetMin = new Vector2(rt.offsetMin.x, bottom);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c991d4191b4586742a5d8dfc8ece23bb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,18 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UIBuildButton : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 56ea3cedd2ebcbc42a98ea19ebc61632
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,213 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using System;
using UnityEngine.UI;
public class UIDialog : MonoBehaviour
{
[SerializeField]
private string dialogTitle;
[SerializeField]
private TextMeshProUGUI dialogTitleHandle;
[SerializeField]
private RectTransform contentHandle;
/// TABS
[SerializeField]
private float tabWidth = 50f;
[SerializeField]
private float tabHeight = 16f;
// List of dialog tabs
private List<GameObject> dialogTabs;
/// ENTRIES
private List<UISelectableEntry> entries;
private float entryHeight = 20f;
private float entryIndentWidth= 10f;
private List<UISelectableEntry> cleanupPool;
private bool isOpen;
// Start is called before the first frame update
public virtual void Start()
{
dialogTitleHandle.text = dialogTitle;
dialogTitleHandle.font = UIManager.Instance.Font;
isOpen = gameObject.activeSelf;
dialogTabs = new List<GameObject>();
entries = new List<UISelectableEntry>();
cleanupPool = new List<UISelectableEntry>();
}
internal RectTransform GetContentHandle()
{
return contentHandle;
}
public bool IsOpen()
{
return isOpen;
}
protected virtual void OnEnable() { isOpen = true; }
protected virtual void OnDisable() { isOpen = false; }
public float GetTabWidth() { return tabWidth; }
public float GetTabHeight() { return tabHeight; }
/// <summary>
/// Returns the height of the dialog title bar
/// </summary>
public float GetTitleBarHeight() { return dialogTitleHandle.rectTransform.sizeDelta.y; }
/// <summary>
/// Adds a new tab to the dialog, when clicked it will call the given callback
/// </summary>
public void AddDialogTab(string tabName, Action onClick)
{
dialogTabs.Add(AddTabButton(tabName, onClick));
}
/// <summary>
/// Add the tab button to the right of the existing tabs
/// </summary>
private GameObject AddTabButton(string tabName, Action onClick)
{
GameObject tabButton = new GameObject("TabButton", typeof(RectTransform));
tabButton.transform.SetParent(transform); // worldPositionStays ?
// RectTransform anchors to the right of the content handle
RectTransform rTransform = tabButton.GetComponent<RectTransform>();
rTransform.anchorMin = new Vector2(0, 1);
rTransform.anchorMax = new Vector2(0, 1);
rTransform.pivot = new Vector2(0, 1);
rTransform.sizeDelta = new Vector2(tabWidth, tabHeight);
// Count tabs * tabSize to get the position from the left
rTransform.anchoredPosition = new Vector2(tabWidth * dialogTabs.Count, -(GetTitleBarHeight()));
// Add the onClick callback to the button
Button button = tabButton.AddComponent<Button>();
button.onClick.AddListener(() => onClick());
// Add the image to the button and link it to the tab
button.targetGraphic = tabButton.AddComponent<Image>();
AddTabText(tabName, tabButton);
return tabButton;
}
/// <summary>
/// Add text as a child of the tab's button object
/// </summary>
private void AddTabText(string tabName, GameObject tabButton)
{
GameObject tabText = new GameObject("TabText", typeof(RectTransform));
tabText.transform.SetParent(tabButton.transform);
// RectTransform anchors to the center of the button
RectTransform textRectTransform = tabText.GetComponent<RectTransform>();
textRectTransform.anchorMin = new Vector2(0.5f, 0.5f);
textRectTransform.anchorMax = new Vector2(0.5f, 0.5f);
textRectTransform.pivot = new Vector2(0.5f, 0.5f);
textRectTransform.sizeDelta = new Vector2(tabWidth, tabHeight);
// Text position
textRectTransform.anchoredPosition = new Vector2(0, 0);
TextMeshProUGUI buttonText = tabText.AddComponent<TextMeshProUGUI>();
buttonText.text = tabName;
buttonText.font = UIManager.Instance.Font;
buttonText.fontSize = 12;
buttonText.color = Color.black;
buttonText.alignment = TextAlignmentOptions.Center;
buttonText.verticalAlignment = VerticalAlignmentOptions.Middle;
}
public virtual UISelectableEntry CreateSelectableEntry()
{
// Create a new entry object with content handle as parent
GameObject go = Instantiate(Resources.Load<GameObject>("Prefabs/EmptyObject"), contentHandle);
go.name = "UISelectableEntry";
UISelectableEntry entry = go.AddComponent<UISelectableEntry>();
entry.SetParent(this);
// add to cleanup pool so we can clear later in clear dialog entries
cleanupPool.Add(entry);
return entry;
}
public void ClearDialogEntries()
{
if (cleanupPool == null)
return;
foreach (UISelectableEntry entry in cleanupPool)
{
GameObject.Destroy(entry.gameObject);
}
cleanupPool.Clear();
if (entries != null)
entries.Clear();
}
/// <summary>
/// Clears, sets, and prints the dialog entries in the order they were added
/// </summary>
public virtual void SetDialogEntries(List<UISelectableEntry> entries)
{
this.entries = entries;
// calculate total height of the content
float heightHead = -1*GetTabHeight();
int count = 0;
foreach (UISelectableEntry entry in this.entries)
{
(heightHead, count) = RecursiveContentPrint(entry, 1, heightHead, count);
}
contentHandle.sizeDelta = new Vector2(contentHandle.sizeDelta.x, count * entryHeight + Mathf.Abs(heightHead));
}
public virtual void SetDialogEntries(UISelectableEntry entry)
{
SetDialogEntries(new List<UISelectableEntry>() { entry });
}
private (float, int) RecursiveContentPrint(UISelectableEntry entry, int depth, float heightHead, int count)
{
RectTransform rTransform = entry.GetComponent<RectTransform>();
rTransform.anchorMin = new Vector2(0, 1);
rTransform.anchorMax = new Vector2(1, 1);
rTransform.pivot = new Vector2(0.5f, 1f);
rTransform.anchoredPosition = new Vector2(0, heightHead); // positioning from top
rTransform.sizeDelta = new Vector2(0, entryHeight);
float padding = 5f;
rTransform.SetRight(padding);
rTransform.SetLeft(padding);
// actually indent the text
entry.GetTextTransform().anchoredPosition = new Vector2(entryIndentWidth * depth, 0);
heightHead -= entryHeight;
count++;
// Print the children
if (entry.GetChildEntries() != null)
{
foreach (UISelectableEntry child in entry.GetChildEntries())
{
(heightHead, count) = RecursiveContentPrint(child, depth + 1, heightHead, count);
}
}
return (heightHead, count);
}
// Update is called once per frame
void Update()
{
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 28b8de234a5dab849bce341b844ea8e1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
[RequireComponent(typeof(UIElementMouseCapturer))]
public class UIElementDragger : EventTrigger
{
public override void OnDrag(PointerEventData eventData)
{
transform.position = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 08d6199088fb8554fa7335c18db3ace4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,25 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class UIElementMouseCapturer : EventTrigger
{
public override void OnPointerEnter(PointerEventData eventData)
{
InputManager.Instance.mouseActive = false;
base.OnPointerEnter(eventData);
}
public override void OnPointerExit(PointerEventData eventData)
{
InputManager.Instance.mouseActive = true;
base.OnPointerExit(eventData);
}
public void OnDisable()
{
OnPointerExit(null);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8b4602e26cd6ff342a532880fc8ac6b8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,127 @@
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class UIManager : MonoBehaviour
{
public static UIManager Instance { get; private set; }
[SerializeField]
private GameObject _agentStatusPanel;
[SerializeField]
private GameObject _configSelectorPanel;
private TMP_Dropdown _configDropdown;
public TextMeshProUGUI agentPanelText;
public TextMeshProUGUI simTimeText;
public TMP_FontAsset Font;
private UIMode curMode = UIMode.NONE;
// Start is called before the first frame update
void Awake()
{
// singleton
if (Instance == null)
Instance = this;
else
Destroy(gameObject);
}
void Start()
{
_configSelectorPanel.SetActive(false);
SetupConfigSelectorPanel();
//inputManager = InputManager.Instance;
//worldManager = WorldManager.Instance;
}
public void ToggleConfigSelectorPanel(){
_configSelectorPanel.SetActive(!_configSelectorPanel.activeSelf);
}
private void SetupConfigSelectorPanel(){
_configSelectorPanel.GetComponentInChildren<Button>().onClick.AddListener(delegate {
LoadSelectedConfig();
});
_configDropdown = _configSelectorPanel.GetComponentInChildren<TMP_Dropdown>();
PopulateConfigDropdown();
}
private void PopulateConfigDropdown(){
_configDropdown.ClearOptions();
string configPath = Path.Combine(Application.streamingAssetsPath, "Configs");
string[] configFiles = Directory.GetFiles(configPath, "*.json");
List<string> configFileNames = new List<string>();
foreach (string configFile in configFiles)
{
configFileNames.Add(Path.GetFileName(configFile));
}
_configDropdown.AddOptions(configFileNames);
}
private void LoadSelectedConfig(){
string selectedConfig = _configDropdown.options[_configDropdown.value].text;
SimManager.Instance.LoadNewConfig(selectedConfig);
_configSelectorPanel.SetActive(false);
//if(!InputManager.Instance.mouseActive){
// InputManager.Instance.mouseActive = true;
//}
}
public void SetUIMode(UIMode mode){
curMode = mode;
}
public UIMode GetUIMode(){
return curMode;
}
public void SetAgentPanelText(string text)
{
agentPanelText.text = text;
}
public string GetAgentPanelText()
{
return agentPanelText.text;
}
private void UpdateAgentPanel()
{
string agentPanelText = "";
foreach(Agent agent in SimManager.Instance.GetActiveAgents())
{
string jobText = agent.name + "| Phase: " + agent.GetFlightPhase().ToString();
agentPanelText += jobText + "\n";
}
SetAgentPanelText(agentPanelText);
}
private void UpdateSimTimeText()
{
simTimeText.text = "Elapsed Sim Time: " + SimManager.Instance.GetElapsedSimulationTime().ToString("F2");
}
// Update is called once per frame
void Update()
{
//UpdateAgentPanel();
UpdateSimTimeText();
}
}
public enum UIMode {
NONE,
BUILD,
MINE
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b078e3e02b9c70645a97175561678909
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,119 @@
using System.Collections.Generic;
using System;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using UnityEngine.EventSystems;
public class UISelectableEntry : EventTrigger {
private List<UISelectableEntry> children;
private List<string> textContent;
private UIDialog parentDialog;
private RectTransform rectTransform;
private CanvasRenderer canvasRenderer;
private Image image;
private TextMeshProUGUI textHandle;
private bool isSelectable = true;
private static Color baseColor = new Color32(31, 31, 45, 140);
private Action<object> OnClickCallback;
private object callbackArgument;
public void Awake() {
rectTransform = gameObject.AddComponent<RectTransform>();
textHandle = Instantiate(Resources.Load<GameObject>("Prefabs/EmptyObject"), rectTransform).AddComponent<TextMeshProUGUI>();
textHandle.gameObject.name = "UISelectableEntry::Text";
textHandle.fontSize = 12;
textHandle.font = UIManager.Instance.Font;
textHandle.alignment = TextAlignmentOptions.MidlineLeft;
textHandle.GetComponent<RectTransform>().anchorMin = new Vector2(0, 0.5f);
textHandle.GetComponent<RectTransform>().anchorMax = new Vector2(1, 0.5f);
textHandle.GetComponent<RectTransform>().pivot = new Vector2(0.5f, 0.5f);
textHandle.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, 0);
textHandle.GetComponent<RectTransform>().sizeDelta = new Vector2(0, 20);
image = gameObject.AddComponent<Image>();
image.type = Image.Type.Sliced;
image.color = baseColor;
}
public void SetClickCallback(Action<object> callback, object argument)
{
OnClickCallback = callback;
callbackArgument = argument;
}
public override void OnPointerEnter(PointerEventData eventData)
{
if(isSelectable)
image.color = baseColor + new Color32(20, 20, 20, 40);
base.OnPointerEnter(eventData);
}
public override void OnPointerDown(PointerEventData eventData)
{
if(isSelectable && OnClickCallback != null)
{
OnClickCallback(callbackArgument);
image.color = baseColor + new Color32(40, 40, 40, 40);
}
base.OnPointerClick(eventData);
}
public override void OnPointerExit(PointerEventData eventData)
{
if(isSelectable)
image.color = baseColor;
base.OnPointerExit(eventData);
}
public void SetIsSelectable(bool isSelectable) {
if(isSelectable)
image.enabled = true;
else
image.enabled = false;
this.isSelectable = isSelectable;
}
public bool GetIsSelectable() {
return isSelectable;
}
public void AddChildEntry(UISelectableEntry child) {
if (children == null) {
children = new List<UISelectableEntry>();
}
children.Add(child);
}
public void SetParent(UIDialog parentDialog) {
this.parentDialog = parentDialog;
}
public void SetChildEntries(List<UISelectableEntry> children) {
this.children = children;
}
// Get the children of this entry
public List<UISelectableEntry> GetChildEntries() {
return children;
}
public void SetTextContent(List<string> textContent) {
this.textContent = textContent;
textHandle.text = string.Join("\t", textContent);
}
public RectTransform GetTextTransform() {
return textHandle.GetComponent<RectTransform>();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b1f58db020114a64fb94cf47d775a20f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5dfa88d362db21f42acf701d3d3e326a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,108 @@
using System.Collections.Generic;
using System;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using UnityEngine.EventSystems;
[RequireComponent(typeof(UIElementMouseCapturer))]
[RequireComponent(typeof(UIElementDragger))]
[RequireComponent(typeof(Image))]
public class UIWindow : MonoBehaviour
{
// Window title
[SerializeField]
private string windowTitle = "Window";
// Close button
private GameObject closeButton;
[SerializeField]
private CloseButtonCallback closeButtonCallback;
[Serializable]
private enum CloseButtonCallback
{
CLOSE_WINDOW,
TOGGLE_WINDOW
}
// IsOpen property
private bool isOpen;
private void OnEnable() { isOpen = true; }
private void OnDisable() { isOpen = false; }
public void ToggleWindow()
{
gameObject.SetActive(!gameObject.activeSelf);
}
public void CloseWindow()
{
Destroy(gameObject);
isOpen = false;
}
/// <summary>
/// Called when the UIWindow component is created in the editor
/// We will use it to configure the image component
/// </summary>
private void Reset()
{
// 18 16 28 125
GetComponent<Image>().color = new Color32(18, 16, 28, 125);
}
public virtual void Start()
{
isOpen = gameObject.activeSelf;
CreateCloseButton();
CreateWindowTitle();
if (closeButtonCallback == CloseButtonCallback.CLOSE_WINDOW)
closeButton.AddComponent<Button>().onClick.AddListener(CloseWindow);
else if (closeButtonCallback == CloseButtonCallback.TOGGLE_WINDOW)
closeButton.AddComponent<Button>().onClick.AddListener(ToggleWindow);
}
private void CreateWindowTitle()
{
GameObject windowTitleObject = new GameObject("WindowTitle", typeof(RectTransform));
windowTitleObject.transform.SetParent(transform);
TextMeshProUGUI windowTitleHandle = windowTitleObject.AddComponent<TextMeshProUGUI>();
windowTitleHandle.text = windowTitle;
windowTitleHandle.font = UIManager.Instance.Font;
windowTitleHandle.fontSize = 14;
windowTitleHandle.color = Color.white;
windowTitleHandle.alignment = TextAlignmentOptions.Left;
windowTitleHandle.rectTransform.anchorMin = new Vector2(0, 1);
windowTitleHandle.rectTransform.anchorMax = new Vector2(1, 1);
windowTitleHandle.rectTransform.pivot = new Vector2(0, 1);
windowTitleHandle.rectTransform.sizeDelta = new Vector2(0, 30);
windowTitleHandle.rectTransform.anchoredPosition = new Vector2(5, 0);
windowTitleHandle.rectTransform.SetRight(30); // Give spacing to the close button
}
/// <summary>
/// Create the close [x] button in the top right corner of the window
/// </summary>
private void CreateCloseButton()
{
closeButton = new GameObject("CloseButton", typeof(RectTransform));
RectTransform buttonTransform = closeButton.GetComponent<RectTransform>();
buttonTransform.SetParent(transform);
// anchor top right
buttonTransform.anchorMin = new Vector2(1, 1);
buttonTransform.anchorMax = new Vector2(1, 1);
buttonTransform.pivot = new Vector2(1, 1);
// position top right
buttonTransform.anchoredPosition = new Vector2(0, 0);
// size
buttonTransform.sizeDelta = new Vector2(30, 30);
// add button component
TextMeshProUGUI textbox = closeButton.AddComponent<TextMeshProUGUI>();
textbox.text = "X";
textbox.font = UIManager.Instance.Font;
textbox.fontSize = 12;
textbox.alignment = TextAlignmentOptions.Center;
textbox.verticalAlignment = VerticalAlignmentOptions.Middle;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0399a7d56c753d241812497084561817
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,9 @@
using UnityEngine;
public static class Utilities {
public static Vector3 GenerateRandomNoise(Vector3 standardDeviation) {
return new Vector3(Random.Range(-standardDeviation.x, standardDeviation.x),
Random.Range(-standardDeviation.y, standardDeviation.y),
Random.Range(-standardDeviation.z, standardDeviation.z));
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: caa881af3be57584a91f17ed3683fed7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,17 @@
{
"name": "bamlab.micromissiles",
"rootNamespace": "",
"references": [
"GUID:6055be8ebefd69e48b49212b09b47b2f",
"GUID:c668b7a00ffe56a498ddb1fc9f96f4e6"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: af8bba08a36038347823e2f46bdc9857
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: