Введение
В первой статье я рассказал про авторитарный сервер и его полезность для защиты от читов. В результате второй части мы получили набор техник, позволяющих игроку контролировать персонажа на удаленном сервере без лага.
В этой статье мы рассмотрим последствия одновременного подключения нескольких игроков к одному серверу.
От переводчика: в этой и последующих статьях для гифок используется демка, написанная мной и bogotoff из NerfGame.
Частота обновления сервера
В предыдущей статье поведение сервера было предельно простым — он считывает ввод клиента; обновляет состояние игры; отправляет его обратно на клиент. Но когда клиентов много, они очень часто отправляют команды. Обновление игрового мира для каждой команды и последующее оповещение всех клиентов об измененном состоянии основательно нагрузило бы процессор и сеть.
Более удачным подходом было бы накапливать все команды без выполнения. Вместо этого мир будет обновляться периодично с низкой частотой, например 10 раз в секунду. Во время каждого обновления все накопленные команды применяются(возможно с несколькими шагами физики, чтобы она была более стабильной) и новое состояние игры рассылается по клиентам.
Прим. Перев. В быстрых играх вы скорее всего захотите выставить частоту обновления не меньше 20 раз в секунду(как в Overwatch) и поднимать его вплоть до 120 (как в CS:GO).
Коротко говоря, у игрового мира есть собственная фиксированная частота обновления, не зависящая от наличия команд или их количества.
Справляемся с редкими обновлениями
Пока игрок один, с его точки зрения все работает так же гладко и мгновенно как и раньше, так как предсказание на стороне клиента работает вне зависимости от частоты ответов сервера.
Но ему приходят слишком редкие обновления о том, что происходит на сервере с другими игроками. Наивная реализация других игроков работает так: клиент применяет обновление состояния другого игрока, как только получает его. Естественно, это приводит к рывкам, так как частота обновления вражеских игроков будет 10 кадров в секунду.
Проблема не только в этом
прим. перев. Даже если сервер отправляет обновления 60 раз в секунду, это не значит что клиент будет получать их 60 раз в секунду. Сети не надежны — пакет с очередным обновлением может придти позже, чем хотелось бы, или вообще не дойти, так что игрок все равно увидит рывки.
В зависимости от типа разрабатываемой игры, есть различные способы бороться с этим. Причем чем более предсказуема игра, тем проще выйти из этой ситуации.
Экстраполяция
Предположим, что вы делаете гонки. Машины довольно предсказуемы — если машина едет 100 м/с, через секунду она будет примерно на 100 метров впереди от того места, где она была.
Почему “примерно”? В течении этой секунды машина могла немного ускориться, или затормозить; немного повернуться. Ключевое слово здесь — немного. Машины устроены так, что позиция преимущественно зависит от предыдущей позиции, скорости и направления; и в меньшей степени от действий пользователя. Другими словами, гоночная машина не может мгновенно развернуться на 180 градусов.
Как же это работает с обновлениями сервера каждые 100 мс? Клиент получает скорость и направление каждой машины; в течении следующих 100 мс он не будет получать новой информации, но он должен показать что машины едут. Самое простое что можно предположить — что направление и ускорение будут константными в течении этого времени и локально воспроизводить физику машины с учетом этих параметров. Позже, когда обновление придет, позиция машины будет скорректирована.
Эта коррекция может быть большой или маленькой в зависимости от множества параметров. Если игрок вел машину прямо и не менял скорость, экстраполированная позиция идеально совпадет со скорректированной. С другой стороны, если игрок врезался во что-то, предсказанная позиция будет абсолютно неправильна.
Этот метод подойдет только для объектов с большой инерцией: машины, корабли.
Интерполяция
Есть ситуации когда экстраполяция вообще не может быть применена. Собственно, во всех сценариях, где направление и скорость персонажа меняются быстро. Например в 3d шутере игроки обычно, останавливаются и огибают препятствия на больших скоростях, делая экстраполяцию бессмысленной, так как позиции не могут быть предсказаны из устаревшей информации.
При этом вы все еще не можете наивно применять обновления с сервера: игроки будут телепортироваться каждые 100 мс и при каждом потерянном/задержавшемся пакете.
У вас есть информация о позиции врагов каждые 100 мс. Так что трюк заключается в том, чтобы показывать игроку что происходило между этими обновлениями в прошлом.
Пусть вы получаете позиции в момент t = 1000. Вы уже получили информацию о t = 900, так что от t = 1000 до t = 1100, вы показываете что этот игрок делал с t = 900 до t = 1000. Таким образом вы показываете настоящее передвижение врагов, но на 100 мс позже.
Учтите, что при использовании этой техники игроки видят немного разное состояние мира, так как себя игрок видит в настоящем времени(это необходимо для отсутствие лага при вводе), а врагов видит в прошлом(это было бы даже без интерполяции, из-за ненулевого пинга). Но даже в быстрых играх задержка на 100 мс обычно незаметна.
Но есть исключения — если нужна высокая пространственная и временная точность, например при выстреле одного игрока в другого. Так как другие игроки видны в прошлом, вы целитесь с задержкой в 100 мс + пинг, то есть туда, где враг был более 100 мс назад! С этим мы будем разбираться в следующей статье.
Итог
В клиент-серверной модели с авторитарным сервером, редкими обновлениями и задержками в связи, вы все равно должны дать игроку иллюзию непрерывного движения. В предыдущей части мы рассматривали способы мгновенно реагировать на пользовательский ввод используя предсказание на стороне клиента и согласование с сервером.
Но другие игроки все еще для нас были проблемой. В этой статье мы рассмотрели два способа справиться с ними:
Экстраполяция — применяется когда позиция может быть предсказана исходя из предыдущей информации — позиции, скорости и ускорения.
Интерполяция — не предсказывает будущее, а использует только реальную информацию, предоставленную сервером, показывая вражеских игроков из недавнего прошлого.
В итоге игрок видит себя в настоящем, а остальных игроков — в прошлом. Обычно это создает отличный игровой опыт.
Но если больше ничего не делать, иллюзия сломается когда события потребуют высокой пространственно-временной точности, например при выстреле.