Разработка аркадной гонки на rust с нуля: опыт создания инди‑“единорога”

Разработка аркадной гонки на Rust с нуля: как я пытался сделать "единорога" и что из этого вышло

Четырнадцать месяцев назад я сел за новый проект - аркадную гонку. План был по‑настоящему наивный: за четыре месяца собрать небольшую, простую игру "для своих", без особых амбиций и пафоса. Разумеется, все пошло иначе. Масштаб постепенно рос, требования к себе повышались, а сроки давно перестали хоть как‑то напоминать изначальный план.

У меня есть странный, местами вредный принцип: делать как можно больше своими руками. Это касается и игровых движков - я их просто не использую. Много лет назад я попробовал писать игры "с нуля" на Rust и с тех пор упорно продолжаю в том же духе. Каждый год придумываю новый проект: сначала был чат для VR‑шлемов, потом космический симулятор... потом еще один... и еще. В итоге у меня накопилось три разных космосима, и именно один из них стал основой ядра будущей гонки.

Идея сделать гонку жила в голове давно. Я очень люблю этот жанр и всегда ощущал, что в нем осталась интересная незанятая ниша. Мне хотелось создать не абстрактный "автосим" и не бездумную аркаду, а нечто посередине - игру, которая управляется и ощущается как симулятор, но порог входа у нее как у аркады. Такой "единорог": аркадная гонка, которая по ощущениям ближе к серьезным гоночным играм. Похожие проекты существуют, но ни один не попадал ровно в тот набор критериев, который был важен для меня.

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

Для достижения такого баланса я решил: двигатель и трансмиссия должны симулироваться достаточно правдоподобно - сцепление, ручное переключение передач, кривая крутящего момента, реакция на перегрев. А вот покрышки, наоборот, должны вести себя чуть более предсказуемо и "аркадно", чтобы машину было приятно контролировать даже без знаний физики. В теории это звучало разумно, на практике же именно шины стали главным источником боли и откровений.

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

Спустя примерно неделю, когда я только начал разбираться в основах моделирования шин и продумывать базовую физику, по сцене уже ездила первая тестовая машинка. Примитивная, угловатая, с сырым управлением, но она хотя бы двигалась, поворачивала и тормозила. Этот момент всегда кажется магией: когда из набора формул, систем и костылей вдруг рождается нечто, что похоже на игру.

Шины: простая задача, которая оказалась адом

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

За основу я выбрал модель динамики шин Ханса Б. Пасейки. Эта формула используется во множестве гоночных игр разного уровня - от простых до вполне серьезных симуляторов (самые хардкорные нередко строят свои модели, но логика похожа). В реальности же внедрить ее в конкретный проект оказалось намного сложнее, чем прочитать статью и вставить пару формул в код.

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

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

Разные игры решают эту задачу по‑своему. Я перебрал несколько подходов, пока методом проб, ошибок и закомментированных километров кода не выработал собственный метод смешивания сил. Гордиться им в академическом смысле сложно, но для проекта он оказался рабочим и, насколько я могу судить, довольно уникальным. Итог: несколько первых месяцев разработки ушли почти полностью на тонкую настройку шин и поведения автомобиля на трассе.

Как трансформировалась визуальная часть

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

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

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

От сетевой "игрушки для друзей" к полноценной кампании

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

Так родилась идея одиночной кампании. Вместе с ней сформировалась главная тема - ремонт и обслуживание автомобилей. Игрок не просто катается по трассам, а зарабатывает деньги, вкладывает их в свою машину, чинит повреждения, подбирает запчасти. От чистого комбат‑рейсинга с пушками и минами я в итоге отказался: фокус сместился с разрушения к восстановлению.

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

Город‑меню, гараж и экономика

Параллельно с механиками я думал и о подаче. В какой‑то момент я играл в Neon White и меня зацепила идея главного меню в виде карты города с локациями, куда можно "заходить" вместо обычных пунктов меню. Это вдохновило сделать не просто список опций, а небольшой живой городок, по которому игрок перемещается между точками интереса.

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

Экономика строилась вокруг простого принципа: каждую гонку игрок либо рискует, либо играет безопасно. Большие риски - больше потенциального дохода, но и шанс угробить машину, попасть на дорогой ремонт и временно "выпасть" из гонок. Я долго крутил числа, стоимость деталей, награды за позиции, чтобы добиться ощущения, что каждая гонка - важное событие, а не просто серия одинаковых заездов.

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

Персонажи, диалоги и "социальная" часть

Со временем в игре появились первые персонажи и диалоги. Они пока играют вспомогательную роль, но уже позволяют немного оживить мир: можно задать им вопросы, поговорить о машинах, вызвать на дуэль или попросить одолжить автомобиль на сложную гонку.

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

Сетевая игра: от идеи до реальности

Несмотря на то что одиночная кампания стала центральной частью проекта, я не отказался от изначальной мечты - дать возможность соревноваться с друзьями. Как только каркас кампании более‑менее устаканился, я вернулся к сетевой части.

Задача здесь - не просто передавать координаты машин по сети, а обеспечить, чтобы физика, написанная под одиночную игру, корректно работала в условиях задержек, лагов и рассинхрона. Я сознательно оставил элементы той же физической модели и управления, чтобы не было ощущения, что в онлайне "другая игра". Это потребовало пересмотра многих решений: пришлось думать о предсказании движения, сглаживании расхождений, защите от читерства.

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

Vulkan и свои границы

Рендер в проекте построен на Vulkan, и это отдельная история про то, как нельзя выбирать инструменты только потому, что они "крутые". Vulkan - мощный и гибкий API, но он буквально требует от разработчика быть и инженером, и художником, и немного шаманом. Количество кода, который нужно написать, чтобы просто вывести треугольник, заметно больше, чем в более высокоуровневых решениях.

Сначала я воспринимал это как интересный вызов: полный контроль над памятью, очередями, синхронизацией. Со временем стало ясно, что столь низкоуровневый подход съедает чудовищное количество времени, которое могло бы уйти на контент и геймдизайн. В какой‑то момент я честно признал себе, что для такого одиночного инди‑проекта Vulkan - это тяжеловесная, местами избыточная технология. Но было уже поздно, фундамент проекта на нем, и менять его по ходу разработки означало бы почти переписать рендер с нуля.

Этот опыт научил меня главному: нужно смелее признавать свои ограничения и думать не только о том, "насколько круто", но и "насколько рационально" решение для конкретного проекта и команды (тем более, если команда - один человек).

Rust: плюсы, минусы и неожиданные эффекты

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

Но у Rust есть и обратная сторона: кривая обучения, необходимость постоянно "договариваться" с компилятором, ограниченность экосистемы именно в области игровых фреймворков по сравнению с более устоявшимися технологиями. Многим задачам просто не у кого научиться - приходится изобретать велосипеды. С одной стороны, это развивает, с другой - снова крадет месяцы времени, которое можно было бы вложить в контент, уровни, историю.

При этом Rust заставляет писать более аккуратный, структурированный код. Когда проект растет, это окупается: проще что‑то рефакторить, выносить в модули, оптимизировать. В целом я не жалею о выборе языка, хотя временами и задавался вопросами, не быстрее ли было бы сделать прототип на чем‑нибудь более привычном и "игровом".

Ошибки, которые я бы не повторил

За эти 14 месяцев накопилось достаточно ошибок, о которых стоит сказать отдельно:

- Слишком амбициозный технический стек для одного человека: свой движок, свой рендер на Vulkan, кастомная физика, неткод. Любая из этих задач могла быть отдельным проектом.
- Размытый первоначальный дизайн: гонка начиналась как сетевая аркада, затем обросла ремонтом, экономикой, кампанией, персонажами. Четкое видение появилось не сразу, а это стоило множества переработок.
- Неправильная оценка сроков: "сделаю за четыре месяца" было чистой фантазией. Сейчас я планирую задачи иначе: закладываю буфер, оставляю место на исследование и тесты.
- Переоценка процедурности: идея с полностью случайными гонками казалась заманчивой, но на практике сильно усложнила геймдизайн и в итоге была отброшена.

И что в итоге

Сейчас игра находится в стадии, которую я сам называю "играбельный, но далеко не законченный прототип". Машины ездят, шины ведут себя почти так, как я мечтал, экономика работает, кампания обретает форму, есть диалоги и зачатки сетевой игры. Путь от простой "гонки на четыре месяца" до текущего состояния растянулся на год с лишним, и впереди еще много работы.

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

И если в голове у вас крутится идея "сделать свою игру с нуля на Rust" - это возможно. Но стоит быть честным с собой: выбирать инструменты осознанно, не тащить в проект все сложное только потому, что оно кажется "крутым", и быть готовым к тому, что ваш изначальный четырехмесячный план очень быстро превратится в многомесячный марафон с ошибками, открытиями и маленькими, но очень важными успехами.

Прокрутить вверх