feat(gameplay): 添加水晶矿资源产出系统和改进AI卡组管理

- 实现水晶矿占领后的能量资源产出功能,包括manaInterval和manaAmount配置
- 添加敌方AI的能量管理系统,支持通过AddMana方法增加能量
- 实现AI卡组轮转机制,包含手牌管理和抽卡队列功能
- 优化单位AI的目标选择和战斗逻辑,修复目标切换问题

- 水晶矿现在可以按设定间隔为所属阵营提供能量资源
-
This commit is contained in:
huadexuan committed 2026-09-21 18:17:05 +08:00
1 parent b185e5768b
commit 06940a849c
21 files changed
+555 -520

No files matched your search

+118 -11
View File
@@ -22,9 +22,17 @@ public class EnemyCastleAI : MonoBehaviour
[SerializeField] private float enemyUnitWeight = 1.5f;
[SerializeField] private float laneRotationWeight = 0.7f;
[Header("卡组")]
[Header("卡组轮转")]
[SerializeField] private int visibleSlotCount = 4;
[SerializeField] private string[] deckDataNames =
{
"人类士兵Data", "弓箭手Data", "盾卫Data", "精锐士兵Data",
"法师Data", "狂暴狼人Data", "石巨人Data", "弓箭猎人Data"
};
[SerializeField] private CharacterData[] deck;
private readonly List<CharacterData> hand = new List<CharacterData>();
private readonly Queue<CharacterData> drawQueue = new Queue<CharacterData>();
private Transform redSpawnPoint;
private AttackTargetController targetController;
private float currentMana;
@@ -40,9 +48,9 @@ public class EnemyCastleAI : MonoBehaviour
redSpawnPoint = FindRedSpawnPoint();
targetController = AttackTargetController.Instance;
currentMana = maxMana;
if (deck == null || deck.Length == 0)
deck = Resources.LoadAll<CharacterData>("Data");
initialized = redSpawnPoint != null && deck != null && deck.Length > 0;
BuildDeck();
BuildHand();
initialized = redSpawnPoint != null && hand.Count > 0;
if (!initialized)
Debug.LogWarning("EnemyCastleAI 初始化失败:缺少红方出兵点或角色卡组。");
}
@@ -65,10 +73,11 @@ public class EnemyCastleAI : MonoBehaviour
int lane = ChooseLane();
CharacterData card = ChooseCard(lane);
if (card == null || card.manaCost > currentMana) return;
if (card == null) return;
if (Spawn(card, lane))
{
currentMana -= card.manaCost;
RotatePlayedCard(card);
spawnTimer = minimumSpawnInterval;
if (lane == lastLane) consecutiveLaneSpawns++;
else consecutiveLaneSpawns = 1;
@@ -76,6 +85,50 @@ public class EnemyCastleAI : MonoBehaviour
}
}
public void AddMana(int amount)
{
if (amount <= 0) return;
currentMana = Mathf.Min(maxMana, currentMana + amount);
}
public int CurrentMana => Mathf.FloorToInt(currentMana);
public bool TrySpawnFromPortal(Vector3 spawnPosition, Quaternion spawnRotation)
{
if (!initialized) return false;
List<CharacterData> affordable = new List<CharacterData>();
foreach (CharacterData card in hand)
{
if (card != null && card.unitPrefab != null && card.manaCost <= currentMana)
affordable.Add(card);
}
if (affordable.Count == 0) return false;
CharacterData selected = SelectWeightedCard(affordable);
if (!SpawnAt(selected, spawnPosition, spawnRotation, teamId)) return false;
currentMana -= selected.manaCost;
RotatePlayedCard(selected);
return true;
}
private CharacterData SelectWeightedCard(List<CharacterData> cards)
{
float totalWeight = 0f;
foreach (CharacterData card in cards)
totalWeight += 1f / Mathf.Max(1, card.manaCost);
float roll = Random.value * totalWeight;
foreach (CharacterData card in cards)
{
roll -= 1f / Mathf.Max(1, card.manaCost);
if (roll <= 0f) return card;
}
return cards[cards.Count - 1];
}
private int ChooseLane()
{
float left = ScoreLane(leftLaneX, 0);
@@ -126,24 +179,71 @@ public class EnemyCastleAI : MonoBehaviour
{
CharacterData best = null;
float bestScore = float.NegativeInfinity;
foreach (var card in deck)
foreach (var card in hand)
{
if (card == null || card.unitPrefab == null || card.manaCost > currentMana) continue;
float score = Random.Range(0f, 1f);
if (card.manaCost <= 2) score += 2f;
if (currentMana >= 7 && card.manaCost >= 5) score += 2f;
if (lane != 1 && card.manaCost <= 4) score += 0.5f;
if (score > bestScore) { bestScore = score; best = card; }
if (score > bestScore)
{
bestScore = score;
best = card;
}
}
return best;
}
private bool Spawn(CharacterData data, int lane)
private void BuildDeck()
{
GameObject unit = Instantiate(data.unitPrefab, redSpawnPoint.position, redSpawnPoint.rotation);
if (deck != null && deck.Length > 0) return;
var loadedDeck = new List<CharacterData>();
foreach (string dataName in deckDataNames)
{
CharacterData data = Resources.Load<CharacterData>("Data/" + dataName);
if (data == null)
{
Debug.LogWarning($"敌方卡组缺少角色数据: Resources/Data/{dataName}.asset");
continue;
}
loadedDeck.Add(data);
}
deck = loadedDeck.ToArray();
}
private void BuildHand()
{
hand.Clear();
drawQueue.Clear();
if (deck == null) return;
int handCount = Mathf.Min(visibleSlotCount, deck.Length);
for (int i = 0; i < handCount; i++)
hand.Add(deck[i]);
for (int i = handCount; i < deck.Length; i++)
drawQueue.Enqueue(deck[i]);
}
private void RotatePlayedCard(CharacterData playedCard)
{
int handIndex = hand.IndexOf(playedCard);
if (handIndex < 0 || drawQueue.Count == 0) return;
hand[handIndex] = drawQueue.Dequeue();
drawQueue.Enqueue(playedCard);
}
private bool SpawnAt(CharacterData data, Vector3 position, Quaternion rotation, int spawnTeamId)
{
GameObject unit = Instantiate(data.unitPrefab, position, rotation);
UnitTeam team = unit.GetComponent<UnitTeam>();
if (team != null) team.teamId = teamId;
unit.tag = "Enemy";
if (team != null)
{
team.teamId = spawnTeamId;
unit.tag = spawnTeamId == 0 ? "Friend" : "Enemy";
}
UnitBase unitBase = unit.GetComponent<UnitBase>();
if (unitBase != null)
@@ -151,6 +251,13 @@ public class EnemyCastleAI : MonoBehaviour
unitBase.InitializeFromPlayerData(data.GetMaxHealth(1), data.GetAttackPower(1), data.GetMoveSpeed(1));
}
return true;
}
private bool Spawn(CharacterData data, int lane)
{
if (!SpawnAt(data, redSpawnPoint.position, redSpawnPoint.rotation, teamId)) return false;
if (targetController != null)
{
targetController.redTarget = FindLaneTarget(lane);