0%

unreal delegate

I still remember when I began to learn blueprint from the official document, actor communication can be realized by interface, cast-to or event dispatchers. According to the example, it will happen when player goes into a collision trigger, make the on actor begin overlap event in the blueprint to communicate with the player.

However, in the cpp version, the event dispatchers is explained as the delegate. At that time, I didnt realize the example as there are too many parameters in the bind event. Now I found an use case with delegate which is also the unreal FPS project template. The delegate is used for the projectile. When the projectile hits other actors, it calls the custom event OnHit() . Lets take a look at the projectile source code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class AProjectile:public AActor
{
UPROPERTY()
USphereComponent* CollisionComp; //U indicate this is a collision component rather than a mesh

UFUNCTION()
void OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);

}

AProjectile::AProjectile()
{
CollisionComp->OnComponentHit.AddDynamic(this, &AFPSCPPProjectile::OnHit); //seems like an event calls the custom function in the blueprint
}

//not all the parameters are used, but the "other actor" and "other component" are the things that the projectile is hitting. If it has the physics feature, add an impulse depend on the projectile. the code is similiar to the blueprint event.
void AProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
if ((OtherActor != nullptr) && (OtherActor != this) && (OtherComp != nullptr) && OtherComp->IsSimulatingPhysics())
{
OtherComp->AddImpulseAtLocation(GetVelocity() * 100.0f, GetActorLocation());
Destroy();
}
}

Get into the engine source code.

The reason why USphereComponent reference can point to the OnComponentHit is that OnComponentHit is a signature of UPrimitiveComponent , its also considered as a delegate name.

1
2
3
4
5
6
7
8
9
10
11
//PrimitiveComponent.h

UCLASS()
class ENGINE_API UPrimitiveComponent : public USceneComponent, public INavRelevantInterface
{
UPROPERTY(BlueprintAssignable, Category="Collision")
FComponentHitSignature OnComponentHit; //considered as an event which can be created in blueprint in the same name
}

//long marco declaretion include parameters
DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_FiveParams( FComponentHitSignature, UPrimitiveComponent, OnComponentHit, UPrimitiveComponent*, HitComponent, AActor*, OtherActor, UPrimitiveComponent*, OtherComp, FVector, NormalImpulse, const FHitResult&, Hit );

How to bind a function to the delegate

1
2
3
//Delegate.h
#define AddDynamic( UserObject, FuncName )
//OnComponentHit.AddDynamic(this, &AFPSCPPProjectile::OnHit) on the top

How to confirm the parameter in the custom function.

1
2
3
4
//SparseDelegate.h
//as we can see, in the projectile example, there are five parameters
#define DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_FiveParams( SparseDelegateClass, OwningClass, DelegateName, Param1Type, Param1Name, Param2Type, Param2Name, Param3Type, Param3Name, Param4Type, Param4Name, Param5Type, Param5Name )

By the way, in the FPS blueprint template, event Hit is used to add impluse. The target of Hit is actor itself.

While in the cpp version, OnComponentHit is used which target is sphere collision. And it is actually including 5 parameters as we saw in the source code.

Above all is the summary of the projectile example.

Go back to the actor communication example.

I create the first actor including a box collision and a delegate.

The second actor bind the delegate.

When player overlap with the first box collision, delegate is active, and the second actor log a message.

1
2
3
4
5
6
7
8
//first actor
DECLARE_DELEGATE(FCustomDelegate);
class ATriggerActor
{
class UBoxComponent* BoxComp;
virtual void NotifyActorBeginOverlap(AActor* OtherActor){ IntoTrigger.ExecuteIfBound(); }
FCustomDelegate IntoTrigger;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
//second actor
class AReactActor
{
class ATriggerActor* TriggerActorReference;
void GetIntoTrigger(){ UE_LOG(LogTemp, Log, TEXT("get into the trigger")); }
}

void AReactActor::BeginPlay()
{
Super::BeginPlay();
TriggerActorReference->IntoTrigger.BindUObject(this, &AReactActor::GetIntoTrigger);

}

As for why using BindUObject to bind a function to the delegate.

1
2
3
4
5
6
7
8
9
10
11
12
13
//DelegateSignatureImpl.inl
class TDelegate
{
//function that creates kinds of delegate UE_NODISCARD
//constructor, destructor, and operator override

//function that binds a delegate
template <typename UserClass, typename... VarTypes>
inline void BindUObject(const UserClass* InUserObject, typename TMemFunPtrType<true, UserClass, RetValType (ParamTypes..., VarTypes...)>::Type InFunc, VarTypes... Vars)
{
*this = CreateUObject(InUserObject, InFunc, Vars...);
}
}

I try to replace BindUObject to CreateUObject , due to the UE_NODISCARD which build errors.

nodiscard is an attribute from cpp17 which seems to far for me……sad