Player.IO. Синхронизация игроков

Сегодня мы поговорим о способах синхронизации игровых процессов на игровых клиентах.

Синхронизация — это получение текущего состояния игры для расчета наиболее точного положения всех игроков и динамических объектов. Чтобы понять основные принципы синхронизации игровых клинетов, мы пока закроем глаза на проблемы с задержками.

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

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

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

Не забывайте, что если вы создаете Flash игры, то они, как правило, должны работать в школах, на работе, на слабых компьютерах и, конечно, же при медленных Интернет соеденениях. Я часто вижу людей, которые смотрят на такие игры как «Halo» или «Team Fortress 2» и говорят, что они хотят сделать что-то такое же плавное.

Но технология, которую мы используем, не идет ни в какое сравнение с теми технологиями, которые могут использовать большие игры. Хорошая коммерческая онлайн игра будет использовать UDP протоколы, а мы вынуждены работать только TCP протоколами и должны адаптировать работу наших игр на слабых компьютерах.

Если вы вдруг не знаете, то TCP и UDP — это два различных протокола для передачи данных с различными возможностями. Я, конечно, сильно утрирую, но основное отличие UDP от TCP заключается в том, что TCP гарантирует доставку данных (отправитель должен получить уведомление о том, что отправленный пакет достиг получателя), а UPD не гарантирует доставки данных и следовательно для него свойственны потери пакетов, но при этом UDP работает намного быстрее и создает наименьшую нагрузку.

Но, как бы там не было, вам совершенно не нужно беспокоиться о выборе между протоколами, потому что это сделали за нас! Flash позволяет работать только с TCP протоколами, так что мы не имеем возможность выбора. Впрочем, если бы выбор между двумя этими протоколами был предоставлен человеку, не понимающему в разработке, то он наверное бы выбрал TCP вместо UDP для игры в реальном времени. Так как работая с TCP, мы имеем гарантии того, что пакеты будут доставлены, даже не смотря на то, что при этом возникают некоторые ограничения. И такой выбор будет действительно лучше для нас, так как нам не нужно будет беспокоиться о потери пакетов и других возможных осложнениях, связанных с этим.

Надеюсь, что вы еще не забыли о наших игроках из предыдущих статей, у которых достаточно ощутимые проблемы с задержками, возникающих, в том числе, и из-за использования протокола TCP?

Мы достаточно поговорили о задержках и протоклах, теперь настало время рассмотреть возможные способы сведения задержек к миниммуму. При использовании TCP протокола с быстрым соединением задержки на самом деле не так велики. Более того, в большинстве случаев, протокол TCP работает почти так же быстро, как и UDP.

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

Так же у нас есть еще и такое ограничение, как пропускная способность. Пропускная способность — это объем информации, который может быть отправлен по сети.

Прежде чем что-то сделать, мы должны знать сколько информации мы можем отправлять. Данная статистика пропускной способности весьма не проста для анализа. Стоит ли нам адаптировать нашу игру на пропускную способность в 56кб/с только потому, что большинство на нее ориентируются? Вот некоторая статистика о передачи данных в Интернет за Август 2009:

Этот график демонстрирует нашу скорость по отношению к прошлым результатам. График построен при помощи dslreports.com. Серое — это скорость скачивания, голобое — это скорость загрузки. Как вы можете видеть около 10% людей находится ниже порога в 300kb.

Как можно видеть по статистике многие работают со скоростью 300кб/с. А как же нам рассчитать объем информации, отправляемый нашей игрой и спрогнозировать возможную нагрузку? Мы должны начать считать биты. Размер каждого типа данных выглядит следующим образом:

int или unit: 4 байта
String (за каждый символ): +1 байт
Number: до 7 байт
Boolean: 1 байт

