UE4自定义动画蓝图步骤
动画蓝图提供了运行特定操作的节点,比如基于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中返回您想要的数据。您可以返回您需要的任何数量的骨骼变换,但请注意层次结构。保持父项到子项的顺序将能确保总是安全的。
声明:文章内容整理来源于网络,版权属于原作者,如有问题,请联系我们!
- 上一篇:Unreal4引擎新手入门 配置 2021/5/6
- 下一篇:UNIT3D更改界面字体大小颜色 2021/5/5