UE5 为啥原生的NotifyState写逻辑会有问题

发布于:2025-09-02 ⋅ 阅读:(18) ⋅ 点赞:(0)

UE5 为啥原生的NotifyState写逻辑会有问题

虚幻的UAnimNotifyState,也就是Montage中配置的通知条存在明显的问题,请勿用来写逻辑,做表现是可以的。

UE5.3 相关源码:

void UAnimInstance::TriggerAnimNotifies(float DeltaSeconds)
{
	SCOPE_CYCLE_COUNTER(STAT_AnimTriggerAnimNotifies);
	USkeletalMeshComponent* SkelMeshComp = GetSkelMeshComponent();

	// Array that will replace the 'ActiveAnimNotifyState' at the end of this function.
	TArray<FAnimNotifyEvent> NewActiveAnimNotifyState;
	NewActiveAnimNotifyState.Reserve(NotifyQueue.AnimNotifies.Num());

	TArray<FAnimNotifyEventReference> NewActiveAnimNotifyEventReference;
	NewActiveAnimNotifyEventReference.Reserve(NotifyQueue.AnimNotifies.Num());

	
	// AnimNotifyState freshly added that need their 'NotifyBegin' event called.
	TArray<const FAnimNotifyEvent *> NotifyStateBeginEvent;
	TArray<const FAnimNotifyEventReference *> NotifyStateBeginEventReference;

	for (int32 Index=0; Index<NotifyQueue.AnimNotifies.Num(); Index++)
	{
		if(const FAnimNotifyEvent* AnimNotifyEvent = NotifyQueue.AnimNotifies[Index].GetNotify())
		{
			// AnimNotifyState
			if (AnimNotifyEvent->NotifyStateClass)
			{
				int32 ExistingItemIndex = INDEX_NONE;

				if (ActiveAnimNotifyState.Find(*AnimNotifyEvent, ExistingItemIndex))
				{
					check(ActiveAnimNotifyState.Num() == ActiveAnimNotifyEventReference.Num());
					ActiveAnimNotifyState.RemoveAtSwap(ExistingItemIndex, 1, false); 
					ActiveAnimNotifyEventReference.RemoveAtSwap(ExistingItemIndex, 1, false);
				}
				else
				{
					NotifyStateBeginEvent.Add(AnimNotifyEvent);
					NotifyStateBeginEventReference.Add(&NotifyQueue.AnimNotifies[Index]);
				}

				NewActiveAnimNotifyState.Add(*AnimNotifyEvent);
				FAnimNotifyEventReference& EventRef = NewActiveAnimNotifyEventReference.Add_GetRef(NotifyQueue.AnimNotifies[Index]);
				EventRef.SetNotify(&NewActiveAnimNotifyState.Top());
				continue;
			}

			// Trigger non 'state' AnimNotifies
			TriggerSingleAnimNotify(NotifyQueue.AnimNotifies[Index]);
		}
	}

	// Send end notification to AnimNotifyState not active anymore.
	for (int32 Index = 0; Index < ActiveAnimNotifyState.Num(); ++Index)
	{
		const FAnimNotifyEvent& AnimNotifyEvent = ActiveAnimNotifyState[Index];
		const FAnimNotifyEventReference& EventReference = ActiveAnimNotifyEventReference[Index];
		if (AnimNotifyEvent.NotifyStateClass && ShouldTriggerAnimNotifyState(AnimNotifyEvent.NotifyStateClass))
		{
#if WITH_EDITOR
			// Prevent firing notifies in animation editors if requested 
			if(!SkelMeshComp->IsA<UDebugSkelMeshComponent>() || AnimNotifyEvent.NotifyStateClass->ShouldFireInEditor())
#endif
			{
				TRACE_ANIM_NOTIFY(this, AnimNotifyEvent, End);
				AnimNotifyEvent.NotifyStateClass->NotifyEnd(SkelMeshComp, Cast<UAnimSequenceBase>(AnimNotifyEvent.NotifyStateClass->GetOuter()), EventReference);
			}
		}
		// The NotifyEnd callback above may have triggered actor destruction and the tear down
		// of this instance via UninitializeAnimation which empties ActiveAnimNotifyState.
		// If that happened, we should stop iterating the ActiveAnimNotifyState array
		if (ActiveAnimNotifyState.IsValidIndex(Index) == false)
		{
			ensureMsgf(false, TEXT("UAnimInstance::ActiveAnimNotifyState has been invalidated by NotifyEnd. AnimInstance: %s, Owning Component: %s, Owning Actor: %s "), *GetNameSafe(this), *GetNameSafe(GetOwningComponent()), *GetNameSafe(GetOwningActor()));
			return;
		}
	}

	check(NotifyStateBeginEventReference.Num() == NotifyStateBeginEvent.Num());
	for (int32 Index = 0; Index < NotifyStateBeginEvent.Num(); Index++)
	{
		const FAnimNotifyEvent* AnimNotifyEvent = NotifyStateBeginEvent[Index];
		const FAnimNotifyEventReference * AnimNotifyEventReference = NotifyStateBeginEventReference[Index];
		if (ShouldTriggerAnimNotifyState(AnimNotifyEvent->NotifyStateClass))
		{
#if WITH_EDITOR
			// Prevent firing notifies in animation editors if requested 
			if(!SkelMeshComp->IsA<UDebugSkelMeshComponent>() || AnimNotifyEvent->NotifyStateClass->ShouldFireInEditor())
#endif
			{
				TRACE_ANIM_NOTIFY(this, *AnimNotifyEvent, Begin);
				AnimNotifyEvent->NotifyStateClass->NotifyBegin(SkelMeshComp, Cast<UAnimSequenceBase>(AnimNotifyEvent->NotifyStateClass->GetOuter()), AnimNotifyEvent->GetDuration(), *AnimNotifyEventReference);
			}
		}
	}

	// Switch our arrays.
	ActiveAnimNotifyState = MoveTemp(NewActiveAnimNotifyState);
	ActiveAnimNotifyEventReference = MoveTemp(NewActiveAnimNotifyEventReference);
	// Tick currently active AnimNotifyState
	for (int32 Index = 0; Index < ActiveAnimNotifyState.Num(); Index++)
	{
		const FAnimNotifyEvent& AnimNotifyEvent = ActiveAnimNotifyState[Index];
		const FAnimNotifyEventReference& EventReference = ActiveAnimNotifyEventReference[Index];
		if (ShouldTriggerAnimNotifyState(AnimNotifyEvent.NotifyStateClass))
		{
#if WITH_EDITOR
			// Prevent firing notifies in animation editors if requested 
			if(!SkelMeshComp->IsA<UDebugSkelMeshComponent>() || AnimNotifyEvent.NotifyStateClass->ShouldFireInEditor())
#endif
			{
				TRACE_ANIM_NOTIFY(this, AnimNotifyEvent, Tick);
				AnimNotifyEvent.NotifyStateClass->NotifyTick(SkelMeshComp, Cast<UAnimSequenceBase>(AnimNotifyEvent.NotifyStateClass->GetOuter()), DeltaSeconds, EventReference);
			}
		}
	}
}

问题一:同帧下的Begin 与End,它是按For循环执行的,没有按照时间顺序执行。如下图的NotifyStateA与NotifyStateB,他们在第N帧的时候,A的End会要比B的Start先执行,但是在时间的顺序上,B的Begin时间是早于A的End的,如果依赖时间顺序去执行,某些代码逻辑时候,当卡的时候,逻辑必有问题。
在这里插入图片描述
问题二:它没有上下执行顺序的问题,并不是同一时间下上面的先执行。
问题三:同一帧里面有NotifyState的Start 与 End,那么End会在下一帧执行,而不是同帧下执行
在这里插入图片描述