Chaos Vehicleにアタッチされているドライバーキャラが自車のソケットに対してIKなどで手足を補正をする際に、車側のアニメーションで動くソケット位置への追従が遅延する問題の解消方法について

Chaos Vehicleで動作している車にドライバーキャラクターをアタッチしている状態で、車側でアニメーションするステアリングに設定されたソケットに対して、ドライバーキャラの手をIKで追従させる仕組みを実装しています。

<br/>

この時、車側では物理挙動の影響を自身のアニメーションに即時反映させるために、Post PhysicsかつMeshのComponentTagsに"ForceFinalizeAnimationUpdate"を設定するようにしているのですが、このComponentTagsの設定を行うと、ドライバーキャラ側のIKによるソケットへの追従が車側のステアリングのアニメーションに対して遅延するという問題が発生しています。

この時、ステアリングのアニメーションが発生しない場合は遅延しません。

<br/>

また、ForceFinalizeAnimationUpdateを削除すると、ドライバーキャラ側のソケットへの追従はステアリングのアニメーションの有無に関わらず遅延しなくなりますが、そうすると元々解決したかった車側の物理挙動の影響の反映が遅延してしまいます。

<br/>

この両者の問題を両立させる、すなわち車側の物理挙動のアニメーションへの反映も、キャラ側のソケット位置の追従のどちらも遅延しないような解決方法が存在するのかをお伺いしたいです。

<br/>

[Attachment Removed]

再現手順

    • Chaos Vehicle
      • Post Physics
      • MeshのComponentTagsに"ForceFinalizeAnimationUpdate"を設定している
    • ステアリング
      • キー入力に連動してアニメーションで左右に回転
        • 急加速時に車体ごとウィリー動作する挙動もアニメーションに含まれる
      • ドライバーの手を追従させるためのソケットが設定されている
        • アニメーションに追従する

  • ​ドライバーキャラ
    • 車にアタッチされている
    • AnimNode(TwoBoneIK)でIK処理することでステアリング位置に手を追従させている​
    • 自車のステアリングのソケット位置をNativeUpdateAnimationで取得している

発生している現象

  • ​車のステアリング回転や急加速時のウィリー動作などのAnimationが発生すると、ソケットへの追従が遅延する
    • ForceFinalizeAnimationUpdateを設定すると、車側の問題は解決するが、ドライバー側に問題が発生する
    • ForceFinalizeAnimationUpdateを削除すると、ドライバー側の問題は解決するが、車側に問題が発生する

[Attachment Removed]

ForceFinalizeAnimationUpdateというフラグはエンジン中に見つけることができませんでした。

恐らくエンジンにカスタマイズを施されていると思われます。名前からはFinalizeAnimationUpdateのタイミングを制御しているのではないかと推察しています。

FinalizeAnimationUpdateは子供の更新などが行われており、ドライバーが正しく追従するようになっているとの事なので大きな問題は無いかと思いますがもしかすると動作に影響を与えている可能性があるためどのようなことを行っているのか具体的にご共有いただけると助かります。

ドライバーキャラのソケット位置への​追従ですが

ドライバーキャラのUpdateAnimationの処理タイミング(車両のFinalizeAnimationUpdateの前か後か)とソケット位置の参照方法はどのように行われていますか?

ForceFinalizeAnimationUpdateの有無でこの処理タイミングが変化し参照しているトランスフォームが変ってしまっているのではないかと思います。

お気づきの点はございませんでしょうか?​

[Attachment Removed]

回答ありがとうございます。

改めてこちらの車側の対応について調査しました所、ForceFinalizeAnimationUpdateタグを入れることで、本来ならばFinalizeAnimationUpdateが実行されない設定の場合でも、強制的にFinalizaAnimationUpdateを実行するような、独自のエンジンカスタマイズが入っていました。

  • Engine/Source/Runtime/Engine/Private/Components/SkeletalMeshComponent.cpp
  • USkeletalMeshComponent::PostAnimEvaluation() 内でFinalizeAnimationUpdateの実行条件を以下のように変更しています。
#if WITH_EDITOR	
		// If we have no physics to blend or in editor since there is no physics tick group, we are done
		if (!ShouldBlendPhysicsBones() || GetWorld()->WorldType == EWorldType::Editor || ComponentHasTag(TEXT("ForceFinalizeAnimationUpdate")))
		{
			// Flip buffers, update bounds, attachments etc.
			FinalizeAnimationUpdate();
		}
#else
		if (!ShouldBlendPhysicsBones() || ComponentHasTag(TEXT("ForceFinalizeAnimationUpdate")))
		{
			// Flip buffers, update bounds, attachments etc.
			FinalizeAnimationUpdate();
		}
#endif
 
 

車側担当者によると、上記変更は以前Suzuki様に回答していただいた以下の件を受けて対応したとのことでした。

[Content removed]

ドライバーキャラのソケット位置への追従に関しては、NativeUpdateAnimation内で車側からGetSocketTransform()して(WorldSpace)、このTransformのLocationとRotationをそのまま使用しています。

WorldSpaceで取得していますが、車自体には追従しているので高速移動中でも車の移動に対して遅れるということはありません。

FinalizeAnimationUpdateを実行することで、ドライバーキャラ側が取得したタイミングよりも後で車側のステアリングや車体のアニメーションが更新されるようになっているので、このようなずれが発生しているという認識です。

適切な対処方法ではないですが、実験的にAnimGraph実行中に車側のソケットの情報を強引に取得することや、ドライバー側のComponentTransformの反映遅れも別途補正することで、追従が遅延しなくなる頻度が高くなることは確認していますが、車側の処理との同期は取られていないので時々遅れることがありますし、そもそも安全な手段ではないため導入はしていません。

エンジンカスタマイズが入っていることを私が認識していなかったので、このようなケースでFinalize処理を待ったりドライバー側の処理タイミングを遅らせる方法があるのかも知れないと思って質問いたした次第です。

[Attachment Removed]

変更点について承知しました。

ステアリングのソケット位置の取得タイミングが車両のFinalizeAnimationUpdateより前に行われているため問題が発生していると考えられます。

対処としてはドライバーのUpdateAnimationをそれより後に行うようにPrerequisiteを設定するか、ステアリングのソケット位置が複雑なブレンドなどを行っていないのであれば、GetBoneTransformでソケットの基点ボーントランスフォームを即時評価して変換したり、AnimSequence内に保存しておいたカーブデータを即時評価して利用する方法などが考えられそうです。

前者の方法はPostPhysics以降に依存関係のあるコンポーネントが並んでしまいCPU使用率が低下してしまうかもしれません。

[Attachment Removed]

回答ありがとうございます。

まずは、ソケットの起点ボーントランスフォームの即時評価で対応しようとしましたが、遅延は解消しませんでした。

疑似コードは以下のような感じです。

// bone_name : socket_nameの親ボーン
// 実ボーンなので内部でGetBoneTransformが呼ばれている(デバッガーで確認済み)
FTransform BoneTransform = Mesh->GetSocketTransform(TEXT("bone_name"));
 
// ソケットなのでSocketLocalTransformが直接返される(デバッガーで確認済み)
FTransform SocketTransform = Mesh->GetSocketTransform(TEXT("socket_name")), RTS_ParentBoneSpace);
 
// 乗算でカレント生成
FTransform CurTransform = SocketTransform * BoneTransform;

実ボーンだとGetSocketTransformの内部でGetBoneTransformが呼ばれているので、起点ボーンのTransformの計算もGetSocketTransformの呼び出しでも問題ないのかと思いましたが、うまくいっていません。

何か不足していることやまずい点がありますでしょうか?

(追記1)

実ボーンのTransform取得をGetBoneIndex + GetBoneTransformで行ってみましたが挙動は変わりませんでした。

(追記2)

AddTickPrerequisiteComponentを使ってVehicleの後まで待つようにした結果、遅延は発生しなくなりました。ただし、そこそこ長めのWaitTaskが発生しているのも確認できました。

[Attachment Removed]

GetSocketTransformはアニメーションの結果を返しているだけなので即時評価ではありません。更新タイミングはFinalizeAnimationUpdateです。

即時評価はUAnimSequenceのGetAnimationPoseにアニメーションの時間を与えて評価します。

ソケット位置のアニメーションが単体のAnimSequenceと時間で評価できるのであれば実装の例として以下の様なコードが利用できます。

※前のご返答ではUAnimSequence::GetBoneTransformを紹介させていただきましたが、この関数だとボーンローカルスペースの1ボーン分の情報しか得られないので全体を取得できるGetAnimationPoseを使います。

このコードでは過不足があるかと思いますが、エンジン内にも似たようなコードがあるのでご参照いただけますとよろしいかと思います。

bool UMyAnimBlueprintFunctionLibrary::EvalBoneTransformImmidiate(
    const UAnimSequence* Anim,
    const USkeletalMesh* SkeletalMesh,
    FName BoneName,
    double TimeSeconds,
    FTransform& OutBoneTransformInComponentSpace)
{
    if (!Anim || !SkeletalMesh) { return false; }
 
    const USkeleton* TargetSkeleton = SkeletalMesh->GetSkeleton();
    const FReferenceSkeleton& RefSkel = SkeletalMesh->GetRefSkeleton();
    const int32 TargetSkelBoneIndex = RefSkel.FindBoneIndex(BoneName);
    if (TargetSkelBoneIndex == INDEX_NONE) { return false; }
 
    FBoneContainer RequiredBones; 
	TArray<FBoneIndexType> RequiredBoneIndexArray;
	RequiredBoneIndexArray.AddUninitialized(RefSkel.GetNum());
	for (int32 BoneIndex = 0; BoneIndex < RequiredBoneIndexArray.Num(); ++BoneIndex)
	{
		RequiredBoneIndexArray[BoneIndex] = BoneIndex;
	}
	RequiredBones.InitializeTo(RequiredBoneIndexArray, UE::Anim::ECurveFilterMode::DisallowAll, *SkeletalMesh);
 
	FCompactPose CompactPose;
    CompactPose.SetBoneContainer(&RequiredBones);
    CompactPose.ResetToRefPose(RequiredBones);
 
    FBlendedCurve DummyCurve;
    UE::Anim::FStackAttributeContainer DummyAttributes;
	FAnimationPoseData PoseData(CompactPose, DummyCurve, DummyAttributes);
 
	Anim->GetAnimationPose(PoseData, FAnimExtractContext(TimeSeconds));
    TArray<FTransform> TransformsInComponentSpace;
    TransformsInComponentSpace.SetNum(CompactPose.GetNumBones());
    FAnimationRuntime::FillUpComponentSpaceTransforms(RefSkel, PoseData.GetPose().GetBones(), TransformsInComponentSpace);
 
    OutBoneTransformInComponentSpace = TransformsInComponentSpace[TargetSkelBoneIndex];
 
    return true;
}

[Attachment Removed]

回答ありがとうございます。

即時評価の件は私の理解が追い付いていませんでしたが、今回の回答で理解できました。

問題となっているVehicle側の部位アニメーションは、プレイヤーの入力に応じて舵角が変わるステアリングと、車速に応じて回転速度が変わるペダルが該当しており、AnimationSequenceではないため、直接的には応用はできなさそうです。

ただ、車側のアルゴリズムを再現する形で即時評価ができそうな気もするので、そちらでの方法も試してみます。

また、Prerequisiteの方では問題解消にはなっていますので、質問は終わりにしたいと思います。

対応ありがとうございました。

[Attachment Removed]