Cat's World - или то, как блокировка сподвигла сделать свою игру.
Эту статью можно было бы написать ещё в 2023-м, когда я только загорелся идеей сделать игру, вдохновлённую Pony Town.
Впервые я узнал об этой игре в 2019 году. Сначала я не понимал, чем там занимаются мои друзья - говорили что-то вроде "найди в браузере, и всё". Меня это заинтриговало, и я решил посмотреть сам.
Оказалось, всё гораздо проще, чем я думал: игра была браузерной.
С этого момента я начал глубже изучать её устройство. Она позиционировалась как социальная, но техническая реализация показалась мне чрезвычайно интересной.
Более того, у разработчиков даже был открыт GitHub-репозиторий для приватного сервера (пока у них не случились внутренние проблемы, после чего его убрали).
Время шло, я занимался другими проектами, но мысль о подобной игре не отпускала.
На дворе стоял уже 2021 год, и желание создать что-то подобное Pony Town снова стало настойчивым. Я решил попробовать.
Использование игровых движков - это стандартный путь: он ускоряет разработку, да и при масштабировании проекта проще найти людей, которые смогут быстро включиться в работу без глубокого погружения. Я выбрал Construct 2. Да, он очень упрощал и ускорял процесс, но некоторые моменты в нём меня не устраивали. К тому же, на тот момент его уже активно вытеснял Construct 3.
Разработка сначала шла полным ходом, но потом постепенно замедлилась и, в конце концов, проект попал в стопку "архивных" - тех, что висят в состоянии "заморожен или заброшен".
И вот на дворе апрель, весна 2023 года. Я по-прежнему интересуюсь технической стороной Pony Town, и тут мне попадается объявление о поиске программиста в команду разработчиков самой игры.
Мы ищем опытных программистов, обладающих навыками разработки игр, игровых движков или серверной части. По окончании ознакомительного периода ожидается, что вы справитесь с разработкой основных функций (сравнимых с системой друзей или чатов), не требуя, чтобы вас держали за руку.
Мы планируем добавить множество важных функций для формирования игры, и вы сыграете значительную роль в их создании.
Должность оплачиваемая и на полный рабочий день, при этом вся работа выполняется удалённо.
Мне показалось это очень интересным и перспективным предложением, к тому же - удалёнка =)
Но, увы и ах, скорее всего, моих навыков оказалось недостаточно (и это бесспорно, так как на тот момент я действительно не дотягивал до нужного им уровня).
После этого меня чудесным образом заблокировали в игре. Это был первый раз. Потом я потерял интерес и забил на эту затею. Висели и другие проекты, я занимался своими делами - откровенно говоря, было не до этого.
Наступил 2025 год. Я каким-то образом вспомнил об игре и решил глянуть, что там и как. Создал новый аккаунт, зашёл... и снова увидел всё то же самое. Но вскоре снова получил бан. И вот с этого момента начинается основная часть моей истории.
Начало разработки
Меня задело, что меня снова забанили. Бан со стороны игры был формально оправдан: создание нескольких аккаунтов. Но немного позже я всё-таки решил взяться и реализовать свою версию.
В этот раз я хотел сделать что-то более инновационное. Я отвык от игровых движков, и в итоге пришёл к идее монорепозитория с бэкендом и фронтендом, реализованными на нативном TypeScript. Это решение имело как плюсы, так и минусы, но я начал работу.
Организовать простое веб-приложение на вебсокетах - задача несложная, так что с базовой структурой проблем не возникло.
Первым делом на canvas появились точки, каждая из которых имела набор символов (условный ник, а на деле - идентификатор подключения).
Затем я отправился в "темницу" бесплатных текстур под названием itch.io и снова нашёл тот самый набор, который использовал ещё в 2023-м. Решил: "было раньше так - будет и сейчас". В наборе были все основные направления спрайтов (но, увы, не хватало диагоналей). Я накидал первый прототип.
Как видно на скриншоте, я добавил отображение вектора скорости (velocity), чтобы персонаж выбирал направление движения.
Затем встал вопрос синхронизации анимаций. Эту задачу я возложил на клиент, чтобы не нагружать сервер, который лишь передаёт направление движения персонажа.
Следующим камнем преткновения стала тайловая карта. Мне нужна была система, совместимая с тайлами из моего набора с itch.io.
Немного подумав, я решил сделать простой двумерный массив на сервере, который клиент бы отрисовывал, используя ID тайлов из ячеек для графического отображения. Так появилась первая версия с тайлами.
Выглядело это, конечно, несуразно, потому что вот так выглядел этот массив в реальности:
Планировать карту таким способом было невероятно сложно. Заниматься лвл-дизайном в голом JSON - то ещё удовольствие.
Вот буквально моя попытка написать слово "СВОБОДА", которые так и не увенчалась успехом:
Тут я вспомнил, что у админа в Pony Town (как я видел в одном из обзоров приватного сервера) была возможность редактировать мир прямо в игре. Да и у обычных игроков на личных островах была механика украшения. Я понял, как мне стоит поступить: сделать микро-редактор мира с выбором тайла и живой перезаписью JSON карты.
Да, это решало проблему, но я не считал такое решение идеальным. Тем не менее, после внедрения редактора благоустройство мира пошло куда легче, хотя чувство "неправильности" решения не отпускало. Но да ладно
Далее я решил развивать функционал и добавил смену времени суток. Утром - одни тона, днём - светло, вечером - розоватое небо, ночью - темно. Выглядело это примерно так:
Но чем дальше я продвигался, тем больше понимал, что накосячил с архитектурой. Я ушёл слишком далеко вперёд, чтобы легко возвращаться и менять структуру. Моё стремление изначально писать "правильно", используя принципы SOLID, разбилось о суровую реальность: у меня получился почти что монолит.
Например, модель клиента и сервера была тесно переплетена. Сервер не только обеспечивал мультиплеер, но и раздавал статический контент. В случае его падения пользователь не мог бы даже загрузить страницу, не понимая, упал сайт или игра закрыта навсегда.
И вот тут я разделил архитектуру на две части:
- Серверная часть: занимается только вебсокетами, игровой логикой и серверными функциями.
- Клиентская часть: была вынесена отдельно. Её можно разместить на CDN, чтобы игра загружалась мгновенно, а экземпляры бэкенда можно раскидать по регионам для лучшей локализации и производительности.
Это привело меня в настоящий экстаз! Хотя первый прототип уже работал, он мне не нравился. Я решил переделать механику камеры. Снова начал с пустого мира, снова разделил сервер и клиент.
Следующие фичи сложно показать скринами, но они включают:
- Плавная камера, следующая за персонажем.
- Функция "Обзор": зажав V, можно осмотреть местность в радиусе 800 условных единиц. Алгоритм проработан так, что вы видите всё пространство между вами и курсором.
- Функция "Наблюдение": можно закрепить фокус на другом игроке. Игра будет показывать расстояние до него, а ваш персонаж автоматически повернётся в сторону объекта наблюдения.
Структура проекта наконец-то начала радовать глаз:
Далее я решил отвлечься на что-то более приятное - интерфейс (да, я знаю, что нарушил принцип YAGNI, но дедлайнов нет, это пет-проект).
Пока баловался с GUI, я уже размышлял, как превратить этот пет-проект из простой социальной игры в полноценную MMORPG... Но это были просто идеи, чтобы отвлечься, потому что я очень глубоко погрузился в разработку и мне нужен был отдых (на тот момент я уже 5 дней подряд жил этим проектом).
Я начал нагружать видеокарту различными эффектами:
Затем встала задача оптимизации. Те, кто занимался разработкой игр, знают: часто быстрее перерисовать объект, чем постоянно хранить его в памяти. Я вернулся к одной из плановых задач и реализовал отрисовку мира по тайлам "на лету".
Оптимизировав концепцию, я добился максимальной производительности даже с эффектами размытия и плавной камерой.
После этого я снова вернулся к циклу дня и ночи. К счастью, логика осталась от предыдущей концепции, и я без труда её перенёс.
Вот для сравнения скриншоты на максимальных настройках графики и на минимальных:
Затем я занялся внутриигровым взаимодействием, тем же слежением за персонажем. Помимо функции обзора, добавилась фиксация фокуса на другом игроке с расчётом расстояния до него:
И снова я вернулся к вопросу с тайлами. Как сделать это правильно? Я решил, что этот вопрос пока второстепенен, так как многое другое требовало внимания. Обошёлся простым генератором квадратных комнат, заполненных тайлами.
И снова я пошёл нарушать YAGNI: раз уж есть смена дня и ночи, почему бы не сделать динамичные тени?
Из нюансов: я не использовал WebGL и сторонние графические библиотеки, решив пойти "тёмным" путём. Вот что из этого вышло:
Сюр при попытке дать поиграть знакомому
Пожалуй, это один из ключевых моментов. Мой знакомый поинтересовался прогрессом и захотел посмотреть на игру. Я решил показать ему вторую концепцию (первую он уже видел, а я её полностью переделал).
И тут я столкнулся с проблемой: у меня не было готовой методики развёртывания. Я-то понимал, как всё устроено, но к такому резкому повороту событий готов не был. У меня не было готового docker-compose, никаких инструкций. Задача сместилась: нужно было срочно навести порядок в деплое.
Я настроил webpack, прописал сценарии сборки для монорепозитория, проверил, как всё билдится и устанавливается. На деле это было сделано быстро, но перепроверить всё стоило.
Раз реализация подразумевала подключение к разным серверам, нужно было оформить главную страницу. Сначала я сделал простое меню с вводом ника и выбором сервера:
И вот, всё было готово для создания первой альфа-версии. Я решил использовать сервисы, которые могут подключаться напрямую к GitHub-репозиторию. Игра вытягивалась двумя разными серверами, каждый из которых по своему сценарию разворачивал систему через npm. Клиентская часть улетала на CDN, а бэкенд оставался с меткой "сервер".
Последние полтора дня я потратил на редизайн главной страницы, отладку взаимодействия клиента с сервером и настройку приятных прогулок по кошачьему миру.
Вот такая получилась история о том, почему и как я реализовал этот пет-проект.