EnemyHurtState 与 EnemyStaggerState 的 Enter 在对应 Clip 为空时守卫式早退, **同时丢掉了恢复安排**:_currentState 永远停在该态,IsControllable 恒假, AiRuntime 的让位门永久挡住条件边求值——敌人被打一次即永久失能。 美术未接入时这影响的是大部分敌人。 TakeDamage 是分发点:霸体被破进 Stagger、否则进 Hurt,两条都可达, 所以这是两个活 bug 而非一个。EnemyKnockUpState 本就有 else 分支走 ScheduleStateRecovery,是正确范式;本次把该分支补给另外两个,并加一条 KnockUp 回归测试守住它。 时长权威没有新造: - Hurt 接上 DamageSourceSO.HitStunDuration——该字段一路铺到 DamageInfo 却零消费者,本就是「这一击让对方僵多久」的既有权威;经 PendingHitStun 传给状态,形状与既有的 PendingLaunchDir 一致。 - Stagger 用 HitTierConfig.staggerDuration——该结构 tooltip 本就写着 「Stagger / KnockUp 参数」却只有 KnockUp 的字段,属补齐而非发明。 顺带修 ScheduleStateRecovery 不取消前一次安排的问题:两个重叠安排里较短的 会先把状态改回 Controlled,静默缩短调用方想要的时长(ReceiveParry 接线后 就会撞上)。并在 OnSpawn 清掉跨池化周期残留的协程句柄。 注:Knockback 目前零调用者,其中的 PendingHitStun 赋值今天是死代码, 但它是 public virtual 且会进 Hurt 态,不设则将来接线时会用到陈旧时长。 验证:编译 0 错;EditMode 263/263(260 + 新增 3)。 变异验证——移除两个 else 分支后恰好 Hurt/Stagger 两条测试变红、 KnockUp 回归测试保持绿,恢复后复验 263/263。
70 lines
3.0 KiB
C#
70 lines
3.0 KiB
C#
using System;
|
||
using UnityEngine;
|
||
|
||
namespace BaseGames.Enemies
|
||
{
|
||
/// <summary>
|
||
/// 受击分级配置(KnockUp 击飞门槛 & 飞行参数)。
|
||
/// 挂载在 EnemyStatsSO.HitTiers 中。
|
||
/// </summary>
|
||
[Serializable]
|
||
public struct HitTierConfig
|
||
{
|
||
[Tooltip("击飞(KnockUp)触发伤害阈值;需同时设置 DamageFlags.Launch;0 = 仅依赖 Launch 标志")]
|
||
[Min(0)] public int launchThreshold;
|
||
[Tooltip("击飞纵向冲量")]
|
||
[Min(0f)] public float launchUpForce;
|
||
[Tooltip("击飞横向冲量(沿受击反方向)")]
|
||
[Min(0f)] public float launchHorzForce;
|
||
[Tooltip("无 KnockUp 动画时的状态持续时长(s)")]
|
||
[Min(0f)] public float knockUpDuration;
|
||
[Tooltip("无 Stagger 动画时的僵直持续时长(s);0 = 用默认 0.5s。" +
|
||
"受击(Hurt)的时长不在这里——它取攻击方 DamageSourceSO 的 HitStunDuration")]
|
||
[Min(0f)] public float staggerDuration;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 敌人属性配置 SO(架构 07_EnemyModule §2)。
|
||
/// </summary>
|
||
[CreateAssetMenu(menuName = "BaseGames/Enemies/EnemyStats")]
|
||
public class EnemyStatsSO : ScriptableObject
|
||
{
|
||
[Header("生命")]
|
||
[Min(1)] public int MaxHP = 50;
|
||
|
||
[Header("防御")]
|
||
[Min(0)] public int Defense = 0;
|
||
|
||
[Header("移动")]
|
||
[Min(0f)] public float WalkSpeed = 2f;
|
||
[Min(0f)] public float RunSpeed = 4f;
|
||
|
||
[Header("战斗")]
|
||
[Min(0f)] public float AttackCooldown = 1f;
|
||
|
||
[Header("攻击选招策略")]
|
||
[Tooltip("到攻击范围内如何从多个候选招里选一个")]
|
||
public BaseGames.Enemies.Abilities.AttackSelectionMode attackSelectionMode
|
||
= BaseGames.Enemies.Abilities.AttackSelectionMode.WeightedRandom;
|
||
|
||
[Tooltip("WeightedRandomAntiRepeat 模式下,上一次选中的招的权重折扣系数。" +
|
||
"0 = 有替代招时绝不连续重复;1 = 不惩罚。仅该模式生效")]
|
||
[Range(0f, 1f)] public float attackAntiRepeatFactor
|
||
= BaseGames.Enemies.Abilities.EnemyAttackSelector.DefaultAntiRepeatFactor;
|
||
|
||
[Header("追击 & AI 阶段")]
|
||
[Tooltip("是否有警觉状态:勾选=未发现态进入视野先进警觉(朝向+警觉动画)再追击;不勾选=进入追逐感知直接追击")]
|
||
public bool HasAlertState = true;
|
||
[Tooltip("最大追击距离(m);超出后放弃追击切换搜查阶段")]
|
||
[Min(0f)] public float MaxChaseDistance = 15f;
|
||
[Tooltip("视线丢失判定超时(s);超过此时长无视线则切换搜查")]
|
||
[Min(0f)] public float LoseLinkTimeout = 2f;
|
||
[Tooltip("归位到达判定半径(m)")]
|
||
[Min(0.01f)] public float HomeRadius = 0.5f;
|
||
|
||
[Header("受击分级")]
|
||
[Tooltip("Stagger / KnockUp 伤害阈值及击飞参数")]
|
||
public HitTierConfig HitTiers;
|
||
}
|
||
}
|