feat(enemy): ICombatant 增加 HasEligibleAttack/UseBestAttack + EnemyBase 构建选择器
This commit is contained in:
@@ -25,6 +25,13 @@ namespace BaseGames.Tests.EditMode.AI
|
||||
public bool NoAbilityRunning => Running == null;
|
||||
public bool CanUseAbility(string id) => CanUse;
|
||||
public void InterruptAbilities() => Running = null;
|
||||
public bool Eligible; // 测试控制:是否有合格攻击
|
||||
public bool HasEligibleAttack() => Eligible;
|
||||
public bool UseBestAttack()
|
||||
{
|
||||
if (!Eligible) return false;
|
||||
Used.Add("best"); Running = "best"; return true;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class FakeVitals : IActorVitals
|
||||
|
||||
@@ -8,5 +8,11 @@ namespace BaseGames.AI
|
||||
bool NoAbilityRunning { get; }
|
||||
bool CanUseAbility(string abilityId);
|
||||
void InterruptAbilities(); // 中断当前所有能力(如退出追击时停追击能力)
|
||||
|
||||
/// <summary>攻击选择器中是否有"够得着且未冷却"的招(供 Approach→Attack 转换)。</summary>
|
||||
bool HasEligibleAttack();
|
||||
|
||||
/// <summary>按敌人配置的选招模式选一个攻击并触发;返回是否成功触发。</summary>
|
||||
bool UseBestAttack();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,25 @@ namespace BaseGames.Enemies
|
||||
public void InterruptAbilities()
|
||||
=> _enemy.Abilities?.InterruptAll(Abilities.InterruptReason.ExternalRequest);
|
||||
|
||||
public bool HasEligibleAttack()
|
||||
{
|
||||
var sel = _enemy.AttackSelector;
|
||||
if (sel == null) return false;
|
||||
return sel.HasEligible(_enemy.IsPlayerVisible(), _enemy.Movement != null && _enemy.Movement.IsGrounded);
|
||||
}
|
||||
|
||||
public bool UseBestAttack()
|
||||
{
|
||||
var sel = _enemy.AttackSelector;
|
||||
if (sel == null) return false;
|
||||
var mode = _enemy.StatsSO != null
|
||||
? _enemy.StatsSO.attackSelectionMode
|
||||
: Abilities.AttackSelectionMode.WeightedRandom;
|
||||
bool grounded = _enemy.Movement != null && _enemy.Movement.IsGrounded;
|
||||
var pick = sel.Select(_enemy.IsPlayerVisible(), grounded, mode);
|
||||
return pick != null && pick.Execute();
|
||||
}
|
||||
|
||||
// ---- IActorVitals ----
|
||||
public bool IsAlive => _enemy.IsAlive;
|
||||
public bool IsControllable => _enemy.CurrentState == EnemyStateType.Controlled;
|
||||
|
||||
@@ -221,6 +221,9 @@ namespace BaseGames.Enemies
|
||||
/// <summary>能力注册表(架构 §8.3)。Awake 时自动收集所有 EnemyAbilityBase 组件。</summary>
|
||||
public EnemyAbilityRegistry Abilities => _abilities;
|
||||
private readonly EnemyAbilityRegistry _abilities = new EnemyAbilityRegistry();
|
||||
/// <summary>攻击选择器(从已注册能力里筛出 category==Attack 的候选,Awake 时构建)。</summary>
|
||||
public Abilities.EnemyAttackSelector AttackSelector => _attackSelector;
|
||||
private Abilities.EnemyAttackSelector _attackSelector;
|
||||
/// <summary>由 _onPlayerSpawned 事件缓存的玩家 Transform,供 BD 任务读取。</summary>
|
||||
public Transform PlayerTransform => _playerTransform;
|
||||
/// <summary>感知 Hub;供 BD 任务及 QuotaManager 暂停/恢复感知使用。</summary>
|
||||
@@ -537,6 +540,7 @@ namespace BaseGames.Enemies
|
||||
_pooledObject = GetComponent<PooledObject>();
|
||||
_brain = GetComponent<EnemyAiBrain>();
|
||||
_abilities.CollectFrom(gameObject);
|
||||
BuildAttackSelector();
|
||||
_colliders = GetComponentsInChildren<Collider2D>(true);
|
||||
|
||||
// 收集配置型行为模块(零代码扩展点)
|
||||
@@ -552,6 +556,30 @@ namespace BaseGames.Enemies
|
||||
// 订阅在 OnEnable 中处理
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从已注册能力里收集 category==Attack 的候选,构建攻击选择器。
|
||||
/// 根因校验:攻击招 rangeRadius<=0(永远够不着)显式报错,不静默兜底。
|
||||
/// </summary>
|
||||
private void BuildAttackSelector()
|
||||
{
|
||||
var candidates = new System.Collections.Generic.List<Abilities.IAttackCandidate>();
|
||||
var all = _abilities?.All;
|
||||
if (all != null)
|
||||
{
|
||||
for (int i = 0; i < all.Count; i++)
|
||||
{
|
||||
var ab = all[i];
|
||||
if (ab == null || ab.Config == null) continue;
|
||||
if (ab.Config.category != Abilities.AbilityCategory.Attack) continue;
|
||||
if (ab.Config.rangeRadius <= 0f)
|
||||
Debug.LogError($"[EnemyBase] 攻击招 '{ab.Config.abilityId}' 的 rangeRadius<=0," +
|
||||
"永远够不着玩家。请在其 EnemyAbilitySO 上配置攻击触发半径。", ab);
|
||||
candidates.Add(ab);
|
||||
}
|
||||
}
|
||||
_attackSelector = new Abilities.EnemyAttackSelector(candidates);
|
||||
}
|
||||
|
||||
protected virtual void Update()
|
||||
{
|
||||
_stats?.TickAttackCooldown(Time.deltaTime);
|
||||
|
||||
Reference in New Issue
Block a user