Download

Play a montage when a previous montage finishes, in C++

I’m doing this in code: use Montage_Play to play montage A. Detect every frame, if Montage_IsPlaying returns false, use Montage_Play to play another montage B.

But I find that if A blend out too soon before B could hardly blend in, it will show T-pose.

I’ve reported the problem here: https://answers.unrealengine.com/que…-if-blend.html.

But at the meantime, I’d like to ask if anyone is doing the same thing in code, and how the problem is solved. I think this kind of logic in code should be very common.

Thanks.

I don’t know if you are trying to avoid the Engine’s built in animation state machine, but if not I suggest you to take a look at the Animation Blueprint and Animations KeyFrame Events instead of trying to synch game loop with animation state manually.

You can define the animation transition condition inside the animation blueprint and get previous animation’s remaining time and compare to use as a transition. Is way more reliable than depending on actor’s tick

Edit: Now I saw in the end of your thread why you are not using animbp. I guess you can leave a KeyFrame Event as a time window in the end of the animation to connect another move and it would work fine instead of relying on checking every tick

Thanks for answering. Yes, I’m not using animbp because I’m porting an old project to UE4 which write animation logic purely in C++ using “play A, if A finishes, play B” pattern.

I’m afraid event method may not a good choice for me. Firstly, I play every animation in this pattern. Adding an event to end of every animation doesn’t look a smart way to manage things. Moreover, there is already a “blend out” setting in montage, which is exactly meant to do what I want to do – it’s time to blend out old animation and blend in a new one. Second, adding events doesn’t solve my problem. It just shifts the problem from “blend out” setting to event. If the event triggers shorter before the animation ends than the second animation’s blend in duration, I still get the same problem.

Thanks.

Can’t you use a timer for this? Get the montage length for the timers length, you could even reduce it a little to allow for blending time and that should fire straight after the first montage finishes.

A timer is equal to what I’m doing right now. The “blend out” of the first montage serves as a timer. For example, the first montage is 10 seconds long. I set the blend out to 20%. It means when the montage is played to 8 second, it starts to blend out, and I detect as Montage_IsPlaying returns false now. Then I play a second montage, unfortunately this second montage is either longer than the first, or it has very long blend in duration. As a result, when the first has totally faded out. the second hasn’t fully blend in to 100%. Hence the problem.

You see, adding a timer and adjust the time is equal to just changing the blend out of the first montage. It doesn’t improve the problem at all.

Thanks.

There’s a subtle difference in what I’m suggesting that’s different to what you’re doing, although I can’t confirm because you haven’t posted any code but it sounds like you are using the point when the first montage blends out to fire the second montage using tick to track the change of Montage_IsPlaying to do this, this is no doubt were the problem is, because the blending out of the first montage has already begun before you start the blend in of the second which results in the T-Pose.

Typically you would just need to call the second montage to start playing and it will handle the blending transition. which is why I suggested a timer because you can get (montage length - blend in time of second montage) and then the timer will start the playing of the second montage and it will handle the transition itself as long as blend out/in are closely matched.

This is how I normally handle playing two montages one after another and I have never had it T-Pose before, well not due to code anyway :wink:

  1. I use no anim montages in my RTS project, only single node animations. To switch anim I would use an anim notify or checking anim percentage from Tick by this:

[FONT=courier new]float URTSSkeletalMeshComponent::GetAnimationPercentage()
{
float CurrentAnimPos = 0.f;
UAnimSingleNodeInstance* SingleNodeInstance = GetSingleNodeInstance();
if (SingleNodeInstance)
{
CurrentAnimPos = SingleNodeInstance->GetCurrentTime();
}
return CurrentAnimPos;
}


  1. in my other project I have just started to use anim montages, and as a good source I checked Tom Looman’s Survival Game, where you can find a solution using a timer, here are the related functions (btw anim montages are blended into an Anim BP but might be okay for you):


void ASWeapon::OnEquip(bool bPlayAnimation)
{
    bPendingEquip = true;
    DetermineWeaponState();

    if (bPlayAnimation)
    {
        float Duration = PlayWeaponAnimation(EquipAnim);
        if (Duration <= 0.0f)
        {
            // Failsafe in case animation is missing
            Duration = NoEquipAnimDuration;
        }
        EquipStartedTime = GetWorld()->TimeSeconds;
        EquipDuration = Duration;

        GetWorldTimerManager().SetTimer(EquipFinishedTimerHandle, this, &ASWeapon::OnEquipFinished, Duration, false);
    }
    else
    {
        /* Immediately finish equipping */
        OnEquipFinished();
    }

    if (MyPawn && MyPawn->IsLocallyControlled())
    {
        PlayWeaponSound(EquipSound);
    }
}




