Показаны сообщения с ярлыком Unreal Engine. Показать все сообщения
Показаны сообщения с ярлыком Unreal Engine. Показать все сообщения

суббота, 20 января 2024 г.

Работа с Blueprint-перечислениями (UUserDefinedEnum) из кода на С++ Unreal Engine 5.2+

    Недавно прилетела нетривиальная задача – хотелось дёргать и работать со значениями перечисления, созданного в редакторе 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++-перечисления работают все же лучше, конечно.

пятница, 19 января 2024 г.

UMaterial, UMaterialInstanceDynamic, UMaterialInstanceConstant и UpdateStaticPermutation в UE5.2+

    Недавно столкнулся со странностями в архитектуре UE 5.2+. В данном случае – с устройством материалов в движке. Материал, как уже известно, в анриле может быт много чем, но меня интересовала первичное его назначение – когда он работает как эдакий шейдер+ с биндингами переменных. Основной проблемой было то, что материалы странно вели себя при рендеринге из коммандлета, который запускался из консоли – в одну из веток поступали какие-то некорректные данные и, увы, это пока не удалось отладить. Частично из-за того, что нет простой прямой связи между объектами рендеринга и материалами. А ещё RenderDoc упорно не хотел показывать никаких вызовов графического API, хотя рендеринг явно происходил. В итоге принял решение сбросить определённый флажок, который имел вид Static Parameter Switch в наследуемом инстансе материала  и должен был решить проблему за счёт убирания глючащей ветки.

    И вот здесь позволю себе отступления, что вообще во время исполнения EG рекомендуют использовать UMaterialInstanceDynamic, который по идее позволяет избежать лишней компиляции материалов и вообще весь шустрый. В принципе, почему бы и нет? Все нужные методы у него есть, хорошо же? Я говорю, само собой, об условном UpdateStaticPermutation, аж в трёх вариантах, который по идее должен давать возможность переопределять статические параметры.

    А вот нет, ни разу. Перегрузка может не триггерить  повторную компиляцию материала. Беглый поиск по редактору и движку, показал, что там чаще используется вот эта перегрузка. Но самое смешное – это если дёрнуть метод на UMaterialInstanceDynamic, то оно счастливо упадёт с ассертом о том, что лишь UMaterialInstanceConstant можно менять такие флаги. В документации ограничения нет, отсюда и пост.

    То есть мы можем использовать в таком случае только UMaterialInstanceConstant. Замечу, что я здесь рассматриваю код, исполняемый, по сути, на этапе ещё не собранной игры, в конечном билде это может не работать.

    Справедливости ради создать UMaterialInstanceConstant в рантайме, вполне можно есть вот такой пример. Однако здесь его кидают в ассет, а это не обязательно всё же делать. Зато, кроме переопределения статических параметров, было нужно ещё и копирование настроек из определённого «предыдущего» материала.

    В итоге получилось что-то такое. Это  далеко не оптимальный код, есть ощущение, что если покопаться ещё, то можно ещё отрезать ненужные действия.


UMaterialInstanceConstantFactoryNew* MaterialFactory = 
	NewObject<UMaterialInstanceConstantFactoryNew>();
// Это совсем базовый материал, а вот Material – это инстанс, который хочется копировать. 
// Замечу, что BaseMaterial должен по идее быть UMaterial
UMaterialInterface* BaseMaterial = Material->GetBaseMaterial();
MaterialFactory->InitialParent = BaseMaterial;
const FName MatName = FName(TEXT("REPLACE_WITH_OWN_MAT_NAME"));
// В данном случае нам не интересен пакет, т.к. мы не планируем из этого делать ассет
UMaterialInstanceConstant* Instance = CastChecked<UMaterialInstanceConstant>(
	MaterialFactory->FactoryCreateNew(
    	UMaterialInstanceConstant::StaticClass(), 
        this, 
        MatName, 
        RF_Standalone | RF_Public, 
        nullptr, 
        GWarn
   )
);

// Это обычно делают перед обновлением свойств в редакторе, не стал менять
Instance->PreEditChange(nullptr); 

// Копирование исходных параметров. Замечу, что если родителем Material является 
// другой инстанс, то его тоже стоит скопировать 
Instance->CopyMaterialUniformParametersEditorOnly(Material, true);

// Далее меняем Static Parameter Switch
FStaticParameterSet Set;
// Важно использовать именно эту перегрузку, другая не возвращает базовые свитчи
Instance->GetStaticParameterValues(Set); 
for (auto& Switch : Set.StaticSwitchParameters)
{
     if (Switch.ParameterInfo.Name.ToString() == TEXT("SWITCH_NAME"))
	{
		Switch.Value = <новое значение>;
		Switch.bOverride = true;
	}
}
Instance->UpdateStaticPermutation(Set, Instance->BasePropertyOverrides, true);

// Тут документация недоговаривает, эта принудительная компиляция прекрасно работает 
// и вне FMaterialUpdateContext. 
// Возможно, в будущем всё изменится, но пока это так.
Instance->InitStaticPermutation();  

    Дополнительно можно подёргать всякие сбросы кешей, чтобы сработало, но, по идее, это не обязательно:


