Unreal Engine
February 28, 2023

Подключение C++ к UMG блюпринтам с помощью BindWidget

Бен UI пишет о том, как управлять логикой из C++ и настраивать UI в блюпринтах.

Один из самых распространенных вопросов, который возникает у тех, кто начинает создавать пользовательские интерфейсы на базе C++, следующий:

Как я могу управлять виджетами, созданными в блюпринтах, из C++?

Ответ звучит так: мета-свойство BindWidget.

Простой пример BindWidget

UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UTextBlock* TitleLabel;

UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UImage* IconImage;

Хотя об этом не говорится в документации по UPROPERTY (), это один из самых полезных тегов для вас, как для разработчика пользовательского интерфейса.

Пометив указатель на виджет как BindWidget, вы можете создать виджет с таким же именем в отнаследованном блюпринте вашего класса C++, а во время выполнения обращаться к нему из C++.

Вот пошаговый процесс создания работающего теста:

  1. Создайте подкласс UUserWidget в C++.
  2. Добавьте в него переменную-член класса, которая является UWidget* или его подклассом (например, UImage, UTextBlock и т. д.).
  3. Пометьте ее UPROPERTY (meta=(BindWidget)).
  4. Запустите редактор и отнаследуйте блюпринт от вашего класса C++.
  5. Создайте виджет с тем же типом и точным именем, что и ваша переменная-член класса.
  6. Теперь вы можете получить доступ к виджету из C++.

Код примера

BindExample.h

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "BindExample.generated.h"

UCLASS(Abstract)
class UBindExample : public UUserWidget
{
    GENERATED_BODY()

protected:
    virtual void NativeConstruct() override;

    UPROPERTY(BlueprintReadOnly, meta=(BindWidget))
    class UTextBlock* ItemTitle;
}

BindExample.cpp

#include "BindExample.h"
#include "Components/TextBlock.h"

void UBindExample::NativeConstruct()
{
	Super::NativeConstruct();

	// ItemTitle can be nullptr if we haven't created it in the
	// Blueprint subclass
	if (ItemTitle)
	{
		ItemTitle->SetText(FText::FromString(TEXT("Hello world!")));
	}
}

Теперь скомпилируйте C++ и откройте блюпринт, в который вы добавили свойство ItemTitle. При компиляции блюпринта будет выдана ошибка, если внутри вашего UserWidget нет виджета TextBlock с именем ItemTitle.

Компиляция блюпринта с отсутствующим свойством BindWidget-marked

Если мы теперь добавим виджет Text и изменим его имя на соответствующее нашему C++ файлу, то ошибка компиляции исчезнет, и когда мы запустим игру, текст будет изменен на «Hello world!».

Блюпринт с правильно названным текстовым блоком

Плюсы и минусы использования BindWidget и C++

Почему мы хотим использовать BindWidget и C++ вместо того, чтобы просто написать всю нашу логику в блюпринтах?

Плюсы:

  • Легче поддерживать сложную логику на C++. Никакой борьбы со спагетти в блюпринтах.
  • Проще для совместной работы, не нужно беспокоиться о блокировке блюпринтов в системе контроля версий.

Минусы:

  • Требуется повторная компиляция, чтобы увидеть изменения. Этих временных затрат можно в некоторой степени избежать, используя **Live Coding**.
  • Не-программистам сложнее увидеть, как заполняются данные, поскольку логика переносится из блюпринтов в C++.

Дополнительные виджеты

Бывают ситуации, когда у вас есть базовый класс C++ и множество различных отнаследованных блюпринтов. Например, базовый класс кнопки может иметь общую логику на C++, но вы можете создать множество различных отнаследованных блюпринтов, по одному для каждого визуального стиля. В некоторых визуальных стилях могут быть иконки, в других — текст, поэтому имеет смысл сделать виджеты текста и изображения иконок необязательными.

Чтобы сделать виджет необязательным, используйте инструкцию meta=(BindWidgetOptional). При этом не будет показана ошибка, если в блюпринте нет виджета с таким именем.

Пример BindWidgetOptional

UPROPERTY(BlueprintReadWrite, meta=(BindWidgetOptional))
UTextBlock* ButtonLabel;

UPROPERTY(BlueprintReadWrite, meta=(BindWidgetOptional))
UImage* IconImage;

Причуды BindWidget

  • Компиляция блюпринта без виджета, отмеченного как BindWidget, приведет к ошибке. Однако запустить игру все равно можно. Если вы хотите избежать сбоев в этой ситуации, вам нужно проверить, что переменная не является nullptr.
  • Обычно при установке флага «Is Variable» на виджете UserWidget он (BindWidget? — непонятно) становится доступным в редактора кода (вкладка Graph). Однако если существует свойство с таким именем, помеченное как BindWidget, единственный способ сделать его доступным в блюпринте — это добавить инструкцию BlueprintReadOnly или BlueprintReadWrite к его тегу UPROPERTY ().
  • По умолчанию переменные, определенные в родительском классе C++, отображаются в списке переменных, только если установлен флажок «Show Inherited Variables» (см. скриншот).
  • Виджеты с пометкой BindWidget, обнуляются в конструкторе C++, они инициализируются позже в жизненном цикле объекта. Если вам нужно сделать что-то при создании объекта, используйте функцию NativeConstruct ().
Отображение переменных, определенных в родительском классе C++

Оригинал: https://benui.ca/unreal/ui-bindwidget/