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;
}
}