AIAgentManager.cs 10 KB

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