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

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

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

Вторая часть, про оптимизацию.
Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 61

    0
    должно работать в Chrome 14,15 и Firefox 7
    Можно расширить с помощью web-socket-js, а поддержку canvas, через excanvas или flashcanvas.
      0
      Про web-socket-js лишнее (в socket.io он вроде)
        0
        а почему предложили именно эти, а не libCanvas и Easeljs, например?
          0
          Я не так выразился: для поддержки ie можно использовать excanvas, а для браузеров без поддержки canvas — flashcanvas. Последнее по-моему лучше решение.
            0
            Тоесть flashcanvas влюбом случае должен в итоге заменить бедный vmlный excanvas?
              0
              Должен. Мне казалась тут простая графика, как раз excanvas можно использовать, но оказывается и так работает как говно.
              А для «старых не ie браузеров», если нет canvas лучше и предложить обновить.
          +1
          Спасибо за интересные ссылки, правда сейчас основная проблема — производительность, так что до поддержки браузеров дело может и не дойти.
            +1
            поддержку canvas, через excanvas или flashcanvas.

            Поддержка Canvas в IE через excanvas для таких проектов — это ересь. Если оно даже на Canvas в Хроме работает как говно, то уж в старых IE.
            0
            Аааа, танчики! Это нереально круто. Просто охренительно круто. Уж простите меня за мой детский восторг.
              +1
              Я в своё время познакомился с игрой Танки Онлайн (легко гуглится), очень понравилось простейшее управление и запуск прямо в браузере. В детстве я очень любил танчики на Денди (да что там, и сейчас играю, поставив эмулятор, два года назад даже джойстик для этого купил). Недавно возникла идея создать подобную Танкам Онлайн игру, но используя концепцию оригинальных дендиевских танчиков. Тут тебе и команда на команду, и бонусы, появляющиеся периодически, и какая-нибудь прокачка. It's gonna be legen...dary.
                –1
                Хах, класс, спасибо! На удивление, не так уж и сильно тормозит в Хроме на линуксе)
                +1
                Сколько куриц было поджарено и звездочек съедено…
                  –4
                  Тоже самое на erlang тянуло бы сотни игр без проблем на том же железе… :)
                    +6
                    не в языке дело, как бы, а в алгоритмах :) и что-то мне подсказывает, что правильный код должен тянуть тысячи игр одновременно, сравните, блин, текущее железо и то, на котором танчики были оригинально :)
                      +1
                      О да! Я мечтаю переписать танчики на Erlang. Но дело не только в javascript. Сам код писался без прицела на производительность. Основной задачей до публикации было как можно скорее сделать что-то работающее.
                        +1
                        Проблема не в технологии. Node.JS прекрасно справляется с тысячами подключений и распараллеливается на несколько ядер процессора.
                        +1
                        Насколько помню, классические танки для описания карты использовали именно двумерный массив. Остальные объекты (танки, пули) хранились отдельно и были ограничения на количество пуль и противников (чисто технические, связанные с малым объёмом памяти и тем что проц имел частоту менее 2 МГц). А просчёт столкновений с пулями считался и вовсе как-то хитро (точно не по пересечениям прямоугольников).
                          0
                          Придирка: в оригинальных танчиках каждый кирпичный «блок» представлял из себя квадрат не 2х2, а 4х4, поэтому за один выстрел (обычным снарядом, не прокачанным) снималась не половина блока, а только четверть.
                            0
                            Disclaimer: демку запустить не удалось (видимо, очень много желающих), так что основывался на этой картинке:

                            image
                              +1
                              так и есть, каждая «1» представляет собой 4 кусочка стены. такой точности достаточно чтобы нарисовать уровень. а в памяти кусочки хранятся по другому и отстреливаются по четверти.
                                0
                                Понятно, спасибо.
                            –2
                            Но почему не github? Я поясню мысль — все остальное просто не удобно для того, чтобы ознакомиться с кодом «по-шустрому». Google code это вообще позор джунглям какой-то.
                              +1
                              Если не секрет что именно не удобно в google code? так же кликаете по папка и файлам и смотрите исходники. и github… он разве не только для Git? вообще я Mercurial использую в этом проекте.
                                +3
                                Абсолютно всё. Нет социальности, — форков, реквестов, сети…

                                Я свои проекты давно мигрировал на GitHub, знаю, о чём говорю.

                                Вам поможет hg-git.github.com/
                                  0
                                  попробуйте bitbucket — тот же github, только с поддержкой mercurial
                                    0
                                    GitHub поддерживает Mercurial и SVN.
                                  0
                                  Mercurial сильно не повезло именно с хостингами, которые его поддерживают. Но это тема другой дискуссии.
                                    +4
                                    Пробежаться в googlecode по проекту очень долго…
                                    Их дерево пока разворачивается, потом файлы открываются в другой странице, а в Github все ajax анимация и гораздо быстрее.
                                    + Граммотно расположенные элементы навигации (сверху) посмотрев файл можно вернуться сразу в корень проекта
                                    + Локальный быстрый поиск по всем файлам
                                    + Network view :) мое любимое
                                    + Удобное расположение форка
                                    и много мелочей

                                    ! И самый главный плюс :) Github человекоориентирован это значит заведя один аккаунт на себя можно форкать сколько угодно проектов к себе. Можете рассматривать это как workspace

                                    Типа о классный проект танчики, положу как я его к себе (один клик) уже лежит, надо бы для себя баги пофиксить, раз и пофиксил (+ удаленно они уже сохранились), один клик и уже поделился с другими.
                                    +2
                                    Чем вам не угодило вот это «ознакомление по-шустрому»?
                                      –2
                                      Оно не шустрое.
                                    +1
                                    Эх, таничики.
                                    Сколько времени было просижено за дэнди.
                                    Хабраэффект все же положил, не дает поиграть.
                                    Пойду нагуглю и все таки поиграю.
                                    0
                                    блин, хочется попробовать, да лежит капитально :-(
                                      0
                                      Можете немного цифр опубликовать? Начиная со скольки коннектов лагать начало, а со скольки вообще легло? Короче, небольшую статистику в студию
                                        +1
                                        Я к сожалению не считал точное кол-во подключений. Но пара десятков пользователей в чате, 2-3 игры и сервер уже еле ворочается, а со временем node занимает 100% процессорного времени даже если нет ни одной активной игры. В общем стыд и срам.
                                      0
                                      Если у вас таки дойдут руки до оптимизации поиска пересечений, стукните мне в скайп panki.ru, пара отличных решений именно для такой игры у меня есть.
                                      • UFO just landed and posted this here
                                        • UFO just landed and posted this here
                                            0
                                            Всеми руками поддерживаю.
                                              0
                                              Я не совсем разделю ваш оптимизм пожалуй. Это будет совсем другая игра. И я не уверен, что гейм дизайн тех самых танчиков успешно впишется в вариант с мультиплеером.

                                              Хотя хотелось бы, да. Я даже пару танчиков уже успел написать, но вот про мультиплеер не думал пока.
                                                0
                                                это отличная идея :)
                                              +2
                                              Ну зачем вы на гуглкод выкладываете, есть же гитхаб!
                                                +2
                                                Вот вам летающие «танчики» на тех же технологиях — rawkets.com
                                                  0
                                                  О да!!!
                                                    0
                                                    Эх. Нет соперников, а в танчиках они всегда есть (:
                                                    И в танчиках пули летали через всю карту, а тут только пару метров.
                                                    0
                                                    >Расчёт передвижения танков, полёта пуль, обработка столкновений и вообще всё, что относится к логике игры — происходит на сервере, так как негоже доверять такие вещи клиенту

                                                    А если (почти) все переложить это на клиентов, а на сервер отправлять контрольные точки, например на клиенте пуля долетела до танка — отправляется одно событие на сервер, а сервер только проверяет возможно ли такое событие и применяет его. Тут опять же свои сложности, синхронизация клиентов с сервером (т.к. время у всех разное), накопление событий будет происходить не только на сервере, но и на клиенте.
                                                      0
                                                      Я плохо себе представляю как это синхронизировать, если каждый клиент будет себе считать игру. Но как вариант можно сделать, чтобы один игрок выступал в роли «сервера», то есть игра обсчитывается у одного игрока, а данные для других пересылаются через реальный сервер. Но к сожалению это не из тех вещей которые бы я мог быстро провернуть. Возможно когда-нибудь будет так.
                                                      0
                                                      Кто-то под ником aim в чате игры предложил такую идею: разбить объекты игры на две группы, 1 — неподвижные объекты, их можно хранить в двумерном массиве, что позволит быстро искать пересечения. а танки, пули, бонусы хранить в виде списка прямоугольников (ну или в виде дерева) и обрабатывать их как сейчас. Должен получить хороший выигрыш по производительности. Идея мне понравилась, найти бы время реализовать её.
                                                        0
                                                        На самом деле дерево — штука не особо удобная. Я бы таки сделал трехмерный массив. Точнее, двухмерный, где каждая клетка поля — объект, который хранит информацию о своем содержимом. Насколько я помню, движение возможно с шагом в пол квадрата размера танка, а кирпичи уничтожаются в блоках еще вдвое меньше. То есть, реальный размер поля — 104×104. По сути, в игре нужно 3 слоя: для танков, для снарядов (отдельный слой, потому что снаряды ботов проходят через других ботов насквозь), для объектов карты (лес, лед, вода) и бонусов. Поскольку два объекта каждого из этих типов в одном месте быть не могут. Некоторые объекты могут занимать несколько клеток, например кирпич и танк — это объекты 4×4, а снаряд — 2×2.

                                                        Тогда для сдвига объекта придется проверять коллизии 8 клеток массива в трех измерениях при движении танка, поскольку он не может двигаться меньше чем на 2 шага, и всего 2 клеток для снаряда. Если на карте 2 игрока и 6 ботов — это максимум 20 движущихся объектов (у игроков может быть 2 снаряда).

                                                        При том клиенту вовсе не обязательно отрисовывать движение так же, как на сервере. На сервере можно считать гораздо грубее. То есть мы при игровом тике сдвигаем все движущиеся объекты, при этом проверяя, что находится на пути. Для объектов разного типа можно использовать разные генераторы тиков или пропускать их.

                                                        Слать изменения надо немедленно, не дожидаясь пока спросит клиент. Иначе зачем вообще сокеты?

                                                        При этом следует слать только информацию о изменении намерений. Если это выстрел — то надо слать информацию о его начале, а в конце — результат попадания. Если движение — о начале и остановке. А уж текущее положение клиент пусть сам вычисляет, задачка простейшая.
                                                          0
                                                          Слать изменения немедленно очень накладно, за один игровой шаг может измениться с десяток свойств, придется рассылать изменение каждого свойства по отдельности. Слать изменения пакетами должно быть быстрее. Сокеты нужны потому что: 1) — это одно постоянное подключение. 2) клиент не дергает каждый раз сервер, если есть что послать клиенту, сервер каждые несколько миллисекунд сам отправляет данные.

                                                          Пересылка только начала движения и остановки, и вычисление текущего положения самим клиентом имеет смысл как оптимизация, когда пересылка текущих координат станет узким местом. Но пока я этого делать не стану, потому что тут будут проблемы рассинхронизации в случае тормозов на сервере. Я бы не назвал алгоритм исправления таких погрешностей простейшей задачкой.
                                                            0
                                                            То, что каждый шаг шлется пакетом — понятно. Но для этого вовсе не обязательно ждать запроса клиента.

                                                            А это смотря как делать. С node.js не работал, но там должен быть механизм, позволяющий вызывать шаги с фиксированным интервалом, независимо от времени, потраченного на каждый шаг. Надеюсь, там не тупо остановка треда на N мсек? Тогда главное — чтобы задержка сети была не более ≈300-400 мсек. В таком случае особых сложностей с рассинхронизацией быть не должно. Разве что разрушаемое окружение немного добавляет проблем — его следует обрабатывать только по команде сервера.
                                                              0
                                                              Виноват, в статье смешал два понятия клиента. В общем, на сервере есть объект user. Этот объект каждые 50мс проверяет, если ли что отправить браузеру и отправляет данные, если они есть. То есть клиен-браузер не дергает сервер, а объект user на сервере, как клиент лога событий, запрашивает изменения у этого самого лога.
                                                              В node, как я понял, все асинхронное, и мой код только «просит» отправить данные клиенту, и тут же продолжает выполнение. А отправка данных происходит уже позже.
                                                              Возможно как раз придется реализовывать эту идею, потому что я уперся в объем трафика.
                                                          +5
                                                          TItemList

                                                          Боже, какой ужас! Я снова вижу Делфи на своём любимом JS. Зачем вы так издеваетесь над языком? =(
                                                            0
                                                            Если вас префик «Т» смутил, то не проблема, избавлюсь от призраков Delphi как-нить :)
                                                              +2
                                                              Смутил — это не то слово =)
                                                            0
                                                            Заметьте сколько энтузиазма! (((:
                                                            Эту энергию да в правильное русло.

                                                            Желаю удачи с проектом!
                                                              0
                                                              недавно закончил писать игру, почти тот же принцип и те же технологии, ремэйк старого Xonix. Наступал та не же грабли что и Вы. Пытался найти инфу, но чего-то вменяемого не нагуглил. Есть статья типа этой netlib.narod.ru/library/book0077/ch05_01.htm, но тут все таки больше для «общего развития», нежели разбор и оптимизация существующих реальных игр и проблем возникающих в их реализации.
                                                              Будет интересно почитать, чем закончились ваши оптимизации, постарайтесь хотя бы кратко записывать и не верные шаги тоже.
                                                              Побольше технической инфы и может даже кусков кода, для решения той или иной проблемы в статьях думаю не помешает.
                                                                0
                                                                Готовится статься про оптимизацию, думаю до конца месяца будет готово. Проблема с пересечениями решена. Решена не идеальным способом, но теперь на первом плане в результатах профилирования совсем другие вещи.
                                                                +1
                                                                Первая мысль для предоления подобного рода тормозов — заиспользовать структуру R-tree (напр. реализацию github.com/imbcmdth/RTree).
                                                                  0
                                                                  Это, конечно же, только для варианта, где данные не описаны массивом.
                                                                  0
                                                                  Мне кажется вы слишком сильно усложнили задачу, вполне можно было работать с двумерной картой, элементы которой являются типом ячейки поля (0-пусто, 1 — стена, 2 — вода, 3 — что-то еще). В добавок достаточно от трасировать пулю по прямой а не искать какие-то супер сложные алгоритмы столкновений. Таким образом задача будет состоять только в том, чтобы найти кратчайший путь, по которому должен будет ехать бот от спауна до твоей базы (он в конце концов будет просто закэширован, обновлять его придется только в случае разрушения приграды), и на каждом его шаге пытаться оттрасировать пулю в 4 стороны, что в худшем случае равно периметру поля… Не нужны тут ни какие 3х мерные массивы, достаточно просто определять поведения по типу препятствия и объекту, который совершает действие.

                                                                  Only users with full accounts can post comments. Log in, please.