Я работаю над изометрической 2D-игрой с умеренным мультиплеером, где примерно 20-30 игроков одновременно подключены к постоянному серверу. У меня были некоторые трудности с получением хорошей реализации предсказания движения.
Физика / движение
Игра не имеет истинной физической реализации, но использует основные принципы для реализации движения. Вместо того, чтобы непрерывно опрашивать ввод, изменения состояния (например, события мыши / вниз / вверх / перемещение) используются для изменения состояния объекта персонажа, которым управляет игрок. Направление игрока (т.е. / северо-восток) объединяется с постоянной скоростью и превращается в настоящий трехмерный вектор - скорость объекта.
В основном игровом цикле «Обновление» вызывается перед «Розыгрышем». Логика обновления запускает «задачу обновления физики», которая отслеживает все объекты с ненулевой скоростью, использует базовую интеграцию для изменения положения объектов. Например: entity.Position + = entity.Velocity.Scale (ElapsedTime.Seconds) (где «Seconds» - это значение с плавающей запятой, но тот же подход будет работать для целочисленных значений в миллисекундах).
Ключевым моментом является то, что для движения не используется интерполяция - элементарный физический движок не имеет понятия «предыдущее состояние» или «текущее состояние», только положение и скорость.
Пакеты изменения состояния и обновления
Когда скорость сущности персонажа, которой управляет игрок, изменяется, на сервер отправляется пакет «перемещение аватара», содержащий тип действия сущности (стойка, ходьба, бег), направление (северо-восток) и текущую позицию. Это отличается от того, как работают 3D игры от первого лица. В 3D-игре скорость (направление) может меняться от кадра к кадру при движении игрока. Отправка каждого изменения состояния будет эффективно передавать пакет на кадр, что будет слишком дорого. Вместо этого 3D-игры, похоже, игнорируют изменения состояния и отправляют пакеты «обновления состояния» через фиксированный интервал - скажем, каждые 80–150 мс.
Так как обновления скорости и направления происходят в моей игре гораздо реже, я могу отправлять каждое изменение состояния. Хотя все физические симуляции происходят с одинаковой скоростью и являются детерминированными, задержка все еще остается проблемой. По этой причине я посылаю обычные пакеты обновления положения (аналогично 3D-игре), но гораздо реже - сейчас каждые 250 мс, но я подозреваю, что с хорошим прогнозом я могу легко увеличить его до 500 мс. Самая большая проблема состоит в том, что я теперь отклонился от нормы - вся другая документация, руководства и образцы онлайн отправляют рутинные обновления и интерполируют между двумя состояниями. Это кажется несовместимым с моей архитектурой, и мне нужно придумать лучший алгоритм предсказания движения, который ближе к (очень простой) архитектуре «сетевой физики».
Затем сервер получает пакет и определяет скорость игрока по типу движения на основе сценария (способен ли игрок бежать? Получить скорость бега игрока). Получив скорость, он комбинирует ее с направлением, чтобы получить вектор - скорость объекта. Происходит некоторое обнаружение читов и базовая проверка, а сущность на стороне сервера обновляется с учетом текущей скорости, направления и положения. Базовое регулирование также выполняется, чтобы не дать игрокам заполнить сервер запросами на перемещение.
После обновления своего собственного объекта сервер передает пакет «обновление позиции аватара» всем остальным игрокам в пределах диапазона. Пакет обновления положения используется, чтобы обновить физические симуляции на стороне клиента (мировое состояние) удаленных клиентов и выполнить прогнозирование и компенсацию запаздывания.
Предсказание и компенсация отставания
Как уже упоминалось выше, клиенты являются авторитетными для своей должности. За исключением случаев мошенничества или аномалий, аватар клиента никогда не будет перемещен сервером. Для аватара клиента не требуется экстраполяция («двигаться сейчас и исправить позже») - игрок видит следующее : правильно. Однако какая-то экстраполяция или интерполяция требуется для всех удаленных объектов, которые движутся. Некоторый вид предсказания и / или компенсации запаздывания явно требуется в локальном механическом / физическом движке клиента.
Проблемы
Я боролся с различными алгоритмами, и у меня есть ряд вопросов и проблем:
Должен ли я быть экстраполирующим, интерполирующим или и тем, и другим? Мое "внутреннее чувство" состоит в том, что я должен использовать чистую экстраполяцию, основанную на скорости. Клиент получает изменение состояния, клиент вычисляет «предсказанную» скорость, которая компенсирует задержку, а обычная физическая система делает все остальное. Тем не менее, он чувствует разногласия со всем другим примером кода и статей - кажется, что все они хранят несколько состояний и выполняют интерполяцию без физического движка.
Когда пакет приходит, я попытался интерполировать положение пакета со скоростью пакета в течение фиксированного периода времени (скажем, 200 мс). Затем я беру разницу между интерполированной позицией и текущей позицией «ошибки», чтобы вычислить новый вектор и поместить его на объект вместо скорости, которая была отправлена. Тем не менее, предполагается, что другой пакет прибудет в этот интервал времени, и невероятно трудно «угадать», когда следующий пакет прибудет - тем более, что они не все приходят через фиксированные интервалы (то есть / также изменяется состояние). Является ли концепция в корне ошибочной или она правильная, но нуждается в некоторых исправлениях / корректировках?
Что происходит, когда удаленный плеер останавливается? Я могу немедленно остановить сущность, но она будет расположена в «неправильном» месте, пока она не начнет двигаться снова. Если я оцениваю вектор или пытаюсь интерполировать, у меня возникает проблема, потому что я не сохраняю предыдущее состояние - физический движок не может сказать «вам нужно остановиться после достижения позиции X». Он просто понимает скорость, ничего более сложного. Я неохотно добавляю информацию о «состоянии перемещения пакетов» к сущностям или физическому движку, поскольку это нарушает базовые принципы проектирования и отбирает сетевой код на остальной части игрового движка.
Что должно произойти, когда объекты сталкиваются? Существует три сценария: контролирующий игрок сталкивается локально, два объекта сталкиваются на сервере во время обновления позиции или обновление удаленного объекта сталкивается на локальном клиенте. Во всех случаях я не уверен, как справиться со столкновением - кроме обмана, оба состояния являются «правильными», но в разные периоды времени. В случае удаленного объекта не имеет смысла рисовать его, проходя сквозь стену, поэтому я выполняю обнаружение столкновений на локальном клиенте и заставляю его «останавливаться». Основываясь на пункте № 2 выше, я мог бы вычислить «исправленный вектор», который постоянно пытается переместить сущность «через стену», которая никогда не будет успешной - удаленный аватар застревает там до тех пор, пока ошибка не станет слишком высокой и не «защелкнется» в позиция. Как игры работают вокруг этого?