
Вы знаете, что игры бывают очень архитектурно «грязными» внутри? Но это не помешало им продаваться миллионами копий, или быть написаннами одним человеком на фреймворке для браузерок, или на Lua поверх библиотеки для геймджемов, или вообще ребенком в бесплатной версии юнити. При этом мощный кастомный движок с ECS, job‑системой, своим рендером и рефлексией повсюду вы тоже знаете, но игра на нем, скорее всего лежит третий год у вас в беклоге, так и не сыграная даже пару часов.
Попросили меня по старой дружбе, где‑то с полгода назад, помочь с разработкой и выводом игры в Steam. Ребята до этого занимались нефтью, и накопив деньжат, решили, что называется оставить след в индустрии. Если честно, я несколько отвык от такого «детского» кода и простых решений, что меня несколько удивило, хотя и вернуло на грешную землю из объятий ентрепрайза. Но я сразу оговорюсь, что простая архитектура это не оправдание плохого кода, а способ выбрать, где именно вы позволяете себе быть сложным. Бюджет сложности конечен... прежде всего размером вашей натуральной оперативкой, и тратить его надо туда, где игрок это увидит.
Начнём с того, что переусложнение это не глупость/лень, но почти всегда результат ума и добросовестно выполненной работы. Человек прочитал грамотную статью, посмотрел сильный доклад, поработал в хорошей студии и пытается делать правильно, но «правильно» пришло из контекста большой или специализированной разработки, где по‑другому уже не получается делать и есть «культура перформанса» или, если хотите, «проклятие масштаба»... выбирай свое.
Доклады про data‑oriented design, правильную укладку структуры в кеш‑линию, а там всего‑то 64 байта, разговоры про виртуальный вызов и косвенность, которые убивают предсказатель переходов, всё это правда... Но эта правда была рассказана людьми, у которых на экране одновременно живёт сто тысяч сущностей, я серьезно... у нас в проекте «кончился» uint16 для идентификаторов ecs. И еще есть бюджет в 16 миллисекунд на весь кадр вместе с рендером, физикой и звуком на все эти «стотыщмильенов» ентити (не у всех всё надо считать, это уже другой разговор). А у ребят было порядка двухсот врагов в топ‑даун шутере, но у них тоже иногда лагало.
Бюджет кадра при 60 fps: 16.6 мс 200 сущностей, наивный Update() с виртуальным вызовом: ~200 × 50 нс = 0.01 мс плюс промахи кеша на каждой: ~200 × 200 нс = 0.04 мс Итого игровая логика: ~0.05 мс из 16.6 мс Тот же кадр, реальные расходы: рендер и отправка команд: 4–8 мс физика: 1–3 мс GC-пауза в неудачный кадр: 2–15 мс ← вот здесь боль
То есть вы можете оптимизировать свою логику в двадцать раз и не получить ни одного лишнего кадра, потому что лаг приходил из аллокаций в горячем цикле. Знаете что они в итоге сделали? Опустили таргет рейт до 30 и о чудо... лаги пропали, потому что движок перестал пытаться натянуть сову на глобус. Я тут немного слукавил, у них было два дня до показа игры паблишеру и лезть что‑то менять в коде, ну такое... можно и не поехать к паблишеру на показ.
Карго‑культ интерфейсов

Большие студии любят интерфейсы и какой‑нибудь IAudioSystem живет не потому, что красиво, хотя это действительно красиво, но у движка есть пять платформ и три звуковых бэкенда, и бэкенды эти допиливаются в процессе разработки, поэтому интерфейс тут будет способом поменьше звать на ревью людей из звуковой подсистемы.
В команде из трёх человек с одной целевой платформой у интерфейса будет одна (всего одна) реализация и один мок, который никто не запускает, потому что тестов нет... хе‑хе, еще одна беда маленьких команд. Но вы все равно заплатили за абстракцию полную цену большого движка и не получили ничего из того, ради чего она вообще существует, оно того стоило? А теперь мы идем на игровую конференцию слушать умный доклад от ребят, которые пишут звуковую подсистему и хотят продать нам свой IAudioSystem
Что видит читатель доклада с GDC: IWeaponSystem ──► WeaponSystemPS5 ├──► WeaponSystemXbox └──► WeaponSystemPC (три платформы, три команды, общий контракт) Что получается в проекте на троих: IWeaponSystem ──► WeaponSystem └──► WeaponSystemMock // не используется с 2023 года (одна реализация, два лишних файла, минус переход по коду)
Отдельная любовь у программистов писать большую систему под десять тысяч объектов до того, как в игре поняли, что они там вообще будут. А если не будет... чтож, мы получили выброс дофамина от написания крутой системы, которая никому не нужна. Написать пулинг, чанки и SoA‑раскладку приятно, потому что задача формальная и в конце есть измеримый результат, а вот выяснять, почему в вашей игре скучно неприятно и результат там не измеряется. Поэтому программисты честно и с удовольствием оптимизируют игру, в которую пока никто не играет... поэтому не давайте программистам делать игру, пусть её делает дизайнер монстров, уровней и механик.
А что сделали те, у кого получилось

