UE4 | Инвентарь для Multiplayer #1 | Хранилище данных на DataAsset
UE4 | Инвентарь для Multiplayer #2 | Подключение Blueprint к C++
UE4 | Инвентарь для Multiplayer #3 | Структура взаимодействия
UE4 | Инвентарь для Multiplayer #4 | Создание и подключение конейнера
UE4 | Инвентарь для Multiplayer #5 | Передача информации между Сервером и Клиентом
В этой статье я постараюсь раскрыть смысл и методику создания DataAsset, как хранилища для различного рода данных, а в нашем случае это библиотека для Actors и их параметров.
Принять решение создавать игру около 2-х лет назад, мне помогло то, что я случайно наткнутся на информацию об Unreal Engine 4 и прочитал как это круто и просто. На деле же, человеку не умеющему писать код (язык программирования не имеет значения в данном контексте) очень сложно создать что-то, сложнее небольшой модификации стандартного набора заготовок из движка. Поэтому, изначальное желание сделать супер-мега игру, с ростом знаний о реальности данного проекта, постепенно переросло в хобби. Поднять все пласты разработки игры, от 3D моделирования и анимации, и до написания кода, для одного человека представляется мало осуществимым предприятием. Тем не менее, это хорошая тренировка для мозга.
Почему решил что-то написать?.. Наверно из-за того. что представленные мануалы либо дают очень поверхностные знания (и таких большинство), либо уж для совсем профи и содержат лишь общие указания.
Начинать почти всегда лучше с начала. Не могу сказать, что поступаю так всегда, но постараюсь излагать последовательно, насколько это возможно.
Конечно же, лучше всего начать со структуры, но, к сожалению, имея закрытый ящик с инструментами, очень сложно понять, что именно можно с их помощью построить. Так давайте же откроем это ящик и посмотрим что содержится внутри.
Первый вопрос, на который следует ответить. Почему именно DataAsset?
- Очень часто в статьях и “туториалах” можно увидеть применение DataTable. Почему это плохо? Если вы храните адрес к конкретному Blueprint, то при переименовании или перемещении его в другую папку вы будете вынуждены изменить этот адрес вручную. Согласитесь — неудобно? С DataAsset же такого не случится. Все связи обновятся автоматически. Если же вы абсолютно уверены в структуре своего проекта на годы вперед, то, конечно же, можно использовать таблицы.
- Второе неоспоримое преимущество — это возможность хранить сложные типы данных, например, такие как структуры (Struct).
Теперь немного об относительных недостатках. На самом деле я вижу только один. Это необходимость писать код на C++.
Если вам уже понятно, что без работы с кодом вы не сделаете ничего эпического, то это уже не недостаток, а особенность.
Надо заметить, что есть один обходной трюк — использовать Actor в качестве такого хранилища. Но такое применение выглядит как последнее оправдание нежелания учить С++, и таит в себе потенциальную возможность попасть в окончательный тупик в будущем.
Если же вы убеждены, что все необходимое для вашего проекта можно сделать на Blueprint, используйте таблицы.
Теперь, когда вы уже уверовали, что DataAsset — это хорошо, рассмотрим как можно его создать для своего проекта.
Есть очень подробное описание по шагам и с картинками на русскоязычном форуме, посвященному UE4. Просто погуглите по запросу ”UE4 создание DataAsset”. Сам осваивал азы именно по этому руководству около года назад.
Первым делом, создаем C++ Class, как Child от UDataAsset.
(Весь код, который содержится ниже, взят из моего, еще не рожденного проекта. Просто переименуйте названия как вам будет удобнее.)
/// 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() }
Теперь уже на базе это класса можно смело создавать Blueprint, но делать это пока рановато… пока это просто пустышка. Хотя, обратите внимание, включения для текстур и имен уже сделаны.
Начиная с этого момента, вы начинаете создавать структуру своего хранилища. Она будет переделываться множество раз, поэтому крайне не рекомендую сразу наполнять свое хранилище. Три-пять элементов, в нашем случае предметов инвентаря, вполне достаточно для тестов. Иногда, после компиляции ваш Blueprint может оказаться девственно пуст, что крайне неприятно, если вы заполнили уже десяток-другой позиций.
Создать структуру можно прямо в заголовочном файле, т.к. в данном случае она вряд ли будет применяться где-то еще. Обычно же, я предпочитаю делать ее в виде отдельного заголовочного файла “SrtuctName.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 };
Будьте аккаунты с TMap . Не реплицируется! В данном случае это неважно.
Обратите внимание, что я не использую FName. Согласно современным веяниям использование FGameplayTag считается более правильным, т.к. существенно снижает риск ошибки и имеет ряд преимуществ, которые нам пригодятся позже.


Хорошим тоном также является прописать в структуре функции для вызова переменных, такие как GetSomething(). Видимо, над моим воспитанием нужно еще поработать, так как конкретно в этой базе данных, я такого вызова еще не сделал.
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; } };
И самый важный момент, это объявление базы данных:
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "ItemsDatabase") TMap<FGameplayTag, FItemsDatabase> ItemsDataBase;
Вот теперь уже можно создавать наш Blueprint и заполнять его.

Но перед этим, напишем еще несколько функций вызова, чтобы иметь возможность получать данные из базы.
/// 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; };
/// 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; }
От мультиплеера тут пока еще ничего нет. Но это первый шаг, который сделан в верном направлении.
В следующей статье я расскажу о методиках подключения DataAsset (да, и любого Blueprint) для считывания данных в C++, и покажу какая из них является наиболее правильной.
Если есть вопросы или пожелания раскрыть какой-либо аспект подробнее, пожалуйста пишите в комментариях.
ссылка на оригинал статьи https://habr.com/post/418913/
Добавить комментарий