GameDev: разработка игр
7.62K subscribers
978 photos
207 videos
1 file
1.41K links
Всё про геймдев для девелоперов, художников, левел- и геймдизайнеров, сценаристов

Разместить рекламу: @tproger_sales_bot

Другие каналы: @tproger_channels

Другие наши проекты: https://tprg.ru/media
Download Telegram
Геймпад PlayStation, а подсказки Xbox

При Steam Input Gamepad Emulation игра видит эмулируемый Xbox-контроллер. Поэтому по одному XInput-вводу нельзя выбрать правильные значки для устройства, которое игрок держит в руках.

У Valve есть цепочка для получения значков из Steam:

1. GetControllerForGamepadIndex связывает XInput-слот с handle контроллера Steam Input.
2. GetActionOriginFromXboxOrigin переводит исходную Xbox-кнопку в origin реального устройства.
3. GetGlyphPNGForActionOrigin возвращает путь к локальному PNG. Его ещё нужно загрузить как текстуру игры.

Если значки нарисованы в своём стиле, предусмотрите запасной вариант для незнакомого origin. Можно взять изображение Steam или через TranslateActionOrigin подобрать ближайший известный вариант.

В справочнике API есть важная оговорка: перевод Xbox-кнопки не учитывает переназначения пользователя. Эта схема относится к Gamepad Emulation. Для контроллера, работающего через полноценный Steam Input API, GetControllerForGamepadIndex handle не вернёт.
⚡1
Большая стена полезнее мелкого декора

При occlusion culling в Godot 4.5 объект убирается из отрисовки, только когда окклюдер полностью закрывает его AABB — ограничивающий прямоугольный параллелепипед. Поэтому стена между комнатами хорошо подходит для occlusion culling, а мелкий декор часто почти ничего не скрывает.

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

Для настройки включите Use Occlusion Culling, добавьте OccluderInstance3D и запеките статические окклюдеры. Одного флажка недостаточно, вся видимая сцена автоматически в них не превращается. Движущиеся объекты исключите из запекания маской слоёв.

В документации 4.5 есть режим просмотра буфера и сравнение числа draw calls. Перемещать сам OccluderInstance3D во время игры дорого: это требует перестройки BVH. Сравнивать время кадра с включённым и выключенным culling нужно на характерных ракурсах своей сцены.
⚡1❤1
В SDL3 конвертация звука переехала из Put в Get

При переносе аудиокода с SDL2 стоит заново посмотреть, где выполняется работа с потоком:

— SDL_PutAudioStreamData копирует исходные данные в очередь без преобразования.
— SDL_GetAudioStreamData выполняет нужную конвертацию и ресемплинг при чтении.

В SDL2 преобразование происходило при добавлении данных, а чтение забирало уже готовый результат. Поэтому прежнее распределение нагрузки между местами вызова не переносится автоматически.

SDL_AudioStream принимает порции разного размера и умеет менять входной и выходной форматы на ходу. В обоих вызовах len измеряется в байтах. Get возвращает фактически прочитанное число байтов или -1 при ошибке.

Это семантика API, доступного с SDL 3.2.0, а не обещание ускорения. Подробности: Put и Get.
❤1
Вложенные Persist-объекты меняют порядок загрузки сейва

У учебного примера сохранений в документации Godot 4.5 есть важное условие: узел группы Persist не должен быть дочерним для другого сохраняемого узла той же группы.

Сам подход простой. Игра собирает узлы Persist, вызывает их save() и записывает каждый словарь отдельной JSON-строкой в user://savegame.save. При загрузке пример очищает сохраняемые объекты, создаёт экземпляры сцен и возвращает свойства.

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

Есть и граница формата: JSON не хранит Vector2, Vector3 и Color напрямую. Их придётся переводить в поддерживаемые значения и собирать обратно. Этот пример даёт основу сериализации, но не решает миграции сейвов между версиями игры.
Размер агента меняет navmesh

