Unable to play the same montage twice

I’m trying to play the same montage twice in a function as such:

UCLASS()
class MYPROJECT_API AMyCharacter : public ACharacter {
    GENERATED_BODY()

   public:
    UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
    UAnimMontage* MyMontage;

    UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
    UAnimMontage* CurrentMontage;

    UFUNCTION(BlueprintCallable)
    void PlayTwice();

    UFUNCTION(BlueprintCallable)
    void BeginMontage(UAnimMontage* Montage);

    UFUNCTION(BlueprintCallable)
    void EndMontage(UAnimMontage* OldMontage, bool Interrupted);

};
void AMyCharacter::PlayTwice() {
    BeginMontage(MyMontage);
    BeginMontage(MyMontage);
}

void AMyCharacter::BeginMontage(UAnimMontage* Montage) {

    UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();

    if (CurrentMontage)
        EndMontage(CurrentMontage, false);

    CurrentMontage = Montage;

    AnimInstance->OnMontageEnded.AddDynamic(this, &AMyCharacter::EndMontage);
    AnimInstance->Montage_Play(Montage);
}

void AMyCharacter::EndMontage(UAnimMontage* Montage, bool Interrupted) {

    UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();

    CurrentMontage = nullptr;

    AnimInstance->OnMontageEnded.RemoveDynamic(this, &AMyCharacter::EndMontage);
    if (AnimInstance->Montage_IsPlaying(Montage)) {
        AnimInstance->Montage_Stop(0.0, Montage);
    }
}

but when I run the PlayTwice() function it does not play the montage at all
I’m guessing maybe the Montage_Stop() actually runs after AddDynamic() maybe in the next tick frame?

How do I make it work as intended?

Note: I’ve intentionally binded to OnMontageEnded please do not recommend to remove it

From looking at the code, PlayTwice() should make it play once, on the second call of BeginMontage().

The current setup plays a montage but immediately stops playing since CurrentMontage was set by the last call of BeginMontage(), which calls EndMontage() (sorry if that was hard to follow haha)
If you want it to run twice I’d recommend making a queue of some sort, maybe an array, and when the montage ends EndMontage() checks for montages in the queue and plays if there are any. PlayTwice can call BeginMontage() then add the same montage to the queue.

As for the delegates and timings, I’d recommend stepping through using a debugger and seeing when your code runs and the delegate fires. If you can’t debug for whatever reason I’d be happy to try in a bit.

I’m not sure I follow but yes if you could debug it would be very helpful since I cannot debug at the moment

Hi,

What it is happening here is: your montages is been playing, but when the first is called the second comes next “overriding” the first.

if you need to play two or more montages, simple create a montage with as many as you want animations sequences.

bye.

This really doesn’t have anything to do with sections I’m just trying to figure out a solution to make it work

Right,

Is your BeginMontage(MyMontage) working?

if so then use FTimerHandle in your PlayTwice, since you can get MyMontage’s play lenth in order to set SetTimer.

Bye.

I looked through the source a bit and this is what I assume is happening:

  • Either by calling Montage_Stop() or playing another montage, the current montage (if it exists) is stopped
  • Montage_Stop() takes a float to determine how long the montage has to blend out
    • When a montage is stopped, you specify 0.0f as the blend out time
    • When a montage is overridden (another is played when montage is playing) it uses the current montage’s blend in time
  • This blend out value is used in a timer/delay, then when this timer/delay is finished OnMontageEnded is broadcast. Since 0.0f is likely passed as the time of the delay, this means it will still take one tick to process the timer and then call OnMontageEnded.
  • Either that is the case, or no timer is created since the blend out time is <=0.0f, however processing the ended montage and broadcasting OnMontageEnded still carries over into the next tick.

Basically, to solve your problem:

Implement a queue system with an array. Upon a montage ending, check for montages in the queue and play them if there are any. This should fix it and play twice, as you aren’t overriding any animations and thus aren’t dealing with the AnimMontage broadcasting OnMontageEnded the tick after starting your montage.
This system would probably be better than using FTimerHandles as it waits for the montage to completely finish, accounting for play rates and everything in between.

Here is an example on how to implement it, without error checking:
(keep in mind I did not write this in an IDE so it may not directly compile, but should give you an idea on what to add)

.h

UCLASS()
class MYPROJECT_API AMyCharacter : public ACharacter {
    GENERATED_BODY()

   public:
    UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
    UAnimMontage* MyMontage;

    UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
    UAnimMontage* CurrentMontage;

    UFUNCTION(BlueprintCallable)
    void PlayTwice();

    UFUNCTION(BlueprintCallable)
    void BeginMontage(UAnimMontage* Montage);

    UFUNCTION(BlueprintCallable)
    void EndMontage(UAnimMontage* OldMontage, bool Interrupted);

protected:

    UPROPERTY()
    TArray<UAnimMontage*> MontageQueue;

};

.cpp

void AMyCharacter::PlayTwice() {
    MontageQueue.Add(MyMontage);
    BeginMontage(MyMontage);
}

void AMyCharacter::BeginMontage(UAnimMontage* Montage) {

    UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();

    if (CurrentMontage)
        EndMontage(CurrentMontage, false);

    CurrentMontage = Montage;

    AnimInstance->OnMontageEnded.AddDynamic(this, &AMyCharacter::EndMontage);
    AnimInstance->Montage_Play(Montage);
}

void AMyCharacter::EndMontage(UAnimMontage* Montage, bool Interrupted) {

    UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();

    CurrentMontage = nullptr;

    AnimInstance->OnMontageEnded.RemoveDynamic(this, &AMyCharacter::EndMontage);
    if (AnimInstance->Montage_IsPlaying(Montage)) {
        AnimInstance->Montage_Stop(0.0, Montage);
    }

    if (MontageQueue.Num() > 0)
    {
        BeginMontage(MontageQueue[0]);
        MontageQueue.RemoveAt(0);
    }

}

Please let me know how it goes.

1 Like

Excellent analysis! Even confirms my suspicion about carrying over into the next tick.

However I found out that it would just be simpler to restart the animation :stuck_out_tongue:

if (CurrentMontage == Montage) {
   AnimInstance->Montage_SetPosition(Montage, 0.0f);
} else {

    if (CurrentMontage)
        EndMontage(CurrentMontage, false);

    CurrentMontage = Montage;

    AnimInstance->OnMontageEnded.AddDynamic(this, &AMyCharacter::EndMontage);
    AnimInstance->Montage_Play(Montage);
}  

Granted this may not give the blend in - out as desired and making a queue would be a better approach but for my purposes this works

Thank you again!

No problem. Yes that would work perfectly fine, if you have one montage which I assume you do. I was thinking about multiple montages.
Glad you have solved it!

1 Like

if you have the time could you take a look at this follow up question?

I think I have another way of detecting montage end