In JavaFX how to move a sprite across the screen?

I'm new to JavaFX and trying to write a game in which an animated 2D character moves across the screen (for example, like the original Legend of Zelda game). I did this in Swing by creating my own Sprite class and overriding the paintComponent () method in Swing and placing my own g2d.drawImage (...); call where it will copy the correct subframe from the sprite to the correct destination x, y in the JPanel, thus obtaining the movement of an animated (walking) 2D image across the screen.

How to do it in JavaFX? I found this great example of how to create a sprite: http://blog.netopyr.com/2012/03/09/creating-a-sprite-animation-with-javafx/ in JavaFX, but how do I make the PaintComponent and drawImage components (...)? Does JavaFX have an equivalent PaintComponent method where everything is redrawn? I tried calling primaryStage.show (); method, but it did not work.

I think I don’t quite understand exactly how to set up my game master cycle, where the update starts, and the image is drawn on the scene in another place x, y?

What is equivalent to JavaFX drawImage (...) method? this method allows me to copy part of the source image to the destination scene at any given offset x, y. This is how I achieved moving the animated sprite on the screen in Swing.

Best wishes,

Zara

+5
source share
1 answer

Your question is too much to be included in SO. However, I created a simple β€œengine” to get started. This is a general purpose, therefore also suitable for your needs.

The main class with the game loop into which the game is loaded, the input is checked, sprites are moved, collisions are checked, the score is updated, etc.

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

The base class for sprites, which includes common methods such as moving, etc.

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

Subclasses of a sprite class such as a player ...

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

... and enemies

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

You also need an input mechanism to control the player’s sprite.

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

And some global settings

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

You can use any image for sprites. I took mine from Wikipedia:

player.png

enter image description here

enemy.png

enter image description here

If you put all this in a game package, you can run Game.java. This will give you a controlled emoticon with zombie emoticons that scroll down. You must evade them. I left the images without transparency for you to notice that I am using simple rectangle collision detection. You will probably go for pixel-collision detection.

It looks like this:

enter image description here

I am not saying that this is a solution, it is just a solution. For example, you will need to limit the animation timer. Or you can set the movement in seconds instead of each frame, etc.

If you need more information, you can check out my blog where I found myself How to create a 2D-Shoot'em'up with JavaFX . There you will also find e. d. how to add animated sprites (I also found out what you sent from the link), scrollable background, cloud layer on top of other layers, etc. Hope this helps you.

+21
source

Source: https://habr.com/ru/post/1215374/


All Articles