MonsterSpawner.cs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. namespace MazeGen
  4. {
  5. /// <summary>
  6. /// Spawns visual monster GameObjects when rooms with encounters are generated.
  7. /// Listens to MazeGenerator's RoomGenerated event and creates MonsterView children
  8. /// in each room that has an encounter.
  9. ///
  10. /// Also keeps the views honest after a fight: <see cref="RefreshRoom"/> removes the views of
  11. /// monsters that died, so a cleared room actually looks cleared.
  12. /// </summary>
  13. public class MonsterSpawner : MonoBehaviour
  14. {
  15. [SerializeField] private MazeGenerator mazeGenerator;
  16. // Container transform to organize spawned monsters in the hierarchy
  17. private Transform _monstersContainer;
  18. // Views per room id, so a resolved fight can clean up just that room's dead.
  19. private readonly Dictionary<int, List<MonsterView>> _viewsByRoom = new Dictionary<int, List<MonsterView>>();
  20. // Subscribing in Awake rather than Start: MazeSceneBuilder generates the start rooms in
  21. // its own Start, and component Start order on a shared GameObject isn't something to rely
  22. // on - subscribing a frame-phase earlier guarantees no room generation is missed.
  23. private void Awake()
  24. {
  25. if (mazeGenerator == null)
  26. mazeGenerator = GetComponent<MazeGenerator>();
  27. if (mazeGenerator == null)
  28. {
  29. Debug.LogError("[MonsterSpawner] Could not find MazeGenerator!");
  30. return;
  31. }
  32. // Create a container for all spawned monsters
  33. _monstersContainer = new GameObject("SpawnedMonsters").transform;
  34. _monstersContainer.SetParent(transform);
  35. _monstersContainer.localPosition = Vector3.zero;
  36. // Subscribe to room generation event
  37. mazeGenerator.RoomGenerated += OnRoomGenerated;
  38. }
  39. private void OnDestroy()
  40. {
  41. if (mazeGenerator != null)
  42. mazeGenerator.RoomGenerated -= OnRoomGenerated;
  43. }
  44. /// <summary>
  45. /// Called whenever a new room is generated. If it has an encounter,
  46. /// spawn visual monsters in that room.
  47. /// </summary>
  48. private void OnRoomGenerated(Room room)
  49. {
  50. if (room.Encounter == null || room.Encounter.Monsters.Count == 0)
  51. return;
  52. SpawnMonstersInRoom(room);
  53. }
  54. /// <summary>
  55. /// Drops the views of any monster in this room that is now dead. Wire this to
  56. /// CombatDirector.FightResolved so the map reflects the fight's outcome.
  57. /// </summary>
  58. public void RefreshRoom(Room room)
  59. {
  60. if (room == null || !_viewsByRoom.TryGetValue(room.Id, out var views)) return;
  61. for (int i = views.Count - 1; i >= 0; i--)
  62. {
  63. MonsterView view = views[i];
  64. if (view == null)
  65. {
  66. views.RemoveAt(i);
  67. continue;
  68. }
  69. if (view.MonsterData != null && view.MonsterData.IsDefeated)
  70. {
  71. views.RemoveAt(i);
  72. view.OnDefeated();
  73. }
  74. }
  75. if (views.Count == 0)
  76. _viewsByRoom.Remove(room.Id);
  77. }
  78. /// <summary>
  79. /// Creates visual MonsterView GameObjects scattered around the room.
  80. /// </summary>
  81. private void SpawnMonstersInRoom(Room room)
  82. {
  83. var encounter = room.Encounter;
  84. int monsterCount = encounter.Monsters.Count;
  85. // Create a parent for this room's monsters
  86. var roomMonstersParent = new GameObject($"Room{room.Id}_Monsters").transform;
  87. roomMonstersParent.SetParent(_monstersContainer);
  88. roomMonstersParent.localPosition = Vector3.zero;
  89. var views = new List<MonsterView>(monsterCount);
  90. _viewsByRoom[room.Id] = views;
  91. // Scatter monsters around the room in a circle
  92. float angleStep = 360f / monsterCount;
  93. float scatterRadius = room.Radius * 0.5f; // Spawn within the room
  94. for (int i = 0; i < monsterCount; i++)
  95. {
  96. var monsterData = encounter.Monsters[i];
  97. float angle = i * angleStep * Mathf.Deg2Rad;
  98. Vector2 offset = new Vector2(
  99. Mathf.Cos(angle) * scatterRadius,
  100. Mathf.Sin(angle) * scatterRadius
  101. );
  102. Vector2 spawnPos = room.Position + offset;
  103. views.Add(SpawnMonster(monsterData, spawnPos, roomMonstersParent));
  104. }
  105. }
  106. /// <summary>
  107. /// Instantiates a single MonsterView at the given position.
  108. /// </summary>
  109. private MonsterView SpawnMonster(MonsterEncounter.MonsterInstance monsterData, Vector2 position, Transform parent)
  110. {
  111. var monsterGO = new GameObject(monsterData.Template.MonsterName);
  112. monsterGO.transform.SetParent(parent);
  113. monsterGO.transform.position = position;
  114. var monsterView = monsterGO.AddComponent<MonsterView>();
  115. monsterView.Initialize(monsterData, position);
  116. // Optional: add a collider for future click-detection or physics
  117. var collider = monsterGO.AddComponent<CircleCollider2D>();
  118. collider.radius = 0.15f;
  119. return monsterView;
  120. }
  121. }
  122. }