Files
daluandou/Assets/Scripts/关卡脚本/DefenseWaveLevelController.cs
T
huadexuan 336ec9980d ```
feat(gameplay): 更新防御关卡金币系统

- 修改金币显示文本格式,移除冒号分隔符
- 实现差异化金币奖励机制:小怪固定1金币,英雄/Boss单位固定5金币
- 添加战术倍率支持,确保最低1金币奖励
- 增加击杀日志输出功能
- 实现英雄/Boss单位识别逻辑,通过名称和角色数据进行判断
```
2026-10-09 00:00:46 +08:00

316 lines
11 KiB
C#
Raw 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;
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;
[Header("UI 绑定")]
public Text waveTextUI; // 绑定 TowerDefenseCanvas/TopBar/WaveText
public Text killCountTextUI; // 绑定 TowerDefenseCanvas/TopBar/KillCountText
private LevelWaveFile level;
private readonly List<GameObject> activeEnemies = new List<GameObject>();
private int currentWaveIndex = -1;
private int totalEnemies;
private int defeatedEnemies;
private void Start()
{
if (!LoadLevel()) return;
EnsureUIReferences();
UpdateProgress(0, level.waves.Length, "准备中");
if (autoStart) StartCoroutine(RunLevel());
}
/// <summary>
/// 外部调用:开始关卡(用于游戏流程控制)
/// </summary>
public IEnumerator StartLevel()
{
if (level == null || level.waves == null || level.waves.Length == 0)
{
Debug.LogError("DefenseWaveLevelController: 无法启动关卡,波次数据未加载");
yield break;
}
yield return 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();
// 每波结束后,让英雄回到初始位置
ResetHeroPositionsAfterWave();
// 每 2 波结束(第 2、4、6、8...波)触发 3 选 1 战术增益弹窗
if ((currentWaveIndex + 1) % 2 == 0)
{
BuffSelectionController.Instance.ShowBuffSelection();
}
yield return new WaitForSeconds(Mathf.Max(0f, waveCompleteDelay));
}
UpdateProgress(level.waves.Length, level.waves.Length, "通关");
Debug.Log("DefenseWaveLevelController: 第一关波次全部完成。");
}
/// <summary>
/// 每波结束后让英雄回到初始位置
/// </summary>
private void ResetHeroPositionsAfterWave()
{
DefenseGameFlowController flowController = FindObjectOfType<DefenseGameFlowController>();
if (flowController != null)
{
flowController.ResetHeroPositions();
Debug.Log($"第 {currentWaveIndex + 1} 波结束,英雄回到初始位置");
}
}
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++;
// 击杀金币奖励:小怪固定 1 金币,英雄/Boss 单位固定 5 金币 (支持战术倍率)
if (GoldManager.Instance != null)
{
bool isHeroUnit = IsHeroOrBossUnit(enemy);
int baseGold = isHeroUnit ? 5 : 1;
float killMult = BuffSelectionController.KillGoldMultiplier;
int goldAmount = Mathf.Max(1, Mathf.RoundToInt(baseGold * killMult));
GoldManager.Instance.AddGold(goldAmount);
Debug.Log($"击杀 {(isHeroUnit ? "英雄/Boss" : "小怪")},获得金币 +{goldAmount}");
}
UpdateProgress(currentWaveIndex + 1, level.waves.Length);
}
}
if (activeEnemies.Count == 0) yield break;
yield return new WaitForSeconds(0.25f);
}
}
private bool IsHeroOrBossUnit(GameObject enemy)
{
if (enemy == null) return false;
string n = enemy.name.ToLower();
if (n.Contains("hero") || n.Contains("boss") || n.Contains("英雄") || n.Contains("石巨人") || n.Contains("狼人"))
{
return true;
}
var unit = enemy.GetComponent<UnitBase>();
if (unit != null && !string.IsNullOrEmpty(unit.characterDataName))
{
// 非小怪名称判断
if (!unit.characterDataName.Contains("小怪") && !unit.characterDataName.Contains("蓝卫") && !unit.characterDataName.Contains("弓童") && !unit.characterDataName.Contains("蛮卒"))
{
return true;
}
}
return false;
}
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;
// 小怪等级根据波次索引动态计算(前几波 Lv.1,随着波次推进适度成长,如 1~3 波 Lv.1,4~6 波 Lv.2,7~10 波 Lv.3)
int enemyLevel = Mathf.Clamp((currentWaveIndex / 3) + 1, 1, 10);
unit.InitializeFromPlayerData(data.GetMaxHealth(enemyLevel), data.GetAttackPower(enemyLevel), data.GetMoveSpeed(enemyLevel), enemyLevel);
}
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 EnsureUIReferences()
{
if (waveTextUI != null && killCountTextUI != null) return;
var canvasObject = GameObject.Find("TowerDefenseCanvas");
if (canvasObject == null) return;
var topBar = canvasObject.transform.Find("TopBar");
if (topBar == null) return;
if (waveTextUI == null)
{
var waveObj = topBar.Find("WaveText");
if (waveObj != null) waveTextUI = waveObj.GetComponent<Text>();
}
if (killCountTextUI == null)
{
var killObj = topBar.Find("KillCountText");
if (killObj != null) killCountTextUI = killObj.GetComponent<Text>();
}
}
private void UpdateProgress(int waveNumber, int waveCount, string statusHint = "")
{
EnsureUIReferences();
int currentDisplayWave = Mathf.Clamp(waveNumber > 0 ? waveNumber : 1, 1, waveCount);
if (waveTextUI != null)
{
waveTextUI.text = $"波次 {currentDisplayWave}/{waveCount}";
}
if (killCountTextUI != null)
{
killCountTextUI.text = $"击杀 {defeatedEnemies}/{totalEnemies}";
}
}
}