Мы считаем размер всех типов значений, вкладываемых в наше сообщение и отправляем, все? Не совсем. Как правило, каждое сообщение требует TCP и Ethernet заголовки с технической информацией, чтобы сообщение могло попасть туда, куда ему нужно. И, к сожалению, в заголовке TCP намного больше информации, чем у UDP. Общий заголовок, включая заголовок Ethernet, состоит по меньшей мере из 84 байт и может быть больше (до 120 байт). И это достаточно большой объем информации по сравнению с тем, сколько информации мы собираемся отправить в таком сообщении. Таким образом, получается, что нам следует сводить к минимуму количество отправляемых сообщений и свести к максимуму количество данных в каждом сообщении.

Синхронизация состояний

Теперь у нас есть общее представление о наших ограничениях в сети и мы можем вернуться к методам синхронизации. Синхронизация игровых состояний состоит из отправки текущего состояния объекта через фиксированные промежутки времени, например каждые 100мс.

По сути, каждый раз, когда вы отправляете сообщение для синхронизации игрового состояния, вы отправляете снимок игрового мира. Но имейте в виду, что мы не должны отправлять 30 сообщений в секунду, чтобы соотвествовать частоте кадров в игре. Это слишком накладно! Кроме того, в таком случае мы будем напрасно тратить пропускную способность сети, и соотвественно в таком случае мы все равно не получим идеально плавную картинку, все из-за тех же пресловутых задержек.

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

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

Одно сообщение с большим количеством данных и технической информацией.

Конечно, при синхронизации состояний есть и свои недостатки. Если мы будем использовать только этот метод, то мы должны ожидать следующего цикла для передачи/получения информации. То есть, это означает то, что мы должны ожидать 100мс или что-то около того, пржде чем игрок получит новое сообщение. В ситуации когда время важно, это может негативно сказаться на игровом процессе.

Много маленьких сообщений с небольшим количеством данных и таким же объемом технических данных.

Событейная синхронизация

Если вы пытаетесь целиться в другого игрока, который двигается, или кто-то стреляет в вас, вы должны узнать эту информацию как можно скорее. Именно поэтому нам требутеся так же и событейная синхронизация.

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

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

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

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

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

Чтобы избегать таких проблем, вам следует устанавливать жесткое ограничение на количество сообщений, то есть записывать действия пользователя в последовательность и отправлять их одним сообщением. Это в большей степени решит вопрос перегрузки сетевого соеденения в обмен на небольшую задержку вызванную сбором информаций и действиях пользователя. Чтобы достичь этого мы можем создать быстрый таймер (около 25мс) и посылать все действия одним сообщением каждый тик этого таймера, если конечно за этот период были какие-либо действия. Такой подход так же помогает нам увеличить количество полезной информации в одном сообщении, что повышает его ценность.

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

 

 

Временные промежутки

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

Хорошо, почему бы тогда не использовать таймеры? Таймеры являются не надежными, а так же тик таймеров не постоянен и он пытается нагнать упущенное время.

Что же нам делать тогда? Мы не можем иметь игроков и объекты, движущихся с разными скоростями в зависимости от того, как быстро работает у них игра, или еще хуже, если их скорость будет зависить от сервера. Это просто кошмар! Конечно, синхронизация состояния игры будет их устанавливать в правильное положение, но координально это не решает проблемы. Таким образом, наша задача сделать так, чтобы все клиенты работали с одной скоростью.

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

Flash имеет два способа вычисления ms: getTimer() и Date.getTime(). getTimer() вернет вам число ms с момента запуска приложения, в то время как Date.getTime() вернет реальное время (системное время) в ms. Я предлагаю вам использовать Date.getTime() в связи с тем, что getTimer() может стать не точным в течении долгого времени работы приложения.

Так как мы можем использовать это? Ответ прост: мы можем записывать время каждый игровой тик до следующего тика:

elapsedTime = nowTime - prevTime;

Где:

прошлоВремени = текущееВремя - предВремя;

Таким образом у нас есть скорость работы в ms и мы можем посчитать сколько времени ушло на доставку сообщений. Однако будьте внимательны, системные часы могут быть немного не точными и отличаются друг от друга. Так что, даже в этом случае мы можем иметь только приблизительное время.

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

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

