Search code examples
javauser-interfacejavafxgraphicsscreen-resolution

Screen resolution scale in JavaFx game


I'm creating a board game in JavaFx, and it works fine in my desktop PC, with a 1920x1080 screen. When i try to open it in my Laptop (which is 1920x1080 as well, but with the default screen size at 125%), it doesn't show the elements properly anymore (their size overflows the bounds of the screen). The only way to make the app work properly is to run it on a 1920x1080 screen with default scree size at 100%

I need a way to scale the whole stage to the right resolution

This is my Main.java:

public class Main extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws IOException {
        final double WIDTH = Toolkit.getDefaultToolkit().getScreenSize().getWidth();
        final double HEIGHT = Toolkit.getDefaultToolkit().getScreenSize().getHeight();

        Parent board = FXMLLoader.load(getClass().getResource("/board.fxml"));

        Pane innerBoard = ((Pane)board.getChildrenUnmodifiable().get(0));
        Assert.assertCmp(innerBoard.getId(), "BOARD");

        Scene scene = new Scene(board, WIDTH, HEIGHT);

        scene.getStylesheets().add(getClass().getResource("/style.css").toExternalForm());
        primaryStage.setScene(scene);
        primaryStage.setFullScreen(true);
        primaryStage.setResizable(true);
        innerBoard.setVisible(false);
        
        primaryStage.setFullScreenExitHint("");
        primaryStage.show();

        
        innerBoard.setVisible(true);
 
    }

}

This is the FXML:

<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="1080.0" prefWidth="1920.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <Pane id="BOARD" fx:id="innerBoard" layoutX="356.0" layoutY="-7.0" style="-fx-background-color: #cfffd1;">
         <children>
            <!-- Lots of children -->
         </children>
      </Pane>
   </children>
</Pane>

Solution

  • I recommend you forego Pane and numeric positions and sizes. You want your board to be capable of displaying in any resolution; explicit positions and sizes directly contradict that.

    Layouts are the best way to make your board fit into any size.

    (Of course, layouts only work with rectangular board regions; I will assume you aren’t going for something complex like a circular or hexagon-based board, or a board with winding paths. In those cases, a Canvas may be a better choice than a Pane.)

    GridPane allows setting RowConstraints and ColumnConstraints, each of which can be percentage-based. If rows and columns have a size that is a percentage of the width of their parent, you get scaling for free. Here is a very primitive rendition of a Monopoly board:

    import javafx.application.Application;
    import javafx.stage.Stage;
    import javafx.scene.Scene;
    import javafx.scene.Node;
    import javafx.scene.layout.GridPane;
    import javafx.scene.layout.BorderPane;
    import javafx.scene.layout.RowConstraints;
    import javafx.scene.layout.ColumnConstraints;
    import javafx.scene.layout.Priority;
    
    public class MonopolyBoard
    extends Application {
        // Source:
        // https://commons.wikimedia.org/wiki/File:Store_Building_Flat_Icon_Vector.svg
        private static final String SIDE_SPACE_IMAGE_URI =
            "https://upload.wikimedia.org/wikipedia/commons/thumb/5/53"
            + "/Store_Building_Flat_Icon_Vector.svg"
            + "/240px-Store_Building_Flat_Icon_Vector.svg.png";
    
        // Source:
        // https://commons.wikimedia.org/wiki/File:Car_icon_transparent.png
        private static final String CORNER_SPACE_IMAGE_URI =
            "https://upload.wikimedia.org/wikipedia/commons/7/7e"
            + "/Car_icon_transparent.png";
    
        @Override
        public void start(Stage stage) {
            GridPane grid = new GridPane();
    
            Node[] row = new Node[11];
    
            row[0] = createSpace(CORNER_SPACE_IMAGE_URI);
            row[10] = createSpace(CORNER_SPACE_IMAGE_URI);
            for (int i = 1; i <= 9; i++) {
                row[i] = createSpace(SIDE_SPACE_IMAGE_URI);
            }
            grid.addRow(0, row);
    
            for (int i = 1; i <= 9; i++) {
                grid.add(createSpace(SIDE_SPACE_IMAGE_URI), 0, i);
                grid.add(createSpace(SIDE_SPACE_IMAGE_URI), 10, i);
            }
    
            row[0] = createSpace(CORNER_SPACE_IMAGE_URI);
            row[10] = createSpace(CORNER_SPACE_IMAGE_URI);
            for (int i = 1; i <= 9; i++) {
                row[i] = createSpace(SIDE_SPACE_IMAGE_URI);
            }
            grid.addRow(10, row);
    
            grid.getColumnConstraints().add(createColumnConstraints(2 / 11.0));
            for (int i = 1; i <= 9; i++) {
                grid.getColumnConstraints().add(createColumnConstraints(1 / 11.0));
            }
            grid.getColumnConstraints().add(createColumnConstraints(2 / 11.0));
    
            grid.getRowConstraints().add(createRowConstraints(2 / 11.0));
            for (int i = 1; i <= 9; i++) {
                grid.getRowConstraints().add(createRowConstraints(1 / 11.0));
            }
            grid.getRowConstraints().add(createRowConstraints(2 / 11.0));
    
            stage.setScene(new Scene(grid, 50 * 11, 50 * 11));
            stage.setTitle("Monopoly");
            stage.show();
        }
    
        private ColumnConstraints createColumnConstraints(double percentage) {
            ColumnConstraints constraints = new ColumnConstraints();
            constraints.setPercentWidth(percentage * 100);
            constraints.setHgrow(Priority.ALWAYS);
            return constraints;
        }
    
        private RowConstraints createRowConstraints(double percentage) {
            RowConstraints constraints = new RowConstraints();
            constraints.setPercentHeight(percentage * 100);
            constraints.setVgrow(Priority.ALWAYS);
            return constraints;
        }
    
        private Node createSpace(String imageURI) {
            BorderPane container = new BorderPane();
            container.setStyle(
                "-fx-border-width: 1px;"
                + " -fx-border-style: solid;"
                + " -fx-background-image: url('" + imageURI + "');"
                + " -fx-background-position: center;"
                + " -fx-background-repeat: no-repeat;"
                + " -fx-background-size: contain;");
    
            return container;
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    In addition to using the percentage properties of RowConstraints and ColumnConstraints, the above code takes advantage of CSS image scaling using -fx-background-size: contain.

    Resize (or maximize) the window to see the scaling at work. Obviously, if it were fullscreen, the same scaling would apply.