103 lines
4.2 KiB
C#
103 lines
4.2 KiB
C#
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);
|
||
}
|
||
}
|
||
}
|