2024ΒΆ

[Data] Flexible Data

We want to create a widget depending on the object data type we have. The default widget will be a W_Object UUserWidget, but we also want to create specific widgets like W_Planet

Our subsystem will create the widgets depending on the data type (used as a type tag to have multiple overloads)

The subsytem

UCLASS()
class UARKSubSystem : public UGameInstanceSubsystem
{
    GENERATED_BODY()

public:
    UUserWidget* GetWidget(UObject* Object, const UARKObjectData*)
    {
        // Create an object widget
        auto* Widget = CreateWidget<UARKObjectWidget>(GetWorld(), ObjectWidget);
        Widget->Object = Object; // Give our object to the widget
        return Widget;
    }
    UUserWidget* GetWidget(UObject* Object, const UARKPlanetData*)
    {
        // Create a planet widget
        auto* Widget = CreateWidget<UARKObjectWidget>(GetWorld(), PlanetWidget);
        Widget->Object = Object; // Give our object to the widget
        return Widget;
    }
};

Default widget for base data

GetWidget could be call from a base class or an interface passing the object containing the data (Data->GetWidget(this);)

UCLASS(BlueprintType, Blueprintable, EditInlineNew, DefaultToInstanced, CollapseCategories)
class UARKObjectData : public UObject
{
    GENERATED_BODY()

public:
    virtual UUserWidget* GetWidget(UObject* Object)
    {
        // Calling GetWidget with this will call the UARKObjectData overload
        return GetWorld()->GetGameInstance()->GetSubsystem<UARKSubSystem>()->GetWidget(Object, this);
    }
};

Specific widget for planet data

UCLASS(BlueprintType, Blueprintable)
class UARKPlanetData : public UARKObjectData
{
    GENERATED_BODY()

public:
    virtual UUserWidget* GetWidget(UObject* Object) override
    {
        // Calling GetWidget with this will call the UARKPlanetData overload
        return GetWorld()->GetGameInstance()->GetSubsystem<UARKSubSystem>()->GetWidget(Object, this);
    }

};

The base for our widgets

UCLASS()
class UARKObjectWidget : public UUserWidget
{
    GENERATED_BODY()

public:
    // Get the object from the widget
    UObject* GetObject() const;
    // Our objects store data so we can just get the datas from a base or an interface
    UNGObjectData* GetData() const
    {
        // Using an interface
        return Cast<IARKObject>(Object)->GetData();
    }

    UPROPERTY()
    UObject* Object;
};
So if we have a W_Planet widget inheriting from UARKObjectWidget, the object associated to our widget would be an AARKPlanet containing a UARKPlanetData.

We can now Get/Make a widget from a base object/interface and get the data from this widget with a hierarchy. YAY !

[GAS] Metatags

You can create metatags that you will combine in gameplay effects to create more interactions

Let's take an example with a stun effect We want the effect to stun us, so we will be unable to cast abilities, or move our character. Instead of creating just a Control.Stun tag, we will create two metatags Meta.BlockMovement and Meta.BlockAbilityActivation

So in our effect we will grant those tags

Control.Stun
Meta.BlockMovement
Meta.BlockAbilityActivation
We had the Control.Stun tag because the effect itself is a stun and we could add other game logic that will block or dispell stuns. The metatags are not part of our game logic itself.

So now if we want to create a Control.Mute effect, we can resuse the Meta.BlockAbilityActivation tag, and for a root, we already have Meta.BlockMovement

[GAS] Native gameplay tags

Gameplay tag macros

#include "GameplayTagContainer.h"
#include "GameplayTagsManager.h"

#define TagNodeRoot(Name) struct Name : NVInternalTagTree { using NVInternalTagTree::NVInternalTagTree;
#define TagNodeRoot_() };

#define TagNode(Name) struct : NVInternalTagTree { using NVInternalTagTree::NVInternalTagTree;
#define TagNode_(Name) } Name{ #Name, this };

#define Tag(Name) NVInternalTag Name{ #Name, this };
#define TagAndCue(Name) NVInternalTag Name{ #Name, this }; NVInternalTag Name##Cue{ #Name, this, true };

#define EffectTag(Name) NVInternalTag Name{ #Name, this};

extern TMap<FGameplayTag*, FName> StrTags;

struct NVInternalTagTree
{
    friend struct NVInternalTag;

    explicit NVInternalTagTree() : Parent{nullptr}
    {}

    NVInternalTagTree(UGameplayTagsManager& InManager, const FString& Name, NVInternalTagTree* parent) : Parent{parent},
        Path{ parent->Path.IsEmpty() ? Name : parent->Path + "." + Name }
    {
        StrTags.Add(&Tag, FName(Path));
    }

    NVInternalTagTree(FString Name, NVInternalTagTree* InParent) : Parent{InParent}, Path{InParent->Path.IsEmpty() ? Name : InParent->Path + "." + Name}
    {}

    operator FGameplayTag() const { return Tag; }

private:
    NVInternalTagTree* Parent;
    FString Path;
    FGameplayTag Tag;
};

struct NVInternalTag
{
    NVInternalTag(const FString& Name, const NVInternalTagTree* const TagTree)
    {
        if (!TagTree->Path.IsEmpty()) StrTags.Add(&Tag, FName(TagTree->Path + "." + Name));
        else StrTags.Add(&Tag, FName(Name));
    }

    NVInternalTag(const FString& Name, const NVInternalTagTree* const TagTree, bool)
    {
        if (!TagTree->Path.IsEmpty()) StrTags.Add(&Tag, FName("GameplayCue." + TagTree->Path + "." + Name));
        else StrTags.Add(&Tag, FName("GameplayCue." + Name));
    }

    operator FGameplayTag() const { return Tag; }

private:
    FGameplayTag Tag;
};

Gameplay tag initialization

Initialize in the project module

IMPLEMENT_PRIMARY_GAME_MODULE(FNVModule, nverse, "nverse");

TMap<FGameplayTag*, FName> StrTags;

GasTags NVTag;
StatTags NVStatRoot;

void FNVModule::StartupModule()
{
    auto& Manager = UGameplayTagsManager::Get();

    for (auto& [TagPtr, TagName] : StrTags)
    {
        *TagPtr = Manager.AddNativeGameplayTag(TagName);
    }

    UGameplayTagsManager::Get().DoneAddingNativeTags();
}