UE4的委托


UE中委托的使用很廣泛,許多Event的觸發都有對應的虛函數和委托,虛函數不用講,只能在派生類中使用,而委托可以在別的類或者藍圖中使用,就應用范圍而言,委托的使用更靈活。以AActor的

	/** 
	 *	Event when this actor overlaps another actor, for example a player walking into a trigger.
	 *	For events when objects have a blocking collision, for example a player hitting a wall, see 'Hit' events.
	 *	@note Components on both this and the other Actor must have bGenerateOverlapEvents set to true to generate overlap events.
	 */
	virtual void NotifyActorBeginOverlap(AActor* OtherActor);
	/** 
	 *	Event when this actor overlaps another actor, for example a player walking into a trigger.
	 *	For events when objects have a blocking collision, for example a player hitting a wall, see 'Hit' events.
	 *	@note Components on both this and the other Actor must have bGenerateOverlapEvents set to true to generate overlap events.
	 */
	UFUNCTION(BlueprintImplementableEvent, meta=(DisplayName = "ActorBeginOverlap"), Category="Collision")
	void ReceiveActorBeginOverlap(AActor* OtherActor);

	/** 
	 *	Event when an actor no longer overlaps another actor, and they have separated. 
	 *	@note Components on both this and the other Actor must have bGenerateOverlapEvents set to true to generate overlap events.
	 */
	virtual void NotifyActorEndOverlap(AActor* OtherActor);

 為例,重疊(overlapped)事件發生時,就會觸發這組函數(Begin/EndOverlap),但僅限於在派生類中進行重載。其實,AActor提供了另外一種更加靈活的方式,那就是:委托(Delegate)

	/** 
	 *	Called when another actor begins to overlap this actor, for example a player walking into a trigger.
	 *	For events when objects have a blocking collision, for example a player hitting a wall, see 'Hit' events.
	 *	@note Components on both this and the other Actor must have bGenerateOverlapEvents set to true to generate overlap events.
	 */
	UPROPERTY(BlueprintAssignable, Category="Collision")
	FActorBeginOverlapSignature OnActorBeginOverlap;

 當然也有對應的End形式。查看FActorBeginOverlapSignature的定義:

 

// Delegate signatures
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams( FTakeAnyDamageSignature, AActor*, DamagedActor, float, Damage, const class UDamageType*, DamageType, class AController*, InstigatedBy, AActor*, DamageCauser );
DECLARE_DYNAMIC_MULTICAST_DELEGATE_NineParams( FTakePointDamageSignature, AActor*, DamagedActor, float, Damage, class AController*, InstigatedBy, FVector, HitLocation, class UPrimitiveComponent*, FHitComponent, FName, BoneName, FVector, ShotFromDirection, const class UDamageType*, DamageType, AActor*, DamageCauser );
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( FActorBeginOverlapSignature, AActor*, OverlappedActor, AActor*, OtherActor );
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( FActorEndOverlapSignature, AActor*, OverlappedActor, AActor*, OtherActor );
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams( FActorHitSignature, AActor*, SelfActor, AActor*, OtherActor, FVector, NormalImpulse, const FHitResult&, Hit );

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam( FActorBeginCursorOverSignature, AActor*, TouchedActor );
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam( FActorEndCursorOverSignature, AActor*, TouchedActor );
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( FActorOnClickedSignature, AActor*, TouchedActor , FKey, ButtonPressed );
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( FActorOnReleasedSignature, AActor*, TouchedActor , FKey, ButtonReleased );
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( FActorOnInputTouchBeginSignature, ETouchIndex::Type, FingerIndex, AActor*, TouchedActor );
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( FActorOnInputTouchEndSignature, ETouchIndex::Type, FingerIndex, AActor*, TouchedActor );
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( FActorBeginTouchOverSignature, ETouchIndex::Type, FingerIndex, AActor*, TouchedActor );
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( FActorEndTouchOverSignature, ETouchIndex::Type, FingerIndex, AActor*, TouchedActor );

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FActorDestroyedSignature, AActor*, DestroyedActor );
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FActorEndPlaySignature, AActor*, Actor , EEndPlayReason::Type, EndPlayReason);

