Итерация по UENUM в Unreal Engine

в 5:16, , рубрики: async, asynchronous, blueprints, c++, for each, iterator, iterators, tutorial, uenum, Unreal Engine

Предисловие

Понадобилось мне создать панель категорий размещаемых предметов в 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js