Событийный игровой сервер — Railorz

 

Событийный игровой сервер - Railorz

Если вы читали некие из моих прошлых постов, то наверное понимаете, что я уже достаточно длительное время занимаюсь разработкой игр для соцсети Facebook. Сейчас я малость расскажу о подходе, который мы используем при разработке игрового сервера для нашего нового проекта.
Наш основной проект на момент написания этой заметки — игра Witchcraft — написан на Ruby on Rails. Начать разработку онлайн-игры конкретно на Rails не было каким-то сознательным решением, быстрее так исторически сложилось, что мне очень нравилось программировать на Rails и я готов был все что угодно делать при помощи этой технологии, не взирая на уместность. У текущей реализации проекта есть один бесспорный плюс — она работает. Но есть и ряд существенных минусов, от которых я очень желаю избавиться и которые мы стараемся исключить при разработке нового продукта.
Минусы текущей реализации
Игровая механика агрессивно ограничена соглашениями Rails. Каждое действие игрока может генерировать только один итог, только один ответ от сервера. К примеру, при клике по кнопке миссии можно только возвратить итог выполнения миссии, но не событие о получении нового уровня либо об открытии новейшей миссии. Естественно, все можно сделать, но код для таковой логике полностью противоречит структуре обычного rails-приложения.
Способности общения сервера с клиентом ограничены ответами на HTTP-запросы. Неважно какая попытка воплотить отправку сообщения с сервера на клиент вне контекста определенного запроса смотрится совсем ортогонально концепции Rails. В дальнейшем планируется создание мобильной версии сайта, для того чтобы пользователи всегда имели доступ.
Внедрение базы данных для хранения игрового контента и данных игрока жесточайше и главное совсем никчемно тратит серверные ресурсы на загрузку/сохранение данных при выполнении маленьких действий, из которых фактически и строится интерактивный игровой процесс.
Завязанность визуализации игрового процесса на сервер. В Witchcraft подавляющая часть всего зрительного представления игры генерируется сервером. Все странички, результаты действий, сообщения юзеру, диалоги — все это в виде HTML поступает с сервера. На клиенте происходит самый минимум — навешивание событий на кнопки и ссылки, которые снова же вызывают сервер для генерации результатов событий. В отличие от бизнес-приложений, где один клиент генерирует 10-15 запросов за сессию, игрок делает 50-1000 действий за игровую сессию, потребляя осязаемое количество ресурсов.
Эти трудности наткнули меня на идея создать игровой сервер, более соответственный природе игр, где весь процесс строится на базе событий.
Событийный игровой сервер
Действия могут быть сгенерированы игроком — клики мышкой, нажатия на кнопки — а могут и поступать от сервера — построилось здание, на игрока напали, пришло сообщение. И те, и другие действия могут происходить вне зависимости друг от друга, меж ними нет связи, другими словами нельзя точно сказать, к примеру, что в итоге данного определенного нажатия на кнопку построилось здание. Действия должны поступать от игрока на сервер и от сервера к игроку без какой-нибудь привязки к запросу.
В процессе работы над макетом игрового сервера у меня выкристаллизовалась система, состоящая из клиента, транспорта, очереди событий и игрового беса.
Игровой клиент
Решив не отходить очень далековато от Rails, я решил разрабатывать игровой клиент на JavaScript с внедрением фреймворка Backbone.js — это оказалось наболее обычным решением. В качестве шаблонизатора употребляется Handlebars.js — шаблоны получаются очень ординарными и с минимумом логики. Клиент собирается и отдается в браузер Rails-приложением, которое кроме отдачи клиента будет отвечать за такие полезные в хозяйстве вещи как регистрация юзеров, опции и прием платежей.
Транспорт
Для доставки событий от клиента на сервер и назад я решил испробовать WebSocket — эта разработка оказалась более подходящей для асинхронного обмена данными. На стороне сервера я поставил Rack middleware прямо на Rails-приложение, обработку websocket-ов воплотил при помощи гема faye-websocket. Клиент подключается к серверу и посылает сообщения о событиях, сервер сваливает приобретенные сообщения в очередь событий для обработки игровым бесом, выгребает из очереди действия, сгенерированные игровым бесом для данного игрока, и посылает их на клиент. Обмен данными происходит асинхронно.
Очередь событий
Хранение очередей входящих и исходящих игровых событий я пока решил воплотить через Redis, потому что с ним я более-менее знаком и в нем отлично реализована возможность сотворения списков объектов. Действия пишутся в ключи, надлежащие игровой сессии — ‘game_123_incoming’ для всех входящих от всех игроков, ‘game_123_ougoing_player_*’ — для исходящих, адресованных определенным игрокам.
Игровой бес
Конкретная обработка игрового процесса происходит в раздельно запущенном демоне на базе EventMachine. Бес загружает в себя игровые сессии по мере их прибавления в хранилище, для хранения данных сессий снова же пока использую Redis. Потом по таймеру (100 раз за секунду) бес опрашивает очередь на предмет наличия новых событий от игроков, а так же запускает так именуемый game loop — обработчик игрового процесса. Этот обработчик производит деяния в согласовании с командами игроков и отсылает игрокам игровые действия, произошедшие в итоге их действий либо в связи с внутренними методами. Все конфигурации характеристик игровой сессии происходят в памяти игрового беса и временами (раз в 10 секунд) записываются в хранилище.
Предолагаемые трудности
Сейчас это экспериментальная схема, но она очень хорошо работает, по последней мере на девелоперской машине :) Но я заблаговременно предвижу несколько заморочек, которые могут появиться:
JS-клиент может довольно очень тормозить при активном рендере. Это отчасти можно скомпенсировать пре-рендером части шаблонов, а так же переходом на какие-либо более резвые средства рендеринга — Flash, Unity, нативный клиент.
Websocket — новенькая разработка, поддерживается не всеми браузерами. Эту делему можно обойти, используя Flash-сокеты, уже даже есть готовые решения. На последний случай доставку событий от клиента можно воплотить обыкновенными HTTP-запросами, а от сервера клиенту — через long-polling.
Redis — не самый подходящий инструмент для хранения очередей. По мере надобности его можно поменять на что-то более подходящее для организации очередей. К примеру, RabbitMQ.
Игровой бес на ruby возможно окажется довольно неспешным при большенном количестве игровых сессий. Это решается рассредотачиванием сессий меж несколькими демонами и/либо переписыванием игровой логики на более резвые языки — Java, Erlang, C++.
Достоинства
У данной схемы кроме недочетов есть и определенные достоинства:
Слабенькая связность компонент позволяет поменять технологии и инструменты 1-го компонента без особенного воздействия на все другие. При желании JS-клиента можно отдавать не рельсами, а нагим nginx-ом, websocket-ы обрабатывать сервером на C++, игровой бес переписать на Java, а очередь хранить RabbitMQ.
Относительная универсальность системы позволяет разрабатывать игры совсем различных жанров — стратегии, rpg, тетрисы и кроссворды — используя одну и ту же инфраструктуру. Все отличия заключаются в 2-ух компонентах — логика игровой сессии и клиент. Все другое остается постоянным.
В целом, опыт сам по для себя оказался очень увлекателен и позволил малость отстраниться от обычной схемы разработки приложений.

 
 
 

0 - Количество комментариев

Оставьте комментарий.

 
 

Оставьте комментарий