Pull to refresh

Танчики на node.js

Reading time5 min
Views16K
image Вот наконец-то первая моя поделка которая перешагнула барьер в первые 90% готовности. Хочу представить на ваш суд танчики на javascript, в которые можно играть прямо в браузере. Для работы требуется браузер с поддержкой websockets и canvas (должно работать в Chrome 14,15 и Firefox 7). Сервер написан так же на javascript. Я постараюсь обойтись без кода в статье, но если кому интересны исходники — они тут. Так же оговорюсь, что nodejs был выбран не из-за каких-то его особенностей, а только из-за javascript, которым я пытаюсь овладеть. Демка, которая тянет всего 2-3 игры одновременно и не выдержит никакого хабраэффекта.

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

Общие принципы

Начнём с описания архитектуры проекта. Код, можно разделить на две основные части, сервер и клиент. Так как всё написано на JavaScript, некоторые части кода используются и на клиенте и на сервере. Но я бы не назвал это большим преимуществом, потому что логика классов клиента и сервера как правило отличаются.
По сути роль клиента заключается в обработке пользовательского ввода, и отображение процесса игры. Все остальное происходит на сервере. Расчёт передвижения танков, полёта пуль, обработка столкновений и вообще всё, что относится к логике игры — происходит на сервере, так как негоже доверять такие вещи клиенту. Так же клиенту отведена анимация, которая никак не влияет на игру. Например, на сервере у объекта-танка только установлено свойство, что сейчас танк бронирован или что за танк дадут бонус. А расчёт того, какой спрайт нужно нарисовать в каждый момент времени, выполняет клиент. Так же, например, взрыв танка запускает анимацию на клиенте из последовательности соответствующих кадров, но на сервере танк просто удаляется в момент взрыва. Такая организация позволяет защитить данные игры от постороннего вмешательства. А так же не нагружать сервер лишними действиями.

Canvas

Отображение игрового поля сделано довольно примитивно. В момент старта игры, с помощью setInterval() каждые 50мс вызывается функция, которая перерисовывает всё поле. Я думаю есть возможность перерисовывать только изменяющиеся части игрового поля, но пока что это только планы на будущее. Каждый кадр имеет свой номер, увеличивающийся на 1 каждый раз. Опираясь на этот номер, отображаемые объекты составляют список спрайтов которые нужно нарисовать. Так например бронированный танк «состоит» из двух спрайтов, самого танка, и мерцающего контура вокруг. Такая возможность порядком упрощает сложную анимацию.

Карта

Пожалуй основной код, который используется и на сервере и на клиенте — это код игрового поля. Пришлось поломать голову над тем, как хранить объекты на карте. Можно было бы взять двумерный массив, индексы которого являлись бы координатами. Так, для описания карты, хватило бы массива размером 26*26. Также такой способ позволил бы легко искать пересечения объектов. Но это только на первый взгляд. Чтобы танки и пули передвигались более плавно, размер массива пришлось бы значительно увеличить. Плюс танк может ехать по льду, или пуля может лететь над водой, то есть массив уже нужен будет не двумерный, а трёхмерный. В общем куча вопросов и решения их совсем не привлекательные. Хоть этот вариант и не подходит для хранения данных в памяти, он очень удобен для хранения карты в файле.

Второй способ, на котором я и остановился — это хранить список присутствующих на карте объектов. У каждого объекта есть координаты, а также ширина и высота, которые используются для поиска пересечений объектов. Недостаток этого способа в том, что для поиска пересечения необходимо перебрать все объекты присутствующие на карте, а этих объектов (кусочки стен, деревьев, воды, танки, пули...) на каждой карте получается около 1000. То есть для каждого движущегося объекта нужно 1000 раз выполнить функцию intersect() которая проверяет пересечения двух прямоугольников.

Третий способ, который сейчас в разработке — это представить карту в виде дерева. Где корень — это вся карта, первые две ветки — левая и правая половина, и т.д. листья дерева — это собственно игровые объекты (танки, пули, стены...). Такой подход сокращает поиск пересечений с объектом с 1000 операций до 20-100, в зависимости от наполненности конкретного сектора карты.

Синхронизация

Идём дальше. Данные между клиентом и сервером нужно как-то синхронизировать, чтобы на клиенте всегда было актуальное состояние игры. Я выбрал для этого следующий вариант: все изменения на сервере логируются, и потом пересылаются клиенту по запросу. У каждого клиента есть время последней синхронизации, поэтому при каждой синхронизации клиент получает изменения за последние несколько десятков миллисекунд. Этот способ отнимает довольно много процессорного времени, но другого достаточно простого варианта я не придумал. Конечно же нет смысла передавать пользователю вообще все данные сервера. Пользователю не нужны данные игр других игроков. Поэтому на сервере ведётся несколько логов: все пользователи онлайн, все игры онлайн, пользователи в текущей игре, объекты на карте текущей игры и т.п.

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

Для передачи данных была выбранна библиотека socket.io, так как это единственный известный мне вариант поддержки WebSockets на сервере. Socket.io не идеальный вариант, потому что она несколько избыточна, так как поддерживает передачу данных не только через WebSockets, но так же через flash сокеты и еще несколькими способами. Но игра идет с адекватной скоростью только при использовании WebSockets. Так же в планах перейти на защищенное соединение, что позволит устанавливать соединения через некоторые прокси, которые по каким-то причинам не хотят передавать WebSocket трафик.

Производительность

Сейчас с производительностью все плохо. Сервер на Intel Pentium 4 3.00GHz способен тянуть только 2-3 игры одновременно. Результаты профилирование указали на 2 проблемы, это поиск пересечений объектов и логирование изменений на сервере. Как говорится, остались вторые 90% работы.

Copyright

Спрайты для игры были найдены и скачаны с интернета. Просветите кто-нибудь насколько это законно. То есть, если я хочу сделать ремейк какой-то игры, можно ли вообще использовать графику из оригинальной игры?

Вторая часть, про оптимизацию.
Tags:
Hubs:
Total votes 79: ↑73 and ↓6+67
Comments61

Articles