Предисловие
Понадобилось мне создать панель категорий размещаемых предметов в UI. Как в градостроительных симуляторах. В наследие мне достался уже готовый UENUM, который в будущем будет изменен.
Естественно, очень не хотелось вручную перемещать и настраивать каждый отдельный виджет. Так еще и заниматься этим в будущем с изменениями категорий. Хотелось чего-то простого и универсального. Чтобы вот вызвал условный For Each Loop
и сгенерировал все как надо, еще и не обязательно только для этого енама.
Выход был найден! Если мы создаем UENUM, то unreal сам генерирует всю нужную информацию и создает для нас UEnum класс, который является UObject. Нужно лишь правильно использовать эту информацию.
Создание uenum итератора
Для удобства будем использовать UBlueprintAsyncActionBase
, хотя это и не единственный способ.
Создадим класс-наследник UEnumIterateAction
.
Код из .h
файла:
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintAsyncActionBase.h"
#include "EnumIterateAction.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnEnumIteration, const uint8, EnumValue);
/**
*
*/
UCLASS()
class YOUR_MODULE_API UEnumIterateAction : public UBlueprintAsyncActionBase
{
GENERATED_BODY()
public:
/** Наш execution выход при каждой итерации.*/
UPROPERTY(BlueprintAssignable)
FOnEnumIteration OnEnumIteration;
/** Сама функция, которую будем вызывать(почти).*/
UFUNCTION(BlueprintCallable, BlueprintInternalUseOnly)
static UEnumIterateAction* IterateByEnum(UEnum* Enum, const bool SkipMax = true);
protected:
virtual void Activate() override;
private:
TWeakObjectPtr EnumToIterate;
uint8 bSkipMax : 1;
};
Что мы тут делаем?
Сильно вдаваться в подробности класса UBlueprintAsyncActionBase
не буду, т.к. статья не совсем про это. Опишу лишь часть.
Создаем саму функцию для итерации IterateByEnum
, которая фактически, будет нашим конструктором. Первым аргументом будет тот самый UEnum
, который мы и будем использовать для итерации. Т.к., при создании енамов из блупринтов, движок добавляет скрытое значение MAX, то неплохо было бы его пропускать, по желанию. Потому добавим соответствующий флаг как второй аргумент.
Делегат OnEnumIteration
, будет вызываться при каждой итерации. По сути, это будет тем самым "асинхронным" execution пином. Важно тут лишь использовать uint8
, т.к. это Byte
тип, который мы будем конвертировать в значение енама в blueprints.
Переопределяем Activation
функцию базового класса. Она и будет телом нашей вызываемой функции.
Т.к. сама функция IterateByEnum
у нас в виде конструктора, то нам надо где-то хранить аргументы для нашего тела(Activation
). Потому создаем дополнительные 2 поля EnumToIterate
и bSkipMax
для этой цели.
На этом подготовка закончена. Приступим к реализации.
Реализация итератора
Код из .cpp
файла:
// Fill out your copyright notice in the Description page of Project Settings.
#include "AsyncActions/EnumIterateAction.h"
UEnumIterateAction* UEnumIterateAction::IterateByEnum(UEnum* Enum, const bool SkipMax)
{
const auto Action = NewObject<UEnumIterateAction>();
if (!IsValid(Action)) return nullptr;
Action->EnumToIterate = Enum;
Action->bSkipMax = SkipMax;
return Action;
}
void UEnumIterateAction::Activate()
{
Super::Activate();
if (!EnumToIterate.IsValid()) return;
for (int32 Index = 0; Index < EnumToIterate->NumEnums(); ++Index)
{
// Пропускаем скрытые значения
if (!EnumToIterate->HasMetaData(TEXT("Hidden"), Index))
{
// Получаем текущее значение енама и проверяем, не максимальное ли
const int64 EnumValue = EnumToIterate->GetValueByIndex(Index);
if (bSkipMax && EnumValue == EnumToIterate->GetMaxEnumValue()) continue;
// Преобразуем значение енама в наш byte, для конвертации в blueprints
const uint8 EnumValueAsByte = static_cast<uint8>(EnumValue);
OnEnumIteration.Broadcast(EnumValueAsByte);
}
}
}
Что мы тут делаем?
С реализацией нашей функции IterateByEnum
все понятно. Как уже упоминалось выше, это просто аналог конструктора, так что останавливаться на ней не будем.
В функции Activate
мы обращаемся к нашему сохраненному енаму и проходимся самым обычным циклом for
, как по какому-нибудь массиву.
Значения енамов unreal хранит как int64
, потому, имея индекс, мы получаем именно такой тип данных. Нас же интересует Byte
, т.к. только его можно конвертировать в блупринтах в значение нашего енама.
Важно учесть, что нет особого смысла отображать скрытые значения, потому мы их пропускаем.
Также, мы добавили флаг для пропуска того самого максимального значения, которое тоже, зачастую, ценности не несет. Максимальное значение в виде int64
, потому, перед конвертацией в Byte
, сравниваем именно EnumValue
с результатом функции GetMaxEnumValue
.
Итог
Как видно, все прекрасно работает!
Автор: MMadmer