角色能力,存档
This commit is contained in:
@@ -1,69 +1,193 @@
|
||||
using UnityEngine;
|
||||
using BaseGames.Combat;
|
||||
|
||||
namespace BaseGames.Player.States
|
||||
{
|
||||
/// <summary>
|
||||
/// 地面攻击状态(3 段连击)。
|
||||
/// 由 PlayerController 实例化,AttackEvent 触发切换。
|
||||
/// 通过 Animancer 帧事件驱动 HitBox 激活/关闭。
|
||||
/// 地面攻击状态(任意段连击)。
|
||||
/// 攻速倍率(Stats.AnimatorSpeedMultiplier)缩放 clip 播放速度及 recoveryTime/comboTimeout。
|
||||
/// 状态机流程:
|
||||
/// 播放动画 → [comboInputOpen] 接受输入 → [cancelWindowOpen] 允许跳跃/冲刺 →
|
||||
/// 动画结束 → 硬直(recoveryTime) → 连击等待(comboTimeout) → 无输入则回 Idle
|
||||
/// </summary>
|
||||
public class AttackState : PlayerStateBase
|
||||
{
|
||||
private int _comboIndex;
|
||||
private int _comboIndex;
|
||||
private bool _comboInputPending; // 连击窗口内已收到攻击输入
|
||||
private bool _comboWindowOpen; // 当前是否接受连击输入
|
||||
|
||||
// 动画结束后的两阶段计时
|
||||
private bool _waitingAfterAnim; // 是否在动画结束后等待阶段
|
||||
private float _recoveryEndTime; // 硬直结束时刻
|
||||
private float _comboTimeoutEnd; // 连击等待结束时刻
|
||||
|
||||
public AttackState(PlayerController owner) : base(owner) { }
|
||||
|
||||
public override void OnStateEnter()
|
||||
{
|
||||
_comboIndex = 0;
|
||||
_comboIndex = 0;
|
||||
_comboInputPending = false;
|
||||
_comboWindowOpen = false;
|
||||
_waitingAfterAnim = false;
|
||||
Input.AttackEvent += OnAttackInput;
|
||||
PlayAttackClip();
|
||||
Input.AttackEvent += OnAttackInput;
|
||||
}
|
||||
|
||||
public override void OnStateExit()
|
||||
{
|
||||
Input.AttackEvent -= OnAttackInput;
|
||||
Owner.Combat?.DisableAllWeaponHitBoxes();
|
||||
Move?.SetCancelWindowOpen(false);
|
||||
}
|
||||
|
||||
public override void OnStateUpdate()
|
||||
{
|
||||
if (!_waitingAfterAnim)
|
||||
{
|
||||
// ── 动画播放中:只处理取消窗口(跳跃/冲刺打断)──────────────
|
||||
if (!Move.CancelWindowOpen) return;
|
||||
TryConsumeCancelInput();
|
||||
return;
|
||||
}
|
||||
|
||||
// ── 动画结束后等待阶段 ─────────────────────────────────────────
|
||||
float now = Time.time;
|
||||
|
||||
// 硬直结束后开放取消窗口
|
||||
if (!Move.CancelWindowOpen && now >= _recoveryEndTime)
|
||||
Move.SetCancelWindowOpen(true);
|
||||
|
||||
// 有缓存的连击输入 → 立即推进
|
||||
if (_comboInputPending)
|
||||
{
|
||||
AdvanceCombo();
|
||||
return;
|
||||
}
|
||||
|
||||
// 连击超时 → 返回 Idle
|
||||
if (now >= _comboTimeoutEnd)
|
||||
{
|
||||
Owner.TransitionTo(Owner.GetState<IdleState>());
|
||||
return;
|
||||
}
|
||||
|
||||
// 恢复期结束后仍可跳跃/冲刺取消
|
||||
if (Move.CancelWindowOpen)
|
||||
TryConsumeCancelInput();
|
||||
}
|
||||
|
||||
public override void OnStateUpdate() { }
|
||||
public override void OnStateFixedUpdate() { }
|
||||
|
||||
// ── 内部 ──────────────────────────────────────────────────────────────
|
||||
|
||||
private void PlayAttackClip()
|
||||
{
|
||||
// ⚠️ 字段名 GroundAttacks(非 AttackChainClips)
|
||||
var clip = AnimCfg.GroundAttacks[_comboIndex];
|
||||
var state = Anim.Play(clip);
|
||||
var events = state.Events(this);
|
||||
_waitingAfterAnim = false;
|
||||
_comboWindowOpen = false;
|
||||
Move.SetCancelWindowOpen(false);
|
||||
|
||||
var weapon = Owner.Weapon?.ActiveWeapon;
|
||||
if (weapon == null)
|
||||
{
|
||||
UnityEngine.Debug.LogWarning("[AttackState] 未找到 ActiveWeapon,请检查 WeaponManager 配置。");
|
||||
Owner.TransitionTo(Owner.GetState<IdleState>());
|
||||
return;
|
||||
}
|
||||
|
||||
var step = weapon.GetGroundStep(_comboIndex);
|
||||
if (step.clip == null || step.clip.Clip == null)
|
||||
{
|
||||
UnityEngine.Debug.LogWarning($"[AttackState] 连击段 {_comboIndex} 动画未配置,请检查 {weapon.weaponId}。");
|
||||
Owner.TransitionTo(Owner.GetState<IdleState>());
|
||||
return;
|
||||
}
|
||||
|
||||
float spd = Stats?.AnimatorSpeedMultiplier ?? 1f;
|
||||
var animState = Anim.Play(step.clip);
|
||||
animState.Speed *= spd; // 在 ClipTransition 自身速度基础上叠加攻速
|
||||
|
||||
var events = animState.Events(this);
|
||||
events.OnEnd = OnClipEnd;
|
||||
|
||||
// HitBox 由 Animancer 归一化时间事件驱动(时间点配置于 PlayerAnimationConfigSO)
|
||||
var timings = AnimCfg?.GroundAttackTimings;
|
||||
float enterTime = timings != null && _comboIndex < timings.Length
|
||||
? timings[_comboIndex].HitBoxEnter : 0.3f;
|
||||
float exitTime = timings != null && _comboIndex < timings.Length
|
||||
? timings[_comboIndex].HitBoxExit : 0.6f;
|
||||
events.Add(enterTime, () => Owner.Combat?.EnableWeaponHitBox(AttackDirection.Ground));
|
||||
events.Add(exitTime, () => Owner.Combat?.DisableAllWeaponHitBoxes());
|
||||
// HitBox 时间窗口(capture step by value for closure safety)
|
||||
var capturedStep = step;
|
||||
events.Add(capturedStep.hitBoxEnter, () =>
|
||||
Owner.Combat?.EnableWeaponHitBox(AttackDirection.Ground,
|
||||
capturedStep.hitBoxId, capturedStep.damageSource));
|
||||
events.Add(capturedStep.hitBoxExit, () => Owner.Combat?.DisableAllWeaponHitBoxes());
|
||||
// 连击输入窗口
|
||||
if (capturedStep.comboInputOpen > 0f)
|
||||
events.Add(capturedStep.comboInputOpen, () => _comboWindowOpen = true);
|
||||
else
|
||||
_comboWindowOpen = true; // 0 = 立即开放
|
||||
|
||||
if (capturedStep.comboInputClose > 0f)
|
||||
events.Add(capturedStep.comboInputClose, () => _comboWindowOpen = false);
|
||||
|
||||
// 取消窗口(跳跃/冲刺)
|
||||
if (capturedStep.cancelWindowOpen > 0f)
|
||||
events.Add(capturedStep.cancelWindowOpen, () => Move.SetCancelWindowOpen(true));
|
||||
}
|
||||
|
||||
private void OnClipEnd()
|
||||
{
|
||||
Owner.Combat?.DisableAllWeaponHitBoxes();
|
||||
Owner.TransitionTo(Owner.GetState<IdleState>());
|
||||
_comboWindowOpen = false;
|
||||
Move.SetCancelWindowOpen(false);
|
||||
|
||||
// 如果已有缓存输入,直接推进(零延迟连击)
|
||||
if (_comboInputPending)
|
||||
{
|
||||
AdvanceCombo();
|
||||
return;
|
||||
}
|
||||
|
||||
// 进入动画后等待阶段
|
||||
var step = Owner.Weapon?.ActiveWeapon?.GetGroundStep(_comboIndex) ?? default;
|
||||
float spd = Stats?.AnimatorSpeedMultiplier ?? 1f;
|
||||
float now = Time.time;
|
||||
|
||||
_waitingAfterAnim = true;
|
||||
_recoveryEndTime = now + step.recoveryTime / spd;
|
||||
_comboTimeoutEnd = _recoveryEndTime + step.comboTimeout / spd;
|
||||
}
|
||||
|
||||
private void AdvanceCombo()
|
||||
{
|
||||
int maxCombo = Owner.Weapon?.ActiveWeapon?.GroundComboCount ?? 1;
|
||||
if (_comboIndex < maxCombo - 1)
|
||||
{
|
||||
_comboIndex++;
|
||||
_comboInputPending = false;
|
||||
PlayAttackClip();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 已是最后一段,忽略多余输入,等待超时
|
||||
_comboInputPending = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void TryConsumeCancelInput()
|
||||
{
|
||||
if (Buffer.ConsumeJump())
|
||||
{
|
||||
Owner.TransitionTo(Owner.GetState<JumpState>());
|
||||
return;
|
||||
}
|
||||
var ds = Owner.GetState<DashState>();
|
||||
if (ds != null && ds.CanDash
|
||||
&& Stats != null && Stats.HasAbility(AbilityType.Dash)
|
||||
&& Buffer.ConsumeDash())
|
||||
{
|
||||
Owner.TransitionTo(ds);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAttackInput()
|
||||
{
|
||||
// 连击段数从配置 SO 动态读取,无须修改代码即可支持任意段数连击
|
||||
int maxCombo = AnimCfg?.GroundAttacks?.Length ?? 1;
|
||||
if (_comboIndex < maxCombo - 1)
|
||||
{
|
||||
_comboIndex++;
|
||||
PlayAttackClip();
|
||||
}
|
||||
if (_comboWindowOpen || _waitingAfterAnim)
|
||||
_comboInputPending = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user