221 lines
8.7 KiB
C#
221 lines
8.7 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
|
||
namespace IndianOceanAssets.Engine2_5D
|
||
{
|
||
/// <summary>
|
||
/// 游戏出生点管理器 —— 每次进入场景时,将玩家和敌人随机分配到出生点。
|
||
///
|
||
/// 使用方式:
|
||
/// 1. 场景中创建空物体,挂上此脚本
|
||
/// 2. 将玩家预制体拖到 playerPrefab
|
||
/// 3. 将 enemy_01~05 预制体拖到 enemyPrefabs 数组
|
||
/// 4. 设置每种敌人的数量
|
||
/// 5. 运行时自动从 EnemySpawnPoint 中随机选取位置
|
||
/// </summary>
|
||
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<Transform> _allSpawnPoints;
|
||
private readonly List<EnemyAI> _spawnedEnemies = new List<EnemyAI>();
|
||
private GameObject _spawnedPlayer;
|
||
|
||
private void Start()
|
||
{
|
||
// 收集所有出生点
|
||
var spawnPointComponents = FindObjectsByType<EnemySpawnPoint>(FindObjectsSortMode.None);
|
||
_allSpawnPoints = new List<Transform>();
|
||
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<CameraFollow>() : null;
|
||
if (camFollow == null && Camera.main != null)
|
||
camFollow = Camera.main.gameObject.AddComponent<CameraFollow>();
|
||
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<EnemyAI>();
|
||
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} 个敌人");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 找一个离已使用点都较远的出生点。
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取出生点编号(用于调试日志)。
|
||
/// </summary>
|
||
int GetSpawnPointIndex(Transform spawnPoint)
|
||
{
|
||
var comp = spawnPoint.GetComponent<EnemySpawnPoint>();
|
||
return comp != null ? comp.Index : -1;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Fisher-Yates 洗牌算法。
|
||
/// </summary>
|
||
void Shuffle<T>(List<T> 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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取所有已生成的敌人。
|
||
/// </summary>
|
||
public List<EnemyAI> GetSpawnedEnemies()
|
||
{
|
||
_spawnedEnemies.RemoveAll(e => e == null);
|
||
return _spawnedEnemies;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取已生成的玩家。
|
||
/// </summary>
|
||
public GameObject GetSpawnedPlayer() => _spawnedPlayer;
|
||
}
|
||
}
|