float ASWeapon::PlayWeaponAnimation(UAnimMontage* Animation, float InPlayRate, FName StartSectionName)
{
    float Duration = 0.0f;
    if (MyPawn)
    {
        if (Animation)
        {
            Duration = MyPawn->PlayAnimMontage(Animation, InPlayRate, StartSectionName);
        }
    }

    return Duration;
}




void ASWeapon::OnEquipFinished()
{
    AttachMeshToPawn();

    bIsEquipped = true;
    bPendingEquip = false;

    DetermineWeaponState();

    if (MyPawn)
    {
        // Try to reload empty clip
        if (MyPawn->IsLocallyControlled() &&
            CurrentAmmoInClip <= 0 &&
            CanReload())
        {
            StartReload();
        }
    }
}





void ASWeapon::StartReload(bool bFromReplication)
{
    /* Push the request to server */
    if (!bFromReplication && Role < ROLE_Authority)
    {
        ServerStartReload();
    }

    /* If local execute requested or we are running on the server */
    if (bFromReplication || CanReload())
    {
        bPendingReload = true;
        DetermineWeaponState();

        float AnimDuration = PlayWeaponAnimation(ReloadAnim);
        if (AnimDuration <= 0.0f)
        {
            AnimDuration = NoAnimReloadDuration;
        }

        GetWorldTimerManager().SetTimer(TimerHandle_StopReload, this, &ASWeapon::StopSimulateReload, AnimDuration, false);
        if (Role == ROLE_Authority)
        {
            GetWorldTimerManager().SetTimer(TimerHandle_ReloadWeapon, this, &ASWeapon::ReloadWeapon, FMath::Max(0.1f, AnimDuration - 0.1f), false);
        }

        if (MyPawn && MyPawn->IsLocallyControlled())
        {
            PlayWeaponSound(ReloadSound);
        }
    }
}


Yes, I agree that either using an event or an timer to trigger the second montage earlier could eliminate the t-pose. But it should be the engine’s job to ensure smooth transition. I used to use UE2 this way for a couple of projects. It didn’t have this problem. Unity doesn’t have this problem either. The 1 frame late problem you suspect is not true, because when Montage_IsPlaying returns false, the blend out is not started, it’s just about to start.

Using timer and event also have problems. If the blend-in of the next animation happens to be super long and longer than the time my timer or event leave it, I still get t-pose. There’re many reasons for a long blend in, either because the animation is long and my animator leave the default blend in time (25%), or my animator thinks the pose has too much difference and wants it long intentionally. We have some commercial AAA projects that have hundreds of animations, to ensure the match of blend in and blend out for any combinations of the animations is a mission impossible.

You said “as long as blend out/in are closely matched” too. The problem is this is hard to achieve in big project.

Thanks for the answer and posting the code. But as I replied to another replies. Carefully using a timer may eliminate the t-pose. But it not a solution if I have too many animations that have various blend time settings. It also defeats data-driven methodology.

Hi, I accidentally found this node, which is latent, and found another method inside the UAbilitySystemComponent class.


float UAbilitySystemComponent::PlayMontage(UGameplayAbility* InAnimatingAbility, FGameplayAbilityActivationInfo ActivationInfo, UAnimMontage* NewAnimMontage, float InPlayRate, FName StartSectionName)

I won’t dive into the AbilitySystemComponent right now and I’m not sure if this could do the trick for you, but maybe it could be of your interest!

Thanks for the information. I looked into those code. They don’t do any extra thing than I have. I think they have the same problem as I have. It’s just my animations are more demanding (very short animations).

Using a timer seems like a hacky solution to me - e.g., if you have a time-slowing mechanic in your game, the timer would need to be kept in sync with the montage.

instead, it’s better to use the UAnimInstance::Montage_SetEndDelegate(FOnMontageEnded& InOnMontageEnded, UAnimMontage* Montage), so you can set up a callback for when your montage ends. If you search the Engine project for usages of it, you can find some examples (the best one I found was in UPlayMontageCallbackProxy::PlayMontage).