Несколько видовых экранов в приложении javafx - PullRequest
0 голосов
/ 18 апреля 2019

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

Представьте себе FPS с кооперативным диваном: экран распадается равномерно, в зависимости от того, сколько людей подключено локально. Каждый другой взгляд смотрит в другом направлении и в другом месте, но в одном и том же «мире».

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

Глядя на другие подобные технологии, такие как OpenGL, ( пример ) большинство из них имеют возможность создать еще одно окно просмотра для приложения, но JavaFX, похоже, не имеет этого.

Некоторые вещи, которые я исключил как необоснованные / невозможные (поправьте меня, если я ошибаюсь):

  • Использование фигур для создания маски клипа для панели (можно использовать только одну маску на узел)
  • Наличие полной глубокой копии каждого узла для каждого представления (слишком дорого, узлы постоянно перемещаются)
  • Наличие x количества пользователей, каждый из которых имеет свой собственный набор узлов, и один цикл обновления обновляет каждый узел в каждом представлении (слишком дорого, слишком много узлов для графа сцены, слишком много)

Как мне создать несколько представлений одного и того же набора узлов, сохраняя при этом индивидуальный пользовательский контроль и меняя постоянство / перемещение узлов между различными представлениями?

Спасибо.

1 Ответ

0 голосов
/ 19 апреля 2019

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

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

Решение не слишком элегантное, и мне придется больше взглянуть на многопоточность (многозадачность?) С Task s (здесь) и Platform.runLater() (здесь) функциями JavaFX для увеличения функциональности.

Вот краткий пример того, что я достиг:

Proof Of Concept Gif

Main.java

public class Main extends Application {

    private static Group root = new Group();
    private static Scene initialScene = new Scene(root, Color.BLACK);

    private static final int NUM_OF_CLIENTS = 8;

    private static long updateSpeed = 20_666_666L;
    private static double deltaTime;
    private static double counter = 0;

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setFullScreen(true);
        primaryStage.setScene(initialScene);
        primaryStage.show();

        initModel();
        initModelViews();
        startUpdates();
    }

    private void initModel() {
        for (int i = 0; i < NUM_OF_CLIENTS; i++) {
            Model.add(new UpdateObject());
        }
    }

    private void initModelViews() {
        //Correctly positioning the views
        int xPanes = (NUM_OF_CLIENTS / 4.0 > 1.0) ? 4 : NUM_OF_CLIENTS;
        int yPanes = (NUM_OF_CLIENTS / 4) + ((NUM_OF_CLIENTS % 4 > 0) ? 1 : 0);

        for (int i = 0; i < NUM_OF_CLIENTS; i++) {
            Pane clientView = new Pane(copyModelNodes());
            clientView.setBackground(new Background(new BackgroundFill(Color.color(Math.random(), Math.random(), Math.random()), CornerRadii.EMPTY, Insets.EMPTY)));
            System.out.println(clientView.getChildren());
            clientView.relocate((i % 4) * (Main.initialScene.getWidth() / xPanes), (i / 4) * (Main.initialScene.getHeight() / yPanes)) ;
            clientView.setPrefSize((Main.initialScene.getWidth() / xPanes), (Main.initialScene.getHeight() / yPanes));
            root.getChildren().add(clientView);
        }
    }

    private Node[] copyModelNodes() {
        ObservableList<UpdateObject> model = Model.getModel();
        Node[] modelCopy = new Node[model.size()];

        for (int i = 0; i < model.size(); i++) {
            ImageView testNode = new ImageView();
            testNode.setImage(model.get(i).getImage());
            testNode.layoutXProperty().bind(model.get(i).layoutXProperty());
            testNode.layoutYProperty().bind(model.get(i).layoutYProperty());
            testNode.rotateProperty().bind(model.get(i).rotateProperty());
            modelCopy[i] = testNode;
        }
        return modelCopy;
    }

    private void startUpdates() {
        AnimationTimer mainLoop = new AnimationTimer() {
            private long lastUpdate = 0;

            @Override
            public void handle(long frameTime) {

                //Time difference from last frame
                deltaTime = 0.00000001 * (frameTime - lastUpdate);

                if (deltaTime <= 0.1 || deltaTime >= 1.0)
                    deltaTime = 0.00000001 * updateSpeed;

                if (frameTime - lastUpdate >= updateSpeed) {
                    update();
                    lastUpdate = frameTime;
                }
            }
        };
        mainLoop.start();
    }

    private void update() {
        counter += 0.1;

        if (counter > 10.0) {
            counter = 0;
        }
        for (UpdateObject objectToUpdate : Model.getModel()) {
            objectToUpdate.setLayoutX(objectToUpdate.getLayoutX() + 0.02 * counter * deltaTime);
            objectToUpdate.setLayoutY(objectToUpdate.getLayoutY() + 0.02 * counter * deltaTime);
            objectToUpdate.setRotate(objectToUpdate.getRotate() + 5);
        }
    }
}

UpdateObject.java

class UpdateObject extends ImageView {

    private static Random random = new Random();
    private static Image testImage = new Image("duckTest.png");

    UpdateObject() {
        this.setImage(testImage);
        this.setLayoutX(random.nextInt(50));
        this.setLayoutY(random.nextInt(50));
        this.setRotate(random.nextInt(360));
    }
}

Model.java

class Model {

    private static ObservableList<UpdateObject> modelList = FXCollections.observableArrayList();

    static void add(UpdateObject objectToAdd) {
        modelList.add(objectToAdd);
    }

    static ObservableList<UpdateObject> getModel() {
        return modelList;
    }
}

Используется тестовое изображение

...