feat(gameplay): 更新单位属性和材质配置

- 调整多个单位的基础生命值和攻击力数值平衡
- 修改CoreBattleGround材质的纹理缩放比例
- 更新地砖材质颜色为白色
- 优化纹理资源设置,调整最大纹理尺寸并添加PlayableAds平台支持
- 修复弓箭手技能目标查找逻辑,改用统一的敌人检测方法
- 为水晶矿添加中立阵营设置和光球视觉效果同步
- 调整预制体中的全局缩放参数
- 为狼人和石巨人添加NavMeshAgent组件支持
- 优化水晶矿血条显示位置计算方式
```
This commit is contained in:
huadexuan committed 2026-09-20 21:52:31 +08:00
1 parent 0e7de5f885
commit 53d95802e5
448 files changed
+133333 -472

No files matched your search

+184
View File
@@ -0,0 +1,184 @@
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 CharacterData[] deck;
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;
if (deck == null || deck.Length == 0)
deck = Resources.LoadAll<CharacterData>("Data");
initialized = redSpawnPoint != null && deck != null && deck.Length > 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 || card.manaCost > currentMana) return;
if (Spawn(card, lane))
{
currentMana -= card.manaCost;
spawnTimer = minimumSpawnInterval;
if (lane == lastLane) consecutiveLaneSpawns++;
else consecutiveLaneSpawns = 1;
lastLane = lane;
}
}
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 deck)
{
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 bool Spawn(CharacterData data, int lane)
{
GameObject unit = Instantiate(data.unitPrefab, redSpawnPoint.position, redSpawnPoint.rotation);
UnitTeam team = unit.GetComponent<UnitTeam>();
if (team != null) team.teamId = teamId;
unit.tag = "Enemy";
UnitBase unitBase = unit.GetComponent<UnitBase>();
if (unitBase != null)
{
unitBase.InitializeFromPlayerData(data.GetMaxHealth(1), data.GetAttackPower(1), data.GetMoveSpeed(1));
}
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;
}
}