Instance->RecacheUniformExpressions(true);
// UPD: 02.03.2024 Вот это вообще на 5.3.2+ начало крашить при рендеринге - причины непонятны, увы
// Впрочем код выше работает и без этого.
Instance->RecacheAllMaterialUniformExpressions(true);
FPropertyChangedEvent Evt(nullptr, EPropertyChangeType::ValueSet);
Instance->PostEditChangeProperty(Evt);
Instance->PostEditChange();

    Как-то так. Это опять же, способ, который я отыскал сам ковырянием в движке и редакторе, но если у кого-то есть более простой и красивый вариант – я буду рад узнать о нём.

пятница, 17 ноября 2023 г.

Unreal Font Cache Flush

 Чтобы кэш шрифтов при оффлайновом построении не сбрасывался из-за большого числа надписей с разными шрифтами, лучше поднимать количество возможных атласов через Slate.MaxFontAtlasPagesBeforeFlush и Slate.MaxFontNonAtlasTexturesBeforeFlush . Их нет в официальной доке, но как минимум в 5.2+ они есть.

среда, 6 сентября 2023 г.

FProperty::HasMetaData в Unreal тоже выпиливается в финальной сборке

И вот это совсем какое-то говно. У меня только один вопрос: зачем говорить, что у вас есть рефлексия, если вы просто выпиливаете часть аннотаций при сборке. 

понедельник, 10 июля 2023 г.

UTextureRenderTarget2D и UTexture2D

 Сегодня узнал что UTextureRenderTarget2D::ConstructTexture2D и UTextureRenderTarget2D::UpdateTexture2D  работают только из редактора. Естественно в документации про это ни слова.

... Потихоньку пора брать свои слова о Bitrix обратно. Тут уж явно не они такие - лучшие практики в области составлении документации в действии.

среда, 28 июня 2023 г.

Если появляется странная лишняя сфера в Unreal

 Вот тут пишут что это может быть из-за некорректных настроек и отсутствия Player Start в сцене. Увы, стоит учесть.


четверг, 22 июня 2023 г.

Маленькая заметка по Cook в Unreal Engine

Cook занимается редактор в специальном режиме.  Для отладки лучше всего сразу лезть сначала в UCookerSettings, потом в класс UCookCommandlet. Как правило, основной работой занимается UCookOnTheFlyServer и его метод TickCookByTheBook, который дёргается, если кукается всё.

суббота, 12 декабря 2020 г.

Daz3D to Unreal 4 Bridge и ALS

 Недавно попробовал новый бридж между Daz3d и Unreal и, по наивности, подумал, что по идее он должен как-то сильно упрощать работу с ALS. Увы нет, это по-прежнему больно и фигурку тяжело ригануть. Зато Daz3d-to-Blender  работает хорошо. Думаю попробовать выкрутиться через перетаскивание скелетов между UE4 и Blender, может так получится быстрее.

UPD: Увы, есть различия, из-за которых, без коррекции, расчёт весов идёт неверно. Как вариант - скрипт Rigify решает эту задачу, но именование костей там другое. Есть Uefy который решает и эту задачу. Стоит он $36 (бесплатная версия по сути не поддерживается, и с Blender 2.83 не работает, хотя есть ролик ещё с более ранней версией). Свежая версия мне пока  дороговата, так что бросаю это бесплодное занятие.

UPD2: nvm, вот здесь более адекватный мануал - и он тоже 40 минут. Увы, это долго, так что всё довольно грустно. В принципе, он частично работает, но всё это очень медленно. Golden bullet are yet to come.

вторник, 18 февраля 2020 г.

Export Genesis 8 Daz3D clothed character to Unreal Engine 4.24.2 with Advanced Locomotion (3 of 3)

В этой части будет рассмотрен риг, ретаргетинг и возня с блюпринтами

Для удобства этот мануал разделён на три части:
3.  Риг и настройка проекта  (эта)

Export Genesis 8 Daz3D clothed character to Unreal Engine 4.24.2 with Advanced Locomotion (2 of 3)

В этой части будет рассмотрен экспорт из Daz Studio  в Unreal Engine, без непосредственно ретаргетинга и ковыряния в блюпринтах

Для удобства мануал разделён на три части:
2.  Экспорт персонажа из Daz3D в FBX и его импорт в UE4  (эта)

Export Genesis 8 Daz3D clothed character to Unreal Engine 4.24.2 with Advanced Locomotion (1 of 3)

Решил недавно разобраться, как перекатывать персонажей из Daz3D в UE4, и сломав первый зуб о два ролика про это (раз, два) упёрся в некоторые различия между тем, что в ролике, и что реально надо сделать. И, так как это довольно геморройно, решил переложить это в немного более короткий мануал.

Большей частью это транскрипт того, что есть в роликах, но, есть некоторые отличия из-за версии. Ещё этот мануал не подойдёт тем, кто хочет кастомизировать одежду персонажа не меняя меш, так как одежда тут запекается в персонажа.