Один коридор может быть проходимым для мыши и слишком узким для человека. В Recast навигационная сетка строится под размеры и возможности конкретного агента, а не просто по свободному месту на уровне.

Четыре параметра задают эту разницу:

— радиус отвечает за ширину агента
— высота определяет, под каким потолком он поместится
— максимальный уклон ограничивает крутизну поверхности
— высота шага задаёт препятствия, на которые он способен подняться

Для нескольких типов персонажей в одном окружении могут понадобиться разные сетки. При построении Recast переводит геометрию в воксели, фильтрует их и снова получает полигональную сетку. Размер вокселя позволяет менять точность и время построения.

Введение Recast Navigation отдельно напоминает: navmesh упрощает геометрию для поиска пути. Полученный коридор полигонов уточняется в маршрут через string pulling. Ни сетка, ни этот маршрут не заменяют геометрию коллизий или сплайн анимации.
❤2🔥1
Субботний скриншотник! Принесите в комментарии кусочек своей игры: скриншот, гифку, видео, демку или ссылку. Можно показать и свой канал.

Что сдвинулось за неделю: анимация, меню, новая механика? Незаконченный прототип тоже подходит. Добавьте пару слов о том, над чем работаете, и загляните к другим авторам: спросите о решении или поддержите понравившуюся работу.

@make_game
❤4
Сейв в облаке ещё должен попасть в нужную папку

Для Steam Auto-Cloud важны не только маски файлов, но и соответствие путей между ОС. В актуальной документации Valve у нового Root Path значение OS Restriction по умолчанию — Cross-Platform. Valve советует оставить его даже игре, которая пока выходит на одной платформе. Привязка к отдельной ОС ограничит дальнейшую синхронизацию этих файлов.

Если сохранения в Windows, macOS и Linux лежат в разных каталогах, настройте Root Overrides. Они сопоставляют исходный путь с расположением файлов на другой ОС. У исходного Root для этого должен стоять Cross-Platform.

Сами сейвы стоит разделять по пользователям Steam. А настройки графики и другие параметры конкретной машины лучше не синхронизировать вместе с прогрессом.

Auto-Cloud переносит выбранные файлы перед сессией и после неё. Совместимость формата сейва между сборками остаётся задачей игры. Dynamic Cloud Sync, позволяющая получать изменения во время сессии, требует отдельной обработки и не включается в игру одной настройкой путей.
У sensor в Box2D два участника

Для зоны обнаружения без физического толчка в Box2D используется sensor. На одном body можно совместить обычные формы и сенсорные. Но одного isSensor = true недостаточно, чтобы получать события.

В документации Box2D 3.2.0 флаг enableSensorEvents по умолчанию выключен. Включить его нужно у обеих участвующих форм, включая обычную форму, входящую в сенсор.

Дальше порядок такой: выполнить b2World_Step, получить b2World_GetSensorEvents, обработать массивы begin и end. Событие end может прийти из-за уничтожения формы, поэтому перед обращением к ней проверьте ID через b2Shape_IsValid.

Ещё две детали из официального руководства. Сенсорная форма может вносить вклад в массу, для исключения этого вклада задают нулевую плотность. А при быстром движении и сенсора, и другой формы надёжные события перекрытия не гарантированы. Для такого случая автор рекомендует ray cast или shape cast.

Проверяйте версию своего физического движка: эти флаги и поведение относятся к Box2D 3.2.0, их нельзя автоматически переносить на 2.x или любой встроенный порт.
❤1
Музыка продолжает играть, но регулятор её громкости больше не действует? Если перед этим переименовали аудиошину в Godot, проверьте, куда теперь направлен AudioStreamPlayer.

По описанию Audio buses в документации 4.5, player хранит имя шины. Перестановка шин связь сохраняет, а переименование её теряет. Тогда звук направляется в Master. После смены имени нужно заново проверить назначение у проигрывателей.

Отсутствие эффекта в Web-сборке может иметь другую причину. Режим Sample не поддерживает эффекты шин, для них нужен Stream. Если потоки не включены, у этого режима выше задержка. Сами эффекты применяются в порядке их расположения на шине, а молчащие несколько секунд шины и эффекты Godot отключает автоматически.

