Многопользовательская игра сокетов реального времени JavaFX - как обновить графический интерфейс из разных потоков и установить сцены - PullRequest
0 голосов
/ 03 апреля 2019

Я пытаюсь создать многопользовательский "понг для четырех игроков" с Java и JavaFX. Сетевая часть работает отлично. Сервер обрабатывает всю игровую логику и отправляет позицию мяча и позицию всех игроков всем четырем клиентам.

Теперь мне нужно добавить графический интерфейс для клиента. Однако проблемы начинаются здесь. Есть две вещи, которые мне нужно знать: как мне обновлять узлы JavaFX из разных потоков без зависания графического интерфейса (например, устанавливать положение шара каждый раз, когда сервер отправляет новые координаты) и как сказать графическому интерфейсу установить другую сцену, снова из другая тема?

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

Я думал о настройке своего класса JavaFX GUI в качестве точки входа для клиента, где я затем создаю новый объект клиента. Затем GUI может вызывать методы на клиентском объекте, например client.move(DIRECTION); или client.sendMessage("Hello"). Это не проблема, но я не могу найти хорошее решение для другого клиента -> GUI.

Клиент каким-то образом должен иметь возможность изменять узлы, например шар. Когда сервер отправляет новую команду BALL_POSITION x y, клиент каким-то образом должен сообщить GUI о новой позиции шара. Я не могу использовать Platform.runLater(Runnable);, потому что по GUI зависает, если позиция шара обновляется каждые 100 мс или около того. Для позиции игроков, это то же самое.

Что сработало для положения мяча, так это использование SimpleIntegerProperty. Я добавил слушатель для этого свойства в GUI, который затем обновлялся без зависания GUI. Это правильный подход, или я должен сделать это совсем по-другому?

А как бы я сказал GUI переключать сцены внутри клиента, который работает в потоке, отличном от GUI?

То, что я пробовал для положения мяча (упрощенно):

Client.java

class Client extends Thread {
  private Ball ball;

  Client() {
    this.ball = new Ball();
    new Thread(ball).start();
  }

  Ball getBall() {
    return this.ball;
  }
}

Ball.java

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;

class Ball implements Runnable {
  private IntegerProperty ballX;
  private IntegerProperty ballY;

  Ball() {
    ballX = new SimpleIntegerProperty(this, "ballX", 0);
    ballY = new SimpleIntegerProperty(this, "ballY", 0);
  }

  public void setPosition(int x, int y) {
     this.ballX.setValue(x);
     this.ballX.setValue(y);
  }

  IntegerProperty getBallX() {
    return this.ballX;
  }

  IntegerProperty getBallY() {
    return this.ballY;
  }
}

Protocol.java

switch(command) {

   case(BALL_POSITION):
      ...
      client.getBall().setPosition(x, y);

}

GameGUI.java

Circle circle = new Circle(0, 0, 30);
final Client client = new Client();
...
final AtomicInteger count1 = new AtomicInteger(-1);
client.getBall().getBallX().addListener(new ChangeListener<Number>() {
   @Override
   public void changed(final ObservableValue<? extends Number> observable,
        final Number oldValue, final Number newValue) {
            if (count1.getAndSet(newValue.intValue()) == -1) {
               Platform.runLater(new Runnable() {
                  @Override
                  public void run() {
                     int value = count1.getAndSet(-1);
                     ballXLabel.setText("BallX: "  + value);
                     circle.setCenterX(value);
                    }
                 });
              }
          }
      });

Это работает хорошо для начала, но я действительно не уверен, если это так, как это должно быть сделано. Кроме того, как бы я переключил сцены, если сервер отправляет команду, например GAME_END?

Я действительно надеюсь, что кто-то готов помочь мне, спасибо заранее!

...