Будет ли "Rubber banding" разрешать многопользовательскую интерполяцию заикаться?

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

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

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

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

Обновление

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

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

Ответ 1

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

// 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 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

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

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

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

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

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

Ответ 2

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

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

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

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

Вместо того, чтобы немедленно применить это как исправление для кода клиента (это одна из причин, по которой вы можете видеть эти переходы), вы выполняете это в течение некоторого времени, 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 wiki. Фактический пример кода в Cpp доступен в их SDK около функции GetPredictionErrorSmoothingVector.