根据提供的code differences信息,由于没有具体的代码变更内容,我将生成一个通用的commit message模板:
``` docs(readme): 更新文档说明 - 添加项目使用指南 - 完善API接口说明 - 修正错误的配置示例 ``` 注意:由于您提供的code differences部分为空,以上是一个示例模板。当您有具体的代码变更时,请提供详细的变更内容以便生成更准确的commit message。
This commit is contained in:
1 parent
8d58c2223c
commit
11f1af357d
191 files changed
+2315
-1539
No files matched your search
@@ -0,0 +1,261 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 战斗场景的出战生成:开战前按阵型数据在对应格位生成英雄。
|
||||
/// 优先使用阵型编排场景保存的数据;没有编排数据时按名单使用默认阵型(近战前排 / 远程后排)。
|
||||
/// </summary>
|
||||
public class BattleFormationSpawner : MonoBehaviour
|
||||
{
|
||||
[Header("出战英雄名单(没有编排数据时的默认来源)")]
|
||||
public HeroRoster roster;
|
||||
|
||||
[Header("生成到哪个父节点(留空则放在场景根)")]
|
||||
public Transform heroParent;
|
||||
|
||||
[Header("玩家阵营 ID")]
|
||||
public int playerTeamId = 0;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
spawnedHeroCount = 0; // 重置计数器
|
||||
SpawnHeroes();
|
||||
}
|
||||
|
||||
private void SpawnHeroes()
|
||||
{
|
||||
// 1. 优先从 PlayerData.json 读取保存的阵型
|
||||
if (PlayerDataManager.Instance != null)
|
||||
{
|
||||
SavedFormationData formationData = PlayerDataManager.Instance.LoadFormation();
|
||||
if (formationData != null && formationData.characterIds != null && formationData.characterIds.Length > 0)
|
||||
{
|
||||
Debug.Log($"从 PlayerData.json 加载阵型,共 {formationData.characterIds.Length} 个英雄");
|
||||
SpawnFromSavedFormation(formationData);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 回退到 FormationData(场景间临时传递的数据)
|
||||
if (FormationData.HasCustomFormation
|
||||
&& FormationData.HeroPrefabs != null
|
||||
&& FormationData.HeroPrefabs.Length > 0)
|
||||
{
|
||||
for (int i = 0; i < FormationData.HeroPrefabs.Length; i++)
|
||||
{
|
||||
int cell = i < FormationData.CellIndices.Length ? FormationData.CellIndices[i] : -1;
|
||||
if (FormationData.HeroPrefabs[i] == null || !DeployGrid.IsValidIndex(cell))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Spawn(FormationData.HeroPrefabs[i], cell);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 没有任何编排数据:使用默认阵型
|
||||
SpawnDefaultFormation();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从保存的阵型数据生成英雄
|
||||
/// </summary>
|
||||
private void SpawnFromSavedFormation(SavedFormationData formationData)
|
||||
{
|
||||
for (int i = 0; i < formationData.characterIds.Length; i++)
|
||||
{
|
||||
string characterId = formationData.characterIds[i];
|
||||
int cell = i < formationData.cellIndices.Length ? formationData.cellIndices[i] : -1;
|
||||
|
||||
if (string.IsNullOrEmpty(characterId) || !DeployGrid.IsValidIndex(cell))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 根据 characterId 找到对应的 Prefab
|
||||
GameObject prefab = FindPrefabByCharacterId(characterId);
|
||||
if (prefab != null)
|
||||
{
|
||||
Spawn(prefab, cell);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"未找到 characterId={characterId} 对应的 Prefab");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据 characterId 查找对应的 Prefab
|
||||
/// </summary>
|
||||
private GameObject FindPrefabByCharacterId(string characterId)
|
||||
{
|
||||
var allData = Resources.LoadAll<CharacterData>("Data");
|
||||
foreach (var data in allData)
|
||||
{
|
||||
if (data.characterId == characterId)
|
||||
{
|
||||
return data.unitPrefab;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void SpawnDefaultFormation()
|
||||
{
|
||||
if (roster == null || roster.heroes == null) return;
|
||||
|
||||
int meleeTotal = 0, rangedTotal = 0;
|
||||
foreach (HeroEntry entry in roster.heroes)
|
||||
{
|
||||
if (entry == null) continue;
|
||||
if (entry.isMelee) meleeTotal++; else rangedTotal++;
|
||||
}
|
||||
|
||||
var used = new HashSet<int>();
|
||||
int meleeIndex = 0, rangedIndex = 0;
|
||||
|
||||
foreach (HeroEntry entry in roster.heroes)
|
||||
{
|
||||
if (entry == null || entry.prefab == null) continue;
|
||||
|
||||
int cell = entry.isMelee
|
||||
? DeployGrid.DefaultCell(true, meleeIndex++, meleeTotal)
|
||||
: DeployGrid.DefaultCell(false, rangedIndex++, rangedTotal);
|
||||
|
||||
if (used.Contains(cell))
|
||||
{
|
||||
cell = NextFreeCell(cell, used);
|
||||
}
|
||||
if (cell < 0) continue;
|
||||
|
||||
used.Add(cell);
|
||||
Spawn(entry.prefab, cell);
|
||||
}
|
||||
}
|
||||
|
||||
private static int NextFreeCell(int from, HashSet<int> used)
|
||||
{
|
||||
for (int i = 0; i < DeployGrid.CellCount; i++)
|
||||
{
|
||||
int candidate = (from + i) % DeployGrid.CellCount;
|
||||
if (!used.Contains(candidate)) return candidate;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int spawnedHeroCount = 0; // 记录已生成英雄数量(改为实例变量)
|
||||
|
||||
private void Spawn(GameObject prefab, int cell)
|
||||
{
|
||||
CharacterData characterData = FindCharacterDataForPrefab(prefab);
|
||||
Vector3 spawnPos = DeployGrid.CellPosition(cell);
|
||||
GameObject unit = Instantiate(prefab, spawnPos, Quaternion.identity);
|
||||
unit.name = "英雄_" + prefab.name;
|
||||
|
||||
if (heroParent != null)
|
||||
{
|
||||
unit.transform.SetParent(heroParent, true);
|
||||
}
|
||||
|
||||
UnitTeam team = unit.GetComponent<UnitTeam>();
|
||||
if (team != null)
|
||||
{
|
||||
team.teamId = playerTeamId;
|
||||
}
|
||||
|
||||
UnitBase unitBase = unit.GetComponent<UnitBase>();
|
||||
if (unitBase != null)
|
||||
{
|
||||
unitBase.returnToAnchorWhenIdle = true;
|
||||
unitBase.anchorArrivalDistance = 0.35f;
|
||||
}
|
||||
|
||||
if (characterData != null && unitBase != null)
|
||||
{
|
||||
PlayerCharacterData playerData = PlayerDataManager.Instance != null
|
||||
? PlayerDataManager.Instance.GetCharacterData(characterData.characterId)
|
||||
: null;
|
||||
int level = playerData != null ? playerData.level : 1;
|
||||
unitBase.InitializeFromPlayerData(
|
||||
characterData.GetMaxHealth(level),
|
||||
characterData.GetAttackPower(level),
|
||||
characterData.GetMoveSpeed(level));
|
||||
}
|
||||
|
||||
// 设置友方英雄可复活
|
||||
WorldHealthBar healthBar = unit.GetComponent<WorldHealthBar>();
|
||||
if (healthBar != null && playerTeamId == 0)
|
||||
{
|
||||
healthBar.canRevive = true;
|
||||
}
|
||||
|
||||
// 绑定到英雄卡片UI
|
||||
BindHeroToCard(unit, characterData, spawnPos);
|
||||
}
|
||||
|
||||
private void BindHeroToCard(GameObject heroUnit, CharacterData characterData, Vector3 spawnPos)
|
||||
{
|
||||
if (spawnedHeroCount >= 5)
|
||||
{
|
||||
Debug.LogWarning($"已达到最大英雄数量5,跳过绑定 {heroUnit.name}");
|
||||
return; // 最多5个英雄
|
||||
}
|
||||
|
||||
// 查找 TowerDefenseCanvas
|
||||
GameObject canvasObj = GameObject.Find("TowerDefenseCanvas");
|
||||
if (canvasObj == null)
|
||||
{
|
||||
Debug.LogError("BindHeroToCard: 未找到 TowerDefenseCanvas");
|
||||
return;
|
||||
}
|
||||
|
||||
Transform heroPanel = canvasObj.transform.Find("BottomUI/HeroCardsPanel");
|
||||
if (heroPanel == null)
|
||||
{
|
||||
Debug.LogError("BindHeroToCard: 未找到 BottomUI/HeroCardsPanel");
|
||||
return;
|
||||
}
|
||||
|
||||
string cardName = $"HeroCard{spawnedHeroCount + 1}";
|
||||
Transform cardObj = heroPanel.Find(cardName);
|
||||
if (cardObj == null)
|
||||
{
|
||||
Debug.LogError($"BindHeroToCard: 未找到 {cardName}");
|
||||
return;
|
||||
}
|
||||
|
||||
HeroCardUI heroCardUI = cardObj.GetComponent<HeroCardUI>();
|
||||
if (heroCardUI == null)
|
||||
{
|
||||
Debug.LogError($"BindHeroToCard: {cardName} 缺少 HeroCardUI 组件");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取英雄单位组件
|
||||
UnitBase unitBase = heroUnit.GetComponent<UnitBase>();
|
||||
if (unitBase == null)
|
||||
{
|
||||
Debug.LogError($"BindHeroToCard: {heroUnit.name} 缺少 UnitBase 组件");
|
||||
return;
|
||||
}
|
||||
|
||||
// 加载对应的头像
|
||||
UnityEngine.Sprite icon = characterData != null ? characterData.cardIcon : null;
|
||||
|
||||
// 绑定英雄到卡片
|
||||
heroCardUI.SetHero(unitBase, icon, spawnPos);
|
||||
|
||||
spawnedHeroCount++;
|
||||
|
||||
Debug.Log($"✅ {cardName} 已绑定英雄: {heroUnit.name}(第 {spawnedHeroCount} 个)");
|
||||
}
|
||||
|
||||
private CharacterData FindCharacterDataForPrefab(GameObject prefab)
|
||||
{
|
||||
if (prefab == null) return null;
|
||||
return Resources.LoadAll<CharacterData>("Data")
|
||||
.FirstOrDefault(data => data != null && data.unitPrefab == prefab);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: DnsX437+UChVX0ij4Nkf5TPsZ/18GQ+w3wf4Y7WFS+sv6hTcsjUrcGg=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,117 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 混沌之门:被占领后按固定间隔从所属阵营卡组随机出兵。
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(CrystalMineCapture))]
|
||||
public class ChaosGateSpawner : MonoBehaviour
|
||||
{
|
||||
[Header("出兵配置")]
|
||||
[Min(1f)] public float spawnInterval = 12f;
|
||||
[Min(0f)] public float spawnRadius = 1.5f;
|
||||
public bool spawnImmediatelyOnCapture = false;
|
||||
|
||||
[Header("蓝方卡组")]
|
||||
[SerializeField] private string[] blueDeckDataNames =
|
||||
{
|
||||
"人类士兵Data", "弓箭手Data", "盾卫Data", "精锐士兵Data",
|
||||
"法师Data", "狂暴狼人Data", "石巨人Data", "弓箭猎人Data"
|
||||
};
|
||||
|
||||
private CrystalMineCapture capture;
|
||||
private UnitTeam unitTeam;
|
||||
private float spawnTimer;
|
||||
private int previousTeamId = 2;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
capture = GetComponent<CrystalMineCapture>();
|
||||
unitTeam = GetComponent<UnitTeam>();
|
||||
spawnTimer = spawnInterval;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (unitTeam == null)
|
||||
{
|
||||
unitTeam = GetComponent<UnitTeam>();
|
||||
if (unitTeam == null) return;
|
||||
}
|
||||
|
||||
int currentTeamId = unitTeam.teamId;
|
||||
if (currentTeamId != previousTeamId)
|
||||
{
|
||||
previousTeamId = currentTeamId;
|
||||
spawnTimer = spawnImmediatelyOnCapture ? 0f : spawnInterval;
|
||||
}
|
||||
|
||||
if (currentTeamId != 0 && currentTeamId != 1)
|
||||
{
|
||||
spawnTimer = spawnInterval;
|
||||
return;
|
||||
}
|
||||
|
||||
spawnTimer -= Time.deltaTime;
|
||||
if (spawnTimer > 0f) return;
|
||||
|
||||
if (TrySpawn(currentTeamId))
|
||||
spawnTimer = spawnInterval;
|
||||
else
|
||||
spawnTimer = 1f;
|
||||
}
|
||||
|
||||
private bool TrySpawn(int teamId)
|
||||
{
|
||||
Vector3 position = transform.position;
|
||||
position.x += Random.Range(-spawnRadius, spawnRadius);
|
||||
position.z += Random.Range(-spawnRadius, spawnRadius);
|
||||
|
||||
if (teamId == 1)
|
||||
{
|
||||
EnemyCastleAI enemyAI = FindObjectOfType<EnemyCastleAI>();
|
||||
return enemyAI != null && enemyAI.TrySpawnFromPortal(position, transform.rotation);
|
||||
}
|
||||
|
||||
CharacterData card = ChooseBlueCard();
|
||||
if (card == null || CastleSpawnManager.Instance == null)
|
||||
return false;
|
||||
|
||||
return SpawnBlueUnit(card, position, transform.rotation);
|
||||
}
|
||||
|
||||
private CharacterData ChooseBlueCard()
|
||||
{
|
||||
CharacterData[] cards = new CharacterData[blueDeckDataNames.Length];
|
||||
int validCount = 0;
|
||||
foreach (string dataName in blueDeckDataNames)
|
||||
{
|
||||
CharacterData data = Resources.Load<CharacterData>("Data/" + dataName);
|
||||
if (data != null && data.unitPrefab != null)
|
||||
cards[validCount++] = data;
|
||||
}
|
||||
|
||||
if (validCount == 0) return null;
|
||||
|
||||
return cards[Random.Range(0, validCount)];
|
||||
}
|
||||
|
||||
private bool SpawnBlueUnit(CharacterData data, Vector3 position, Quaternion rotation)
|
||||
{
|
||||
GameObject unit = Instantiate(data.unitPrefab, position, rotation);
|
||||
UnitTeam team = unit.GetComponent<UnitTeam>();
|
||||
if (team != null)
|
||||
{
|
||||
team.teamId = 0;
|
||||
unit.tag = "Friend";
|
||||
}
|
||||
|
||||
UnitBase unitBase = unit.GetComponent<UnitBase>();
|
||||
if (unitBase != null)
|
||||
{
|
||||
unitBase.InitializeFromPlayerData(data.GetMaxHealth(1), data.GetAttackPower(1), data.GetMoveSpeed(1));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: CXkWsyr5V3PqLCVXf9kh9UDgagA4PNu+m8m3e+26o8Vi3MvAgRRmtzA=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,257 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class DefenseWaveLevelController : MonoBehaviour
|
||||
{
|
||||
[System.Serializable]
|
||||
private class LevelWaveFile
|
||||
{
|
||||
public string levelId;
|
||||
public string levelName;
|
||||
public WaveEntry[] waves;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private class WaveEntry
|
||||
{
|
||||
public int waveIndex;
|
||||
public float restBeforeWave = 2f;
|
||||
public float spawnInterval = 1f;
|
||||
public string[] enemyIds;
|
||||
public int[] counts;
|
||||
public float[] spawnX;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class EnemyPrefabEntry
|
||||
{
|
||||
public string id;
|
||||
public GameObject prefab;
|
||||
}
|
||||
|
||||
[Header("关卡 JSON")]
|
||||
public TextAsset levelJson;
|
||||
|
||||
[Header("JSON enemyId 对应 Prefab")]
|
||||
public EnemyPrefabEntry[] enemyPrefabs;
|
||||
|
||||
[Header("自动模拟")]
|
||||
public bool autoStart = true;
|
||||
public float spawnZ = 20f; // 敌人生成的Z位置
|
||||
public float spawnY = 0.16f;
|
||||
public float waveCompleteDelay = 3f;
|
||||
|
||||
private LevelWaveFile level;
|
||||
private readonly List<GameObject> activeEnemies = new List<GameObject>();
|
||||
private Slider progressSlider;
|
||||
private Text progressText;
|
||||
private int currentWaveIndex = -1;
|
||||
private int totalEnemies;
|
||||
private int defeatedEnemies;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (!LoadLevel()) return;
|
||||
CreateProgressBar();
|
||||
if (autoStart) StartCoroutine(RunLevel());
|
||||
}
|
||||
|
||||
private bool LoadLevel()
|
||||
{
|
||||
if (levelJson == null)
|
||||
{
|
||||
Debug.LogError("DefenseWaveLevelController: 未配置关卡 JSON。");
|
||||
return false;
|
||||
}
|
||||
|
||||
level = JsonUtility.FromJson<LevelWaveFile>(levelJson.text);
|
||||
if (level == null || level.waves == null || level.waves.Length == 0)
|
||||
{
|
||||
Debug.LogError("DefenseWaveLevelController: JSON 没有有效波次。");
|
||||
return false;
|
||||
}
|
||||
|
||||
totalEnemies = level.waves.Sum(w => TotalCount(w));
|
||||
defeatedEnemies = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
private IEnumerator RunLevel()
|
||||
{
|
||||
for (int i = 0; i < level.waves.Length; i++)
|
||||
{
|
||||
currentWaveIndex = i;
|
||||
var wave = level.waves[i];
|
||||
yield return new WaitForSeconds(Mathf.Max(0f, wave.restBeforeWave));
|
||||
yield return SpawnWave(wave);
|
||||
yield return WaitForWaveCleared();
|
||||
yield return new WaitForSeconds(Mathf.Max(0f, waveCompleteDelay));
|
||||
}
|
||||
|
||||
UpdateProgress(level.waves.Length, level.waves.Length, "通关");
|
||||
Debug.Log("DefenseWaveLevelController: 第一关波次全部完成。");
|
||||
}
|
||||
|
||||
private IEnumerator SpawnWave(WaveEntry wave)
|
||||
{
|
||||
int total = TotalCount(wave);
|
||||
int spawned = 0;
|
||||
UpdateProgress(wave.waveIndex, level.waves.Length, $"第 {wave.waveIndex} 波 0/{total}");
|
||||
|
||||
for (int typeIndex = 0; typeIndex < wave.enemyIds.Length; typeIndex++)
|
||||
{
|
||||
var prefab = FindPrefab(wave.enemyIds[typeIndex]);
|
||||
if (prefab == null) continue;
|
||||
|
||||
int count = wave.counts != null && typeIndex < wave.counts.Length ? wave.counts[typeIndex] : 0;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
float x = wave.spawnX != null && wave.spawnX.Length > 0
|
||||
? wave.spawnX[(spawned) % wave.spawnX.Length]
|
||||
: (spawned % 2 == 0 ? -3f : 3f);
|
||||
var enemy = Instantiate(prefab, new Vector3(x, spawnY, spawnZ), Quaternion.identity);
|
||||
enemy.tag = "Enemy";
|
||||
|
||||
// 设置敌方阵营(teamId=1),否则会被当成友军
|
||||
UnitTeam enemyTeam = enemy.GetComponent<UnitTeam>();
|
||||
if (enemyTeam == null) enemyTeam = enemy.AddComponent<UnitTeam>();
|
||||
enemyTeam.teamId = 1;
|
||||
|
||||
// 增大敌人的寻敌距离
|
||||
UnitBase unitBase = enemy.GetComponent<UnitBase>();
|
||||
if (unitBase != null)
|
||||
{
|
||||
unitBase.detectionRange = 100f; // 从默认30增加到100
|
||||
}
|
||||
|
||||
ApplyCharacterData(enemy, wave.enemyIds[typeIndex]);
|
||||
activeEnemies.Add(enemy);
|
||||
spawned++;
|
||||
UpdateProgress(wave.waveIndex, level.waves.Length, $"第 {wave.waveIndex} 波 {spawned}/{total}");
|
||||
yield return new WaitForSeconds(Mathf.Max(0.1f, wave.spawnInterval));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator WaitForWaveCleared()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
for (int i = activeEnemies.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var enemy = activeEnemies[i];
|
||||
if (enemy == null || !enemy.activeInHierarchy)
|
||||
{
|
||||
activeEnemies.RemoveAt(i);
|
||||
defeatedEnemies++;
|
||||
}
|
||||
}
|
||||
|
||||
if (activeEnemies.Count == 0) yield break;
|
||||
yield return new WaitForSeconds(0.25f);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyCharacterData(GameObject enemy, string characterId)
|
||||
{
|
||||
// 从所有 CharacterData 中按 characterId 字段查找,而不是按文件名
|
||||
var allData = Resources.LoadAll<CharacterData>("Data");
|
||||
CharacterData data = null;
|
||||
foreach (var d in allData)
|
||||
{
|
||||
if (d != null && d.characterId == characterId)
|
||||
{
|
||||
data = d;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var unit = enemy.GetComponent<UnitBase>();
|
||||
if (data == null)
|
||||
{
|
||||
Debug.LogWarning($"ApplyCharacterData: 找不到 characterId={characterId} 的 CharacterData");
|
||||
return;
|
||||
}
|
||||
if (unit == null) return;
|
||||
|
||||
unit.InitializeFromPlayerData(data.GetMaxHealth(1), data.GetAttackPower(1), data.GetMoveSpeed(1));
|
||||
}
|
||||
|
||||
private int TotalCount(WaveEntry wave)
|
||||
{
|
||||
if (wave == null || wave.counts == null) return 0;
|
||||
return wave.counts.Sum(c => Mathf.Max(0, c));
|
||||
}
|
||||
|
||||
private GameObject FindPrefab(string id)
|
||||
{
|
||||
if (enemyPrefabs == null) return null;
|
||||
var entry = enemyPrefabs.FirstOrDefault(e => e != null && e.id == id);
|
||||
if (entry == null || entry.prefab == null)
|
||||
{
|
||||
Debug.LogError("DefenseWaveLevelController: 找不到小怪 Prefab: " + id);
|
||||
return null;
|
||||
}
|
||||
return entry.prefab;
|
||||
}
|
||||
|
||||
private void CreateProgressBar()
|
||||
{
|
||||
var canvasObject = new GameObject("WaveProgressHUD", typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
|
||||
var canvas = canvasObject.GetComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
canvas.sortingOrder = 500;
|
||||
|
||||
var root = canvasObject.GetComponent<RectTransform>();
|
||||
var panel = new GameObject("WaveProgressPanel", typeof(RectTransform), typeof(Image));
|
||||
panel.transform.SetParent(root, false);
|
||||
var panelRect = panel.GetComponent<RectTransform>();
|
||||
panelRect.anchorMin = new Vector2(0.5f, 1f);
|
||||
panelRect.anchorMax = new Vector2(0.5f, 1f);
|
||||
panelRect.pivot = new Vector2(0.5f, 1f);
|
||||
panelRect.anchoredPosition = new Vector2(0f, -20f);
|
||||
panelRect.sizeDelta = new Vector2(420f, 64f);
|
||||
panel.GetComponent<Image>().color = new Color(0f, 0f, 0f, 0.45f);
|
||||
|
||||
var sliderObject = new GameObject("WaveProgress", typeof(RectTransform), typeof(Slider));
|
||||
sliderObject.transform.SetParent(panel.transform, false);
|
||||
var sliderRect = sliderObject.GetComponent<RectTransform>();
|
||||
sliderRect.anchorMin = new Vector2(0.5f, 0f);
|
||||
sliderRect.anchorMax = new Vector2(0.5f, 0f);
|
||||
sliderRect.pivot = new Vector2(0.5f, 0f);
|
||||
sliderRect.anchoredPosition = new Vector2(0f, 10f);
|
||||
sliderRect.sizeDelta = new Vector2(360f, 16f);
|
||||
progressSlider = sliderObject.GetComponent<Slider>();
|
||||
progressSlider.minValue = 0f;
|
||||
progressSlider.maxValue = 1f;
|
||||
progressSlider.value = 0f;
|
||||
progressSlider.interactable = false;
|
||||
|
||||
var textObject = new GameObject("WaveProgressText", typeof(RectTransform), typeof(Text));
|
||||
textObject.transform.SetParent(panel.transform, false);
|
||||
var textRect = textObject.GetComponent<RectTransform>();
|
||||
textRect.anchorMin = new Vector2(0.5f, 1f);
|
||||
textRect.anchorMax = new Vector2(0.5f, 1f);
|
||||
textRect.pivot = new Vector2(0.5f, 1f);
|
||||
textRect.anchoredPosition = new Vector2(0f, -8f);
|
||||
textRect.sizeDelta = new Vector2(390f, 28f);
|
||||
progressText = textObject.GetComponent<Text>();
|
||||
progressText.alignment = TextAnchor.MiddleCenter;
|
||||
progressText.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
|
||||
progressText.fontSize = 18;
|
||||
progressText.color = Color.white;
|
||||
}
|
||||
|
||||
private void UpdateProgress(int waveNumber, int waveCount, string text)
|
||||
{
|
||||
if (progressSlider != null)
|
||||
{
|
||||
float defeatedRatio = totalEnemies > 0 ? (float)defeatedEnemies / totalEnemies : 0f;
|
||||
progressSlider.value = Mathf.Clamp01((waveNumber - 1 + defeatedRatio) / Mathf.Max(1, waveCount));
|
||||
}
|
||||
if (progressText != null) progressText.text = text;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: CHId5COuWi40H/2cT8R/W3rVc3AiY8surGsSqvgv4U3rQfhN5lEKc0o=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,124 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class LevelWaveManager : MonoBehaviour
|
||||
{
|
||||
[Header("关卡配置")]
|
||||
public TextAsset levelConfigJson;
|
||||
|
||||
[Header("资源引用")]
|
||||
public Transform playerTransform;
|
||||
|
||||
private LevelConfig levelConfig;
|
||||
private List<GameObject> waveSpawners = new List<GameObject>();
|
||||
private Dictionary<string, CharacterData> characterDataCache = new Dictionary<string, CharacterData>();
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (playerTransform == null)
|
||||
{
|
||||
var mainChar = FindObjectOfType<MainCharacterCombat>();
|
||||
if (mainChar != null)
|
||||
{
|
||||
playerTransform = mainChar.transform;
|
||||
}
|
||||
}
|
||||
|
||||
if (levelConfigJson != null)
|
||||
{
|
||||
LoadLevelConfig();
|
||||
CreateWaveSpawners();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("LevelWaveManager: 未配置关卡 JSON 文件!");
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadLevelConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
levelConfig = JsonUtility.FromJson<LevelConfig>(levelConfigJson.text);
|
||||
Debug.Log($"加载关卡配置:{levelConfig.levelName},共 {levelConfig.waves.Length} 波");
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Debug.LogError($"解析关卡配置失败:{e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateWaveSpawners()
|
||||
{
|
||||
if (levelConfig == null || levelConfig.waves == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var waveConfig in levelConfig.waves)
|
||||
{
|
||||
GameObject spawnerObj = new GameObject($"WaveSpawner_Wave{waveConfig.waveIndex}");
|
||||
spawnerObj.transform.SetParent(transform);
|
||||
spawnerObj.transform.position = new Vector3(0, 0, waveConfig.triggerZPosition + 5f); // 生成点在触发点后方5米
|
||||
|
||||
WaveSpawner spawner = spawnerObj.AddComponent<WaveSpawner>();
|
||||
spawner.triggerZPosition = waveConfig.triggerZPosition;
|
||||
spawner.autoStart = false;
|
||||
|
||||
// 构建 WaveSpawner.Wave 数据
|
||||
WaveSpawner.Wave wave = new WaveSpawner.Wave();
|
||||
wave.waveName = $"第{waveConfig.waveIndex}波";
|
||||
|
||||
List<GameObject> enemyPrefabs = new List<GameObject>();
|
||||
List<Vector3> spawnPositions = new List<Vector3>();
|
||||
|
||||
foreach (var enemyConfig in waveConfig.enemies)
|
||||
{
|
||||
CharacterData characterData = LoadCharacterData(enemyConfig.characterDataId);
|
||||
if (characterData == null)
|
||||
{
|
||||
Debug.LogError($"找不到角色数据:{enemyConfig.characterDataId}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 根据配置生成多个敌人
|
||||
for (int i = 0; i < enemyConfig.count; i++)
|
||||
{
|
||||
enemyPrefabs.Add(characterData.unitPrefab);
|
||||
|
||||
Vector3 spawnPos = (i < enemyConfig.spawnPositions.Length)
|
||||
? enemyConfig.spawnPositions[i]
|
||||
: new Vector3(i * 2f, 0, 0); // 默认横向排列
|
||||
|
||||
spawnPositions.Add(spawnPos);
|
||||
}
|
||||
}
|
||||
|
||||
wave.enemyPrefabs = enemyPrefabs.ToArray();
|
||||
wave.spawnPositions = spawnPositions.ToArray();
|
||||
wave.spawnDelay = 0.5f;
|
||||
|
||||
spawner.waves = new WaveSpawner.Wave[] { wave };
|
||||
|
||||
waveSpawners.Add(spawnerObj);
|
||||
|
||||
Debug.Log($"创建 WaveSpawner:波次{waveConfig.waveIndex},触发位置 Z={waveConfig.triggerZPosition},敌人数量={enemyPrefabs.Count}");
|
||||
}
|
||||
}
|
||||
|
||||
private CharacterData LoadCharacterData(string characterDataId)
|
||||
{
|
||||
if (characterDataCache.ContainsKey(characterDataId))
|
||||
{
|
||||
return characterDataCache[characterDataId];
|
||||
}
|
||||
|
||||
CharacterData data = Resources.Load<CharacterData>($"Data/{characterDataId}");
|
||||
if (data != null)
|
||||
{
|
||||
characterDataCache[characterDataId] = data;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: DHkdtC3/Ant2wF17ErOO+wr45AFJBS7u4J81SlvAfFESXuAbVlHhiR8=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,116 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class WaveSpawner : MonoBehaviour
|
||||
{
|
||||
[System.Serializable]
|
||||
public class Wave
|
||||
{
|
||||
public string waveName = "第一波";
|
||||
public GameObject[] enemyPrefabs;
|
||||
public Vector3[] spawnPositions;
|
||||
public float spawnDelay = 0.5f;
|
||||
}
|
||||
|
||||
[Header("波次配置")]
|
||||
public Wave[] waves;
|
||||
|
||||
[Header("触发设置")]
|
||||
public float triggerZPosition = 20f;
|
||||
public bool autoStart = false;
|
||||
|
||||
private int currentWave = 0;
|
||||
private bool waveStarted = false;
|
||||
private bool waveCompleted = false;
|
||||
private List<GameObject> activeEnemies = new List<GameObject>();
|
||||
private Transform player;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
player = GameObject.FindGameObjectWithTag("Player")?.transform;
|
||||
if (player == null)
|
||||
{
|
||||
player = FindObjectOfType<MainCharacterCombat>()?.transform;
|
||||
}
|
||||
|
||||
if (autoStart)
|
||||
{
|
||||
StartWave();
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!waveStarted && !waveCompleted && player != null)
|
||||
{
|
||||
if (player.position.z >= triggerZPosition)
|
||||
{
|
||||
StartWave();
|
||||
}
|
||||
}
|
||||
|
||||
if (waveStarted && !waveCompleted)
|
||||
{
|
||||
CleanupDestroyedEnemies();
|
||||
|
||||
if (activeEnemies.Count == 0)
|
||||
{
|
||||
CompleteWave();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void StartWave()
|
||||
{
|
||||
if (currentWave >= waves.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
waveStarted = true;
|
||||
StartCoroutine(SpawnWave(waves[currentWave]));
|
||||
}
|
||||
|
||||
private IEnumerator SpawnWave(Wave wave)
|
||||
{
|
||||
Debug.Log($"开始生成:{wave.waveName}");
|
||||
|
||||
int count = Mathf.Min(wave.enemyPrefabs.Length, wave.spawnPositions.Length);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (wave.enemyPrefabs[i] != null)
|
||||
{
|
||||
Vector3 spawnPos = transform.position + wave.spawnPositions[i];
|
||||
GameObject enemy = Instantiate(wave.enemyPrefabs[i], spawnPos, Quaternion.identity);
|
||||
enemy.tag = "Enemy";
|
||||
|
||||
activeEnemies.Add(enemy);
|
||||
|
||||
Debug.Log($"生成敌人:{enemy.name} 位置={spawnPos}");
|
||||
|
||||
yield return new WaitForSeconds(wave.spawnDelay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CleanupDestroyedEnemies()
|
||||
{
|
||||
activeEnemies.RemoveAll(enemy => enemy == null);
|
||||
}
|
||||
|
||||
private void CompleteWave()
|
||||
{
|
||||
waveCompleted = true;
|
||||
Debug.Log($"完成:{waves[currentWave].waveName}");
|
||||
}
|
||||
|
||||
public void NextWave()
|
||||
{
|
||||
currentWave++;
|
||||
waveStarted = false;
|
||||
waveCompleted = false;
|
||||
activeEnemies.Clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: WS4dvHz4Vi/MCpwbZF3V4KTGzHumOULxrueweyUBP1BkjTEOv8xKxSk=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in new issue
Block a user