UE4 | Інвентар для Multiplayer #1 | Сховище даних на DataAsset

Список статей

UE4 | Інвентар для Multiplayer #1 | Сховище даних на DataAsset
UE4 | Інвентар для Multiplayer #2 | Підключення Blueprint до C++
UE4 | Інвентар для Multiplayer #3 | Структура взаємодії

 

У цій статті я постараюся розкрити зміст і методику створення DataAsset, як сховища для різного роду даних, а в нашому випадку це бібліотека для Actors і їх параметрів.

Невеликий вступ, яке можна пропустити

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

 

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

 

Починати майже завжди краще з початку. Не можу сказати, що роблю так завжди, але постараюся викладати послідовно, наскільки це можливо.

 

Звичайно ж, краще всього почати зі структури, але, на жаль, маючи закритий ящик з інструментами, дуже складно зрозуміти, що саме можна з їх допомогою побудувати. Так давайте відкриємо це скринька і подивимося що міститься всередині.

 

Перше питання, на яке слід відповісти. Чому саме DataAsset?

 

  1. Дуже часто в статтях і “туторіалах” можна побачити застосування DataTable. Чому це погано? Якщо ви зберігаєте адресу до конкретного Blueprint, то при перейменуванні або переміщення його в іншу папку ви будете змушені змінити цю адресу вручну. Погодьтеся, незручно? З DataAsset ж такого не станеться. Всі зв’язки оновлюються автоматично. Якщо ж ви абсолютно впевнені в структурі свого проекту на роки вперед, то, звичайно ж, можна використовувати таблиці.
  2. Друге незаперечна перевага — це можливість зберігати складні типи даних, наприклад, такі як структури (Struct).
Читайте також  Як зробити пошук користувачів з GitHub використовуючи React + RxJS 6 + Recompose

 

Тепер трохи про відносні недоліки. Насправді я бачу тільки один. Це необхідність писати код на 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 */
//includes no

#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 в редакторі
DataTable для GameplayTag експортована з Excel

Хорошим тоном є також прописати в структурі функції для виклику змінних, такі як 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

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

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 */
//includes no

#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.срр

/// Copyright 2018 Dreampax Games, Inc. All Rights Reserved.

#include "DreampaxItemsDataAsset.h"

/* Includes from Engine */
// includes no

/* Includes from Dreampax */
// includes no

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

 

Читайте також  Історії IT юриста. Життя аутсорсинг бізнесу. Частина 1

Від мультиплеєра тут поки що нічого немає. Але це перший крок, який зроблений у вірному напрямку.

 

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

 

Якщо є питання або побажання розкрити будь-який аспект детальніше, будь ласка пишіть в коментарях.

Степан Лютий

Обожнюю технології в сучасному світі. Хоча частенько і замислююся над тим, як далеко вони нас заведуть. Не те, щоб я прям і знаюся на ядрах, пікселях, коллайдерах і інших парсеках. Просто приходжу в захват від того, що може в творчому пориві вигадати людський розум.

You may also like...

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *