using System.Collections.Generic; using UnityEngine; namespace IndianOceanAssets.Engine2_5D { /// /// 游戏出生点管理器 —— 每次进入场景时,将玩家和敌人随机分配到出生点。 /// /// 使用方式: /// 1. 场景中创建空物体,挂上此脚本 /// 2. 将玩家预制体拖到 playerPrefab /// 3. 将 enemy_01~05 预制体拖到 enemyPrefabs 数组 /// 4. 设置每种敌人的数量 /// 5. 运行时自动从 EnemySpawnPoint 中随机选取位置 /// public class GameSpawnManager : MonoBehaviour { [Header("玩家")] [Tooltip("玩家预制体")] [SerializeField] GameObject playerPrefab; [Header("传送门")] [Tooltip("传送门/出口预制体(DoorPortal)")] [SerializeField] GameObject doorPrefab; [Header("敌人配置")] [Tooltip("敌人预制体数组(enemy_01 ~ enemy_05)")] [SerializeField] GameObject[] enemyPrefabs; [Tooltip("每种敌人的数量(数组长度需与 enemyPrefabs 一致)")] [SerializeField] int[] enemyCounts; [Header("设置")] [Tooltip("敌人总数上限(即使 enemyCounts 总和超过此值也不会多生成)")] [SerializeField] int maxEnemies = 5; [Tooltip("玩家出生点离敌人的最小安全距离")] [SerializeField] float playerSafeDistance = 5f; // 运行时 private List _allSpawnPoints; private readonly List _spawnedEnemies = new List(); private GameObject _spawnedPlayer; private void Start() { // 收集所有出生点 var spawnPointComponents = FindObjectsByType(FindObjectsSortMode.None); _allSpawnPoints = new List(); foreach (var sp in spawnPointComponents) { _allSpawnPoints.Add(sp.transform); } if (_allSpawnPoints.Count == 0) { Debug.LogWarning("[GameSpawnManager] 场景中没有找到任何 EnemySpawnPoint!"); return; } Debug.Log($"[GameSpawnManager] 找到 {_allSpawnPoints.Count} 个出生点"); // 打乱出生点顺序 Shuffle(_allSpawnPoints); int usedIndex = 0; // 1. 生成玩家 if (playerPrefab != null) { // 找一个离已使用点较远的位置 Transform playerSpawn = FindSafeSpawnPoint(usedIndex, playerSafeDistance); if (playerSpawn == null) playerSpawn = _allSpawnPoints[usedIndex]; // 将玩家使用的点交换到 usedIndex 位置,标记为已使用 int playerIdx = _allSpawnPoints.IndexOf(playerSpawn); if (playerIdx != usedIndex) { var temp = _allSpawnPoints[usedIndex]; _allSpawnPoints[usedIndex] = _allSpawnPoints[playerIdx]; _allSpawnPoints[playerIdx] = temp; } usedIndex++; _spawnedPlayer = Instantiate(playerPrefab, playerSpawn.position, playerSpawn.rotation); Debug.Log($"[GameSpawnManager] 玩家出生在 #{GetSpawnPointIndex(playerSpawn)} 位置=({playerSpawn.position.x:F1}, {playerSpawn.position.y:F1}, {playerSpawn.position.z:F1})"); // 将摄像机挂到玩家身上 var camFollow = Camera.main != null ? Camera.main.GetComponent() : null; if (camFollow == null && Camera.main != null) camFollow = Camera.main.gameObject.AddComponent(); if (camFollow != null) { camFollow.SetTarget(_spawnedPlayer.transform); Debug.Log("[GameSpawnManager] 摄像机已跟随玩家"); } } // 2. 生成传送门(选择离玩家最远的出生点) if (doorPrefab != null && usedIndex < _allSpawnPoints.Count) { Vector3 playerPos = _spawnedPlayer != null ? _spawnedPlayer.transform.position : _allSpawnPoints[0].position; Transform doorSpawn = null; float maxDist = -1f; for (int i = usedIndex; i < _allSpawnPoints.Count; i++) { float dist = Vector3.Distance(_allSpawnPoints[i].position, playerPos); if (dist > maxDist) { maxDist = dist; doorSpawn = _allSpawnPoints[i]; } } if (doorSpawn == null) doorSpawn = _allSpawnPoints[usedIndex]; int doorIdx = _allSpawnPoints.IndexOf(doorSpawn); if (doorIdx != usedIndex) { var temp = _allSpawnPoints[usedIndex]; _allSpawnPoints[usedIndex] = _allSpawnPoints[doorIdx]; _allSpawnPoints[doorIdx] = temp; } usedIndex++; Instantiate(doorPrefab, doorSpawn.position, doorSpawn.rotation); Debug.Log($"[GameSpawnManager] 传送门出生在 #{GetSpawnPointIndex(doorSpawn)} 位置=({doorSpawn.position.x:F1}, {doorSpawn.position.y:F1}, {doorSpawn.position.z:F1}) 距玩家={maxDist:F1}"); } // 3. 生成敌人 if (enemyPrefabs != null && enemyCounts != null) { int prefabCount = Mathf.Min(enemyPrefabs.Length, enemyCounts.Length); for (int p = 0; p < prefabCount; p++) { if (enemyPrefabs[p] == null) continue; for (int i = 0; i < enemyCounts[p] && usedIndex < _allSpawnPoints.Count; i++) { // 检查是否达到敌人总数上限 if (_spawnedEnemies.Count >= maxEnemies) break; Transform spawnPos = _allSpawnPoints[usedIndex++]; var enemy = Instantiate(enemyPrefabs[p], spawnPos.position, spawnPos.rotation); var ai = enemy.GetComponent(); if (ai != null) _spawnedEnemies.Add(ai); Debug.Log($" 敌人{p + 1}#{_spawnedEnemies.Count}: {enemyPrefabs[p].name} 位置=({spawnPos.position.x:F1}, {spawnPos.position.y:F1}, {spawnPos.position.z:F1}) 出生点=#{GetSpawnPointIndex(spawnPos)}"); } // 外层也检查 if (_spawnedEnemies.Count >= maxEnemies) break; } } Debug.Log($"[GameSpawnManager] 生成完毕:1 个玩家 + {(doorPrefab != null ? "1 个传送门 + " : "")}{_spawnedEnemies.Count} 个敌人"); } /// /// 找一个离已使用点都较远的出生点。 /// Transform FindSafeSpawnPoint(int startIndex, float minDistance) { for (int i = startIndex; i < _allSpawnPoints.Count; i++) { bool safe = true; for (int j = 0; j < startIndex; j++) { if (Vector3.Distance(_allSpawnPoints[i].position, _allSpawnPoints[j].position) < minDistance) { safe = false; break; } } if (safe) return _allSpawnPoints[i]; } return null; } /// /// 获取出生点编号(用于调试日志)。 /// int GetSpawnPointIndex(Transform spawnPoint) { var comp = spawnPoint.GetComponent(); return comp != null ? comp.Index : -1; } /// /// Fisher-Yates 洗牌算法。 /// void Shuffle(List list) { for (int i = list.Count - 1; i > 0; i--) { int j = Random.Range(0, i + 1); T temp = list[i]; list[i] = list[j]; list[j] = temp; } } /// /// 获取所有已生成的敌人。 /// public List GetSpawnedEnemies() { _spawnedEnemies.RemoveAll(e => e == null); return _spawnedEnemies; } /// /// 获取已生成的玩家。 /// public GameObject GetSpawnedPlayer() => _spawnedPlayer; } }