You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaFX 8游戏控制台全屏缩放实现问题求助

解决JavaFX控制台全屏缩放及重复子节点错误问题

问题背景

此前已实现启动屏的全屏缩放,但游戏控制台的缩放仍无法正常工作。运行代码后出现重复子节点添加错误,移除Console类中的BorderPane后窗口仅显示空白,需要实现控制台的全屏缩放并正常显示。

错误信息

Caused by: java.lang.RuntimeException: Exception in Application start method
    at javafx.graphics@18.0.1/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:901)
    at javafx.graphics@18.0.1/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:196)
    at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.IllegalArgumentException: Children: duplicate children added: parent = BorderPane@56dca201

现有代码

Console类

package application.console;

import java.util.List;

import application.areas.startingArea.SA;
import application.areas.vanguardForest.VFCmds;
import application.areas.vanguardForest.VFNavi;
import application.areas.vanguardForest.VFPkups;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;

public class Console extends Region {

    public static double WIDTH = 990;
    public static double HEIGHT = 525;
    
    private final Font cinzel;
    
    private final BorderPane root;
    
    private final VBox console;
    private final ScrollPane scroll;
    
    private final HBox inputBar;
    private final TextField input;
    private final Text carrot;
    
    public Console() {
        this.root = new BorderPane();
        root.setStyle("-fx-background-color: #232323;");
        
        this.cinzel = Font.loadFont("file:fonts/static/Cinzel-Medium.ttf", 16);
        
        this.console = new VBox();
        console.setPrefWidth(WIDTH);
        console.setPrefHeight(HEIGHT);
        
        this.scroll = new ScrollPane();
        scroll.setContent(console);
        scroll.setStyle("-fx-background: #232323;"
                + "-fx-background-color: transparent;"
                + "-fx-border-color: #232323;"
                + "-fx-focus-color: #232323;");
        scroll.setHbarPolicy(ScrollBarPolicy.NEVER);
        scroll.setVbarPolicy(ScrollBarPolicy.NEVER);
        scroll.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null)));
        
        console.setStyle("-fx-background-color: #232323;"
                + "-fx-focus-color: #232323;");
        
        console.heightProperty().addListener(new ChangeListener<Object>() {
            @Override
            public void changed(ObservableValue<?> observable, Object oldValue, Object newValue) {
                scroll.setVvalue((Double)newValue);
            }
        });
        
        this.inputBar = new HBox();
        inputBar.setPrefSize(WIDTH, 16);
        
        this.input = new TextField();
        input.setStyle("-fx-background-color: transparent;"
                + "-fx-text-fill: #FFFFFF;"
                + "-fx-highlight-fill: #FFFFFF;"
                + "-fx-highlight-text-fill: #232323;"
                );
        input.setFont(cinzel);
        input.setPrefWidth(WIDTH);
        
        this.carrot = new Text(" > ");
        carrot.setFont(Font.loadFont("file:fonts/static/Cinzel-Medium.ttf", 24));
        carrot.setFill(Color.WHITE);
        
        inputBar.getChildren().addAll(carrot, input);
        
        root.setMinSize(WIDTH, (HEIGHT - input.getHeight()));
        
        input.setOnAction(e -> {
            String s = (input.getText()).stripTrailing();
            
            if ((SA.isBuried || SA.inVF) && SS.gameStart) {
                Cmds.commands(s);   //has general functions
                VFCmds.commands(s); //doesn't have function until specific command 
                VFPkups.pickUp(s); //commands that allows pickups to be made within the game
                VFNavi.commands(s); //commands navigation for selected area
            } else {
                Cmds.commands(s);
            }
            input.clear();
        });
        
        root.getChildren().addAll(console, scroll, inputBar);
        root.setBottom(inputBar);
    }
    
    @Override 
    protected void layoutChildren() {
        double xScale = getWidth() / root.getWidth();
        double yScale = getHeight() / root.getHeight();
        double scale = Math.min(xScale, yScale);
        
        for (BorderPane bp : List.of(root)) {
            scaleAndCenter(bp, scale); 
        }
        
        for (VBox vb : List.of(console)) {
            scaleAndCenter(vb, scale);
        }
        
        for (HBox hb : List.of(inputBar)) {
            scaleAndCenter(hb, scale);
        }
        
        for (ScrollPane sp : List.of(scroll)) {
            scaleAndCenter(sp, scale); 
        }
        
        for (TextField tf : List.of(input)) {
            scaleAndCenter(tf, scale);
        }
        
        for (Text text : List.of(carrot)) {
            scaleAndCenter(text, scale); 
        }
    }

    private void scaleAndCenter(BorderPane root, double scale) {
        double w = scale * root.getWidth();
        double h = scale * root.getHeight();
        
        root.setPrefSize(w, h);
        root.relocate((getWidth() - w) / 2, (getHeight() - h) / 2);
    }
    
    private void scaleAndCenter(VBox vb, double scale) {
        double w = scale * vb.getWidth();
        double h = scale * vb.getHeight();
        
        vb.setPrefSize(w, h);
        vb.relocate((getWidth() - w) / 2, (getHeight() - h) / 2);
    }
    
    private void scaleAndCenter(HBox hb, double scale) {
        double w = scale * hb.getWidth();
        double h = scale * hb.getHeight();
        
        hb.setPrefSize(w, h);
        hb.relocate((getWidth() - w) / 2, (getHeight() - h) / 2);
    }
    
    private void scaleAndCenter(TextField input, double scale) {
        double w = scale * input.getWidth();
        double h = scale * input.getHeight();
        
        input.setPrefSize(w, 16);
        input.relocate((getWidth() - w) / 2, (getHeight() - h) / 2);
    }
    
    private void scaleAndCenter(ScrollPane scroll, double scale) {
        double w = scale * scroll.getWidth();
        double h = scale * scroll.getHeight();
        
        scroll.setPrefSize(w, h);
        scroll.relocate((getWidth() - w) / 2, (getHeight() - h) / 2);
    }
    
    private void scaleAndCenter(Text text, double scale) {
        double w = scale * text.getLayoutBounds().getWidth();
        double h = scale * text.getLayoutBounds().getHeight();
        double size = scale * text.getFont().getSize();
        
        text.setFont(Font.font(
                text.getFont().getFamily(),
                size));
        text.relocate((getWidth() - w) / 2, (getHeight() - h) / 2);
    }
}

