Как работает предсказание на стороне клиента?

33

Я прочитал Valve + Gafferon и сотни страниц из Google, но по какой-то причине я не могу понять, как прогнозируют клиенты.

Насколько я понимаю, основная проблема заключается в:

  • Клиент А отправляет информацию на T0
  • Сервер получает вход в T1
  • Все клиенты получают изменения по адресу T2

При T2однако, используя предсказание клиента, клиент А теперь в положении уместно T4.

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

Крис Эванс
источник

Ответы:

35

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

В частности, в статье о предсказании стороны клиента находится эта .

ggambett
источник
Отличные статьи :-) Мне бы очень хотелось увидеть четвертую часть серии. В качестве небольшого предложения, ссылка на следующую часть в конце каждой статьи, безусловно, улучшит навигацию.
ИЛИ Mapper
5
@ORMapper - я наконец-то написал 4-ю статью! gabrielgambetta.com/fpm4.html
ggambett
Престижность для вашей серии статей :-) Очень полезно, спасибо :-)
ИЛИ Mapper
Все статьи (которые я мог бы найти), в которых говорится о восстановлении прошлого с использованием сохраненных снимков, взяты в качестве примера. Это относится и к движению? Я могу себе представить, что переимуляция движения может привести к большим различиям для других игроков, если они могут столкнуться друг с другом. Допустим, два игрока движутся друг против друга, и один из них перестает двигаться на несколько шагов от точки столкновения. Эти команды остановки прибывают поздно из-за задержки, поэтому, если мы повторно имитируем мир, оба игрока будут в совершенно разных позициях
Лопе
Это интересный вопрос. К сожалению, у меня нет однозначного ответа. Я думаю, это зависит от того, насколько важны движения для игры; ты просто сталкиваешься с кем-то еще и ничего не происходит? В этом случае серверу, вероятно, все равно, это воспринимается как ошибка предсказания (мы все видели, что это происходит в точках дросселирования, верно?) Вы убиваете другого игрока при контакте? В этом случае правильная настройка является гораздо более важной и может стоить повторной имитации. Обратите внимание, что в какой-то момент вам нужно отбросить некоторые пакеты как «слишком старые», в противном случае вы потенциально будете повторно имитировать с t = 0 в любое время.
ggambett
4

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

Вот что вы сказали, что происходит:

Клиент A отправляет данные в момент времени T0

Сервер получает вход в T1

Все клиенты получают изменения в T2

Однако в момент Т2, используя прогнозирование клиента, клиент А теперь находится в позиции, соответствующей Т4.

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

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

Также клиент всегда рендерит «в прошлое». Итак, вы предполагаете, что мир, который видит клиент, скажем, на 100 мс отстает от времени сервера.

Итак, давайте перефразируем ваш пример с временем сервера (обозначено S).

Клиент отправляет входные данные в момент времени T0 со временем сервера S0 (что, я полагаю, на самом деле является «клиентским представлением времени сервера минус время интерполяции»). Клиент не ждет ответа от сервера и немедленно перемещается.

Сервер получает вход в T1. Сервер определяет авторитетную позицию клиента в момент времени сервера S0, данный клиентом. Отправляет это клиенту.

Клиент получает официальную позицию в T2 (все еще с обозначением времени сервера S0). Клиент следит за прошлым количеством времени предыдущих событий (вероятно, просто очередью всех неподтвержденных предсказаний).

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

тетрада
источник
3
Это все правильно, за исключением части о рендеринге клиента в прошлом. Относительно сервера клиент фактически рендерит в будущем! Сервер знает, что информация, которую он имеет от каждого клиента, является старой и что каждый клиент уже изменился с тех пор.
Kylotan
2

На самом деле в github есть реализация с открытым исходным кодом, которая показывает, как это делается. Проверьте Lance.gg

репозиторий github: https://github.com/lance-gg/lance

Код предсказания клиента реализован в модуле под названием src/syncStrategies/ExtrapolateStrategy.js

Помимо экстраполяции, есть две концепции, которые я не видел вышеупомянутыми:

  1. Инкрементный изгиб. По сути, вместо того, чтобы применять исправление сервера одновременно, вы позволяете дельте применяться небольшими шагами. Таким образом, удаленные объекты будут постепенно корректировать свои позиции в соответствии с позициями сервера. Есть изгиб положения, изгиб скорости, изгиб угла и изгиб угловой скорости. Также вам могут потребоваться разные изгибающие факторы для разных объектов.
  2. Шаг-Постановка. Тот факт, что данные ушли в прошлое, означает, что вы можете откатить время до времени данных сервера и перезапустить с этого момента. Конечно, вам все равно придется наклониться к вновь найденной позиции, а не прыгать к ней.
Гари Вайс
источник
1

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

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

В нижней части статьи Valve есть несколько дополнительных ссылок, которые стоит прочитать - это одна из них: https://developer.valvesoftware.com/wiki/Prediction

Kylotan
источник
Итак, я прав, думая , что клиент (в t=4) получает информацию о t=2, так он сбрасывает состояние на t=2то повторно пробегов обновления , чтобы принести объекты из t=2к t=4?
Джордж Дакетт
Я все еще не понимаю это по какой-то причине. Серверу не сообщается позиция игрока, только входы. Таким образом, игрок движется из последней позиции, в которой сервер сказал, что он был. Вклад применяется. Сервер информирован. Сервер подтверждает ввод для всех. Предполагая, что все команды приняты, сервер по-прежнему будет находиться за клиентом A, поэтому, когда клиент A останавливается, его персонаж немедленно останавливается, а затем возвращается к расположению сервера, когда получает подтверждение остановки.
Крис Эванс
@GeorgeDuckett: да (хотя это не должно быть t = 4, это может произойти при обнаружении расхождений, и может быть любое количество повторных применений обновлений.)
Kylotan
@ChrisEvans: известное состояние + изменения, основанные на вводе, в любом случае эквивалентны состоянию отправки. Что касается примера остановки, то он сам по себе является входом, и сервер все еще имитирует движение, пока не получит этот вход. Предполагая постоянную задержку, сервер остановит движение игрока в том же положении, в котором он находился, когда клиент перестал двигаться, потому что клиент опередил сервер. (В реальном мире задержка меняется, поэтому вы немного интерполируете ее, чтобы сгладить.)
Kylotan