using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.SceneManagement; using UnityEngine.UI; /// /// 阵型编排页控制器:底部英雄卡拖到网格格位放置英雄,「开始战斗」保存阵型并进入战斗场景。 /// 什么都不摆直接开始 = 默认阵型(近战最前排 / 远程最后排)。 /// public class FormationManager : MonoBehaviour { [Header("出战英雄名单")] public HeroRoster roster; [Header("战斗场景")] public string battleSceneName = "DefenseTest"; [Header("界面引用")] public Text hintText; public Button defaultButton; public Button startButton; public FormationCardDrag[] cards = new FormationCardDrag[0]; // 格位索引 -> 放置记录 private class Placement { public int heroIndex; public GameObject preview; } private readonly Dictionary placed = new Dictionary(); private readonly HashSet placedHeroes = new HashSet(); private Camera mainCamera; private GameObject dropHighlight; private Material highlightMaterial; private int draggingHero = -1; private static readonly Color ValidColor = new Color(0.2f, 1f, 0.35f, 0.40f); private static readonly Color InvalidColor = new Color(1f, 0.25f, 0.2f, 0.40f); private void Start() { mainCamera = Camera.main; CreateDropHighlight(); SetupCards(); if (defaultButton != null) defaultButton.onClick.AddListener(ApplyDefaultFormation); if (startButton != null) startButton.onClick.AddListener(StartBattle); UpdateHint(); } private void Update() { if (draggingHero >= 0) { UpdateDropHighlight(Input.mousePosition); } // 点击场上的英雄把它收回卡片 if (Input.GetMouseButtonDown(0) && !IsPointerOverUI()) { TryRemoveAtPointer(); } } // ===== 卡片初始化 ===== private void SetupCards() { if (cards == null) return; for (int i = 0; i < cards.Length; i++) { FormationCardDrag card = cards[i]; if (card == null) continue; card.manager = this; card.heroIndex = i; HeroEntry entry = GetHero(i); card.SetContent(entry != null ? entry.displayName : $"英雄{i + 1}", entry != null ? entry.icon : null); card.SetPlaced(false); } } private HeroEntry GetHero(int index) { if (roster == null || roster.heroes == null) return null; if (index < 0 || index >= roster.heroes.Length) return null; return roster.heroes[index]; } private int HeroCount => roster != null && roster.heroes != null ? roster.heroes.Length : 0; // ===== 拖拽回调(由 FormationCardDrag 调用) ===== public bool CanDrag(int heroIndex) { return GetHero(heroIndex) != null && !placedHeroes.Contains(heroIndex); } public void OnCardDragStart(int heroIndex) { draggingHero = heroIndex; UpdateDropHighlight(Input.mousePosition); } public void OnCardDragging(int heroIndex, Vector2 screenPosition) { draggingHero = heroIndex; UpdateDropHighlight(screenPosition); } public void OnCardDragEnd(int heroIndex, Vector2 screenPosition) { draggingHero = -1; if (dropHighlight != null) dropHighlight.SetActive(false); int cell = ScreenToCell(screenPosition); if (cell < 0) { return; // 没拖到网格范围内,忽略 } if (placed.ContainsKey(cell)) { Debug.Log("这个格位已经有英雄了"); return; } PlaceHero(heroIndex, cell); } // ===== 放置 / 移除 ===== private void PlaceHero(int heroIndex, int cell) { HeroEntry entry = GetHero(heroIndex); if (entry == null || entry.prefab == null || !DeployGrid.IsValidIndex(cell)) return; if (placed.ContainsKey(cell)) return; GameObject preview = Instantiate(entry.prefab, DeployGrid.CellPosition(cell), Quaternion.identity); preview.name = $"预览_{entry.displayName}"; PreparePreview(preview); placed[cell] = new Placement { heroIndex = heroIndex, preview = preview }; placedHeroes.Add(heroIndex); if (cards != null && heroIndex < cards.Length && cards[heroIndex] != null) { cards[heroIndex].SetPlaced(true); } UpdateHint(); } /// 预览体只做展示:关掉 AI 与寻路,避免它自己跑起来。 private static void PreparePreview(GameObject preview) { var unitBase = preview.GetComponent(); if (unitBase != null) unitBase.enabled = false; var agent = preview.GetComponent(); if (agent != null) agent.enabled = false; var status = preview.GetComponent(); if (status != null) status.enabled = false; var animator = preview.GetComponent(); if (animator != null) animator.applyRootMotion = false; } private bool TryRemoveAtPointer() { if (mainCamera == null) { mainCamera = Camera.main; if (mainCamera == null) return false; } Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition); RaycastHit[] hits = Physics.RaycastAll(ray, 200f); GameObject closest = null; float closestDistance = float.MaxValue; foreach (RaycastHit hit in hits) { GameObject root = hit.collider.transform.root.gameObject; if (IsPreview(root) && hit.distance < closestDistance) { closestDistance = hit.distance; closest = root; } } if (closest == null) return false; RemovePreview(closest); return true; } private bool IsPreview(GameObject candidate) { foreach (var pair in placed) { if (pair.Value.preview == candidate) return true; } return false; } private void RemovePreview(GameObject preview) { int cell = -1; foreach (var pair in placed) { if (pair.Value.preview == preview) { cell = pair.Key; break; } } if (cell < 0) return; int heroIndex = placed[cell].heroIndex; placed.Remove(cell); placedHeroes.Remove(heroIndex); if (cards != null && heroIndex < cards.Length && cards[heroIndex] != null) { cards[heroIndex].SetPlaced(false); } Destroy(preview); UpdateHint(); } private void ClearAll() { foreach (var pair in placed) { if (pair.Value.preview != null) Destroy(pair.Value.preview); } placed.Clear(); placedHeroes.Clear(); if (cards != null) { foreach (FormationCardDrag card in cards) { if (card != null) card.SetPlaced(false); } } } // ===== 默认阵型 ===== public void ApplyDefaultFormation() { ClearAll(); int meleeTotal = 0, rangedTotal = 0; for (int i = 0; i < HeroCount; i++) { HeroEntry entry = GetHero(i); if (entry == null) continue; if (entry.isMelee) meleeTotal++; else rangedTotal++; } int meleeIndex = 0, rangedIndex = 0; for (int i = 0; i < HeroCount; i++) { HeroEntry entry = GetHero(i); if (entry == null) continue; int cell = entry.isMelee ? DeployGrid.DefaultCell(true, meleeIndex++, meleeTotal) : DeployGrid.DefaultCell(false, rangedIndex++, rangedTotal); PlaceHero(i, cell); } UpdateHint(); } /// 没摆放的英雄补到默认位置,保证开战时全部出战。 private void FillUnplacedWithDefault() { for (int i = 0; i < HeroCount; i++) { HeroEntry entry = GetHero(i); if (entry == null || placedHeroes.Contains(i)) continue; int row = entry.isMelee ? DeployGrid.Rows - 1 : 0; int cell = FindFreeCellInRow(row); if (cell < 0) cell = FindFreeCellInRow(DeployGrid.Rows / 2); if (cell >= 0) PlaceHero(i, cell); } } private int FindFreeCellInRow(int row) { for (int c = 0; c < DeployGrid.Cols; c++) { int index = DeployGrid.ToIndex(row, c); if (!placed.ContainsKey(index)) return index; } return -1; } // ===== 保存阵型 ===== public void StartBattle() { FillUnplacedWithDefault(); SaveFormationToJson(); } private void SaveFormationToJson() { int count = HeroCount; var characterIds = new string[count]; var cells = new int[count]; // 初始化数组 for (int i = 0; i < count; i++) { HeroEntry entry = GetHero(i); characterIds[i] = entry != null ? GetCharacterId(entry.prefab) : ""; cells[i] = -1; } // 填充已放置的格位 foreach (var pair in placed) { int heroIndex = pair.Value.heroIndex; if (heroIndex >= 0 && heroIndex < count) { cells[heroIndex] = pair.Key; } } // 保存到 PlayerData.json if (PlayerDataManager.Instance != null) { PlayerDataManager.Instance.SaveFormation(characterIds, cells); } else { Debug.LogError("PlayerDataManager.Instance 未找到,无法保存阵型"); } } /// /// 根据 Prefab 获取 characterId /// private string GetCharacterId(GameObject prefab) { if (prefab == null) return ""; // 查找对应的 CharacterData var allData = Resources.LoadAll("Data"); foreach (var data in allData) { if (data.unitPrefab == prefab) { return data.characterId; } } return ""; } private void SaveFormation() { int count = HeroCount; var prefabs = new GameObject[count]; var cells = new int[count]; for (int i = 0; i < count; i++) { HeroEntry entry = GetHero(i); prefabs[i] = entry != null ? entry.prefab : null; cells[i] = -1; } foreach (var pair in placed) { int heroIndex = pair.Value.heroIndex; if (heroIndex >= 0 && heroIndex < count) { cells[heroIndex] = pair.Key; } } FormationData.HeroPrefabs = prefabs; FormationData.CellIndices = cells; FormationData.HasCustomFormation = placed.Count > 0; } // ===== 落点高亮与坐标换算 ===== private void CreateDropHighlight() { dropHighlight = GameObject.CreatePrimitive(PrimitiveType.Quad); dropHighlight.name = "DropHighlight"; Destroy(dropHighlight.GetComponent()); dropHighlight.transform.rotation = Quaternion.Euler(90f, 0f, 0f); dropHighlight.transform.localScale = new Vector3(DeployGrid.CellWidth * 0.95f, DeployGrid.CellDepth * 0.95f, 1f); highlightMaterial = new Material(Shader.Find("Sprites/Default")); dropHighlight.GetComponent().sharedMaterial = highlightMaterial; dropHighlight.SetActive(false); } private void UpdateDropHighlight(Vector2 screenPosition) { if (dropHighlight == null) return; int cell = ScreenToCell(screenPosition); if (cell < 0) { dropHighlight.SetActive(false); return; } dropHighlight.SetActive(true); Vector3 position = DeployGrid.CellPosition(cell); dropHighlight.transform.position = new Vector3(position.x, DeployGrid.GridY + 0.02f, position.z); highlightMaterial.color = placed.ContainsKey(cell) ? InvalidColor : ValidColor; } /// 屏幕坐标 -> 格位索引(用地平面求交,不受场上单位碰撞体干扰)。 private int ScreenToCell(Vector2 screenPosition) { if (mainCamera == null) { mainCamera = Camera.main; if (mainCamera == null) return -1; } Ray ray = mainCamera.ScreenPointToRay(screenPosition); var groundPlane = new Plane(Vector3.up, new Vector3(0f, DeployGrid.UnitY, 0f)); if (!groundPlane.Raycast(ray, out float distance)) return -1; return DeployGrid.SnapToCell(ray.GetPoint(distance)); } private static bool IsPointerOverUI() { return EventSystem.current != null && EventSystem.current.IsPointerOverGameObject(); } private void UpdateHint() { if (hintText == null) return; if (placed.Count == 0) { hintText.text = "把下面的英雄卡拖到网格上摆放;不摆放也可以,直接开战 = 默认阵型(近战前排 / 远程后排)"; } else { hintText.text = $"已摆放 {placed.Count}/{HeroCount} 点击场上的英雄可以收回"; } } }