Files
zeling_v2/Assets/_Game/Scripts/Enemies/EnemyQuotaManager.cs
Joywayer a1b4e629aa feat: Implement Room Streaming System
- Add RoomStreamingManager to manage room loading and unloading based on player proximity.
- Create StreamingBudgetConfigSO for memory and performance budgeting of the streaming system.
- Introduce TransitionDirector to handle seamless and atmospheric fade transitions between rooms.
- Develop WorldGraph to represent room connectivity and facilitate neighbor queries and distance calculations.
- Implement RoomNode and RoomEdge classes to structure room data and connections.
2026-05-23 19:10:29 +08:00

124 lines
4.9 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>
/// 敌人 BehaviorTree 配额管理器(架构 07_EnemyModule §13
/// 每 REBALANCE_INTERVAL 帧,按到玩家距离排序已注册敌人,
/// 仅对最近的 _maxActiveBehaviorTrees 个敌人启用 BT。
/// 挂载在场景管理器 GameObject 上(每场景一个实例)。
/// </summary>
public class EnemyQuotaManager : MonoBehaviour
{
[SerializeField, Min(1)] private int _maxActiveBehaviorTrees = 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;
}
#if GRAPH_DESIGNER
for (int i = n - 1; i >= 0; i--)
{
var enemy = _registered[i];
if (enemy == null) { _registered.RemoveAt(i); continue; }
var bt = enemy.BehaviorTree;
bool active = i < _maxActiveBehaviorTrees;
if (bt != null && bt.enabled != active)
{
bt.enabled = active;
// 同步暂停/恢复 SensorToolkit Sensor避免远处敌人无效 tick
enemy.SensorHub?.SetSuspended(!active);
}
}
#endif
}
}
}