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;
}
}
}