diff --git a/Assets/_Game/Scripts/Enemies/Abilities/EnemyAbilityBase.cs b/Assets/_Game/Scripts/Enemies/Abilities/EnemyAbilityBase.cs
index 5751efd6..98b7ec86 100644
--- a/Assets/_Game/Scripts/Enemies/Abilities/EnemyAbilityBase.cs
+++ b/Assets/_Game/Scripts/Enemies/Abilities/EnemyAbilityBase.cs
@@ -16,7 +16,7 @@ namespace BaseGames.Enemies.Abilities
/// - 受击/死亡时由 调用 。
///
[DisallowMultipleComponent]
- public abstract class EnemyAbilityBase : MonoBehaviour
+ public abstract class EnemyAbilityBase : MonoBehaviour, IAttackCandidate
{
[Header("配置 SO")]
[SerializeField] protected EnemyAbilitySO _config;
@@ -43,6 +43,24 @@ namespace BaseGames.Enemies.Abilities
/// BD 任务统一查询入口:当前是否可用(冷却完毕且未执行中)。
public virtual bool CanUse => !_isRunning && !IsOnCooldown && _enemy != null && _enemy.IsAlive;
+ // ── IAttackCandidate(供 EnemyAttackSelector 选招)──────────────────
+ public bool RequiresLineOfSight => _config != null && _config.requiresLineOfSight;
+ public bool RequiresGrounded => _config != null && _config.requiresGrounded;
+ public float Weight => _config != null ? _config.weight : 0f;
+ public int Priority => _config != null ? _config.priority : 0;
+
+ /// 本招圆形攻击范围内是否有玩家(招式自管其射程;LOS 复用敌人级 IsPlayerVisible)。
+ public virtual bool InAttackRange()
+ {
+ if (_config == null || _config.rangeRadius <= 0f
+ || _enemy == null || _enemy.PlayerTransform == null) return false;
+ float sign = _transform.localScale.x < 0f ? -1f : 1f;
+ Vector2 origin = (Vector2)_transform.position
+ + new Vector2(_config.rangeOffset.x * sign, _config.rangeOffset.y);
+ float r = _config.rangeRadius;
+ return ((Vector2)_enemy.PlayerTransform.position - origin).sqrMagnitude <= r * r;
+ }
+
protected virtual void Awake()
{
_enemy = GetComponentInParent();
diff --git a/Assets/_Game/Scripts/Enemies/Abilities/IAttackCandidate.cs b/Assets/_Game/Scripts/Enemies/Abilities/IAttackCandidate.cs
new file mode 100644
index 00000000..2cb61610
--- /dev/null
+++ b/Assets/_Game/Scripts/Enemies/Abilities/IAttackCandidate.cs
@@ -0,0 +1,17 @@
+namespace BaseGames.Enemies.Abilities
+{
+ ///
+ /// 攻击选择器的候选契约。攻击类 实现之;
+ /// 抽象出来使 可脱离 MonoBehaviour 单测。
+ ///
+ public interface IAttackCandidate
+ {
+ bool CanUse { get; } // 未冷却 + 未运行 + 存活
+ bool RequiresLineOfSight { get; } // 是否需要视线(由敌人级 LOS 提供)
+ bool RequiresGrounded { get; } // 是否需要着地
+ float Weight { get; } // WeightedRandom 权重
+ int Priority { get; } // Priority 优先级
+ bool InAttackRange(); // 招式自管圆形射程内是否有玩家
+ bool Execute(); // 触发本招
+ }
+}