問題の説明
UE4 では、パフォーマンスの最適化のため、多くの場合、メッシュ ティックは専用サーバー (つまり ) でオフになるため、OnlyTickMontagesWhenNotRendered
サーバー上ではTPoseが維持され、クライアント (つまりAlwaysTickPoseAndRefreshBones
) ではメッシュ ポーズのみがリアルタイムで更新されます。
if (GetNetMode() == NM_DedicatedServer && GetMesh())
{
GetMesh()->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered;
}
これは問題を引き起こします: サーバー上のメッシュ ポーズを知る必要があるとき (ソケットの位置を計算するときなど)、サーバー上でマジック ボールを解放し、マジック ボールの誕生トランスフォームが特定の時点での魔術師のハンドが計算されます。エラーが発生しました (サーバー上の TPose であるため)。
簡単な解決策は、ルートポイントに応じてオフセットを設定し、そのオフセットを使用して手の位置から魔球を離す効果を実現することですが、この実装では、どのような姿勢やアクションであっても、魔法のボールのスポーン位置は変化しません。魔球が固定され性能が悪くなる。。
最善の方法は、サーバーが生成される前にメッシュ ポーズに 1 回チェックを入れることです。つまり、TPose から正しい (クライアントと一致する) ポーズにチェックを入れることです。UE4 AnswerHubを参照すると、Tick メソッドは次のとおりです。
if (GetMesh())
{
GetMesh()->TickPose(0.f, false);
GetMesh()->RefreshBoneTransforms();
}
パフォーマンスの観点から見ると、サーバーの現在のフレームのポーズを計算するという目的は確かに達成できますが、新しい問題も発見されました ( TriggerAnimNotify 再帰呼び出しの問題)。
TriggerAnim再帰呼び出しの問題を通知します
[UE4] TriggerAnimNotifying 再帰呼び出しの問題 を参照してください。つまり、NotifyBegin または NotifyEnd の処理で、関数をトリガーするメソッドが呼び出されると、このフレーム内で Begin が 1 回、End が 2 回トリガーされるTriggerAnimNotifies
ように見えます。 Tick Pose への呼び出しをもう一度見てみましょう。NotifyState
GetMesh()->TickPose(0.f, false);
GetMesh()->RefreshBoneTransforms();
TickPose
ソースコードの一部:
void USkeletalMeshComponent::TickPose(float DeltaTime, bool bNeedsValidRootMotion)
{
Super::TickPose(DeltaTime, bNeedsValidRootMotion);
if (ShouldTickAnimation())
{
// ...
// 计算 DeltaTimeForTick;
TickAnimation(DeltaTimeForTick, bNeedsValidRootMotion);
// ...
}
// ...
}
TickAnimation
ソースコードの一部:
void USkeletalMeshComponent::TickAnimation(float DeltaTime, bool bNeedsValidRootMotion)
{
//...
if (SkeletalMesh != nullptr)
{
// We're about to UpdateAnimation, this will potentially queue events that we'll need to dispatch.
bNeedsQueuedAnimEventsDispatched = true;
// We update linked instances first incase we're using either root motion or non-threaded update.
// This ensures that we go through the pre update process and initialize the proxies correctly.
for (UAnimInstance* LinkedInstance : LinkedInstances)
{
// Sub anim instances are always forced to do a parallel update
LinkedInstance->UpdateAnimation(DeltaTime * GlobalAnimRateScale, false, UAnimInstance::EUpdateAnimationFlag::ForceParallelUpdate);
}
if (AnimScriptInstance != nullptr)
{
// Tick the animation
AnimScriptInstance->UpdateAnimation(DeltaTime * GlobalAnimRateScale, bNeedsValidRootMotion);
}
if(ShouldUpdatePostProcessInstance())
{
PostProcessAnimInstance->UpdateAnimation(DeltaTime * GlobalAnimRateScale, false);
}
/**
If we're called directly for autonomous proxies, TickComponent is not guaranteed to get called.
So dispatch all queued events here if we're doing MontageOnly ticking.
*/
if (ShouldOnlyTickMontages(DeltaTime))
{
ConditionallyDispatchQueuedAnimEvents();
}
}
}
ConditionallyDispatchQueuedAnimEvents
アニメーション通知がトリガーされるため、トリガーしたくない場合は DeltaTime に 0 を渡す必要があり、現在のフレームが必要な場合は 0 も渡す必要があります。
RefreshBoneTransforms
ソースコードの一部:
void USkeletalMeshComponent::RefreshBoneTransforms(FActorComponentTickFunction* TickFunction)
{
// ...
if (TickFunction == nullptr && ShouldBlendPhysicsBones())
{
FinalizeBoneTransform();
}
}
FinalizeBoneTransform
これが呼び出され(でこの変数が true に設定されているとConditionallyDispatchQueuedAnimEvents()
判断されます)、最後に が呼び出され、MoveTemp の前に別の End (つまり、1 つの Begin と 2 つの End) がトリガーされます。bNeedsQueuedAnimEventsDispatched
TickAnimation
TriggerAnimNotifies
解決
この問題を回避するには、次のように呼び出すことができます。これにより、メッシュ ポーズの 1 フレームを更新する効果も得られます。
if (SkelMeshComponent->AnimScriptInstance)
{
// Tick the animation
SkelMeshComponent->AnimScriptInstance->UpdateAnimation(0.f, false);
}
SkelMeshComponent->RefreshBoneTransforms();
ただし、UE 自身のソース コードには次のコードも存在します (この問題が考慮されていないのか、または 2 回終了しても問題がないのかはわかりません)。
void USkeletalMeshComponent::InitAnim(bool bForceReinit)
{
// ...
//...
{
TickAnimation(0.f, false);
RefreshBoneTransforms();
}
// ...
}