BattleSetup.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. using UnityEngine.AI;
  4. public class BattleSetup : MonoBehaviour
  5. {
  6. List<GameObject> unplacedEnemyCharacters = new List<GameObject>();
  7. List<GameObject> placedPlayerCharacters = new List<GameObject>();
  8. List<GameObject> placedEnemyCharacters = new List<GameObject>();
  9. private List<GameObject> playerCharacters = new List<GameObject>();
  10. private List<GameObject> enemyCharacters = new List<GameObject>();
  11. private List<CharacterSelection> playerSelections = new List<CharacterSelection>();
  12. private List<CharacterSelection> enemySelections = new List<CharacterSelection>();
  13. [Tooltip("Select the layer(s) that represent the ground for raycasting.")]
  14. public LayerMask groundLayerMask;
  15. public GameObject playerPrefab; // Assign in the inspector or through GameManager
  16. public GameObject enemyPrefab; // Assign in the inspector or through GameManager
  17. public GameObject SwordPrefab; // Assign in the inspector or through GameManager
  18. public GameObject BowPrefab; // Assign in the inspector or through GameManager
  19. GameObject playerSpawnArea;
  20. GameObject enemySpawnArea;
  21. Bounds playerSpawnBounds;
  22. private Camera mainCamera;
  23. private GameObject currentPlacingCharacterInstance;
  24. private int nextPlayerCharacterPrefabIndex = 0;
  25. private bool isPlacingPlayerCharacters = false;
  26. void Awake()
  27. {
  28. mainCamera = Camera.main;
  29. if (mainCamera == null)
  30. {
  31. Debug.LogError("Main camera not found. Please ensure there is a camera tagged as 'MainCamera' in the scene.");
  32. enabled = false;
  33. return;
  34. }
  35. playerCharacters.Clear();
  36. enemyCharacters.Clear();
  37. // Store the selections for use during placement
  38. playerSelections = new List<CharacterSelection>(BattleSetupData.playerSelections);
  39. enemySelections = new List<CharacterSelection>(BattleSetupData.enemySelections);
  40. // Initialize the lists with characters from the game manager or other sources
  41. playerSpawnArea = GameObject.Find("PlayerSpawnArea");
  42. enemySpawnArea = GameObject.Find("EnemySpawnArea");
  43. if (playerSpawnArea == null)
  44. {
  45. Debug.LogError("PlayerSpawnArea GameObject not found in scene. Please ensure there is a GameObject named 'PlayerSpawnArea' in the scene.");
  46. enabled = false;
  47. return;
  48. }
  49. Collider playerSpawnAreaCollider = playerSpawnArea.GetComponent<Collider>();
  50. if (playerSpawnAreaCollider == null)
  51. {
  52. Debug.LogError("PlayerSpawnArea does not have a collider component.");
  53. enabled = false;
  54. return;
  55. }
  56. playerSpawnBounds = playerSpawnAreaCollider.bounds;
  57. if (enemySpawnArea == null)
  58. {
  59. // Enemy placement might still work if unplacedEnemyCharacters is empty
  60. Debug.LogWarning("EnemySpawnArea GameObject not found in the scene. Enemy placement might be affected.");
  61. }
  62. // Call the setup methods to place characters
  63. PlaceEnemyCharacters();
  64. InitiatePlayerCharacterPlacement();
  65. }
  66. void EquipWeapon(Character character, string weaponType)
  67. {
  68. Weapon weapon = null;
  69. if (weaponType == "Sword")
  70. {
  71. var weaponObj = Instantiate(SwordPrefab, character.transform);
  72. weapon = weaponObj.GetComponent<Weapon>();
  73. }
  74. else if (weaponType == "Bow")
  75. {
  76. var weaponObj = Instantiate(BowPrefab, character.transform);
  77. weapon = weaponObj.GetComponent<Weapon>();
  78. }
  79. else
  80. {
  81. Debug.LogWarning($"Unknown weapon type: {weaponType}. No weapon equipped.");
  82. }
  83. if (weapon != null)
  84. {
  85. Transform attachPoint = character.transform.Find("WeaponAttachPoint");
  86. if (attachPoint != null)
  87. {
  88. weapon.transform.SetParent(attachPoint, false);
  89. weapon.transform.localPosition = Vector3.zero;
  90. weapon.transform.localRotation = Quaternion.identity;
  91. }
  92. else
  93. {
  94. weapon.transform.SetParent(character.transform, false);
  95. weapon.transform.localPosition = new Vector3(0.5f, 0, 0);
  96. }
  97. character.Weapon = weapon;
  98. weapon.SetWielder(character);
  99. }
  100. }
  101. void Update()
  102. {
  103. if (!isPlacingPlayerCharacters || currentPlacingCharacterInstance == null)
  104. {
  105. return;
  106. }
  107. HandleCharacterPlacement();
  108. }
  109. private void InitiatePlayerCharacterPlacement()
  110. {
  111. if (playerSelections.Count == 0)
  112. {
  113. return;
  114. }
  115. isPlacingPlayerCharacters = true;
  116. nextPlayerCharacterPrefabIndex = 0;
  117. SpawnNextPlayerCharacterForPlacement();
  118. }
  119. private void SpawnNextPlayerCharacterForPlacement()
  120. {
  121. if (currentPlacingCharacterInstance != null)
  122. {
  123. // This case should ideally not happen if logic flows correctly,
  124. // but as a safeguard if a previous instance wasn't cleaned up.
  125. Destroy(currentPlacingCharacterInstance);
  126. currentPlacingCharacterInstance = null;
  127. }
  128. if (nextPlayerCharacterPrefabIndex < playerSelections.Count)
  129. {
  130. var selection = playerSelections[nextPlayerCharacterPrefabIndex];
  131. currentPlacingCharacterInstance = Instantiate(playerPrefab);
  132. currentPlacingCharacterInstance.name = selection.characterName;
  133. var character = currentPlacingCharacterInstance.GetComponent<Character>();
  134. if (character != null)
  135. {
  136. EquipWeapon(character, selection.weaponType);
  137. character.CharacterName = selection.characterName + " " + (nextPlayerCharacterPrefabIndex + 1);
  138. }
  139. currentPlacingCharacterInstance.GetComponent<NavMeshAgent>().enabled = false;
  140. // Optionally disable AI or other components that might interfere with placement
  141. // e.g., currentPlacingCharacterInstance.GetComponent<AIController>()?.enabled = false;
  142. }
  143. else
  144. {
  145. FinalizePlayerPlacement();
  146. }
  147. }
  148. private void HandleCharacterPlacement()
  149. {
  150. Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
  151. Debug.DrawRay(ray.origin, ray.direction * 200f, Color.yellow); // Visualize the ray
  152. if (Physics.Raycast(ray, out RaycastHit hit, 200f, groundLayerMask))
  153. {
  154. Vector3 targetPosition = hit.point;
  155. // Clamp position to playerSpawnArea bound (X and Z axis)
  156. targetPosition.x = Mathf.Clamp(targetPosition.x, playerSpawnBounds.min.x, playerSpawnBounds.max.x);
  157. targetPosition.z = Mathf.Clamp(targetPosition.z, playerSpawnBounds.min.z, playerSpawnBounds.max.z);
  158. // Y is determined by the raycast hit on the ground
  159. // --- Direct Y Position Calculation for Mouse-Following Character ---
  160. // This approach was found to be more stable than repeatedly calling AdjustCharacterOnGround.
  161. // It assumes a standard Unity CapsuleCollider where the pivot is at the center.
  162. Vector3 finalCharacterPosition = targetPosition; // targetPosition.y is from the ground hit (hit.point.y)
  163. CapsuleCollider capCollider = currentPlacingCharacterInstance.GetComponent<CapsuleCollider>();
  164. if (capCollider != null)
  165. {
  166. // For a capsule, its pivot is typically at its center.
  167. // To place its bottom on targetPosition.y (ground level),
  168. // its center (pivot) needs to be at targetPosition.y + (height / 2).
  169. // We also account for the character's local scale.
  170. finalCharacterPosition.y = targetPosition.y + (capCollider.height * currentPlacingCharacterInstance.transform.localScale.y / 2f);
  171. }
  172. else
  173. {
  174. // Fallback if not a capsule or if a different pivot is used.
  175. // This might need adjustment based on your character's actual pivot and collider.
  176. Debug.LogWarning($"'{currentPlacingCharacterInstance.name}' does not have a CapsuleCollider. Using default Y offset for placement. Adjust if incorrect.");
  177. finalCharacterPosition.y = targetPosition.y + 0.5f; // Example offset, adjust as needed
  178. }
  179. currentPlacingCharacterInstance.transform.position = finalCharacterPosition;
  180. // AdjustCharacterOnGround(currentPlacingCharacterInstance); // No longer called every frame for mouse-follow
  181. }
  182. else
  183. {
  184. }
  185. // Else mouse is not over valid ground, character stays at last valid position or initial spawn
  186. if (Input.GetMouseButtonDown(0))
  187. { // Left-click to place character
  188. // Check if the current position is valid (within bounds and not overlapping)
  189. // The position is already clamped and on ground due to the logic above.
  190. // We primarily need to check for overlaps.
  191. if (!IsOverlappingOtherCharacters(currentPlacingCharacterInstance))
  192. {
  193. PlaceCurrentCharacter();
  194. }
  195. else
  196. {
  197. Debug.LogWarning("Cannot place character: Overlapping with another character.");
  198. // Optionally, you could provide feedback to the player about the overlap (e.g. change color?).
  199. }
  200. }
  201. if (Input.GetMouseButtonDown(1))
  202. { // Right-click to cancel placement
  203. if (placedPlayerCharacters.Count > 0)
  204. {
  205. FinalizePlayerPlacement();
  206. }
  207. else
  208. {
  209. }
  210. }
  211. }
  212. private void PlaceCurrentCharacter()
  213. {
  214. placedPlayerCharacters.Add(currentPlacingCharacterInstance);
  215. currentPlacingCharacterInstance = null;
  216. nextPlayerCharacterPrefabIndex++;
  217. SpawnNextPlayerCharacterForPlacement(); // Spawn next character for placement
  218. }
  219. private void AdjustCharacterOnGround(GameObject character)
  220. {
  221. if (character == null)
  222. {
  223. return;
  224. }
  225. Collider charCollider = character.GetComponent<Collider>();
  226. if (charCollider != null)
  227. {
  228. // This ensured the lowest point of the collider is at character.transform.position.y
  229. // (which should be the ground hit point)
  230. // currentY is the Y-coordinate of the character's pivot.
  231. // We want the character's collider bottom (feet) to be at this Y-level.
  232. float currentY = character.transform.position.y;
  233. float colliderMinYWorld = charCollider.bounds.min.y;
  234. float verticalOffset = currentY - colliderMinYWorld;
  235. const float tolerance = 0.001f; // Small tolerance to avoid floating point issues
  236. if (Mathf.Abs(verticalOffset) > tolerance)
  237. {
  238. character.transform.position += Vector3.up * verticalOffset;
  239. }
  240. }
  241. else
  242. {
  243. Debug.LogWarning($"Character: {character.name} has no collider. Cannot accurately adjust to ground.");
  244. }
  245. }
  246. private bool IsOverlappingOtherCharacters(GameObject character)
  247. {
  248. Collider newCharCollider = character.GetComponent<Collider>();
  249. if (newCharCollider == null)
  250. {
  251. Debug.LogWarning($"Character: {character.name} has no collider. Cannot check for overlaps.");
  252. return false; // Or true to precent placement if no collider
  253. }
  254. Bounds newCharBounds = newCharCollider.bounds;
  255. foreach (GameObject placedCharacter in placedPlayerCharacters)
  256. {
  257. Collider otherCollider = placedCharacter.GetComponent<Collider>();
  258. if (otherCollider != null && newCharBounds.Intersects(otherCollider.bounds))
  259. {
  260. return true;
  261. }
  262. }
  263. foreach (GameObject placedEnemy in placedEnemyCharacters)
  264. {
  265. Collider otherCollider = placedEnemy.GetComponent<Collider>();
  266. if (otherCollider != null && newCharBounds.Intersects(otherCollider.bounds))
  267. {
  268. return true;
  269. }
  270. }
  271. return false;
  272. }
  273. private void FinalizePlayerPlacement()
  274. {
  275. isPlacingPlayerCharacters = false;
  276. if (currentPlacingCharacterInstance != null)
  277. {
  278. Destroy(currentPlacingCharacterInstance); // Destroy the preview instance if it wasn't placed
  279. currentPlacingCharacterInstance = null;
  280. }
  281. EndPlacementPhase();
  282. // Here you might want to trigger the next phase of your game, e.g., start the battle.
  283. GameManager.Instance.StartBattle(placedPlayerCharacters, placedEnemyCharacters);
  284. Destroy(playerSpawnArea);
  285. Destroy(enemySpawnArea);
  286. playerSpawnArea = null;
  287. enemySpawnArea = null;
  288. }
  289. private void EndPlacementPhase()
  290. {
  291. // This method can be used to clean up or finalize the placement phase.
  292. // For example, you might want to enable AI, re-enable NavMeshAgents, etc.
  293. foreach (GameObject character in placedPlayerCharacters)
  294. {
  295. NavMeshAgent agent = character.GetComponent<NavMeshAgent>();
  296. if (agent != null)
  297. {
  298. agent.enabled = true; // Re-enable NavMeshAgent
  299. }
  300. // Enable AI or other components as needed
  301. // e.g., character.GetComponent<AIController>()?.enabled = true;
  302. }
  303. foreach (GameObject character in enemyCharacters)
  304. {
  305. NavMeshAgent agent = character.GetComponent<NavMeshAgent>();
  306. if (agent != null)
  307. {
  308. agent.enabled = true; // Re-enable NavMeshAgent
  309. }
  310. // Enable AI or other components as needed
  311. // e.g., character.GetComponent<AIController>()?.enabled = true;
  312. }
  313. }
  314. private void PlaceEnemyCharacters()
  315. {
  316. Collider spawnAreaCollider = enemySpawnArea.GetComponent<Collider>();
  317. if (spawnAreaCollider == null)
  318. {
  319. Debug.LogError("Enemy spawn area does not have a collider component.");
  320. return;
  321. }
  322. Bounds spawnBounds = spawnAreaCollider.bounds;
  323. for (int i = enemySelections.Count - 1; i >= 0; i--)
  324. {
  325. var selection = enemySelections[i];
  326. float randomX = UnityEngine.Random.Range(spawnBounds.min.x, spawnBounds.max.x);
  327. float randomZ = UnityEngine.Random.Range(spawnBounds.min.z, spawnBounds.max.z);
  328. Vector3 rayOrigin = new Vector3(randomX, enemySpawnArea.transform.position.y + 10f, randomZ);
  329. Vector3 spawnPosition = new Vector3(randomX, enemySpawnArea.transform.position.y, randomZ);
  330. // Raycast to find the ground
  331. if (Physics.Raycast(rayOrigin, Vector3.down, out RaycastHit hit, 200f, groundLayerMask))
  332. {
  333. spawnPosition = hit.point;
  334. }
  335. else
  336. {
  337. Debug.LogWarning($"Raycast did not hit ground below ({randomX}, {randomZ}). Placing enemy at spawn area Y level.");
  338. }
  339. GameObject placedEnemy = Instantiate(enemyPrefab, spawnPosition, Quaternion.identity);
  340. Character character = placedEnemy.GetComponent<Character>();
  341. if (character != null)
  342. {
  343. EquipWeapon(character, selection.weaponType);
  344. character.CharacterName = selection.characterName + "_" + (i + 1);
  345. }
  346. AdjustCharacterOnGround(placedEnemy); // Adjust the enemy to the ground
  347. placedEnemyCharacters.Add(placedEnemy);
  348. }
  349. }
  350. }