Reorganization and many improvements of rendering
This commit is contained in:
@@ -25,6 +25,7 @@ public abstract class Agent : MonoBehaviour
|
||||
[SerializeField]
|
||||
protected Agent _target;
|
||||
protected bool _isHit = false;
|
||||
protected bool _isMiss = false;
|
||||
|
||||
protected DynamicConfig _dynamicConfig;
|
||||
[SerializeField]
|
||||
@@ -71,10 +72,25 @@ public abstract class Agent : MonoBehaviour
|
||||
return _isHit;
|
||||
}
|
||||
|
||||
public bool IsMiss() {
|
||||
return _isMiss;
|
||||
}
|
||||
|
||||
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 MarkAsHit() {
|
||||
_isHit = true;
|
||||
_flightPhase = FlightPhase.TERMINATED;
|
||||
TerminateAgent();
|
||||
}
|
||||
|
||||
public void MarkAsMiss() {
|
||||
_isMiss = true;
|
||||
TerminateAgent();
|
||||
}
|
||||
|
||||
|
||||
@@ -147,7 +163,7 @@ public abstract class Agent : MonoBehaviour
|
||||
Quaternion targetRotation = Quaternion.LookRotation(velocity, Vector3.up);
|
||||
|
||||
// Smoothly rotate towards the target rotation
|
||||
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, 100f * Time.deltaTime);
|
||||
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, 1000f * Time.deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
8
Assets/Scripts/Assignment.meta
Normal file
8
Assets/Scripts/Assignment.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06e870818cf329e49a0d0c5a0d024f21
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
74
Assets/Scripts/Assignment/ThreatAssignment.cs
Normal file
74
Assets/Scripts/Assignment/ThreatAssignment.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
// The threat assignment class assigns missiles to the targets based
|
||||
// on the threat level of the targets.
|
||||
public class ThreatAssignment : Assignment
|
||||
{
|
||||
// Assign a target to each missile that has not been assigned a target yet.
|
||||
public override void Assign(List<Agent> missiles, List<Agent> targets)
|
||||
{
|
||||
List<int> assignableMissileIndices = GetAssignableMissileIndices(missiles);
|
||||
if (assignableMissileIndices.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<int> activeTargetIndices = GetActiveTargetIndices(targets);
|
||||
if (activeTargetIndices.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 missilesMeanPosition = CalculateMeanPosition(missiles);
|
||||
List<ThreatInfo> threatInfos = CalculateThreatLevels(targets, activeTargetIndices, missilesMeanPosition);
|
||||
|
||||
foreach (int missileIndex in assignableMissileIndices)
|
||||
{
|
||||
if (threatInfos.Count == 0) break;
|
||||
|
||||
ThreatInfo highestThreat = threatInfos[0];
|
||||
missileToTargetAssignments.AddFirst(new AssignmentItem(missileIndex, highestThreat.TargetIndex));
|
||||
threatInfos.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector3 CalculateMeanPosition(List<Agent> agents)
|
||||
{
|
||||
return agents.Aggregate(Vector3.zero, (sum, agent) => sum + agent.transform.position) / agents.Count;
|
||||
}
|
||||
|
||||
private List<ThreatInfo> CalculateThreatLevels(List<Agent> targets, List<int> activeTargetIndices, Vector3 missilesMeanPosition)
|
||||
{
|
||||
List<ThreatInfo> threatInfos = new List<ThreatInfo>();
|
||||
|
||||
foreach (int targetIndex in activeTargetIndices)
|
||||
{
|
||||
Agent target = targets[targetIndex];
|
||||
float distanceToMean = Vector3.Distance(target.transform.position, missilesMeanPosition);
|
||||
float velocityMagnitude = target.GetVelocity().magnitude;
|
||||
|
||||
// Calculate threat level based on proximity and velocity
|
||||
float threatLevel = (1 / distanceToMean) * velocityMagnitude;
|
||||
|
||||
threatInfos.Add(new ThreatInfo(targetIndex, threatLevel));
|
||||
}
|
||||
|
||||
// Sort threats in descending order
|
||||
return threatInfos.OrderByDescending(t => t.ThreatLevel).ToList();
|
||||
}
|
||||
|
||||
private class ThreatInfo
|
||||
{
|
||||
public int TargetIndex { get; }
|
||||
public float ThreatLevel { get; }
|
||||
|
||||
public ThreatInfo(int targetIndex, float threatLevel)
|
||||
{
|
||||
TargetIndex = targetIndex;
|
||||
ThreatLevel = threatLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Assignment/ThreatAssignment.cs.meta
Normal file
11
Assets/Scripts/Assignment/ThreatAssignment.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e9829915a9eb41409ea03fb46910432
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Scripts/Config.meta
Normal file
8
Assets/Scripts/Config.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 068fab6561d36f445b1d765a9d3af005
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Scripts/Editor.meta
Normal file
8
Assets/Scripts/Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91594f3f9a39e6746879216d6dd0d4ec
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
157
Assets/Scripts/Editor/GenerateCone.cs
Normal file
157
Assets/Scripts/Editor/GenerateCone.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Editor/GenerateCone.cs.meta
Normal file
11
Assets/Scripts/Editor/GenerateCone.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 27925dcc8d7b5dd4ab28ed7de5b806a4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Scripts/Interceptors.meta
Normal file
8
Assets/Scripts/Interceptors.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6f05ad8af46b8df4c9720b5deb691295
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -30,17 +30,9 @@ public class Micromissile : Missile
|
||||
|
||||
// Sense the target
|
||||
SensorOutput sensorOutput = GetComponent<Sensor>().Sense(_target);
|
||||
|
||||
// Check whether the target has been hit
|
||||
if (_target.IsHit())
|
||||
{
|
||||
float killProbability = _target.GetComponent<Agent>().StaticConfig.hitConfig.killProbability;
|
||||
if (Random.value < killProbability)
|
||||
{
|
||||
MarkAsHit();
|
||||
_target.MarkAsHit();
|
||||
return;
|
||||
}
|
||||
if(sensorOutput.velocity.range > 1000f) {
|
||||
this.MarkAsMiss();
|
||||
_target.MarkAsMiss();
|
||||
}
|
||||
|
||||
// Calculate the acceleration input
|
||||
@@ -94,10 +86,10 @@ public class Micromissile : Missile
|
||||
Debug.DrawLine(transform.position, _target.transform.position, Color.white);
|
||||
|
||||
// Velocity vector
|
||||
Debug.DrawRay(transform.position, GetVelocity(), Color.blue);
|
||||
Debug.DrawRay(transform.position, GetVelocity()*0.01f, Color.blue);
|
||||
|
||||
// Acceleration input
|
||||
Debug.DrawRay(transform.position, _accelerationCommand*1000f, Color.green);
|
||||
Debug.DrawRay(transform.position, _accelerationCommand*1f, Color.green);
|
||||
|
||||
// Current forward direction
|
||||
Debug.DrawRay(transform.position, transform.forward * 5f, Color.yellow);
|
||||
@@ -79,11 +79,22 @@ public class Missile : Agent
|
||||
{
|
||||
// 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.5f);
|
||||
// Mark both this agent and the other agent as hit
|
||||
this.MarkAsHit();
|
||||
otherAgent.MarkAsHit();
|
||||
|
||||
}
|
||||
else {
|
||||
// Set red for miss
|
||||
markerObject.GetComponent<Renderer>().material.color = new Color(1, 0, 0, 0.5f);
|
||||
this.MarkAsMiss();
|
||||
otherAgent.MarkAsMiss();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
8
Assets/Scripts/Sensors.meta
Normal file
8
Assets/Scripts/Sensors.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c45dceac2c26594d8ac013a8f72e042
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -26,7 +26,7 @@ public class SimManager : MonoBehaviour
|
||||
simulationRunning = true;
|
||||
}
|
||||
|
||||
private void InitializeSimulation()
|
||||
private void InitializeSimulation()
|
||||
{
|
||||
// Create missiles based on config
|
||||
foreach (var swarmConfig in simulationConfig.missile_swarm_configs)
|
||||
@@ -46,7 +46,7 @@ public class SimManager : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
_assignment = new RoundRobinAssignment();
|
||||
_assignment = new ThreatAssignment();
|
||||
// Perform initial assignment
|
||||
AssignMissilesToTargets();
|
||||
}
|
||||
@@ -191,7 +191,7 @@ public class SimManager : MonoBehaviour
|
||||
bool allMissilesTerminated = true;
|
||||
foreach (var missile in missiles)
|
||||
{
|
||||
if (missile != null && !missile.IsHit())
|
||||
if (missile != null && !missile.IsHit() && !missile.IsMiss())
|
||||
{
|
||||
allMissilesTerminated = false;
|
||||
break;
|
||||
|
||||
8
Assets/Scripts/Targets.meta
Normal file
8
Assets/Scripts/Targets.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2342a881813cdd645962533af3f6f755
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user