feat(enemy): 新增 StallDetector 推进停滞检测(+8 单测),为收敛三处重复实现做准备
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
using NUnit.Framework;
|
||||
using BaseGames.Enemies;
|
||||
|
||||
namespace BaseGames.Tests.EditMode.Enemies
|
||||
{
|
||||
/// <summary>
|
||||
/// StallDetector:窗口内净位移不足即判停滞。
|
||||
/// 该逻辑原本在 3 处各写一遍(巡逻 Pace / 导航层 / 冲锋),此处集中钉住行为。
|
||||
/// </summary>
|
||||
public class StallDetectorTests
|
||||
{
|
||||
const float Window = 0.5f, MinProgress = 0.05f;
|
||||
|
||||
[Test]
|
||||
public void BeforeWindowElapses_NeverStalled()
|
||||
{
|
||||
var d = new StallDetector(Window, MinProgress);
|
||||
d.Reset(0f);
|
||||
Assert.IsFalse(d.Tick(0f, 0.1f)); // 完全没动,但窗口未到
|
||||
Assert.IsFalse(d.Tick(0f, 0.1f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NoProgressOverWindow_Stalled()
|
||||
{
|
||||
var d = new StallDetector(Window, MinProgress);
|
||||
d.Reset(0f);
|
||||
Assert.IsFalse(d.Tick(0f, 0.4f));
|
||||
Assert.IsTrue (d.Tick(0f, 0.2f)); // 累计 0.6s ≥ 窗口,净位移 0
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EnoughProgressOverWindow_NotStalled()
|
||||
{
|
||||
var d = new StallDetector(Window, MinProgress);
|
||||
d.Reset(0f);
|
||||
Assert.IsFalse(d.Tick(1f, 0.6f)); // 位移 1m 远超阈值
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ProgressJustBelowThreshold_Stalled()
|
||||
{
|
||||
var d = new StallDetector(Window, MinProgress);
|
||||
d.Reset(0f);
|
||||
Assert.IsTrue(d.Tick(0.04f, 0.6f)); // 0.04 < 0.05
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ProgressCountsBothDirections()
|
||||
{
|
||||
var d = new StallDetector(Window, MinProgress);
|
||||
d.Reset(0f);
|
||||
Assert.IsFalse(d.Tick(-1f, 0.6f)); // 向左移动同样算推进
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AfterStalled_WindowRestartsFromCurrentPosition()
|
||||
{
|
||||
var d = new StallDetector(Window, MinProgress);
|
||||
d.Reset(0f);
|
||||
Assert.IsTrue (d.Tick(0f, 0.6f)); // 第一次判停滞
|
||||
Assert.IsFalse(d.Tick(0f, 0.1f)); // 窗口已重开,未到期
|
||||
Assert.IsTrue (d.Tick(0f, 0.6f)); // 再次到期且仍无推进
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Reset_ClearsAccumulatedTime()
|
||||
{
|
||||
var d = new StallDetector(Window, MinProgress);
|
||||
d.Reset(0f);
|
||||
d.Tick(0f, 0.4f);
|
||||
d.Reset(0f); // 重开窗口
|
||||
Assert.IsFalse(d.Tick(0f, 0.2f)); // 只累计了 0.2s
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithoutReset_FirstTickInitializes()
|
||||
{
|
||||
var d = new StallDetector(Window, MinProgress);
|
||||
Assert.IsFalse(d.Tick(5f, 1f)); // 未 Reset 时首帧只做初始化,不误判
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Enemies
|
||||
{
|
||||
/// <summary>
|
||||
/// 推进停滞检测:滑动窗口内的**净水平位移**不足阈值即判为停滞。
|
||||
/// 用于"命令了移动却推不动"的兜底(撞上未纳入检测层的碰撞体、贴墙微抖等)。
|
||||
///
|
||||
/// <para>用净位移而非逐帧差值,避免"贴墙推进-被弹回"的微抖每帧重置计数。</para>
|
||||
/// <para><b>调用方须以字段持有本结构</b>(struct 的方法会就地修改字段);
|
||||
/// 开始移动 / 更换目标后调用 <see cref="Reset"/> 重开窗口。</para>
|
||||
/// </summary>
|
||||
public struct StallDetector
|
||||
{
|
||||
private readonly float _windowSeconds;
|
||||
private readonly float _minProgress;
|
||||
private float _timer;
|
||||
private float _startX;
|
||||
private bool _initialized;
|
||||
|
||||
/// <param name="windowSeconds">判定窗口时长(秒)。</param>
|
||||
/// <param name="minProgress">窗口内所需的最小净位移(米)。</param>
|
||||
public StallDetector(float windowSeconds, float minProgress)
|
||||
{
|
||||
_windowSeconds = windowSeconds;
|
||||
_minProgress = minProgress;
|
||||
_timer = 0f;
|
||||
_startX = 0f;
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
/// <summary>以当前位置重开窗口。</summary>
|
||||
public void Reset(float currentX)
|
||||
{
|
||||
_startX = currentX;
|
||||
_timer = 0f;
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 推进一帧。返回 true 表示本窗口内推进不足(停滞);返回 true 后窗口自动以当前位置重开。
|
||||
/// </summary>
|
||||
public bool Tick(float currentX, float deltaTime)
|
||||
{
|
||||
if (!_initialized) { Reset(currentX); return false; }
|
||||
|
||||
_timer += deltaTime;
|
||||
if (_timer < _windowSeconds) return false;
|
||||
|
||||
bool stalled = Mathf.Abs(currentX - _startX) < _minProgress;
|
||||
_startX = currentX;
|
||||
_timer = 0f;
|
||||
return stalled;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user