UE4 | Inventory for Multiplayer # 1 | Data Store on DataAsset

Published on August 01, 2018

UE4 | Inventory for Multiplayer # 1 | Data Store on DataAsset




DataassetIn this article I will try to uncover the meaning and methodology of creating a DataAsset as storage for various types of data, and in our case it is a library for Actors and their parameters.




A little intro that you can skip

Принять решение создавать игру около 2-х лет назад, мне помогло то, что я случайно наткнутся на информацию об Unreal Engine 4 и прочитал как это круто и просто. На деле же, человеку не умеющему писать код (язык программирования не имеет значения в данном контексте) очень сложно создать что-то, сложнее небольшой модификации стандартного набора заготовок из движка. Поэтому, изначальное желание сделать супер-мега игру, с ростом знаний о реальности данного проекта, постепенно переросло в хобби. Поднять все пласты разработки игры, от 3D моделирования и анимации, и до написания кода, для одного человека представляется мало осуществимым предприятием. Тем не менее, это хорошая тренировка для мозга.


Почему решил что-то написать?.. Наверно из-за того. что представленные мануалы либо дают очень поверхностные знания (и таких большинство), либо уж для совсем профи и содержат лишь общие указания.


Starting is almost always better from the beginning. I can not say that I always do this, but I will try to set out in a consistent manner, as far as possible.


Of course, it is best to start with the structure, but unfortunately, having a closed tool box, it is very difficult to understand exactly what can be built with their help. So let's open this box and see what is inside.




The first question to be answered. Why choose DataAsset ?


  1. Very often in articles and “tutorials” you can see the use of DataTable . Why is that bad? If you store the address to a specific Blueprint , then if you rename it or move it to another folder, you will be forced to change this address manually. Agree - uncomfortable? With DataAsset , this will not happen. All links will be updated automatically. If you are absolutely confident in the structure of your project for the years ahead, then, of course, you can use the tables.
  2. The second indisputable advantage is the ability to store complex data types, such as structures ( Struct ) , for example .

Now a little about the relative disadvantages. In fact, I only see one. This is the need to write code in C ++ .


If you already understand that without working with the code you will not do anything epic , then this is no longer a disadvantage, but a feature.


It should be noted that there is one workaround trick - use Actor as such storage. But such an application looks like the last excuse for not wanting to learn C ++, and it has the potential to end up in the final deadlock in the future.
If you are convinced that everything you need for your project can be done on Blueprint , use the tables.




Now that you have already believed that DataAsset is good, let's consider how you can create it for your project.


For those who are not at all in a tank
Есть очень подробное описание по шагам и с картинками на русскоязычном форуме, посвященному UE4. Просто погуглите по запросу ”UE4 создание DataAsset”. Сам осваивал азы именно по этому руководству около года назад.

First of all, create a C ++ Class , like Child from UDataAsset .
(All the code below is taken from my still-unborn project. Just rename the names as you prefer.)


/// Copyright 2018 Dreampax Games, Inc. All Rights Reserved.
#pragma once
/* Includes from Engine */
#include "Engine/DataAsset.h"
#include "Engine/Texture2D.h"
#include "GameplayTagContainer.h"
/* Includes from Dreampax */
//no includes
#include "DreampaxItemsDataAsset.generated.h"
UCLASS(BlueprintType)
class DREAMPAX_API UDreampaxItemsDataAsset : public UDataAsset
{
    GENERATED_BODY()
}

Now, on the basis of this class, you can safely create a Blueprint , but it’s still too early to do it ... for now it’s just a dummy. Note, however, that inclusions for textures and names have already been made.




From this point on, you start creating your storage structure. It will be redone many times, so I strongly do not recommend immediately filling up my vault. Three to five items, in our case, inventory items, is enough for tests. Sometimes, after compiling, your Blueprint can be a virgin empty, which is extremely unpleasant if you have filled out a dozen or so positions.


You can create a structure directly in the header file, since in this case, it is unlikely to be applied elsewhere. Usually, I prefer to do it as a separate header file “SrtuctName.h” , and connect it where necessary as needed.


In my case, it looks like this.
USTRUCT(BlueprintType)
struct FItemsDatabase
{
    GENERATED_USTRUCT_BODY()
    /* Storage for any float constant data */
    UPROPERTY(EditDefaultsOnly, Category = "ItemsDatabase")
    TMap<FGameplayTag, float> ItemData;
    /* Gameplay tag container to store the properties */
    UPROPERTY(EditDefaultsOnly, Category = "ItemsDatabase")
    FGameplayTagContainer ItemPropertyTags;
    /* Texture for showing in the inventory */
    UPROPERTY(EditDefaultsOnly, Category = "ItemsDatabase")
    UTexture2D* IconTexture;
    /* The class put on the Mesh on the character */
    UPROPERTY(EditDefaultsOnly, Category = "ItemsDatabase")
    TSubclassOf<class ADreampaxOutfitActor> ItemOutfitClass;
    /* The class to spawn the Mesh in the level then it is dropped */
    UPROPERTY(EditDefaultsOnly, Category = "ItemsDatabase")
    TSubclassOf<class ADreampaxPickupActor> ItemPickupClass;
//TODO internal call functions
};

Будьте аккаунты с TMap . Не реплицируется! В данном случае это неважно.


Please note that I do not use FName . According to modern trends, using FGameplayTag is considered more correct, since significantly reduces the risk of error and has a number of advantages that will come in handy later.


GameplayTag in the editor
GameplayTag

DataTable for GameplayTag exported from Excel
DataTable

It is also good practice to write functions for calling variables in the structure, such as GetSomething () . Apparently, I still need to work on my upbringing, since it was specifically in this database that I have not made such a call yet.


This is how it can be done using another database.
USTRUCT(BlueprintType)
struct FBlocksDatabase
{
    GENERATED_USTRUCT_BODY()
    /* The class put on the Mesh for the building block */
    UPROPERTY(EditDefaultsOnly, Category = "BlocksDatabase")
    TSubclassOf<class ADreampaxBuildingBlock> BuildingBlockClass;
    UPROPERTY(EditDefaultsOnly, Category = "BlocksDatabase")
    FVector DefaultSize;
    UPROPERTY(EditDefaultsOnly, Category = "BlocksDatabase")
    FVector SizeLimits;
    UPROPERTY(EditDefaultsOnly, Category = "BlocksDatabase")
    TArray<class UMaterialInterface *> BlockMaterials;
    FORCEINLINE TSubclassOf<class ADreampaxBuildingBlock> * GetBuildingBlockClass()
    {
        return &BuildingBlockClass;
    }
    FORCEINLINE FVector GetDefaultSize()
    {
        return DefaultSize;
    }
    FORCEINLINE FVector GetSizeLimits()
    {
        return SizeLimits;
    }
    FORCEINLINE TArray<class UMaterialInterface *> GetBlockMaterials()
    {
        return BlockMaterials;
    }
};

And the most important point is the database announcement:


UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "ItemsDatabase")
TMap<FGameplayTag, FItemsDatabase> ItemsDataBase;

Now you can create our Blueprint and fill it.


Example of filling DataAsset
Пример заполнения DataAsset

But before that, we will write some more call functions in order to be able to receive data from the database.


DreampaxItemsDataAsset.h
/// Copyright 2018 Dreampax Games, Inc. All Rights Reserved.
#pragma once
/* Includes from Engine */
#include "Engine/DataAsset.h"
#include "Engine/Texture2D.h"
#include "GameplayTagContainer.h"
/* Includes from Dreampax */
//no includes
#include "DreampaxItemsDataAsset.generated.h"
USTRUCT(BlueprintType)
struct FItemsDatabase
{
    GENERATED_USTRUCT_BODY()
    /* Storage for any float constant data */
    UPROPERTY(EditDefaultsOnly, Category = "ItemsDatabase")
    TMap<FGameplayTag, float> ItemData;
    /* Gameplay tag container to store the properties */
    UPROPERTY(EditDefaultsOnly, Category = "ItemsDatabase")
    FGameplayTagContainer ItemPropertyTags;
    /* Texture for showing in the inventory */
    UPROPERTY(EditDefaultsOnly, Category = "ItemsDatabase")
    UTexture2D* IconTexture;
    /* The class put on the Mesh on the character */
    UPROPERTY(EditDefaultsOnly, Category = "ItemsDatabase")
    TSubclassOf<class ADreampaxOutfitActor> ItemOutfitClass;
    /* The class to spawn the Mesh in the level then it is dropped */
    UPROPERTY(EditDefaultsOnly, Category = "ItemsDatabase")
    TSubclassOf<class ADreampaxPickupActor> ItemPickupClass;
    //TODO internal call functions
};
UCLASS(BlueprintType)
class DREAMPAX_API UDreampaxItemsDataAsset : public UDataAsset
{
    GENERATED_BODY()
protected:
    /* This GameplayTag is used to find a Max size of the stack for the Item. This tag can be missed in the ItemData */
    UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "ItemsDatabase")
    FGameplayTag DefaultGameplayTagForMaxSizeOfStack;
    /* This is the main Database for all Items. It contains constant common variables */
    UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "ItemsDatabase")
    TMap<FGameplayTag, FItemsDatabase> ItemsDataBase;
