根据提供的code differences信息,由于没有具体的代码变更内容,我将生成一个通用的commit message模板:
``` docs(readme): 更新文档说明 - 添加项目使用指南 - 完善API接口说明 - 修正错误的配置示例 ``` 注意:由于您提供的code differences部分为空,以上是一个示例模板。当您有具体的代码变更时,请提供详细的变更内容以便生成更准确的commit message。
This commit is contained in:
1 parent
11f1af357d
commit
7d9d0f52d1
83 files changed
+3510
-1493
No files matched your search
@@ -245,7 +245,7 @@ public class BattleFormationSpawner : MonoBehaviour
|
||||
UnityEngine.Sprite icon = characterData != null ? characterData.cardIcon : null;
|
||||
|
||||
// 绑定英雄到卡片
|
||||
heroCardUI.SetHero(unitBase, icon, spawnPos);
|
||||
heroCardUI.SetHero(unitBase, icon, spawnPos, characterData);
|
||||
|
||||
spawnedHeroCount++;
|
||||
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
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,599 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
/// <summary>
|
||||
/// 塔防模式游戏流程控制器
|
||||
/// 1. 开局进入布阵阶段:显示部署网格,玩家把底部英雄卡拖到格位上
|
||||
/// 2. 点击"开始游戏"后收起布阵 UI、显示血条并按波次出兵
|
||||
/// 3. 每波结束后英雄回到自己的格位(锚点)
|
||||
/// </summary>
|
||||
public class DefenseGameFlowController : MonoBehaviour
|
||||
{
|
||||
public static DefenseGameFlowController Instance { get; private set; }
|
||||
|
||||
[Header("引用")]
|
||||
public DefenseWaveLevelController waveLevelController;
|
||||
public Button startBattleButton;
|
||||
public GameObject deploymentUI; // 布阵 UI(英雄卡片面板)
|
||||
|
||||
[Header("英雄配置")]
|
||||
public HeroRoster roster;
|
||||
public Transform heroParent; // 生成英雄的父节点(战场/英雄)
|
||||
public int playerTeamId = 0;
|
||||
|
||||
[Header("布阵配置")]
|
||||
public int maxHeroCount = 5;
|
||||
public string deployGridObjectName = "部署网格"; // 战场 下的可见网格对象名
|
||||
|
||||
[Header("阵位守护")]
|
||||
[Tooltip("英雄只在「离自己格位不超过寻敌视野 detectionRange」的范围内接敌,避免一路推出去。")]
|
||||
public bool heroGuardPost = true;
|
||||
|
||||
// 布阵登记
|
||||
private readonly Dictionary<int, int> cardToCell = new Dictionary<int, int>(); // 卡片序号 -> 格位
|
||||
private readonly Dictionary<int, GameObject> cellToHero = new Dictionary<int, GameObject>(); // 格位 -> 英雄
|
||||
private readonly Dictionary<int, HeroCardDrag> cardDrags = new Dictionary<int, HeroCardDrag>();
|
||||
|
||||
// 已生成英雄
|
||||
private readonly List<GameObject> spawnedHeroes = new List<GameObject>();
|
||||
private readonly Dictionary<GameObject, Vector3> heroInitialPositions = new Dictionary<GameObject, Vector3>();
|
||||
private readonly List<GameObject> hiddenHeroBars = new List<GameObject>(); // 布阵阶段藏起来的血条
|
||||
|
||||
private GameObject deployGridRoot;
|
||||
private bool battleStarted;
|
||||
|
||||
/// <summary>true = 还在布阵阶段(可以拖动卡牌)。</summary>
|
||||
public bool IsDeployPhase => !battleStarted;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
// 场上不应该有预先摆好的英雄
|
||||
HideExistingHeroes();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (waveLevelController != null)
|
||||
{
|
||||
waveLevelController.autoStart = false;
|
||||
}
|
||||
|
||||
if (startBattleButton != null)
|
||||
{
|
||||
startBattleButton.onClick.AddListener(OnStartBattleClicked);
|
||||
startBattleButton.interactable = true;
|
||||
}
|
||||
|
||||
if (deploymentUI != null)
|
||||
{
|
||||
deploymentUI.SetActive(true);
|
||||
}
|
||||
|
||||
SetupDeployGrid();
|
||||
DeployHighlight.Ensure();
|
||||
InitializeCardDrag();
|
||||
|
||||
Debug.Log("DefenseGameFlowController: 布阵阶段,把英雄卡拖到网格上。");
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (battleStarted) return;
|
||||
|
||||
// 布阵阶段:点击已经放好的英雄可以把他撤下来
|
||||
if (!TryGetTap(out Vector2 screenPosition)) return;
|
||||
if (IsPointerOverUI()) return;
|
||||
|
||||
var camera = Camera.main;
|
||||
if (camera == null) return;
|
||||
|
||||
var ray = camera.ScreenPointToRay(screenPosition);
|
||||
if (!Physics.Raycast(ray, out RaycastHit hit, 300f, ~0, QueryTriggerInteraction.Collide)) return;
|
||||
|
||||
var unit = hit.collider.GetComponentInParent<UnitBase>();
|
||||
if (unit != null && spawnedHeroes.Contains(unit.gameObject))
|
||||
{
|
||||
RemoveHero(unit.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (Instance == this) Instance = null;
|
||||
}
|
||||
|
||||
// ── 布阵阶段 ────────────────────────────────────────────
|
||||
|
||||
/// <summary>隐藏场景里预先摆好的英雄。</summary>
|
||||
private void HideExistingHeroes()
|
||||
{
|
||||
foreach (var unit in FindObjectsOfType<UnitBase>(true))
|
||||
{
|
||||
unit.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>找到可见的部署网格,摆正到 DeployGrid 定义的位置并显示。</summary>
|
||||
private void SetupDeployGrid()
|
||||
{
|
||||
var battlefield = GameObject.Find("战场");
|
||||
if (battlefield != null)
|
||||
{
|
||||
var child = battlefield.transform.Find(deployGridObjectName);
|
||||
if (child != null) deployGridRoot = child.gameObject;
|
||||
}
|
||||
if (deployGridRoot == null)
|
||||
{
|
||||
deployGridRoot = GameObject.Find(deployGridObjectName);
|
||||
}
|
||||
if (deployGridRoot == null)
|
||||
{
|
||||
Debug.LogWarning($"DefenseGameFlowController: 没找到部署网格对象「{deployGridObjectName}」。");
|
||||
return;
|
||||
}
|
||||
|
||||
AlignGridCells(deployGridRoot.transform);
|
||||
deployGridRoot.SetActive(true);
|
||||
}
|
||||
|
||||
/// <summary>把网格下的格位按 DeployGrid 的坐标摆好,保证视觉与吸附完全一致。</summary>
|
||||
private static void AlignGridCells(Transform gridRoot)
|
||||
{
|
||||
gridRoot.localPosition = new Vector3(0f, DeployGrid.GridY, 0f);
|
||||
gridRoot.localRotation = Quaternion.identity;
|
||||
|
||||
int count = gridRoot.childCount;
|
||||
if (count == 0) return;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var cell = gridRoot.GetChild(i);
|
||||
int index = ParseCellIndex(cell.name, i);
|
||||
if (!DeployGrid.IsValidIndex(index)) continue;
|
||||
|
||||
var pos = DeployGrid.CellPosition(index);
|
||||
cell.localPosition = new Vector3(pos.x, 0f, pos.z);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>格位命名形如「格位_3_4」(行_列,从 1 开始);解析失败时退回顺序索引。</summary>
|
||||
private static int ParseCellIndex(string name, int fallback)
|
||||
{
|
||||
var parts = name.Split('_');
|
||||
if (parts.Length >= 3
|
||||
&& int.TryParse(parts[parts.Length - 2], out int row)
|
||||
&& int.TryParse(parts[parts.Length - 1], out int col))
|
||||
{
|
||||
int r = row - 1;
|
||||
int c = col - 1;
|
||||
if (r >= 0 && r < DeployGrid.Rows && c >= 0 && c < DeployGrid.Cols)
|
||||
{
|
||||
return DeployGrid.ToIndex(r, c);
|
||||
}
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按玩家出战阵容(PlayerData)给底部每张英雄卡挂上拖动组件,并刷新卡面。
|
||||
/// 站位不再从 PlayerData 读取(改成开局手动布阵),但「用哪几个角色」仍然读 PlayerData。
|
||||
/// </summary>
|
||||
private void InitializeCardDrag()
|
||||
{
|
||||
var canvasObj = GameObject.Find("TowerDefenseCanvas");
|
||||
if (canvasObj == null)
|
||||
{
|
||||
Debug.LogWarning("DefenseGameFlowController: 没找到 TowerDefenseCanvas。");
|
||||
return;
|
||||
}
|
||||
|
||||
var heroPanel = canvasObj.transform.Find("BottomUI/HeroCardsPanel");
|
||||
if (heroPanel == null)
|
||||
{
|
||||
Debug.LogWarning("DefenseGameFlowController: 没找到 BottomUI/HeroCardsPanel。");
|
||||
return;
|
||||
}
|
||||
|
||||
var deck = GetDeckCharacters();
|
||||
|
||||
for (int i = 0; i < maxHeroCount; i++)
|
||||
{
|
||||
var cardObj = heroPanel.Find($"HeroCard{i + 1}");
|
||||
if (cardObj == null) continue;
|
||||
|
||||
var drag = cardObj.GetComponent<HeroCardDrag>();
|
||||
if (drag == null)
|
||||
{
|
||||
drag = cardObj.gameObject.AddComponent<HeroCardDrag>();
|
||||
}
|
||||
|
||||
var data = i < deck.Length ? deck[i] : null;
|
||||
drag.Setup(i, data != null ? data.unitPrefab : null, data);
|
||||
cardDrags[i] = drag;
|
||||
|
||||
// 卡面(头像 / 名字 / 稀有度)也和出战阵容保持一致
|
||||
var cardUI = cardObj.GetComponent<HeroCardUI>();
|
||||
if (cardUI != null && data != null)
|
||||
{
|
||||
cardUI.SetCharacterInfo(data);
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"DefenseGameFlowController: 已按出战阵容开启 {cardDrags.Count} 张卡片的拖动。");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取玩家出战阵容(PlayerData.json 的 currentDeck),按槽位返回角色数据。
|
||||
/// PlayerData 不可用时回退到场景里配置的 HeroRoster。
|
||||
/// </summary>
|
||||
private CharacterData[] GetDeckCharacters()
|
||||
{
|
||||
var deck = new CharacterData[maxHeroCount];
|
||||
var allData = Resources.LoadAll<CharacterData>("Data");
|
||||
|
||||
if (PlayerDataManager.Instance != null)
|
||||
{
|
||||
for (int i = 0; i < maxHeroCount; i++)
|
||||
{
|
||||
var owned = PlayerDataManager.Instance.GetDeckCharacter(i);
|
||||
if (owned == null || string.IsNullOrEmpty(owned.characterId)) continue;
|
||||
|
||||
foreach (var data in allData)
|
||||
{
|
||||
if (data == null || data.characterId != owned.characterId) continue;
|
||||
if (data.unitPrefab == null) break; // 只有数据的角色(如荒野之王)不能出战
|
||||
deck[i] = data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 兜底:PlayerData 没给出阵容时用 HeroRoster
|
||||
bool anyDeck = false;
|
||||
foreach (var d in deck) if (d != null) { anyDeck = true; break; }
|
||||
|
||||
if (!anyDeck && roster != null && roster.heroes != null)
|
||||
{
|
||||
for (int i = 0; i < roster.heroes.Length && i < maxHeroCount; i++)
|
||||
{
|
||||
var entry = roster.heroes[i];
|
||||
if (entry == null || entry.prefab == null) continue;
|
||||
deck[i] = FindCharacterDataForPrefab(entry.prefab);
|
||||
}
|
||||
}
|
||||
|
||||
return deck;
|
||||
}
|
||||
|
||||
/// <summary>把某张卡的英雄放到格位上。成功返回 true。</summary>
|
||||
public bool PlaceHeroFromCard(int cardIndex, int cellIndex)
|
||||
{
|
||||
if (battleStarted) return false;
|
||||
if (cardIndex < 0 || cardIndex >= maxHeroCount) return false;
|
||||
if (!DeployGrid.IsValidIndex(cellIndex)) return false;
|
||||
if (IsCardPlaced(cardIndex)) return false;
|
||||
if (IsCellOccupied(cellIndex)) return false;
|
||||
|
||||
if (!cardDrags.TryGetValue(cardIndex, out var drag) || drag == null || drag.heroPrefab == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var hero = SpawnHero(drag.heroPrefab, cellIndex, cardIndex);
|
||||
if (hero == null) return false;
|
||||
|
||||
cardToCell[cardIndex] = cellIndex;
|
||||
cellToHero[cellIndex] = hero;
|
||||
|
||||
Debug.Log($"布阵: 第 {cardIndex + 1} 张卡 → 格位 {cellIndex} ({hero.name})");
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>撤销某个格位上的英雄,并让对应卡片恢复可用。</summary>
|
||||
public void RemoveHeroAt(int cellIndex)
|
||||
{
|
||||
if (cellToHero.TryGetValue(cellIndex, out var hero) && hero != null)
|
||||
{
|
||||
RemoveHero(hero);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveHero(GameObject hero)
|
||||
{
|
||||
int cardIndex = -1;
|
||||
foreach (var kv in cardToCell)
|
||||
{
|
||||
if (cellToHero.TryGetValue(kv.Value, out var h) && h == hero)
|
||||
{
|
||||
cardIndex = kv.Key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cardIndex >= 0)
|
||||
{
|
||||
cardToCell.Remove(cardIndex);
|
||||
if (cardDrags.TryGetValue(cardIndex, out var drag) && drag != null)
|
||||
{
|
||||
drag.SetPlaced(false);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kv in new List<KeyValuePair<int, GameObject>>(cellToHero))
|
||||
{
|
||||
if (kv.Value == hero) cellToHero.Remove(kv.Key);
|
||||
}
|
||||
|
||||
spawnedHeroes.Remove(hero);
|
||||
heroInitialPositions.Remove(hero);
|
||||
|
||||
Debug.Log($"撤回英雄: {hero.name}");
|
||||
Destroy(hero);
|
||||
}
|
||||
|
||||
public bool IsCellOccupied(int cellIndex)
|
||||
{
|
||||
return cellToHero.TryGetValue(cellIndex, out var hero) && hero != null;
|
||||
}
|
||||
|
||||
public bool IsCardPlaced(int cardIndex)
|
||||
{
|
||||
return cardToCell.ContainsKey(cardIndex);
|
||||
}
|
||||
|
||||
public int GetCellOfCard(int cardIndex)
|
||||
{
|
||||
return cardToCell.TryGetValue(cardIndex, out int cell) ? cell : -1;
|
||||
}
|
||||
|
||||
// ── 开战 ────────────────────────────────────────────────
|
||||
|
||||
private void OnStartBattleClicked()
|
||||
{
|
||||
if (battleStarted) return;
|
||||
battleStarted = true;
|
||||
|
||||
if (deploymentUI != null) deploymentUI.SetActive(false);
|
||||
if (startBattleButton != null) startBattleButton.gameObject.SetActive(false);
|
||||
if (deployGridRoot != null) deployGridRoot.SetActive(false);
|
||||
if (DeployHighlight.Instance != null) DeployHighlight.Instance.Hide();
|
||||
|
||||
// 卡片从"待放置"切换成"英雄状态显示":恢复满不透明度
|
||||
foreach (var drag in cardDrags.Values)
|
||||
{
|
||||
if (drag != null) drag.SetPlaced(false);
|
||||
}
|
||||
|
||||
// 一个都没布置时用默认阵型兜底
|
||||
if (cellToHero.Count == 0)
|
||||
{
|
||||
SpawnDefaultFormation();
|
||||
}
|
||||
|
||||
// 布阵阶段藏起来的血条,开战后显示
|
||||
foreach (var barObject in hiddenHeroBars)
|
||||
{
|
||||
if (barObject != null) barObject.SetActive(true);
|
||||
}
|
||||
|
||||
Debug.Log("DefenseGameFlowController: 开始游戏,出兵!");
|
||||
StartCoroutine(StartBattleSequence());
|
||||
}
|
||||
|
||||
private IEnumerator StartBattleSequence()
|
||||
{
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
if (waveLevelController != null)
|
||||
{
|
||||
StartCoroutine(waveLevelController.StartLevel());
|
||||
}
|
||||
}
|
||||
|
||||
// ── 生成英雄 ────────────────────────────────────────────
|
||||
|
||||
private GameObject SpawnHero(GameObject prefab, int cell, int cardIndex)
|
||||
{
|
||||
var characterData = FindCharacterDataForPrefab(prefab);
|
||||
var spawnPos = DeployGrid.CellPosition(cell);
|
||||
|
||||
var unit = Instantiate(prefab, spawnPos, Quaternion.identity);
|
||||
unit.name = "英雄_" + prefab.name;
|
||||
|
||||
if (heroParent != null)
|
||||
{
|
||||
unit.transform.SetParent(heroParent, true);
|
||||
}
|
||||
|
||||
var team = unit.GetComponent<UnitTeam>();
|
||||
if (team != null) team.teamId = playerTeamId;
|
||||
|
||||
var unitBase = unit.GetComponent<UnitBase>();
|
||||
if (unitBase != null)
|
||||
{
|
||||
unitBase.returnToAnchorWhenIdle = true;
|
||||
unitBase.anchorArrivalDistance = 0.35f;
|
||||
// 阵位守护:接敌范围直接用单位自己的寻敌视野 detectionRange
|
||||
unitBase.useAnchorLeash = heroGuardPost;
|
||||
unitBase.ResetAnchorPosition();
|
||||
}
|
||||
|
||||
if (characterData != null && unitBase != null)
|
||||
{
|
||||
var 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));
|
||||
}
|
||||
|
||||
var healthBar = unit.GetComponent<WorldHealthBar>();
|
||||
if (healthBar != null)
|
||||
{
|
||||
if (playerTeamId == 0) healthBar.canRevive = true;
|
||||
|
||||
// 布阵阶段把血条收起来,开战时再显示(血条是 WorldHealthBar.Awake 生成的子物体)
|
||||
var barObject = unit.transform.Find("WorldHealthBar");
|
||||
if (barObject != null && !battleStarted)
|
||||
{
|
||||
barObject.gameObject.SetActive(false);
|
||||
hiddenHeroBars.Add(barObject.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
spawnedHeroes.Add(unit);
|
||||
heroInitialPositions[unit] = spawnPos;
|
||||
|
||||
BindHeroToCard(unit, characterData, spawnPos, cardIndex);
|
||||
|
||||
return unit;
|
||||
}
|
||||
|
||||
/// <summary>一张都没摆时用出战阵容按"近战前排 / 远程后排"兜底。</summary>
|
||||
private void SpawnDefaultFormation()
|
||||
{
|
||||
var deck = GetDeckCharacters();
|
||||
|
||||
int meleeTotal = 0, rangedTotal = 0;
|
||||
for (int i = 0; i < deck.Length; i++)
|
||||
{
|
||||
if (deck[i] == null) continue;
|
||||
if (IsMelee(deck[i])) meleeTotal++; else rangedTotal++;
|
||||
}
|
||||
|
||||
var used = new HashSet<int>();
|
||||
int meleeIndex = 0, rangedIndex = 0, spawned = 0;
|
||||
|
||||
for (int i = 0; i < deck.Length && spawned < maxHeroCount; i++)
|
||||
{
|
||||
var data = deck[i];
|
||||
if (data == null || data.unitPrefab == null) continue;
|
||||
|
||||
int cell = IsMelee(data)
|
||||
? 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);
|
||||
if (SpawnHero(data.unitPrefab, cell, i) == null) continue;
|
||||
|
||||
cellToHero[cell] = spawnedHeroes[spawnedHeroes.Count - 1];
|
||||
spawned++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>判定角色是否近战(远程单位 attackType 为 RangedProjectile)。</summary>
|
||||
private static bool IsMelee(CharacterData data)
|
||||
{
|
||||
// 用"是否配置了投射物"来判定更可靠:RangedProjectile 一定有 projectilePrefab
|
||||
return data.unitPrefab == null || data.unitPrefab.GetComponent<UnitBase>()?.attackType != UnitBase.AttackType.RangedProjectile;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>把英雄绑到它来源的那张卡片上(显示名字 / 稀有度 / 血条)。</summary>
|
||||
private void BindHeroToCard(GameObject heroUnit, CharacterData characterData, Vector3 spawnPos, int cardIndex)
|
||||
{
|
||||
if (cardIndex < 0 || cardIndex >= maxHeroCount) return;
|
||||
if (!cardDrags.TryGetValue(cardIndex, out var drag) || drag == null) return;
|
||||
|
||||
var cardUI = drag.GetComponent<HeroCardUI>();
|
||||
var unitBase = heroUnit.GetComponent<UnitBase>();
|
||||
if (cardUI == null || unitBase == null) return;
|
||||
|
||||
var icon = characterData != null ? characterData.cardIcon : null;
|
||||
cardUI.SetHero(unitBase, icon, spawnPos, characterData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一波清完后让英雄回格位。
|
||||
/// 正常情况放开目标,让 UnitBase 自己走回格位;只有寻路真的不可用(卡住)时才瞬移兜底。
|
||||
/// </summary>
|
||||
public void ResetHeroPositions()
|
||||
{
|
||||
foreach (var hero in spawnedHeroes)
|
||||
{
|
||||
if (hero == null || !hero.activeSelf) continue;
|
||||
|
||||
var unitBase = hero.GetComponent<UnitBase>();
|
||||
var navAgent = hero.GetComponent<UnityEngine.AI.NavMeshAgent>();
|
||||
|
||||
bool canWalk = UnityEngine.AI.NavMesh.SamplePosition(
|
||||
hero.transform.position, out _, 2f, UnityEngine.AI.NavMesh.AllAreas);
|
||||
|
||||
if (navAgent != null && navAgent.enabled && canWalk)
|
||||
{
|
||||
if (unitBase != null) unitBase.target = null; // 放开目标,进入回位
|
||||
navAgent.isStopped = false;
|
||||
navAgent.ResetPath();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 卡住了(比如被挤出 NavMesh)才直接摆回格位
|
||||
if (heroInitialPositions.TryGetValue(hero, out var initialPos))
|
||||
{
|
||||
Debug.LogWarning($"DefenseGameFlowController: {hero.name} 寻路不可用,瞬移回格位");
|
||||
hero.transform.position = initialPos;
|
||||
if (navAgent != null) navAgent.nextPosition = initialPos;
|
||||
if (unitBase != null) unitBase.ResetAnchorPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 工具 ────────────────────────────────────────────────
|
||||
|
||||
private CharacterData FindCharacterDataForPrefab(GameObject prefab)
|
||||
{
|
||||
if (prefab == null) return null;
|
||||
foreach (var data in Resources.LoadAll<CharacterData>("Data"))
|
||||
{
|
||||
if (data != null && data.unitPrefab == prefab) return data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool TryGetTap(out Vector2 screenPosition)
|
||||
{
|
||||
if (Input.GetMouseButtonDown(0))
|
||||
{
|
||||
screenPosition = Input.mousePosition;
|
||||
return true;
|
||||
}
|
||||
if (Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Began)
|
||||
{
|
||||
screenPosition = Input.GetTouch(0).position;
|
||||
return true;
|
||||
}
|
||||
screenPosition = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsPointerOverUI()
|
||||
{
|
||||
if (EventSystem.current == null) return false;
|
||||
|
||||
if (Input.touchCount > 0)
|
||||
{
|
||||
return EventSystem.current.IsPointerOverGameObject(Input.GetTouch(0).fingerId);
|
||||
}
|
||||
return EventSystem.current.IsPointerOverGameObject();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: CXkWsyr5V3PqLCVXf9kh9UDgagA4PNu+m8m3e+26o8Vi3MvAgRRmtzA=
|
||||
guid: B38YtCOpVSouc7G4rbHoBIMoJtLgT79fWI15sJ401Ny0ANjUOgZHriM=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -59,6 +59,19 @@ public class DefenseWaveLevelController : MonoBehaviour
|
||||
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)
|
||||
@@ -88,12 +101,29 @@ public class DefenseWaveLevelController : MonoBehaviour
|
||||
yield return new WaitForSeconds(Mathf.Max(0f, wave.restBeforeWave));
|
||||
yield return SpawnWave(wave);
|
||||
yield return WaitForWaveCleared();
|
||||
|
||||
// 每波结束后,让英雄回到初始位置
|
||||
ResetHeroPositionsAfterWave();
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user