Недавно прилетела нетривиальная задача – хотелось дёргать и работать со значениями перечисления, созданного в редакторе UE для блюпринтов. Навскидку удалось отказаться от этого, просто переведя это само перечисление на плюсы, но осадок остался. Сел разбираться.
В документации ясно, что все перечисления из БП имеют тип UUserDefinedEnum, но если прописать UUserDefinedEnum* аргументом в открытом для блюпринтов коде C++, то окажется, что это ассет со всем блюпринтовым Enum и метод будет принимать на входе сам тип, а не то, что нужно. Конечно, такое удобно для определённых целей, но всё же, хочется принимать значения.
Чтобы протестировать то, как же изнутри выглядят такие переменные, я попробовал создать в БП свойство блюпринтового перечисления, найти его через рефлексию и достать оттуда класс. И оказалось… Там, по сути, обычный FByteProperty.
// this – это объект блюпринтового класса, где такое
// свойство есть (в примере это TestEnum)
UClass* Me = this->GetClass();
if (FProperty* Prop = Me->FindPropertyByName(TEXT("TestEnum")))
{
if (Prop->GetClass()->GetName() == TEXT("ByteProperty"))
{
const FByteProperty* ByteProp = static_cast<FByteProperty*>(Prop);
uint8 Value = 0;
ByteProp->GetValue_InContainer(this, &Value);
// Какой-нибудь вывод значения
// GEditor->AddOnScreenDebugMessage(
// 0, 500, FColor::Red, FString::FormatAsNumber(Value)
// );
}
}
Более-менее разбирающийся читатель сейчас уже лезет за вилами и топором – как же так, мы тут Cast не используем. А вот почему – вот FProperty, а вот FByteProperty . Классы связаны, но эта "склейка" иерархий классов возникает где-то на TProperty из-за чего, судя по всему, компилятор путается и Cast не работает. Замечу, что это какой-то косяк пятой версии, а, возможно, и самой студии, в четвёртой всё было норм. Ну или шаблонная магия всё поломала. Опять же, отладчик показывает именно такое. Value, понятное дело, будет содержать числовое значение перечисления.
const uint8 Value = 1;
ByteProp->SetValue_InContainer(this, Value);
Чтобы получить более-менее внятное текстовое значение перечисления нужно что-то вида:
// Дорогая операция, лучше закешировать, например передать перечисление аргументом
// PATH_TO_BLUEPRINT_ENUM – путь к перечислению, можно достать
// через Copy Reference в Content Browser
const UObject* Obj = StaticLoadObject(
UUserDefinedEnum::StaticClass(),
this,
TEXT("PATH_TO_BLUEPRINT_ENUM")
);
if (Obj)
{
if (const UUserDefinedEnum* UDE = Cast<UUserDefinedEnum>(Obj))
{
// Проверка значения на валидность
if (Value >= 0 && Value < UDE->GetMaxEnumValue())
{
// тут будет что-то типа
// <имя перечисленияя>::NewEnumerator0
const FName Name = UDE->GetNameByValue(Value);
// а тут реальное, отображаемое в редакторе,
// имя значения перечисления
const FText DisplayName = UDE->GetDisplayNameTextByValue(Value);
GEditor->AddOnScreenDebugMessage(
0, 500, FColor::Red, Name.ToString() + DisplayName.ToString()
);
}
else
{
LogRuntimeWarning(FText::FromString(TEXT("Invalid enum value passed")));
}
}
}
Стоит отметить, что при таких условиях нет никакого нормального сильно типобезопасного способа передать в плюсовый код значение перечисления БП или вернуть его. Но опять же, при таких условиях можно тупо передавать целые числа, используя ToInteger, а возвращать их через преобразование инта применяя Utilities/Enum/Byte To <имя перечисления>, обернув для проверки корректности вызов в блюпринтовую функцию.
В общем, это сложновато, но в принципе, возможный сценарий решения таких проблем. Но C++-перечисления работают все же лучше, конечно.
Комментариев нет:
Отправить комментарий