Устранит ли резиновая лента интерполяцию в многопользовательской игре?

Я написал многопользовательский Pong, используя UDP. Я использую интерполяцию и экстраполяцию, чтобы создать плавный эффект на клиенте.

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

Должен быть способ сделать игру более плавной. Я читал про резиновую ленту. Как лучше отсюда переехать?

Я надеюсь, что кто-то, кто сможет хорошо ответить на мой вопрос, найдет его.

Обновить

По просьбе Ивана, вот график времени пинга. Однако я считаю, что проблема существует внутри кода сглаживания клиента.

введите здесь описание изображения


person Z0q    schedule 11.10.2016    source источник
comment
Кстати, пинг/фпс замеряли?   -  person Ivan    schedule 14.10.2016
comment
@Ivan Я измеряю пинг, да. Однако в данный момент я им не пользуюсь.   -  person Z0q    schedule 14.10.2016
comment
Я спрашиваю об этих показателях, чтобы мы могли количественно определить, что такое «небольшое отставание» и при каких обстоятельствах. Например. если ваш пинг составляет 900 мс, в принципе, любой опыт в порядке   -  person Ivan    schedule 14.10.2016
comment
Было бы интересно измерить проблемы с заиканием с течением времени и наложить это на ваш текущий пинг-график. Шипы совпадают? Сколько поправок требовалось в каждый момент времени?   -  person tucuxi    schedule 14.10.2016
comment
@tucuxi Проблемы с заиканием во многом возникают в то время, когда скачки пинга были высокими. Я помню игровое время, когда это произошло, и вижу, что эти всплески нанесены точно в одно и то же время.   -  person Z0q    schedule 14.10.2016
comment
У вас есть центральный сервер (возможно, совмещенный с клиентом), который координирует игровой процесс, или все клиенты находятся в равных условиях?   -  person tucuxi    schedule 14.10.2016
comment
Я использую одноранговое соединение. Один из клиентов действует как сервер и отправляет состояние игры с фиксированным шагом времени (20 Гц) другому узлу.   -  person Z0q    schedule 14.10.2016
comment
Зачем сохранять фиксированный временной шаг? Почему бы не отправлять только обновления?   -  person tucuxi    schedule 14.10.2016
comment
Спасибо за подробности. @ Z0q, не могли бы вы поделиться некоторыми подробностями кода на стороне клиента? Сколько обновлений вы буферизуете для интерполяции? Клиент сам предсказывает позицию или просто отображает обновления сервера? Если клиент обнаружил, что вычисленная прогнозируемая позиция отличается от того, что только что было получено от сервера, вы немедленно обновляете эффект, наблюдаемый пользователем, или распределяете его по времени с помощью сглаживания?   -  person Ivan    schedule 14.10.2016
comment
@Ivan Я буферизую 2 состояния игры и интерполирую между этими двумя. Я экстраполирую, если не было добавлено новое состояние. Эта экстраполяция является предсказанием. Если есть какая-то разница, я обновляю ее напрямую. Я не уверен, какой алгоритм использовать для его распространения/сглаживания.   -  person Z0q    schedule 14.10.2016
comment
@ Z0q Когда вы обнаружите разницу, вы должны отметить время (m_flPredictionErrorTime). Затем вы выбираете некоторое время, в течение которого будет происходить сглаживание cl_smoothtime. Где-то рядом с кодом отображения вы вычисляете, сколько ошибок вы собираетесь отображать errorAmount = ( currentTimeMillis() - m_flPredictionErrorTime ) / cl_smoothtime. Умножьте свой вектор различий на эти vOffset = m_vecPredictionError * errorAmount и добавьте к вектору параметров (x, y, скорость, ...). Как только errorAmount больше 1, вы прекращаете его рассматривать (отображается полная дельта)   -  person Ivan    schedule 14.10.2016
comment
@Z0q мой комментарий из вышеизложенного пересказывает функцию GetPredictionErrorSmoothingVector из Исходный код SDK. Расскажите, как это сработало для вас, или задайте дополнительные вопросы.   -  person Ivan    schedule 14.10.2016


Ответы (2)


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

// input thread
if input changed,
   alter paddle speed and/or direction
   send timestamped message to inform my opponent of paddle change

// incoming network thread
if paddle packet received
   alter opponent's paddle speed and/or direction at time it was sent
   fix any errors in previously extrapolated paddle position <--- Easy
if ball-packet received
   fix any errors in ball position and speed <--- Tricky

// update entities thread
for each entity (my paddle, opponent paddle, the ball)
   compute updated entity position, adjusted by time-since-last-update
   if ball reached my end, send ball-packet to other side
   draw updated entity

Это предполагает, что происходит обмен двумя типами пакетов:

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

Псевдокод выполняет экстраполяцию («предположим, что все идет как обычно») для всех неизвестных в потоке update-entities. Единственная точка, где возникают проблемы, отмечена <--- стрелками.

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

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

person tucuxi    schedule 14.10.2016
comment
Привет, спасибо за ваш ответ. Это не мой предыдущий вопрос. Как мне избежать этих скачков, если оба клиента не согласны? это мой вопрос - person Z0q; 20.10.2016
comment
И мой ответ заключается в том, что вы слишком часто обмениваетесь информацией неправильного типа, что приводит к частым разногласиям, которых можно избежать. Следует обмениваться информацией только от клиента, который что-то знает, к тому, кто этого еще не знает, а затем доверять поступающей информации. Клиент B не может знать, какие движения манипулятора недавно делал клиент A из-за сетевых задержек. Следовательно, Клиент Б не должен сообщать Клиенту А ничего о передвижениях Клиента А. - person tucuxi; 20.10.2016

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

Как это работает

В какой-то момент в вашем коде вы определяете, что позиция мяча и клиент различны.

Несоответствие

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

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

public void onErrorDetected() {
    this.m_flPredictionErrorTime = System.currentTimeMillis();
}

Где-то рядом с кодом отображения вы вычисляете, сколько ошибок вы собираетесь отображать. Вот некоторый псевдокод для этого.

public void draw() {
    Point preditctionError = this.clientPredictedBallCoordinates - this.serverCoordinates;
    Point deltaToDisplay = calculateErrorVector(preditctionError);
    Point positionToDisplay = clientPredictedBallCoordinates + deltaToDisplay;
    // actually draw the ball here
}

public Point calculateErrorVector(Point coordinatesDelta) {
     double errorAmount = ( System.currentTimeMillis() - this.m_flPredictionErrorTime ) / this.cl_smoothtime.
     if (errorAmount > 1.0) {
         // whole difference applied in full, so returning zero delta
         return new Point(0,0);
     }
     if (errorAmount < 0) {
         // no errors detected yet so return zero delta
         return new Point(0,0);
     }
     Point delta = new Point(coordinates.x*errorAmount, coordinates.y*errorAmount);
     return delta;
}

Я взял эту идею из вики Source Multiplayer Networking. Фактический пример кода в Cpp доступен в их SDK по адресу Функция GetPredictionErrorSmoothingVector.

person Ivan    schedule 20.10.2016