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


Попутно мы ответим на вопрос получения доступа к любому Blueprint из C++.




Со взаимодействием Blueprints все достаточно прозрачно.
Ввиду того, что мы закрыли прямой доступ к нашей базе данных, мы не можем просто так обратится к ней из Blueprint. Обратите внимание на protected: в коде ниже.


protected:

    /* This is the main Database for all Items. It contains constant common variables */
    UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "ItemsDatabase")
    TMap<FGameplayTag, FItemsDatabase> ItemsDataBase;

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


/* Used in the widget */
    UFUNCTION(BlueprintCallable, Category = "ItemDatabase")
    FORCEINLINE UTexture2D * GetItemIconTexture(const FGameplayTag & ItemNameTag) const;

BlueprintCallable как раз и означает, что данная функция может быть использована в Blueprint. Если вы читали предыдущую статью, то наверно заметили, что другие функции вызова такого атрибута не имеют. Так сделано только потому, что вызываемые ими данные на данный момент в Blueprint не понадобились. Если кому-то что-то знать не нужно — не спешим об этом сообщать.


Следующий шаг, это создание в любом Blueprint переменной типа созданной нами базы данных (в моем случае это BP_DreampaxItemsDataAsset).


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




Теперь рассмотрим как получить доступ к информации в C++.
Мы не можем просто обратиться к классу DreampaxItemsDataAsset, поскольку он не содержит никакой информации. Нам нужно заполучить доступ к BP_DreampaxItemsDataAsset.


Существует два основных метода как достучаться до Blueprint.
Сначала рассмотрим неудобный способ подключения с использованием костыля ConstructorHelpers. В данном случае это доступ к текстуре.


ASHUD::ASHUD(const class FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
    /* You can use the FObjectFinder in C++ to reference content directly in code. Although it's advisable to avoid this and instead assign content through Blueprint child classes. */
    static ConstructorHelpers::FObjectFinder<UTexture2D> HUDCenterDotObj(TEXT("/Game/UI/HUD/T_CenterDot_M.T_CenterDot_M"));
    CenterDotIcon = UCanvas::MakeIcon(HUDCenterDotObj.Object);
}

Пример взят из замечательного проекта EpicSurvivalGameSeries, идеально подходящего для изучения Multiplayer на C++. Автор поставил цель показать как можно больше методов и приемов программирования игры на C++.


Почему данный способ неудобен? Та же беда как и с DataTable — при изменении названия Blueprint или месторасположения, файл не будет найден.


Наиболее предпочтительным можно считать метод в котором мы объявляем переменную в заголовочном файле, для последующего назначения ее уже в наследованном Blueprint. Для примера выше это могло бы выглядеть так:


    UPROPERTY(EditDefaultsOnly, Category = "AimPointer")
    FCanvasIcon CenterDotIcon;

Намного проще, верно?




Теперь, зная как получить доступ к любому Blueprint, мы можем без проблем подключить нашу базу данных.


UCLASS()
class ADreampaxGameMode : public AGameMode
{
    GENERATED_BODY()

 public:

    ADreampaxGameMode(const FObjectInitializer & ObjectInitializer);

 /////////////////////////////////////////////////////////////////////////////
 //Data Bases
 /////////////////////////////////////////////////////////////////////////////
 public:
    /* Connect data base in BP for items*/
    UPROPERTY(EditDefaultsOnly, Category = "Database")
    class UDreampaxItemsDataAsset * DreampaxItemsDataAsset;

    FORCEINLINE UDreampaxItemsDataAsset * GetDreampaxItemsDataAsset() const;

Небольшое отступление для не профи на тему объявления переменных

Как человек почти незнакомый с C++, я сломал немало копий, пытаясь понять как правильно объявлять кастомные переменные.


Если стоит цель объявить переменную созданного нами класса, как, например


UDreampaxItemsDataAsset * DreampaxItemsDataAsset;
// или
class UDreampaxItemsDataAsset * DreampaxItemsDataAsset;

лично мне какое-то время было непонятно когда нужно применять class, а когда нет.


Все оказалось до боли просто.


  1. Если не ставить class, то нужно выполнить включение #include "Data/DreampaxItemsDataAsset.h", содержащее объявление этого класса.
  2. Если ставить class, то #include "Data/DreampaxItemsDataAsset.h" можно сделать уже в .cpp.
  3. И еще одна опция предыдущего пункта, если нужно объявить сразу много переменных данного класса. Непосредственно после всех #include предварительно объявить наш класс class UDreampaxItemsDataAsset;, а после объявлять переменные уже без приставки class.

Какой из этих способов правильный — не берусь сказать. Если кто-то объяснит, буду благодарен.


Делаем переменную в C++ классе ADreampaxGameMode, так как он виден только серверу, а все что связано со спауном объектов должно идти только через сервер. Данный класс является родителем для BP_DreampaxGameMode, где мы и подключим наш BP_DreampaxItemsDataAsset.


Теперь вся мощь C++ может быть использована для работы с данными нашей базы данных.


В следующей статье (наконец-то!) мы поговорим о создании инвентаря и узнаем почему нам никак не обойтись без уже созданного DataAsset.


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

Комментарии (0)