fix(enemy): 应用 Task 3 代码审查意见
补上池化路径的防重复记忆重置——ResetRepeatMemory 此前文档承诺了 「对象池复用时清除」却无任何调用点,而它要取代的 BossBase.LastUsedSkillId 恰好在 OnSpawn 里有重置。放 EnemyBase.OnSpawn 以覆盖全部池化敌人。 折扣系数改为每次选招前刷新,与 attackSelectionMode 的读取时机对齐—— 此前策划在播放模式下调系数无效、调模式却立即生效,容易误判为功能没生效。 默认值收敛为 EnemyAttackSelector.DefaultAntiRepeatFactor 单一常量。 测试:IneligiblePrevious 用例改用系数 0.5——原先用 0 时,把折扣错写在 eligibility 归零之前也照样通过,是个永远不会失败的测试;统计用例边界由 7σ 收紧到 5σ 使其真正验证该系数;补 ResetRepeatMemory 的确定性用例、 系数 1.0 无惩罚、三候选三条。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -109,6 +109,9 @@ namespace BaseGames.Enemies
|
||||
var mode = _enemy.StatsSO != null
|
||||
? _enemy.StatsSO.attackSelectionMode
|
||||
: Abilities.AttackSelectionMode.WeightedRandom;
|
||||
// 折扣系数与 mode 同步每次刷新,使播放模式下调参立即生效(不必重进场景)
|
||||
if (_enemy.StatsSO != null)
|
||||
sel.AntiRepeatFactor = _enemy.StatsSO.attackAntiRepeatFactor;
|
||||
bool grounded = _enemy.Movement != null && _enemy.Movement.IsGrounded;
|
||||
var pick = sel.Select(_enemy.IsPlayerVisible(), grounded, mode);
|
||||
return pick != null && pick.Execute();
|
||||
|
||||
@@ -9,23 +9,35 @@ namespace BaseGames.Enemies.Abilities
|
||||
/// </summary>
|
||||
public sealed class EnemyAttackSelector
|
||||
{
|
||||
/// <summary>防重复折扣系数的默认值。EnemyStatsSO 字段初值与 EnemyBase 的兜底分支共用此常量。</summary>
|
||||
public const float DefaultAntiRepeatFactor = 0.3f;
|
||||
|
||||
private readonly List<IAttackCandidate> _candidates;
|
||||
// 权重缓冲区(与 _candidates 等长,每次选招复用):避免每次选招 new List 造成 GC
|
||||
private readonly List<float> _weightBuf;
|
||||
// 防重复折扣系数:0 = 有替代时绝不连续重复,1 = 不惩罚
|
||||
private readonly float _antiRepeatFactor;
|
||||
private float _antiRepeatFactor;
|
||||
|
||||
private IAttackCandidate _lastPicked;
|
||||
|
||||
public EnemyAttackSelector(IEnumerable<IAttackCandidate> candidates, float antiRepeatFactor = 0.3f)
|
||||
public EnemyAttackSelector(IEnumerable<IAttackCandidate> candidates,
|
||||
float antiRepeatFactor = DefaultAntiRepeatFactor)
|
||||
{
|
||||
_candidates = new List<IAttackCandidate>(candidates);
|
||||
_weightBuf = new List<float>(_candidates.Count);
|
||||
_antiRepeatFactor = Mathf.Clamp01(antiRepeatFactor);
|
||||
_candidates = new List<IAttackCandidate>(candidates);
|
||||
_weightBuf = new List<float>(_candidates.Count);
|
||||
AntiRepeatFactor = antiRepeatFactor; // 经属性赋值,夹紧逻辑只有一处
|
||||
}
|
||||
|
||||
public int Count => _candidates.Count;
|
||||
|
||||
/// <summary>防重复折扣系数。与 attackSelectionMode 一样每次选招前由调用方刷新,
|
||||
/// 使策划在播放模式下调参能立即生效。</summary>
|
||||
public float AntiRepeatFactor
|
||||
{
|
||||
get => _antiRepeatFactor;
|
||||
set => _antiRepeatFactor = Mathf.Clamp01(value);
|
||||
}
|
||||
|
||||
/// <summary>上一次实际选中的招(防重复折扣的作用对象;供测试与调试面板读取)。</summary>
|
||||
public IAttackCandidate LastPicked => _lastPicked;
|
||||
|
||||
|
||||
@@ -601,9 +601,11 @@ namespace BaseGames.Enemies
|
||||
candidates.Add(ab);
|
||||
}
|
||||
}
|
||||
// 折扣系数是策划配置项,未配 StatsSO 的敌人用 0.3 这个合法业务缺省(非掩盖漏配:
|
||||
// 该模式本身是可选的,没配就等于用默认折扣)。
|
||||
float antiRepeat = _statsSO != null ? _statsSO.attackAntiRepeatFactor : 0.3f;
|
||||
// 缺 StatsSO 已由本类其它处显式报错,这里的三目只是避免空引用;
|
||||
// 真正的业务缺省是 SO 字段自身的初值。
|
||||
float antiRepeat = _statsSO != null
|
||||
? _statsSO.attackAntiRepeatFactor
|
||||
: BaseGames.Enemies.Abilities.EnemyAttackSelector.DefaultAntiRepeatFactor;
|
||||
_attackSelector = new Abilities.EnemyAttackSelector(candidates, antiRepeat);
|
||||
}
|
||||
|
||||
@@ -760,6 +762,9 @@ namespace BaseGames.Enemies
|
||||
// 重置能力冷却
|
||||
_abilities.InterruptAll(InterruptReason.Dead);
|
||||
|
||||
// 对象池复用不得继承上一条命的防重复记忆(否则新生即带着上次的招被折扣)
|
||||
_attackSelector?.ResetRepeatMemory();
|
||||
|
||||
// 重置决策层(回到 Entry、清临时态)
|
||||
if (_brain != null) _brain.enabled = true; // 复用时恢复决策组件(可能曾被 QuotaManager 裁剪禁用)
|
||||
_brain?.ResetBrain();
|
||||
|
||||
@@ -46,7 +46,8 @@ namespace BaseGames.Enemies
|
||||
|
||||
[Tooltip("WeightedRandomAntiRepeat 模式下,上一次选中的招的权重折扣系数。" +
|
||||
"0 = 有替代招时绝不连续重复;1 = 不惩罚。仅该模式生效")]
|
||||
[Range(0f, 1f)] public float attackAntiRepeatFactor = 0.3f;
|
||||
[Range(0f, 1f)] public float attackAntiRepeatFactor
|
||||
= BaseGames.Enemies.Abilities.EnemyAttackSelector.DefaultAntiRepeatFactor;
|
||||
|
||||
[Header("追击 & AI 阶段")]
|
||||
[Tooltip("是否有警觉状态:勾选=未发现态进入视野先进警觉(朝向+警觉动画)再追击;不勾选=进入追逐感知直接追击")]
|
||||
|
||||
Reference in New Issue
Block a user