福感科技有限公司 欢迎您!
联系方式

    地址:北京市平谷区马坊镇金河北街17号院3号楼7层712

    电话:010-89968230

    网站:http://www.fgsense.com

UE4自定义动画蓝图步骤

2021-5-6 9:52:22      点击:

动画蓝图提供了运行特定操作的节点,比如基于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中返回您想要的数据。您可以返回您需要的任何数量的骨骼变换,但请注意层次结构。保持父项到子项的顺序将能确保总是安全的。

声明:文章内容整理来源于网络,版权属于原作者,如有问题,请联系我们!

Copyright 2019 www.fgsense.com

福感科技有限公司 版权所有 All Rights Reserved

京ICP备20002031号

010-89968230