Files
zeling_v2/Assets/_Game/Scripts/Enemies/EnemyQuotaManager.cs
Joywayer 06048c966a feat: Add HurtBoxOwnerGuard to prevent multiple damage registrations from the same HitBox activation
- Implemented HurtBoxOwnerGuard to ensure that multiple HurtBoxes on the same character do not register damage multiple times during a single HitBox activation.
- Added custom editor for HitBox to facilitate the creation of shape colliders with HitBoxColliderProxy.
- Introduced PhysicsPerceptionSystem for enemy perception, supporting multiple detection modes including RangeCircle, BatchLOS, FanCast, and BoxCast.
- Created EnemyPatrolZone to define patrol and chase areas for enemies, allowing for shared zones among multiple enemies.
- Added BD_IsOutsideZone conditional task for Behavior Designer to check if an enemy or player is outside a defined patrol zone.
2026-06-02 16:10:44 +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;
// 同步暂停/恢复感知系统,避免远处敌人无效 tick
enemy.SensorHub?.SetSuspended(!active);
}
}
#endif
}
}
}