game类

package application.console;

import javafx.scene.Parent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;

public class game {

    public static Parent runGame() {
        
        Console c = new Console();
        AnchorPane anchor = new AnchorPane(c); 
        
        AnchorPane.setTopAnchor(c, 0.0);
        AnchorPane.setRightAnchor(c, 0.0);
        AnchorPane.setBottomAnchor(c, 0.0);
        AnchorPane.setLeftAnchor(c, 0.0);
        
        BorderPane root = new BorderPane();
        root.setCenter(anchor);
        
        return root;
    }
}

解决方案

1. 修复重复子节点错误

BorderPane不能通过getChildren().addAll()直接添加子节点,且你已经通过root.setBottom(inputBar)添加了inputBar,再调用addAll会导致重复添加。同时console已经被设置为scroll的内容,不需要再添加到root中。修改Console构造方法中的布局代码:

// 移除错误代码
// root.getChildren().addAll(console, scroll, inputBar);
// 改为正确的BorderPane布局方式
root.setCenter(scroll);
root.setBottom(inputBar);

2. 简化缩放逻辑

不需要逐个缩放每个子控件,只需要缩放根BorderPane即可,子控件会跟随根节点自动适配。同时要确保根BorderPane被添加到Console的子节点中,否则不会显示内容。修改layoutChildren方法和缩放方法:

@Override 
protected void layoutChildren() {
    double xScale = getWidth() / WIDTH;
    double yScale = getHeight() / HEIGHT;
    double scale = Math.min(xScale, yScale);
    
    // 缩放并居中根BorderPane
    scaleAndCenter(root, scale);
    // 确保根节点被添加到Console的子节点中
    if (!getChildren().contains(root)) {
        getChildren().add(root);
    }
    
    // 单独调整Text的字体大小以适配缩放
    double newFontSize = scale * 24;
    carrot.setFont(Font.loadFont("file:fonts/static/Cinzel-Medium.ttf", newFontSize));
}

private void scaleAndCenter(Region node, double scale) {
    double w = scale * node.getPrefWidth();
    double h = scale * node.getPrefHeight();
    
    node.setPrefSize(w, h);
    node.relocate((getWidth() - w) / 2, (getHeight() - h) / 2);
}

修改后的完整Console类

package application.console;

