October 18

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 тайлов из ячеек для графического отображения. Так появилась первая версия с тайлами.

вот так выглядит карта

Выглядело это, конечно, несуразно, потому что вот так выглядел этот массив в реальности:

вот так карта выглядела в map.json

Планировать карту таким способом было невероятно сложно. Заниматься лвл-дизайном в голом JSON - то ещё удовольствие.

Вот буквально моя попытка написать слово "СВОБОДА", которые так и не увенчалась успехом:

ну вы все поняли, да?
Реально сложно

Тут я вспомнил, что у админа в Pony Town (как я видел в одном из обзоров приватного сервера) была возможность редактировать мир прямо в игре. Да и у обычных игроков на личных островах была механика украшения. Я понял, как мне стоит поступить: сделать микро-редактор мира с выбором тайла и живой перезаписью JSON карты.

тут же мир стал преображаться, а такие декор-надписи стало делать куда проще

Да, это решало проблему, но я не считал такое решение идеальным. Тем не менее, после внедрения редактора благоустройство мира пошло куда легче, хотя чувство "неправильности" решения не отпускало. Но да ладно

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

режим дебага, закат

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

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

И вот тут я разделил архитектуру на две части:

  • Серверная часть: занимается только вебсокетами, игровой логикой и серверными функциями.
  • Клиентская часть: была вынесена отдельно. Её можно разместить на CDN, чтобы игра загружалась мгновенно, а экземпляры бэкенда можно раскидать по регионам для лучшей локализации и производительности.

Это привело меня в настоящий экстаз! Хотя первый прототип уже работал, он мне не нравился. Я решил переделать механику камеры. Снова начал с пустого мира, снова разделил сервер и клиент.

И вот что в итоге получилось:

Котик может "залететь на сервер".
Анимации движения полноценно синхронизированы между всеми игроками.
У каждого котика есть свой ник и скин. Свой персонаж подсвечен (возможно, уберу), а при наведении на другого виден его ник.
Реализован статус "печатает".
Игроки могут обмениваться сообщениями, которые видны внизу экрана.
По нажатию F1 открывается полная история чата.
Добавлена миникарта, показывающая местоположение всех "котят"
Реализована функция наблюдения (описание ниже)

Следующие фичи сложно показать скринами, но они включают:

  • Плавная камера, следующая за персонажем.
  • Функция "Обзор": зажав V, можно осмотреть местность в радиусе 800 условных единиц. Алгоритм проработан так, что вы видите всё пространство между вами и курсором.
  • Функция "Наблюдение": можно закрепить фокус на другом игроке. Игра будет показывать расстояние до него, а ваш персонаж автоматически повернётся в сторону объекта наблюдения.

Структура проекта наконец-то начала радовать глаз:

сказка, структура проекта такая лакомая, мммммм

Далее я решил отвлечься на что-то более приятное - интерфейс (да, я знаю, что нарушил принцип YAGNI, но дедлайнов нет, это пет-проект).

Пока баловался с GUI, я уже размышлял, как превратить этот пет-проект из простой социальной игры в полноценную MMORPG... Но это были просто идеи, чтобы отвлечься, потому что я очень глубоко погрузился в разработку и мне нужен был отдых (на тот момент я уже 5 дней подряд жил этим проектом).

Я начал нагружать видеокарту различными эффектами:

нюансы, что требовали оптимизации

Затем встала задача оптимизации. Те, кто занимался разработкой игр, знают: часто быстрее перерисовать объект, чем постоянно хранить его в памяти. Я вернулся к одной из плановых задач и реализовал отрисовку мира по тайлам "на лету".

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

Оптимизировав концепцию, я добился максимальной производительности даже с эффектами размытия и плавной камерой.

функция "обзор" уже фокусируется на другом игроке с размытием, FPS обманывает, честно говорю =)

После этого я снова вернулся к циклу дня и ночи. К счастью, логика осталась от предыдущей концепции, и я без труда её перенёс.

Вот для сравнения скриншоты на максимальных настройках графики и на минимальных:

Максимальное количество тайлов + размытие
Минимальное количество тайлов + размытие

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

Следят друг за другом, сталкера

И снова я вернулся к вопросу с тайлами. Как сделать это правильно? Я решил, что этот вопрос пока второстепенен, так как многое другое требовало внимания. Обошёлся простым генератором квадратных комнат, заполненных тайлами.

И снова я пошёл нарушать YAGNI: раз уж есть смена дня и ночи, почему бы не сделать динамичные тени?

Из нюансов: я не использовал WebGL и сторонние графические библиотеки, решив пойти "тёмным" путём. Вот что из этого вышло:

Динамичные тени (обрубыши)
Темнота и эффект неполноценного свечения

Сюр при попытке дать поиграть знакомому

Пожалуй, это один из ключевых моментов. Мой знакомый поинтересовался прогрессом и захотел посмотреть на игру. Я решил показать ему вторую концепцию (первую он уже видел, а я её полностью переделал).

И тут я столкнулся с проблемой: у меня не было готовой методики развёртывания. Я-то понимал, как всё устроено, но к такому резкому повороту событий готов не был. У меня не было готового docker-compose, никаких инструкций. Задача сместилась: нужно было срочно навести порядок в деплое.

Я настроил webpack, прописал сценарии сборки для монорепозитория, проверил, как всё билдится и устанавливается. На деле это было сделано быстро, но перепроверить всё стоило.

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

Обычный select с выбором сервера

И вот, всё было готово для создания первой альфа-версии. Я решил использовать сервисы, которые могут подключаться напрямую к GitHub-репозиторию. Игра вытягивалась двумя разными серверами, каждый из которых по своему сценарию разворачивал систему через npm. Клиентская часть улетала на CDN, а бэкенд оставался с меткой "сервер".

Если что, они все в режиме наблюдения за игроком asd

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

Вот так сейчас выглядит главная страница

Вот такая получилась история о том, почему и как я реализовал этот пет-проект.