using System.Collections.Generic; using UnityEngine; namespace BaseGames.Enemies.Perception { /// /// 场景级 Sight 槽位批量调度器(Phase 3 性能优化)。 /// /// 设计意图: /// Sight 槽位内置多点 LOS 射线检测,当场景中存在大量敌人时(10+ 个), /// 每帧同步刷新所有 Sight 槽位会产生明显的 CPU 峰值。 /// 本组件将全局 Sight 更新量限制在 个/帧, /// 通过轮询方式公平分配每帧的 Sight 预算,把瞬时峰值摊平到多帧。 /// /// 使用方式: /// 1. 在场景中添加一个空 GameObject,挂载本组件(建议放在 Managers 节点下)。 /// 2. 本组件自动在 DefaultExecutionOrder(-50) 执行,比默认 PhysicsPerceptionSystem 早。 /// 3. 当本组件存在时,PhysicsPerceptionSystem.FixedUpdate() 会跳过所有 Sight 槽位的更新, /// 由本组件统一调度(优雅降级:本组件不存在时 Sight 每帧正常更新)。 /// /// 性能参考(测试用): /// • maxSystemsPerFrame = 4,60fps,20 个敌人 → 平均每帧 4 × losRayCount 次射线, /// 每个敌人 Sight 刷新周期约 5 帧(~83ms),适合慢速视线感应。 /// • 增大此值可减少延迟,减小此值可进一步降低每帧开销。 /// [AddComponentMenu("BaseGames/Enemies/Sight Batch System")] [DefaultExecutionOrder(-50)] public sealed class SightBatchSystem : MonoBehaviour { // ── 单例 ────────────────────────────────────────────────────────────── public static SightBatchSystem Instance { get; private set; } // ── 配置 ────────────────────────────────────────────────────────────── [Min(1)] [Tooltip("每帧最多更新多少个 PhysicsPerceptionSystem 的 Sight 槽位。\n" + "建议值:场景敌人数 / 5(使平均刷新延迟 ≈ 5 帧)。\n" + "值越大 = 延迟越低但 CPU 峰值越高;值越小反之。")] [SerializeField] private int maxSystemsPerFrame = 4; // ── 内部状态 ────────────────────────────────────────────────────────── private readonly List _registrants = new List(32); private int _offset; // ── 单例生命周期 ────────────────────────────────────────────────────── private void Awake() { if (Instance != null && Instance != this) { Debug.LogWarning("[SightBatchSystem] 场景中存在多个 SightBatchSystem,将销毁多余实例。", this); Destroy(gameObject); return; } Instance = this; } private void OnDestroy() { if (Instance == this) Instance = null; } // ── 注册 / 注销 ─────────────────────────────────────────────────────── /// PhysicsPerceptionSystem.Awake() 自动调用。 public void Register(PhysicsPerceptionSystem system) { if (system == null || _registrants.Contains(system)) return; _registrants.Add(system); } /// PhysicsPerceptionSystem.OnDestroy() 自动调用。O(1) 交换删除。 public void Unregister(PhysicsPerceptionSystem system) { int idx = _registrants.IndexOf(system); if (idx < 0) return; int last = _registrants.Count - 1; _registrants[idx] = _registrants[last]; _registrants.RemoveAt(last); // 防止 _offset 越界 if (_offset >= _registrants.Count) _offset = 0; } // ── 调度 ────────────────────────────────────────────────────────────── private void FixedUpdate() { int total = _registrants.Count; if (total == 0) return; int budget = Mathf.Min(maxSystemsPerFrame, total); for (int i = 0; i < budget; i++) { int idx = (_offset + i) % total; _registrants[idx].ExecuteSightSlots(); } _offset = (_offset + budget) % total; } } }