Лука Галанте, разработчик слотов, в начале 2021 забил на денежную и не пыльную работу и на HTML5-фреймворке Phaser (вдохновившись мобильной Magic Survivor если я правильно помню) написал Vampire Survivors. Ранний доступ в декабре 2021 стал одним из главных хитов десятилетия, потеснив разные AAA релизы и породив новый жанр, и только в 2023 году игра переехала на Unity ради производительности и кроссплатформенности. Т.е. сначала игра нашла аудиторию на браузерном фреймворке, потом под неё подвели «взрослый» стек.
Соло‑герой LocalThunk два с половиной года на движке Löve (изначально как побочный проект для резюме на позицию диза) делал Balatro, которая к январю 2025 продалась тиражом в пять миллионов копий. Löve это Lua плюс тонкая обёртка над SDL, вообще без редактора, без сцены и без инспектора. Моддеры, разобравшие игру, обнаружили внутри обычный Lua, кучу дерева и разного цвета субстанции, функции по 2-3к строк и минимум комментариев.
Stardew Valley, Undertale, Minecraft, Unturned, Phasmophobia (XNA, GameMaker, LWJGL, Unity, Unity) и опять один‑два человека, никакого своего движка, никакой архитектурной экзотики, «плохо пахнущий код», зато авторы потратили свои силы на содержание, а не на инфраструктуру кода.
И, чтобы вы не думали, что игровой движок это бесплатно — то когда Vampire Survivors переезжал с Phaser на Unity, это заняло около года работы и выросшей до десяти человек команды. То есть простой стек дешев, но не исчезает бесследно и вам придется заплатить когда у вас уже есть деньги и люди, но придется. Я думаю это хорошая сделка, но это именно сделка, а не подарок.
Берите скучный стек

Готовый коммерческий движок это не признание в творческой импотенции, хотя мнение это живо и активно тиражируется на конференциях, и сам я когда смотрю на очередное поделие на Unreal и не могу отличить его от неделю назад другого игранного поделия, меня терзают смутные сомнения, что не зря об этом говорят.
Но скучный стек это больше про управлению рисками и Unity, Unreal и Godot уже отладили загрузку ассетов, звук, поддержку геймпадов, экспорт билда за вас и, что важнее тех, кто планирует консоли, там нормальные платформенные бэкенды и минимум проблем с сертификацией, у Microsoft есть целый отдел, который ревьювает только игры на юньке.
И еще... выбирая чужой движок, вы берёте на себя и чужие бизнес‑решения, и если кому‑то из топ‑менеджмента не хватит пары шекелей на яхту побольше, он не раздумывая выкатит очередную гениальную идею и вы вдруг начнете платить за установки, бесплатные установки из playstore... потом конечно одумаются, но доверие уже подорвано и многие скажут «спасибо, не надо». Цена скучного стека в экономии годов разработки, но однажды кто‑то в не вашем совете директоров захочет табун на пятьсот кобыл в розовом цвете, и вам опять придется с этим смириться.
Монолит лучше связки систем
Это обычно вызывает больше всего возмущения, когда я предлагаю команде отказаться от развесистых подсистем. Потому что в игровом коде большой прямолинейный класс, который делает много вещей, часто выгоднее композиции из восьми маленьких сущностей. Не потому, что он лучше написан, а потому что игровая логика меняется, и стоимость изменения становится важнее стоимости чтения и красоты кода. А вот когда у вас будут деньги и преданные фанаты, можно подумать и красоте и философии, но вариант, который стыдно показывать на код‑ревью будет и если вариант вам стыдно показывать на код‑ревью, ну чтож... просто будет стыдно, от стыда еще никто не умирал, зато быстро и через полчаса нужная фича в игре.
class GameManager { Player player; WaveSpawner spawner; UpgradeList upgrades; float runTime; void Update(float dt) { runTime += dt; spawner.SpawnIfNeeded(runTime); player.Update(dt); enemies.UpdateAll(dt); CheckCollisions(); if (player.xp >= nextLevelXp) OpenUpgradeScreen(); hud.Refresh(player, runTime); } }
А вот так было «правильно» изначально (EventBus + ServiceLocator + IUpgradeProvider + TimeScaleService) и каждая правка занимала несколько часов. Надо было понять кто подписан на PlayerLevelUp, в каком порядке отработают подписчики, почему TimeScaleService уже применился к снарядам, и добавить четвёртый сервис, чтобы этого не происходило.
Абстракция это ставка на то, что вы угадаете ось изменений на месяц, два или год вперед. В движке, в рендере, в загрузке ресурсов вы её угадываете, там оси известны, но в логике игры вы её не угадаете почти никогда, особенно если команда небольшая и ищет свою нишу, а дизайнер меняет формулировку задачи от итерации к итерации. Тогда красивый интерфейс превращается в препятствие, которое надо сначала разобрать, а потом собрать обратно. Но монолитный менеджер очень легко превращается в глобальный синглтон, к которому обращаются из ста мест, и вот тогда всё действительно становится плохо, но это совсем другая история, и чинить вы её будете после выхода игры, если будете...
Синхронно и линейно это нормально

Событийные шины, реактивность и асинхронность в геймплее продаются под лозунгом «слабая связанность». Продают эти штукенции обычно тем ребятам, которые заняты написанием IAudioSystem и у которых в запасе есть годик, чтобы это осмыслить и внедрить, но инди покупает вместе с этим только невозможность прочитать порядок выполнения кадра по коду. Классический баг тут выглядит что урон применился после того, как UI обновил полоску здоровья, поэтому игрок некоторое время видит старое значение, а на слабом железе видит его еще дольше и таких багов и зависимостей становится все больше и больше, и вам нужен отдельный человек, чтобы следить за асинхронными системами.
// скучно, зато весь кадр помещается в голову void Tick(float dt) { input.Poll(); player.Move(dt); enemies.Move(dt); physics.Step(dt); combat.ResolveDamage(); // урон считается здесь и только здесь world.RemoveDead(); hud.Refresh(); // UI всегда видит финальное состояние }
А в событийной версия порядок определяется тем, кто в какой момент успел подписаться, а это, в свою очередь, определяется порядком загрузки сцены, который меняется при добавлении префаба или системы. Но как только вам понадобилось настраивать порядок выполнения скриптов, вы уже потеряли контроль над кадром и теперь приходится договариваться с разными системами, делать посредников, заводить фейковые сущность. Просто запомните, что один явный Tick в большинстве случаев дешевле любых настроек.
Платить приходится за всё и линейный Tick растёт вместе с игрой, становясь в какой‑то момент размером в двести вызовов и половина из них условные. Это неприятно, но это можно прочитать сверху вниз за минуту, в отличие от графа подписок, который читается под отладчиком и только в конкретном запуске.
Ложная сложность

Всё вышеописанное имеет смысл только в том случае, если высвободившиеся силы уходят в саму игру, что к счастью почти всегда верно и силы разработчиков уходят в более интересные механики и «сочный» геймплей, что в докладах называют juice. На самом деле это с десяток дешёвых приёмов вроде тряски камеры, паузы на ударах, правильного масштабирования, частиц, отдельный звук на каждое событие. Ни один из этих приёмов не требует ни архитектуры, ни сложных технических систем... но все они требуют времени на подбор чисел, и вдумчивого тестирования на живом игроке.
А сложная система с пулом на десять тысяч юнитов, потребует профилирования и неординарного инженерного мышления, но никак не повлияет на fps игры, потому что fps это нелинейная величина и падение со 120 до 90 стоит вам 2.8 мс, а падение с 30 до 25 стоит 6.7 мс. В реальности это вообще разные величины и ваши выигранные 0.2мс дадут оптимизацию меньше одного процента фпс и займут пять процентов кода, потому что ваша интуиция про горячие места ошиблась до запуска профайлера.
Где сложность оправдана так это системы сборки и тестирования, и автоматическая сборка с воспроизводимым билдом, автотестами, соблюдением платформенных требований, которые у Sony, Microsoft и Nintendo разные — всё это скучно, всё это не видно в трейлере, не видно программистам и дизайнерам, но та работа, которая отделяет «мы сделали игру» от «игра вышла». Можно сделать игру, но не выпустить её в срок или попасть на штрафы паблишера, провалив сертификацию из‑за неправильной обработки отключения геймпада. Согласитесь это намного обиднее, чем не написать свою ECS.
Где простая архитектура ломается
Было бы нечестно закончить на том, что надо просто писать попроще и всё будет хорошо. Не будет, потому что у простоты есть свои проблемы, а вот мириться с ними или нет, уже выбирает каждый сам.
Простой код со временем превращается в болото от отсутствия границ. И даже минимальный набор границ окупается всегда и везде, а стоит примерно ничего: чтобы данные игры были отдельно от игровой логики, игровая логика отдельно от представления, а представления от пользовательского интерфейса. У вас получается ноль интерфейсов, но уже нельзя случайно поменять здоровье игрока из обработчика нажатия кнопки.
И еще сохранения... это, пожалуй, единственное место, где я готов защищать проектирование заранее, потому что тут вы будете обязаны сделать обратную совместимость, а её не сделать нормально без хорошей архитектуры. И вам в любом случае придется её делать, потому что игроку, который потратил сорок часов и потерявший сейв... мягко скажем, может очень много накинуть на вентилятор, а если таких игроков не один, и даже не сто?
Все остальное может быть настолько дубово, насколько у вас хватит совести. Можете спросить у автора очень занимательной игрушки VVVVVV, игра отличная, но опять сделана из субстанций и деревяшек. (github.com/TerryCavanagh/VVVVVV/blob/master/desktop_version/src/Logic.cpp)

Архитектура это инструмент
Игра как код существует только в вашей голове. Игрок не откроет репозиторий, и не узнает, был ли у вас ECS, использовался попахивающий интерфейс, и как часто вы мучалиGameManager в рендере. Ресурс времени разработчика ограничен и его надо тратить не на сложность, а на то, что видно на экране, и понимать, что любое красивое инженерное решение имеет свою цену. Вы видели в рецензиях Steam отзыв «Геймплей отличный, визуал потрясающий, но за синглтон в игровом цикле ставлю двух мейерсов из десяти»? Вот и я не видел...
Комментарии (14)

ardraeiss
16.09.2026 18:06и породив новый жанр
Просто люди массово забыли Crimson Land, хотя он и в стиме даже жив. Кримсонлэндлайк было бы уместным жанром. И да, он чуть отличается(в основном "сам и целься, и ходи, и стреляй", то есть шутер) - но автошутер "а ты только целься и уворачивайся" в нём тоже есть.
Т.е. сначала игра нашла аудиторию на браузерном фреймворке, потом под неё подвели «взрослый» стек.
Из аналогичного "из браузера в взрослое-сурьёзное" я Shapez первым делом думаю(просто потому что нравится). Первая игра так и имеет браузерный вариант на io вместе со стим-версией, а вот вторая уже резко юнитёвая и трёхмерно-графичная, с красотами.

noidol
16.09.2026 18:06Тоже был сильно удивлён, что из-за хайпа Vampire Survivors, целый жанр подобных игр стал называться в честь нее, хотя существуют много дольше, чем названная игра.
Тут по логике получается, что Asteroids (игра 1979 года, на минуточку!) не является Vampire Survivors like, только из-за того, что выстрелы происходят по воле игрока. Ну такое...
Crimson Land отличная игра, с кооперативом на одном компьютере... Эх...

Moog_Prodigy
16.09.2026 18:06Ох, Shapez это моя любовь, что первая что вторая в 3д. А какая там музыка!

Sixshaman
16.09.2026 18:06Просто есть люди, которым интересно игру делать, а есть которым нравится ковыряться в архитектуре, ECS, подсистемах и пр.
У первых игровые идеи бьют фонтаном. Им некогда думать над красотой кода; они одну механику реализовали и у них ещё 8 на очереди. В такие моменты нужно делать фичи быстро, пока глаза горят.
Вторые наоборот — в голове вообще ни одной игровой идеи, и видимо никогда не будет. Зато есть архитектура, взаимодействие подсистем, управление ресурсами движка, оптимизация расходов памяти, и в этом пипец как интересно ковыряться! А в игры пускай дети играют.

stmirage
16.09.2026 18:06Рад был услышать что-то такое от человека, который так погружен в игровые движки (клянусь, прочитаю вашу книгу, она лежит на полочке!). Я сам редкая нудила. даже на геймджем, я как глупыш начинаю думать о глобальной системе событий и менеджере уровней. Хотя за всё время самой главной проблемой всех игр, что я делал и делали мои знакомые, было то, чтобы в них поиграло хотя бы человек 20 ть)
Самыми успешнымми проектами были игры, которые мы создавали для каких-то ивентов и мероприятий фоном во время работы. Типа скоро день рождения компании или большое мероприятие - давайте немного его разнообразим!
в эти игры играли сотни человек, соревновались , писали комментарии, в личку, обсуждали на кухне классные дизайнерские ходы. Никто не вспомнит, что в одной из поделок у меня была задумана на будущее прекрасная система глобальных состояний уровня, которая позволяляла бы делать в будущем события на уровне легко расширяемыми.
Но зато я до сих пор помню текстовую РПГ в офисе на день рождения компании, где никто не мог понять, как победить босса, которым был директор компании. Разгадка была в том, что надо сказать ему больше слов, чем он скажет тебе, и это было отличной шуткой про его манеру говорить длинные речи

PickaPickaMan
16.09.2026 18:06Сценарий про нефтяников, которые накопили денег и решили ворваться в геймдев, достоин отдельной экранизации)
mynameco
каждый новый сотрудник приходит и начинает говорить - у вас все сложно, долго искать. а по сути - не хочу разбираться в устройстве систем. тоже постоянно предлагают все викунуть и написать в чуть ли не в одном классе.
а потом, а вот это зачем так сложно? вот для этого. - ааа. а вот это почему так, вот для того того то - ааа.
сложно, согласен. зато у нас есть тулсет для редактирования данных для дизов, есть фсм редактор для квестов, есть бехейвир три редактор для логики поведения. есть поддержка сейвов, есть поддержка длц. есть кросс сцен ссылки. консоль для рантайиа, где можно все посмотреть даже в релизе.
dalerank Автор
эм... есть такая категория вновь приходящих, выкидыватели называются. Это просто люди такие, и тут ничего уже не поделать, но варианты есть %) "учить, лечить, мочить" (с)
Kromster80
С одной стороны всё так, а с другой - вот недавно выделили время на рефактор и выкинули около 30тыщ строк кода из самых разных мест, потому что вот это остатки от прошлой реализации, вот это вот костыли для WinXP, а вон там швабры от порта под компилятор FPC, а эти директивы условной компиляции мы вообще-то вынесли в настройки и теперь они часть нормального кода, а не спешал дебаг билд. И получилось, что теперь проще продираться сквозь ядро, и стало чуть меньше вопросов "а для чего это?".
PickaPickaMan
Главное в таком азарте случайно не выпилить неочевидный костыль, на котором держалась физика прыжка, а то потом выяснится, что директива компиляции фиксила баг какого-нибудь древнего драйвера)
mynameco
был какой то прикол как программист передавал проект другому ) там про остров, комнату с газами, большой вентилятор и воздушный шар )
dalerank Автор
Хехе, мы тоже вот отпилили один раз поддержку Google Stadia, а потом оказалось что пропали облачные сейвы в стиме. На вопрос "а какого рожна у нас стим юзал гугловый механизм?" оказалось что они очень похожи по апи и сделали, как сделали. Пришлось ревертнуть почившую уже лет как пять стадию, чтобы не писать заново поддержку для стима. Чудны дела твои, неизвестный рефактормэн
PickaPickaMan
Граница тут тонкая, иногда система реально перегружена лишней шелухой, но выяснять это надо с тестами в руках, а не на эмоциях в первый месяц работы)