Страница помечена Work in progress: её ещё не обновили для 4.5, и описание может быть устаревшим. Это ориентир для диагностики, поведение нужно проверить в версии своего проекта.
A* можно подсказать, где на карте стены

Эвристика расстояния видит близкую цель, но не знает, что дорога к ней начинается с обхода в другую сторону. В разборе Red Blob Games Амит Патель показывает другой источник оценки: заранее рассчитанные расстояния до ориентиров, или landmarks.

Для каждого ориентира сохраняют стоимость кратчайшего пути от узлов карты. В неориентированном графе разность расстояний от двух узлов до одного ориентира даёт нижнюю оценку пути между ними:

h(B, X) = |d(B, L) - d(X, L)|

Можно взять максимум по нескольким ориентирам и допустимой исходной эвристике. Сам A* менять не нужно. Расчёт используется повторно в запросах с разными целями, но требует памяти: число ориентиров × число узлов × размер числа. Удачное расположение ориентиров тоже зависит от карты.

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

На странице есть интерактивные карты, в том числе лабиринты и уровни Cogmind. Материал датирован июлем 2026 года, и автор прямо отмечает, что ещё не применял метод в реальном проекте. По демо можно изучить поведение эвристики, но производственного замера здесь нет.
❤2
Объекты совпали на экране, но столкновения между ними нет

В Defold это возможно, если объекты принадлежат разным мирам, загруженным через collection proxy. У каждого такого мира собственная физика. Коллизии, триггеры и ray casts работают только внутри него, даже при одинаковых координатах объектов из двух миров.

Здесь важна разница двух компонентов:

Collection factory создаёт экземпляры содержимого коллекции в текущем мире.
Collection proxy загружает отдельный мир. После load или async_load приходит proxy_loaded, затем можно выполнить init и enable.

Миры могут обмениваться сообщениями по полному URL объекта. А напрямую вызвать go.get_position для объекта из другого мира нельзя. Для передачи ввода в загруженную коллекцию объект с proxy должен приобрести ввод.

Руководство Defold отмечает и расход памяти: новый мир обходится заметно дороже обычного экземпляра иерархии. Для множества однотипных объектов уместнее collection factory, а proxy полезен там, где нужна именно отдельная коллекция со своим миром.
This media is not supported in your browser
VIEW IN TELEGRAM
SpringArm3D учитывает ближнюю плоскость камеры

В ролике из документации Godot камера приближается к персонажу у стены. За это отвечает SpringArm3D: на каждом физическом кадре он проверяет путь на заданную длину и сдвигает дочерние узлы к месту столкновения.

Для этого важна иерархия узлов. Если Camera3D — непосредственный ребёнок плеча, а Shape пуст, проверка использует пирамиду ближней плоскости камеры. Так учитывается объём у объектива, а не только линия до него.

Если камера вложена глубже, а Shape не задан, остаётся луч, который может пропустить препятствие у края кадра. В руководстве Godot 4.5 показаны обе схемы.
❤3
Media is too big
VIEW IN TELEGRAM
Эти полосы меняют направление вместе с входным полем. Rune Skovbo Johansen показал такой шум в разборе Phacelle Noise: результат — вектор с компонентами косинуса и синуса. Из его фазы можно получить разные профили полос.

У автора два варианта. Simple получает направление одним входным параметром и проходит 4 × 4 ячейки на пиксель. Sampled считывает направление для каждой из этих 16 ячеек.

Разница видна на резких изменениях поля. У Simple там могут появляться разрывы, а Sampled устраняет этот вид разрыва. Sampled требует больше выборок входного поля, но оценить прирост FPS по статье нельзя: подробного GPU-бенчмарка в ней нет.
❤1
Для шага нужна площадка сверху

Ступенька достаточно низкая, а персонаж всё равно упирается? Чтобы autostep в Rapier сработал, нужно соблюсти несколько условий:

— высота укладывается в заданный максимум
— сверху хватает горизонтального места, узкий уступ не подойдёт
— персонаж уже касается пола перед препятствием

В руководстве JavaScript 0.21 есть и отдельная настройка: разрешать ли подъём на динамические тела. Прыжок рядом с платформой не должен превращаться в автоматический подъём на неё.

Контроллер вычисляет допустимое перемещение, но применяет его к телу или коллайдеру уже код игры. Он обрабатывает поступательное движение, а не вращение.
❤2
Для этого рельефа Rune Skovbo Johansen взял функцию высоты и её градиент, затем добавил несколько октав направленных борозд. Течение воды по всей карте фильтр не моделирует. Каждая точка вычисляется независимо, что удобно для GPU и генерации чанков.

Горные гребни получаются ветвящимися и чёткими, но физически связной речной сети здесь нет. Автор замечает, что линии стока иногда обрываются посреди склона. Фильтр воспроизводит внешний вид эрозии.

В разборе разработки можно проследить, что дали затухание слоёв и частичная нормализация. Код опубликован под MPL 2.0, в статье есть ссылки на реализации и Shadertoy.
🔥2
This media is not supported in your browser
VIEW IN TELEGRAM
Один меш, разные фазы движения рыб

В этом демо одна рыба. Посмотрите на голову: маска ограничивает волну и скручивание задней частью тела, чтобы модель не изгибалась целиком.

В разборе Godot для экземпляров MultiMesh показан приём: INSTANCE_CUSTOM.x сдвигает фазу времени, а .y позволяет менять скорость. Общий меш и шейдер не заставляют всех рыб двигаться синхронно.

При повороте с маской результат нужно смешивать через mix. Простое умножение на маску сожмёт модель.

Страница ветки 4.5 помечена Work in progress и содержит старые поля API. Её стоит читать как объяснение анимации, а не готовый код для текущей версии. Движение стаи и её AI в этот этап не входят.
❤1
Маленький PNG может занимать много памяти

Уменьшить размер игры для скачивания и расход памяти текстур — две разные задачи. После декодирования небольшой файл может занять гораздо больше места. Для GPU нужны свои форматы сжатия, а результат с потерями придётся проверить глазами.

В Defold настройка устроена так:

.texture_profiles задаёт форматы для платформ.
Правила путей назначают профили ресурсам. Специфические glob-правила ставят раньше общих.
BasisU хранит промежуточное представление, которое транскодируется в подходящий формат GPU.

В руководстве по профилям описаны и ограничения: несколько форматов могут увеличить bundle, а сжатие — время сборки. Поддержку формата нужно сверять с целевыми устройствами.
This media is not supported in your browser
VIEW IN TELEGRAM
Волна стала пилой

Это пример из Unity 2017.4.4f1, Surface Shader для Built-in. Вершины сетки стоят с шагом 1, а длина волны равна 2. Точек слишком мало, чтобы передать изгиб, и поверхность превращается в стоящую пилу. Дело здесь не в сломанных нормалях.

Автор разбора Waves советует выбирать длину волны больше удвоенной длины ребра сетки и оставлять запас по плотности. Анимация текстуры эту проблему не решит: геометрию двигает vertex displacement. Связь длины волны с сеткой пригодится и сейчас, хотя код для современного URP или HDRP придётся адаптировать.
😴2❤1
У реки не одна ближайшая точка

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

Rune Skovbo Johansen описал Point Cloud Sound: по точкам вдоль реки рассчитываются суммарная громкость, взвешенное направление и spread. Для каждого звукового компонента используется один источник. Чем короче средний вектор направлений, тем шире spread. Так звук может окружать слушателя, а не прыгать между берегами.

У итогового источника нужно отключить встроенное затухание по расстоянию, иначе оно учтётся дважды. Метод рассчитан на общий фон вроде воды и листвы. Объединять в нём независимые шаги или реплики отдельных персонажей не стоит. В статье — фрагменты алгоритма, не готовая библиотека.