Обычно вам не нужны темы для чего-то подобного. Общая стратегия игры (или симуляции) в JavaFX:
- Определение классов для представления состояния игры / симуляции ("модель")
- Определение обработчиков событий, которые обновляются то же самое состояние на основе ввода usr (например, обработчики клавиш)
- Используйте
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, а не через ввод использования.
Вот более обширный пример, который представляет собой симуляцию (поэтому все сущности являются автономными ).