using UnityEngine;
using System.Collections.Generic;
using System.Linq;
///
/// Manages spawning and tracking of AI agents in the maze
/// Supports configurable spawn counts and runtime spawning
///
public class AIAgentManager : MonoBehaviour
{
[Header("Agent Spawning")]
[SerializeField] private int initialAgentCount = 10;
[SerializeField] private string agentCharacterType = "Default";
[SerializeField] private float spawnDelay = 0.1f;
[Header("Agent Prefab")]
[SerializeField] private GameObject agentPrefab;
[Header("Visual Settings")]
[Tooltip("Show path debug lines for all agents. Disable for large agent counts (>100) for major performance gains.")]
[SerializeField] private bool showAgentPaths = false;
[SerializeField] private Color agentPathColor = Color.yellow;
[SerializeField] private Material agentMaterial;
[Header("Grouping")]
[SerializeField] private float groupCheckInterval = 2f;
[SerializeField] private float groupingRadius = 1.5f;
private MazeController mazeController;
private List activeAgents = new();
private readonly List activeGroups = new();
private int nextAgentId = 0;
private bool isInitialized = false;
private float lastGroupCheck = 0f;
// ---- Death statistics ----
private int agentsKilledByMonsters = 0;
private int monstersKilledByAgents = 0;
/// How many agents have been killed by monsters this run.
public int AgentsKilledByMonsters => agentsKilledByMonsters;
/// How many monsters agents have killed this run.
public int MonstersKilledByAgents => monstersKilledByAgents;
///
/// Called by a dying AIAgent so the manager can update stats and clean up.
///
public void RegisterAgentDeath(AIAgent agent)
{
agentsKilledByMonsters++;
activeAgents.Remove(agent);
Debug.Log($"[Manager] Agent {agent.AgentName} killed. Total killed: {agentsKilledByMonsters}");
}
/// Called by an agent after landing a killing blow on a monster.
public void RegisterMonsterKill()
{
monstersKilledByAgents++;
}
void Awake()
{
mazeController = GetComponent();
if (mazeController == null)
{
mazeController = FindAnyObjectByType();
}
}
void Update()
{
if (!isInitialized) return;
if (Time.time - lastGroupCheck < groupCheckInterval) return;
lastGroupCheck = Time.time;
EvaluateGrouping();
}
///
/// Checks all solo/leader agents for nearby agents and tries to form or join groups.
///
private void EvaluateGrouping()
{
var candidates = activeAgents
.Where(a => a != null && !a.IsGroupFollower && !a.HasReachedGoal)
.ToList();
for (int i = 0; i < candidates.Count; i++)
{
AIAgent a = candidates[i];
if (Random.value > a.GroupUpAffinity) continue;
for (int j = i + 1; j < candidates.Count; j++)
{
AIAgent b = candidates[j];
if (Vector3.Distance(a.transform.position, b.transform.position) > groupingRadius) continue;
if (Random.value > b.GroupUpAffinity) continue;
if (a.Group != null && !a.IsGroupFollower && b.Group == null)
a.Group.TryAddMember(b);
else if (b.Group != null && !b.IsGroupFollower && a.Group == null)
b.Group.TryAddMember(a);
else if (a.Group == null && b.Group == null)
{
var newGroup = new AgentGroup(a);
newGroup.TryAddMember(b);
activeGroups.Add(newGroup);
}
}
}
activeGroups.RemoveAll(g => g.Size <= 1);
}
void OnValidate()
{
// Called when inspector values change (both in play mode and edit mode)
// Update all active agents' path visibility
if (isInitialized && activeAgents.Count > 0)
{
SetPathVisibilityForAllAgents(showAgentPaths);
}
}
void Start()
{
if (mazeController == null)
{
Debug.LogError("AIAgentManager: MazeController not found!");
enabled = false;
return;
}
// Create agent prefab if not assigned
if (agentPrefab == null)
{
CreateDefaultAgentPrefab();
}
// Spawn initial agents
StartCoroutine(SpawnAgentsCoroutine(initialAgentCount));
isInitialized = true;
}
///
/// Creates a default agent prefab if one isn't assigned
///
private void CreateDefaultAgentPrefab()
{
GameObject prefab = new GameObject("AIAgent");
prefab.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f);
// Use 3D collider instead of 2D for 3D environment
var collider = prefab.AddComponent();
collider.radius = 1.0f;
// Add mesh renderer with a simple sphere
var meshFilter = prefab.AddComponent();
var meshRenderer = prefab.AddComponent();
// Use Unity's built-in sphere primitive mesh
meshFilter.mesh = Resources.GetBuiltinResource("Sphere.fbx");
// Create a cyan material using URP shader (Universal Render Pipeline)
var mat = new Material(Shader.Find("Universal Render Pipeline/Lit"));
if (mat.shader == null)
{
// Fallback to simpler URP shader if Lit is not available
mat = new Material(Shader.Find("Universal Render Pipeline/Simple Lit"));
}
if (mat.shader == null)
{
// Last resort - use Standard shader
mat = new Material(Shader.Find("Standard"));
}
mat.color = Color.cyan;
meshRenderer.material = mat;
prefab.AddComponent();
agentPrefab = prefab;
}
///
/// Spawns agents with delay
///
private System.Collections.IEnumerator SpawnAgentsCoroutine(int count)
{
for (int i = 0; i < count; i++)
{
SpawnAgent();
yield return new WaitForSeconds(spawnDelay);
}
}
///
/// Spawns a single AI agent
///
public AIAgent SpawnAgent()
{
if (agentPrefab == null)
{
Debug.LogError("AIAgentManager: Agent prefab not set!");
return null;
}
GameObject agentGO = Instantiate(agentPrefab);
agentGO.name = $"AIAgent_{agentCharacterType}_{nextAgentId}";
var agent = agentGO.GetComponent();
agent.SetShowPath(showAgentPaths);
if (agent == null)
{
agent = agentGO.AddComponent();
}
// Set agent properties via reflection or public properties
var idField = typeof(AIAgent).GetField("agentId",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var typeField = typeof(AIAgent).GetField("agentCharacterType",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (idField != null) idField.SetValue(agent, nextAgentId);
if (typeField != null) typeField.SetValue(agent, agentCharacterType);
activeAgents.Add(agent);
nextAgentId++;
// Suppress per-agent spawn log for large counts (1000 Debug.Log calls is significant overhead)
// Debug.Log($"Spawned agent {nextAgentId - 1} ({agentCharacterType}). Total agents: {activeAgents.Count}");
return agent;
}
///
/// Spawns multiple agents at once
///
public void SpawnAgents(int count)
{
StartCoroutine(SpawnAgentsCoroutine(count));
}
///
/// Removes and destroys an agent
///
public void RemoveAgent(AIAgent agent)
{
if (activeAgents.Remove(agent))
{
Destroy(agent.gameObject);
Debug.Log($"Removed agent {agent.AgentId}. Remaining: {activeAgents.Count}");
}
}
///
/// Gets agents of a specific character type
///
public List GetAgentsByType(string characterType)
{
var agents = new List();
foreach (var agent in activeAgents)
{
if (agent.CharacterType == characterType)
{
agents.Add(agent);
}
}
return agents;
}
///
/// Gets the count of active agents
///
public int GetAgentCount()
{
return activeAgents.Count;
}
///
/// Gets agent count by character type
///
public int GetAgentCountByType(string characterType)
{
return GetAgentsByType(characterType).Count;
}
///
/// Clears all agents and resets
///
public void ClearAllAgents()
{
foreach (var agent in activeAgents)
{
Destroy(agent.gameObject);
}
activeAgents.Clear();
nextAgentId = 0;
AIRoomMemoryManager.ClearAllMemories();
}
///
/// Resets agents for a new maze
///
public void ResetForNewMaze()
{
ClearAllAgents();
if (isInitialized)
{
StartCoroutine(SpawnAgentsCoroutine(initialAgentCount));
}
}
///
/// Gets UI info about agents
///
public string GetAgentStats()
{
int total = activeAgents.Count;
string stats = $"Total Agents: {total}\n";
var typeGroups = new Dictionary();
foreach (var agent in activeAgents)
{
if (!typeGroups.ContainsKey(agent.CharacterType))
{
typeGroups[agent.CharacterType] = 0;
}
typeGroups[agent.CharacterType]++;
}
foreach (var kvp in typeGroups)
{
var memory = AIRoomMemoryManager.GetMemory(kvp.Key);
stats += $"{kvp.Key}: {kvp.Value} agents, {memory.VisitedCount} rooms explored\n";
}
return stats;
}
///
/// Gets the initial agent count setting
///
public int InitialAgentCount => initialAgentCount;
///
/// Sets the initial agent count (affects next reset)
///
public void SetInitialAgentCount(int count)
{
initialAgentCount = count;
}
///
/// Gets the count of active agents
///
public int GetActiveAgentCount()
{
return activeAgents.Count;
}
///
/// Gets the list of active agents
///
public List GetActiveAgents()
{
return activeAgents;
}
public IReadOnlyList GetActiveGroups() => activeGroups;
///
/// Updates path visibility for all active agents
///
public void SetPathVisibilityForAllAgents(bool show)
{
showAgentPaths = show;
foreach (var agent in activeAgents)
{
if (agent != null)
{
agent.SetShowPath(show);
}
}
Debug.Log($"Path visibility set to: {show} for {activeAgents.Count} agents");
}
}