C++ Programmatically Create Delegates

Very trimmed down version of my code below. It compiles but only OnAnyStatChanged shows up in Blueprints and any other that I explicitly define are there.

However, I’m wanting to be able to programmatically create delegates for specific ChangleStatValues per Stat that are assignable via Blueprints but I haven’t been able to figure it out.

Any help would be appreciated, let me know if you require more information

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "ReplComponent.generated.h"

class AActor;

// macro version of EStat
#define STAT_LIST(Action) \
    Action(Health) \
    Action(Shield) \
    Action(Energy) \
    Action(Stamina) 

// macro version of EChangeableStatValues
#define STAT_VALUE_LIST(Action, StatName) \
    Action(StatName, CurrentValue) \
    Action(StatName, MaxValue) \
    Action(StatName, MinValue)

#define DECLARE_STAT_VALUE_DELEGATE_PROPERTY(StatName, StatValueName) \
    UPROPERTY(BlueprintAssignable) \
    FOnStatChangedDelegate OnChanged##StatName##StatValueName;

#define DECLARE_STAT_VALUE_DELEGATES_FOR_STAT(StatName) \
    STAT_VALUE_LIST(DECLARE_STAT_VALUE_DELEGATE_PROPERTY, StatName)

#define DECLARE_ALL_STAT_VALUE_DELEGATES STAT_LIST(DECLARE_STAT_VALUE_DELEGATES_FOR_STAT)

DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnStatChangedDelegate, EStat, Stat, EChangeableStatValues, StatValue, float, NewValue);

UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class PROJECTNUNYA_API UReplComponent : public UActorComponent {
    GENERATED_BODY()

public:
    UReplComponent();

protected:
    virtual void BeginPlay() override;

public:
    virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override;

    DECLARE_ALL_STAT_VALUE_DELEGATES

    UPROPERTY(BlueprintAssignable)
    FOnStatChangedDelegate OnAnyStatChanged;
};

Yeah, you can’t put any of the reflection macros like UPROPERTY within a macro. Unreal Header Tool only parses the exact header files, not the version after macros are expanded.

I see a few potential options.

First is the boilerplate option where you do everything manually. Or at least all the parts that involve reflection.

Second would be fallback to relying on the AnyStatChanged callback.

Third would be to manage the callbacks at runtime instead of compile time. By that I mean you keep some sort of data structure like a TMap or TArray that can associate each stat with a delegate. You’d have to write custom functions for binding & unbinding, but at least you’d only have to write that once and it would support any stat.

I’ll say that in my experience, the third one is usually the solution. Not for any “best practice” reason but because the stats become data driven (through data assets or blueprints) so you can’t rely on a solution that requires compile time setup.

1 Like