Files
ksh999000 c06a8abad0 增加传送门逻辑
增加无敌时间
2026-07-05 22:50:58 +08:00

221 lines
8.7 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}