Files
zeling_v2/Assets/_Game/Scripts/Enemies/EnemyQuotaManager.cs
T
joywayerandClaude Opus 5 cf0ac60fad refactor(enemy): 清全库行为树残留,删无人使用的 Boss 技能事件频道
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>
2026-07-30 17:14:09 +08:00

125 lines
5.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}
}
}
}