Як тримати синхронізацію годинник-сервер для точних мережевих ігор, таких як 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

Після пошуків навколо здається, що синхронізація годин 2 і більше комп'ютерів не є тривіальним завданням. Такий протокол, як NTP, робить хорошу роботу, але, мабуть, повільний і занадто складний, щоб бути практичним в іграх. Крім того, він використовує UDP, який не працює для мене, оскільки я працюю з web-сокетами, які не підтримують UDP.

Однак тут я знайшов метод , який здається досить простим:

Він стверджує, що синхронізувати годинник не менше 150 мс (або краще) один від одного.

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

Ось алгоритм, який він пропонує:

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

Простий алгоритм із цими властивостями полягає в наступному:

  1. Клієнт маркує поточний місцевий час на пакеті "запит на час" і відправляє на сервер
  2. Після отримання сервером сервер маркує час сервера і повертається
  3. Після отримання клієнтом клієнт віднімає поточний час від відправленого часу і ділиться на два, щоб обчислити затримку. Він віднімає поточний час від часу сервера, щоб визначити дельту часу клієнт-сервер, і додає з половиною затримки, щоб отримати правильну дельту годинника. (Поки що цей альготим дуже схожий на SNTP)
  4. Перший результат слід негайно використати для оновлення годинника, оскільки він отримає локальний годинник принаймні правильним бальним парком (принаймні правильним часовим поясом!)
  5. Клієнт повторює кроки від 1 до 3 п’ять і більше разів, кожен раз призупиняючи кілька секунд. Інший трафік може бути дозволений у проміжний час, але його слід мінімізувати для найкращих результатів
  6. Результати надходжень пакетів акумулюються та сортуються за найменшою затримкою до найвищого латентного порядку. Середня затримка визначається шляхом вибору зразка середньої точки з цього упорядкованого списку.
  7. Усі зразки вище приблизно 1 стандартного відхилення від медіани відкидаються, а решта зразків усереднюються за допомогою середнього арифметичного.

Єдиною тонкістю цього алгоритму є те, що пакети вище одного стандартного відхилення над медіаною відкидаються. Метою цього є усунення пакетів, які були повторно передані TCP. Щоб візуалізувати це, уявіть, що зразок з п'яти пакетів був надісланий через TCP, і повторна передача не відбулася. У цьому випадку гістограма затримки матиме єдиний режим (кластер), орієнтований навколо серединної затримки. Тепер уявіть, що в іншому випробуванні повторно передається один пакет із п'яти. Повторна передача призведе до того, що цей зразок впаде далеко вправо на гістограмі затримки, в середньому вдвічі більше, ніж медіана основного режиму. Просто вирізавши всі вибірки, які випадають більше ніж одне стандартне відхилення від медіани, ці бродячі режими легко усуваються, припускаючи, що вони не складають основну частину статистики.

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


Як це виявилося для вас тоді? Зараз я перебуваю в тій же ситуації. Я використовую серверну основу, яка підтримує тільки TCP, отже, я не можу використовувати NTP, який надсилає дейтаграми UDP. Я намагаюся знайти будь-які алгоритми синхронізації часу, які вимагають зробити надійну синхронізацію часу через TCP. Синхронізації протягом секунди було б достатньо для моїх потреб.
динамокай

@dynamokaj працює досить добре.
Йонком

Класно. Можливо, ви могли б поділитися реалізацією?
динамокай

@dynamokaj Здається, я не можу знайти такого втілення в жодному проекті, про який я зараз думаю. Крім того, що для мене досить добре працює: 1) негайно скористайтеся затримкою, яку ви обчислюєте з одного запиту / відповіді ping, а потім, 2) для всіх майбутніх таких відповідей, орієнтуючись на нове значення поступово, а не миттєво. Це має ефект "усереднення", який був досить точним для моїх цілей.
Йонком

Без проблем. Я запускаю свою серверну службу в Google App Engine, отже, в інфраструктурі Googles, де сервери синхронізуються за допомогою сервера Google NTP: 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.