3

I'm writing a dungeon style game, the dungeon is basically a GridPane. I want to allow the player move for one grid for every 0.5 seconds if he keeping holding the control keys. But I'm not sure how to accomplish this. I have read JavaFX : How to detect if a key is being held down? . But this question is not particularly related to my problem (except for I can track how many key event happens and perhaps do more based on that). So I follow this post and try to use Thread.sleep() to solve my problem, but it turns out the player just get stoped for a few seconds and then suddenly move a few grid.

Does anyone know how to solve this?

@FXML
public void handleKeyPress(KeyEvent event) {

    switch (event.getCode()) {
    case UP:
        System.out.println("Up");
        player.moveUp();
        break;
    case DOWN:
        System.out.println("Down");
        player.moveDown();
        break;
    case LEFT:
        System.out.println("Left");
        player.moveLeft();
        break;
    case RIGHT:
        System.out.println("Right");
        player.moveRight();
        break;
    default:
        break;
    }
}
Dickson
  • 113
  • 6

1 Answers1

5

Basically, you'll have to keep track of when you last allowed a move and only move again if a specified amount of time has passed. Here's a proof-of-concept:

import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class Main extends Application {

    private static final long THRESHOLD = 500_000_000L; // 500 ms

    private long lastMoveNanos;
    private Rectangle rect;

    @Override
    public void start(Stage primaryStage) {
        rect = new Rectangle(25, 25, Color.DODGERBLUE);
        rect.setStroke(Color.BLACK);
        rect.setStrokeWidth(1);

        var root = new Group(rect);
        root.setOnKeyPressed(this::handleKeyPressed);

        var scene = new Scene(root, 600, 400);
        rect.setX(scene.getWidth() / 2 - rect.getWidth() / 2);
        rect.setY(scene.getHeight() / 2 - rect.getHeight() / 2);

        primaryStage.setScene(scene);
        primaryStage.setResizable(false);
        primaryStage.show();

        root.requestFocus();
    }

    private void handleKeyPressed(KeyEvent event) {
        if (event.getCode().isArrowKey()) {
            event.consume();

            long now = System.nanoTime();
            if (lastMoveNanos <= 0L || now - lastMoveNanos >= THRESHOLD) {
                switch (event.getCode()) {
                    case UP:
                        rect.setY(rect.getY() - rect.getHeight());
                        break;
                    case DOWN:
                        rect.setY(rect.getY() + rect.getHeight());
                        break;
                    case LEFT:
                        rect.setX(rect.getX() - rect.getWidth());
                        break;
                    case RIGHT:
                        rect.setX(rect.getX() + rect.getWidth());
                        break;
                    default:
                        throw new AssertionError();
                }
                lastMoveNanos = now;
            }
        }
    }

}

The above moves the Rectangle an amount equal to its width/height (to simulate a "grid") at a maximum speed of 1 move per 500 milliseconds. If the change-in-position should be continuous you can use an animation for the movement (e.g. a TranslateTransition with its byX and byY properties set). When using an animation, if the duration is as long as the desired throttle time you could use the animation itself to track when the next move is allowed.

A more robust example might use AnimationTimer and may keep track of all movement keys that have been pressed but not yet released (e.g. to pick up the last still-pressed key when the "current" key is released, if any). Here's another proof-of-concept:

import java.util.ArrayDeque;
import java.util.Deque;
import javafx.animation.AnimationTimer;
import javafx.scene.input.KeyCode;
import javafx.scene.shape.Rectangle;

class MoveAnimationTimer extends AnimationTimer {

    private static final long THRESHOLD = 500_000_000L; // 500 ms

    private final Rectangle node;

    private final Deque<KeyCode> keyStack = new ArrayDeque<>(4);
    private long then;

    MoveAnimationTimer(Rectangle node) {
        this.node = node;
    }

    void pushKeyIfAbsent(KeyCode code) {
        if (code.isArrowKey() && !keyStack.contains(code)) {
            keyStack.push(code);
            start();
        }
    }

    void removeKey(KeyCode code) {
        if (code.isArrowKey()) {
            keyStack.remove(code);
            if (keyStack.isEmpty()) {
                stop();
            }
        }
    }

    @Override
    public void handle(long now) {
        if (then <= 0L || now - then >= THRESHOLD) {
            switch (keyStack.getFirst()) {
                case UP:
                    node.setY(node.getY() - node.getHeight());
                    break;
                case DOWN:
                    node.setY(node.getY() + node.getHeight());
                    break;
                case LEFT:
                    node.setX(node.getX() - node.getWidth());
                    break;
                case RIGHT:
                    node.setX(node.getX() + node.getWidth());
                    break;
                default:
                    throw new AssertionError();
            }
            then = now;
        }
    }

}
Slaw
  • 37,820
  • 8
  • 53
  • 80