using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; /// /// 塔防模式游戏流程控制器 /// 1. 开局进入布阵阶段:显示部署网格,玩家把底部英雄卡拖到格位上 /// 2. 点击"开始游戏"后收起布阵 UI、显示血条并按波次出兵 /// 3. 每波结束后英雄回到自己的格位(锚点) /// 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 cardToCell = new Dictionary(); // 卡片序号 -> 格位 private readonly Dictionary cellToHero = new Dictionary(); // 格位 -> 英雄 private readonly Dictionary cardDrags = new Dictionary(); // 已生成英雄 private readonly List spawnedHeroes = new List(); private readonly Dictionary heroInitialPositions = new Dictionary(); private readonly List hiddenHeroBars = new List(); // 布阵阶段藏起来的血条 private GameObject deployGridRoot; private bool battleStarted; /// true = 还在布阵阶段(可以拖动卡牌)。 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(); if (unit != null && spawnedHeroes.Contains(unit.gameObject)) { RemoveHero(unit.gameObject); } } private void OnDestroy() { if (Instance == this) Instance = null; } // ── 布阵阶段 ──────────────────────────────────────────── /// 隐藏场景里预先摆好的英雄。 private void HideExistingHeroes() { foreach (var unit in FindObjectsOfType(true)) { unit.gameObject.SetActive(false); } } /// 找到可见的部署网格,摆正到 DeployGrid 定义的位置并显示。 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); } /// 把网格下的格位按 DeployGrid 的坐标摆好,保证视觉与吸附完全一致。 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); } } /// 格位命名形如「格位_3_4」(行_列,从 1 开始);解析失败时退回顺序索引。 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; } /// /// 按玩家出战阵容(PlayerData)给底部每张英雄卡挂上拖动组件,并刷新卡面。 /// 站位不再从 PlayerData 读取(改成开局手动布阵),但「用哪几个角色」仍然读 PlayerData。 /// 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(); if (drag == null) { drag = cardObj.gameObject.AddComponent(); } var data = i < deck.Length ? deck[i] : null; drag.Setup(i, data != null ? data.unitPrefab : null, data); cardDrags[i] = drag; // 卡面(头像 / 名字 / 稀有度)也和出战阵容保持一致 var cardUI = cardObj.GetComponent(); if (cardUI != null && data != null) { cardUI.SetCharacterInfo(data); } } Debug.Log($"DefenseGameFlowController: 已按出战阵容开启 {cardDrags.Count} 张卡片的拖动。"); } /// /// 读取玩家出战阵容(PlayerData.json 的 currentDeck),按槽位返回角色数据。 /// PlayerData 不可用时回退到场景里配置的 HeroRoster。 /// private CharacterData[] GetDeckCharacters() { var deck = new CharacterData[maxHeroCount]; var allData = Resources.LoadAll("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; } /// 把某张卡的英雄放到格位上。成功返回 true。 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; } /// 撤销某个格位上的英雄,并让对应卡片恢复可用。 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>(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(); if (team != null) team.teamId = playerTeamId; var unitBase = unit.GetComponent(); 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(); 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; } /// 一张都没摆时用出战阵容按"近战前排 / 远程后排"兜底。 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 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++; } } /// 判定角色是否近战(远程单位 attackType 为 RangedProjectile)。 private static bool IsMelee(CharacterData data) { // 用"是否配置了投射物"来判定更可靠:RangedProjectile 一定有 projectilePrefab return data.unitPrefab == null || data.unitPrefab.GetComponent()?.attackType != UnitBase.AttackType.RangedProjectile; } private static int NextFreeCell(int from, HashSet used) { for (int i = 0; i < DeployGrid.CellCount; i++) { int candidate = (from + i) % DeployGrid.CellCount; if (!used.Contains(candidate)) return candidate; } return -1; } /// 把英雄绑到它来源的那张卡片上(显示名字 / 稀有度 / 血条)。 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(); var unitBase = heroUnit.GetComponent(); if (cardUI == null || unitBase == null) return; var icon = characterData != null ? characterData.cardIcon : null; cardUI.SetHero(unitBase, icon, spawnPos, characterData); } /// /// 一波清完后让英雄回格位。 /// 正常情况放开目标,让 UnitBase 自己走回格位;只有寻路真的不可用(卡住)时才瞬移兜底。 /// public void ResetHeroPositions() { foreach (var hero in spawnedHeroes) { if (hero == null || !hero.activeSelf) continue; var unitBase = hero.GetComponent(); var navAgent = hero.GetComponent(); 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("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(); } }