I\'m trying to write a game in JavaFX but I\'m having a slight issue with it and that\'s the key listeners getting interrupted by other key presses. I\'m using scene.s
I would make a set of all keys currently pressed and just check them in a timer. It doesn't eat many cpu cycles on my old machine. In motion it's 5%, but 0% when no key is pressed.
For a possibly faster solution use an ObservableSet
and start and stop actions when a key is added or removed.
package keytest;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import javafx.collections.SetChangeListener;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class KeyTest extends Application {
@Override
public void start(Stage primaryStage) {
ObservableSet<KeyCode> downKeys = FXCollections.observableSet();
Circle circle = new Circle(10);
Pane root = new Pane(circle);
Scene scene = new Scene(root, 500, 500);
scene.setOnKeyPressed(evt->{
downKeys.add(evt.getCode());
});
scene.setOnKeyReleased(evt->{
downKeys.remove(evt.getCode());
});
Timeline timer = new Timeline(new KeyFrame(
javafx.util.Duration.millis(16), ae -> {
downKeys.stream().parallel().forEach(kc -> {
Platform.runLater(() -> {//why?
switch(kc){
case UP: circle.setTranslateY(circle.getTranslateY()-2);
break;
case DOWN: circle.setTranslateY(circle.getTranslateY()+2);
break;
case LEFT: circle.setTranslateX(circle.getTranslateX()-2);
break;
case RIGHT: circle.setTranslateX(circle.getTranslateX()+2);
break;
}
});
});
}));
timer.setCycleCount(Animation.INDEFINITE);
timer.play();
//I think this might be faster
downKeys.addListener((SetChangeListener.Change<? extends KeyCode> change) -> {
if (change.wasAdded()){
System.out.println("start action "+change.getElementAdded());
}
if (change.wasRemoved()){
System.out.println("stop action "+change.getElementRemoved());
}
});
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I'm thinking the parallel is stupid and the Platform.runLater
costs more than the threading of +2 or -2 . But hey, java8 that's why
This is similar to this question, though the requirements look slightly different. Similar techniques work, but if you just want to respond to the most-recently-pressed key, you probably need some kind of stack.
This example allows you to move a rectangle around the screen, with the up, down, left, or right cursor keys; only the most recently pressed key that is still down is used.
import java.util.LinkedList;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class KeyStackExample extends Application {
@Override
public void start(Stage primaryStage) {
Rectangle rect = new Rectangle(50, 50, 100, 50);
rect.setFill(Color.SALMON);
Pane pane = new Pane(rect);
Scene scene = new Scene(pane, 800, 600);
final double rectangleHSpeed = 100 ; // pixels per second
final double rectangleVSpeed = 100 ;
final double minX = 0 ;
final double maxX = 800 ;
final double minY = 0 ;
final double maxY = 600 ;
final LinkedList<KeyCode> keyStack = new LinkedList<>();
scene.setOnKeyPressed(event -> {
KeyCode code = event.getCode();
if (! keyStack.contains(code)) {
keyStack.push(code);
}
});
scene.setOnKeyReleased(event ->
keyStack.remove(event.getCode()));
final LongProperty lastUpdateTime = new SimpleLongProperty();
final AnimationTimer rectangleAnimation = new AnimationTimer() {
@Override
public void handle(long timestamp) {
if (! keyStack.isEmpty() && lastUpdateTime.get() > 0) {
final double elapsedSeconds = (timestamp - lastUpdateTime.get()) / 1_000_000_000.0 ;
double deltaX = 0 ;
double deltaY = 0 ;
switch(keyStack.peek()) {
case UP:
deltaY = -rectangleVSpeed * elapsedSeconds;
break ;
case DOWN:
deltaY = rectangleVSpeed * elapsedSeconds ;
break ;
case LEFT:
deltaX = -rectangleHSpeed * elapsedSeconds ;
break ;
case RIGHT:
deltaX = rectangleHSpeed * elapsedSeconds ;
default:
break ;
}
double oldX = rect.getTranslateX() ;
double oldY = rect.getTranslateY() ;
rect.setTranslateX(clamp(oldX + deltaX, minX, maxX));
rect.setTranslateY(clamp(oldY + deltaY, minY, maxY));
}
lastUpdateTime.set(timestamp);
}
};
rectangleAnimation.start();
primaryStage.setScene(scene);
primaryStage.show();
}
private double clamp(double value, double min, double max) {
return Math.max(min, Math.min(max, value));
}
public static void main(String[] args) {
launch(args);
}
}