Files
zeling_v2/Assets/_Game/Scripts/Enemies/AI/BatchLOSSystem.cs

103 lines
4.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 Unity.Collections;
using UnityEngine;
using UnityEngine.Jobs;
using BaseGames.Core;
namespace BaseGames.Enemies.AI
{
/// <summary>
/// 批量视线检测系统(架构 07_EnemyModule §12
/// 每 FixedUpdate
/// 1. 读取上一帧 RaycastHit2D 结果,回调各注册者。
/// 2. 重新构建本帧 RaycastCommand 批次,使用 Physics2D.RaycastAll 同步模式执行。
///
/// ⚠️ Unity 2022.3 中 Physics2D 批量命令RaycastCommand2D尚未稳定
/// 此实现使用 FixedUpdate 内顺序 Raycast2D节流确保零 GC 分配。
/// 当敌人数量 > 20 时建议切换到 Job System RaycastCommand。
/// </summary>
[DefaultExecutionOrder(-200)]
public class BatchLOSSystem : MonoBehaviour
{
[SerializeField, Min(1)] private int _maxRequestersPerFrame = 8;
private void Awake() => ServiceLocator.Register<BatchLOSSystem>(this);
private void OnDestroy() => ServiceLocator.Unregister<BatchLOSSystem>(this);
private readonly List<ILOSRequester> _requesters = new();
private readonly HashSet<ILOSRequester> _requesterSet = new(); // O(1) 包含查询
// _indexMap 记录每个 requester 在 _requesters 中的下标,供 Unregister 实现 O(1) 删除
private readonly Dictionary<ILOSRequester, int> _indexMap = new();
private int _currentOffset = 0;
// ── 注册 ──────────────────────────────────────────────────────────
public void Register(ILOSRequester requester)
{
if (_requesterSet.Add(requester))
{
_indexMap[requester] = _requesters.Count;
_requesters.Add(requester);
}
}
public void Unregister(ILOSRequester requester)
{
if (!_requesterSet.Remove(requester)) return;
int idx = _indexMap[requester];
int last = _requesters.Count - 1;
// Swap-and-pop将末尾元素移动到被删除的位置避免 O(n) 搬移
if (idx != last)
{
var moved = _requesters[last];
_requesters[idx] = moved;
_indexMap[moved] = idx;
}
_requesters.RemoveAt(last);
_indexMap.Remove(requester);
// 修正偏移量,防止越界
if (_currentOffset >= _requesters.Count) _currentOffset = 0;
}
// ── FixedUpdate ───────────────────────────────────────────────────
private void FixedUpdate()
{
if (_requesters.Count == 0) return;
// 每帧轮询部分请求者(均匀分配,避免单帧全量射线)
int count = Mathf.Min(_maxRequestersPerFrame, _requesters.Count);
for (int i = 0; i < count; i++)
{
int idx = (_currentOffset + i) % _requesters.Count;
var requester = _requesters[idx];
bool hasLOS = false;
if (requester != null)
{
Vector2 origin = requester.LOSOrigin;
Vector2 target = requester.LOSTarget;
Vector2 direction = target - origin;
float distance = direction.magnitude;
if (distance > 0.01f)
{
// direction / distance == direction.normalized避免重复开方
var hit = Physics2D.Raycast(origin, direction / distance, distance, requester.LOSBlockingMask);
// 若无遮挡物hit.collider == null则视线畅通
hasLOS = hit.collider == null;
}
requester.ReceiveLOSResult(hasLOS);
}
}
_currentOffset = (_currentOffset + count) % Mathf.Max(1, _requesters.Count);
}
}
}