日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

UE5: UpdateOverlap - 从源码深入探究UE的重叠触发

發布時間:2024/1/16 windows 46 coder
生活随笔 收集整理的這篇文章主要介紹了 UE5: UpdateOverlap - 从源码深入探究UE的重叠触发 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

出于工作需要和個人好奇,本文對UE重疊事件更新的主要函數UpdateOverlaps從源碼的角度進行了詳細的分析,通過閱讀源碼,深入理解重疊事件是如何被觸發和更新的。

解決問題

閱讀本文,你將得到至少以下問題的答案:

  1. BeginComponentOverlap和EndComponentOverlap事件是如何被觸發的?

  2. UE是如何保存和管理組件之間的碰撞的?

  3. SetActorEnableCollision是如何起到作用的?

  4. UE如何處理不同Actor,或者同一個Actor里重復碰撞的情況?

以上只是提出了幾個很基礎的問題,隨著源碼的不斷解析,還會有更多新的問題會被提出。

說是深入探究,更像是筆者個人的學習筆記。其中一些筆者認為本應被了解的細節不會被提起,請讀者至少對UE的重疊機制有基礎的理解。


重疊更新的入口:USceneComponent::UpdateOverlaps

Queries world and updates overlap tracking state for this component

當一個組件需要更新當前重疊狀態時,就會調用這個函數。

