UE4 | Инвентарь для Multiplayer #2 | Подключение Blueprint к C++

habr.png

В предыдущей статье я рассказывал как создать 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 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 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.

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

© Habrahabr.ru