java - 如何更改动画计时器内通过键盘输入移动的对象的速度?

标签 java animation javafx event-handling

我有这个 JavaFX Circle,它根据键盘的箭头移动。 AnimationTimer 所做的只是每帧刷新圆圈位置。

我发现每次触发 KeyEvent 时都会发生 0.1 的移动,对于动画来说足够平滑,但移动速度非常慢。另一方面,如果我将运动更改为 1.010.0,它无疑会更快,但也会更加不稳定(您可以清楚地看到它开始按离散值移动)。

我希望能够保持每帧最多 0.1 的平移流畅度,而且还能够更改每次触发按键时应移动的空间大小。

下面是对问题的描述:

public class MainFX extends Application {

    private double playerX;
    private double playerY;

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        AnchorPane pane = new AnchorPane();
        Scene scene = new Scene(pane, 800, 600);
        primaryStage.setScene(scene);
        primaryStage.show();

        playerX = pane.getWidth()/2;
        playerY = pane.getHeight()/2;
        Circle player = new Circle(playerX,playerY,10);
        pane.getChildren().add(player);
        scene.addEventHandler(KeyEvent.KEY_PRESSED, this::animate);

        AnimationTimer timer = new AnimationTimer() {
            @Override
            public void handle(long now) {
                player.setCenterX(playerX);
                player.setCenterY(playerY);
            }
        };
        timer.start();

    }
    private void animate(KeyEvent key){
        if (key.getCode() == KeyCode.UP) {
            playerY-=0.1;
        }
        if (key.getCode() == KeyCode.DOWN) {
            playerY+=0.1;
        }
        if (key.getCode() == KeyCode.RIGHT) {
            playerX+=0.1;
        }
        if (key.getCode() == KeyCode.LEFT) {
            playerX-=0.1;
        }
    }
}

最佳答案

AnimationTimer 的 handle() 方法在每个帧渲染时调用。假设 FX 应用程序线程没有被其他工作淹没,这将以每秒大约 60 帧的速度发生。通过此方法更新 View 将提供相对平滑的动画。

相比之下,按键事件处理程序会在每次按键(或释放等)事件时调用。通常,当按住某个键时, native 系统将以一定的速率发出重复的按键事件,该速率取决于系统(通常是用户可配置的),并且通常比动画帧慢得多(通常每半秒左右)。从此处更改 UI 元素的位置将导致运动不平稳。

您当前的代码从 AnimationTimer 中的 playerXplayerY 变量更新 UI 元素的位置:但是您只更改这些变量是关键的事件处理程序。因此,如果 AnimationTimer 以 60fps 运行,并且按键事件每 0.5 秒发生一次(例如),您将使用每个新值“更新”UI 元素 30 次,仅更改实际位置每秒两次。

更好的方法是仅使用按键事件处理程序来维护指示每个键是否被按下的变量状态。在 AnimationTimer 中,根据按键的状态以及自上次更新以来耗时量更新 UI。

这是使用此方法的代码版本:

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;

public class MainFX extends Application {

    private boolean leftPressed ;
    private boolean rightPressed ;
    private boolean upPressed ;
    private boolean downPressed ;
    
    private static final double SPEED = 100 ; // pixels/second
    private static  final double PLAYER_RADIUS = 10 ;
    private AnchorPane pane;

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        pane = new AnchorPane();
        Scene scene = new Scene(pane, 800, 600);
        primaryStage.setScene(scene);
        primaryStage.show();

        double playerX = pane.getWidth() / 2;
        double playerY = pane.getHeight() / 2;
        Circle player = new Circle(playerX, playerY, PLAYER_RADIUS);
        pane.getChildren().add(player);
        scene.addEventHandler(KeyEvent.KEY_PRESSED, this::press);
        scene.addEventHandler(KeyEvent.KEY_RELEASED, this::release);

        AnimationTimer timer = new AnimationTimer() {
            
            private long lastUpdate = System.nanoTime() ;
            @Override
            public void handle(long now) {
                double elapsedSeconds = (now - lastUpdate) / 1_000_000_000.0 ;
                
                
                int deltaX = 0 ;
                int deltaY = 0 ;
                
                if (leftPressed) deltaX -= 1 ;
                if (rightPressed) deltaX += 1 ;
                if (upPressed) deltaY -= 1 ;
                if (downPressed) deltaY += 1 ;
                
                Point2D translationVector = new Point2D(deltaX, deltaY)
                        .normalize()
                        .multiply(SPEED * elapsedSeconds);
                
                player.setCenterX(clampX(player.getCenterX() + translationVector.getX()));
                player.setCenterY(clampY(player.getCenterY() + translationVector.getY()));
                
                lastUpdate = now ;
            }
        };
        timer.start();

    }
    
    private double clampX(double value) {
        return clamp(value, PLAYER_RADIUS, pane.getWidth() - PLAYER_RADIUS);
    }
    
    private double clampY(double value) {
        return clamp(value, PLAYER_RADIUS, pane.getHeight() - PLAYER_RADIUS);
    }
    
    private double clamp(double value,  double min, double max) {
        return Math.max(min, Math.min(max, value));
    }
    
    private void press(KeyEvent event) {
        handle(event.getCode(), true);
    }
    
    private void release(KeyEvent event) {
        handle(event.getCode(), false);
    }

    private void handle(KeyCode key, boolean press) {
        switch(key) {
        case UP: upPressed = press ; break ;
        case DOWN: downPressed = press ; break ;
        case LEFT: leftPressed = press ; break ;
        case RIGHT: rightPressed = press ; break ;
        default: ;
        }
    }
}

关于java - 如何更改动画计时器内通过键盘输入移动的对象的速度?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69604915/

相关文章:

java - 如何将 TV Tuner Card 用于实时视频的 Java 编程

java - 如何在java程序中让某些事情在固定的时间间隔发生?

java - 尝试从另一个对象引用变量(JAVA)

ios - ios中使用关键帧动画时如何设置动画曲线?

java - 如何使 JavaFX Web 浏览器显示警报和确认消息

java - 在java中将日期格式从1-1-2012转换为2012年1月1日?

Android L Action 项动画

CSS3 : Smooth transition between animations change on :hover

binding - 如何使用格式化程序绑定(bind)属性

java - 在 JavaFX 中的图像上使用 ColorPicker