Конечно, если сообщение опоздало, то ущерб уже нанесен. Мы должны были предсказать что произойдет, пока дожидались сообщения. Если полученная информация — это не то, что мы ожидали, то у нас возможны серьезные ошибки, которые могут накапливаться в нашей игре. Даже зная сколько мы ожидали сообщение (запомнили временной интервал) — это не поможет нам устранить проблемы, и нам потребуется большая корреция данных.

Различные решения этой проблемы будут дополнительно рассмотрены в статье «Player.IO. Советы и рекомендации».

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

Ну, а пока вы можете посмотреть пример с реализаций синхронизаций через события и через синхронизацию игровых состояний с использованием Player.IO:

 

Это вольный перевод статьи: «Building Flash Multiplayer Games — Synchronization»

Содержание

  1. Как создать онлайн игру? Анонс
  2. Player.IO. Введение
  3. Player.IO. Основы или первая онлайн игра
  4. Player.IO. Пошаговые игры
  5. Player.IO. Сетевые архитектуры
  6. Player.IO. Безопасность в онлайн играх
  7. Player.IO. Пример пошаговой игры
  8. Player.IO. Онлайн игры в реальном времени
  9. Player.IO. Синхронизация игроков
  10. Player.IO. Интерполяция или удивительный мир обмана
  11. Player.IO. Решение проблемы задержек
  12. Player.IO. Советы и рекомендации

 


Индикаторы: Action Script 3, Уроки
Постоянная ссылка

 

 

Прошло 8 месяцев с момента выхода последнего урока по созданию TD. Стоит ли надеяться на продолжение ?
Впрочем это не первый случай когда обещают показать весь процесс от создания до релиза и получения денег. Как обычно дальше прототипа дело не уходит.
З.Ы. Это не в коем случае не "негатива псто". В любом случае спасибо за уже данные уроки.

Неуловимый Джо
19 Декабря 2012
— 04:05
#

Спасибо Антон за замечательныей уроки^^

Crash
19 Декабря 2012
— 05:02
#

Шарики не просто сильно дёргаются, а иногда и вообще оказываются в различных положениях на разных вкладках.. =/

Александр
19 Декабря 2012
— 06:08
#

@Неуловимый Джо, продолжение уроков по TowerDefence еще будет. Только не помню, чтобы я обещал раскрыть весь процесс в плоть до получения денег. Уроки закончатся когда основная часть игры будет закончена (игровые меню + один-два уровня). То есть изначально я планировал раскрыть только техническую часть игры, но никак не продажу.

Возможно упоминание про сайтлоки могло ввести вас в заблуждение, но это опять же лишь технические вопросы по подготовке возможных сайтлоков с использованием общедоступного API, например от Mochi.

Ant.Karlov
19 Декабря 2012
— 06:25
#

@Александр, данный пример — это лишь грубая реализация того, что описано в статье: синхронизация через состояния и через события. В примере еще не используется интерполяция и экстраполяция. Погрешность (неточное позиционирование) тоже возможна и об этом будет рассказано более подробно уже в следующих записях.

Ant.Karlov
19 Декабря 2012
— 06:28
#

Спасибо за перевод статьи. И за красочные примеры.

elbik
19 Декабря 2012
— 13:45
#

спасибо, очень ждал. Жаль что сразу не перешли к самом сложному для понимания - интерполяции.

rnd_d
20 Декабря 2012
— 05:15
#

Большое спасибо за урок, очень интересно!
Не поверил глазам, когда увидел его =)
P.S. в течение месяца каждый день проверял блог =)

Ян
20 Декабря 2012
— 15:49
#

Антон , спасибо за статью !!!

Алексей
20 Декабря 2012
— 22:52
#

Решил попробовать player.io в качестве сервера для этой игрули: http://playspal.com/games/action/battle-road/

