keywords: [UE4]Animation Montage动画相关

常用API

C++如何播放Montage动画

Character接口:
假设是在Character内执行,其中MontagePtr为Montage指针:

float AnimDuration = PlayAnimMontage(MontagePtr) / (GetMesh() && GetMesh()->GlobalAnimRateScale > 0 ? GetMesh()->GlobalAnimRateScale : 1);

AnimInstace接口:

float AnimDuration = Montage_Play(Param.Montage, Param.PlayRate);

如果只是播放Animation Sequence,不需要Montage,可以使用 PlayAnimation():

if (USkeletalMeshComponent *Mesh = GetMesh())
{
    Mesh->PlayAnimation(AnimSequence, false);
}

注意:PlayAnimation可以直接来播放AnimSequence,但是不能混合动作。比如当播放完攻击动作后,无法自动回到空闲状态,但是通过Montage的Slot可以完成自动切换动作。

其他参考:
How can I play animations strictly from C++?
https://answers.unrealengine.com/questions/292345/how-can-i-play-animations-strictly-from-c.html

C++动态播放Montage(通过AnimSequence创建)

keywords:UE4, C++, AnimSequence, AnimMontage, Slot, 动态创建, 播放动画

首先要在动画蓝图中添加Slot节点,默认的Slot名字为DefaultSlot。这个名字会在C++代码中使用

假设已经获取了要播放的AnimSequence对象指针:AnimSeq

float AnimLength = 0.f;
if (USkeletalMeshComponent *Mesh = MyActor->FindComponentByClass<USkeletalMeshComponent>())
{
    if (UAnimInstance *AnimInst = Mesh->GetAnimInstance())
    {
        if (UAnimMontage* Mtg = AnimInst->PlaySlotAnimationAsDynamicMontage(AnimSeq, TEXT("DefaultSlot"), 0.25f, 0.25f, 1.f, 1))
        {
            AnimLength = Mtg->GetPlayLength();
        }
    }
}

使用UAnimInstance::PlaySlotAnimationAsDynamicMontage()播放Montage时,参数 Animation Sequence 中 Notify 仍然对动态生成的 Montage 有效,即:Montage 也会触发同样的Notify。

如果将PlaySlotAnimationAsDynamicMontage()的返回值Montage保存起来,则这个Montage对象会保存创建时指定的LoopCount, SlotName等信息。
如果下次播放该Montage时希望修改LoopCountSlotName等信息,则需要创建新的Montage对象。

使用PlaySlotAnimationAsDynamicMontage()播放Montage时,如果不是循环播放的Montage,播放完毕后一定要执行UAnimInstance::Montage_Stop()停掉当前Montage,否则会干扰蓝图动画的逻辑,导致切换其他Montage时无效。

C++播放Montage动画时让动作停留在最后一帧

keywords:UE4、Montage、C++、播放速度、播放速率、加速播放、减速播放

四种方式
方式1: 在想要冻结的那一帧内,将SkeletonComponent的GlobalAnimRateScale属性设置为0。
比如想要角色的骨骼定格在第三帧,那么就在动画播放到第三帧的时候将该属性设置为0。

ACharacter::GetMesh()->GlobalAnimRateScale = 0.f;

方式2:

ACharacter::GetMesh()->bNoSkeletonUpdate = true;

方式3:

ACharacter::GetMesh()->bPauseAnims = true;

注意:正常播放完AnimSequence时,只要不是设置为循环播放,播放完以后会自动停留在最后一帧。只有播放Montage时才需要设置以上属性。

方式4:

Enable Auto Blend Out属性设置为false,这样当Montage播放到结尾时不会自动Blend Out,而是停留在最后一帧直到手动Stop。

动画状态机切换动画时让动画停留在最后一帧

动画蓝图中的State内部播放动画的节点属性Loop修改为false

如果想让动画停留在最后一帧,另一种方式是在动画的结尾添加Notify事件,在事件中修改动画的播放速率为0。

C++播放Montage时指定Section

方式1:
通过第三个参数 StartSectionName 指定 Section

float ACharacter::PlayAnimMontage(class UAnimMontage* AnimMontage, float InPlayRate = 1.f, FName StartSectionName = NAME_None);

方式2:
使用 UAnimInstance::Montage_JumpToSection()

if (USkeletalMeshComponent *Mesh = Character->GetMesh())
{
    if (UAnimInstance *AnimInst = Mesh->GetAnimInstance())
    {
        Char->PlayAnimMontage(MontagePtr);
        AnimInst->Montage_JumpToSection(FName("step_03"), MontagePtr);
    }
}

执行 Montage_JumpToSection 时请确保角色动画的正处于Section所属Montage的播放状态,否则 JumpToSection 无效。

获取当前正在播放的Montage
/** Get a current Active Montage in this AnimInstance. 
    Note that there might be multiple Active at the same time. This will only return the first active one it finds. **/
UAnimMontage* UAnimInstance::GetCurrentActiveMontage() const
停止Montage播放

如果要停止当前正在播放的Montage,三种方式。

方式1:

void ACharacter::StopAnimMontage(class UAnimMontage* AnimMontage);

方式2:

void UAnimInstance::Montage_Stop(float InBlendOutTime, const UAnimMontage* Montage = nullptr);

If no Montage reference was gave (Montage is nullptr), all actived montages will be stop.

Source in Engine:

void UAnimInstance::Montage_Stop(float InBlendOutTime, const UAnimMontage* Montage)
{
    if (Montage)
    {
        FAnimMontageInstance* MontageInstance = GetActiveInstanceForMontage(Montage);
        if (MontageInstance)
        {
            MontageInstance->Stop(FAlphaBlend(Montage->BlendOut, InBlendOutTime));
        }
    }
    else
    {
        // If no Montage reference, do it on all active ones.
        for (int32 InstanceIndex = 0; InstanceIndex < MontageInstances.Num(); InstanceIndex++)
        {
            FAnimMontageInstance* MontageInstance = MontageInstances[InstanceIndex];
            if (MontageInstance && MontageInstance->IsActive())
            {
                MontageInstance->Stop(FAlphaBlend(MontageInstance->Montage->BlendOut, InBlendOutTime));
            }
        }
    }
}

方式3:

void UAnimMontage::Stop(const FAlphaBlend& InBlendOut, bool bInterrupt=true);

如果希望停止播放Montage时有动画融合效果,记得将Montage的Blend Out Time设置为大于0,比如默认值 0.25 。

一旦通过StopAnimMontage停止正在播放的Montage,角色就会恢复到待机动作(假设动画蓝图的默认动画为待机动画的话)。

设置Montage播放结束和融合结束后的回调函数
//设置Montage播放完毕(动画的最后一帧)的回调
void UAnimInstance::Montage_SetEndDelegate(FOnMontageEnded & InOnMontageEnded, UAnimMontage* Montage = NULL);

//设置Montage混出(BlendOut)开始时的回调
void UAnimInstance::Montage_SetBlendingOutDelegate(FOnMontageBlendingOutStarted & InOnMontageBlendingOut, UAnimMontage* Montage = NULL);

如果在播放Montage的同时添加一个float类型的变量(比如float AnimPlayingDelata = 0.f;),并在Tick中执行AnimPlayingDelata += DeltaSeconds,那么AnimPlayingDelata >= Montage->SequenceLength将在Montage_SetEndDelegate回调之前触发。

Blend In 和 Blend Out 区别

Engine\Source\Runtime\Engine\Classes\Animation\AnimMontage.h

/** Blend out option. This is only used when it blends out itself. If it's interrupted by other montages, it will use new montage's BlendIn option to blend out. */
UPROPERTY(EditAnywhere, Category=BlendOption)
FAlphaBlend BlendOut;

BlendOut作用:只有当前Montage快结束时,且结束后恢复到待机动画(动画蓝图中的默认Pose),BlendOut才会起作用,即在待机Pose和Montage快结束时的Pose之间进行融合,融合时长为BlendOut
如果Montage A在播放未结束时,Montage B开始播放,那么Montage A的BlendOut不在生效,而是使用Montage B的BlendIn
如果某个Montage循环播放,那么BlendOut不会起作用;如果某个Montage连续播放多次,那么BlendOut只在最后一次播放将要结束时才生效。

如何指定动画的某个时间点作为起始播放点

UAnimInstance::PlaySlotAnimationAsDynamicMontage()UAnimInstance::Montage_Play()都提供了参数InTimeToStartMontageAt,表示从当前动作的哪个时间点还是融合,默认为0,即从第一帧开始融合。

常见问题

Montage Section切换无效的问题(2018-02-11)

keywords: UE4 Montage Section not working

问题现象:
SetNextSection在编辑器中运行正常,但是在打包版本中无效。

解决办法:
将SetNextSection替换为JumpToSection。

参考自:Montage unwanted section switching
https://answers.unrealengine.com/questions/172537/montage-unwanted-section-switching.html

如果Section的时间很短,需要将Blend InBlend Out改小或者设置为 0(默认为0.25),否则 JumpToSection 无效果。

Montage 循环播放(2018-10-26)

如果用动画蓝图控制动画切换,直接用动画状态机去指定 AnimSequence 即可,不需要其他设置,因为动画蓝图会自动根据状态机去循环播放指定的 AnimSequence 。
如果用C++播放Montage,则需要注意以下细节:

  1. 如果是使用现成的Montage资源,需要将Montage设置为循环播放,方式如下:
    打开Montage编辑窗口后,先鼠标单击位置1,然后鼠标单击位置2(都是鼠标左键单击)

    然后Preview Section 进度条中就多了一个 X ,此时表示Montage为循环播放了
    之后再执行 ACharacter::PlayAnimMontage() 就可以自动循环播放指定Montage了。PlayAnimMontage() 只需执行一次即可。

  2. 如果动态创建Montage并播放,则需要将LoopCount设置为足够大,比如 9999。这样当前Montage就会循环播放:

     AnimInstance->PlaySlotAnimationAsDynamicMontage(AnimSeq, TEXT("DefaultSlot"), 0.25f, 0.25f, 1.f, 9999));
    

这种方式是否可行:不设置Montage循环播放,而是在Tick中不停检测上一次Montage的累计播放时长,当播完时马上执行下一次播放。答案是不可行!!实际效果是:两次的Montage播放之间会有短暂时间切换到Idle状态动画。

Montage 切换时没有融合的问题

原因:
Montage 的参数Blend In被设置为了0。

解决办法:
将Montage 的参数Blend In(Blend Option -> Blend In -> Blend Time)改成大于0,比如默认值0.25。

Crash on PlayAnimMontage

If a Character play a Montage which isn’t same Skeleton, client may crash.

MyCharacter->PlayAnimMontage(MyMtg, 1.f, FName("Sec01"));

人生如逆旅,我亦是行人。----苏轼《临江仙》