Линейный импульс и неправильные позиции в мультиплеере

Я создаю игру в LibGDX. Сначала я следовал учебному пособию по игре Super Mario Brother на YouTube, однако я изменил игру, включив в нее своих персонажей и графику. Я использую Kryonet как средство включения многопользовательских функций в свою игру. Здесь я столкнулся с несколькими проблемами.

Во-первых, я использую линейный импульс для движения, поэтому, когда мой персонаж нажимает усиление, применяется линейный импульс, и ему дается сила двигаться вперед быстрее. Это работает нормально, пока вы не посмотрите на оба экрана, позиция персонажа А отличается на экране одного игрока и другая позиция на экране другого. Значения, которые я получаю для линейного импульса для обоих персонажей, также различаются. Мои обычные движения вправо, влево и вверх также выполняются с линейным импульсом. Из-за случайности значений линейного импульса я получаю эту ошибку, которая, я бы сказал, похожа на ошибку скольжения льда: толкание кубика льда по поверхности не всегда гарантирует одно и то же положение. Ниже приведен код из моего многопользовательского клиентского класса.

client.addListener(new ThreadedListener(new Listener() {
            // What to do with the packets.
            public void connected(Connection connection) {

            }

            public void received(Connection connection, Object object) {

                if (object instanceof MovementJump) {
                    MovementJump packet = (MovementJump) object;
                    
                    PlayScreen.player.b2body.applyLinearImpulse(new Vector2(0, 4f),
                            PlayScreen.player.b2body.getWorldCenter(), true);
                }

                if (object instanceof MovementRight) {
                    MovementRight packet = (MovementRight) object;
                    
                    PlayScreen.player.b2body.applyLinearImpulse(new Vector2(packet.impulse, 0),
                            PlayScreen.player.b2body.getWorldCenter(), true);
                }

                if (object instanceof MovementLeft) {
                    MovementLeft packet = (MovementLeft) object;
                    
                    PlayScreen.player.b2body.applyLinearImpulse(new Vector2(-packet.impulse, 0),
                            PlayScreen.player.b2body.getWorldCenter(), true);
                }

                if (object instanceof MovementP2Jump) {
                    MovementP2Jump packet = (MovementP2Jump) object;

                    PlayScreen.player2.b2body.applyLinearImpulse(new Vector2(0, 4f),
                            PlayScreen.player2.b2body.getWorldCenter(), true);
                }

                if (object instanceof MovementP2Right) {
                    MovementP2Right packet = (MovementP2Right) object;
                    
                    PlayScreen.player2.b2body.applyLinearImpulse(new Vector2(packet.impulse, 0),
                            PlayScreen.player2.b2body.getWorldCenter(), true);
                }

                if (object instanceof MovementP2Left) {
                    MovementP2Left packet = (MovementP2Left) object;
                    
                    PlayScreen.player2.b2body.applyLinearImpulse(new Vector2(-packet.impulse, 0),
                            PlayScreen.player2.b2body.getWorldCenter(), true);
                }
            }

        }));
    }


Это какой-то код из моего playScreen

            if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)
                        && player.b2body.getLinearVelocity().x <= player.getMinSpeed()) {
                    
                    
                    MovementRight pos = new MovementRight();
                    MPClient.client.sendTCP(pos);
                }

                if (Gdx.input.isKeyPressed(Input.Keys.LEFT)
                        && player.b2body.getLinearVelocity().x >= -player.getMinSpeed()) {
                    
                    MovementLeft pos = new MovementLeft();
                    MPClient.client.sendTCP(pos);
                }


person newToBeingNerdy    schedule 17.12.2020    source источник


Ответы (1)


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

Почему? Если вы применяете импульс и отправляете пакет, уведомляющий других клиентов об этом импульсе, их импульс будет добавлен первым после задержки ping, которая никогда не будет равна 0. Это означает, что их импульс будет добавлен позже, и поэтому их символы всегда будут быть позади.

Я могу придумать две структуры для этого, но, вероятно, есть много других способов! Первый — клиентский, а второй — серверный, у обоих есть свои плюсы и минусы. Лично я рекомендую серверную структуру, потому что взломанные многопользовательские игры действительно отталкивают игроков.

Структура на стороне клиента:

Плюсы (меньше задержек, более отзывчивое управление для игроков с задержками) Минусы: (легко взломать)

  1. Всякий раз, когда позиция персонажа вашего клиента изменяется, отправьте PositionUpdatePacket на сервер с вашими текущими координатами. Этот пакет должен содержать только (float x, float y, byte characterID)
  2. Сервер отправляет пакет всем другим подключенным клиентам.
  3. Каждый клиент обновляет персонажа на основе идентификатора персонажа.

Структура на стороне сервера:

Плюсы (трудно взломать) Минусы: (менее отзывчивое управление для лагов)

  1. Когда игрок делает ввод (например, перемещается влево), вы отправляете MoveRequestPacket с желаемым направлением на сервер. Это нужно отправить только один раз через TCP всякий раз, когда меняется направление или если игрок остановился.
  2. Сервер обновляет этот плеер и движется в нужном направлении, т.е. поведение метода обновления. Это применит импульсы. Преимущество здесь в том, что сервер может выполнять различные проверки хитбоксов и предотвращать движение, если игрок хочет пройти сквозь стену и т. д., чтобы предотвратить взлом.
  3. Сервер постоянно отправляет PositionUpdatePacket ВСЕМ подключенным клиентам, включая того, кто отправляет запрос, с текущей позицией персонажа, пока персонаж движется.
  4. Каждый клиент обновляет персонажа.

В обеих структурах PositionUpdatePacket должен отправляться всем подключенным клиентам непрерывно, пока персонаж движется, а это значит, что этот пакет будет отправлен много. Чтобы уменьшить влияние на производительность, вы должны отправлять его через UDP и только x раз в секунду. Minecraft отправляет свою позицию со скоростью 20 т/с.

Если вы отправляете PositionUpdatePacket с более низкой частотой, чем ваши методы #update или #step, то каждый символ будет заикаться и отставать. Чтобы бороться с этим, вы можете использовать интерполяцию, которая сглаживает движение.

Надеюсь, это помогло. Удачи!

person Community    schedule 18.12.2020