长按左键蓄力抬手只作用于上半身——不管是地面 WASD 跑动腿在动,还是浮空腿部做飞行动作,蓄力抬手都能很好叠加。松手接甩出→放下手。短按仍走原来的普攻连招,手感不受影响。
— 核心诉求
一、三次演进才定下最终规格
| 版本 | 用户反馈 | 修订 |
|---|---|---|
| V1 初版 | — | 长按→One,松开→Two→衔接Three;超时自动释放 |
| V2 PIE 后 | 按着没松手就自动播完了,应该一直保持蓄力 | 长按不松手→保持 One,播到 75 帧→65↔75 往复(蓄力呼吸动),累计 >2s 强制执行 |
| V3 微调 | Two 太慢 | Two 播放速度 ×1.5 → 再 ×1.5(最终 2.25),验收满意 |
二、短按 vs 长按的判定窗口
左键只这一个按键,用 0.25s 判定窗口分流:
- 按下后 0.25s 内松开 → 短按 → 走原有普攻连招(DoAttack → PlayMontage)
- 按下后超过 0.25s 才松开 → 长按 → 走蓄力链(One → Two → Three)
- 长按超过 2s 仍未松手 → 强制执行(蓄力自动释放)
// ChangliCharacter.cpp · 左键输入守卫
void AChangliCharacter::OnAttackPressedKey()
{
if (bIsCharging) return; // 蓄力中不重复触发
bWasChargingReleased = false;
GetWorldTimerManager().SetTimer(
ChargeKeyPressTimer, this,
&AChangliCharacter::OnChargeDetected, 0.25f, false); // 0.25s 后判定
}
void AChangliCharacter::OnAttackReleasedKey()
{
if (!bWasChargingReleased) {
// 0.25s 内松开 → 短按,走普攻
GetWorldTimerManager().ClearTimer(ChargeKeyPressTimer);
DoAttack(); return;
}
// 长按松开 → 触发蓄力释放
if (bIsCharging) { ReleaseCharge(); }
}
三、AnimGraph 四层结构(上半身分层关键)
原 ABP AB_ChangLi_Nyx 只有 1 个 Slot(DefaultSlot),新增分层 Slot 后变成四层:
Locomotion(StateMachine)
└─> Slot(DefaultSlot) ← 原有全身蒙太奇仍走这里,不受影响
├─> SaveCachedPose(ChargeBasePose)
│ ├─> UseCachedPose A ──> LayeredBoneBlend.BasePose
│ └─> UseCachedPose B ──> Slot(UpperBody).Source
└─> Slot(UpperBody).Pose ──> LayeredBoneBlend.BlendPoses_0
LayeredBoneBlend.Pose ──> LocalToComponentSpace ──> ModifyBone ──> TwoBoneIK×2 ──> AnimDynamics ──> KawaiiPhysics×5 ──> Root
为什么用 SaveCachedPose + UseCachedPose:
- DefaultSlot 里是全身 Locomotion(走路/跑动/浮空腿部动作),它是"底座"。
- SaveCachedPose 把底座 Pose 存一份给 LayeredBoneBlend.BasePose 用(保证下半身保持全身 Pose)。
- Slot(UpperBody) 是新的分层入口,LayeredBoneBlend 只对脊柱以上骨骼 Blend(Branch Filter 填脊柱骨骼名),这样蓄力只叠加上半身,腿部继续走路/飞行动画。
四、三段动画与时序
| 动画 | 帧数 | 时长 | 播放速度 | Loop |
|---|---|---|---|---|
| AM_CL_Attack_01_One(蓄力抬手) | 76 | 2.533s | 1.25× | 是(65↔75 往复) |
| AM_CL_Attack_01_Two(甩出) | 30 | 1.0s | 2.25× | 否 |
| AM_CL_Attack_01_Three(放下手) | 45 | 1.5s | 1.0× | 否 |
关键:没有对应蒙太奇,直接用 PlayAnimation 播动画序列。蓄力抬手 One 播到 75 帧仍未松手 → AnimBP 里 AnimBP Event BlueprintUpdateAnimation 驱动 Tick,在进度 65↔75 之间往复播放(蓄力呼吸效果)。
五、AnimBP 里的事件驱动链
// AB_ChangLi_Nyx 的 AnimGraph Event BlueprintUpdateAnimation(每帧 Tick)
// 输入:Player → IsCharging(bool)、ChargeProgress(0-1 float)
// 动作:
// if IsCharging=true → Set Slot(UpperBody) Pose = PlayAnimation(One)
// 播放到 75 帧 → 在 65↔75 之间 Reverse → Forward 往复
// if IsCharging=false 且 bJustReleased=true → Set Slot(UpperBody) Pose = PlayAnimation(Two, then Three)
// else → Slot(UpperBody) 置空 → 上半身回归全身 Locomotion
六、踩坑清单
- InputKey 孤立节点:蓝图 Attack 图里那个
K2Node_InputKey_2是**孤立节点**,实际左键输入完全由 C++ 驱动(bBindLegacyAttackKey → BindKey(LeftMouseButton, IE_Pressed))。不要去改蓝图孤立节点。 - LayeredBoneBlend 分支过滤骨骼名:Branch Filter 必须填脊柱骨骼名(
spine_01 / spine_02 / spine_03),不填 → 整个身体被蓄力动画覆盖。 - C++ 与 AnimBP 数据同步:Player 蓝图里
IsCharging/ChargeProgress两个变量同时被 C++ 写入、被 AnimBP 读取。AnimBP Event BlueprintUpdateAnimation 每帧读取,不要只在 Event Blueprint Initialize 读一次。 - 蓄力计时独立于蒙太奇系统:因为蓄力动画不是蒙太奇(没有 Slot Montage),所以计时逻辑在 C++ 侧 TimeManager 单独管理,AnimBP 只负责根据 C++ 状态驱动 LayeredBoneBlend。
- 蓄力中按闪现需要先打断:闪现守卫
if (bIsCharging) { StopCharge(); },否则两个 AnimGraph 源同时驱动同一个 Slot 会出现动画撕裂。