Как сохранить синхронизацию между сервером и клиентом для точных сетевых игр, таких как Quake 3?


15

Я работаю над 2D-шутером сверху вниз и стараюсь копировать концепции, используемые в сетевых играх, таких как Quake 3.

  • У меня есть авторитетный сервер.
  • Сервер отправляет снимки клиентам.
  • Снимки содержат метку времени и позиции объекта.
  • Объекты интерполируются между положениями снимка, поэтому движение выглядит плавным.
  • По необходимости, интерполяция сущностей происходит немного «в прошлом», поэтому у нас есть несколько снимков, между которыми можно интерполировать.

Проблема, с которой я сталкиваюсь, это «синхронизация часов».

  • Для простоты давайте на мгновение представим, что при передаче пакетов на сервер и с него существует нулевая задержка.
  • Если часы сервера на 60 секунд опережают часы клиента, то метка времени снимка будет на 60000 мс впереди локальной метки времени клиента.
  • Следовательно, моментальные снимки сущности будут собираться и храниться в течение примерно 60 секунд, прежде чем клиент увидит, что какой-то конкретный объект делает свои ходы, потому что это занимает много времени, чтобы часы клиента успели догнать.

Мне удалось преодолеть это путем вычисления разницы между часами сервера и клиента при каждом получении снимка.

// For simplicity, don't worry about latency for now...
client_server_clock_delta = snapshot.server_timestamp - client_timestamp;

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

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

Другими словами, как я могу предотвратить слишком позднюю или слишком раннюю интерполяцию, когда часы значительно десинхронизированы, без появления рывков?

Редактировать: Согласно Википедии , NTP может использоваться для синхронизации часов через Интернет с точностью до нескольких миллисекунд. Тем не менее, протокол кажется сложным, и, возможно, излишним для использования в играх?


как это сложно ? Это запрос и ответ, каждый с отметками времени передачи и прибытия, а затем немного математики, чтобы получить
урод

@ratchetfreak: По словам ( mine-control.com/zack/timesync/timesync.html ): «К сожалению, NTP очень сложен и, что еще более важно, медленно сходится в точном перепаде времени. Это делает NTP менее чем идеальным для сети игровой процесс, в котором игрок ожидает, что игра начнется немедленно ... »
Joncom

Ответы:


10

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

Я нашел способ здесь , однако, что , кажется , относительно проста:

Он утверждает, что синхронизирует часы с точностью до 150 мс (или лучше) друг от друга.

Я не знаю, будет ли это достаточно хорошо для моих целей, но я не смог найти более точную альтернативу.

Вот алгоритм, который это обеспечивает:

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

Простой алгоритм с этими свойствами выглядит следующим образом:

  1. Клиент маркирует текущее местное время в пакете «запрос времени» и отправляет на сервер
  2. При получении сервером сервер помечает серверное время и возвращает
  3. После получения клиентом клиент вычитает текущее время из отправленного времени и делит на два для вычисления задержки. Он вычитает текущее время из времени сервера, чтобы определить дельту времени клиент-сервер, и добавляет половину задержки, чтобы получить правильную дельту часов. (Пока этот algothim очень похож на SNTP)
  4. Первый результат должен быть немедленно использован для обновления часов, поскольку он доставит локальные часы по крайней мере в правую точку (по крайней мере, в правильный часовой пояс!)
  5. Клиент повторяет шаги с 1 по 3 пять или более раз, каждый раз останавливаясь на несколько секунд. В промежутке может быть разрешен другой трафик, но для достижения наилучших результатов его следует минимизировать.
  6. Результаты приема пакетов накапливаются и сортируются в порядке наименьшей задержки на наибольшую задержку. Медианная задержка определяется путем выбора выборки средней точки из этого упорядоченного списка.
  7. Все образцы выше приблизительно 1 стандартного отклонения от медианы отбрасываются, а остальные образцы усредняются с использованием среднего арифметического.

Единственная тонкость этого алгоритма состоит в том, что пакеты выше стандартного отклонения выше медианы отбрасываются. Цель этого состоит в том, чтобы устранить пакеты, которые были повторно переданы TCP. Чтобы визуализировать это, представьте, что выборка из пяти пакетов была отправлена ​​через TCP, и не было повторной передачи. В этом случае гистограмма задержки будет иметь один режим (кластер), центрированный вокруг средней задержки. Теперь представьте, что в другом испытании один пакет из пяти передается повторно. Повторная передача приведет к тому, что этот один образец упадет далеко вправо на гистограмме латентности, в среднем вдвое дальше, чем медиана основного режима. Просто вырезая все выборки, которые отклоняются более чем на одно стандартное отклонение от медианы, эти паразитные моды легко устраняются, если предположить, что они не составляют основную часть статистики.

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


Как это сработало для вас тогда? Я сейчас в такой же ситуации. Я использую серверную платформу, которая поддерживает только TCP, поэтому я не могу использовать NTP, который отправляет дейтаграммы UDP. Я изо всех сил пытаюсь найти любые алгоритмы синхронизации времени, которые претендуют на надежную синхронизацию времени по TCP. Синхронизация в течение секунды будет достаточно для моих нужд, хотя.
Динамокай

@dynamokaj Работает довольно хорошо.
Joncom

Здорово. Возможно ли, что вы могли бы поделиться реализацией?
Динамокай

@dynamokaj Кажется, я не могу найти такую ​​реализацию ни в одном проекте, о котором я могу думать прямо сейчас. В качестве альтернативы, для меня достаточно хорошо работает: 1) немедленно использовать задержку, которую вы рассчитываете из одного запроса / ответа ping, а затем, 2) для всех будущих таких ответов постепенно, а не мгновенно, переходить к новому значению. Это имеет эффект «усреднения», который был достаточно точным для моих целей.
Joncom

Нет проблем. Я использую свой бэкэнд-сервис в Google App Engine, следовательно, в инфраструктуре Googles, где серверы синхронизируются с помощью NTP-сервера Google: time.google.com ( developers.google.com/time ), поэтому я использую следующий NTP-клиент для своего мобильного клиента Xamarin Mobile чтобы получить смещение между клиентом и сервером. components.xamarin.com/view/rebex-time - Спасибо, что нашли время ответить.
Динамокай

1

По сути, вы не можете исправить [весь] мир и, в конце концов, вам придется провести черту.

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

Учтите, что во многих популярных MMO отстающие игроки визуально очевидны. Если вы видите, что они бегут на месте, прямо в стену, ваш клиент экстраполирует. Когда ваш клиент получит новые данные, игрок (на его клиенте), возможно, переместился на значительное расстояние и будет «резиновым» или телепортироваться в новое место («джерки», о котором вы упоминали?). Это происходит даже в крупных играх известных брендов.

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

«Если у вас плохой интернет, вы не можете играть в эту игру на конкурентной основе».
Это не попутный доллар или ошибка.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.