using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; // 决战玩法管理器:布阵阶段在己方半场网格放置单位,开战后生成敌方阵型,全灭判定胜负。 public class BattleSetupManager : MonoBehaviour { public static BattleSetupManager Instance { get; private set; } public enum BattleState { Setup, Battle, Ended } [Header("布阵网格")] public float cellSize = 2f; public Vector2 xRange = new Vector2(-8f, 8f); public Vector2 zRange = new Vector2(-20f, -8f); [Header("人口")] public int maxPopulation = 30; [Header("可用兵种(生成底部卡片)")] public CharacterData[] availableCharacters; [Header("敌方阵型")] public CharacterData[] enemyFrontLine; public CharacterData[] enemyBackLine; public float enemyFrontZ = 14f; public float enemyBackZ = 18f; public float enemySpacing = 2f; public BattleState State { get; private set; } = BattleState.Setup; public int RemainingPopulation => maxPopulation - usedPopulation; // 已放置单位 -> 格子与数据,移除时退回人口。 private class PlacementRecord { public Vector2Int cell; public CharacterData data; } private readonly HashSet occupiedCells = new HashSet(); private readonly Dictionary placements = new Dictionary(); private readonly Dictionary cardImages = new Dictionary(); private CharacterData selectedCharacter; private int usedPopulation; private Camera mainCamera; private GameObject cellHighlight; private Material highlightMaterial; private GameObject startButton; private Text populationText; private Text resultText; private float endCheckTimer; private static readonly Color ValidCellColor = new Color(0.2f, 1f, 0.3f, 0.35f); private static readonly Color InvalidCellColor = new Color(1f, 0.25f, 0.2f, 0.35f); private static readonly Color CardNormalColor = new Color(0.15f, 0.15f, 0.2f, 0.85f); private static readonly Color CardSelectedColor = new Color(0.2f, 0.55f, 0.25f, 0.95f); private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; } private void Start() { mainCamera = Camera.main; CreateCellHighlight(); CreateCardUI(); CreateStartButton(); CreateResultText(); } private void Update() { if (State == BattleState.Setup) { HandleSetupInput(); } else if (State == BattleState.Battle) { CheckBattleEnd(); } } // 布阵阶段输入:选兵种后点格子放置,点击已放单位移除。 private void HandleSetupInput() { if (mainCamera == null) { mainCamera = Camera.main; if (mainCamera == null) return; } bool overUI = EventSystem.current != null && EventSystem.current.IsPointerOverGameObject(); bool highlightVisible = !overUI && selectedCharacter != null; if (cellHighlight != null) { cellHighlight.SetActive(highlightVisible); } if (!highlightVisible) return; if (!RaycastGround(out Vector3 groundPoint)) return; Vector2Int cell = WorldToCell(groundPoint); bool cellValid = IsCellInRange(cell) && !occupiedCells.Contains(cell); cellHighlight.transform.position = new Vector3(cell.x * cellSize, 0.02f, cell.y * cellSize); highlightMaterial.color = cellValid ? ValidCellColor : InvalidCellColor; if (Input.GetMouseButtonDown(0)) { // 优先移除点中的己方单位。 if (TryRemoveUnitAtMouse()) return; if (cellValid) { PlaceUnit(selectedCharacter, cell); } } } // 卡片点击入口:选中/再点取消。 public void SelectCharacter(CharacterData data) { if (State != BattleState.Setup || data == null) return; if (selectedCharacter == data) { selectedCharacter = null; } else if (RemainingPopulation >= data.manaCost) { selectedCharacter = data; } else { Debug.Log($"人口不足,{data.characterName}需要{data.manaCost}人口,剩余{RemainingPopulation}"); return; } RefreshCardSelection(); } // 人口是否足够,供卡片按钮判断可用性。 public bool CanAfford(CharacterData data) { return data != null && RemainingPopulation >= data.manaCost; } public void StartBattle() { if (State != BattleState.Setup) return; if (placements.Count == 0) { Debug.Log("请先放置至少一个单位再开战"); return; } selectedCharacter = null; RefreshCardSelection(); SpawnEnemyLine(enemyFrontLine, enemyFrontZ); SpawnEnemyLine(enemyBackLine, enemyBackZ); State = BattleState.Battle; if (startButton != null) startButton.SetActive(false); if (populationText != null) populationText.gameObject.SetActive(false); if (cellHighlight != null) cellHighlight.SetActive(false); Debug.Log("战斗开始!"); } // 每0.5秒统计双方存活,全灭判定胜负。 private void CheckBattleEnd() { endCheckTimer += Time.deltaTime; if (endCheckTimer < 0.5f) return; endCheckTimer = 0f; int friends = CountActiveUnits("Friend"); int enemies = CountActiveUnits("Enemy"); if (friends == 0 && enemies == 0) { EndBattle("平局!"); } else if (enemies == 0) { EndBattle("胜利!"); } else if (friends == 0) { EndBattle("失败"); } } private static int CountActiveUnits(string tag) { // 单位死亡时会被立即隐藏并销毁,只统计激活对象。 GameObject[] objects = GameObject.FindGameObjectsWithTag(tag); int count = 0; foreach (GameObject obj in objects) { if (obj.activeInHierarchy) count++; } return count; } private void EndBattle(string result) { State = BattleState.Ended; if (resultText != null) { resultText.text = result; resultText.gameObject.SetActive(true); } Debug.Log($"战斗结束:{result}"); } // ===== 放置与移除 ===== private void PlaceUnit(CharacterData data, Vector2Int cell) { if (data == null || data.unitPrefab == null) return; if (RemainingPopulation < data.manaCost) return; if (!occupiedCells.Add(cell)) return; Vector3 position = CellToWorld(cell); GameObject unit = Instantiate(data.unitPrefab, position, Quaternion.identity); unit.name = "Friend_" + data.characterName; unit.tag = "Friend"; var unitBase = unit.GetComponent(); if (unitBase != null) { unitBase.InitializeFromPlayerData( data.GetMaxHealth(1), data.GetAttackPower(1), data.GetMoveSpeed(1)); } placements[unit] = new PlacementRecord { cell = cell, data = data }; usedPopulation += data.manaCost; UpdatePopulationText(); if (RemainingPopulation <= 0) { selectedCharacter = null; RefreshCardSelection(); } } private bool TryRemoveUnitAtMouse() { Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition); RaycastHit[] hits = Physics.RaycastAll(ray, 200f); GameObject closest = null; float closestDistance = float.MaxValue; foreach (RaycastHit hit in hits) { var unitBase = hit.collider.GetComponentInParent(); if (unitBase != null && unitBase.CompareTag("Friend") && placements.ContainsKey(unitBase.gameObject)) { if (hit.distance < closestDistance) { closestDistance = hit.distance; closest = unitBase.gameObject; } } } if (closest == null) return false; if (placements.TryGetValue(closest, out PlacementRecord record)) { occupiedCells.Remove(record.cell); usedPopulation -= record.data.manaCost; placements.Remove(closest); UpdatePopulationText(); } Destroy(closest); return true; } // ===== 敌方阵型 ===== private void SpawnEnemyLine(CharacterData[] line, float z) { if (line == null) return; float startX = -(line.Length - 1) * 0.5f * enemySpacing; for (int i = 0; i < line.Length; i++) { CharacterData data = line[i]; if (data == null || data.unitPrefab == null) continue; GameObject unit = Instantiate( data.unitPrefab, new Vector3(startX + i * enemySpacing, 0f, z), Quaternion.Euler(0f, 180f, 0f)); unit.name = "Enemy_" + data.characterName; unit.tag = "Enemy"; var unitBase = unit.GetComponent(); if (unitBase != null) { unitBase.InitializeFromPlayerData( data.GetMaxHealth(1), data.GetAttackPower(1), data.GetMoveSpeed(1)); } } } // ===== 网格工具 ===== private Vector2Int WorldToCell(Vector3 world) { return new Vector2Int( Mathf.RoundToInt(world.x / cellSize), Mathf.RoundToInt(world.z / cellSize)); } private Vector3 CellToWorld(Vector2Int cell) { return new Vector3(cell.x * cellSize, 0f, cell.y * cellSize); } private bool IsCellInRange(Vector2Int cell) { float x = cell.x * cellSize; float z = cell.y * cellSize; return x >= xRange.x && x <= xRange.y && z >= zRange.x && z <= zRange.y; } private bool RaycastGround(out Vector3 point) { point = Vector3.zero; if (mainCamera == null) return false; Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out RaycastHit hit, 200f)) { point = hit.point; return true; } return false; } // ===== UI 创建(代码生成,无需场景预制) ===== private Canvas EnsureCanvas() { Canvas canvas = FindObjectOfType(); if (canvas == null) { GameObject canvasObject = new GameObject("Canvas", typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster)); canvas = canvasObject.GetComponent(); canvas.renderMode = RenderMode.ScreenSpaceOverlay; var scaler = canvasObject.GetComponent(); scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; scaler.referenceResolution = new Vector2(1080f, 1920f); scaler.matchWidthOrHeight = 0.5f; } if (FindObjectOfType() == null) { new GameObject("EventSystem", typeof(EventSystem), typeof(StandaloneInputModule)); } return canvas; } private void CreateCellHighlight() { cellHighlight = GameObject.CreatePrimitive(PrimitiveType.Quad); cellHighlight.name = "CellHighlight"; // 高亮块不能挡射线,删掉自带的碰撞体。 Destroy(cellHighlight.GetComponent()); cellHighlight.transform.rotation = Quaternion.Euler(90f, 0f, 0f); cellHighlight.transform.localScale = new Vector3(cellSize * 0.95f, cellSize * 0.95f, 1f); cellHighlight.transform.position = new Vector3(0f, 0.02f, 0f); highlightMaterial = new Material(Shader.Find("Sprites/Default")); highlightMaterial.color = ValidCellColor; cellHighlight.GetComponent().sharedMaterial = highlightMaterial; cellHighlight.SetActive(false); } private void CreateCardUI() { if (availableCharacters == null || availableCharacters.Length == 0) return; Canvas canvas = EnsureCanvas(); const float cardWidth = 108f; const float cardHeight = 150f; const float gap = 10f; int count = availableCharacters.Length; float totalWidth = count * cardWidth + (count - 1) * gap; float startX = -totalWidth * 0.5f + cardWidth * 0.5f; for (int i = 0; i < count; i++) { CharacterData data = availableCharacters[i]; if (data == null) continue; GameObject card = new GameObject("卡片_" + data.characterName, typeof(RectTransform), typeof(Image), typeof(Button)); card.transform.SetParent(canvas.transform, false); var cardRect = card.GetComponent(); cardRect.anchorMin = cardRect.anchorMax = new Vector2(0.5f, 0f); cardRect.pivot = new Vector2(0.5f, 0f); cardRect.anchoredPosition = new Vector2(startX + i * (cardWidth + gap), 24f); cardRect.sizeDelta = new Vector2(cardWidth, cardHeight); var cardImage = card.GetComponent(); cardImage.color = CardNormalColor; cardImages[data] = cardImage; // 头像占上半部。 GameObject iconObject = new GameObject("Icon", typeof(RectTransform), typeof(Image)); iconObject.transform.SetParent(card.transform, false); var iconRect = iconObject.GetComponent(); iconRect.anchorMin = new Vector2(0.08f, 0.35f); iconRect.anchorMax = new Vector2(0.92f, 0.95f); iconRect.offsetMin = Vector2.zero; iconRect.offsetMax = Vector2.zero; var iconImage = iconObject.GetComponent(); if (data.cardIcon != null) { iconImage.sprite = data.cardIcon; iconImage.preserveAspect = true; } else { iconImage.color = new Color(0.4f, 0.4f, 0.45f, 1f); } // 名字占底部。 CreateChildText(card.transform, "Name", data.characterName, 26, new Vector2(0.05f, 0.02f), new Vector2(0.95f, 0.28f)); // 人口占顶部。 CreateChildText(card.transform, "Cost", data.manaCost + "人口", 28, new Vector2(0.05f, 0.72f), new Vector2(0.95f, 0.98f), new Color(1f, 0.85f, 0.3f)); // 挂通用卡片按钮:点击进入选兵,人口不足自动置灰。 var summonButton = card.AddComponent(); summonButton.characterData = data; } } private void CreateStartButton() { Canvas canvas = EnsureCanvas(); startButton = new GameObject("开战按钮", typeof(RectTransform), typeof(Image), typeof(Button)); startButton.transform.SetParent(canvas.transform, false); var buttonRect = startButton.GetComponent(); buttonRect.anchorMin = buttonRect.anchorMax = new Vector2(0.5f, 1f); buttonRect.pivot = new Vector2(0.5f, 1f); buttonRect.anchoredPosition = new Vector2(0f, -30f); buttonRect.sizeDelta = new Vector2(220f, 90f); startButton.GetComponent().color = new Color(0.75f, 0.2f, 0.15f, 0.95f); CreateChildText(startButton.transform, "Label", "开 战", 44, Vector2.zero, Vector2.one, Color.white); startButton.GetComponent