Task 21 Step 2 全库扫描后的收尾: 注释与 Tooltip 里的旧决策层措辞逐处改为只描述功能—— EnemyControlledState / RangedEnemy / FlyingEnemy / SensorSlotNames / EnemyPoiseComponent / EnemyAnimationConfigSO / EnemyAiBrain。 EnemyQuotaManager 的序列化字段 _maxActiveBehaviorTrees 改名 _maxActiveBrains (它裁剪的实际是 EnemyAiBrain),带 FormerlySerializedAs。复核确认目前 无任何场景 / 预制体挂载该组件,故无值可丢;属未雨绸缪。 BossSkillEventChannelSO + BossEvents.BossSkillEvent + EVT_BossSkill.asset 删除: 唯一发送方 BossSkillExecutor 已随旧轨删除,复核确认全项目零发送方、零订阅方, 资产 guid 在场景 / 预制体 / Addressables 分组中均无引用。 自检:编译 0 error;EditMode 260/260 通过; SO 校验 0 error 0 warning;层矩阵 25/25 正确。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
125 lines
5.2 KiB
C#
125 lines
5.2 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using BaseGames.Core.Events;
|
||
|
||
namespace BaseGames.Enemies
|
||
{
|
||
/// <summary>
|
||
/// 敌人决策层配额管理器(架构 07_EnemyModule §13)。
|
||
/// 每 REBALANCE_INTERVAL 帧,按到玩家距离排序已注册敌人,
|
||
/// 仅对最近的 _maxActiveBrains 个敌人启用 <see cref="EnemyAiBrain"/>,其余暂停决策与感知。
|
||
/// 挂载在场景管理器 GameObject 上(每场景一个实例)。
|
||
/// </summary>
|
||
public class EnemyQuotaManager : MonoBehaviour
|
||
{
|
||
[Tooltip("同时保持决策活跃的敌人上限;超出的按距离由远及近暂停")]
|
||
[UnityEngine.Serialization.FormerlySerializedAs("_maxActiveBehaviorTrees")]
|
||
[SerializeField, Min(1)] private int _maxActiveBrains = 12;
|
||
[Tooltip("PlayerController.Start() 广播此频道,替代 FindWithTag")]
|
||
[SerializeField] private TransformEventChannelSO _onPlayerSpawned;
|
||
|
||
private const int REBALANCE_INTERVAL = 10;
|
||
|
||
private int _frameCount;
|
||
// _registeredSet 用于 O(1) 重复检测,_registered List 用于 Sort
|
||
private readonly HashSet<EnemyBase> _registeredSet = new();
|
||
private readonly List<EnemyBase> _registered = new();
|
||
private readonly Dictionary<EnemyBase, int> _indexMap = new();
|
||
// 排序临时缓冲区:预计算每个敌人到玩家的距离,避免 Sort 比较器内重复 Vector3 运算(O(n logn) → O(n))
|
||
private readonly List<(EnemyBase enemy, float sqDist)> _sortTemp = new();
|
||
// 缓存玩家 Transform
|
||
private Transform _playerTransform;
|
||
private readonly CompositeDisposable _subs = new();
|
||
|
||
private void OnEnable()
|
||
{
|
||
_onPlayerSpawned?.Subscribe(OnPlayerSpawned).AddTo(_subs);
|
||
}
|
||
|
||
private void OnDisable() => _subs.Clear();
|
||
|
||
private void OnPlayerSpawned(Transform playerTransform)
|
||
=> _playerTransform = playerTransform;
|
||
|
||
// ── 注册 / 注销 ───────────────────────────────────────────────────
|
||
public void Register(EnemyBase enemy)
|
||
{
|
||
if (enemy != null && _registeredSet.Add(enemy))
|
||
{
|
||
_indexMap[enemy] = _registered.Count;
|
||
_registered.Add(enemy);
|
||
}
|
||
}
|
||
|
||
public void Unregister(EnemyBase enemy)
|
||
{
|
||
if (enemy == null || !_registeredSet.Remove(enemy)) return;
|
||
int idx = _indexMap[enemy];
|
||
int last = _registered.Count - 1;
|
||
if (idx != last)
|
||
{
|
||
var moved = _registered[last];
|
||
_registered[idx] = moved;
|
||
_indexMap[moved] = idx;
|
||
}
|
||
_registered.RemoveAt(last);
|
||
_indexMap.Remove(enemy);
|
||
}
|
||
|
||
// ── Unity 生命周期 ────────────────────────────────────────────────
|
||
private void Update()
|
||
{
|
||
if (++_frameCount % REBALANCE_INTERVAL == 0)
|
||
Rebalance();
|
||
}
|
||
|
||
// ── 内部 ──────────────────────────────────────────────────────────
|
||
private void Rebalance()
|
||
{
|
||
int n = _registered.Count;
|
||
if (n == 0) return;
|
||
|
||
var playerPos = _playerTransform != null ? _playerTransform.position : Vector3.zero;
|
||
|
||
// ① 预计算距离(O(n) Vector3 运算,而非在比较器内重复执行 O(n logn) 次)
|
||
_sortTemp.Clear();
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
var e = _registered[i];
|
||
float sqd = e != null
|
||
? (e.transform.position - playerPos).sqrMagnitude
|
||
: float.MaxValue;
|
||
_sortTemp.Add((e, sqd));
|
||
}
|
||
|
||
// ② 对临时列表排序(比较器只做 float 比较,无额外 Vector3 开销)
|
||
_sortTemp.Sort(static (a, b) => a.sqDist.CompareTo(b.sqDist));
|
||
|
||
// ③ 将排序结果写回 _registered,同步重建 _indexMap(修复排序后索引过期的 bug)
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
var e = _sortTemp[i].enemy;
|
||
_registered[i] = e;
|
||
if (e != null) _indexMap[e] = i;
|
||
}
|
||
|
||
for (int i = n - 1; i >= 0; i--)
|
||
{
|
||
var enemy = _registered[i];
|
||
if (enemy == null) { _registered.RemoveAt(i); continue; }
|
||
|
||
var brain = enemy.Brain;
|
||
bool active = i < _maxActiveBrains;
|
||
|
||
if (brain != null && brain.enabled != active)
|
||
{
|
||
// 禁用 brain 即停其 Update/Tick,敌人保持静止但仍存在于场景中。
|
||
brain.enabled = active;
|
||
// 同步暂停/恢复感知系统,避免远处敌人无效 tick
|
||
enemy.SensorHub?.SetSuspended(!active);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|