DECLARE_DELEGATE_SixParams(FMakeNoiseDelegate, AActor*, float /*Loudness*/, class APawn*, const FVector&, float /*MaxRange*/, FName /*Tag*/);

其中DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams,表明這是一個多播。在UE中,委托有單播和多播兩種方式,另外還有委托的函數帶不帶返回值(_RetVal后綴),單播和多播均為無返回值(函數返回類型為void)。

一、單播:

使用如下定義(這里只使用無參和一個參數兩種類型,別的類似):

//聲明委托(單播),無參數
DECLARE_DELEGATE(MyDelegate)
DECLARE_DELEGATE_OneParam(MyIntDelegate, int32)

 這樣就聲明了兩種委托類型,MyDelegate(無參數),MyIntDelegate(帶有一個int32參數類型),用法如下:

1)聲明和實現回調函數,注意:一定要加UFUNCTION修飾函數聲明,因為委托是參與UE的反射系統中的,即UE引擎要知道有這個函數,這在下面的例子中會看到,聲明和實現如下:

	UFUNCTION()
	void IntFunction(int32 Value)
	{
		GLog->Log(ELogVerbosity::Warning, "IntFunction: " + FString::FromInt(Value));
	}

	UFUNCTION()
	void SecondIntFunction(int32 Value)
	{
		GLog->Log(ELogVerbosity::Warning, "SecondIntFunction: " + FString::FromInt(Value));
	}
	UFUNCTION()
	void ThirdIntFunction(int32 Value)
	{
		GLog->Log(ELogVerbosity::Warning, "ThirdIntFunction: " + FString::FromInt(Value));
	}


	UFUNCTION()
	void SomeFunction()
	{
		GLog->Log(ELogVerbosity::Warning, "SomeFunction: ");
	}

 使用方法:

隨便找個地方測試,這里在BeginPlay()中測試:

	MyIntDelegate IntDelegate;
	IntDelegate.BindUFunction(this, FName("IntFunction"));
	IntDelegate.BindUFunction(this, FName("SecondIntFunction"));

 單播,只能觸發一個委托,因此上面雖然綁定了兩個函數IntFunction和SecondIntFuction,但是只有第2個函數(SecondIntFunction)得到調用

	IntDelegate.Execute(999);

 在引擎的日志中顯示如下:


注意到一個有趣的地方,我們綁定函數時,不是通過函數的名字來綁定的,而是通過函數的名字對應的FName來綁定的,這是說明(或許稱推測更准確),這些函數在UE引擎中通過某種方式(VM更有可能)有函數和其對應名稱字符串的一個映射,這也是前面在函數聲明處添加UFUNCTION修飾的原因(告訴UHT要把這個函數添加進反射系統)。另外一個有趣的地方是調用委托(IntDelegate.Execute(999))時,VC會自動推導出參數類型(VS2017測試)為int32。

二、多播:多播時可以觸發多個委托的函數,多播帶有Multicast這樣的字符串,這很容易和單播區分出來。多播就意味着可以觸發多個事件,如下:

	GLog->Log(ELogVerbosity::Warning, TEXT("將要進行多播……。"));
	MyIntMulticastDelegate IntMulticastDelegate;
	IntMulticastDelegate.AddUFunction(this, FName("IntFunction"));
	IntMulticastDelegate.AddUFunction(this, FName("SecondIntFunction"));
	IntMulticastDelegate.AddUFunction(this, FName("ThirdIntFunction"));
	IntMulticastDelegate.Broadcast(123);

 注意,多播要使用Broadcast觸發,Broadcast就意味着廣播,廣播嘛,大家只要願意,都能收到,這也正是觀察者(Observer)模式。

運行結果:


三、擴展:

