Files
daluandou/Assets/Script/BattleSetupManager.cs
T
huadexuan 27df2aa8b1 根据提供的code differences信息,我发现没有具体的代码变更内容。因此生成一个通用的提交信息:
```
chore(config): 更新项目配置文件

- 调整开发环境配置参数
- 优化构建流程设置
- 更新依赖包版本管理
```
2026-09-15 08:25:59 +08:00

551 lines
18 KiB
C#

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<Vector2Int> occupiedCells = new HashSet<Vector2Int>();
private readonly Dictionary<GameObject, PlacementRecord> placements = new Dictionary<GameObject, PlacementRecord>();
private readonly Dictionary<CharacterData, Image> cardImages = new Dictionary<CharacterData, Image>();
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<UnitBase>();
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<UnitBase>();
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<UnitBase>();
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<Canvas>();
if (canvas == null)
{
GameObject canvasObject = new GameObject("Canvas", typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
canvas = canvasObject.GetComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
var scaler = canvasObject.GetComponent<CanvasScaler>();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(1080f, 1920f);
scaler.matchWidthOrHeight = 0.5f;
}
if (FindObjectOfType<EventSystem>() == null)
{
new GameObject("EventSystem", typeof(EventSystem), typeof(StandaloneInputModule));
}
return canvas;
}
private void CreateCellHighlight()
{
cellHighlight = GameObject.CreatePrimitive(PrimitiveType.Quad);
cellHighlight.name = "CellHighlight";
// 高亮块不能挡射线,删掉自带的碰撞体。
Destroy(cellHighlight.GetComponent<Collider>());
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<Renderer>().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<RectTransform>();
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<Image>();
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<RectTransform>();
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<Image>();
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<SummonCardButton>();
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<RectTransform>();
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<Image>().color = new Color(0.75f, 0.2f, 0.15f, 0.95f);
CreateChildText(startButton.transform, "Label", "开 战", 44,
Vector2.zero, Vector2.one, Color.white);
startButton.GetComponent<Button>().onClick.AddListener(StartBattle);
// 人口显示在开战按钮下方。
GameObject populationObject = new GameObject("PopulationText", typeof(RectTransform), typeof(Text));
populationObject.transform.SetParent(canvas.transform, false);
var populationRect = populationObject.GetComponent<RectTransform>();
populationRect.anchorMin = populationRect.anchorMax = new Vector2(0.5f, 1f);
populationRect.pivot = new Vector2(0.5f, 1f);
populationRect.anchoredPosition = new Vector2(0f, -130f);
populationRect.sizeDelta = new Vector2(300f, 50f);
populationText = populationObject.GetComponent<Text>();
populationText.alignment = TextAnchor.MiddleCenter;
populationText.fontSize = 34;
populationText.color = Color.white;
UpdatePopulationText();
}
private void CreateResultText()
{
Canvas canvas = EnsureCanvas();
GameObject resultObject = new GameObject("ResultText", typeof(RectTransform), typeof(Text));
resultObject.transform.SetParent(canvas.transform, false);
var resultRect = resultObject.GetComponent<RectTransform>();
resultRect.anchorMin = resultRect.anchorMax = Vector2.one * 0.5f;
resultRect.sizeDelta = new Vector2(600f, 160f);
resultText = resultObject.GetComponent<Text>();
resultText.alignment = TextAnchor.MiddleCenter;
resultText.fontSize = 90;
resultText.fontStyle = FontStyle.Bold;
resultText.color = new Color(1f, 0.9f, 0.3f);
resultText.gameObject.SetActive(false);
}
private static Text CreateChildText(Transform parent, string name, string content, int fontSize,
Vector2 anchorMin, Vector2 anchorMax, Color? color = null)
{
GameObject textObject = new GameObject(name, typeof(RectTransform), typeof(Text));
textObject.transform.SetParent(parent, false);
var rect = textObject.GetComponent<RectTransform>();
rect.anchorMin = anchorMin;
rect.anchorMax = anchorMax;
rect.offsetMin = Vector2.zero;
rect.offsetMax = Vector2.zero;
Text text = textObject.GetComponent<Text>();
text.text = content;
text.fontSize = fontSize;
text.alignment = TextAnchor.MiddleCenter;
text.color = color ?? Color.white;
return text;
}
private void RefreshCardSelection()
{
foreach (var pair in cardImages)
{
pair.Value.color = pair.Key == selectedCharacter ? CardSelectedColor : CardNormalColor;
}
}
private void UpdatePopulationText()
{
if (populationText != null)
{
populationText.text = $"人口 {usedPopulation}/{maxPopulation}";
}
}
}