【UE4】TriggerAnimNotifies 递归调用问题

  AnimNotifyState 的 Trigger 核心是三个数组:

  1. ActiveAnimNotifyState: 记录上一帧在 Active 的 NotifyState(简称 Act)
  2. NotifyQueue.AnimNotifies: 记录这一帧 Montage 上所有 Notify(包括 Notify 和 NotifyState)
  3. NewActiveAnimNotifyState(局部变量): 通过 1 和 2 计算出有哪些是这一帧新的 NotifyState(简称 NewAct)
  4. NotifyStateBeginEvent(不太重要,局部):只用来触发新的 NotifyState 的 Begin

  TriggerAnimNotifies (在这里进行动画通知 AnimNotify、动画通知状态 AnimNotifyState 的 Begin、End、Tick 的触发)里的流程如下:

1. 创建 NewActiveAnimNotifyState

   创建 NewActiveAnimNotifyState 并设为 NotifyQueue.AnimNotifies 的 大小(NotifyQueue.AnimNotifies 是每帧算这一帧动画上有哪些动画通知并存起来的)

// Array that will replace the 'ActiveAnimNotifyState' at the end of this function. 
TArray<FAnimNotifyEvent> NewActiveAnimNotifyState; 
NewActiveAnimNotifyState.Reserve(NotifyQueue.AnimNotifies.Num());
2. 添加 NotifyStateBeginEvent
// 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);
    }
}

   遍历这一帧所有的 Notify(从 NotifyQueue.AnimNotifies 中)

  1. 如果一个 NotifyState 在 Act 里(即上一帧在 Active),那就不会添加到 NotifyStateBeginEvent,反之则会添加,且把它从 Act 中移除;
  2. 把所有的 NotifyState 添加到 NewAct 里边
  3. 触发所有 non ‘state’ Notify
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. MoveTemp
// Switch our arrays. 
ActiveAnimNotifyState = MoveTemp(NewActiveAnimNotifyState);

   把 NewAct 给 Act,因为 Act 是成员变量,NewAct 是局部变量

Note

   由于在 NotifyBegin 和 NotifyEnd 之后才进行 Act 的刷新,那么如果在 NotifyBegin 和 NotifyEnd 的流程中,触发了 TriggerAnimNotifies,即一帧内 TriggerAnimNotifies 递归调用了,那么 Act 没有刷新,还是原来的,那么原来的 Act 里的 Notify 就会再次 End,导致一个 Notify 一帧内一次 Begin,两次 End,即 End 的逻辑会触发两次。
   发现这种问题出现的情况是,在 NotifyBegin里调用了:

if (GetMesh())
{
    
    
	GetMesh()->TickPose(GetWorld()->GetDeltaSeconds(), false);
	GetMesh()->RefreshBoneTransforms();
}

   而这里会调用 TriggerAnimNotifies 导致递归调用。

猜你喜欢

转载自blog.csdn.net/Bob__yuan/article/details/113997897