14

I want to write a little game where I can move a ball on a JavaFX Panel using the W, A, S, D keys.
I have a getPosX() and setPosX() but I don't know how to write a KeyListener which will e.g. calculate setPosX(getPosX()+1) if I press D.

What do I have to do?

assylias
  • 321,522
  • 82
  • 660
  • 783
SUT
  • 323
  • 1
  • 3
  • 14
  • 1
    Take a look at [this answer](http://stackoverflow.com/questions/29057870/in-javafx-how-do-i-move-a-sprite-across-the-screen/29058909#29058909). – Roland Apr 30 '15 at 08:28

3 Answers3

34

From a JavaRanch Forum post.

Key press and release handlers are added on the scene and update movement state variables recorded in the application. An animation timer hooks into the JavaFX pulse mechanism (which by default will be capped to fire an event 60 times a second) - so that is a kind of game "loop". In the timer the movement state variables are checked and their delta actions applied to the character position - which in effect moves the character around the screen in response to key presses.

zelda

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.*;
import javafx.scene.image.*;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
 
/**
 * Hold down an arrow key to have your hero move around the screen.
 * Hold down the shift key to have the hero run.
 */
public class Runner extends Application {
 
    private static final double W = 600, H = 400;
 
    private static final String HERO_IMAGE_LOC =
            "http://icons.iconarchive.com/icons/raindropmemory/legendora/64/Hero-icon.png";
 
    private Image heroImage;
    private Node  hero;
 
    boolean running, goNorth, goSouth, goEast, goWest;
 
    @Override
    public void start(Stage stage) throws Exception {
        heroImage = new Image(HERO_IMAGE_LOC);
        hero = new ImageView(heroImage);
 
        Group dungeon = new Group(hero);
 
        moveHeroTo(W / 2, H / 2);
 
        Scene scene = new Scene(dungeon, W, H, Color.FORESTGREEN);
 
        scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
            @Override
            public void handle(KeyEvent event) {
                switch (event.getCode()) {
                    case UP:    goNorth = true; break;
                    case DOWN:  goSouth = true; break;
                    case LEFT:  goWest  = true; break;
                    case RIGHT: goEast  = true; break;
                    case SHIFT: running = true; break;
                }
            }
        });
 
        scene.setOnKeyReleased(new EventHandler<KeyEvent>() {
            @Override
            public void handle(KeyEvent event) {
                switch (event.getCode()) {
                    case UP:    goNorth = false; break;
                    case DOWN:  goSouth = false; break;
                    case LEFT:  goWest  = false; break;
                    case RIGHT: goEast  = false; break;
                    case SHIFT: running = false; break;
                }
            }
        });
 
        stage.setScene(scene);
        stage.show();
 
        AnimationTimer timer = new AnimationTimer() {
            @Override
            public void handle(long now) {
                int dx = 0, dy = 0;
 
                if (goNorth) dy -= 1;
                if (goSouth) dy += 1;
                if (goEast)  dx += 1;
                if (goWest)  dx -= 1;
                if (running) { dx *= 3; dy *= 3; }
 
                moveHeroBy(dx, dy);
            }
        };
        timer.start();
    }
 
    private void moveHeroBy(int dx, int dy) {
        if (dx == 0 && dy == 0) return;
 
        final double cx = hero.getBoundsInLocal().getWidth()  / 2;
        final double cy = hero.getBoundsInLocal().getHeight() / 2;
 
        double x = cx + hero.getLayoutX() + dx;
        double y = cy + hero.getLayoutY() + dy;
 
        moveHeroTo(x, y);
    }
 
    private void moveHeroTo(double x, double y) {
        final double cx = hero.getBoundsInLocal().getWidth()  / 2;
        final double cy = hero.getBoundsInLocal().getHeight() / 2;
 
        if (x - cx >= 0 &&
            x + cx <= W &&
            y - cy >= 0 &&
            y + cy <= H) {
            hero.relocate(x - cx, y - cy);
        }
    }
 
    public static void main(String[] args) { launch(args); }
}

On filters, handlers and focus

To receive key events, the object that the event handlers are set on must be focus traversable. This example sets handlers on the scene directly, but if you were to set handlers on the pane instead of the scene, it would need to be focus traversable and have focus.

If you want a global intercept point to override or intercept events that are to be routed through the in-built event handlers which will consume events you want (e.g. buttons and text fields), you can have an event filter on the scene rather than a handler.

To better understand the difference between a handler and a filter, make sure that you study and understand the event capturing and bubbling phases as explained in the JavaFX event tutorial.

Generic input handler

Please ignore the rest of this answer if the information already provided is sufficient for your purposes.

While the above solution is sufficient to answer this question, if interested, a more sophisticated input handler (with a more general and separated, input and update handling logic), can be found in this demo breakout game:

Example generic input handler from the sample breakout game:

class InputHandler implements EventHandler<KeyEvent> {
    final private Set<KeyCode> activeKeys = new HashSet<>();

    @Override
    public void handle(KeyEvent event) {
        if (KeyEvent.KEY_PRESSED.equals(event.getEventType())) {
            activeKeys.add(event.getCode());
        } else if (KeyEvent.KEY_RELEASED.equals(event.getEventType())) {
            activeKeys.remove(event.getCode());
        }
    }

    public Set<KeyCode> getActiveKeys() {
        return Collections.unmodifiableSet(activeKeys);
    }
}

