UE4 | Инвентарь для Multiplayer #1 | Хранилище данных на DataAsset

от автора


DataAssetВ этой статье я постараюсь раскрыть смысл и методику создания DataAsset, как хранилища для различного рода данных, а в нашем случае это библиотека для Actors и их параметров.


Небольшое вступление, которое можно пропустить

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

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

Начинать почти всегда лучше с начала. Не могу сказать, что поступаю так всегда, но постараюсь излагать последовательно, насколько это возможно.

Конечно же, лучше всего начать со структуры, но, к сожалению, имея закрытый ящик с инструментами, очень сложно понять, что именно можно с их помощью построить. Так давайте же откроем это ящик и посмотрим что содержится внутри.


Первый вопрос, на который следует ответить. Почему именно DataAsset?

  1. Очень часто в статьях и “туториалах” можно увидеть применение DataTable. Почему это плохо? Если вы храните адрес к конкретному Blueprint, то при переименовании или перемещении его в другую папку вы будете вынуждены изменить этот адрес вручную. Согласитесь — неудобно? С DataAsset же такого не случится. Все связи обновятся автоматически. Если же вы абсолютно уверены в структуре своего проекта на годы вперед, то, конечно же, можно использовать таблицы.
  2. Второе неоспоримое преимущество — это возможность хранить сложные типы данных, например, такие как структуры (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 считается более правильным, т.к. существенно снижает риск ошибки и имеет ряд преимуществ, которые нам пригодятся позже.

GameplayTag в редакторе

GameplayTag

DataTable для GameplayTag экспортированная из Excel

DataTable

Хорошим тоном также является прописать в структуре функции для вызова переменных, такие как 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 и заполнять его.

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

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

Но перед этим, напишем еще несколько функций вызова, чтобы иметь возможность получать данные из базы.

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; }

От мультиплеера тут пока еще ничего нет. Но это первый шаг, который сделан в верном направлении.

В следующей статье я расскажу о методиках подключения DataAsset (да, и любого Blueprint) для считывания данных в C++, и покажу какая из них является наиболее правильной.

Если есть вопросы или пожелания раскрыть какой-либо аспект подробнее, пожалуйста пишите в комментариях.


ссылка на оригинал статьи https://habr.com/post/418913/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *