Прежде чем почитать – поиграйте в саму игру!!!!
Мой игровой бекграунд
Я очень долго думал, что хочу делать игры. Когда я только учился программированию, я думал, что это верх программистской карьеры. Это же очень круто – когда у тебя какой-то арт есть, кнопочки, интерактивность, все двигается.
Разумеется, когда ты начинаешь учиться программировать, оказывается, что все твои программы почему-то консольные. Всякие фреймворки, графические обертки и движки очень сложные для новичков, так что я как-то очень долго не пробовал программирование игр вообще.
Первый проект, похожий на игру, я написал где-то в районе 11го класса. Я хотел генерализовать игру Dual, в которую я играл со своим лучшим другом Петей. Я написал клиент-сервер на сокетах, используя python и pygame. Петя нарисовал несколько текстурок, и получился очень угарный вариант игры для компов. По моей задумке, в него можно было бы играть, создавая комнату в локальной сети.
Оказалось, что запустить игру на чужом компьютере… Сложно. Я нашел какой-то упаковщик, который должен был генерировать запускаемый файл с игрой – но они работали через жопу. Кажется, я справился сделать так, чтобы игра запускалась на винде и линуксе (потому что мог их протестировать на компьютерах дома). У Пети был макбук, у него ничего не заработало, и я забросил проект.

С приходом нейросетей, кстати, я переписал все на тайпскрипт одним днем, и теперь в соло-режим можно играть в вебе. Локально поднять сервер, возможно, тоже можно, но я не тестировал. Энивей, ссылка тут.
Графика
Оказавшись в универе, я как-то для себя понял, что концепция программирования игр мне нравится, но как будто бы индустрия сумасшедшая. Там и увольняют всех, и переработки гигантские, и платят в среднем плохо. Тем не менее, там есть серьезные технические интересные задачи. Всякая 3д-математика – очень круто же!
Я так решил на 2 курсе сделать свой небольшой 3д движок, чтобы совместить мое новое знание линейной алгебры с чем-то, что действительно напоминало бы 3д игру. В общем, я сделал что-то очень странное – написал 3д-движок целиком на CPU, который рисовал экран попиксельно, не пользуясь шейдерами вообще. Они были под капотом какой-то небольшой либы, которую я юзал, но сам я пользовался только примитивом setColor(x,y,color).

Мне очень понравилось копаться в таких приколах, захотелось что-то еще попробовать. Оказалось, что на ФКНе особо не было курсов в “визуальном” направлении. Я попробовал взять какой-то курс из гарвардского набора CS50, который был бы введением в геймдев. Это было интересно, я сделал сколько-то проектов, но мне не понравилось, что там везде было предоставлено очень много кода по умолчанию, за счет чего я достаточно плохо понимал, что происходит на самом деле. Типа, в фреймворке несложно заставить картинки двигаться, а как сам фреймворк-то работает?
Какие-то курсы по компьютерному зрению и всяким смежным темам я смотрел, что-то про OpenGL читал. Системного знания у меня совсем-совсем не было, но я хотел разобраться в том, как все эти визуальные системы работают. И пошел в магистратуру на AI & Advanced Visual Computing. По идее, оптимальная траектория, чтобы разобраться в том, что там под капотом. Не буду тут лишний раз ругаться на эколь политехник, но оно того не стоило, в общем, так я экспертом и не стал.
Веб
Паралелльно со всеми этими приключениями в графике, я работал software engineer’ом, потому что прокармливать себя-то надо было. В основном бекендом занимался, но в целом что только не успел поделать. В частности, я успел поделать софт в браузере – и понял, что браузерное апи гораздо лучше позволяет делиться софтом, чем собирание бинарников, которые надо как-то передавать на другую операционную систему. Из минусов – хз, я как-то подумал, и минусов для своего случая не нашел. Ну вернее был один, что код в вебе на джаваскрипте пишется – но оказывается, его уже давно решает wasm, webgl и emscripten. Пиши себе на плюсах, а оно там дальше как-то само соберется.
И я как-то подумал – ну ведь наверняка можно сделать такой фреймворк для себя, чтобы он и в локальный билд собирался, и в веб? Тогда я смогу делиться своей игрушкой без каких-либо проблем, и разрабатывать буду нормально, на каком-то своем фреймворке, с пониманием результатов и своим тулингом. Да, AAA игры на этом не напишешь, но я к этому моменту понял, что это не career choice, а hobby choice.
Короче, я потыкался, собрал прототип на OpenGL / WebGL, вроде заработало. Придумал собрать свою ECS, которая получилась вполне разумной и приятной. У меня есть идея игры, которую я хотел бы сделать, но она оказалось достаточно масштабной, и я после первых прототипов понял, что не потяну этим заниматься.
Грибы
Люся в какой-то момент собрала себе в инстаграм-портфолио смешной арт-прототип игрушки с грибами, где надо ловить грибы. Ну и я подумал – вообще-то, это очень простой код, наверняка я должен суметь такое сделать.
Решил, что это должна быть нормальная игра. Типа, я хочу пройти все стадии создания – протестировать нормально, поэкспериментировать, придумать какую-то интересную фишку, все по списку.
В общем, пошел прототипировать.
Геймплей
Я долго думал, вокруг чего сделать прототип. Понятно, что основной процесс в игре должен быть простой – грибы падают, надо двигаться героем и ловить. Несколько прямоугольников, поверх которых рисуются картинки. Счетчик еще. Если гриб падает – все, game over.
Все хорошо, но очень прямолинейно. Просто лови все, что видишь.
Люся придумала концепцию зелий, которую я впоследствии обозвал рецептами. Идея в том, что нужно поймать ровно столько грибов конкретного вида, сколько просит рецепт. В игру сразу добавляется сколько-то глубины, потому что какие-то грибы нужно пропускать, чтобы не переполнить корзину.
Прототип такой игрушки собирается за полдня, когда ECS, утилити для рендеринга и сборка в веб уже написаны. А, и еще утилити для отрисовски текста – freetype это такой страшный страх, что проще картинками цифры вставлять было первое время.
На этом можно в целом заканчивать, публиковаться и писать очередной туториал на хабр. Но я хотел нормальную игру. Может, маленькую и простую, но нормальную.
Бесконечно улучшаемся
Быстро стало понятно, что если какие-то грибы ловить не надо, чтобы не портить рецепт, то и ронять их тоже нехорошо. Было решено добавить стрельбу – чтобы ты подстреливал те грибы, которые собирать не собираешься. А падение на землю, соответственно, забирает жизни.
Это оказалось лучше, но в это тоже невозможно было играть. Спавн грибов происходил моим рандомайзером, и он радостно мог заспавнить одновременно грибы в разных частях экрана – и ведь не поймаешь их никак! Можно, конечно, перепройти уровень, со знанием рандома, и подстрелить один из двух.. Но все хаки какие-то.
Проблема простая – ломать грибы можно издалека и наперед с помощью выстрелов. А ловить можно только то, что ровно сейчас на твоем уровне. Хочется, чтобы можно было заранее поймать какой-то гриб. Так и родился механизм ловушек.
В целом, на этом игра была закончена геймплейно. Это очень простая игра. Мы пошли пробовать и тестировали, тестировали и тестировали до посинения.

Навскидку, надо было добавить следующее:
- Чтобы можно было играть с телефона, у меня был уже готовый самописный джойстик и кнопки. Выяснилось, что ими невозможно играть, потому что слишком мелко – пришлось вынести на уровень веба, поставив большие кнопки с эмодзи.
- Раз есть кнопки и есть веб, то надо бы сделать так, чтобы везде было написано, какая кнопка чему соответствует – и, в идеале, чтобы можно было еще поменять биндинг кнопок.
- Чтобы был биндинг кнопок, нужно меню настроек.
- Чтобы было меню настроек, нужно основное меню и меню паузы.
- Когда грибы появляются, ломаются, падают, съедаются итд – нужно добавлять какие-нибудь маленькие дерганья и particles, иначе никакого вайба
- Надо добавить какую-то аудиодорожку, иначе никакого вайба
- А раз есть аудио, надо добавить слайдер для регулировки громкости
- Конечно же нужен туториал
- А если есть туториал, то нужен и кеш браузера, чтобы не показывать его при каждом старте игры
- Кстати, еще нужно кешировать пройденные уровни и результаты бесконечного уровня
- Кстати о бесконечном уровне – там надо добавить шарилку результатов, чтобы можно было получить вордл-эффект.
- А чтобы бесконечный уровень был интересным, и при этом им можно было соревноваться с друзьями, нужно чтобы сид рандома зависел от дня календаря.
И это я опускаю такие вещи, как калибровки численных параметров – движения, стрельбы, скоростей, сложностей уровней.
В какой-то момент мы поняли, что стрелять пулями странно – придумали стрелять летучими мышами. Тогда само собой случилось очень разумное ограничение, что есть всего три доступные летучие мыши – и на ловушки, и на выстрелы. Для них добавились таймеры на перезарядку, они стали возвращаться к игроку.
Тогда же мы выяснили, что 3 жизни – это очень странное ограничение, если уже есть рецепт, в котором ты уже и так балансируешь между ограничениями. Подумав немного, мы поняли, что это просто два разных режима – попроще (без рецепта, с жизнями) и посложнее (с рецептом, без жизней).
Наверное, есть еще с десяток потенциальных решений, но мы решили остановиться на том, что есть сейчас.
Активная разработка
Изначально я очень много времени потратил в движок сам по себе, делая какие-то прикольные штуки в стол. Обычно у меня находился один выходной в две недели, когда я мог основательно засесть часа на четыре написать какого-то кода. Так, наверное, полгода продлилось примерно суммарно с большими перерывами на всякие сайд-квесты.
В какой-то момент, когда у меня уже был готов основной геймплей и было решено, что это будет игра про ловлю грибов, настал момент для полировки – который, как видно по прошлой части, тратит 90% усилий. Моего энтузиазма бы не хватило даже близко, потому что надо переделывать просто все по кругу раз десять. К счастью, в этот момент случился агентский AI, а у меня случился Pro-шный Codex. Я перестал писать код и начал просто промптить, переписал половину кода из черновых репозиториев в нормальное разделение движок-игра, и запилил все маленькие фичи и улучшения из списков выше. Это был шикарный опыт, потому что примерно все необходимые действия абсолютно очевидны, но мануальны – поэтому можно подумать днем, что надо сделать, а потом одним запросом поставить агента работать, пока вечером смотришь фильм с женой.

Короче, вот! Сделал первую нормальную игру, очень доволен. Есть надежды, что когда-нибудь доделаю другие, более прикольные проекты на этом же движке.

I Released My First Game!
This is a ChatGPT translation that was not properly verified by any human being.
Before reading this, go play the actual game!!!!
My gaming background
For a very long time I thought I wanted to make games. When I was just learning programming, I thought it was the pinnacle of a programmer’s career. It is incredibly cool: you have some art, buttons, interactivity, everything moves.
Of course, once you start learning to program, it turns out that all your programs are somehow console apps. Frameworks, graphical wrappers, and engines are very hard for beginners, so for a long time I did not really try game programming at all.
I wrote my first game-like project somewhere around 11th grade. I wanted to generalize Dual, a game I played with my best friend Petya. I wrote a client-server app over sockets using Python and pygame. Petya drew a few textures, and we got a hilarious desktop version. My idea was that you could play it by creating a room on a local network.
It turned out that running a game on someone else’s computer is… hard. I found some packager that was supposed to generate an executable file with the game, but those tools worked terribly. I think I managed to make the game run on Windows and Linux, because I could test those on computers at home. Petya had a MacBook, nothing worked for him, and I abandoned the project.

By the way, with the arrival of neural networks, I rewrote the whole thing in TypeScript in one day, and now the solo mode can be played on the web. It may also still be possible to run the server locally, but I have not tested it. Anyway, the link is here.
Graphics
Once I got to university, I realized that I liked the concept of game programming, but the industry itself seemed kind of insane. People get laid off, crunch is huge, and on average the pay is bad. Still, there are serious and technically interesting problems there. All that 3D math is very cool!
So in my second year I decided to make a small 3D engine of my own, to combine my new linear algebra knowledge with something that would actually resemble a 3D game. In short, I made something very strange: a 3D engine entirely on the CPU, drawing the screen pixel by pixel without using shaders at all. They were under the hood of some small library I used, but I only used the setColor(x,y,color) primitive myself.

I really enjoyed digging into that kind of stuff, and wanted to try something else. It turned out that at FCS there were not really any courses in a “visual” direction. I tried taking some course from the Harvard CS50 set that was supposed to be an introduction to game development. It was interesting, and I made several projects, but I did not like that they all came with a lot of default code, which meant I understood rather poorly what was actually happening. Like, making pictures move in a framework is not hard, but how does the framework itself work?
I watched some computer vision courses and related things, read a bit about OpenGL. I had no systematic knowledge at all, but I wanted to understand how all these visual systems work. So I went to a master’s program in AI & Advanced Visual Computing. In theory, it was the optimal trajectory for figuring out what was under the hood. I will not rant about Ecole Polytechnique again here, but it was not worth it; in short, I never became an expert.
Web
In parallel with all these graphics adventures, I worked as a software engineer, because I had to feed myself. Mostly I did backend, but in general I managed to do all kinds of things. In particular, I worked on software in the browser, and realized that the browser API makes it much easier to share software than assembling binaries that then have to be somehow sent to another operating system. As for downsides – I do not know, I thought about it and did not find any for my case. Well, there was one: web code is written in JavaScript. But it turns out wasm, WebGL, and Emscripten have been solving that for a long time. You write in C++, and then it somehow builds itself from there.
And I thought: surely it is possible to make some framework for myself that builds both locally and for the web? Then I would be able to share my little game without any problems, while developing normally, on my own framework, with an understanding of the results and my own tooling. Yes, you cannot write AAA games on this, but by that point I had realized that this was not a career choice, but a hobby choice.
In short, I poked around, assembled a prototype on OpenGL / WebGL, and it seemed to work. I came up with my own ECS, which turned out quite reasonable and pleasant. I have an idea for a game I would like to make, but it turned out to be fairly large-scale, and after the first prototypes I realized I could not handle working on it.
Mushrooms
At some point Lucy put together a funny art prototype for an Instagram portfolio: a little game with mushrooms where you have to catch mushrooms. And I thought: actually, this is very simple code; surely I should be able to make it.
I decided it had to be a proper game. I wanted to go through every stage of creation: test it properly, experiment, come up with some interesting twist, the whole checklist.
So I went off to prototype.
Gameplay
I spent a long time thinking about what to build the prototype around. Obviously, the core process had to be simple: mushrooms fall, you move the hero and catch them. A few rectangles with images drawn on top. A counter too. If a mushroom falls – that’s it, game over.
All good, but very straightforward. Just catch everything you see.
Lucy came up with the concept of potions, which I later renamed recipes. The idea is that you need to catch exactly as many mushrooms of a specific type as the recipe asks for. This immediately adds some depth to the game, because you have to let some mushrooms pass so you do not overfill the basket.
A prototype of such a game can be assembled in half a day when the ECS, rendering utilities, and web build are already written. Oh, and a text-rendering utility too – freetype is such a terrifying terror that at first it was easier to insert digits as images.
At that point you can basically stop, publish, and write yet another tutorial for Habr. But I wanted a proper game. Maybe small and simple, but proper.
Improving forever
It quickly became clear that if you do not need to catch certain mushrooms because they would spoil the recipe, then it is also bad to let them fall. We decided to add shooting, so you could shoot down the mushrooms you were not going to collect. Falling to the ground, accordingly, would take away lives.
That was better, but it was still impossible to play. Mushrooms spawned through my randomizer, and it could happily spawn mushrooms in different parts of the screen at the same time – and of course you cannot catch them all! You could, of course, replay the level with knowledge of the random sequence and shoot one of the two… but that is all hacks.
The problem is simple: you can break mushrooms from far away and ahead of time with shots. But you can only catch what is right now on your level. I wanted it to be possible to catch some mushroom in advance. That is how the trap mechanic was born.
In general, at that point the game was finished from a gameplay perspective. It is a very simple game. We went off to try it and tested, tested, and tested until we were blue in the face.

Off the top of my head, the following had to be added:
- To make the game playable on a phone, I already had a homemade joystick and buttons. It turned out they were impossible to play with because they were too small, so I had to move them to the web level and add big emoji buttons.
- Since there are buttons and there is the web, it would be good to write everywhere which button corresponds to what – and ideally allow changing key bindings too.
- To have key bindings, you need a settings menu.
- To have a settings menu, you need a main menu and a pause menu.
- When mushrooms appear, break, fall, get eaten, and so on, you need to add little shakes and particles; otherwise there is no vibe.
- We needed to add some audio track; otherwise there is no vibe.
- And since there is audio, we needed a volume slider.
- Of course we needed a tutorial.
- And if there is a tutorial, we need browser cache so it does not show up every time the game starts.
- By the way, completed levels and endless-mode results also need to be cached.
- Speaking of endless mode, we need to add result sharing to get the Wordle effect.
- And to make endless mode interesting, while still letting friends compete with each other, the random seed needs to depend on the calendar day.
And I am omitting things like calibrating numeric parameters: movement, shooting, speeds, level difficulties.
At some point we realized that shooting bullets was strange, so we came up with shooting bats. Then a very sensible limitation appeared on its own: there are only three available bats total, for both traps and shots. Recharge timers were added for them, and they started returning to the player.
Around the same time we realized that three lives is a very strange constraint if there is already a recipe, where you are already balancing between limitations. After thinking a bit, we realized these are simply two different modes: an easier one, without recipes and with lives, and a harder one, with recipes and without lives.
There are probably another dozen potential decisions like this, but we decided to stop with what we have now.
Active development
Initially, I spent a lot of time on the engine itself, making various cool things just to leave them in a drawer. Usually I had one weekend every two weeks when I could sit down seriously for about four hours and write some code. That probably lasted around half a year in total, with long breaks for various side quests.
At some point, when the core gameplay was ready and we had decided it would be a game about catching mushrooms, it was time for polish – which, as you can see from the previous section, eats 90% of the effort. My enthusiasm would not have been nearly enough, because you have to redo absolutely everything in circles about ten times. Fortunately, at that moment agentic AI happened, and I got Pro Codex. I stopped writing code and started prompting: I rewrote half the code from draft repositories into a normal engine/game separation, and built all the small features and improvements from the lists above. It was a gorgeous experience, because almost all the necessary actions are completely obvious but manual – so you can think during the day about what needs to be done, and then with one request set the agent to work while you watch a movie with your wife in the evening.

So, here it is! I made my first proper game, and I am very happy with it. I hope that one day I will finish other, cooler projects on the same engine.

