Hi there.
I have found a very frustrating bug that prevents bound delegate listeners in C++ firing whilst in design time. However, blueprint listeners work as expected.
Steps to reproduce in a clean project:
- Create a new C++ game project (4.25.3)
- Add C++ actor class called “ContentManager”
- Add C++ actor class called “ActorBase”
Code for ContentManager.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ContentManager.generated.h"
UCLASS()
class DELEGATEUTILITYBUG_API AContentManager : public AActor
{
GENERATED_BODY()
//Delegate Signatures
//------------------------------
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FSendMessage);
//Delegate Handlers
//------------------------------
public:
UPROPERTY(BlueprintAssignable, Category = "Content Manager|Event Dispatchers")
FSendMessage OnSendMessage;
//Functions
//------------------------------
public:
// Sets default values for this actor's properties
AContentManager();
UFUNCTION(BlueprintCallable, Category = "Content Manager|Behaviour")
void SendMessageToObjects();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
};
Code for ContentManager.cpp
#include "ContentManager.h"
// Sets default values
AContentManager::AContentManager(){}
void AContentManager::SendMessageToObjects()
{
UE_LOG(LogTemp, Warning, TEXT("Has bound objects %s. Number of bound objects %d"), OnSendMessage.IsBound() ? TEXT("TRUE") : TEXT("FALSE"), OnSendMessage.GetAllObjects().Num());
OnSendMessage.Broadcast();
}
// Called when the game starts or when spawned
void AContentManager::BeginPlay()
{
Super::BeginPlay();
}
Code for ActorBase.h
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ActorBase.generated.h"
//Class Definitions
class AContentManager;
UCLASS()
class DELEGATEUTILITYBUG_API AActorBase : public AActor
{
GENERATED_BODY()
//Variables / Properties
//--------------------------
private:
AContentManager* contentManager;
public:
// Sets default values for this actor's properties
AActorBase();
UFUNCTION(BlueprintCallable, Category = "ActorBase|Initialise")
void Initialise(AContentManager * inContentManager);
UFUNCTION(BlueprintCallable, Category = "ActorBase|Behaviour")
void OnReceiveMessage();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
};
Code for ActorBase.cpp
#include "ActorBase.h"
#include "ContentManager.h"
// Sets default values
AActorBase::AActorBase(){}
void AActorBase::Initialise(AContentManager* inContentManager)
{
contentManager = inContentManager;
if (IsValid(contentManager))
{
contentManager->OnSendMessage.AddDynamic(this, &AActorBase::OnReceiveMessage);
UE_LOG(LogTemp, Log, TEXT("Actor base successfuly initialised"));
}
}
void AActorBase::OnReceiveMessage()
{
UE_LOG(LogTemp, Log, TEXT("%s has received the message"), *GetName());
}
// Called when the game starts or when spawned
void AActorBase::BeginPlay()
{
Super::BeginPlay();
}
- In the editor, create an Editor Utility Blueprint called WBP_ContentTool
- Remove the canvas panel and add a button and a text widget as a child
- Add the following blueprint code to the buttons “OnClicked” event
- In the editor, right-click WBP_ContentTool and "Run Editor Utility Widget
- Click the button and notice in the logs, only the blueprints bound event was called.
- Now open the level blueprint
- Copy and paste the code from WBP_TestMap and execute from BeginPlay
**Be sure to replace the “Spawn Actor” function nodes to work at run time **
- Remove the ContentManger and ActorBase objects if they still exist in the world outliner
- Run the simulation. Note both delegate listeners were called.
Expected Result
Both listeners should have been outputted in the Editor blueprint.
I am really surprised that it is the C++ implementation that is broken and not the blueprint implementation.
Thank you for your time.
Kind regards
Alex