feat(gameplay): 更新防御关卡金币系统 - 修改金币显示文本格式,移除冒号分隔符 - 实现差异化金币奖励机制:小怪固定1金币,英雄/Boss单位固定5金币 - 添加战术倍率支持,确保最低1金币奖励 - 增加击杀日志输出功能 - 实现英雄/Boss单位识别逻辑,通过名称和角色数据进行判断 ```
316 lines
11 KiB
C#
316 lines
11 KiB
C#
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}";
|
||
}
|
||
}
|
||
}
|