← Back to project technical overview

The long-press left click charge wind-up only affects the upper body — whether ground WASD running (lower-body locomotion in progress) or airborne legs executing a flight animation, the charge wind-up overlays well. After release, the release (fling) connects into the follow-through. Short taps retain the original basic attack combo, with no effect on feel.

— Core requirement

1. Final spec settled after three iterations

VersionUser feedbackRevisions
V1 first version—Long press → One, release → Two → connects to Three; auto-release on timeout
V2 after PIEHeld down without releasing, so it auto-completed. It should keep charging the whole timeLong press without release → stays in One, plays to frame 75 → 65↔75 oscillation (charge breathing motion); force-release if cumulative exceeds 2 s
V3 tuningTwo is too slowTwo playback speed ×1.5 → ×1.5 again (final 2.25); acceptance satisfied

2. Short tap vs long press detection window

Left click is a single key, routed with a 0.25 s detection window:

// CharacterBase.cpp · Left click input guard
void ACharacterBase::OnAttackPressedKey()
{
    if (bIsCharging) return;                    // Do not re-trigger during charge
    bWasChargingReleased = false;
    GetWorldTimerManager().SetTimer(
        ChargeKeyPressTimer, this,
        &ACharacterBase::OnChargeDetected, 0.25f, false);  // Detect after 0.25s
}

void ACharacterBase::OnAttackReleasedKey()
{
    if (!bWasChargingReleased) {
        // Released within 0.25s → short tap, follow basic attack
        GetWorldTimerManager().ClearTimer(ChargeKeyPressTimer);
        DoAttack(); return;
    }
    // Long press release → trigger charge release
    if (bIsCharging) { ReleaseCharge(); }
}

3. AnimGraph four-layer structure (the upper-body layering key)

The original ABP AB_角色蓝图 only contained 1 Slot(DefaultSlot); after adding layered Slots it was extended to four layers:

Locomotion(StateMachine)
  └─> Slot(DefaultSlot)                          ← Original full-body montage still goes here, unaffected
        ├─> 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

Why use SaveCachedPose + UseCachedPose:

4. Three-segment animation and timing

AnimationFrame countDurationPlayback speedLoop
AM_CL_Attack_01_One (charge wind-up)762.533 s1.25×Yes (65↔75 oscillation)
AM_CL_Attack_01_Two (fling)301.0 s2.25×No
AM_CL_Attack_01_Three (follow-through)451.5 s1.0×No

Key point: this animation sequence has no corresponding montage; it is played by calling PlayAnimation directly. When the charge wind-up One plays to frame 75 and the key is still held → the AnimBP Event BlueprintUpdateAnimation in the AnimBP drives a per-frame Tick, playing back and forth between progress 65↔75 (charge breathing effect).

5. Event-driven chain in the AnimBP

// AB_角色蓝图's AnimGraph Event BlueprintUpdateAnimation (per-frame Tick)
// Inputs: Player → IsCharging(bool), ChargeProgress(0-1 float)
// Actions:
//   if IsCharging=true → Set Slot(UpperBody) Pose = PlayAnimation(One)
//     play to frame 75 → Reverse → Forward oscillation between 65↔75
//   if IsCharging=false and bJustReleased=true → Set Slot(UpperBody) Pose = PlayAnimation(Two, then Three)
//   else → Slot(UpperBody) cleared → upper body returns to full-body Locomotion

6. Issues and fix list