既然引入時使用了重疊的例子,這里把這個事件再注解下,老了,記性不好:(

void AActor::NotifyActorBeginOverlap(AActor* OtherActor)
{
	// call BP handler
	ReceiveActorBeginOverlap(OtherActor);
}

 該函數是虛函數,調用一個ReceiveActorBeginOverlap函數,ReceiveActorBeginOverlap原型在引入中有原型,看下說明知道,這里其實就是調用藍圖中對應的OnActorBeginOverlap結點(NODE)。所以重載時要注意,如果需要調用藍圖中的事件(通常如此)要顯式調用Super::NotifyActorBeginOverlap(OtherActor)。調用的位置,即先於自定義代碼,或者在自定義代碼后調用,要根據自己的情況,例如,如果在派生類中做些初始化,以供藍圖使用,則應先調用自己的代碼,后調用父類,否則,無所謂。

四,進一步說明重疊事件:

在前方中提及,AActor是不參與物體的collide的,這里又有重載,又有多播,這不是打自己的臉?其實不是。AActor是容器!AActor是容器!AActor是容器!所以功能的實現都是通過組件實現的。在前方中說明碰撞(collide,當然包括重疊和Hit,唔,,Hit叫撞擊聽着不順就Hit好了)是在UPrimitiveComponent中實現,這里把它扒出來,晾晾。

// @todo, don't need to pass in Other actor?
void UPrimitiveComponent::BeginComponentOverlap(const FOverlapInfo& OtherOverlap, bool bDoNotifies)
{
	// If pending kill, we should not generate any new overlaps
	if (IsPendingKill())
	{
		return;
	}

	//	UE_LOG(LogActor, Log, TEXT("BEGIN OVERLAP! Self=%s SelfComp=%s, Other=%s, OtherComp=%s"), *GetNameSafe(this), *GetNameSafe(MyComp), *GetNameSafe(OtherComp->GetOwner()), *GetNameSafe(OtherComp));

	UPrimitiveComponent* OtherComp = OtherOverlap.OverlapInfo.Component.Get();

	if (OtherComp)
	{
		bool const bComponentsAlreadyTouching = IsOverlappingComponent(OtherOverlap);
		if (!bComponentsAlreadyTouching && CanComponentsGenerateOverlap(this, OtherComp))
		{
			AActor* const OtherActor = OtherComp->GetOwner();
			AActor* const MyActor = GetOwner();

			const bool bNotifyActorTouch = bDoNotifies && !MyActor->IsOverlappingActor(OtherActor);

			// Perform reflexive touch.
			OverlappingComponents.Add(OtherOverlap);										// already verified uniqueness above
			OtherComp->OverlappingComponents.AddUnique(FOverlapInfo(this, INDEX_NONE));		// uniqueness unverified, so addunique
			
			if (bDoNotifies)
			{
				// first execute component delegates
				if (!IsPendingKill())
				{
					OnComponentBeginOverlap.Broadcast(this, OtherActor, OtherComp, OtherOverlap.GetBodyIndex(), OtherOverlap.bFromSweep, OtherOverlap.OverlapInfo);
				}

				if (!OtherComp->IsPendingKill())
				{
					// 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);
					}
				}
			}
		}
	}
}

 代碼有點長,似乎超過50,想必Epic不會找我麻煩,只看后一段好了。

 

簡單來講就是,如果Actor有效(Valid)則調用自己的Notify對應的事件,然后再廣播!順便做個好人,也幫對方觸發一下事件,也幫對方發個小廣告,為什么這么做?唔,,,思考一下,很快就明白,在Sweep方式下,被重疊的物體是通常都是被動的,如果它再主動檢查,很耗資源,所以,它就在那里吃瓜好了,等我碰到你再幫你觸發事件,再幫你發你要發的消息(小廣告),反正如果大伙都不動,肯定不會發生碰撞(沒有進行物理模擬情況下,有物理模擬,唔,同樣的結論似乎也成立)。

 五、進進進進一步說明重疊事件:剛才突發奇想,打開物理模擬后又如何?簡單測試,隨便弄個球好了,球打開物理模擬,設置對pawn對象overlap,勾選generated overlap event,然后在level blueprint中隨便打印個字符串,我選擇的是輸出:overlap字符串。pawn一碰到球,產生了同樣的事件,結論成立。

六、感謝Epic提供的源代碼,引用我最喜歡的侯捷老師一句話:源碼面前,了無秘密。話說侯老師,你不准備寫一本《UE4引擎深入淺出》嗎?多少money我都支持,擦,,,,MFC深入淺出我買了二本,第一本大多數人都沒見過,淺藍色封面的,可惜送人了。嘻嘻。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM