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"); } }