C++ DataTable in Unreal Engine 5

In this blog post, we are going to see how to set up and use DataTable in C++, by building a simple Item system.

A DataTable allows us to store and manage data by storing rows of data. Those rows are strongly typed and need to be defined as a Structure, either in Blueprint or C++. If you are familiar with DataAsset, a DataTable is just multiple DataAssets of the same type stored together.

Setting up the project

We use the First Person C++ Template as a starting point for the project.

project-creation.png

You can then open the .sln solution using your favorite IDE. I’ll be using Rider for this post, but you can do as you please.

Setting up the Game Instance

I always declare DataTables in the GameInstance object, because it is persisted during the entirety of the game, whatever the level you are in, or whatever state the game is in. In this way, our DataTable will be loaded only once at the start of the game.

But it’s not always the best choice depending on your use case. For example, it might be better to store it inside a specific Actor class so you can choose when it will be loaded.

UCLASS()
class DATATABLE_API UDataTableGameInstance : public UGameInstance
{
	GENERATED_BODY()

public:

	UDataTableGameInstance();

	UDataTable* GetItemDB() const;

protected:

	UPROPERTY(EditDefaultsOnly)
	class UDataTable* ItemDB;
};
#include "DataTableGameInstance.h"

UDataTableGameInstance::UDataTableGameInstance()
{
	static ConstructorHelpers::FObjectFinder<UDataTable> BP_ItemDB(TEXT("DataTable'/Game/Data/ItemDB.ItemDB'"));
	ItemDB = BP_ItemDB.Object;
}

UDataTable* UDataTableGameInstance::GetItemDB() const
{
	return ItemDB;
}

We use the FObjectFinder to load the DataTable Blueprint asset. It means we need to create a DataTable Blueprint asset at the same path mentioned in the code.

We also need to define the ItemData type that is used as a DataTable type :

USTRUCT(BlueprintType)
struct FItemData: public FTableRowBase
{
	GENERATED_BODY()

public:
	FItemData() : Name(FText::FromString("No Name")) {}

	FItemData(const FItemData& I) : ItemID(I.ItemID), Name(I.Name), Attribute(I.Attribute),Class(I.Class),Thumbnail(I.Thumbnail), AdditionalData(I.AdditionalData) {}

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	FName ItemID = FName("No Name");

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	FText Name ;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	float Attribute = 0.f;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	TSubclassOf<AActor> Class = AActor::StaticClass();

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	TSoftObjectPtr<UTexture2D> Thumbnail = nullptr;

	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	FString AdditionalData;

	bool operator==(const FItemData& OtherItem) const
	{
		if (ItemID == OtherItem.ItemID)
			return true;
		return false;
	}
};

To be able to use this type as a DataTable type, we need to inherit from the FTableRowBase type.

Apart from that, we just need to define the different fields, as well as the constructors and the == operator.

Easing Access with a Utility Class

To ease access to the DataTable, I like to use a Utility class inheriting from UBlueprintFunctionLibrary, and containing static methods we can call directly from anywhere.

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "DataTableUtility.generated.h"

/**
 *
 */
UCLASS()
class DATATABLE_API UDataTableUtility : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()

public:

	UFUNCTION(BlueprintCallable, BlueprintPure)
	static FItemData GetItemData(UObject* WorldContextObject, FName ItemID);
};
#include "DataTableUtility.h"
#include "DataTableGameInstance.h"

FItemData UDataTableUtility::GetItemData(UObject* WorldContextObject, FName ItemID)
{
	if (WorldContextObject && WorldContextObject->GetWorld())
	{
		UDataTableGameInstance* GameInstance = Cast<UDataTableGameInstance>(WorldContextObject->GetWorld()->GetGameInstance());
		if (GameInstance)
		{
			UDataTable* Table = GameInstance->GetItemDB();

			FItemData* Temp = Table->FindRow<FItemData>(ItemID, "");

			if (Temp)
				return *Temp;
		}
	}

	return FItemData();
}

Here, I only implement a method to get an Item, but we could also add a method to add an item, remove an Item, or any other method if necessary.

Using the DataTable in Blueprint

We first need to create the DataTable asset at the location specified in the GameInstance constructor and add a new row so that we can test our implementation.

datatable.png

We can now go to any Blueprint and call our newly defined method from the Utility class to get the item data anywhere.

blueprint.png