Pull to refresh

Создание сервера онлайн ММО игр на PHP и Unity ч. 11 — FPS, Ping, паузы между командами, интерполяция и экстраполяция

Level of difficultyMedium
Reading time7 min
Views4K

В этой серии статей речь пойдет о том что такое PING и какими приемами можно сгладить задержку пересылки пакетов при его высоком значении в realtime онлайн играх с примерами кода на C# в игровом движке Unity для игр на ПК, мобильных устройствах и браузерных игр WebGL.

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

Что такое PING

Начнем с теории: отправляя команду в ММО игре, на сервер она доходит не моментально - ей требуется время дойти до физического сервера ("железа") по кабелю, который проложен, в том числе по дну океана и часто этот сервер находится очень далеко (поэтому часто делают несколько серверов в странах, близких к игрокам).

Сервер также может отправлять пакеты игроку на устройство, но т.к. у нас websocket сервер, это не происходит сразу после запроса (хотя можно сделать, но будет неправильно) как, например, в HTTP соединениях (то есть наш сервер подойдет не только к браузерным играм, но и для ПК, мобильных устройств и консолей, однако для демонстрации я использую браузерную на моем сайте).

я изменил картинку из предыдущей статьеб чтобы показать что websocket это не классическая модуль запрос - ответ
я изменил картинку из предыдущей статье, чтобы показать что websocket это не классическая модуль запрос - ответ

Время, которое затрачено на отправку пакета и его возврат обратно - называется Ping, выражается (обычно) в миллисекундах (в одной секунде 1000 мс.) и чем он ниже - тем лучше. Важно отметить, что в него не включено время программных вычислений для обработки запроса, как например в расчете RPS сайтов и web приложений

Как высчитать PING

В Unity для версий игр ПК и мобильных устройств есть готовые инструменты анализа PING на устройстве, но в браузерных версиях игр (WebGL) они не работают.

инструменты расчета ping через код в Unity
инструменты расчета ping через код в Unity

Я расскажу о своем приеме расчета PING, который сможет заменить встроенный инструмент в Unity и использоваться в браузерных версиях игр:

  1. Отправляем с пакетом временную метку Unix Timestamp с точностью до миллисекунды по времени клиента. На скриншоте ниже пример демонстрационной игры с моего сайта (код ее открыт и доступен в GIT) я добавляю метку к каждому запросу (по времени на устройстве пользователя)

  1. На сервере, когда нужно отправить пакет по данной команде обратно, я отнимаю от этой метки-времени время, которое прошло с момента как сервер получил данную команду (разница в миллисекундах) - строки $event['time'] - $microtime .

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

код написан на PHP, но это не важно - идею можно использовать на любом языке
код написан на PHP, но это не важно - идею можно использовать на любом языке
  1. При получении команд в клиентской части я снова по времени клиента отнимаю полученные значения и это и есть чистый сетевой Ping. Для статистики я сохраняю результаты в массив (список), чтобы выводить среднее значение (сумма всех значений, делённое на количество этих значений в массиве).

  1. Периодически шлю попутно с пакетами из клиента на сервер дополнительный параметр ping, тем самым сервер знает средний пинг каждого игрока (этот параметр не влияет ни на какие вычисления на сервере и служит как информативный в связи с тем что его легко подменить в клиентской части через код)

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

Однако можно заметить, что PING в нашем случае зависит от размера пакета (вдобавок Websocket протокол на базе TCP в случае потери пакетов начнут их повторно отправлять, будет ждать подтверждение и т.п.), но как по мне это значение более удобно (мне важнее сетевые задержки на реальных игровых пакетах, чем анализ скорости отправки пары байт).

На вопрос каким является хороший показатель Ping в играх Google ответил цифру 50 мс.

Эта цифра означает, что если наш пакет из клиента (игры) будет идти 25 мс. в одну сторону и 25мс. с сервера в клиент (игру), т.е. в другую.

Я подготовил видео, где поясняю и показываю то, о чем написал в данном разделе (функционал уже немного изменился, но концепт остался тот же):

Что такое FPS

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

Возможно Вы знаете из Wikipedia, что глаз человека воспринимает смену изображений как плавную при частоте в диапазоне 48 (фильм "Хоббит" был первым снят с этой частой) - 60 FPS (кадров в секунду), поэтому у каждой команды есть своя пауза и она составляет обычно столько, сколько человеческий глаз может уловить и больше, а значит минимальную комфортную паузу между кадрами можно высчитать по формуле: 1 секунда / 60 FPS = 0,017 (округленно) секунды = 17 мс.

Однако в играх (в том числе realtime стиля "экшен") паузы между командами намного больше, например за одну команду движения персонаж обычно идет больше, чем тот минимум, который мы способны уловить.

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

Отсюда и считается, что хорошим показателем FPS в играх является 60 FPS

FixedUpdate Unity

В Unity есть циклы, выполняющийся раз в фиксированное времяFixedUpdateчастота которого и выражается в виде миллисекунд. В текущем примере стоят частота 50 FPS

Интерполяция

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

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

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

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

  2. мы знаем среднее значение него PING на текущий момент, а значит мы знаем когда примерно когда пакет достигнет сервера от момента отправки - 1/2 от скорости пинг

  3. при получении ответа от сервера мы знаем что она выполнилась 1/2 пинга назад (т.к. этот пакет шел до нас это время).

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

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

Видео ниже демонстрирует как работает интерполяция:

Экстраполяция

Выше я уже сказал что PING величина не постоянная и может резко меняться (особенно при работе с беспроводным интернетом) и в моменте он может резко увеличится (ухудшиться) и вернутся на нормальное значение. За это время весь игровой мир может замедлиться - такое явление называют "фризы" (от англ. freez - замерзнуть). Для подобных ситуаций используют подход, который продолжает поведение объекта как если бы он продолжил бы делать то же самое, но в нашем случае (в игре) - с меньшей скоростью, называется экстраполяция

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

А так же подготовил видео с демонстрацией как это работает в самой игре (это улучшило переход в бесшовном мире)

В настоящий момент проект на моем сайте http://mmogick.ru является не коммерческим (есть бесплатный демо доступ в админ панель) и я веду его в частном порядке, но верю что продолжая и делясь архитектурными идеями в будущем это станет рабочим продуктом сделанным в России, а статьи помогут в написании игрового сервера для MMO игр.

Буду благодарен за лайк статье.

Если вам интересно чем закончится данный проект - подписывайтесь на мой профиль (я публикую только статьи данного проекта) и следите за новыми видео на Youtube (они выходят чаще чем статьи).

История:

  1. Введение

  2. Масштабируемость и асинхронность

  3. WebSocket

  4. Redis

  5. LUA и JavaScript

  6. Выбор технологий, протокола и архитектурный шаблон Entity Component System

  7. Игровые локации (тайловые карты)

  8. Клиентская часть на Unity

  9. Игровые серверные механики

  10. Открытый бесшовный мир в 2D игре

  11. FPS, Ping, паузы между командами, интерполяция и экстраполяция

  12. Очереди и параллельное программирование на CPU

  13. Event-driven паттерн, JSON-RPC и почему не сервисная (SOA) архитектура

  14. Сетевая карта и задержка кадра (Latency frame) по RFC 2544 (1242)

  15. Создание сервера для онлайн ММО игр на PHP

  16. Готовое MVP сервиса 2D MMO RPG игр (realtime)

Tags:
Hubs:
Total votes 4: ↑1 and ↓3-2
Comments36

Articles