While an ObservableSet with an appropriate set change listener could be used for the set of active keys, I have used an accessor which returns an unmodifiable set of keys which were active at a snapshot in time, because that is what I was interested in here rather than observing changes to the set of active keys in real-time.

If you want to keep track of the order in which keys are pressed, a Queue, List, or TreeSet can be used rather than a Set (for example, with a TreeSet ordering events on the time of keypress, the most recent key pressed would be the last element in the set).

Example generic input handler usage:

Scene gameScene = createGameScene();

// register the input handler to the game scene.
InputHandler inputHandler = new InputHandler();
gameScene.setOnKeyPressed(inputHandler);
gameScene.setOnKeyReleased(inputHandler);

gameLoop = createGameLoop();

// . . .

private AnimationTimer createGameLoop() {
    return new AnimationTimer() {
        public void handle(long now) {
            update(now, inputHandler.getActiveKeys());
            if (gameState.isGameOver()) {
                this.stop();
            }
        }
    };
}

public void update(long now, Set<KeyCode> activeKeys) {
    applyInputToPaddle(activeKeys);
    // . . . rest of logic to update game state and view.
}

// The paddle is sprite implementation with
// an in-built velocity setting that is used to
// update its position for each frame.
//
// on user input, The paddle velocity changes 
// to point in the correct predefined direction.
private void applyInputToPaddle(Set<KeyCode> activeKeys) {
    Point2D paddleVelocity = Point2D.ZERO;

    if (activeKeys.contains(KeyCode.LEFT)) {
        paddleVelocity = paddleVelocity.add(paddleLeftVelocity);
    }

    if (activeKeys.contains(KeyCode.RIGHT)) {
        paddleVelocity = paddleVelocity.add(paddleRightVelocity);
    }

    gameState.getPaddle().setVelocity(paddleVelocity);
}
jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • how to I do this when I'm loading an fxml. doesn't seem to work, do I have to use the annotation @FXML to register the listener somehow? – Flying Swissman Feb 28 '17 at 15:05
  • 2
    @FlyingSwissman This example really doesn't have anything to do with FXML. In the example the key event handlers are added to the scene which would not be defined in FXML even if the application were FXML based. If you continue to have questions about event handlers and FXML please ask them as new questions with an [mcve](http://stackoverflow.com/help/mcve). – jewelsea Feb 28 '17 at 18:16
4
Scene myScene = new Scene();

KeyCombination cntrlZ = new KeyCodeCombination(KeyCode.Z, KeyCodeCombination.CONTROL_DOWN);
myScene.setOnKeyPressed(new EventHandler<KeyEvent>(){
    @Override
    public void handle(KeyEvent event) {
        if(contrlZ.match(event)){
           //Do something
        }
    }
});
Pang
  • 9,564
  • 146
  • 81
  • 122
0

using JNativeHook: https://github.com/kwhat/jnativehook

<!-- https://mvnrepository.com/artifact/com.1stleg/jnativehook -->
<dependency>
  <groupId>com.1stleg</groupId>
  <artifactId>jnativehook</artifactId>
  <version>2.1.0</version>
</dependency>



    private void initKeyListener(Stage primaryStage){
    /* Note: JNativeHook does *NOT* operate on the event dispatching thread.
     * Because Swing components must be accessed on the event dispatching
     * thread, you *MUST* wrap access to Swing components using the
     * SwingUtilities.invokeLater() or EventQueue.invokeLater() methods.
     */
    try {
        GlobalScreen.registerNativeHook();
    } catch (NativeHookException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
    }
    GlobalScreen.addNativeKeyListener(new NativeKeyListener() {
        public void nativeKeyPressed(NativeKeyEvent e) {
            if ( (e.getModifiers() & NativeKeyEvent.CTRL_MASK) != 0
                    && (e.getModifiers() & NativeKeyEvent.ALT_MASK) != 0
                    && (e.getKeyCode() == NativeKeyEvent.VC_B)){
                logger.debug("key :Hide");
                primaryStage.hide();
            }
            if ( (e.getModifiers() & NativeKeyEvent.CTRL_MASK) != 0
                    && (e.getModifiers() & NativeKeyEvent.SHIFT_MASK) != 0
                    && (e.getModifiers() & NativeKeyEvent.ALT_MASK) != 0
                    && (e.getKeyCode() == NativeKeyEvent.VC_B)){
                logger.debug("key :Show");
                primaryStage.show();
            }
            //System.out.println("Key Pressed: " + NativeKeyEvent.getKeyText(e.getKeyCode()));
        }

        public void nativeKeyReleased(NativeKeyEvent e) {
            //System.out.println("Key Released: " + NativeKeyEvent.getKeyText(e.getKeyCode()));
        }

        public void nativeKeyTyped(NativeKeyEvent e) {
            //System.out.println("Key Typed: " + NativeKeyEvent.getKeyText(e.getKeyCode()));
        }
    });
    /*
    GlobalScreen.addNativeMouseListener(new NativeMouseListener() {
        @Override
        public void nativeMouseReleased(NativeMouseEvent arg0) {
            // TODO Auto-generated method stub
            System.out.println("MouseReleased()");
        }

        @Override
        public void nativeMousePressed(NativeMouseEvent arg0) {
            // TODO Auto-generated method stub
            System.out.println("MousePressed()");
        }

        @Override
        public void nativeMouseClicked(NativeMouseEvent arg0) {
            // TODO Auto-generated method stub
            System.out.println("MouseClicked()");
        }
    });
    */
}