среда, 24 апреля 2024 г.

libPNG 1.6.43 vs stb_image 2.14 на загрузке PNG

    Я  как-то в своё время решил заюзать stb_image для загрузки картинок в движке, ибо он был быстрый, а ещё - легко встраивался в приложение, без страшной возни с зависимостями, условными autoconf и прочим. И в какой-то момент немного поправили в pro.graphon: вот, дескать, libpng быстрее. Беглый поиск привёл меня к тесту, и сомнения стали смущать: а что, если, правда, быстрее? Не пора ли съехать? Забегая вперёд, я хочу отметить сноску в этом посте, где автор пишет, что на малых PNG, у него stb иногда и выигрывал по перфу. И это почти мой случай - у меня нагрузка состоит из POT текстур от 512px до 2048px.

понедельник, 22 апреля 2024 г.

hrtp-demake: релиз демки и босса второго акта.

 



    Наконец могу спокойно релизнуть демку проекта, так как второй босс-файт готов. Впрочем эта часть блога переезжает, и теперь новые новости по проекту будут на https://hrtp-demake.site/ . Сюда только буду кросспостить и делать комментарии не для основного блога проекта. Собственно, скачать билд демки можно по ссылке

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

    Если я вернуть к нему, то, скорее всего  в первую очередь займусь профилированием и вознёй с Optick, т.к. в ряде конфигураций есть явно проблемы с перфом. Ну и третий акт само собой.


четверг, 18 апреля 2024 г.

Про A Thousand Suns

 

(тысяча солнц жи!)

    Вчера по наводке из другого блога, посмотрел текущие вышедшие серии этого соперника "Любви, смерти и роботов" и, в итоге, напомнил этим почему не смотрю 99,5% современного кино.

среда, 17 апреля 2024 г.

О Blood Midnight Blossom

