Static class members for Blueprints [Unreal]

One limitation Blueprint assets have in Unreal is the absence of static data members support. The solution presented in this post describes a workaround which can be helpful whenever you are in need of a static variable for your Blueprint assets. It is a C++ solution, therefore you will need to implement at least this part of your asset in C++. Then, you could create a Blueprint only asset derived from the C++ asset you just created.

Main Idea

It is really simple: in Unreal C++, you can easily define static class members as you would usually do in typical C++. The problem is that it is not possible to retrieve this data using a static C++ defined Blueprint function (e.g. UFUNCTION(BlueprintPure) ), since you cannot use the static specifier for UFUNCTIONs. The first workaround is that of exposing the same static data, for example a static int, through a regular UFUNCTION. This works and will give the expected result, but you will always be forced to call this function from one instance of your class. That is, you need an object of that datatype existing in your environment. While this might be perfectly fine, I find it even more convenient to create a Blueprint Function Library to go side by side with it. Functions in this library will be able to access the static variables without going through instances of your class.

Example

At the end of this post, I attach the header and source file for an AActor derived class and Blueprint Function Library doing exactly that: allowing to access static data from Blueprinst. The information available as “class-wide data” is the number of currently instanced objects of this class, and the centroid computed starting from the world location of all the available instances. This data is available through a static TArray of pointers to this AActor derived class.

In the following images, you can see how the current number of instances can be displayed, and how the centroid (green sphere) moves according to the average of all instances locations.

The green sphere represents the centroid of the 4 available instances of our class. Both the number and centroid data come from a static data member.

The way we access the static data member is, as we said, through any instance of the class, or through a Blueprint Function Library. I think the latter approach is cleaner and closer to what you would expect from C++.

Accesing static data through an object indtance.
Accessing the same static data through Blueprint Function Library calls.

Unreal, class constructors and the Prototype Pattern

In the provided code, it is possible to see how class objects instances are added to the TArray during BeginPlay. This was not my intention at the beginning, since I wanted to do that within the class constructor: that is the way I would normally do it, since that’s the moment when objects instances are usually created.

When it comes to Unreal, though, things are a bit more complicated. Unreal deals with objects construction in its own special way, and it seems like parts of this approach have also been changing a lot over time. For example, one of the things the Unreal Engine does is to follow the Prototype Pattern. With this approach, the actual object instances that you will use in your environment will be initialized starting from a template object, the Class Default Object (CDO). The CDO which will be created BEFORE the instances you create and use. This means that as soon as you start the engine, constructors will be called even if you have no instances of your objects in your scene.

Moreover, as stated in this post, UObjects can be constructed by composition. For example, think about ActorComponents as subobjects of other objects. These subobjects are not shared by different objects, because each object would need their on subobject. So, each object creates its own instance of subobjects. This implies potentially more construction of CDOs and template instances. There are some ways to check if the current object instance is a prototype, template or similar, but for this example I preferred to deal with the issue by simply using the BeginPlay: that function should be called only by objects actually present in your scene!

References:

https://forums.unrealengine.com/development-discussion/c-gameplay-programming/25162-why-are-con-de-structors-called-multiple-times-during-game-startup-and-shutdown

ActorWithStaticVariable.h

// Dario Mazzanti, 2019

// ActorWithStaticVariable.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Runtime/Engine/Classes/Kismet/BlueprintFunctionLibrary.h"

#include "ActorWithStaticVariable.generated.h"

UCLASS()
class DEVPLAYGROUND_API AActorWithStaticVariable : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AActorWithStaticVariable();

	// getter, callable from Blueprint (and pure)
	UFUNCTION(BlueprintPure, DisplayName = "GetInstancesNumber")
	const int GetInstancesNumberBP();

	// getter, callable from Blueprint (and pure)
	UFUNCTION(BlueprintPure, DisplayName = "GetCentroid")
	const FVector GetCentroidBP();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
	virtual void Destroyed() override;

	void Register();
	void Unregister();

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// static getters
	static int GetInstancesNumber();
	static FVector GetCentroid();

private:

	// a static variable which will contain the list of all instances of this class.
	// Instances will be added at BeginPlay
	static TArray<AActorWithStaticVariable*> Instances;
};



UCLASS()
class DEVPLAYGROUND_API UHelperLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()

	UFUNCTION(BlueprintPure, Category = "Sample Helper Library")
		static inline FVector GetActorWithStaticVariableCentroid()
	{		
		return AActorWithStaticVariable::GetCentroid();
	}

	UFUNCTION(BlueprintPure, Category = "Sample Helper Library")
		static inline int GetActorWithStaticVariableNum()
	{
		return AActorWithStaticVariable::GetInstancesNumber();
	}
};


ActorWithStaticVariable.cpp

// Dario Mazzanti, 2019

// ActorWithStaticVariable.cpp

#include "ActorWithStaticVariable.h"


 // initialize static instances array
TArray<AActorWithStaticVariable*> AActorWithStaticVariable::Instances = TArray<AActorWithStaticVariable*>();

// Sets default values
AActorWithStaticVariable::AActorWithStaticVariable()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	// Create a SceneComponent to be the RootComponent
	RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
}

// Called when the game starts or when spawned
void AActorWithStaticVariable::BeginPlay()
{
	Super::BeginPlay();

	// Register the instance.
	// Doing this in the constructor causes undesired behaviour, since Unreal might call objects constructor for internal reasons
	Register();
}

void AActorWithStaticVariable::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Unregister();
}

void AActorWithStaticVariable::Destroyed()
{
	// When object is destroyed, unregister it from the static instances array
	Unregister();
}

// Called every frame
void AActorWithStaticVariable::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
}


const int AActorWithStaticVariable::GetInstancesNumberBP()
{
	return GetInstancesNumber();
}

const FVector AActorWithStaticVariable::GetCentroidBP()
{
	return GetCentroid();
}

int AActorWithStaticVariable::GetInstancesNumber()
{
	return Instances.Num();
}

FVector AActorWithStaticVariable::GetCentroid()
{
	FVector AveragePos = FVector::ZeroVector;

	if (Instances.Num() <= 0)
		return AveragePos;

	for (size_t i = 0; i < Instances.Num(); i++)
		AveragePos += Instances[i]->GetActorLocation();	

	AveragePos /= Instances.Num();
	return AveragePos;
}

// Adding this instance to the instances array, and incrementing the counter
void AActorWithStaticVariable::Register()
{
	Instances.Add(this);
}

// removing this instance from the instances array
void AActorWithStaticVariable::Unregister()
{
	if (Instances.Contains(this))
		Instances.Remove(this);	
}

dario mazzanti, 2020

Up ↑