Порог вхождения минимальный, очень просто и доступно, но с объёмом в 1кбc справляется как-то не очень. Пинг бывает до 600мс. В игруле очень заметны ошибки экстраполяции и вялая обратная связь игрока на отдельно взятом экране.

Может руки кривые, может бесплатный аккаунт, а может сервис не подходит для игр с большими скоростями объектов.

Был у кого-нибудь положительный опыт с подобными задачами?

Эс
24 Декабря 2012
— 08:11
#

Появилось у нас желание сделать игру сетевую, сначала хотел серверную часть сам делать. Но ты тук как тут с подходящей статьей :). Начал разбираться в этом движке ну и конечно наткнулся на проблемы с задержками. В общем для России не вариант, задержки очень большие. У меня были прыжки как у тебя в примере выше, причем без преувеличения. Теперь вот рассматриваю движки которые можно на свой сервер ставить. В частности приглянулся photon, правда порог вхождения оказался больше а документации меньше :). Зато возможностей больше а ограничений меньше.

Сергей
25 Декабря 2012
— 20:55
#

2сергей:
Я в итоге тоже от player.io отказался из-за лага. Доделаю игру до конца на нём, а потом на цпп сервер перепишу.

Вердикт: player.io - отличный инструмент для понимания основных принципов создания мультиплеерных игр без необходимости вникать в суть сокетов и протокола, но для проектов с претензией на качество сервис совершенно не подходит.
Возможно, платные аккаунты решают проблему высокого пинга, но за 30$ в месяц можно купить виртуальный хостинг с возможностью запуска демонов, а за 500$ вообще арендовать сервак целиком.

Антону спасибо за наводку на player.io и всё из этого вытекающее.

Эс
25 Декабря 2012
— 21:51
#

2Эс:
В принципе на первое время в качестве сервера может выступать домашний комп, если канал достаточно широкий.
По моему сразу бросаться в разработку своего сервера тоже смысла нет да и граблей там раскидано не мало. А такие вот фраемворки как фотон позволяют не задумываться о заморочках с передачей данных и дает больше времени на логику игры.
Я вот подумал быстро все равно лучше не получится, а цены для инди разработчиков вполне приемлемые, есть бесплатный вариант до 100чел онлайн, что в полне достаточно для многих.

Вообще тема интересная хотелось больше обсуждений, обмена опыта так сказать :)

Сергей
25 Декабря 2012
— 22:47
#

@Эс, посмотрел вашу игру — интересно сделана, но в таком виде сетевая игра конечно совершено не играбильна. Не зря наверное пишут в этих статьях мол чтобы сделать хорошую сетевую игру на флеше — надо быть настоящим волшебником :)

Какой у вас пинг в игре в среднем?

Ant.Karlov
28 Декабря 2012
— 07:07
#

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

Ant.Karlov
28 Декабря 2012
— 07:09
#

@Эс,

> Вердикт: player.io - отличный инструмент для понимания основных принципов создания мультиплеерных игр без необходимости вникать в суть сокетов и протокола, но для проектов с претензией на качество сервис совершенно не подходит.

Сетевые проекты бывают разные, и, например, для пошаговых игр или для каких-то не очень шустрых реалтаймовых игр Player.IO более чем подходит. На сайте Player.IO есть же ссылки на игры которые используют этот сервис и они хорошо играются даже из России ;)

Ant.Karlov
28 Декабря 2012
— 07:12
#

Заменил быстрые машинки на медленных роботов.
Стало вполне возможно играть, но всё-таки пинг ощутимый.

http://playspal.com/games/action/laserbots/

Эс
29 Декабря 2012
— 18:17
#

Всех с наступающим новым годом! Желаю красивого кода в новом году!

Марк
31 Декабря 2012
— 19:38
#

Спасибо огромное за ваши статьи ! Интересно а сколько бы вы запросили за обучение вашему стилю рисования ? :))))

flashfoxter
15 Апреля 2013
— 12:31
#