• 您的位置:首页 > 新闻动态 > UE4

    UE4自定义动画蓝图步骤

    2021/1/2      点击:

    动画蓝图提供了运行特定操作的节点,比如基于alpha值混合多个节点或者播放一个动画。 这里,您可以找到关于基本动画蓝图节点的更多信息。

    这些节点提供了您将需要的标准功能,但是通常您应用动画时,一般需要创建自定义节点。这比较简单,但是却需要您了解基础系统的设计原理。该链接提供了关于该系统的深入知识,但是这里我想着重强调一下基本系统的内容,因为我发现有些人经常会遇到问题。

    正如上面链接中所述的,系统需要两个类:一个是您在编辑器中看到的图表节点,一个是真正在运行时工作的行为节点。我们出于优化目的将其分离开来。节点构建的性能消耗比较大,使用400个节点每30秒让20个角色死亡或生成将会给内存及CPU造成巨大负担。如果您使用动画蓝图生成了一个角色,那么该角色将不会具有任何图表节点,仅具有行为节点。

    让我们比较一下动画图表节点和动画行为节点的代码:

    动画图表节点

    class UAnimGraphNode_SequencePlayer : public UAnimGraphNode_Base

    动画行为节点

    struct ENGINE_API FAnimNode_SequencePlayer : public FAnimNode_Base

    您将注意到这两个节点的基类是不同的:一个基类是UObject,另一个的基类是UStruct。在该博客中,我们将前一个作为动画图表节点,后一个作为动画行为节点。所有的图表节点包含了类似这样的对应行为节点:

    class UAnimGraphNode_SequencePlayer : public UAnimGraphNode_Base
    {
        GENERATED_UCLASS_BODY()

        UPROPERTY(EditAnywhere, Category=Settings)
        FAnimNode_SequencePlayer Node;

    }

    该动画图表节点知道另一个节点的存在,但反之则不能,这一个比较重要的区别。所有动画图表节点都是出于这个原因存在于编辑器中的,因为它不会随同游戏加载,仅存在于编辑器中。另一方面,行为节点存在于运行时代码中,而这正是真正发生混合的地方。请确保您的类指向正确的模块。

    这对骨架控制节点来说也是一样的。

    动画图表节点

    class UAnimGraphNode_ModifyBone : public UAnimGraphNode_SkeletalControlBase

    动画行为节点

    struct ENGINE_API FAnimNode_ModifyBone : public FAnimNode_SkeletalControlBase

    您将注意到骨架控制节点也具有不同的基类。

    该系统如此设计,动画图表节点负责任何编辑器工作,比如显示节点名称、显示工具提示信息或创建自定义引脚。动画行为节点负责实际工作,比如混合、计算目标位置,及输出正确姿势。所以动画图表节点在编辑器中是重要的,而动画行为节点则在运行时是重要的。

    再次说明,请参照该链接来查看变量的元数据是如何变为节点的输入或输出的。

    比如,FPoseLink是传入骨骼变换数组的姿势连接,如果您像下面这样声明它,那么它将会像图片中那样显示出来。


    UPROPERTY(Category=Links)
    FPoseLink BasePose;


    知道FPoseLink如何工作比较重要,因为任何时候当您调用任何动画函数时,您也必须调用该Pose函数。比如在您的Update函数中您应该调用BasePose->Update。同样,如果您有BasePose作为成员变量,您也应该在CacheBones函数中调用BasePose->CacheBones。

    现在,我想谈下对于每种节点类型您应该关注的函数。我将不会集中介绍动画蓝图图表节点,因为它同任何其他蓝图节点的工作方式比较类似,但是我想集中介绍下这个真正工作的节点。

    让我们看下FAnimNode_Base节点:

    struct ENGINE_API FAnimNode_Base
    {
        // Interface to implement
        virtual void Initialize(const FAnimationInitializeContext& Context) {}
        virtual void Update(const FAnimationUpdateContext& Context) {}
        virtual void Evaluate(FPoseContext& Output) { check(false); }
        virtual void CacheBones(const FAnimationCacheBonesContext& Context) {}
        virtual void GatherDebugData(FNodeDebugData& DebugData){}

    };

    它不是这么简单,但我正在进行简化以仅集中介绍您应该关心的主要事情。

    有三个决定了您的节点如何表现的主要函数。它们是Initialize、Update和Evaluate,这里是对它们应用的简单描述:

    • Initialize - 任何时候当您需要进行初始化或重新初始化时调用该函数(当修改实例的网格物体时)。
    • Update - 调用该函数来更新当前状态(比如更新播放时间或混合权重)。该函数取入一个FAnimationUpdateContext,它知道更新的DeltaTime和当前的节点混合权重。
    • Evaluate - 调用该函数来生成一个‘姿势’(一系列的骨骼变换)。
      • 示例:
        • void FAnimNode_SequenceEvaluator::Evaluate(FPoseContext& Output)
          {
              if ((Sequence != NULL) && (Output.AnimInstance->CurrentSkeleton->IsCompatible(Sequence->GetSkeleton())))
              {
                  Output.AnimInstance->SequenceEvaluatePose(Sequence, Output.Pose, FAnimExtractContext(ExplicitTime));
              }
              else
              {
                  Output.ResetToRefPose();
              }
          }

      • Evaluate 判断是否设置了序列及它是否同当前骨架兼容。如果是,那么它将该骨骼变换填充到Output.Pose中。如果不是,则将Output设置为参考姿势。


    在这些基本函数的基础上,您需要提供两个函数的实现,以确保您的节点可以正常同图表的其他部分协同工作:

    virtual void CacheBones(const FAnimationCacheBonesContext& Context) {}
    virtual void GatherDebugData(FNodeDebugData& DebugData){}

    CacheBones用于刷新该节点所引用的骨骼索引,GatherDebugData用于使用"ShowDebug Animation"数据进行调试。为了保持到子项的连接,使用这些是很重要的。正如我之前所提到的,FPoseLink 应该调用它下面的所有节点,以确保您的节点连接的任何姿势连接都会被调用。

    请参照该示例:

    void FAnimNode_BlendListBase::CacheBones(const FAnimationCacheBonesContext& Context)
    {
        for(int32 ChildIndex=0; ChildIndex
        {
            BlendPose[ChildIndex].CacheBones(Context);
        }
    }

    您需要实现它,以便这些事件不会被您的节点阻止。

    同时注意我们有FAnimationRuntime,当进行动画时它提供了大量功能。

    这里是一个关于骨架控制节点的示例:

    struct ENGINE_API FAnimNode_SkeletalControlBase : public FAnimNode_Base
    {
        // FAnimNode_Base interface
        virtual void Initialize(const FAnimationInitializeContext& Context) override;
        virtual void CacheBones(const FAnimationCacheBonesContext& Context)  override;
        virtual void Update(const FAnimationUpdateContext& Context) override;
        virtual void EvaluateComponentSpace(FComponentSpacePoseContext& Output) override;
        // End of FAnimNode_Base interface
    }

    这同动画节点类似但又有所不同,因为骨架控制节点在组件空间上工作。再次说明,查看FAnimationRuntime将向您展示一种在本地空间和组件空间之间转换的好方法。

    您一般都使用EvaluateComponentSpace。这里是一个涉及到CopyBone节点的简单应用示例:

    void FAnimNode_CopyBone::EvaluateBoneTransforms(USkeletalMeshComponent* SkelComp, FCSPose& MeshBases, TArray& OutBoneTransforms)
    {
        check(OutBoneTransforms.Num() == 0);
        // Pass through if we're not doing anything.
        if( !bCopyTranslation && !bCopyRotation && !bCopyScale )
        {
            return;
        }
        // Get component space transform for source and current bone.
        const FBoneContainer& BoneContainer = MeshBases.GetPose().GetBoneContainer();
        FCompactPoseBoneIndex TargetBoneIndex = TargetBone.GetCompactPoseIndex(BoneContainer);
        const FTransform& SourceBoneTM = MeshBases.GetComponentSpaceTransform(SourceBone.GetCompactPoseIndex(BoneContainer));
        FTransform CurrentBoneTM = MeshBases.GetComponentSpaceTransform(TargetBoneIndex);
        // Copy individual components
        if (bCopyTranslation)
        {
            CurrentBoneTM.SetTranslation( SourceBoneTM.GetTranslation() );
        }
        if (bCopyRotation)
        {
            CurrentBoneTM.SetRotation( SourceBoneTM.GetRotation() );
        }
        if (bCopyScale)
        {
            CurrentBoneTM.SetScale3D( SourceBoneTM.GetScale3D() );
        }
        // Output new transform for current bone.
        OutBoneTransforms.Add(FBoneTransform(TargetBoneIndex, CurrentBoneTM));
    }

    其目的是在OutBoneTransforms中返回您想要的数据。您可以返回您需要的任何数量的骨骼变换,但请注意层次结构。保持父项到子项的顺序将能确保总是安全的。

    我希望这向您进行了进一步介绍,以使得您开始创建自己的自定义节点变得更加容易。