using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class BattleSetup : MonoBehaviour { List unplacedEnemyCharacters = new List(); List placedPlayerCharacters = new List(); List placedEnemyCharacters = new List(); private List playerCharacters = new List(); private List enemyCharacters = new List(); private List playerSelections = new List(); private List enemySelections = new List(); [Tooltip("Select the layer(s) that represent the ground for raycasting.")] public LayerMask groundLayerMask; public GameObject playerPrefab; // Assign in the inspector or through GameManager public GameObject enemyPrefab; // Assign in the inspector or through GameManager public GameObject SwordPrefab; // Assign in the inspector or through GameManager public GameObject BowPrefab; // Assign in the inspector or through GameManager GameObject playerSpawnArea; GameObject enemySpawnArea; Bounds playerSpawnBounds; private Camera mainCamera; private GameObject currentPlacingCharacterInstance; private int nextPlayerCharacterPrefabIndex = 0; private bool isPlacingPlayerCharacters = false; void Awake() { mainCamera = Camera.main; if (mainCamera == null) { Debug.LogError("Main camera not found. Please ensure there is a camera tagged as 'MainCamera' in the scene."); enabled = false; return; } playerCharacters.Clear(); enemyCharacters.Clear(); // Store the selections for use during placement playerSelections = new List(BattleSetupData.playerSelections); enemySelections = new List(BattleSetupData.enemySelections); // Initialize the lists with characters from the game manager or other sources playerSpawnArea = GameObject.Find("PlayerSpawnArea"); enemySpawnArea = GameObject.Find("EnemySpawnArea"); if (playerSpawnArea == null) { Debug.LogError("PlayerSpawnArea GameObject not found in scene. Please ensure there is a GameObject named 'PlayerSpawnArea' in the scene."); enabled = false; return; } Collider playerSpawnAreaCollider = playerSpawnArea.GetComponent(); if (playerSpawnAreaCollider == null) { Debug.LogError("PlayerSpawnArea does not have a collider component."); enabled = false; return; } playerSpawnBounds = playerSpawnAreaCollider.bounds; if (enemySpawnArea == null) { // Enemy placement might still work if unplacedEnemyCharacters is empty Debug.LogWarning("EnemySpawnArea GameObject not found in the scene. Enemy placement might be affected."); } // Call the setup methods to place characters PlaceEnemyCharacters(); InitiatePlayerCharacterPlacement(); } void EquipWeapon(Character character, string weaponType) { Weapon weapon = null; if (weaponType == "Sword") { var weaponObj = Instantiate(SwordPrefab, character.transform); weapon = weaponObj.GetComponent(); } else if (weaponType == "Bow") { var weaponObj = Instantiate(BowPrefab, character.transform); weapon = weaponObj.GetComponent(); } else { Debug.LogWarning($"Unknown weapon type: {weaponType}. No weapon equipped."); } if (weapon != null) { Transform attachPoint = character.transform.Find("WeaponAttachPoint"); if (attachPoint != null) { weapon.transform.SetParent(attachPoint, false); weapon.transform.localPosition = Vector3.zero; weapon.transform.localRotation = Quaternion.identity; } else { weapon.transform.SetParent(character.transform, false); weapon.transform.localPosition = new Vector3(0.5f, 0, 0); } character.Weapon = weapon; weapon.SetWielder(character); } } void Update() { if (!isPlacingPlayerCharacters || currentPlacingCharacterInstance == null) { return; } HandleCharacterPlacement(); } private void InitiatePlayerCharacterPlacement() { if (playerSelections.Count == 0) { return; } isPlacingPlayerCharacters = true; nextPlayerCharacterPrefabIndex = 0; SpawnNextPlayerCharacterForPlacement(); } private void SpawnNextPlayerCharacterForPlacement() { if (currentPlacingCharacterInstance != null) { // This case should ideally not happen if logic flows correctly, // but as a safeguard if a previous instance wasn't cleaned up. Destroy(currentPlacingCharacterInstance); currentPlacingCharacterInstance = null; } if (nextPlayerCharacterPrefabIndex < playerSelections.Count) { var selection = playerSelections[nextPlayerCharacterPrefabIndex]; currentPlacingCharacterInstance = Instantiate(playerPrefab); currentPlacingCharacterInstance.name = selection.characterName; var character = currentPlacingCharacterInstance.GetComponent(); if (character != null) { EquipWeapon(character, selection.weaponType); character.CharacterName = selection.characterName + " " + (nextPlayerCharacterPrefabIndex + 1); } currentPlacingCharacterInstance.GetComponent().enabled = false; // Optionally disable AI or other components that might interfere with placement // e.g., currentPlacingCharacterInstance.GetComponent()?.enabled = false; } else { FinalizePlayerPlacement(); } } private void HandleCharacterPlacement() { Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition); Debug.DrawRay(ray.origin, ray.direction * 200f, Color.yellow); // Visualize the ray if (Physics.Raycast(ray, out RaycastHit hit, 200f, groundLayerMask)) { Vector3 targetPosition = hit.point; // Clamp position to playerSpawnArea bound (X and Z axis) targetPosition.x = Mathf.Clamp(targetPosition.x, playerSpawnBounds.min.x, playerSpawnBounds.max.x); targetPosition.z = Mathf.Clamp(targetPosition.z, playerSpawnBounds.min.z, playerSpawnBounds.max.z); // Y is determined by the raycast hit on the ground // --- Direct Y Position Calculation for Mouse-Following Character --- // This approach was found to be more stable than repeatedly calling AdjustCharacterOnGround. // It assumes a standard Unity CapsuleCollider where the pivot is at the center. Vector3 finalCharacterPosition = targetPosition; // targetPosition.y is from the ground hit (hit.point.y) CapsuleCollider capCollider = currentPlacingCharacterInstance.GetComponent(); if (capCollider != null) { // For a capsule, its pivot is typically at its center. // To place its bottom on targetPosition.y (ground level), // its center (pivot) needs to be at targetPosition.y + (height / 2). // We also account for the character's local scale. finalCharacterPosition.y = targetPosition.y + (capCollider.height * currentPlacingCharacterInstance.transform.localScale.y / 2f); } else { // Fallback if not a capsule or if a different pivot is used. // This might need adjustment based on your character's actual pivot and collider. Debug.LogWarning($"'{currentPlacingCharacterInstance.name}' does not have a CapsuleCollider. Using default Y offset for placement. Adjust if incorrect."); finalCharacterPosition.y = targetPosition.y + 0.5f; // Example offset, adjust as needed } currentPlacingCharacterInstance.transform.position = finalCharacterPosition; // AdjustCharacterOnGround(currentPlacingCharacterInstance); // No longer called every frame for mouse-follow } else { } // Else mouse is not over valid ground, character stays at last valid position or initial spawn if (Input.GetMouseButtonDown(0)) { // Left-click to place character // Check if the current position is valid (within bounds and not overlapping) // The position is already clamped and on ground due to the logic above. // We primarily need to check for overlaps. if (!IsOverlappingOtherCharacters(currentPlacingCharacterInstance)) { PlaceCurrentCharacter(); } else { Debug.LogWarning("Cannot place character: Overlapping with another character."); // Optionally, you could provide feedback to the player about the overlap (e.g. change color?). } } if (Input.GetMouseButtonDown(1)) { // Right-click to cancel placement if (placedPlayerCharacters.Count > 0) { FinalizePlayerPlacement(); } else { } } } private void PlaceCurrentCharacter() { placedPlayerCharacters.Add(currentPlacingCharacterInstance); currentPlacingCharacterInstance = null; nextPlayerCharacterPrefabIndex++; SpawnNextPlayerCharacterForPlacement(); // Spawn next character for placement } private void AdjustCharacterOnGround(GameObject character) { if (character == null) { return; } Collider charCollider = character.GetComponent(); if (charCollider != null) { // This ensured the lowest point of the collider is at character.transform.position.y // (which should be the ground hit point) // currentY is the Y-coordinate of the character's pivot. // We want the character's collider bottom (feet) to be at this Y-level. float currentY = character.transform.position.y; float colliderMinYWorld = charCollider.bounds.min.y; float verticalOffset = currentY - colliderMinYWorld; const float tolerance = 0.001f; // Small tolerance to avoid floating point issues if (Mathf.Abs(verticalOffset) > tolerance) { character.transform.position += Vector3.up * verticalOffset; } } else { Debug.LogWarning($"Character: {character.name} has no collider. Cannot accurately adjust to ground."); } } private bool IsOverlappingOtherCharacters(GameObject character) { Collider newCharCollider = character.GetComponent(); if (newCharCollider == null) { Debug.LogWarning($"Character: {character.name} has no collider. Cannot check for overlaps."); return false; // Or true to precent placement if no collider } Bounds newCharBounds = newCharCollider.bounds; foreach (GameObject placedCharacter in placedPlayerCharacters) { Collider otherCollider = placedCharacter.GetComponent(); if (otherCollider != null && newCharBounds.Intersects(otherCollider.bounds)) { return true; } } foreach (GameObject placedEnemy in placedEnemyCharacters) { Collider otherCollider = placedEnemy.GetComponent(); if (otherCollider != null && newCharBounds.Intersects(otherCollider.bounds)) { return true; } } return false; } private void FinalizePlayerPlacement() { isPlacingPlayerCharacters = false; if (currentPlacingCharacterInstance != null) { Destroy(currentPlacingCharacterInstance); // Destroy the preview instance if it wasn't placed currentPlacingCharacterInstance = null; } EndPlacementPhase(); // Here you might want to trigger the next phase of your game, e.g., start the battle. GameManager.Instance.StartBattle(placedPlayerCharacters, placedEnemyCharacters); Destroy(playerSpawnArea); Destroy(enemySpawnArea); playerSpawnArea = null; enemySpawnArea = null; } private void EndPlacementPhase() { // This method can be used to clean up or finalize the placement phase. // For example, you might want to enable AI, re-enable NavMeshAgents, etc. foreach (GameObject character in placedPlayerCharacters) { NavMeshAgent agent = character.GetComponent(); if (agent != null) { agent.enabled = true; // Re-enable NavMeshAgent } // Enable AI or other components as needed // e.g., character.GetComponent()?.enabled = true; } foreach (GameObject character in enemyCharacters) { NavMeshAgent agent = character.GetComponent(); if (agent != null) { agent.enabled = true; // Re-enable NavMeshAgent } // Enable AI or other components as needed // e.g., character.GetComponent()?.enabled = true; } } private void PlaceEnemyCharacters() { Collider spawnAreaCollider = enemySpawnArea.GetComponent(); if (spawnAreaCollider == null) { Debug.LogError("Enemy spawn area does not have a collider component."); return; } Bounds spawnBounds = spawnAreaCollider.bounds; for (int i = enemySelections.Count - 1; i >= 0; i--) { var selection = enemySelections[i]; float randomX = UnityEngine.Random.Range(spawnBounds.min.x, spawnBounds.max.x); float randomZ = UnityEngine.Random.Range(spawnBounds.min.z, spawnBounds.max.z); Vector3 rayOrigin = new Vector3(randomX, enemySpawnArea.transform.position.y + 10f, randomZ); Vector3 spawnPosition = new Vector3(randomX, enemySpawnArea.transform.position.y, randomZ); // Raycast to find the ground if (Physics.Raycast(rayOrigin, Vector3.down, out RaycastHit hit, 200f, groundLayerMask)) { spawnPosition = hit.point; } else { Debug.LogWarning($"Raycast did not hit ground below ({randomX}, {randomZ}). Placing enemy at spawn area Y level."); } GameObject placedEnemy = Instantiate(enemyPrefab, spawnPosition, Quaternion.identity); Character character = placedEnemy.GetComponent(); if (character != null) { EquipWeapon(character, selection.weaponType); character.CharacterName = selection.characterName + "_" + (i + 1); } AdjustCharacterOnGround(placedEnemy); // Adjust the enemy to the ground placedEnemyCharacters.Add(placedEnemy); } } }