根据提供的code differences信息,由于没有具体的代码变更内容,我将生成一个通用的commit message模板:
``` docs(readme): 更新文档说明 - 添加项目使用指南 - 完善API接口说明 - 修正错误的配置示例 ```
This commit is contained in:
1 parent
a74d1e62d7
commit
8d58c2223c
167 files changed
+1119
-15679
No files matched your search
@@ -0,0 +1,284 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 红方城堡自动出兵 AI:独立能量、三路目标评估、低费试探与高费推进。
|
||||
/// </summary>
|
||||
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<CharacterData> hand = new List<CharacterData>();
|
||||
private readonly Queue<CharacterData> drawQueue = new Queue<CharacterData>();
|
||||
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 -= 1f;
|
||||
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<CharacterData> affordable = new List<CharacterData>();
|
||||
foreach (CharacterData card in hand)
|
||||
{
|
||||
if (card != null && card.unitPrefab != null)
|
||||
affordable.Add(card);
|
||||
}
|
||||
|
||||
if (affordable.Count == 0) return false;
|
||||
|
||||
CharacterData selected = affordable[Random.Range(0, affordable.Count)];
|
||||
if (!SpawnAt(selected, spawnPosition, spawnRotation, teamId)) return false;
|
||||
|
||||
currentMana -= 1f;
|
||||
RotatePlayedCard(selected);
|
||||
return true;
|
||||
}
|
||||
|
||||
private CharacterData SelectWeightedCard(List<CharacterData> cards)
|
||||
{
|
||||
return cards == null || cards.Count == 0 ? null : cards[Random.Range(0, cards.Count)];
|
||||
}
|
||||
|
||||
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<UnitTeam>())
|
||||
{
|
||||
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<CrystalMineCapture>())
|
||||
{
|
||||
if (mine == null) continue;
|
||||
Vector3 p = mine.transform.position;
|
||||
if (Mathf.Abs(p.x - laneX) > laneHalfWidth * 1.8f) continue;
|
||||
UnitTeam team = mine.GetComponent<UnitTeam>();
|
||||
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) continue;
|
||||
float score = Random.Range(0f, 1f);
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
best = card;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
private void BuildDeck()
|
||||
{
|
||||
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 = spawnTeamId;
|
||||
unit.tag = spawnTeamId == 0 ? "Friend" : "Enemy";
|
||||
}
|
||||
|
||||
UnitBase unitBase = unit.GetComponent<UnitBase>();
|
||||
if (unitBase != null)
|
||||
{
|
||||
unitBase.InitializeFromPlayerData(data.GetMaxHealth(1), data.GetAttackPower(1), data.GetMoveSpeed(1));
|
||||
|
||||
// 如果是敌人(teamId != 0),增大寻敌距离
|
||||
if (spawnTeamId != 0)
|
||||
{
|
||||
unitBase.detectionRange = 100f; // 从默认30增加到100
|
||||
}
|
||||
}
|
||||
|
||||
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<CrystalMineCapture>())
|
||||
{
|
||||
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<CastleSpawnManager>();
|
||||
if (manager != null && manager.redSpawnPoint != null) return manager.redSpawnPoint;
|
||||
var castles = GameObject.Find("红色城堡");
|
||||
return castles != null ? castles.transform.Find("出兵点") : null;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user