Перехват нажатия клавиш с помощью Thread, связанного с приложением JavaFX - PullRequest
0 голосов
/ 12 апреля 2020

Я начал программировать игру с друзьями для проекта. Мы пытались создать файтинг, и мне было довольно сложно работать с параллельным доступом в JavaFX. Как я могу прислушиваться к ключевым депрессиям с помощью связанной ветки, не замораживая приложение? Заранее благодарим за помощь и счастливой Пасхи!

1 Ответ

2 голосов
/ 12 апреля 2020

Обычно вам не нужны темы для чего-то подобного. Общая стратегия игры (или симуляции) в JavaFX:

  1. Определение классов для представления состояния игры / симуляции ("модель")
  2. Определение обработчиков событий, которые обновляются то же самое состояние на основе ввода usr (например, обработчики клавиш)
  3. Используйте AnimationTimer в качестве "игры l oop". Метод AnimationTimer handle() вызывается при каждом рендеринге сцены, поэтому вы можете обновить пользовательский интерфейс в зависимости от состояния игры и количества времени, прошедшего с момента последнего обновления.

Вот очень простой пример, когда игрок просто перемещается по экрану. У игрока есть наблюдаемые координаты x и y, значения boolean, указывающие, движется ли он в каждом из четырех направлений, и метод move(), который обновляет позицию на основе этих направлений и прошедшего времени.

import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;

public class Player {
    private final DoubleProperty x = new SimpleDoubleProperty();
    private final DoubleProperty y = new SimpleDoubleProperty();
    private boolean up ;
    private boolean down ;
    private boolean left ;
    private boolean right ;

    private final double minX ;
    private final double maxX ;
    private final double minY ;
    private final double maxY ;

    public Player(double minX, double maxX, double minY, double maxY) {
        this.minX = minX ;
        this.maxX = maxX ;
        this.minY = minY ;
        this.maxY = maxY ;
    }


    private static final double SPEED = 100 ; // pixels/second

    public void move(double elapsedSeconds) {
        int horiz = 0 ;
        if (isLeft()) horiz = horiz - 1 ;
        if (isRight()) horiz = horiz + 1 ;

        setX(clamp(getX()+horiz * SPEED * elapsedSeconds, minX, maxX));

        int vert = 0 ;
        if (isUp()) vert = vert - 1 ;
        if (isDown()) vert = vert + 1 ;

        setY(clamp(getY()+vert * SPEED * elapsedSeconds, minY, maxY));
    }

    // return value "clamped" between minValue and maxValue:
    private double clamp(double value, double minValue, double maxValue) {
        if (value < minValue) return minValue ;
        if (value > maxValue) return maxValue ;
        return value ;
    }

    public DoubleProperty xProperty() {
        return x ;
    }

    public final double getX() {
        return xProperty().get();
    }

    public final void setX(double x) {
        xProperty().set(x);
    }

    public DoubleProperty yProperty() {
        return y ;
    }

    public final double getY() {
        return yProperty().get();
    }

    public final void setY(double y) {
        yProperty().set(y);
    }   

    public final boolean isUp() { return up ;}  
    public final void setUp(boolean up) { this.up = up ;}
    public final boolean isDown() { return down ; }
    public final void setDown(boolean down) { this.down = down ; }
    public final boolean isLeft() { return left; }
    public final void setLeft(boolean left) { this.left = left ;}   
    public final boolean isRight() { return right ; }
    public final void setRight(boolean right) { this.right = right ;}       

}

Вот класс контроллера, который регистрирует ключевых слушателей на Scene для обновления Player:

import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;

public class PlayerController {

    private final Player player ;
    private final KeyCode leftKey ;
    private final KeyCode rightKey ;
    private final KeyCode upKey ;
    private final KeyCode downKey ;

    public PlayerController(Player player, KeyCode leftKey, KeyCode rightKey, KeyCode upKey, KeyCode downKey) {
        this.player = player;
        this.leftKey = leftKey;
        this.rightKey = rightKey;
        this.upKey = upKey;
        this.downKey = downKey;
    }


    public void register(Scene scene) {
        scene.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
            if (e.getCode() == leftKey) player.setLeft(true);
            if (e.getCode() == rightKey) player.setRight(true);
            if (e.getCode() == upKey) player.setUp(true);
            if (e.getCode() == downKey) player.setDown(true);
        });
        scene.addEventHandler(KeyEvent.KEY_RELEASED, e -> {
            if (e.getCode() == leftKey) player.setLeft(false);
            if (e.getCode() == rightKey) player.setRight(false);
            if (e.getCode() == upKey) player.setUp(false);
            if (e.getCode() == downKey) player.setDown(false);
        });
    }
}

и простого представления, которое просто отображает игрока как Rectangle :

import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;

public class PlayerView {

    private final Rectangle view ;

    public PlayerView(Player player, Color color) {
        this.view = new Rectangle(50, 50);
        view.setFill(color);
        view.xProperty().bind(player.xProperty());
        view.yProperty().bind(player.yProperty());
    }

    public Node getView() {
        return view ;
    }
}

Наконец, класс приложения. Это создает двух игроков, каждый из которых имеет свой собственный набор клавиш (первый игрок: W = вверх, A = влево, Z = вниз, S = вправо; второй игрок: I = вверх, J = влево, M = вниз, K = правильно).

AnimationTimer просто вызывает метод move() для каждого игрока, передавая количество времени, прошедшее с момента последнего обновления.

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class Game extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {

        Pane gamePane = new Pane();
        gamePane.setPrefSize(800, 800);

        Scene scene = new Scene(gamePane);

        Player player1 = new Player(0, 750, 0, 750);
        player1.setX(200);
        player1.setY(400);
        gamePane.getChildren().add(new PlayerView(player1, Color.STEELBLUE).getView());
        new PlayerController(player1, KeyCode.A, KeyCode.S, KeyCode.W, KeyCode.Z).register(scene);

        Player player2 = new Player(0, 750, 0, 750);
        player2.setX(600);
        player2.setY(400);
        gamePane.getChildren().add(new PlayerView(player2, Color.CORAL).getView());
        new PlayerController(player2, KeyCode.J, KeyCode.K, KeyCode.I, KeyCode.M).register(scene);

        AnimationTimer gameLoop = new AnimationTimer() {

            private long lastUpdate = 0 ;

            @Override
            public void handle(long now) {

                // first iteration:
                if (lastUpdate == 0) {
                    lastUpdate = now ;
                    return ;
                }

                long elapsedNanoSeconds = now - lastUpdate ;
                double elapsedSeconds = elapsedNanoSeconds / 1_000_000_000.0 ;
                player1.move(elapsedSeconds);
                player2.move(elapsedSeconds);

                lastUpdate = now ;
            }
        };

        gameLoop.start();

        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        Application.launch(args);
    }
}

Здесь вы можете легко добавить другие объекты, которые являются «автономными», т. е. перемещаются через свои собственные логики c, а не через ввод использования.

Вот более обширный пример, который представляет собой симуляцию (поэтому все сущности являются автономными ).

...