Sponsored By

Topic: Referencing assets for C++ lovers

Source for UE4.26: https://github.com/klauth86/UE4Cookery/tree/main/CPP009

Artur Kh, Blogger

June 21, 2021

3 Min Read

Many developers prefer to move all logic that they can in C++ to gain more control and optimization. However, this can cause another problem and that problem is referencing assets. For example, one can face it while refactoring UMG components to custom Slate widgets. Of course, referencing can be done by creating static class, that will be a container for asset path names. But in this case renaming and assets dependencies are out, so there must be a more handy way!

Suppose, that we have many different fonts, button styles, textures and so on, imported and designed for our UI. If all our widgets are in C++ (custom Slate widgets), then to apply that suff we need a way to reference them in code. How can we solve that? First of all, we can create custom asset

 

UIData.h


#pragma once

#include "UObject/NoExportTypes.h"
#include "Fonts/SlateFontInfo.h"
#include "UIData.generated.h"

UCLASS()
class CPP009_API UUIData : public UObject {

	GENERATED_BODY()

public:

	const FSlateFontInfo& GetMenuFontInfo() const { return MenuFontInfo; }

	const FSlateFontInfo& GetGameFontInfo() const { return GameFontInfo; }

protected:

	UPROPERTY(EditDefaultsOnly, Category = "UI Data: Menu Font Info")
		FSlateFontInfo MenuFontInfo;

	UPROPERTY(EditDefaultsOnly, Category = "UI Data: Game Font Info")
		FSlateFontInfo GameFontInfo;
};

 

Ok, so that is our UI Data asset. To be able to create it right in Editor we need to add Editor module to our project and create two classes based on UFactory and UAssetTypeActions_Base. After that is done, we can create UI Data asset right in Content folder

Playing with asset in Editor

 

So, we have referenced all stuff around UI and that is fine, but how can we reference UI Data asset itself? Let see. We can create another object class and that will be

 

#include "GameDataSingleton.h"


#pragma once

#include "UObject/NoExportTypes.h"
#include "GameDataSingleton.generated.h"

class UUIData;

UCLASS(Blueprintable)
class CPP009_API UGameDataSingleton : public UObject {

	GENERATED_UCLASS_BODY()

public:

	UUIData* GetUIData() const { return MyUIData; }

protected:

	UPROPERTY(EditDefaultsOnly)
		UUIData* MyUIData;

public:

	static UGameDataSingleton* GetInstance() { 
		static UGameDataSingleton* instance = Cast<UGameDataSingleton>(GEngine->GameSingleton);
		return instance;
	}
};

UGameDataSingleton::UGameDataSingleton(const FObjectInitializer& ObjInitializer) :Super(ObjInitializer) {}

 

Here we have created static function to access singleton instance, however we don't create it by our selves, rather we are trying just to pick it up from GEngine->GameSingleton. We can create blueprint around UGameDataSingleton and fill all props in it, lets name it BP_GameDataSingleton. To make it all happen to work, there is only one step left - we need to change Project Settings:

Project Settings

It will be Engine, who will create UGameDataSingleton by provided class and will store it in GEngine->GameSingleton.

 

For demonstration purposes we can create Slate widget in project Game mode class and set some of widget props by accessing UGameDataSingleton:

 

CPP009GameModeBase.h


#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "CPP009GameModeBase.generated.h"

class STextBlock;

UCLASS()
class CPP009_API ACPP009GameModeBase : public AGameModeBase
{
	GENERATED_BODY()
	
public:

	virtual void BeginPlay() override;

	virtual void EndPlay(EEndPlayReason::Type reason) override;

protected:

	TSharedPtr<STextBlock> MyTextBlock;
};

 

CPP009GameModeBase.cpp


#include "CPP009GameModeBase.h"
#include "Kismet/GameplayStatics.h"
#include "Slate/SGameLayerManager.h"
#include "Slate/SceneViewport.h"
#include "Widgets/Text/STextBlock.h"
#include "GameDataSingleton.h"
#include "UIData.h"

void ACPP009GameModeBase::BeginPlay() {
	Super::BeginPlay();

	auto uiData = UGameDataSingleton::GetInstance()->GetUIData();

	if (auto controller = UGameplayStatics::GetPlayerController(this, 0)) {
		auto localPlayer = Cast<ULocalPlayer>(controller->Player);
		if (localPlayer && localPlayer->ViewportClient) {
			auto layerManager = localPlayer->ViewportClient->GetGameLayerManager();
			if (layerManager.Get()) {
				layerManager->AddWidgetForPlayer(localPlayer, SAssignNew(MyTextBlock, STextBlock).Text(FText::FromString("Hello, Game Data Singleton!")).Font(uiData->GetMenuFontInfo()), 0);
			}
		}
	}
}

void ACPP009GameModeBase::EndPlay(EEndPlayReason::Type reason) {
	Super::EndPlay(reason);

	if (MyTextBlock.IsValid()) {	
		if (auto controller = UGameplayStatics::GetPlayerController(this, 0)) {
			auto localPlayer = Cast<ULocalPlayer>(controller->Player);
			if (localPlayer && localPlayer->ViewportClient) {
				auto layerManager = localPlayer->ViewportClient->GetGameLayerManager();
				if (layerManager.Get()) {
					layerManager->RemoveWidgetForPlayer(localPlayer, MyTextBlock.ToSharedRef());
				}
			}
		}
		MyTextBlock.Reset();
	}
}

 

Finally, we have

Hello, Game Data Singleton!

Read more about:

Blogs

About the Author(s)

Daily news, dev blogs, and stories from Game Developer straight to your inbox

You May Also Like