根据提供的code differences信息,我发现没有具体的代码变更内容。因此生成一个通用的提交信息:
``` chore(config): 更新项目配置文件 - 调整开发环境配置参数 - 优化构建流程设置 - 更新依赖包版本管理 ```
This commit is contained in:
1 parent
4e892d74c4
commit
27df2aa8b1
5 files changed
+652
-1308
No files matched your search
+69
-1305
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,550 @@
|
||||
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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: CigetXv+VSpp9gA0m88lS7VLDwIk2XT3cg+F1ub+vqbAzjQne5UPqKQ=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -72,7 +72,14 @@ public class SummonCardButton : MonoBehaviour
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查建筑费用是否足够
|
||||
// 决战模式:有布阵管理器时走选兵逻辑,不消耗金币。
|
||||
if (BattleSetupManager.Instance != null)
|
||||
{
|
||||
BattleSetupManager.Instance.SelectCharacter(characterData);
|
||||
return;
|
||||
}
|
||||
|
||||
// 建筑模式:检查建筑费用是否足够
|
||||
if (GoldSystem.Instance == null || GoldSystem.Instance.CurrentGold < characterData.buildingCost)
|
||||
{
|
||||
Debug.Log($"金币不足,需要{characterData.buildingCost}");
|
||||
@@ -145,7 +152,19 @@ public class SummonCardButton : MonoBehaviour
|
||||
|
||||
private bool CanAfford()
|
||||
{
|
||||
if (GoldSystem.Instance == null || characterData == null)
|
||||
if (characterData == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 决战模式按人口判断。
|
||||
if (BattleSetupManager.Instance != null)
|
||||
{
|
||||
return BattleSetupManager.Instance.CanAfford(characterData);
|
||||
}
|
||||
|
||||
// 建筑模式按金币判断。
|
||||
if (GoldSystem.Instance == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
- [2026-09-14 21:19:53] 2026-09-13 新增建筑系统核心脚本:BuildingHealth(建筑血量+生产进度接口)、GoldSystem/GoldMine(金币经济与产出)、Barracks(兵营产兵,读取 CharacterData.productionInterval)。WorldHealthBar 增加 SetEnergy() 通用下方条接口,建筑复用该条显示生产进度,单位仍用于技能能量。BuildingPlacementManager 支持虚拟网格吸附、占位检测、金币扣除,新放置建筑自动挂 BuildingHealth。UnitBase/Projectile 的 ApplyDirectDamage/ApplySkillHit/ApplyAreaSkillDamage/HitTarget 均已接入 BuildingHealth,建筑可被近战/技能/范围/飞行物伤害。兵营 Prefab(Assets/Prefabs/建筑/兵营.prefab)已写入 Barracks+WorldHealthBar+BuildingHealth,默认阵营 Friend,场景实例保留 Enemy 覆盖。**Why:** 建筑作为可攻击目标是新对攻玩法的核心,生产进度条复用血条下方条避免额外 UI。**How to apply:** 后续新增建筑类型(防御塔/资源建筑)参照 BuildingHealth 接口;修改血条下方条逻辑时注意区分单位和建筑语义。
|
||||
- [2026-09-14 21:34:17] 2026-09-14 发现:团结引擎 1.10.2 (macOS) 原生 NavMesh 烘焙失效——空场景放一个静态 Cube,用 NavMeshBuilder.BuildNavMeshData / UpdateNavMeshDataAsync / 经典全局 BuildNavMesh 烘焙全部产出 0 三角形,无报错;收集源(CollectSources)、Job 系统、AI 模块包均正常;换构建目标/域重载/重启编辑器均无效。**Why:** 原生导航烘焙在此安装上深层失效,继续用 NavMesh 会浪费时间。**How to apply:** 本项目寻路不要用 NavMesh/NavMeshAgent/NavMeshSurface,改用纯 C# 网格 A*(见 GridPathfinder.cs);若未来升级团结版本可先重跑最小 Cube 烘焙测试再决定。
|
||||
- [2026-09-14 22:55:12] 2026-09-14 伤害数字系统(WorldHealthBar.SpawnDamageNumber + DamageNumber 组件)已上线:白字黑边样式(1 白主字 + 4 向偏移黑副本,TextMesh 无原生描边所以用副本组合),入口在 TakeDamage/Heal,全场并发上限 60。两个关键坑:1) TextMesh 可读面朝本地-Z,billboard 必须用 LookRotation(selfPos - cameraPos),方向写反会左右镜像;2) 同材质同 sortingOrder 的多个 TextMesh 渲染顺序不确定,后创建的黑色副本会盖住白字——必须显式分层:黑边 sortingOrder=60、白主字=61,白字必须最后创建或层级更高。**Why:** 用户要求白色黑边,先后修复了镜像和纯黑覆盖问题。**How to apply:** 后续做其他 3D 漂浮文字(暴击、金币)沿用 DamageNumber 模式、朝向写法和 sortingOrder 分层。
|
||||
|
||||
- [2026-09-15 08:09:51] 2026-09-15 决战玩法(BattleTest.scene)布阵系统已实现:BattleSetupManager 单例管理 Setup/Battle/Ended 三状态。布阵阶段:己方半场 2x2 米虚拟网格(X:-8~8, Z:-20~-8),底部 8 张卡片(代码生成 UI),点卡片选兵种→点格子放置(绿/红高亮预览),manaCost 作为人口消耗(上限 30),点已放单位可移除退人口。开战按钮生成敌方预设阵型(前排近战 Z=14 / 后排远程 Z=18,共 28 人口),全灭判定胜负(0.5 秒轮询)。SummonCardButton 已支持双模式:有 BattleSetupManager 走人口判断选兵,否则走金币建筑放置——两个场景共存互不干扰。**Why:** 用户确定决战玩法方向,网格布阵解决开局扎堆且让排兵布阵成为策略。**How to apply:** 后续决战玩法迭代(AI 阵型多样化、主角辅助技能、重开按钮)都基于 BattleSetupManager 扩展;不要在 CoreBattleTest 场景放 BattleSetupManager。
|
||||
|
||||
### Reference
|
||||
- [2026-09-09 12:23:17] 飞行物脚本 Projectile.cs 负责锁敌追踪、碰撞检测和伤害结算。箭矢预制体 ArrowProjectile_White.prefab 已配置 Projectile 组件和 CapsuleCollider(Is Trigger)。远程单位需在攻击动画松弦帧添加 OnAttackEffect Event,并在 UnitBase 中设置 attackType=RangedProjectile、projectilePrefab、projectileSpawnPoint。
|
||||
Reference in new issue
Block a user