using System.Collections.Generic; using UnityEngine; /// /// 红方城堡自动出兵 AI:独立能量、三路目标评估、低费试探与高费推进。 /// public class EnemyCastleAI : MonoBehaviour { [Header("AI 阵营")] [SerializeField] private int teamId = 1; [SerializeField] private int maxMana = 10; [SerializeField] private float manaRegenInterval = 3f; [SerializeField] private float decisionInterval = 1.2f; [SerializeField] private float minimumSpawnInterval = 1.4f; [Header("三路目标")] [SerializeField] private float leftLaneX = -10f; [SerializeField] private float centerLaneX = 0f; [SerializeField] private float rightLaneX = 10f; [SerializeField] private float laneHalfWidth = 4f; [SerializeField] private float resourceThreatWeight = 2.5f; [SerializeField] private float enemyUnitWeight = 1.5f; [SerializeField] private float laneRotationWeight = 0.7f; [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 hand = new List(); private readonly Queue drawQueue = new Queue(); private Transform redSpawnPoint; private AttackTargetController targetController; private float currentMana; private float manaTimer; private float decisionTimer; private float spawnTimer; private int lastLane = 1; private int consecutiveLaneSpawns; private bool initialized; private void Start() { redSpawnPoint = FindRedSpawnPoint(); targetController = AttackTargetController.Instance; currentMana = maxMana; BuildDeck(); BuildHand(); initialized = redSpawnPoint != null && hand.Count > 0; if (!initialized) Debug.LogWarning("EnemyCastleAI 初始化失败:缺少红方出兵点或角色卡组。"); } private void Update() { if (!initialized) return; manaTimer += Time.deltaTime; while (manaTimer >= manaRegenInterval) { manaTimer -= manaRegenInterval; currentMana = Mathf.Min(maxMana, currentMana + 1f); } decisionTimer -= Time.deltaTime; spawnTimer -= Time.deltaTime; if (decisionTimer > 0f || spawnTimer > 0f) return; decisionTimer = decisionInterval; int lane = ChooseLane(); CharacterData card = ChooseCard(lane); if (card == null) return; if (Spawn(card, lane)) { currentMana -= card.manaCost; RotatePlayedCard(card); spawnTimer = minimumSpawnInterval; if (lane == lastLane) consecutiveLaneSpawns++; else consecutiveLaneSpawns = 1; lastLane = lane; } } 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 affordable = new List(); 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 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); float center = ScoreLane(centerLaneX, 1); float right = ScoreLane(rightLaneX, 2); if (consecutiveLaneSpawns >= 2) { if (lastLane == 0) left -= 3f; if (lastLane == 1) center -= 3f; if (lastLane == 2) right -= 3f; } float best = Mathf.Max(left, Mathf.Max(center, right)); if (Mathf.Approximately(best, left)) return 0; if (Mathf.Approximately(best, right)) return 2; return 1; } private float ScoreLane(float laneX, int lane) { float score = 1f; score += lane == 1 ? 0.8f : 0f; score += lane == lastLane ? laneRotationWeight : 0f; foreach (var unit in FindObjectsOfType()) { if (unit == null || unit.teamId != 0) continue; Vector3 p = unit.transform.position; if (Mathf.Abs(p.x - laneX) <= laneHalfWidth) score += enemyUnitWeight; } foreach (var mine in FindObjectsOfType()) { if (mine == null) continue; Vector3 p = mine.transform.position; if (Mathf.Abs(p.x - laneX) > laneHalfWidth * 1.8f) continue; UnitTeam team = mine.GetComponent(); if (team == null || team.teamId != teamId) score += resourceThreatWeight; } return score; } private CharacterData ChooseCard(int lane) { CharacterData best = null; float bestScore = float.NegativeInfinity; 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; } } return best; } private void BuildDeck() { if (deck != null && deck.Length > 0) return; var loadedDeck = new List(); foreach (string dataName in deckDataNames) { CharacterData data = Resources.Load("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(); if (team != null) { team.teamId = spawnTeamId; unit.tag = spawnTeamId == 0 ? "Friend" : "Enemy"; } UnitBase unitBase = unit.GetComponent(); if (unitBase != null) { 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); } return true; } private GameObject FindLaneTarget(int lane) { float x = lane == 0 ? leftLaneX : lane == 2 ? rightLaneX : centerLaneX; GameObject best = null; float bestDistance = float.PositiveInfinity; foreach (var mine in FindObjectsOfType()) { if (Mathf.Abs(mine.transform.position.x - x) < laneHalfWidth * 2f) { float d = Mathf.Abs(mine.transform.position.z - redSpawnPoint.position.z); if (d < bestDistance) { bestDistance = d; best = mine.gameObject; } } } return best != null ? best : targetController?.GetEnemyCastleOfTeam(teamId); } private Transform FindRedSpawnPoint() { var manager = GetComponent(); if (manager != null && manager.redSpawnPoint != null) return manager.redSpawnPoint; var castles = GameObject.Find("红色城堡"); return castles != null ? castles.transform.Find("出兵点") : null; } }