public:
    FORCEINLINE TMap<FGameplayTag, float> * GetItemData(const FGameplayTag &ItemNameTag);
    FORCEINLINE FGameplayTagContainer * GetItemPropertyTags(const FGameplayTag &ItemNameTag);
    /* Used in the widget */
    UFUNCTION(BlueprintCallable, Category = "ItemDatabase")
    FORCEINLINE UTexture2D * GetItemIconTexture(const FGameplayTag & ItemNameTag) const;
    FORCEINLINE TSubclassOf<class ADreampaxOutfitActor> * GetItemOutfitClass(const FGameplayTag & ItemNameTag);
    FORCEINLINE TSubclassOf<class ADreampaxPickupActor> * GetItemPickupClass(const FGameplayTag & ItemNameTag);
    int GetItemMaxStackSize(const FGameplayTag & ItemNameTag);
    FORCEINLINE bool ItemIsFound(const FGameplayTag & ItemNameTag) const;
};

DreampaxItemsDataAsset.сpp
/// Copyright 2018 Dreampax Games, Inc. All Rights Reserved.
#include "DreampaxItemsDataAsset.h"
/* Includes from Engine */
// no includes
/* Includes from Dreampax */
// no includes
TMap<FGameplayTag, float>* UDreampaxItemsDataAsset::GetItemData(const FGameplayTag & ItemNameTag)
{
    return & ItemsDataBase.Find(ItemNameTag)->ItemData;
}
FGameplayTagContainer * UDreampaxItemsDataAsset::GetItemPropertyTags(const FGameplayTag & ItemNameTag)
{
    return & ItemsDataBase.Find(ItemNameTag)->ItemPropertyTags;
}
UTexture2D* UDreampaxItemsDataAsset::GetItemIconTexture(const FGameplayTag &ItemNameTag) const
{
    if (ItemNameTag.IsValid())
    { 
        return ItemsDataBase.Find(ItemNameTag)->IconTexture;
    }
    return nullptr;
}
TSubclassOf<class ADreampaxOutfitActor>* UDreampaxItemsDataAsset::GetItemOutfitClass(const FGameplayTag &ItemNameTag)
{
    return & ItemsDataBase.Find(ItemNameTag)->ItemOutfitClass;
}
TSubclassOf<class ADreampaxPickupActor>* UDreampaxItemsDataAsset::GetItemPickupClass(const FGameplayTag &ItemNameTag)
{
    return & ItemsDataBase.Find(ItemNameTag)->ItemPickupClass;
}
int UDreampaxItemsDataAsset::GetItemMaxStackSize(const FGameplayTag & ItemNameTag)
{
    // if DefaultGameplayTagForMaxSizeOfStack is missed return 1 for all items
    if (!DefaultGameplayTagForMaxSizeOfStack.IsValid())
    {
        return 1;
    }
    int MaxStackSize = floor(GetItemData(ItemNameTag)->FindRef(DefaultGameplayTagForMaxSizeOfStack));
    if (MaxStackSize > 0)
    {
        return MaxStackSize;
    }
    // if Tag for MaxStackSize is "0" return 1
    return 1;
}
bool UDreampaxItemsDataAsset::ItemIsFound(const FGameplayTag & ItemNameTag) const
{
    if (ItemsDataBase.Find(ItemNameTag))
    {
        return true;
    }
    return false;
}

From multiplayer there is still nothing. But this is the first step that has been taken in the right direction.


In the next article, I will discuss the DataAsset connection methods (yes, and any Blueprint ) for reading data in C ++, and show which one is the most correct.


If you have questions or requests to disclose any aspect in more detail, please write in the comments.