0

I am attempting to create a multi-user, multi-screen application within JavaFX, and I am having trouble with the multi-screen part.

Think an FPS with couch co-op: the screen splits evenly depending on how many people are connected locally. Every different view is looking in a different direction, and at a different place, but at the same 'world'.

I learned the hard way (confirmed in a comment here) that each node can only appear in the active scene graph once, so, for instance, I cannot have the same node spread across multiple distinct panes (which is conceptually ideal). And that's where I'm not sure where to go next.

Looking at other similar technologies like OpenGL, (example) most have the ability to create another viewport for the application, but JavaFX does not seem to have this.

Some things I have ruled out as unreasonable/impossible (correct me if I'm wrong):

  • Using shapes to create a clip mask for a pane (Can only use one mask per node)
  • Having a complete deep copy of each node for each view (too expensive, nodes moving constantly)
  • Having x number of users each have their own set of nodes and have one update loop update every node in every view (too expensive, too many nodes for scene graph, too much)

How would I go about creating multiple views of the same set of nodes, while still maintaining individual user control, and changing persistence/moving nodes, between every different view?

Thanks.

4

1 回答 1

0

感谢评论中的人提供解决方案。我最终为每个要镜像的视图创建了一个背景模型,然后为每个视图创建了一组新的节点,这些节点具有绑定到背景模型的相关属性。

然后更新循环必须只更新一个背景模型,并且所有副本都会自动更新。每个节点副本都有对其正在模仿的模型节点的引用,因此当用户输入节点的更改时,模型节点会更改,从而更改副本节点。

该解决方案不太优雅,我将不得不更多地研究多线程(多任务处理?)与JavaFXTask的 s (此处)Platform.runLater() (此处)功能以增加功能。

这是我完成的一个简单示例:

概念证明 Gif

主.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);
        }
    }
}

更新对象.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));
    }
}

模型.java

class Model {

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

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

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

使用的测试图像

于 2019-04-19T16:20:09.057 回答