這個函數定義在USceneComponent,表明只有場景組件的子類才能調用該函數。并且它不是一個虛函數,更新重疊相關的具體實現放在一個叫``UpdateOverlapsImpl的虛函數中。因此可以將UpdateOverlaps視作為重疊更新的總入口,然后調用子類的UpdateOverlapsImpl`從而執行具體的更新邏輯。

觀察該函數的聲明:

bool UpdateOverlaps(const TOverlapArrayView* PendingOverlaps = nullptr, bool bDoNotifies = true, const TOverlapArrayView* OverlapsAtEndLocation = nullptr);

其中,TOverlapArrayView就是經過了typedef的TArrayView<const FOverlapInfo>.

FOverlapInfo是對FHitResult的一個簡單封裝,FHitResult相信大家都很熟悉了,通過使用FHitResult,我們可以很輕松的獲得本次碰撞查詢中碰撞到的組件,以及碰撞的各種信息,例如碰撞坐標,法線等等。

接下來對參數列表中三個參數進行講解,這幾個參數還是挺重要的,后面會反復使用到這三個參數。

NewPendingOverlaps

An ordered list of components that the MovedComponent overlapped during its movement (eg. generated during a sweep). Only used to add potentially new overlaps.
Might not be overlapping them now.

移動組件在移動過程中重疊的有序組件列表(例如:在掃描過程中生成)。僅用于添加潛在的新重疊。

說人話就是,本次碰撞查詢中檢測到的將要碰到的重疊。之后在UpdateOverlapsImpl中,將會使用該數組調用BeginComponentOverlap.

值得一提的是,即使我們當前的組件(后續我們就叫它Self組件吧)已經在其他組件的重疊里了,此時如果有移動行為的話,該數組仍會把已經重疊的組件保存進去,至于會不會重復觸發BeginOverlap,后續當然有相關的邏輯處理,這里先按下不表。

如果當前沒有移動,只是簡單的對組件進行了旋轉,那么這個數組將會是空的,可以查閱UPrimitiveComponent::MoveComponentImpl, 其中有這么一段代碼:

				TInlineOverlapInfoArray OverlapsAtEndLocation;
				bool bHasEndOverlaps = false;
				if (bRotationOnly)
				{
					bHasEndOverlaps = ConvertRotationOverlapsToCurrentOverlaps(OverlapsAtEndLocation, OverlappingComponents);
				}
				else
				{
					bHasEndOverlaps = ConvertSweptOverlapsToCurrentOverlaps(OverlapsAtEndLocation, PendingOverlaps, 0, GetComponentLocation(), GetComponentQuat());
				}
				TOverlapArrayView PendingOverlapsView(PendingOverlaps);
				TOverlapArrayView OverlapsAtEndView(OverlapsAtEndLocation);
				UpdateOverlaps(&PendingOverlapsView, true, bHasEndOverlaps ? &OverlapsAtEndView : nullptr);

有個bRotationOnly變量,如果只有旋轉的話,不會對PendingOverlaps進行賦值。

也就是說,組件只做原地旋轉的話,是不會有新的重疊開始事件的。

OverlapsAtEndLocation

If non-null, the given list of overlaps will be used as the overlaps for this component at the current location, rather than checking for them with a scene query.
Generally this should only be used if this component is the RootComponent of the owning actor and overlaps with other descendant components have been verified.

(機翻)如果非空,則給定的重疊列表將用作該組件在當前位置的重疊,而不是使用場景查詢來檢查它們。
一般來說,只有當這個組件是擁有Actor的RootComponent,并且與其他子組件的重疊已經被驗證時,才應該使用這個組件。

說人話就是,這個數組將會存有Self組件當前位置(查詢末端位置)的所有重疊,并且只有self組件是Actor的根組件時才應該使用這個數組。

bDoNotifies

True to dispatch being/end overlap notifications when these events occur.

用于判斷是否觸發重疊事件。例如,當bDoNotifies為false時,OnBeginComponentOverlap、OnBeginActorComponentOverlap、OnEndComponentOverlap等相關委托都不會被觸發。

目前看來OverlapsAtEndLocation和NewPendingOverlaps的關系挺微妙的,隨著后面代碼的分析,他們的作用會越來越清晰。

調用該函數的幾種情況

那么什么情況下需要更新組件的重疊呢?

很明顯,當組件產生任何Transform的變換時,都應該更新重疊以防止漏過任何一個事件。

除此以外,當組件的碰撞狀態發生變化時,也應該及時更新重疊。筆者經過對一個Character進行不嚴謹的調試,找到了幾個比較典型的調用方式:

1. UCharacterMovementComponent::PerformMovement

該函數是移動組件進行移動的主要函數,該函數會結合碰撞查詢,計算出組件移動的目標位置,調用棧如下:

也就是說當你控制角色,使用移動組件進行移動時,每tick都會對重疊進行一次更新。

2.UPrimiticeComponent::MoveComponent

該函數用于更新Actor的transform時調用。例如SetActorRotation,SetActorPosition等函數,最終都會調用到MoveComponent函數,并對重疊進行更新

3.AActor::SetActorEnableCollision

這類函數用于改變組件的碰撞狀態,同理還有設置組件的通道類型等函數。當組件的碰撞狀態發生改變時,都會調用一次UpdateOverlaps以更新重疊。

值得一提的是,這類函數對UpdateOverlaps調用的傳參都是默認的,即傳入的兩個數組都是空值。這意味著更新重疊時不會引入新的重疊,只會對當前已記錄的重疊進行操作。

// update overlaps once after all components have been updated
UpdateOverlaps();

真正更新重疊的實現函數:UPrimitiveComponent::UpdateOverlapsImpl

篇幅有限,筆者不會去詳細講解碰撞是如何查詢并產生結果的,也不會去講解組件移動具體會發生什么事情(因為筆者也沒來得及弄懂)。現在只需要知道一個前提:UE能通過某種方式獲得當前的碰撞信息,并存入前面提到的函數參數中的兩個數組中。根據這個前提,接下來將圍繞UpdateOverlapsImpl 函數對整個重疊更新進行詳細的講解。

總所周知,USceneComponent為Actor提供了表達自身空間信息的能力,可以為開發者提供Transform等信息,而碰撞相關的信息則交給了其子類UPrimitiveComponent。也就是說,只有繼承了UPrimitiveComponent的類才能擁有碰撞處理的能力,否則這個組件就是空間中的一個幽靈,無法與世界進行任何交互。

而作為第一個擁有碰撞能力的組件,它擁有著一個足以彰顯其身份的成員:

TArray<FOverlapInfo> OverlappingComponents;

Set of components that this component is currently overlapping.

含義很明顯,保存了所有與當前組件重疊且能生成重疊事件的其他組件。記住這個組件,可以說一個組件的重疊更新始終是圍繞著這個組件完成的。

對新加入的重疊進行處理

一開始是一些簡單的判斷。如果Actor還沒有beginPlayer,將不會繼續后續的邏輯。

緊隨其后的,就是對NewPendingOverlaps數組進行處理,相關代碼如下:

// first, dispatch any pending overlaps
	if (GetGenerateOverlapEvents() && IsQueryCollisionEnabled())	
	{
		bCanSkipUpdateOverlaps = false;
		if (MyActor)
		{
			const FTransform PrevTransform = GetComponentTransform();
			// If we are the root component we ignore child components. Those children will update their overlaps when we descend into the child tree.
			// This aids an optimization in MoveComponent.
			const bool bIgnoreChildren = (MyActor->GetRootComponent() == this);

			if (NewPendingOverlaps)
			{
				// Note: BeginComponentOverlap() only triggers overlaps where GetGenerateOverlapEvents() is true on both components.
				const int32 NumNewPendingOverlaps = NewPendingOverlaps->Num();
				for (int32 Idx=0; Idx < NumNewPendingOverlaps; ++Idx)
				{
					BeginComponentOverlap( (*NewPendingOverlaps)[Idx], bDoNotifies );
				}
			}
.........

GetGenerateOverlapEvents() && IsQueryCollisionEnabled()

是否生成重疊事件&是否允許碰撞。

IsQueryCollisionEnabled()可以通過SetActorEnableCollision改變其狀態;

GetGenerateOverlapEvents()可以在藍圖里勾選“生成重疊事件”或者改變bool值bGenerateOverlapEvents進行修改。

補充一點,只有兩個組件都能生成重疊事件,才會觸發雙方的BeginOverlap事件。


注意到有一個bIgnoreChildren變量,當self組件為根組件時其為true。這意味著根組件始終不會考慮子組件的影響。而子組件呢,默認下是會與本Actor的其他組件發生碰撞的,實際使用中我們很少會考慮這種問題,但這里可以作為一個小細節記一下。

UPrimitiveComponent::BeginComponentOverlap(const FOverlapInfo& OtherOverlap, bool bDoNotifies)

之后將對NewPendingOverlaps進行一次完整的遍歷。前面提到,NewPendingOverlaps可能包含已經重疊的組件,也可能包含還未重疊的組件。這些組件將在這個函數中進行統一處理,忽略已經重疊的組件,而未重疊的組件則調用雙方的OnComponentBeginOverlap委托。

void UPrimitiveComponent::BeginComponentOverlap(const FOverlapInfo& OtherOverlap, bool bDoNotifies)
{
	// If pending kill, we should not generate any new overlaps
	if (!IsValid(this))
	{
		return;
	}

	const bool bComponentsAlreadyTouching = (IndexOfOverlapFast(OverlappingComponents, OtherOverlap) != INDEX_NONE);
	if (!bComponentsAlreadyTouching)
	{
		UPrimitiveComponent* OtherComp = OtherOverlap.OverlapInfo.Component.Get();
		if (CanComponentsGenerateOverlap(this, OtherComp))
		{
			GlobalOverlapEventsCounter++;			
			AActor* const OtherActor = OtherComp->GetOwner();
			AActor* const MyActor = GetOwner();

			const bool bSameActor = (MyActor == OtherActor);
			const bool bNotifyActorTouch = bDoNotifies && !bSameActor && !AreActorsOverlapping(*MyActor, *OtherActor);

			// Perform reflexive touch.
			OverlappingComponents.Add(OtherOverlap);												// already verified uniqueness above
			AddUniqueOverlapFast(OtherComp->OverlappingComponents, FOverlapInfo(this, INDEX_NONE));	// uniqueness unverified, so addunique
			
			const UWorld* World = GetWorld();
			const bool bLevelStreamingOverlap = (bDoNotifies && MyActor->bGenerateOverlapEventsDuringLevelStreaming && MyActor->IsActorBeginningPlayFromLevelStreaming());
			if (bDoNotifies && ((World && World->HasBegunPlay()) || bLevelStreamingOverlap))
			{
				// first execute component delegates
				if (IsValid(this))
				{
					OnComponentBeginOverlap.Broadcast(this, OtherActor, OtherComp, OtherOverlap.GetBodyIndex(), OtherOverlap.bFromSweep, OtherOverlap.OverlapInfo);
				}

				if (IsValid(OtherComp))
				{
					// Reverse normals for other component. When it's a sweep, we are the one that moved.
					OtherComp->OnComponentBeginOverlap.Broadcast(OtherComp, MyActor, this, INDEX_NONE, OtherOverlap.bFromSweep, OtherOverlap.bFromSweep ? FHitResult::GetReversedHit(OtherOverlap.OverlapInfo) : OtherOverlap.OverlapInfo);
				}

				// then execute actor notification if this is a new actor touch
				if (bNotifyActorTouch)
				{
					// First actor virtuals
					if (IsActorValidToNotify(MyActor))
					{
						MyActor->NotifyActorBeginOverlap(OtherActor);
					}

					if (IsActorValidToNotify(OtherActor))
					{
						OtherActor->NotifyActorBeginOverlap(MyActor);
					}

					// Then level-script delegates
					if (IsActorValidToNotify(MyActor))
					{
						MyActor->OnActorBeginOverlap.Broadcast(MyActor, OtherActor);
					}

					if (IsActorValidToNotify(OtherActor))
					{
						OtherActor->OnActorBeginOverlap.Broadcast(OtherActor, MyActor);
					}
				}
			}
		}
	}
}

邏輯并不難,主要做了以下幾件事:

  1. 檢查OverlappingComponents數組,判斷該組件是否已重疊,如果未重疊就執行后面的邏輯
  2. CanComponentsGenerateOverlap 判斷雙方是否都能生成重疊事件,如果其中一方不能重疊,函數到這也就結束了
  3. 判斷兩個Actor是否已重疊,如果已重疊,后續則不會觸發ActorOverlap事件
  4. 添加新的重疊到自己的OverlappingComponents中
  5. 將自己添加到對方的OverlappingComponents中
  6. 觸發雙方的ComponentBeginOverlap委托
  7. 觸發雙方的ActorBeginOverlap委托

可以看到,組件通過檢查自己的OverlappingComponents數組來判斷是否是已經觸發的重疊,來規避重疊事件的重復觸發。另外,主動觸發重疊的一方會直接觸發雙方的重疊事件,因為重疊更新通常是在運動中觸發的,如果其中一方不移動,只觸發主動方的事件的話將會漏掉對方的重疊事件。

由于該函數會自動規避已重疊的組件,因此我們就不用費心思考慮是否會重復觸發重疊開始事件了,這個后面也會用到。


在重疊開始事件中往往會存在各種各樣的邏輯,其中包括移動、銷毀、添加其他Actor等等邏輯,這些都是不可預測的,UE很明顯考慮到了這一點,在重疊開始事件結束后,還需要再次檢查當前的狀態是否和之前有所改變。

另外,我們還需要考慮本次重疊更新調用時,是否有舊的重疊已失效,比如我們走出了重疊的范圍,或是別的組件自己關閉了碰撞。

// now generate full list of new touches, so we can compare to existing list and determine what changed
			TInlineOverlapInfoArray OverlapMultiResult;
			TInlineOverlapPointerArray NewOverlappingComponentPtrs;

因此,代碼里新定義了兩個臨時數組,其中OverlapMultiResult將會保存在新的位置重新重疊檢測的結果;NewOverlappingComponentPtrs更重要一些,會保存當前重疊的指針,讓我們繼續往后看。

Self組件沒有移動的情況

// Might be able to avoid testing for new overlaps at the end location.
				if (OverlapsAtEndLocation != nullptr && bAllowCachedOverlapsCVar && PrevTransform.Equals(GetComponentTransform()))
				{
					const bool bCheckForInvalid = (NewPendingOverlaps && NewPendingOverlaps->Num() > 0);
					if (bCheckForInvalid)
					{
						// BeginComponentOverlap may have disabled what we thought were valid overlaps at the end (collision response or overlap flags could change).
						GetPointersToArrayDataByPredicate(NewOverlappingComponentPtrs, *OverlapsAtEndLocation, FPredicateFilterCanOverlap(*this));
					}
					else
					{
						GetPointersToArrayData(NewOverlappingComponentPtrs, *OverlapsAtEndLocation);
					}
				}

篩選OverlapsAtEndLocation,將當前能觸發重疊事件的組件指針存入NewOverlappingComponentPtrs。

這里使用bCheckForInvalid做了一個小優化,如果NewPendingOverlaps為空,就意味著沒有任何BeginOverlap事件,就不需要篩選OverlapsAtEndLocation了,畢竟始終沒有機會改變。

Self組件有移動的情況(或OverlapsAtEndLocation為空的情況)

else
				{
					SCOPE_CYCLE_COUNTER(STAT_PerformOverlapQuery);
					UE_LOG(LogPrimitiveComponent, VeryVerbose, TEXT("%s->%s Performing overlaps!"), *GetNameSafe(GetOwner()), *GetName());
					UWorld* const MyWorld = GetWorld();
					TArray<FOverlapResult> Overlaps;
					// note this will optionally include overlaps with components in the same actor (depending on bIgnoreChildren). 
					FComponentQueryParams Params(SCENE_QUERY_STAT(UpdateOverlaps), bIgnoreChildren ? MyActor : nullptr);
					Params.bIgnoreBlocks = true;	//We don't care about blockers since we only route overlap events to real overlaps
					FCollisionResponseParams ResponseParam;
					InitSweepCollisionParams(Params, ResponseParam);
					ComponentOverlapMulti(Overlaps, MyWorld, GetComponentLocation(), GetComponentQuat(), GetCollisionObjectType(), Params);

					for (int32 ResultIdx=0; ResultIdx < Overlaps.Num(); ResultIdx++)
					{
						const FOverlapResult& Result = Overlaps[ResultIdx];

						UPrimitiveComponent* const HitComp = Result.Component.Get();
						if (HitComp && (HitComp != this) && HitComp->GetGenerateOverlapEvents())
						{
							const bool bCheckOverlapFlags = false; // Already checked above
							if (!ShouldIgnoreOverlapResult(MyWorld, MyActor, *this, Result.OverlapObjectHandle.FetchActor(), *HitComp, bCheckOverlapFlags))
							{
								OverlapMultiResult.Emplace(HitComp, Result.ItemIndex);		// don't need to add unique unless the overlap check can return dupes
							}
						}
					}

					// Fill pointers to overlap results. We ensure below that OverlapMultiResult stays in scope so these pointers remain valid.
					GetPointersToArrayData(NewOverlappingComponentPtrs, OverlapMultiResult);
				}

當Self組件在BeginOverlap中發生了坐標的變化,那么我們就需要重新進行碰撞查詢。這段代碼看著復雜,其實也就只做了這一件事:調用ComponentOverlapMulti函數進行重疊查詢,然后將新查詢到的重疊的指針放入NewOverlappingComponentPtrs中。

另外,這里再次用到了bIgnoreChildren,說明UE真的很不想讓根組件更新到子組件的重疊,據說是為了優化MoveComponent的流程?大概吧,但是這并不意味著子組件不會和根組件發生重疊事件,當子組件主動更新重疊時,仍會檢測到根組件,并觸發雙方的重疊事件。

這里埋下了一個伏筆,這段函數還有一個觸發條件,就是OverlapsAtEndLocation為空的情況。本以為是一個不起眼的判斷,卻為子組件的重疊更新埋下了伏筆。


整理出可能存在的新的重疊后,我們還需考慮舊的重疊是否已經失效,因此需要對比新舊重疊,來獲取新增的過時的重疊。

對比新舊重疊

緩存舊重疊

總之先把舊的重疊緩存一下吧,很顯然,直到前面調用重疊開始事件之前,OverlappingComponents數組里都是“舊重疊”。

這里的代碼定義了OldOverlappingComponentPtrs數組,緩存了舊重疊的指針,對應前面的NewOverlappingComponentPtrs數組。之后將OverlappingComponents的元素以指針的方式拷貝到OldOverlappingComponentPtrs中。

// If we have any overlaps from BeginComponentOverlap() (from now or in the past), see if anything has changed by filtering NewOverlappingComponents
			if (OverlappingComponents.Num() > 0)
			{
				TInlineOverlapPointerArray OldOverlappingComponentPtrs;
				if (bIgnoreChildren)
				{
					GetPointersToArrayDataByPredicate(OldOverlappingComponentPtrs, OverlappingComponents, FPredicateOverlapHasDifferentActor(*MyActor));
				}
				else
				{
					GetPointersToArrayData(OldOverlappingComponentPtrs, OverlappingComponents);
				}

篩選新舊重疊

那么怎么判斷哪些重疊是過時的,哪些重疊是新的呢?

我們現在手里有兩個數組,一個是NewOverlappingComponentPtrs,保存了當前所有有效的重疊;另一個是OldOverlappingComponentPtrs,保存了曾經有效的重疊。

那么去除這兩個數組重復的部分,我們就可以篩選出過時的重疊和新的需要觸發重疊事件的重疊。他們之間的關系如下圖所示。

// Now we want to compare the old and new overlap lists to determine 
				// what overlaps are in old and not in new (need end overlap notifies), and 
				// what overlaps are in new and not in old (need begin overlap notifies).
				// We do this by removing common entries from both lists, since overlapping status has not changed for them.
				// What is left over will be what has changed.
				// 去除重復的部分
				for (int32 CompIdx=0; CompIdx < OldOverlappingComponentPtrs.Num() && NewOverlappingComponentPtrs.Num() > 0; ++CompIdx)
				{
					// RemoveAtSwap is ok, since it is not necessary to maintain order
					const bool bAllowShrinking = false;

					const FOverlapInfo* SearchItem = OldOverlappingComponentPtrs[CompIdx];
					const int32 NewElementIdx = IndexOfOverlapFast(NewOverlappingComponentPtrs, SearchItem);
					if (NewElementIdx != INDEX_NONE)
					{
						NewOverlappingComponentPtrs.RemoveAtSwap(NewElementIdx, 1, bAllowShrinking);
						OldOverlappingComponentPtrs.RemoveAtSwap(CompIdx, 1, bAllowShrinking);
						--CompIdx;
					}
				}

最終,OldOverlappingComponentPtrs就只剩下了過時的,需要調用EndOverlap的重疊;NewOverlappingComponentPtrs剩下了新增的,需要調用BeginOverlap的重疊。

EndComponentOverlap


				const int32 NumOldOverlaps = OldOverlappingComponentPtrs.Num();
				if (NumOldOverlaps > 0)
				{
					// Now we have to make a copy of the overlaps because we can't keep pointers to them, that list is about to be manipulated in EndComponentOverlap().
					TInlineOverlapInfoArray OldOverlappingComponents;
					OldOverlappingComponents.SetNumUninitialized(NumOldOverlaps);
					for (int32 i=0; i < NumOldOverlaps; i++)
					{
						OldOverlappingComponents[i] = *(OldOverlappingComponentPtrs[i]);
					}

					// OldOverlappingComponents now contains only previous overlaps that are confirmed to no longer be valid.
					for (const FOverlapInfo& OtherOverlap : OldOverlappingComponents)
					{
						if (OtherOverlap.OverlapInfo.Component.IsValid())
						{
							EndComponentOverlap(OtherOverlap, bDoNotifies, false);
						}
						else
						{
							// Remove stale item. Reclaim memory only if it's getting large, to try to avoid churn but avoid bloating component's memory usage.
							const bool bAllowShrinking = (OverlappingComponents.Max() >= 24);
							const int32 StaleElementIndex = IndexOfOverlapFast(OverlappingComponents, OtherOverlap);
							if (StaleElementIndex != INDEX_NONE)
							{
								OverlappingComponents.RemoveAtSwap(StaleElementIndex, 1, bAllowShrinking);
							}
						}
					}
				}

具體EndComponentOverlap發生了什么,基本和BeginCompoentOverlap反著來,筆者就不贅述了。

之后再將新的重疊遍歷調用BeginCompoentOverlap,本次重疊更新的主要內容就基本結束了。

Self組件沒開啟碰撞的情況

還記得前面提到的GetGenerateOverlapEvents() && IsQueryCollisionEnabled()條件判斷嗎?對于調用了SetActorEnableCollision關閉Actor碰撞的情況,這里當然也是有考慮的。

// first, dispatch any pending overlaps
	if (GetGenerateOverlapEvents() && IsQueryCollisionEnabled())	//TODO: should modifying query collision remove from mayoverlapevents?
	{....}
	else
	{
		// GetGenerateOverlapEvents() is false or collision is disabled
		// End all overlaps that exist, in case GetGenerateOverlapEvents() was true last tick (i.e. was just turned off)
		if (OverlappingComponents.Num() > 0)
		{
			const bool bSkipNotifySelf = false;
			ClearComponentOverlaps(bDoNotifies, bSkipNotifySelf);
		}
	}

當OverlappingComponents數組里還有重疊,我們需要將這些重疊全部處理掉,也就是一一調用EndComponentOverlap,UE在這里將其寫成了一個ClearComponentOverlaps函數。

ClearComponentOverlaps

void UPrimitiveComponent::ClearComponentOverlaps(bool bDoNotifies, bool bSkipNotifySelf)
{
	if (OverlappingComponents.Num() > 0)
	{
		// Make a copy since EndComponentOverlap will remove items from OverlappingComponents.
		const TInlineOverlapInfoArray OverlapsCopy(OverlappingComponents);
		for (const FOverlapInfo& OtherOverlap : OverlapsCopy)
		{
			EndComponentOverlap(OtherOverlap, bDoNotifies, bSkipNotifySelf);
		}
	}
}

調用子組件的UpdateOverlap

在講解這部分之前,必須強調很重要的一點:

前面提到的移動過程產生的重疊更新,是不會直接通過子組件調用的,必須通過根組件先調用UpdateOverlap,然后經過循環遞歸調用,才能觸發子組件的UpdateOverlap。

然后呢,看看在根組件經過前面一大串的邏輯后,在這個函數的末尾,是如何調用子組件的UpdateOverlap的:

// now update any children down the chain.
	// since on overlap events could manipulate the child array we need to take a copy
	// of it to avoid missing any children if one is removed from the middle
	TInlineComponentArray<USceneComponent*> AttachedChildren;
	AttachedChildren.Append(GetAttachChildren());

	for (USceneComponent* const ChildComp : AttachedChildren)
	{
		if (ChildComp)
		{
			// Do not pass on OverlapsAtEndLocation, it only applied to this component.
			bCanSkipUpdateOverlaps &= ChildComp->UpdateOverlaps(nullptr, bDoNotifies, nullptr);
		}
	}

先說一個小細節:在遍歷子組件之前,先緩存了一份子組件,是因為子組件更新重疊的過程中,可能會自己脫離父組件,導致循環出現BUG,這點大家平時寫代碼的時候要注意一下。

我們可以看到最后調用了這樣一行代碼:

ChildComp->UpdateOverlaps(nullptr, bDoNotifies, nullptr);

然后發現傳入的兩個數組都是nullptr。

what?兩個數組都是空指針的話,那么子組件還怎么更新重疊?

現在回過去看 Self組件有移動的情況(或OverlapsAtEndLocation為空的情況)這一節,會發現子組件會直接走這段邏輯,也就是現場判斷組件在場景中的重疊的方式,之后再進行后面的邏輯。

至此,UpdateOverlap的流程就基本結束了。

根組件跳過子組件的重疊查詢

斷點調試發現,根組件在調用UpdateOverlaps的NewPendingOverlaps數組中,并沒有任何子組件,哪怕子組件碰撞全開。

往上追溯,才發現UPrimitiveComponent::MoveComponentImpl里在重疊檢測時還藏了一手:

			FComponentQueryParams Params(SCENE_QUERY_STAT(MoveComponent), Actor);
			FCollisionResponseParams ResponseParam;
			InitSweepCollisionParams(Params, ResponseParam);
			Params.bIgnoreTouches |= !(GetGenerateOverlapEvents() || bForceGatherOverlaps);
			Params.TraceTag = TraceTagName;
			bool const bHadBlockingHit = MyWorld->ComponentSweepMulti(Hits, this, TraceStart, TraceEnd, InitialRotationQuat, Params);

FComponentQueryParams Params的第二個參數就是要忽略的Actor,這里的Actor指的就是本身,所以檢測的結果自然就沒有自己的子組件了。

不過即便如此,如果子組件在碰撞上允許和根組件生成重疊事件時,在子組件的UpdateOverlaps還是不可避免地與根組件發生重疊關系。不過UE的注釋里都提到了,這都是為了優化MovementCompoennt的移動流程。

參考

角色移動組件 | 虛幻引擎文檔 (unrealengine.com)

UE4的移動碰撞 - 知乎 (zhihu.com)

總結

以上是生活随笔為你收集整理的UE5: UpdateOverlap - 从源码深入探究UE的重叠触发的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。