1. Части I, II (синглплеер с авторитарным сервером)
  2. Часть III (Появление врага)
  3. Часть IV (Хэдшот!)

Как повесить идеальный хэдшот если у тебя пинг 2 секунды? Вы узнаете в этой статье.

Текущий алгоритм работы мультиплеера


  • Сервер получает команды с клиентов и времена их отправления
  • Сервер обновляет состояние мира
  • Сервер с некоторой частотой отправляет свое состояние всем клиентам
  • Клиент отправляет команды и локально воспроизводит их результат
  • Клиент получает обновленные состояния мира и:
    • Применяет состояние от сервера
    • Заново применяет все свои команды, которые сервер не успел применить.
    • Интерполирует предыдущие состояния других игроков
  • С точки зрения игрока, есть два серьезных последствия:
    • Игрок видит себя в настоящем
    • Игрок видит других в прошлом.

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

Компенсация лага


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

Но вы промахнулись!

Почему это произошло? Как такое случилось?

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



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

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

Вот как это работает:
  • Когда вы стреляете, клиент отправляет событие выстрела сервера, содержащее серверное время состояния, которое отображалось в момент выстрела. И точное направление вашего оружия.
  • Это КЛЮЧЕВОЙ момент. Так как сервер все равно рассылает свое состояние каждый кадр по клиентам, он может восстановить любой момент из прошлого. В том числе, он может восстановить мир в точности таким, каким его видел клиент в момент выстрела.
  • Таким образом сервер оставляет стрелка в том положении, в котором он находится; а весь остальной мир меняет на то состояние, которое клиент отображал в момент выстрела, т.е. возвращает назад во времени.
  • Сервер обрабатывает выстрел и получает его результат.
  • Сервер возвращает весь мир в текущее состояние времени.
  • Сервер применяет результат выстрела и рассылает обновления по клиентам как обычно.

И все счастливы!



Сервер счастлив потому что он сервер. Он всегда счастлив, ведь он всегда прав.
Стрелок счастлив, потому что он целился в голову врага и разнес её одним точным выстрелом.
Убитый — единственный, кто может быть недоволен. Если он стоял на месте в момент выстрела, он сам и виноват. Если он бежал, значит вы хороший стрелок.
Но что если он перебежал через опасную зону, а доли секунды спустя его застрелили, когда он думал что находится в безопасности? Он будет огорчен.



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

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

Было бы гораздо хуже, если бы точный выстрел не сработал!

Доводим до абсурда


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




Заключение


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

Материалы для дальнейшего прочтения(англ. яз.):
Gaffer On Games
Valve's latency compensation methods

От переводчика
На этом заканчивается серия статей от Gabriel Gambetta.
Но я с вами не прощаюсь. Я еще собраюсь написать статью о том, как построить сериализацию игрового мира, да и вообще никогда не знаешь куда тебя заведет исследование такой интересной темы как мультиплеер!


UPD


krimtsev задал весьма резонный вопрос.
FPS и PING понятно как влияют на это, а что скажете на счет TICKRATE?

Отвечаю. Мы с bogotoff позволили себе отойти от описанного алгоритма, так как нам показалось неудобным играть при частоте обновления сервера 10 раз в секунду.

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



Хотя игра при такой частоте сервера, конечно, выглядит ужасно. Не делайте так. И вообще держите FPS как можно выше. Чем больше частота дискретизации игрового мира, тем более качественный игровой опыт будет у игрока. Хотя мое личное ИМХО что выше 60 — уже не имеет смысла.