在 JavaFX 中如何在屏幕上移动精灵?
In JavaFX how do I move a sprite across the screen?
我是 JavaFX 的新手,正在尝试编写一个动画 2D 角色在屏幕上行走的游戏(例如原始的塞尔达传说游戏)。我在 Swing 中完成了此操作,方法是创建我自己的 Sprite class 并覆盖 Swing 中的 paintComponent() 方法,并将我自己的 g2d.drawImage(...);在那里调用,它将正确的子帧从 Sprite 复制到 JPanel 中正确的 x,y 目标,从而实现动画(行走)2D 图像在屏幕上的移动。
如何在 JavaFX 中执行此操作?我找到了这个关于如何创建精灵的优秀示例:
http://blog.netopyr.com/2012/03/09/creating-a-sprite-animation-with-javafx/
在 JavaFX 中,但我该如何处理 PaintComponent 和 drawImage(...) 部分? JavaFX 是否具有与重绘所有内容的 PaintComponent 等效的方法?
我尝试调用 primaryStage.show();方法,但没有用。
我想我不清楚如何准确设置我的主游戏循环,触发刷新并在场景中不同的 x,y 位置绘制图像?
与 drawImage(...) 方法等效的 JavaFX 是什么?这种方法允许我以任何给定的 x,y 偏移量将源图像的一部分复制到目标场景中。这就是我如何在 Swing 中实现在屏幕上移动动画精灵。
此致,
查雷
你的问题太多了,SO 无法涵盖。但是,我创建了一个简单的 "engine" 供您开始使用。它是通用的,因此也适合您的需求。
主要 class 游戏循环,其中加载游戏、检查输入、移动精灵、检查碰撞、更新分数等
public class Game extends Application {
Random rnd = new Random();
Pane playfieldLayer;
Pane scoreLayer;
Image playerImage;
Image enemyImage;
List<Player> players = new ArrayList<>();
List<Enemy> enemies = new ArrayList<>();
Text collisionText = new Text();
boolean collision = false;
Scene scene;
@Override
public void start(Stage primaryStage) {
Group root = new Group();
// create layers
playfieldLayer = new Pane();
scoreLayer = new Pane();
root.getChildren().add( playfieldLayer);
root.getChildren().add( scoreLayer);
scene = new Scene( root, Settings.SCENE_WIDTH, Settings.SCENE_HEIGHT);
primaryStage.setScene( scene);
primaryStage.show();
loadGame();
createScoreLayer();
createPlayers();
AnimationTimer gameLoop = new AnimationTimer() {
@Override
public void handle(long now) {
// player input
players.forEach(sprite -> sprite.processInput());
// add random enemies
spawnEnemies( true);
// movement
players.forEach(sprite -> sprite.move());
enemies.forEach(sprite -> sprite.move());
// check collisions
checkCollisions();
// update sprites in scene
players.forEach(sprite -> sprite.updateUI());
enemies.forEach(sprite -> sprite.updateUI());
// check if sprite can be removed
enemies.forEach(sprite -> sprite.checkRemovability());
// remove removables from list, layer, etc
removeSprites( enemies);
// update score, health, etc
updateScore();
}
};
gameLoop.start();
}
private void loadGame() {
playerImage = new Image( getClass().getResource("player.png").toExternalForm());
enemyImage = new Image( getClass().getResource("enemy.png").toExternalForm());
}
private void createScoreLayer() {
collisionText.setFont( Font.font( null, FontWeight.BOLD, 64));
collisionText.setStroke(Color.BLACK);
collisionText.setFill(Color.RED);
scoreLayer.getChildren().add( collisionText);
// TODO: quick-hack to ensure the text is centered; usually you don't have that; instead you have a health bar on top
collisionText.setText("Collision");
double x = (Settings.SCENE_WIDTH - collisionText.getBoundsInLocal().getWidth()) / 2;
double y = (Settings.SCENE_HEIGHT - collisionText.getBoundsInLocal().getHeight()) / 2;
collisionText.relocate(x, y);
collisionText.setText("");
collisionText.setBoundsType(TextBoundsType.VISUAL);
}
private void createPlayers() {
// player input
Input input = new Input( scene);
// register input listeners
input.addListeners(); // TODO: remove listeners on game over
Image image = playerImage;
// center horizontally, position at 70% vertically
double x = (Settings.SCENE_WIDTH - image.getWidth()) / 2.0;
double y = Settings.SCENE_HEIGHT * 0.7;
// create player
Player player = new Player(playfieldLayer, image, x, y, 0, 0, 0, 0, Settings.PLAYER_SHIP_HEALTH, 0, Settings.PLAYER_SHIP_SPEED, input);
// register player
players.add( player);
}
private void spawnEnemies( boolean random) {
if( random && rnd.nextInt(Settings.ENEMY_SPAWN_RANDOMNESS) != 0) {
return;
}
// image
Image image = enemyImage;
// random speed
double speed = rnd.nextDouble() * 1.0 + 2.0;
// x position range: enemy is always fully inside the screen, no part of it is outside
// y position: right on top of the view, so that it becomes visible with the next game iteration
double x = rnd.nextDouble() * (Settings.SCENE_WIDTH - image.getWidth());
double y = -image.getHeight();
// create a sprite
Enemy enemy = new Enemy( playfieldLayer, image, x, y, 0, 0, speed, 0, 1,1);
// manage sprite
enemies.add( enemy);
}
private void removeSprites( List<? extends SpriteBase> spriteList) {
Iterator<? extends SpriteBase> iter = spriteList.iterator();
while( iter.hasNext()) {
SpriteBase sprite = iter.next();
if( sprite.isRemovable()) {
// remove from layer
sprite.removeFromLayer();
// remove from list
iter.remove();
}
}
}
private void checkCollisions() {
collision = false;
for( Player player: players) {
for( Enemy enemy: enemies) {
if( player.collidesWith(enemy)) {
collision = true;
}
}
}
}
private void updateScore() {
if( collision) {
collisionText.setText("Collision");
} else {
collisionText.setText("");
}
}
public static void main(String[] args) {
launch(args);
}
}
精灵的基础class,包括移动等常用方法
public abstract class SpriteBase {
Image image;
ImageView imageView;
Pane layer;
double x;
double y;
double r;
double dx;
double dy;
double dr;
double health;
double damage;
boolean removable = false;
double w;
double h;
boolean canMove = true;
public SpriteBase(Pane layer, Image image, double x, double y, double r, double dx, double dy, double dr, double health, double damage) {
this.layer = layer;
this.image = image;
this.x = x;
this.y = y;
this.r = r;
this.dx = dx;
this.dy = dy;
this.dr = dr;
this.health = health;
this.damage = damage;
this.imageView = new ImageView(image);
this.imageView.relocate(x, y);
this.imageView.setRotate(r);
this.w = image.getWidth(); // imageView.getBoundsInParent().getWidth();
this.h = image.getHeight(); // imageView.getBoundsInParent().getHeight();
addToLayer();
}
public void addToLayer() {
this.layer.getChildren().add(this.imageView);
}
public void removeFromLayer() {
this.layer.getChildren().remove(this.imageView);
}
public Pane getLayer() {
return layer;
}
public void setLayer(Pane layer) {
this.layer = layer;
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public double getR() {
return r;
}
public void setR(double r) {
this.r = r;
}
public double getDx() {
return dx;
}
public void setDx(double dx) {
this.dx = dx;
}
public double getDy() {
return dy;
}
public void setDy(double dy) {
this.dy = dy;
}
public double getDr() {
return dr;
}
public void setDr(double dr) {
this.dr = dr;
}
public double getHealth() {
return health;
}
public double getDamage() {
return damage;
}
public void setDamage(double damage) {
this.damage = damage;
}
public void setHealth(double health) {
this.health = health;
}
public boolean isRemovable() {
return removable;
}
public void setRemovable(boolean removable) {
this.removable = removable;
}
public void move() {
if( !canMove)
return;
x += dx;
y += dy;
r += dr;
}
public boolean isAlive() {
return Double.compare(health, 0) > 0;
}
public ImageView getView() {
return imageView;
}
public void updateUI() {
imageView.relocate(x, y);
imageView.setRotate(r);
}
public double getWidth() {
return w;
}
public double getHeight() {
return h;
}
public double getCenterX() {
return x + w * 0.5;
}
public double getCenterY() {
return y + h * 0.5;
}
// TODO: per-pixel-collision
public boolean collidesWith( SpriteBase otherSprite) {
return ( otherSprite.x + otherSprite.w >= x && otherSprite.y + otherSprite.h >= y && otherSprite.x <= x + w && otherSprite.y <= y + h);
}
/**
* Reduce health by the amount of damage that the given sprite can inflict
* @param sprite
*/
public void getDamagedBy( SpriteBase sprite) {
health -= sprite.getDamage();
}
/**
* Set health to 0
*/
public void kill() {
setHealth( 0);
}
/**
* Set flag that the sprite can be removed from the UI.
*/
public void remove() {
setRemovable(true);
}
/**
* Set flag that the sprite can't move anymore.
*/
public void stopMovement() {
this.canMove = false;
}
public abstract void checkRemovability();
}
精灵的子classes class 喜欢玩家...
public class Player extends SpriteBase {
double playerShipMinX;
double playerShipMaxX;
double playerShipMinY;
double playerShipMaxY;
Input input;
double speed;
public Player(Pane layer, Image image, double x, double y, double r, double dx, double dy, double dr, double health, double damage, double speed, Input input) {
super(layer, image, x, y, r, dx, dy, dr, health, damage);
this.speed = speed;
this.input = input;
init();
}
private void init() {
// calculate movement bounds of the player ship
// allow half of the ship to be outside of the screen
playerShipMinX = 0 - image.getWidth() / 2.0;
playerShipMaxX = Settings.SCENE_WIDTH - image.getWidth() / 2.0;
playerShipMinY = 0 - image.getHeight() / 2.0;
playerShipMaxY = Settings.SCENE_HEIGHT -image.getHeight() / 2.0;
}
public void processInput() {
// ------------------------------------
// movement
// ------------------------------------
// vertical direction
if( input.isMoveUp()) {
dy = -speed;
} else if( input.isMoveDown()) {
dy = speed;
} else {
dy = 0d;
}
// horizontal direction
if( input.isMoveLeft()) {
dx = -speed;
} else if( input.isMoveRight()) {
dx = speed;
} else {
dx = 0d;
}
}
@Override
public void move() {
super.move();
// ensure the ship can't move outside of the screen
checkBounds();
}
private void checkBounds() {
// vertical
if( Double.compare( y, playerShipMinY) < 0) {
y = playerShipMinY;
} else if( Double.compare(y, playerShipMaxY) > 0) {
y = playerShipMaxY;
}
// horizontal
if( Double.compare( x, playerShipMinX) < 0) {
x = playerShipMinX;
} else if( Double.compare(x, playerShipMaxX) > 0) {
x = playerShipMaxX;
}
}
@Override
public void checkRemovability() {
// TODO Auto-generated method stub
}
}
...和敌人
public class Enemy extends SpriteBase {
public Enemy(Pane layer, Image image, double x, double y, double r, double dx, double dy, double dr, double health, double damage) {
super(layer, image, x, y, r, dx, dy, dr, health, damage);
}
@Override
public void checkRemovability() {
if( Double.compare( getY(), Settings.SCENE_HEIGHT) > 0) {
setRemovable(true);
}
}
}
你还需要一个输入机制来控制玩家精灵
public class Input {
/**
* Bitset which registers if any {@link KeyCode} keeps being pressed or if it is released.
*/
private BitSet keyboardBitSet = new BitSet();
// -------------------------------------------------
// default key codes
// will vary when you let the user customize the key codes or when you add support for a 2nd player
// -------------------------------------------------
private KeyCode upKey = KeyCode.UP;
private KeyCode downKey = KeyCode.DOWN;
private KeyCode leftKey = KeyCode.LEFT;
private KeyCode rightKey = KeyCode.RIGHT;
private KeyCode primaryWeaponKey = KeyCode.SPACE;
private KeyCode secondaryWeaponKey = KeyCode.CONTROL;
Scene scene;
public Input( Scene scene) {
this.scene = scene;
}
public void addListeners() {
scene.addEventFilter(KeyEvent.KEY_PRESSED, keyPressedEventHandler);
scene.addEventFilter(KeyEvent.KEY_RELEASED, keyReleasedEventHandler);
}
public void removeListeners() {
scene.removeEventFilter(KeyEvent.KEY_PRESSED, keyPressedEventHandler);
scene.removeEventFilter(KeyEvent.KEY_RELEASED, keyReleasedEventHandler);
}
/**
* "Key Pressed" handler for all input events: register pressed key in the bitset
*/
private EventHandler<KeyEvent> keyPressedEventHandler = new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
// register key down
keyboardBitSet.set(event.getCode().ordinal(), true);
}
};
/**
* "Key Released" handler for all input events: unregister released key in the bitset
*/
private EventHandler<KeyEvent> keyReleasedEventHandler = new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
// register key up
keyboardBitSet.set(event.getCode().ordinal(), false);
}
};
// -------------------------------------------------
// Evaluate bitset of pressed keys and return the player input.
// If direction and its opposite direction are pressed simultaneously, then the direction isn't handled.
// -------------------------------------------------
public boolean isMoveUp() {
return keyboardBitSet.get( upKey.ordinal()) && !keyboardBitSet.get( downKey.ordinal());
}
public boolean isMoveDown() {
return keyboardBitSet.get( downKey.ordinal()) && !keyboardBitSet.get( upKey.ordinal());
}
public boolean isMoveLeft() {
return keyboardBitSet.get( leftKey.ordinal()) && !keyboardBitSet.get( rightKey.ordinal());
}
public boolean isMoveRight() {
return keyboardBitSet.get( rightKey.ordinal()) && !keyboardBitSet.get( leftKey.ordinal());
}
public boolean isFirePrimaryWeapon() {
return keyboardBitSet.get( primaryWeaponKey.ordinal());
}
public boolean isFireSecondaryWeapon() {
return keyboardBitSet.get( secondaryWeaponKey.ordinal());
}
}
以及一些全局设置
public class Settings {
public static double SCENE_WIDTH = 400;
public static double SCENE_HEIGHT = 800;
public static double PLAYER_SHIP_SPEED = 4.0;
public static double PLAYER_SHIP_HEALTH = 100.0;
public static double PLAYER_MISSILE_SPEED = 4.0;
public static double PLAYER_MISSILE_HEALTH = 200.0;
public static int ENEMY_SPAWN_RANDOMNESS = 100;
}
您可以为精灵使用任何图像。我从维基百科获取了我的:
player.png
enemy.png
如果全部放入游戏包中,就可以启动Game.java。它会给你一个可控制的笑脸,带有向下滚动的僵尸笑脸。你必须避开他们。我为您保留了不透明的图像,以便您会注意到我使用了简单的矩形碰撞检测。您可能会进行逐像素碰撞检测。
看起来像这样:
我并不是说这是解决方案,它只是一个解决方案。例如,您必须限制动画计时器。或者您可能希望设置每秒移动而不是每帧等。
如果您想了解更多信息,请随时查看我发现自己的博客 How to create a 2D Shoot'em'up with JavaFX。在那里您还会找到 e。 G。如何添加动画精灵(我也从您发布的 link 中了解到)、滚动背景、其他图层之上的云层等。希望对您有所帮助。
我是 JavaFX 的新手,正在尝试编写一个动画 2D 角色在屏幕上行走的游戏(例如原始的塞尔达传说游戏)。我在 Swing 中完成了此操作,方法是创建我自己的 Sprite class 并覆盖 Swing 中的 paintComponent() 方法,并将我自己的 g2d.drawImage(...);在那里调用,它将正确的子帧从 Sprite 复制到 JPanel 中正确的 x,y 目标,从而实现动画(行走)2D 图像在屏幕上的移动。
如何在 JavaFX 中执行此操作?我找到了这个关于如何创建精灵的优秀示例: http://blog.netopyr.com/2012/03/09/creating-a-sprite-animation-with-javafx/ 在 JavaFX 中,但我该如何处理 PaintComponent 和 drawImage(...) 部分? JavaFX 是否具有与重绘所有内容的 PaintComponent 等效的方法? 我尝试调用 primaryStage.show();方法,但没有用。
我想我不清楚如何准确设置我的主游戏循环,触发刷新并在场景中不同的 x,y 位置绘制图像?
与 drawImage(...) 方法等效的 JavaFX 是什么?这种方法允许我以任何给定的 x,y 偏移量将源图像的一部分复制到目标场景中。这就是我如何在 Swing 中实现在屏幕上移动动画精灵。
此致,
查雷
你的问题太多了,SO 无法涵盖。但是,我创建了一个简单的 "engine" 供您开始使用。它是通用的,因此也适合您的需求。
主要 class 游戏循环,其中加载游戏、检查输入、移动精灵、检查碰撞、更新分数等
public class Game extends Application {
Random rnd = new Random();
Pane playfieldLayer;
Pane scoreLayer;
Image playerImage;
Image enemyImage;
List<Player> players = new ArrayList<>();
List<Enemy> enemies = new ArrayList<>();
Text collisionText = new Text();
boolean collision = false;
Scene scene;
@Override
public void start(Stage primaryStage) {
Group root = new Group();
// create layers
playfieldLayer = new Pane();
scoreLayer = new Pane();
root.getChildren().add( playfieldLayer);
root.getChildren().add( scoreLayer);
scene = new Scene( root, Settings.SCENE_WIDTH, Settings.SCENE_HEIGHT);
primaryStage.setScene( scene);
primaryStage.show();
loadGame();
createScoreLayer();
createPlayers();
AnimationTimer gameLoop = new AnimationTimer() {
@Override
public void handle(long now) {
// player input
players.forEach(sprite -> sprite.processInput());
// add random enemies
spawnEnemies( true);
// movement
players.forEach(sprite -> sprite.move());
enemies.forEach(sprite -> sprite.move());
// check collisions
checkCollisions();
// update sprites in scene
players.forEach(sprite -> sprite.updateUI());
enemies.forEach(sprite -> sprite.updateUI());
// check if sprite can be removed
enemies.forEach(sprite -> sprite.checkRemovability());
// remove removables from list, layer, etc
removeSprites( enemies);
// update score, health, etc
updateScore();
}
};
gameLoop.start();
}
private void loadGame() {
playerImage = new Image( getClass().getResource("player.png").toExternalForm());
enemyImage = new Image( getClass().getResource("enemy.png").toExternalForm());
}
private void createScoreLayer() {
collisionText.setFont( Font.font( null, FontWeight.BOLD, 64));
collisionText.setStroke(Color.BLACK);
collisionText.setFill(Color.RED);
scoreLayer.getChildren().add( collisionText);
// TODO: quick-hack to ensure the text is centered; usually you don't have that; instead you have a health bar on top
collisionText.setText("Collision");
double x = (Settings.SCENE_WIDTH - collisionText.getBoundsInLocal().getWidth()) / 2;
double y = (Settings.SCENE_HEIGHT - collisionText.getBoundsInLocal().getHeight()) / 2;
collisionText.relocate(x, y);
collisionText.setText("");
collisionText.setBoundsType(TextBoundsType.VISUAL);
}
private void createPlayers() {
// player input
Input input = new Input( scene);
// register input listeners
input.addListeners(); // TODO: remove listeners on game over
Image image = playerImage;
// center horizontally, position at 70% vertically
double x = (Settings.SCENE_WIDTH - image.getWidth()) / 2.0;
double y = Settings.SCENE_HEIGHT * 0.7;
// create player
Player player = new Player(playfieldLayer, image, x, y, 0, 0, 0, 0, Settings.PLAYER_SHIP_HEALTH, 0, Settings.PLAYER_SHIP_SPEED, input);
// register player
players.add( player);
}
private void spawnEnemies( boolean random) {
if( random && rnd.nextInt(Settings.ENEMY_SPAWN_RANDOMNESS) != 0) {
return;
}
// image
Image image = enemyImage;
// random speed
double speed = rnd.nextDouble() * 1.0 + 2.0;
// x position range: enemy is always fully inside the screen, no part of it is outside
// y position: right on top of the view, so that it becomes visible with the next game iteration
double x = rnd.nextDouble() * (Settings.SCENE_WIDTH - image.getWidth());
double y = -image.getHeight();
// create a sprite
Enemy enemy = new Enemy( playfieldLayer, image, x, y, 0, 0, speed, 0, 1,1);
// manage sprite
enemies.add( enemy);
}
private void removeSprites( List<? extends SpriteBase> spriteList) {
Iterator<? extends SpriteBase> iter = spriteList.iterator();
while( iter.hasNext()) {
SpriteBase sprite = iter.next();
if( sprite.isRemovable()) {
// remove from layer
sprite.removeFromLayer();
// remove from list
iter.remove();
}
}
}
private void checkCollisions() {
collision = false;
for( Player player: players) {
for( Enemy enemy: enemies) {
if( player.collidesWith(enemy)) {
collision = true;
}
}
}
}
private void updateScore() {
if( collision) {
collisionText.setText("Collision");
} else {
collisionText.setText("");
}
}
public static void main(String[] args) {
launch(args);
}
}
精灵的基础class,包括移动等常用方法
public abstract class SpriteBase {
Image image;
ImageView imageView;
Pane layer;
double x;
double y;
double r;
double dx;
double dy;
double dr;
double health;
double damage;
boolean removable = false;
double w;
double h;
boolean canMove = true;
public SpriteBase(Pane layer, Image image, double x, double y, double r, double dx, double dy, double dr, double health, double damage) {
this.layer = layer;
this.image = image;
this.x = x;
this.y = y;
this.r = r;
this.dx = dx;
this.dy = dy;
this.dr = dr;
this.health = health;
this.damage = damage;
this.imageView = new ImageView(image);
this.imageView.relocate(x, y);
this.imageView.setRotate(r);
this.w = image.getWidth(); // imageView.getBoundsInParent().getWidth();
this.h = image.getHeight(); // imageView.getBoundsInParent().getHeight();
addToLayer();
}
public void addToLayer() {
this.layer.getChildren().add(this.imageView);
}
public void removeFromLayer() {
this.layer.getChildren().remove(this.imageView);
}
public Pane getLayer() {
return layer;
}
public void setLayer(Pane layer) {
this.layer = layer;
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public double getR() {
return r;
}
public void setR(double r) {
this.r = r;
}
public double getDx() {
return dx;
}
public void setDx(double dx) {
this.dx = dx;
}
public double getDy() {
return dy;
}
public void setDy(double dy) {
this.dy = dy;
}
public double getDr() {
return dr;
}
public void setDr(double dr) {
this.dr = dr;
}
public double getHealth() {
return health;
}
public double getDamage() {
return damage;
}
public void setDamage(double damage) {
this.damage = damage;
}
public void setHealth(double health) {
this.health = health;
}
public boolean isRemovable() {
return removable;
}
public void setRemovable(boolean removable) {
this.removable = removable;
}
public void move() {
if( !canMove)
return;
x += dx;
y += dy;
r += dr;
}
public boolean isAlive() {
return Double.compare(health, 0) > 0;
}
public ImageView getView() {
return imageView;
}
public void updateUI() {
imageView.relocate(x, y);
imageView.setRotate(r);
}
public double getWidth() {
return w;
}
public double getHeight() {
return h;
}
public double getCenterX() {
return x + w * 0.5;
}
public double getCenterY() {
return y + h * 0.5;
}
// TODO: per-pixel-collision
public boolean collidesWith( SpriteBase otherSprite) {
return ( otherSprite.x + otherSprite.w >= x && otherSprite.y + otherSprite.h >= y && otherSprite.x <= x + w && otherSprite.y <= y + h);
}
/**
* Reduce health by the amount of damage that the given sprite can inflict
* @param sprite
*/
public void getDamagedBy( SpriteBase sprite) {
health -= sprite.getDamage();
}
/**
* Set health to 0
*/
public void kill() {
setHealth( 0);
}
/**
* Set flag that the sprite can be removed from the UI.
*/
public void remove() {
setRemovable(true);
}
/**
* Set flag that the sprite can't move anymore.
*/
public void stopMovement() {
this.canMove = false;
}
public abstract void checkRemovability();
}
精灵的子classes class 喜欢玩家...
public class Player extends SpriteBase {
double playerShipMinX;
double playerShipMaxX;
double playerShipMinY;
double playerShipMaxY;
Input input;
double speed;
public Player(Pane layer, Image image, double x, double y, double r, double dx, double dy, double dr, double health, double damage, double speed, Input input) {
super(layer, image, x, y, r, dx, dy, dr, health, damage);
this.speed = speed;
this.input = input;
init();
}
private void init() {
// calculate movement bounds of the player ship
// allow half of the ship to be outside of the screen
playerShipMinX = 0 - image.getWidth() / 2.0;
playerShipMaxX = Settings.SCENE_WIDTH - image.getWidth() / 2.0;
playerShipMinY = 0 - image.getHeight() / 2.0;
playerShipMaxY = Settings.SCENE_HEIGHT -image.getHeight() / 2.0;
}
public void processInput() {
// ------------------------------------
// movement
// ------------------------------------
// vertical direction
if( input.isMoveUp()) {
dy = -speed;
} else if( input.isMoveDown()) {
dy = speed;
} else {
dy = 0d;
}
// horizontal direction
if( input.isMoveLeft()) {
dx = -speed;
} else if( input.isMoveRight()) {
dx = speed;
} else {
dx = 0d;
}
}
@Override
public void move() {
super.move();
// ensure the ship can't move outside of the screen
checkBounds();
}
private void checkBounds() {
// vertical
if( Double.compare( y, playerShipMinY) < 0) {
y = playerShipMinY;
} else if( Double.compare(y, playerShipMaxY) > 0) {
y = playerShipMaxY;
}
// horizontal
if( Double.compare( x, playerShipMinX) < 0) {
x = playerShipMinX;
} else if( Double.compare(x, playerShipMaxX) > 0) {
x = playerShipMaxX;
}
}
@Override
public void checkRemovability() {
// TODO Auto-generated method stub
}
}
...和敌人
public class Enemy extends SpriteBase {
public Enemy(Pane layer, Image image, double x, double y, double r, double dx, double dy, double dr, double health, double damage) {
super(layer, image, x, y, r, dx, dy, dr, health, damage);
}
@Override
public void checkRemovability() {
if( Double.compare( getY(), Settings.SCENE_HEIGHT) > 0) {
setRemovable(true);
}
}
}
你还需要一个输入机制来控制玩家精灵
public class Input {
/**
* Bitset which registers if any {@link KeyCode} keeps being pressed or if it is released.
*/
private BitSet keyboardBitSet = new BitSet();
// -------------------------------------------------
// default key codes
// will vary when you let the user customize the key codes or when you add support for a 2nd player
// -------------------------------------------------
private KeyCode upKey = KeyCode.UP;
private KeyCode downKey = KeyCode.DOWN;
private KeyCode leftKey = KeyCode.LEFT;
private KeyCode rightKey = KeyCode.RIGHT;
private KeyCode primaryWeaponKey = KeyCode.SPACE;
private KeyCode secondaryWeaponKey = KeyCode.CONTROL;
Scene scene;
public Input( Scene scene) {
this.scene = scene;
}
public void addListeners() {
scene.addEventFilter(KeyEvent.KEY_PRESSED, keyPressedEventHandler);
scene.addEventFilter(KeyEvent.KEY_RELEASED, keyReleasedEventHandler);
}
public void removeListeners() {
scene.removeEventFilter(KeyEvent.KEY_PRESSED, keyPressedEventHandler);
scene.removeEventFilter(KeyEvent.KEY_RELEASED, keyReleasedEventHandler);
}
/**
* "Key Pressed" handler for all input events: register pressed key in the bitset
*/
private EventHandler<KeyEvent> keyPressedEventHandler = new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
// register key down
keyboardBitSet.set(event.getCode().ordinal(), true);
}
};
/**
* "Key Released" handler for all input events: unregister released key in the bitset
*/
private EventHandler<KeyEvent> keyReleasedEventHandler = new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
// register key up
keyboardBitSet.set(event.getCode().ordinal(), false);
}
};
// -------------------------------------------------
// Evaluate bitset of pressed keys and return the player input.
// If direction and its opposite direction are pressed simultaneously, then the direction isn't handled.
// -------------------------------------------------
public boolean isMoveUp() {
return keyboardBitSet.get( upKey.ordinal()) && !keyboardBitSet.get( downKey.ordinal());
}
public boolean isMoveDown() {
return keyboardBitSet.get( downKey.ordinal()) && !keyboardBitSet.get( upKey.ordinal());
}
public boolean isMoveLeft() {
return keyboardBitSet.get( leftKey.ordinal()) && !keyboardBitSet.get( rightKey.ordinal());
}
public boolean isMoveRight() {
return keyboardBitSet.get( rightKey.ordinal()) && !keyboardBitSet.get( leftKey.ordinal());
}
public boolean isFirePrimaryWeapon() {
return keyboardBitSet.get( primaryWeaponKey.ordinal());
}
public boolean isFireSecondaryWeapon() {
return keyboardBitSet.get( secondaryWeaponKey.ordinal());
}
}
以及一些全局设置
public class Settings {
public static double SCENE_WIDTH = 400;
public static double SCENE_HEIGHT = 800;
public static double PLAYER_SHIP_SPEED = 4.0;
public static double PLAYER_SHIP_HEALTH = 100.0;
public static double PLAYER_MISSILE_SPEED = 4.0;
public static double PLAYER_MISSILE_HEALTH = 200.0;
public static int ENEMY_SPAWN_RANDOMNESS = 100;
}
您可以为精灵使用任何图像。我从维基百科获取了我的:
player.png
enemy.png
如果全部放入游戏包中,就可以启动Game.java。它会给你一个可控制的笑脸,带有向下滚动的僵尸笑脸。你必须避开他们。我为您保留了不透明的图像,以便您会注意到我使用了简单的矩形碰撞检测。您可能会进行逐像素碰撞检测。
看起来像这样:
我并不是说这是解决方案,它只是一个解决方案。例如,您必须限制动画计时器。或者您可能希望设置每秒移动而不是每帧等。
如果您想了解更多信息,请随时查看我发现自己的博客 How to create a 2D Shoot'em'up with JavaFX。在那里您还会找到 e。 G。如何添加动画精灵(我也从您发布的 link 中了解到)、滚动背景、其他图层之上的云层等。希望对您有所帮助。