この記事では、 UE4 におけるアニメーション通知AnimNotify
(アニメーション通知ステータスAnimNotifyState
)のソース コード分析と問題点の概要をまとめます。
1. アニメーション通知の概要
アニメーション通知は、主にUE4 のアニメーション ( Animation
) またはアニメーション モンタージュ ( ) 上で設定されるAnimMontage
通知で、アニメーションの実行によって通知がトリガーされます。通知には次の 2 種類があります。
AnimNotify
(アニメーション通知) – 期間なし、Notify
メソッドのみ、詳細を参照AnimNotify.h
AnimNotifyState
(アニメーション通知ステータス) – 期間 (TotalDuration
) があり、NotifyBegin
、NotifyTick
、NotifyEnd
メソッドがあります。詳細については、 を参照してください。AnimNotifyState.h
についてAnimNotifyState
、公式の説明には次のように書かれています。
- Notify Begin Eventから開始することが保証されています
- Notify End Eventから確実に終了する
- 通知ティックが通知開始イベントと通知終了イベントの間にあることが保証されます。
- Anim Notify の順序 (通常または状態) は保証できません。2 つの AnimNotifyStates が端から端まで接続されている場合、前の Notify の End が次の Notify の Begin より前にあるという保証はありません。ここでは、他の通知に関連しない個別の操作のみを実行する必要があります。オリジナル:
異なる Anim Notify (通常または状態) 間の順序は保証されません。
2 つの Anim Notify ステートを並べて配置した場合、最初の Anim Notify ステートが次の Anim Notify ステートが開始する前に終了するという保証はありません。
これは、他の通知に依存しない個別のアクションにのみ使用してください。
つまり、2 つの AnimNotifyState A と B は、実行順序が次であることを保証できません。
A.開始 --> A.終了 --> B.開始 --> B.終了
代わりに、次のようになります。
A.開始 --> B.開始 --> A.終了 --> B.終了
さらに、次の点にも注目してください。
-
アニメーション通知はデフォルトで DedicateServer、メイン制御端末、およびシミュレーション セグメントで実行されます。DS上で実行したくない場合は、以下のオプションのチェックを外してください。
-
同じモンタージュまたはアニメーションの通知の場合、ポインターは同じであるため、データが通知に保存され、2 つの同一のロールが同じモンタージュを同時にリリースすると、2 つの通知は同じデータ部分を変更します。
-
同じモンタージュ上の同じタイプの複数の通知は異なるポインタを持ちます。GetName() から出てくるのは、次のように xxx_C_1、xxx_C_2 です。
2. アニメーション通知のトリガー
上記のアニメーション通知メソッドのトリガーは主にUAnimInstance::TriggerAnimNotifies
から(後で説明する他のメソッドもあります)。
2.1 TriggerAnim ソースコード解析を通知する
Tick の( type)AnimInstance
は、このフレームのアニメーション通知(AnimNotify、AnimNotifyState)を追加し、 TriggerAnimNotifyでこのフレームのすべてのアニメーション通知を実行します。実行順序は次のとおりです。NotifyQueue
AnimNotifies
FAnimNotifyQueue
- 全部
AnimNotify
_Notify
- 以前のものはすべてまだ存在していますが、このフレームはもう
AnimNotifyState
存在しませんNotifyEnd
- 前のフレームは存在せず、このフレームに新たに追加されました
AnimNotifyState
。NotifyBegin
- このフレーム
AnimNotifyState
すべてNotifyTick
(つまり、Begin の同じフレームがティックされます)
についてAnimNotify
は、 しかないNotify
ので、比較的単純なので説明しません。次に、AnimNotifyState
分析に焦点を当てます。
AnimNotifyState
トリガー コアは 4 つのアレイです。
ActiveAnimNotifyState
: 前のフレームをアクティブに記録しますAnimNotifyState
(Act と呼ばれます)。NotifyQueue.AnimNotifies
: このフレームのモンタージュ上のすべての通知を記録します (通知と通知状態を含む)NewActiveAnimNotifyState
(部分的): 1 と 2 を通じて、このフレームの新しい NotifyStates (略して NewAct) がどれであるかを計算します。NotifyStateBeginEvent
(部分的): 新しい NotifyState をトリガーするためにのみ使用されます。NotifyBegin
2.2 TriggerAnim プロセス分析の完了を通知します
1.NewActiveAnimNotifyStateを作成する
// Array that will replace the 'ActiveAnimNotifyState' at the end of this function.
TArray<FAnimNotifyEvent> NewActiveAnimNotifyState;
NewActiveAnimNotifyState.Reserve(NotifyQueue.AnimNotifies.Num());
2.最も重要なステップ
このフレームにまだあるものを削除しActiveAnimNotifyState
、残りは前のフレームにありこのフレームにないもので、後続のトリガーに使用されます。前のNotifyEnd
フレーム
になく、このフレームにあるものを置きます。 Trigger ; すべてNotifyStateBeginEvent
をトリガーする;NotifyBegin
AnimNotify
// AnimNotifyState freshly added that need their 'NotifyBegin' event called.
TArray<const FAnimNotifyEvent *> NotifyStateBeginEvent;
for (int32 Index=0; Index<NotifyQueue.AnimNotifies.Num(); Index++)
{
if(const FAnimNotifyEvent* AnimNotifyEvent = NotifyQueue.AnimNotifies[Index].GetNotify())
{
// AnimNotifyState
if (AnimNotifyEvent->NotifyStateClass)
{
if (!ActiveAnimNotifyState.RemoveSingleSwap(*AnimNotifyEvent, false))
{
// Queue up calls to 'NotifyBegin', so they happen after 'NotifyEnd'.
NotifyStateBeginEvent.Add(AnimNotifyEvent);
}
NewActiveAnimNotifyState.Add(*AnimNotifyEvent);
continue;
}
// Trigger non 'state' AnimNotifies
TriggerSingleAnimNotify(AnimNotifyEvent);
}
}
3. Act 内のすべての NotifyEnd をトリガーする
// Send end notification to AnimNotifyState not active anymore.
for (int32 Index = 0; Index < ActiveAnimNotifyState.Num(); ++Index)
{
const FAnimNotifyEvent& AnimNotifyEvent = ActiveAnimNotifyState[Index];
if (AnimNotifyEvent.NotifyStateClass && ShouldTriggerAnimNotifyState(AnimNotifyEvent.NotifyStateClass))
{
TRACE_ANIM_NOTIFY(this, AnimNotifyEvent, End);
AnimNotifyEvent.NotifyStateClass->NotifyEnd(
SkelMeshComp,
Cast<UAnimSequenceBase>(AnimNotifyEvent.NotifyStateClass->GetOuter()));
}
// blabla ActiveAnimNotifyState 是否已经失效检查
}
4. NotifyStateBeginEvent ですべての NotifyBegin をトリガーする
// Call 'NotifyBegin' event on freshly added AnimNotifyState.
for (const FAnimNotifyEvent* AnimNotifyEvent : NotifyStateBeginEvent)
{
if (ShouldTriggerAnimNotifyState(AnimNotifyEvent->NotifyStateClass))
{
TRACE_ANIM_NOTIFY(this, *AnimNotifyEvent, Begin);
AnimNotifyEvent->NotifyStateClass->NotifyBegin(
SkelMeshComp,
Cast<UAnimSequenceBase>(AnimNotifyEvent->NotifyStateClass->GetOuter()),
AnimNotifyEvent->GetDuration());
}
}
5. ムーブテンポ
// Switch our arrays.
ActiveAnimNotifyState = MoveTemp(NewActiveAnimNotifyState);
Act はメンバー変数であり、NewAct はローカル変数であるため、Act に NewAct を与えます。
6. アクト内のすべての NotifyTick をトリガーします
// Tick currently active AnimNotifyState
for(const FAnimNotifyEvent& AnimNotifyEvent : ActiveAnimNotifyState)
{
if (ShouldTriggerAnimNotifyState(AnimNotifyEvent.NotifyStateClass))
{
TRACE_ANIM_NOTIFY(this, AnimNotifyEvent, Tick);
AnimNotifyEvent.NotifyStateClass->NotifyTick(
SkelMeshComp,
Cast<UAnimSequenceBase>(AnimNotifyEvent.NotifyStateClass->GetOuter()),
DeltaSeconds);
}
}
さらに、次の点にも注目してください。
NotifyEnd
トリガーするかどうかの判断方法は、A通知の前のフレームがまだ存在し、今回のフレームが存在しないかを判断することなのでNotifyEnd
、通知の最後のフレームに見える場合はトリガーされません。エンドツーエンドは間違いなく次のものは最初に始まり、次のフレームの前にあるものは終了します。エンドツーエンドで接続する必要さえありませんが、最初と最後は同じフレーム内にあります(図のように)同様に、次のフレームが最初に開始され、次のフレームの前のフレームが終了します。
3. アニメーション通知に関する問題の概要
詳細を見る