Issue
I am learning JavaFX and trying to figure out a good approach to a modular structure of a simple game. My current implementation doesnt work like expected, there is something finicky with my game loop.
Here is my Main class
public class Main extends Application {
private static Game game;
public static void main(String[] args) {
//calls init(), start(), and stop()
launch(args);
}
@Override
public void start(Stage stage) {
game = new Game(stage);
final long startNanoTime = System.nanoTime();
new AnimationTimer() {
public void handle(long currentNanoTime) {
double t = (currentNanoTime - startNanoTime) / 1000000000.0;
game.update(t);
game.render();
}
}.start();
}
}
And here is my Game class
class Game {
public Stage stage;
public Group root;
public Scene scene;
public Canvas canvas;
public GraphicsContext gc;
final static int WINDOW_WIDTH = 800;
final static int WINDOW_HEIGHT = 600;
public Game(Stage stage) {
this.stage = stage;
this.root = new Group();
this.scene = new Scene(this.root);
this.canvas = new Canvas(WINDOW_WIDTH, WINDOW_HEIGHT);
this.gc = this.canvas.getGraphicsContext2D();
this.stage.setTitle("Pong");
this.stage.setScene(this.scene);
this.root.getChildren().add(this.canvas);
}
public void update(double time) {
this.handleInput();
this.gc.setFill(Color.RED);
this.gc.fillRect(0,0, WINDOW_WIDTH, WINDOW_HEIGHT);
}
public void render() {
this.stage.show();
}
private void handleInput() {
ArrayList<String> input = new ArrayList<String>();
this.scene.setOnKeyPressed(
new EventHandler<KeyEvent>() {
public void handle(KeyEvent e) {
String code = e.getCode().toString();
if (!input.contains(code)) {
input.add(code);
}
}
});
this.scene.setOnKeyReleased(
new EventHandler<KeyEvent>() {
public void handle(KeyEvent e) {
String code = e.getCode().toString();
input.remove(code);
}
});
System.out.println(input); //this prints an empty array, should print my keycodes
}
}
For example, I can't seem to exit the game consistently, sometimes the window stays open when I press exit, making me believe there is something wrong with the way I handle events. My main question is why doesnt my event codes get printed to the console? Thats my expected result, but I get an empty array.
Solution
The AnimationTimer
's handle()
method is called every time a frame is rendered. Consequently, handleInput()
is being called about 60 times per second. Each time you register new key event handlers with the scene, create a new (empty) list, and print its contents. Obviously this is not what you want.
Instead, register the event handlers just once, and create just one list. Since you presumably need to access that list later, you need to make it an instance variable. This should print the keys currently pressed:
class Game {
private Stage stage;
private Group root;
private Scene scene;
private Canvas canvas;
private GraphicsContext gc;
final static int WINDOW_WIDTH = 800;
final static int WINDOW_HEIGHT = 600;
private List<KeyCode> input = new ArrayList<>();
public Game(Stage stage) {
this.stage = stage;
this.root = new Group();
this.scene = new Scene(this.root);
this.canvas = new Canvas(WINDOW_WIDTH, WINDOW_HEIGHT);
this.gc = this.canvas.getGraphicsContext2D();
this.stage.setTitle("Pong");
this.stage.setScene(this.scene);
this.root.getChildren().add(this.canvas);
this.scene.setOnKeyPressed(
new EventHandler<KeyEvent>() {
public void handle(KeyEvent e) {
KeyCode code = e.getCode();
if (!input.contains(code)) {
input.add(code);
}
}
});
this.scene.setOnKeyReleased(
new EventHandler<KeyEvent>() {
public void handle(KeyEvent e) {
KeyCode code = e.getCode();
input.remove(code);
}
});
}
public void update(double time) {
this.handleInput();
this.gc.setFill(Color.RED);
this.gc.fillRect(0,0, WINDOW_WIDTH, WINDOW_HEIGHT);
}
public void render() {
this.stage.show();
}
private void handleInput() {
System.out.println(input);
}
}
Answered By - James_D
Answer Checked By - David Goodson (JavaFixing Volunteer)