(картинка, сгенерированная условным https://www.craiyon.com/ по запросу)

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

    Недавно натолкнулся на просто невероятное описание визуальной новеллы. Вдумайтесь: "Главная героиня – обычная девушка по имени АННИГИЛЯЦИЯ, которая живёт в маленьком городке А. Она получает по наследству огромный долг своего отца и в отчаянии ищет выход. На улице она встречает незнакомку в плаще, назвавшуюся КАТАРСИС, которая предлагает ДЕМОНСКУЮ подработку на неделю." . Честно говоря, надо отдать должное автору - тут на фоне остальных ВН волей-неволей такое привлекает внимание. Я успел смачно посмеяться над сиим эпичным синопсисом, и вроде бы даже собирался пройти мимо... Но любопытство погубило кошку, и, я всё же не удержался и купил это в стиме.
    Я скажу честно - у меня неоднозначное отношение к русским VN. Да, я успел в юности навернуть пару-тройку японских новелл, но условное "Бесконечное Лето" для меня - просто ужасная графомания начиная с самого начала. Но, в целом я ничего против не имею, хоть у большинства довольно примитивное и скучное описание, не вызывающее интереса.
    ... И игра мне понравилась. Неплохой сеттинг, не самая тривиальная история (даже несколько поворотов сюжета завезли!). Для новеллы по цене за две шаурмы - вообще кайф. Я провёл пару вечеров, выбил большую часть концовок, за минусом тролльной хорошей. 
    Пожалуй, в игре есть два недостатка - крайне короткий сценарий, всё же хотелось бы больше деталей о персонажах, их предыстории и прочем. Оно, конечно, видно, что есть заимствования элементов сеттинга из других (новелл?? аниме?? манги??), но блин, хочется побольше деталей о прошлом героев, их жизни и прочем.
    Второй, более серьёзный - это рисовка. Я хз, где там нейросети, но, как по мне, строго НЕ ХВАТАЕТ иной раз фонов, особенно для всяких экшонистых моментов и динамики. Герои, к несчастью, сильно по рисовке напоминают витюберов и просто просят сделать дизайн персонажей получше.
    Но купить и порофлить можно, надеюсь, у автора появится БЮДЖЕТ, и я в будущем увижу более крутые новеллы от него.

воскресенье, 31 марта 2024 г.

Очередной девлог-апдейт по разработке hrtp-demake

 

(взято отсюда: https://vk.com/wall-143107500_73606)
    Я уже давно не писал ничего на эту тему в первую очередь, потому что был сильно занят на основной работе. Но прогресс по задаче есть: в принципе, все пункты из поста сделаны. Игра на довольно мощной машине грузится не 13с, а 3-5, а то и меньше, памяти отъедает в пике не более 400 Мб, в принципе жить можно. Тесты прошли на основном компе тоже хорошо, особых проблем нет.

    Казалось бы - вперёд, выкладывай и кайфуй. Но, увы, пока сильно радоваться не приходится из-за некоторых технических проблем. Производительность на слабых машинах оставляет желать лучшего, но это не фатально - есть ощущение, что если собрать, наконец, полноценный релизный билд со всеми жёсткими оптимизациями и протестировать его, можно в принципе забить. Это не значит, что профилировать не придётся, но я, скорее всего, отложу такое на будущее. Хочется всё же идти вперёд, а то разглядывать текущий контент, откровенно говоря, задолбало и демотивирует. Ещё последний тест на Windows XP показал странные графические артефакты - возможно, у старья, конечно, отваливается древняя встроенная видеокарта, возможно просто что-то с мипами. Игра при этом работает хорошо, геймплей это не блокирует. Но, скорее всего, и это отложу на потом, хоть мне их и хочется отладить побыстрее и такие проблемы самого раздражают. Юмор ситуации заключается в том, что именно релизный билд с оптимизацией на древнем слабеньком компе с допотопной виндой показывает скорость получше, чем на сравнительно свежем без. В общем - поле для исследования есть, найти бы на это время, как и на планируемую интеграцию Optick для профилирования.

    В итоге из-за абзаца выше, я не могу порадоваться результатам - ясно, что нужно потратить больше времени. Но пока оставлю всё как есть, скорее всего, потому, что есть и более приоритетные вещи. Они, конечно, не выглядят серьёзными, и я надеюсь, что это не потребует кучу времени, как в прошлый раз. Попробую их перечислить:

  • Небольшой рефакторинг, на уровне полутора классов
  • Нужно немного поправить контент: пара спрайтов не нравится и  перерисовать будет хорошо
  • Нужно ещё добавить одно мелкое изображение для конца игры. Это давно было запланировано, но увы, руки доходят только сейчас.
  • Немного поправить тему в конце игры, возможно, даже как-то более серьёзно
  • Ну и немного начать делать по третьей части (это хочется сделать до выкладывания, но, может, и отложу).
  • Стейджинговые скрипты (уже почти что готово, но надо довести до ума).

  Ну и хоть на паре локальных машин посмотреть, насколько всё в релизной сборке работоспособно. Должно быть хорошо - на Windows XP, по сути, она и тестировалась, но неясно являются ли проблемы выше чисто местным колоритом или это что-то иное.

    После чего я, наверное, выложу текущий билд как есть, иначе, если править всё — то в силу того, что приходится делать это всё в одиночку, оно может и до конца года растянуться. А так - хоть что-то.


вторник, 20 февраля 2024 г.

Про "Архитектуру видеоигровых миров. Уровень пройден!"

     Закончил недавно читать "Архитектуру видеоигровых миров. Уровень пройден!" и теперь могу описать своё мнение об этой книге.

    В целом, в первую очередь - это не гайд и не учебник по левелдизайну. Да, кое-какие советы книга даёт, но они в целом, вряд ли чем-то помогут в сложных случаях.
    Однако, как набор эссе — книга однозначно топ. Мне довольно сильно понравилась идея рассмотреть внутриигровые миры с позиции современного градостроительства и логики архитектуры. Вышло интересно, некоторые моменты действительно вызывают комментарий вида "да как же я сам не догадался?". Однако иногда авторы фиксировались на некоторых странных моментах. Например, условному Найт-Сити из CP2077 досталось по полной - его и так рассматривали, и так. И в некоторых местах пришли к парадоксальному, на мой взгляд, выводу, что как город он не работает. С другой стороны - ну не работает, и ладно. Город как бы сам по себе в стиле ретрофутуризма выполнен, а там далеко не всегда всё было логично и в книгах. Ещё не понравился длительный анализ Control - я в неё не играл, но футажи её архитектуры довольно скучны, как по мне, разбирать там особо нечего.
    Отдельно забавным выглядит раздел 3, где в качестве элементов рассмотрены санузел, дверь, лестницы и монументальное искусство. Да-да, именно в таком порядке. Несмотря на крайнюю забавность, подглава про санузлы написана довольно хорошо, в принципе, читать хочется. Но порядок, честно говоря странный всё равно. Про монументальное искусство стоит сказать отдельно - рассматривать некоторые оставшиеся артефакты, серьёзно изменяющие мир игры, и создающие точки интереса, как монументы (в качестве примера - затонувшая машина из диско элизиума) - по-своему  оригинальная мысль.
    Отдельно зашла типографика - очень хорошо сделано, цветные иллюстрации, и у меня, кроме пары опечаток, нет замечаний.
    Думаю, читать стоит.

суббота, 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();

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

четверг, 18 января 2024 г.

Про «Деконструкцию виртуальных миров» Кадикова

Недавно я натолкнулся на пост об уязвимостях в дизайне уровней.  Он довольно хороший и интересный, но я позволю себе не согласиться с одним тезисом (об этом ниже). Но вдобавок у меня оказалась книга этого же автора, так что – чтобы два раза не ходить, напишу короткое мнение  и о записи в блоге, и о книге.