feat(enemy): EnemyAttackSelector 选招器(射程+冷却+LOS+着地过滤,两模式选招)+单测

This commit is contained in:
2026-07-24 12:15:41 +08:00
parent 2cf0d6b1a9
commit 04a267b69f
2 changed files with 176 additions and 0 deletions
@@ -0,0 +1,68 @@
using System.Collections.Generic;
using UnityEngine;
namespace BaseGames.Enemies.Abilities
{
/// <summary>
/// 多攻击选招器:从候选(category==Attack)里按"射程 + 冷却 + LOS + 着地"过滤,
/// 再按 <see cref="AttackSelectionMode"/> 选一个。纯逻辑、无 MonoBehaviour 依赖,可单测。
/// </summary>
public sealed class EnemyAttackSelector
{
private readonly List<IAttackCandidate> _candidates;
public EnemyAttackSelector(IEnumerable<IAttackCandidate> candidates)
=> _candidates = new List<IAttackCandidate>(candidates);
public int Count => _candidates.Count;
private static bool Eligible(IAttackCandidate c, bool hasLOS, bool grounded)
=> c != null && c.CanUse && c.InAttackRange()
&& (!c.RequiresLineOfSight || hasLOS)
&& (!c.RequiresGrounded || grounded);
public bool HasEligible(bool hasLOS, bool grounded)
{
for (int i = 0; i < _candidates.Count; i++)
if (Eligible(_candidates[i], hasLOS, grounded)) return true;
return false;
}
public IAttackCandidate Select(bool hasLOS, bool grounded, AttackSelectionMode mode)
=> mode == AttackSelectionMode.Priority
? SelectByPriority(hasLOS, grounded)
: SelectByWeight(hasLOS, grounded);
private IAttackCandidate SelectByPriority(bool hasLOS, bool grounded)
{
IAttackCandidate best = null;
for (int i = 0; i < _candidates.Count; i++)
{
var c = _candidates[i];
if (!Eligible(c, hasLOS, grounded)) continue;
if (best == null || c.Priority > best.Priority) best = c; // 并列取首个
}
return best;
}
private IAttackCandidate SelectByWeight(bool hasLOS, bool grounded)
{
float total = 0f;
for (int i = 0; i < _candidates.Count; i++)
{
var c = _candidates[i];
if (Eligible(c, hasLOS, grounded)) total += Mathf.Max(0f, c.Weight);
}
if (total <= 0f) return SelectByPriority(hasLOS, grounded); // 权重全 0 → 退化为确定性取首个合格
float roll = Random.value * total;
for (int i = 0; i < _candidates.Count; i++)
{
var c = _candidates[i];
if (!Eligible(c, hasLOS, grounded)) continue;
roll -= Mathf.Max(0f, c.Weight);
if (roll <= 0f) return c;
}
return null; // 理论不达(浮点边界兜底)
}
}
}