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深入浅出我买了二本,第一本大多数人都没见过,浅蓝色封面的,可惜送人了。嘻嘻。