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