【问题标题】:JavaFX AnimationTimer not stopping properlyJavaFX AnimationTimer 没有正确停止
【发布时间】:2020-11-29 15:44:00
【问题描述】:

所以,我正在使用 JavaFX 创建贪吃蛇游戏,但我似乎无法让游戏正确暂停,即它偶尔会暂停,有时游戏会忽略暂停。所以,基本上我有一个 Main 类,我在其中初始化所有 GUI 组件,它还充当 javafx 应用程序的控制器。

我有一个名为gameControlButton 用于启动/暂停游戏,一个变量Boolean pause 用于跟踪游戏状态(新建/暂停/运行),以及方法startGamepauseGame .

gameControl按钮的EventHandler如下:

gameControl.setOnClicked(event->{
    if(paused == null) startGame(); //new game
    else if(paused) continueGame(); //for paused game
    else pauseGame();               //for running game
});

startGame 函数看起来像这样:

void startGame(){
    paused = false;
    Snake snake = new Snake(); //the snake sprite
    //following gameLoop controls the animation of the snake
    gameLoop = new AnimationTimer(){
        @Override
        public void handle(long now){
            drawSnake(); //draws the snake on the game
            snake.move(); //move snake ahead

            //following code is for slowing down the gameLoop renders to make it easier to play
            Task<Void> sleeper = new Task<>(){
                @Override
                protected Void call() throws Exception {
                    gameLoop.stop();
                    Thread.sleep(30);
                    gameLoop.start();
                    return null;
                }
            };
            new Thread(sleeper).start();
            //force garbage collection or else throws a bunch of exceptions after a while of running.
            //not sure of the cause...
            System.gc();
        }
    };
    gameLoop.start();
}

AnimationTimer gameLoop 是类的变量,允许从其他函数调用。

还有pauseGame 函数:

void pauseGame() {
    paused = true;
    gameLoop.stop();
}

所以,正如我之前所说,每次我点击gameControl 按钮时游戏都不会暂停,我怀疑这是由于gameLoopTask 内的Thread.sleep(30); 行所致。话虽如此,我仍然不完全确定,也不知道如何解决这个问题。任何帮助将不胜感激。

【问题讨论】:

标签: java javafx task java-threads


【解决方案1】:

“暂停”是什么类型?您检查它是否为空,然后将其视为布尔值。我不明白为什么它会是一个大的“B”布尔对象包装器而不是原始布尔类型。

这个:

        //following code is for slowing down the gameLoop renders to make it easier to play
        Task<Void> sleeper = new Task<>(){
            @Override
            protected Void call() throws Exception {
                gameLoop.stop();
                Thread.sleep(30);
                gameLoop.start();
                return null;
            }
        };

绝对是一种限制速度的可怕方式。让你的游戏循环运行,检查每个循环的时间,看看是否有足够的时间过去,你应该更新东西。您的动画计时器将驱动游戏。您不想暂停主平台线程,也不想暂停任何正在处理任务的工作线程。如果您正在安排任务,请将它们安排在您想要的时间间隔运行 - 不要在 call() 方法中限制线程。

你真正想要的是这样的:

//following gameLoop controls the animation of the snake
gameLoop = new AnimationTimer(){
    @Override
    public void handle(long now){
        if ((now - lastTime) > updateIterval) {
            drawSnake(); //draws the snake on the game
            snake.move(); //move snake ahead
            lastTime = now;
        }

如果动画计时器由于某种原因落后,您甚至可以创建一个循环来“赶上”:

        while ((now - lastTime) > updateIterval) {
            drawSnake(); //draws the snake on the game
            snake.move(); //move snake ahead
            lastTime += updateIterval;
        }

【讨论】:

  • 非常感谢您的反馈。这也是一个有效的解决方案。我以前没有这样想过,可能是因为我从 stackOverflow 上的另一个答案中找到了它
【解决方案2】:

您认为Thread.sleep(30); 可能导致问题的直觉是正确的。用户可以单击调用pauseGame 的按钮,将paused 设置为true,并告诉gameloop 停止。如果新的 sleeper 线程在那个时刻启动并处于休眠状态,它将在游戏循环中调用 start()

一种选择可能是简单地检查睡眠任务中paused 的值以确定它是否应该启动游戏循环。

Task<Void> sleeper = new Task<>(){
    @Override
    protected Void call() throws Exception {
        gameLoop.stop();
        Thread.sleep(30);
        if (!paused) {
            gameLoop.start();
        }
        return null;
    }
};

鉴于暂停值是从多个线程中设置和读取的,我建议添加一种机制来确保您不会获得不确定的行为。将布尔值交换为 AtomicBoolean 是确保一致性的直接选项。

【讨论】:

  • 在这种情况下,使布尔值变为 volatile 可能就足够了,但如果游戏由 AnimationTimer 驱动,则所有相关代码都应该在平台线程上发生。使用 sleeper 任务在各个方面都是错误的。
  • @swpalmer 在他的回答中提出了一个很好的观点,并提供了一个引人注目的重构机会。
猜你喜欢
  • 2012-03-23
  • 2020-03-02
  • 1970-01-01
  • 2014-01-11
  • 1970-01-01
  • 2018-10-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多