AIAgentManager.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. /// <summary>
  5. /// Manages spawning and tracking of AI agents in the maze
  6. /// Supports configurable spawn counts and runtime spawning
  7. /// </summary>
  8. public class AIAgentManager : MonoBehaviour
  9. {
  10. [Header("Agent Spawning")]
  11. [SerializeField] private int initialAgentCount = 10;
  12. [SerializeField] private string agentCharacterType = "Default";
  13. [SerializeField] private float spawnDelay = 0.1f;
  14. [Header("Agent Prefab")]
  15. [SerializeField] private GameObject agentPrefab;
  16. [Header("Visual Settings")]
  17. [Tooltip("Show path debug lines for all agents. Disable for large agent counts (>100) for major performance gains.")]
  18. [SerializeField] private bool showAgentPaths = false;
  19. [SerializeField] private Color agentPathColor = Color.yellow;
  20. [SerializeField] private Material agentMaterial;
  21. [Header("Grouping")]
  22. [SerializeField] private float groupCheckInterval = 2f;
  23. [SerializeField] private float groupingRadius = 1.5f;
  24. private MazeController mazeController;
  25. private List<AIAgent> activeAgents = new();
  26. private readonly List<AgentGroup> activeGroups = new();
  27. private int nextAgentId = 0;
  28. private bool isInitialized = false;
  29. private float lastGroupCheck = 0f;
  30. // ---- Death statistics ----
  31. private int agentsKilledByMonsters = 0;
  32. private int monstersKilledByAgents = 0;
  33. /// <summary>How many agents have been killed by monsters this run.</summary>
  34. public int AgentsKilledByMonsters => agentsKilledByMonsters;
  35. /// <summary>How many monsters agents have killed this run.</summary>
  36. public int MonstersKilledByAgents => monstersKilledByAgents;
  37. /// <summary>
  38. /// Called by a dying AIAgent so the manager can update stats and clean up.
  39. /// </summary>
  40. public void RegisterAgentDeath(AIAgent agent)
  41. {
  42. agentsKilledByMonsters++;
  43. activeAgents.Remove(agent);
  44. Debug.Log($"[Manager] Agent {agent.AgentName} killed. Total killed: {agentsKilledByMonsters}");
  45. }
  46. /// <summary>Called by an agent after landing a killing blow on a monster.</summary>
  47. public void RegisterMonsterKill()
  48. {
  49. monstersKilledByAgents++;
  50. }
  51. void Awake()
  52. {
  53. mazeController = GetComponent<MazeController>();
  54. if (mazeController == null)
  55. {
  56. mazeController = FindAnyObjectByType<MazeController>();
  57. }
  58. }
  59. void Update()
  60. {
  61. if (!isInitialized) return;
  62. if (Time.time - lastGroupCheck < groupCheckInterval) return;
  63. lastGroupCheck = Time.time;
  64. EvaluateGrouping();
  65. }
  66. /// <summary>
  67. /// Checks all solo/leader agents for nearby agents and tries to form or join groups.
  68. /// </summary>
  69. private void EvaluateGrouping()
  70. {
  71. var candidates = activeAgents
  72. .Where(a => a != null && !a.IsGroupFollower && !a.HasReachedGoal)
  73. .ToList();
  74. for (int i = 0; i < candidates.Count; i++)
  75. {
  76. AIAgent a = candidates[i];
  77. if (Random.value > a.GroupUpAffinity) continue;
  78. for (int j = i + 1; j < candidates.Count; j++)
  79. {
  80. AIAgent b = candidates[j];
  81. if (Vector3.Distance(a.transform.position, b.transform.position) > groupingRadius) continue;
  82. if (Random.value > b.GroupUpAffinity) continue;
  83. if (a.Group != null && !a.IsGroupFollower && b.Group == null)
  84. a.Group.TryAddMember(b);
  85. else if (b.Group != null && !b.IsGroupFollower && a.Group == null)
  86. b.Group.TryAddMember(a);
  87. else if (a.Group == null && b.Group == null)
  88. {
  89. var newGroup = new AgentGroup(a);
  90. newGroup.TryAddMember(b);
  91. activeGroups.Add(newGroup);
  92. }
  93. }
  94. }
  95. activeGroups.RemoveAll(g => g.Size <= 1);
  96. }
  97. void OnValidate()
  98. {
  99. // Called when inspector values change (both in play mode and edit mode)
  100. // Update all active agents' path visibility
  101. if (isInitialized && activeAgents.Count > 0)
  102. {
  103. SetPathVisibilityForAllAgents(showAgentPaths);
  104. }
  105. }
  106. void Start()
  107. {
  108. if (mazeController == null)
  109. {
  110. Debug.LogError("AIAgentManager: MazeController not found!");
  111. enabled = false;
  112. return;
  113. }
  114. // Create agent prefab if not assigned
  115. if (agentPrefab == null)
  116. {
  117. CreateDefaultAgentPrefab();
  118. }
  119. // Spawn initial agents
  120. StartCoroutine(SpawnAgentsCoroutine(initialAgentCount));
  121. isInitialized = true;
  122. }
  123. /// <summary>
  124. /// Creates a default agent prefab if one isn't assigned
  125. /// </summary>
  126. private void CreateDefaultAgentPrefab()
  127. {
  128. GameObject prefab = new GameObject("AIAgent");
  129. prefab.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f);
  130. // Use 3D collider instead of 2D for 3D environment
  131. var collider = prefab.AddComponent<SphereCollider>();
  132. collider.radius = 1.0f;
  133. // Add mesh renderer with a simple sphere
  134. var meshFilter = prefab.AddComponent<MeshFilter>();
  135. var meshRenderer = prefab.AddComponent<MeshRenderer>();
  136. // Use Unity's built-in sphere primitive mesh
  137. meshFilter.mesh = Resources.GetBuiltinResource<Mesh>("Sphere.fbx");
  138. // Create a cyan material using URP shader (Universal Render Pipeline)
  139. var mat = new Material(Shader.Find("Universal Render Pipeline/Lit"));
  140. if (mat.shader == null)
  141. {
  142. // Fallback to simpler URP shader if Lit is not available
  143. mat = new Material(Shader.Find("Universal Render Pipeline/Simple Lit"));
  144. }
  145. if (mat.shader == null)
  146. {
  147. // Last resort - use Standard shader
  148. mat = new Material(Shader.Find("Standard"));
  149. }
  150. mat.color = Color.cyan;
  151. meshRenderer.material = mat;
  152. prefab.AddComponent<AIAgent>();
  153. agentPrefab = prefab;
  154. }
  155. /// <summary>
  156. /// Spawns agents with delay
  157. /// </summary>
  158. private System.Collections.IEnumerator SpawnAgentsCoroutine(int count)
  159. {
  160. for (int i = 0; i < count; i++)
  161. {
  162. SpawnAgent();
  163. yield return new WaitForSeconds(spawnDelay);
  164. }
  165. }
  166. /// <summary>
  167. /// Spawns a single AI agent
  168. /// </summary>
  169. public AIAgent SpawnAgent()
  170. {
  171. if (agentPrefab == null)
  172. {
  173. Debug.LogError("AIAgentManager: Agent prefab not set!");
  174. return null;
  175. }
  176. GameObject agentGO = Instantiate(agentPrefab);
  177. agentGO.name = $"AIAgent_{agentCharacterType}_{nextAgentId}";
  178. var agent = agentGO.GetComponent<AIAgent>();
  179. agent.SetShowPath(showAgentPaths);
  180. if (agent == null)
  181. {
  182. agent = agentGO.AddComponent<AIAgent>();
  183. }
  184. // Set agent properties via reflection or public properties
  185. var idField = typeof(AIAgent).GetField("agentId",
  186. System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
  187. var typeField = typeof(AIAgent).GetField("agentCharacterType",
  188. System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
  189. if (idField != null) idField.SetValue(agent, nextAgentId);
  190. if (typeField != null) typeField.SetValue(agent, agentCharacterType);
  191. activeAgents.Add(agent);
  192. nextAgentId++;
  193. // Suppress per-agent spawn log for large counts (1000 Debug.Log calls is significant overhead)
  194. // Debug.Log($"Spawned agent {nextAgentId - 1} ({agentCharacterType}). Total agents: {activeAgents.Count}");
  195. return agent;
  196. }
  197. /// <summary>
  198. /// Spawns multiple agents at once
  199. /// </summary>
  200. public void SpawnAgents(int count)
  201. {
  202. StartCoroutine(SpawnAgentsCoroutine(count));
  203. }
  204. /// <summary>
  205. /// Removes and destroys an agent
  206. /// </summary>
  207. public void RemoveAgent(AIAgent agent)
  208. {
  209. if (activeAgents.Remove(agent))
  210. {
  211. Destroy(agent.gameObject);
  212. Debug.Log($"Removed agent {agent.AgentId}. Remaining: {activeAgents.Count}");
  213. }
  214. }
  215. /// <summary>
  216. /// Gets agents of a specific character type
  217. /// </summary>
  218. public List<AIAgent> GetAgentsByType(string characterType)
  219. {
  220. var agents = new List<AIAgent>();
  221. foreach (var agent in activeAgents)
  222. {
  223. if (agent.CharacterType == characterType)
  224. {
  225. agents.Add(agent);
  226. }
  227. }
  228. return agents;
  229. }
  230. /// <summary>
  231. /// Gets the count of active agents
  232. /// </summary>
  233. public int GetAgentCount()
  234. {
  235. return activeAgents.Count;
  236. }
  237. /// <summary>
  238. /// Gets agent count by character type
  239. /// </summary>
  240. public int GetAgentCountByType(string characterType)
  241. {
  242. return GetAgentsByType(characterType).Count;
  243. }
  244. /// <summary>
  245. /// Clears all agents and resets
  246. /// </summary>
  247. public void ClearAllAgents()
  248. {
  249. foreach (var agent in activeAgents)
  250. {
  251. Destroy(agent.gameObject);
  252. }
  253. activeAgents.Clear();
  254. nextAgentId = 0;
  255. AIRoomMemoryManager.ClearAllMemories();
  256. }
  257. /// <summary>
  258. /// Resets agents for a new maze
  259. /// </summary>
  260. public void ResetForNewMaze()
  261. {
  262. ClearAllAgents();
  263. if (isInitialized)
  264. {
  265. StartCoroutine(SpawnAgentsCoroutine(initialAgentCount));
  266. }
  267. }
  268. /// <summary>
  269. /// Gets UI info about agents
  270. /// </summary>
  271. public string GetAgentStats()
  272. {
  273. int total = activeAgents.Count;
  274. string stats = $"Total Agents: {total}\n";
  275. var typeGroups = new Dictionary<string, int>();
  276. foreach (var agent in activeAgents)
  277. {
  278. if (!typeGroups.ContainsKey(agent.CharacterType))
  279. {
  280. typeGroups[agent.CharacterType] = 0;
  281. }
  282. typeGroups[agent.CharacterType]++;
  283. }
  284. foreach (var kvp in typeGroups)
  285. {
  286. var memory = AIRoomMemoryManager.GetMemory(kvp.Key);
  287. stats += $"{kvp.Key}: {kvp.Value} agents, {memory.VisitedCount} rooms explored\n";
  288. }
  289. return stats;
  290. }
  291. /// <summary>
  292. /// Gets the initial agent count setting
  293. /// </summary>
  294. public int InitialAgentCount => initialAgentCount;
  295. /// <summary>
  296. /// Sets the initial agent count (affects next reset)
  297. /// </summary>
  298. public void SetInitialAgentCount(int count)
  299. {
  300. initialAgentCount = count;
  301. }
  302. /// <summary>
  303. /// Gets the count of active agents
  304. /// </summary>
  305. public int GetActiveAgentCount()
  306. {
  307. return activeAgents.Count;
  308. }
  309. /// <summary>
  310. /// Gets the list of active agents
  311. /// </summary>
  312. public List<AIAgent> GetActiveAgents()
  313. {
  314. return activeAgents;
  315. }
  316. public IReadOnlyList<AgentGroup> GetActiveGroups() => activeGroups;
  317. /// <summary>
  318. /// Updates path visibility for all active agents
  319. /// </summary>
  320. public void SetPathVisibilityForAllAgents(bool show)
  321. {
  322. showAgentPaths = show;
  323. foreach (var agent in activeAgents)
  324. {
  325. if (agent != null)
  326. {
  327. agent.SetShowPath(show);
  328. }
  329. }
  330. Debug.Log($"Path visibility set to: {show} for {activeAgents.Count} agents");
  331. }
  332. }