[Перевод] Мультиплеер в быстрых играх (Часть III: появление врага)

8bc68b5994f4492a812e5367e9789bf8.jpg

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


Введение


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

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

От переводчика: в этой и последующих статьях для гифок используется демка, написанная мной и bogotoff из NerfGame.

Частота обновления сервера


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

Более удачным подходом было бы накапливать все команды без выполнения. Вместо этого мир будет обновляться периодично с низкой частотой, например 10 раз в секунду. Во время каждого обновления все накопленные команды применяются (возможно с несколькими шагами физики, чтобы она была более стабильной) и новое состояние игры рассылается по клиентам.
Прим. Перев. В быстрых играх вы скорее всего захотите выставить частоту обновления не меньше 20 раз в секунду (как в Overwatch) и поднимать его вплоть до 120 (как в CS: GO).

Коротко говоря, у игрового мира есть собственная фиксированная частота обновления, не зависящая от наличия команд или их количества.

Справляемся с редкими обновлениями


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

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

Проблема не только в этом

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


c6a09dbaca3c4439a0b3e8826cbb01eb.png

49f5e11320df4befb5c5aab56399b399.gif

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

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


Предположим, что вы делаете гонки. Машины довольно предсказуемы — если машина едет 100 м/с, через секунду она будет примерно на 100 метров впереди от того места, где она была.

Почему «примерно»? В течении этой секунды машина могла немного ускориться, или затормозить; немного повернуться. Ключевое слово здесь — немного. Машины устроены так, что позиция преимущественно зависит от предыдущей позиции, скорости и направления; и в меньшей степени от действий пользователя. Другими словами, гоночная машина не может мгновенно развернуться на 180 градусов.

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

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

Этот метод подойдет только для объектов с большой инерцией: машины, корабли.

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


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

При этом вы все еще не можете наивно применять обновления с сервера: игроки будут телепортироваться каждые 100 мс и при каждом потерянном/задержавшемся пакете.

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

Пусть вы получаете позиции в момент t = 1000. Вы уже получили информацию о t = 900, так что от t = 1000 до t = 1100, вы показываете что этот игрок делал с t = 900 до t = 1000. Таким образом вы показываете настоящее передвижение врагов, но на 100 мс позже.

8ea5b47511b24b6d9a5526d0e32b3417.png

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

d5c2d1b55d924b9eb958fdd81a4e0a61.gif

Но есть исключения — если нужна высокая пространственная и временная точность, например при выстреле одного игрока в другого. Так как другие игроки видны в прошлом, вы целитесь с задержкой в 100 мс + пинг, то есть туда, где враг был более 100 мс назад! С этим мы будем разбираться в следующей статье.
55412a350f33432e85a885114c114bb2.gif

Итог


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

Но другие игроки все еще для нас были проблемой. В этой статье мы рассмотрели два способа справиться с ними:

Экстраполяция — применяется когда позиция может быть предсказана исходя из предыдущей информации — позиции, скорости и ускорения.

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

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

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

© Habrahabr.ru