杭州战争网站建设,windows部署网站php,搜索引擎国外,网站制作公司的网站1. Unity(2022)的应用
由Animtor组件控制 在Animation Clip下可进行详细设置 官方文档的介绍(Animation选项卡 - Unity 手册) 上述动画类型在Rag选项卡中设置: Rig 选项卡上的设置定义了 Unity 如何将变形体映射到导入模型中的网格#xff0c;以便能够将其动画化。 对于人…1. Unity(2022)的应用
由Animtor组件控制 在Animation Clip下可进行详细设置 官方文档的介绍(Animation选项卡 - Unity 手册) 上述动画类型在Rag选项卡中设置: Rig 选项卡上的设置定义了 Unity 如何将变形体映射到导入模型中的网格以便能够将其动画化。 对于人形 (Humanoid) 角色这意味着需要分配或创建 Avatar(ps:有时将动画限制为特定的身体部位会很有用。例如在一个行走动画中角色可能会挥动他们的手臂但如果他们拿起火炬他们应该将火炬举起来投光。可以使用 Avatar 身体遮罩 (Avatar BodyMask) 来指定应将动画限制在角色的哪些部位)。 对于非人形角色通用(Generic)角色这意味着需要在骨架中确定根骨骼。 默认情况下在项目视图中选择模型时Unity会确定哪个动画类型 (Animation Type)与所选的模型最匹配然后将其显示在 Rig 选项卡中。如果 Unity 从未导入该文件则 Animation Type 设置为 None。动画类型如下:
属性功能Animation Type指定动画类型。None不存在动画Legacy使用旧版动画系统。与 Unity 3.x 及更早版本一样导入和使用动画。Generic如果骨架为非人形四足动物或任何要动画化的实体请使用通用动画系统。Unity 会选择一个根节点但可以确定另一个用作__根节点__的骨骼。Humanoid如果骨架为人形有两条腿、两条手臂和一个头请使用人形动画系统。Unity 通常会检测骨架并将其正确映射到 Avatar。有些情况下可能需要更改 Avatar 定义 (Avatar Definition) 并手动对映射进行__配置 (Configure)__。
2. Unreal(5.4)的应用
Unreal中根骨骼的定义是指向角色两脚中心的一个虚拟骨骼。要使用RootMotio功能,首先需要通过在动画序列编辑器编辑动画,设置RootMotion的启用: 强制根骨骼锁定和屏蔽根骨骼位移从效果上看是一样的,均是固定根骨骼的位置。RootMotion的使用设置如下表:
启用根运动EnableRootMotion启用后将允许提取根运动。使用动画蓝图的类默认属性 根运动模式Root Motion Mode 来定义如何提取根运动。根运动根锁Root Motion Root Lock在提取根运动时将根骨骼锁定在定义的位置。可以用以下选项来锁定根骨骼 参考姿势Ref Pose将根骨骼锁定在其在骨骼网格体 参考姿势Reference Pose 中的位置。 动画第一帧Anim First Frame将根骨骼锁定在选中动画的 第一帧 的位置。 零Zero将根骨骼锁定在网格体相对坐标的0,0,0位置。强制根锁Force Root Lock启用后强制施加根骨骼锁定即使未启用 根运动Root Motion 也是如此。使用规格化根运动比例Use Normalized Root Motion Scale启用后将对提取的根运动使用规格化比例值。FVector(1.0, 1.0, 1.0)。
然后在动画蓝图中设置,选择是否导出编辑后动画的RootMotion。 需要注意的是,即使动画开启了RootMotion开关,但在动画蓝图中没有设置导出RootMotion,场景中的角色依旧做原动画,即根骨骼的运动没有被导出。
无根运动提取No Root Motion Extraction根运动Root Motion按原样保留应用到根骨骼。忽略根运动Ignore Root Motion提取根运动Root Motion并从根骨骼中移除根运动但不应用到角色。来自每一项目的根运动Root Motion from Everything提取每个帮助构建最终角色姿势的动画资源的根运动。每一部分的提取根运动均根据构成该姿势的源资产的权重进行混合。仅来自蒙太奇的根运动Root Motion from Montages Only仅从启用了根动作的动画蒙太奇中提取根动作。
启用RootMotion并且在动画蓝图中定义好RootMotion提取的应用方式后动画会在播放时驱动动作组件。如下图展示,开启RootMotion的角色蓝图会将他根骨骼的运动附加到角色网格体所属的胶囊体上,所以胶囊体会随着角色的移动而变化。 虚幻引擎中的根运动 | 虚幻引擎 5.4 文档 | EpicDeveloper Community (epicgames.com)
3.使用视频
1.Unity2022的使用 RootMotion Unity的演示视频 2.Unreal 5.4的使用 RootMotion Unreal的演示视频 4.RootMotion Unreal5.4源码
分为两部分:1.RootMotion提取2.RootMotion应用。
1.RootMotion提取
从UE5.4动画播放接口UpdateAnimation为entry point:
void UAnimInstance::UpdateAnimation(float DeltaSeconds, bool bNeedsValidRootMotion, EUpdateAnimationFlag UpdateFlag)
{
//....
// need to update montage BEFORE node update or Native Update.
// so that node knows where montage isUpdateMontage(DeltaSeconds);// now we know all montage has advanced// time to test sync groupsUpdateMontageSyncGroup();// Update montage eval data, to be used by AnimGraph Update and Evaluate phases.UpdateMontageEvaluationData();
//....
}void UAnimInstance::UpdateMontage(float DeltaSeconds)
{//...// update montage weightMontage_UpdateWeight(DeltaSeconds);// update montage should run in game thread// if we do multi threading, make sure this stays in game thread. // This is because branch points need to execute arbitrary code inside this call.Montage_Advance(DeltaSeconds);// ...
}void UAnimInstance::UpdateMontage(float DeltaSeconds)
{//...for (int32 InstanceIndex 0; InstanceIndex MontageInstances.Num(); InstanceIndex){FAnimMontageInstance* const MontageInstance MontageInstances[InstanceIndex];// should never be NULLensure(MontageInstance);if (MontageInstance MontageInstance-IsValid()){// 动画混合的情况bool const bUsingBlendedRootMotion (RootMotionMode ERootMotionMode::RootMotionFromEverything);// 判断是否在动画序列中打开了RootMotion开关bool const bNoRootMotionExtraction (RootMotionMode ERootMotionMode::NoRootMotionExtraction);// Extract root motion if we are using blend root motion // (RootMotionFromEverything) or if we are set to extract root // motion AND we are the active root motion instance. This is so we can make // root motion deterministic for networking when// we are not using RootMotionFromEverythingbool const bExtractRootMotion !MontageInstance-IsRootMotionDisabled() (bUsingBlendedRootMotion || (!bNoRootMotionExtraction (MontageInstance GetRootMotionMontageInstance())));FRootMotionMovementParams LocalExtractedRootMotion;FRootMotionMovementParams* RootMotionParams nullptr;if (bExtractRootMotion){// 开启RootMotion的情况下RootMotionParams ExtractedRootMotion(为AnimInstantce.h的成员变量)。RootMotionParams (RootMotionMode ! ERootMotionMode::IgnoreRootMotion) ? ExtractedRootMotion : LocalExtractedRootMotion;}MontageInstance-MontageSync_PreUpdate();MontageInstance-Advance(DeltaSeconds, RootMotionParams, bUsingBlendedRootMotion);// ...}}// ...
}AnimInstantce.h:
//...
// Root motion read from proxy (where it is calculated)
// and stored here to avoid potential stalls by calling GetProxyOnGameThread.
// Utility struct to accumulate root motion.!!!
FRootMotionMovementParams ExtractedRootMotion;void FAnimMontageInstance::Advance(float DeltaTime, struct FRootMotionMovementParams* OutRootMotionParams, bool bBlendRootMotion)
{if (IsValid()){// with custom curves, we cant just filter by weight// also if you have custom curve with longer 0, youll likely to pause montage during that blending time// I think that is a bug. It still should move, the weight might come back later. if (bPlaying){const bool bExtractRootMotion (OutRootMotionParams ! nullptr) Montage-HasRootMotion();// ...// Extract Root Motion for this time slice, and accumulate it.// IsRootMotionDisabled() can be changed by AnimNotifyState BranchingPoints // while advancing, so it needs to be checked here.if (bExtractRootMotion AnimInstance.IsValid() !IsRootMotionDisabled()){const FTransform RootMotion Montage-ExtractRootMotionFromTrackRange(PreviousSubStepPosition, Position);if (bBlendRootMotion){// Defer blending in our root motion until after we get our slot // weight updatedconst float Weight Blend.GetBlendedValue();AnimInstance.Get()-QueueRootMotionBlend(RootMotion, Montage- SlotAnimTracks[0].SlotName, Weight);}else{// Component Space中的RootMotion Transform数据AccumulateOutRootMotionParams-Accumulate(RootMotion);}}}}
}/** Extract RootMotion Transform from a contiguous Track position range.* *CONTIGUOUS* means that if playing forward StartTractPosition EndTrackPosition.* No wrapping over if looping. No jumping across different sections.* So the AnimMontage has to break the update into contiguous pieces to handle those cases.** This does handle Montage playing backwards (StartTrackPosition EndTrackPosition).** It will break down the range into steps if needed to handle looping animations, or different animations.* These steps will be processed sequentially, and output the RootMotion transform in component space.*/
FTransform UAnimMontage::ExtractRootMotionFromTrackRange(float StartTrackPosition, float EndTrackPosition) const
{FRootMotionMovementParams RootMotion;// For now assume Root Motion only comes from first track.if( SlotAnimTracks.Num() 0 ){const FAnimTrack SlotAnimTrack SlotAnimTracks[0].AnimTrack;// Get RootMotion pieces from this track.// We can deal with looping animations, or multiple animations. So we break those // up into sequential operations.// (Animation, StartFrame, EndFrame) so we can then extract root motion sequentially. ExtractRootMotionFromTrack(SlotAnimTrack, StartTrackPosition, EndTrackPosition, RootMotion);}return RootMotion.GetRootMotionTransform();
}void UAnimCompositeBase::ExtractRootMotionFromTrack(const FAnimTrack SlotAnimTrack, float StartTrackPosition, float EndTrackPosition, FRootMotionMovementParams RootMotion) const
{TArrayFRootMotionExtractionStep RootMotionExtractionSteps;// 从Animation Track中根据Position获取动画数据 SlotAnimTrack.GetRootMotionExtractionStepsForTrackRange(RootMotionExtractionSteps, StartTrackPosition, EndTrackPosition);// Go through steps sequentially, extract root motion, and accumulate it.// This has to be done in order so root motion translation rotation is applied properly (as translation is relative to rotation)for (int32 StepIndex 0; StepIndex RootMotionExtractionSteps.Num(); StepIndex){// 遍历所有的动画序列(Montage combine several animation sequunces int oa single asset)const FRootMotionExtractionStep CurrentStep RootMotionExtractionSteps[StepIndex];if (CurrentStep.AnimSequence-bEnableRootMotion){// 开启RootMotion,提取RootMotion数据 FTransform DeltaTransform CurrentStep.AnimSequence-ExtractRootMotionFromRange(CurrentStep.StartPosition, CurrentStep.EndPosition);// 设置Root Motion的Transfrom,将DeletaTransfrom应用为现有的根运动RootMotion.Accumulate(DeltaTransform);}}
}
void Accumulate(const FTransform InTransform)
{if (!bHasRootMotion){// 在开启RootMotion后,根骨骼Transform被导出Set(InTransform);}else{RootMotionTransform InTransform * RootMotionTransform;RootMotionTransform.SetScale3D(RootMotionScale);}
}从AnimSegment(存储动画序列–this is animation segment that defines what animation and how)对象中获取相应的动画序列AnimatonSequence和开始位置以及结束位置。
/** * Given a Track delta position [StartTrackPosition, EndTrackPosition]* See if any AnimSegment overlaps any of it, and if any do, break them up into a sequence of FRootMotionExtractionStep.* Supports animation playing forward and backward. Track range should be a contiguous range, not wrapping over due to looping.*/
void FAnimTrack::GetRootMotionExtractionStepsForTrackRange(TArrayFRootMotionExtractionStep RootMotionExtractionSteps, const float StartTrackPosition, const float EndTrackPosition) const
{for(int32 AnimSegmentIndex0; AnimSegmentIndexAnimSegments.Num();AnimSegmentIndex){const FAnimSegment AnimSegment AnimSegments[AnimSegmentIndex];AnimSegment.GetRootMotionExtractionStepsForTrackRange(RootMotionExtractionSteps, StartTrackPosition, EndTrackPosition);}
}AnimSegment.GetRootMotionExtractionStepsForTrackRange主要是根据StartTrackPosition和EndTrackPosition从对FRootMotionExtractionStep容器的Add,FRootMotionExtractionStep结构如下所示:
/** Struct defining a RootMotionExtractionStep.* When extracting RootMotion we can encounter looping animations (wrap around), or different animations.* We break those up into different steps, to help with RootMotion extraction, * as we can only extract a contiguous range per AnimSequence.*/
USTRUCT()
struct FRootMotionExtractionStep
{GENERATED_USTRUCT_BODY()/** AnimSequence ref */UPROPERTY()TObjectPtrUAnimSequence AnimSequence;/** Start position to extract root motion from. */UPROPERTY()float StartPosition;/** End position to extract root motion to. */UPROPERTY()float EndPosition;FRootMotionExtractionStep() : AnimSequence(nullptr), StartPosition(0.f), EndPosition(0.f){}FRootMotionExtractionStep(UAnimSequence * InAnimSequence, float InStartPosition, float InEndPosition) : AnimSequence(InAnimSequence), StartPosition(InStartPosition), EndPosition(InEndPosition){}
};其中TObjectPtr AnimSequence传入的是FAnimSegment对象的TObjectPtr AnimReference;FAnimaSegement的主要成员变量如下所示:
/** this is anim segment that defines what animation and how **/
USTRUCT()
struct FAnimSegment
{GENERATED_USTRUCT_BODY()PRAGMA_DISABLE_DEPRECATION_WARNINGSFAnimSegment(const FAnimSegment) default;FAnimSegment(FAnimSegment) default;FAnimSegment operator(const FAnimSegment) default;FAnimSegment operator(FAnimSegment) default;PRAGMA_ENABLE_DEPRECATION_WARNINGSUE_DEPRECATED(5.1, Public access to AnimReference has been deprecated, use Set/Get-AnimReference instead)/** Anim Reference to play - only allow AnimSequence or AnimComposite **/UPROPERTY(EditAnywhere, CategoryAnimSegment, meta(DisplayName Animation Reference))TObjectPtrUAnimSequenceBase AnimReference;#if WITH_EDITORONLY_DATAUPROPERTY()float CachedPlayLength 0.f;#endif#if WITH_EDITORfriend class UEditorAnimSegment;friend class UEditorAnimCompositeSegment;ENGINE_API void UpdateCachedPlayLength();#endif // WITH_EDITOR...
}从AnimationSegment中获取到AnimSequence以及startPosition和endPosition后,接下来遍历获取到的AnimSequence,根据对应的start/end position来获取Transform。
回到 UAnimCompositeBase::ExtractRootMotionFromTrack方法中,获取到Animation Sequence中的数据后,接下来就是导出根骨骼运动数据:
// Extract Root Motion transform from a contiguous position range (no looping)
FTransform UAnimSequence::ExtractRootMotionFromRange(float StartTrackPosition, float EndTrackPosition) const
{const FVector DefaultScale(1.f);FTransform RootTransformRefPose FTransform::Identity;if (const USkeleton* MySkeleton GetSkeleton()){const FReferenceSkeleton RefSkeleton MySkeleton-GetReferenceSkeleton();if (RefSkeleton.GetNum() 0){// 获取根骨骼的世界变化:Component Space RootBone SpaceRootTransformRefPose RefSkeleton.GetRefBonePose()[0];}}// 读取Offset Transform FTransform StartTransform ExtractRootTrackTransform(StartTrackPosition, nullptr);FTransform EndTransform ExtractRootTrackTransform(EndTrackPosition, nullptr);// Use old calculation if needed.if (bUseNormalizedRootMotionScale){//Clear scale as it will muck up GetRelativeTransformStartTransform.SetScale3D(FVector(1.f));EndTransform.SetScale3D(FVector(1.f));}else{if (IsValidAdditive()){StartTransform.SetScale3D(StartTransform.GetScale3D() DefaultScale);EndTransform.SetScale3D(EndTransform.GetScale3D() DefaultScale);}}// Transform to Component Space// 取逆:RootBone Space Component Space; 最终变化矩阵 RootToComponent Matrix * Offset Transform(Root Space)const FTransform RootToComponent RootTransformRefPose.Inverse();StartTransform RootToComponent * StartTransform;EndTransform RootToComponent * EndTransform;return EndTransform.GetRelativeTransform(StartTransform);
}读取根骨骼坐标系下的Offset Transform
// Time StartTrackPosition / EndTrackPosition 在根骨骼运动Track中的位置
FTransform UAnimSequence::ExtractRootTrackTransform(float Time, const FBoneContainer * RequiredBones) const
{FTransform RootTransform;// 根据根骨骼id 获取RootTransformGetBoneTransform(RootTransform, FSkeletonPoseBoneIndex(RootBoneIndex), static_castdouble(Time), PRAGMA_DISABLE_DEPRECATION_WARNINGSbUseRawDataOnlyPRAGMA_ENABLE_DEPRECATION_WARNINGS);return RootTransform;
}/** IAnimationDataModel instance containing (source) animation data */UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category Animation Model)TScriptInterfaceIAnimationDataModel DataModelInterface;
...
void UAnimSequence::GetBoneTransform(FTransform OutAtom, FSkeletonPoseBoneIndex BoneIndex, double Time, bool bUseRawData) const
{//...// 这里实际Run的是压缩后的transform_track数据,但不存在可读性,所以只展示未压缩的代码const FName BoneName GetSkeleton()-GetReferenceSkeleton().GetBoneName(BoneIndex.GetInt());// 根据Frame获取骨骼的原始变化信息OutAtom DataModelInterface-EvaluateBoneTrackTransform(BoneName, DataModelInterface-GetFrameRate().AsFrameTime(Time), Interpolation);// 查找相应骨骼Track的变化曲线,并根据id获取Curve曲线const FAnimationCurveIdentifier TransformCurveId(BoneName, ERawCurveTrackTypes::RCT_Transform);if (const FTransformCurve* TransformCurvePtr DataModelInterface-FindTransformCurve(TransformCurveId)){// 根据变化曲线获取当前位置的附加变化const FTransform AdditiveTransform TransformCurvePtr-Evaluate(Time, 1.f);const FTransform LocalTransform OutAtom;OutAtom.SetRotation(LocalTransform.GetRotation() * AdditiveTransform.GetRotation());OutAtom.SetTranslation(LocalTransform.TransformPosition(AdditiveTransform.GetTranslation()));OutAtom.SetScale3D(LocalTransform.GetScale3D() * AdditiveTransform.GetScale3D()); }
}templatetypename T
FORCEINLINE TVectorT TTransformT::TransformPosition(const TVectorT V) const
{DiagnosticCheckNaN_All();const TransformVectorRegister InputVectorW0 VectorLoadFloat3_W0(V);//Transform using QST is following//QST(P) Q.Rotate(S*P) T where Q quaternion, S scale, T translation//RotatedVec Q.Rotate(Scale*V.X, Scale*V.Y, Scale*V.Z, 0.f)// 先缩放,再旋转,最后平移// 在原始向量上应用缩放变化const TransformVectorRegister ScaledVec VectorMultiply(Scale3D, InputVectorW0);// 在缩放后的向量上应用旋转变化const TransformVectorRegister RotatedVec VectorQuaternionRotateVector(Rotation, ScaledVec);// 在缩放,旋转后的向量上应用位移变化const TransformVectorRegister TranslatedVec VectorAdd(RotatedVec, Translation);TVectorT Result;VectorStoreFloat3(TranslatedVec, Result);return Result;
}StartTransform和EndTransform都获取到后,EndTransform.GetRelativeTransform(StartTransform)计算Relative Transform。
templatetypename T
TTransformT TTransformT::GetRelativeTransform(const TTransformT Other) const
{// A * B(-1) VQS(B)(-1) (VQS (A))// // Scale S(A)/S(B)// Rotation Q(B)(-1) * Q(A)// Translation 1/S(B) *[Q(B)(-1)*(T(A)-T(B))*Q(B)]// where A this, B OtherTTransformT Result;
if (Other.IsRotationNormalized() false)
{return TTransformT::Identity;
}if (Private_AnyHasNegativeScale(this-Scale3D, Other.Scale3D))
{// note, if you have 0 scale with negative, youre going to lose rotation as it cant convert back to quatGetRelativeTransformUsingMatrixWithScale(Result, this, Other);
}
else
{// 计算相对变化// Scale S(A)/S(B)static ScalarRegister STolerance(UE_SMALL_NUMBER);TransformVectorRegister VSafeScale3D VectorSet_W0(GetSafeScaleReciprocal(Other.Scale3D, STolerance));// 相对缩放TransformVectorRegister VScale3D VectorMultiply(Scale3D, VSafeScale3D);// 计算相对位移//VQTranslation ( ( T(A).X - T(B).X ), ( T(A).Y - T(B).Y ), ( T(A).Z - T(B).Z), 0.f );TransformVectorRegister VQTranslation VectorSet_W0(VectorSubtract(Translation, Other.Translation));// 计算旋转后的相对位移,将StartTransform的Rotation取逆然后应用到相对位移VQTranslation上// Inverse RotatedTranslationTransformVectorRegister VInverseRot VectorQuaternionInverse(Other.Rotation);TransformVectorRegister VR VectorQuaternionRotateVector(VInverseRot, VQTranslation);// 将StartTransform的Scale应用到相对旋转上,得到旋转,缩放后的相对位移//Translation 1/S(B)TransformVectorRegister VTranslation VectorMultiply(VR, VSafeScale3D);// 将StartTransfrom的Inverse Rotation应用到EndTransform的Rotation,计算出相对旋转// Rotation Q(B)(-1) * Q(A)TransformVectorRegister VRotation VectorQuaternionMultiply2(VInverseRot, Rotation);Result.Scale3D VScale3D;Result.Translation VTranslation;Result.Rotation VRotation;Result.DiagnosticCheckNaN_All();#if DEBUG_INVERSE_TRANSFORMTMatrixT AM ToMatrixWithScale();TMatrixT BM Other.ToMatrixWithScale();Result.DebugEqualMatrix(AM * BM.InverseFast());
#endif}return Result;
}关于RootMotion开启后,根骨骼运动的提取方式如下: 2.RootMotion应用
经过的上面RootMotion提取,根骨骼的Transform数据被存储在AnimInstance.h的FRootMotionMovementParams ExtractedRootMotion;变量中,若要观察RootMotion数据的应用,只需要追踪这个变量的使用。
void UCharacterMovementComponent::TickCharacterPose(float DeltaTime)
{if (DeltaTime UCharacterMovementComponent::MIN_TICK_TIME){return;}check(CharacterOwner CharacterOwner-GetMesh());USkeletalMeshComponent* CharacterMesh CharacterOwner-GetMesh();// bAutonomousTickPose is set, we control TickPose from the Characters Movement and Networking updates, and bypass the Components update.// (Or Simulating Root Motion for remote clients)CharacterMesh-bIsAutonomousTickPose true;if (CharacterMesh-ShouldTickPose()){// Keep track of if were playing root motion, just in case the root motion montage ends this frame.const bool bWasPlayingRootMotion CharacterOwner-IsPlayingRootMotion();CharacterMesh-TickPose(DeltaTime, true);// Grab root motion now that we have ticked the poseif (CharacterOwner-IsPlayingRootMotion() || bWasPlayingRootMotion){// 获取Animation Instance的RootMotion数据 FRootMotionMovementParams RootMotion CharacterMesh-ConsumeRootMotion();if (RootMotion.bHasRootMotion){// bHasRootMotion此时为trueRootMotion.ScaleRootMotionTranslation(CharacterOwner-GetAnimRootMotionTranslationScale());RootMotionParams.Accumulate(RootMotion);}}}
}FRootMotionMovementParams USkeletalMeshComponent::ConsumeRootMotion()
{float InterpAlpha;if(bExternalTickRateControlled)InterpAlpha ExternalInterpolationAlpha;elseInterpAlpha ShouldUseUpdateRateOptimizations() ? AnimUpdateRateParams-GetRootMotionInterp() : 1.f; return ConsumeRootMotion_Internal(InterpAlpha);
}这个AnimScriptInstance对象类型正是之前UpdateAnimation的接口UAnimInstance
FRootMotionMovementParams USkeletalMeshComponent::ConsumeRootMotion_Internal(float InAlpha)
{FRootMotionMovementParams RootMotion;if(AnimScriptInstance){ RootMotion.Accumulate(AnimScriptInstance-ConsumeExtractedRootMotion(InAlpha));for(UAnimInstance* LinkedInstance : LinkedInstances){RootMotion.Accumulate(LinkedInstance-ConsumeExtractedRootMotion(InAlpha));}}if(PostProcessAnimInstance){RootMotion.Accumulate(PostProcessAnimInstance-ConsumeExtractedRootMotion(InAlpha));}return RootMotion;
}ps:
{/** The active animation graph program instance. */UPROPERTY(transient, NonTransactional)TObjectPtrUAnimInstance AnimScriptInstance;
}执行Animation Instance的ConsumeExtractedRootMotion,获取之前提取的RootMotion数据并返回。
FRootMotionMovementParams UAnimInstance::ConsumeExtractedRootMotion(float Alpha)
{if (Alpha ZERO_ANIMWEIGHT_THRESH){return FRootMotionMovementParams();}else if (Alpha (1.f - ZERO_ANIMWEIGHT_THRESH)){FRootMotionMovementParams RootMotion ExtractedRootMotion;ExtractedRootMotion.Clear();return RootMotion;}else{return ExtractedRootMotion.ConsumeRootMotion(Alpha);}
}获取之后,回到 UCharacterMovementComponent::TickCharacterPose方法,
void UCharacterMovementComponent::TickCharacterPose(float DeltaTime)
{if (CharacterOwner-IsPlayingRootMotion() || bWasPlayingRootMotion){// 获取Animation Instance的RootMotion数据 FRootMotionMovementParams RootMotion CharacterMesh-ConsumeRootMotion();if (RootMotion.bHasRootMotion){// bHasRootMotion此时为trueRootMotion.ScaleRootMotionTranslation(CharacterOwner-GetAnimRootMotionTranslationScale());RootMotionParams.Accumulate(RootMotion);}}....}
//CharacterMovementComponent对象中的RootMotionParams变量的定义,实为Animation Instance中的ExtractedRootMotion变量
/** Root Motion movement params. Holds result of anim montage root motion during PerformMovement(), and is overridden
* during autonomous move playback to force historical root motion for MoveAutonomous() calls */
UPROPERTY(Transient)
FRootMotionMovementParams RootMotionParams;而CharacterMovementComponent中的执行堆栈为:TickComponent-ControlledCharacterMove-PerfomMovement-TickCharacterPose。关于TickCharacterPose,在PerformMovement方法的执行如下:
void UCharacterMovementComponent::PerformMovement(float DeltaSeconds)
{// Prepare Root Motion (generate/accumulate from root motion sources to be used later)if (bHasRootMotionSources !CharacterOwner-bClientUpdating !CharacterOwner-bServerMoveIgnoreRootMotion){// Animation root motion - If using animation RootMotion, tick animations before running physics.if( CharacterOwner-IsPlayingRootMotion() CharacterOwner-GetMesh() ){ TickCharacterPose(DeltaSeconds);// Make sure animation didnt trigger an event that destroyed usif (!HasValidData()){return;}// For local human clients, save off root motion data so it can be used by movement networking code.if( CharacterOwner-IsLocallyControlled() (CharacterOwner-GetLocalRole() ROLE_AutonomousProxy) CharacterOwner- IsPlayingNetworkedRootMotionMontage() ){CharacterOwner-ClientRootMotionParams RootMotionParams;}}// Generates root motion to be used this frame from sources other than animation{SCOPE_CYCLE_COUNTER(STAT_CharacterMovementRootMotionSourceCalculate);CurrentRootMotion.PrepareRootMotion(DeltaSeconds, *CharacterOwner, *this, true);}// For local human clients, save off root motion data so it can be used by movement networking code.if( CharacterOwner-IsLocallyControlled() (CharacterOwner-GetLocalRole() ROLE_AutonomousProxy) ){CharacterOwner-SavedRootMotion CurrentRootMotion;}}// Apply Root Motion to Velocityif( CurrentRootMotion.HasOverrideVelocity() || HasAnimRootMotion() ){// Animation root motion overrides Velocity and currently doesnt allow any other root motion sourcesif( HasAnimRootMotion() ){// Convert to world space (animation root motion is always local)USkeletalMeshComponent * SkelMeshComp CharacterOwner-GetMesh();if( SkelMeshComp ){// Convert Local Space Root Motion to world space. Do it right before used by physics to make sure we use up to date // transforms, as translation is relative to rotation. RootMotionParams.Set( ConvertLocalRootMotionToWorld(RootMotionParams.GetRootMotionTransform(), DeltaSeconds) );}// Then turn root motion to velocity to be used by various physics modes.if( DeltaSeconds 0.f ){AnimRootMotionVelocity CalcAnimRootMotionVelocity(RootMotionParams.GetRootMotionTransform().GetTranslation(), DeltaSeconds, Velocity);Velocity ConstrainAnimRootMotionVelocity(AnimRootMotionVelocity, Velocity);if (IsFalling()){Velocity FVector(DecayingFormerBaseVelocity.X, DecayingFormerBaseVelocity.Y, 0.f);}}}} // Clear jump input now, to allow movement events to trigger it for next update.CharacterOwner-ClearJumpInput(DeltaSeconds);NumJumpApexAttempts 0;// change positionStartNewPhysics(DeltaSeconds, 0);if (!HasValidData()){return;}// Update character state based on change from movementUpdateCharacterStateAfterMovement(DeltaSeconds);if (bAllowPhysicsRotationDuringAnimRootMotion || !HasAnimRootMotion()){PhysicsRotation(DeltaSeconds);}// Apply Root Motion rotation after movement is complete.if( HasAnimRootMotion() ){const FQuat OldActorRotationQuat UpdatedComponent-GetComponentQuat();const FQuat RootMotionRotationQuat RootMotionParams.GetRootMotionTransform().GetRotation();if( !RootMotionRotationQuat.IsIdentity() ){const FQuat NewActorRotationQuat RootMotionRotationQuat * OldActorRotationQuat;MoveUpdatedComponent(FVector::ZeroVector, NewActorRotationQuat, true);}...// Root Motion has been used, clearRootMotionParams.Clear();}
}RootMotionParams中存储的Transfrom数据是位于Component Space坐标系下的,应用时需要转为Actor Space。
FTransform UCharacterMovementComponent::ConvertLocalRootMotionToWorld(const FTransform LocalRootMotionTransform, float DeltaSeconds)
{const FTransform PreProcessedRootMotion ProcessRootMotionPreConvertToWorld.IsBound() ? ProcessRootMotionPreConvertToWorld.Execute(LocalRootMotionTransform, this, DeltaSeconds) : LocalRootMotionTransform;const FTransform WorldSpaceRootMotion CharacterOwner-GetMesh()-ConvertLocalRootMotionToWorld(PreProcessedRootMotion);return ProcessRootMotionPostConvertToWorld.IsBound() ? ProcessRootMotionPostConvertToWorld.Execute(WorldSpaceRootMotion, this, DeltaSeconds) : WorldSpaceRootMotion;
}FTransform USkeletalMeshComponent::ConvertLocalRootMotionToWorld(const FTransform InTransform)
{// Make sure component to world is up to dateConditionalUpdateComponentToWorld();//Calculate new actor transform after applying root motion to this componentconst FTransform ActorToWorld GetOwner()-GetTransform();// Component Transform Actor Transformconst FTransform ComponentToActor ActorToWorld.GetRelativeTransform(GetComponentTransform());const FTransform NewComponentToWorld InTransform * GetComponentTransform();const FTransform NewActorTransform ComponentToActor * NewComponentToWorld;const FVector DeltaWorldTranslation NewActorTransform.GetTranslation() - ActorToWorld.GetTranslation();const FQuat NewWorldRotation GetComponentTransform().GetRotation() * InTransform.GetRotation();const FQuat DeltaWorldRotation NewWorldRotation * GetComponentTransform().GetRotation().Inverse();const FTransform DeltaWorldTransform(DeltaWorldRotation, DeltaWorldTranslation);return DeltaWorldTransform;
}
Root Motion的坐标系数据已经处理完成,经过了三个阶段:Bone SpaceComponent SpaceActor Space。然后便是UCharacterMovementComponent中的应用。MoveUpdatedComponent为UMovementComponent组件提供的方法。
bool UMovementComponent::MoveUpdatedComponentImpl( const FVector Delta, const FQuat NewRotation, bool bSweep, FHitResult* OutHit, ETeleportType Teleport)
{if (UpdatedComponent){const FVector NewDelta ConstrainDirectionToPlane(Delta);// 更新SceneComponent的Rotationreturn UpdatedComponent-MoveComponent(NewDelta, NewRotation, bSweep, OutHit, MoveComponentFlags, Teleport);}return false;
}
/**A SceneComponent has a transform and supports attachment* The component we move and update.* If this is null at startup and bAutoRegisterUpdatedComponent is true, the owning Actors root component will automatically be set as * our UpdatedComponent at startup.* see bAutoRegisterUpdatedComponent, SetUpdatedComponent(), UpdatedPrimitive*/
UPROPERTY(BlueprintReadOnly, Transient, DuplicateTransient, CategoryMovementComponent)
TObjectPtrUSceneComponent UpdatedComponent;如上,使用USceneComponent类型的UpdatedComponent进行实际Transfrom的更新。
对于RootMotion的应用,主要是在CharacterComponent每次Tick过程中,对角色移动的控制(ControlledCharacterMove方法),此时需要用到另一个组件UCharacterMovementComponent的TickCharacterPose方法中,将Component Space中的Root Motion Transfrom转换到Actor Space中。然后应用到SceneComponent中。