import application.areas.startingArea.SA;
import application.areas.vanguardForest.VFCmds;
import application.areas.vanguardForest.VFNavi;
import application.areas.vanguardForest.VFPkups;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;

public class Console extends Region {

    public static double WIDTH = 990;
    public static double HEIGHT = 525;
    
    private final Font cinzel;
    
    private final BorderPane root;
    
    private final VBox console;
    private final ScrollPane scroll;
    
    private final HBox inputBar;
    private final TextField input;
    private final Text carrot;
    
    public Console() {
        this.root = new BorderPane();
        root.setStyle("-fx-background-color: #232323;");
        
        this.cinzel = Font.loadFont("file:fonts/static/Cinzel-Medium.ttf", 16);
        
        this.console = new VBox();
        console.setPrefWidth(WIDTH);
        console.setPrefHeight(HEIGHT);
        
        this.scroll = new ScrollPane();
        scroll.setContent(console);
        scroll.setStyle("-fx-background: #232323;"
                + "-fx-background-color: transparent;"
                + "-fx-border-color: #232323;"
                + "-fx-focus-color: #232323;");
        scroll.setHbarPolicy(ScrollBarPolicy.NEVER);
        scroll.setVbarPolicy(ScrollBarPolicy.NEVER);
        scroll.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null)));
        
        console.setStyle("-fx-background-color: #232323;"
                + "-fx-focus-color: #232323;");
        
        console.heightProperty().addListener(new ChangeListener<Object>() {
            @Override
            public void changed(ObservableValue<?> observable, Object oldValue, Object newValue) {
                scroll.setVvalue((Double)newValue);
            }
        });
        
        this.inputBar = new HBox();
        inputBar.setPrefSize(WIDTH, 16);
        
        this.input = new TextField();
        input.setStyle("-fx-background-color: transparent;"
                + "-fx-text-fill: #FFFFFF;"
                + "-fx-highlight-fill: #FFFFFF;"
                + "-fx-highlight-text-fill: #232323;"
                );
        input.setFont(cinzel);
        input.setPrefWidth(WIDTH);
        
        this.carrot = new Text(" > ");
        carrot.setFont(Font.loadFont("file:fonts/static/Cinzel-Medium.ttf", 24));
        carrot.setFill(Color.WHITE);
        
        inputBar.getChildren().addAll(carrot, input);
        
        root.setMinSize(WIDTH, (HEIGHT - input.getHeight()));
        
        input.setOnAction(e -> {
            String s = (input.getText()).stripTrailing();
            
            if ((SA.isBuried || SA.inVF) && SS.gameStart) {
                Cmds.commands(s);   //has general functions
                VFCmds.commands(s); //doesn't have function until specific command 
                VFPkups.pickUp(s); //commands that allows pickups to be made within the game
                VFNavi.commands(s); //commands navigation for selected area
            } else {
                Cmds.commands(s);
            }
            input.clear();
        });
        
        // 正确设置BorderPane的子节点
        root.setCenter(scroll);
        root.setBottom(inputBar);
    }
    
    @Override 
    protected void layoutChildren() {
        double xScale = getWidth() / WIDTH;
        double yScale = getHeight() / HEIGHT;
        double scale = Math.min(xScale, yScale);
        
        // 缩放并居中根BorderPane
        scaleAndCenter(root, scale);
        // 确保根节点被添加到Console的子节点中
        if (!getChildren().contains(root)) {
            getChildren().add(root);
        }
        
        // 单独调整Text的字体大小以适配缩放
        double newFontSize = scale * 24;
        carrot.setFont(Font.loadFont("file:fonts/static/Cinzel-Medium.ttf", newFontSize));
    }

    private void scaleAndCenter(Region node, double scale) {
        double w = scale * node.getPrefWidth();
        double h = scale * node.getPrefHeight();
        
        node.setPrefSize(w, h);
        node.relocate((getWidth() - w) / 2, (getHeight() - h) / 2);
    }
}

说明

  • 修复了重复子节点错误:通过BorderPane的setCenter和setBottom方法正确添加子节点,避免直接调用getChildren().addAll()。
  • 实现全屏缩放:通过计算缩放比例,只缩放根BorderPane,子控件会自动跟随适配,同时单独调整Text的字体大小保证显示正常。
  • 确保内容显示:将根BorderPane添加到Console的子节点中,解决移除BorderPane后空白的问题。

内容的提问来源于stack exchange,提问作者Providence

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 04:45:37