2015-03-15 24 views
5

Sono nuovo di JavaFX e sto provando a scrivere un gioco in cui un personaggio 2D animato attraversa lo schermo (ad esempio, come nel gioco originale di Legend of Zelda). Lo avevo fatto in Swing, creando la mia classe Sprite e sovrascrivendo il metodo paintComponent() in Swing e inserendo il mio g2d.drawImage (...); chiama qui, dove copia il sottoframe corretto da uno Sprite alla destinazione x, y corretta in un JPanel, ottenendo così il movimento di un'immagine 2D animata (a piedi) attraverso lo schermo.In JavaFX come si sposta uno sprite sullo schermo?

Come posso farlo in JavaFX? Ho trovato questo eccellente esempio su come creare uno sprite: http://blog.netopyr.com/2012/03/09/creating-a-sprite-animation-with-javafx/ in JavaFX, ma come faccio a fare le parti PaintComponent e drawImage (...)? JavaFX ha un metodo equivalente a PaintComponent in cui tutto viene ridisegnato? Ho provato a chiamare il primaryStage.show(); metodo, ma non ha funzionato.

Immagino di non essere chiaro su come impostare esattamente il mio ciclo di gioco principale, dove viene attivato un aggiornamento e un'immagine viene dipinta sulla scena in una posizione x, y diversa?

Qual è l'equivalente JavaFX del metodo drawImage (...)? questo metodo mi consente di copiare una parte di un'immagine sorgente in una scena di destinazione in ogni dato x, y offset. È così che ho realizzato lo spostamento di uno sprite animato sullo schermo di Swing.

migliori saluti,

Zareh

+2

Demo di base per [spostamento di un personaggio attorno a uno schermo] (http://www.coderanch.com/t/626468/JavaFX/java/Key-Combinations) basato su [AnimationTimer] (http: // docs .oracle.com/JavaSE/8/javafx/api/javafx/animazione/AnimationTimer.html). – jewelsea

+0

Grazie mille! Ho eseguito il codice e ha funzionato perfettamente. Molto apprezzato. – Zareh

risposta

20

La tua domanda è troppo per essere coperti su SO. Tuttavia ho creato un semplice "motore" per iniziare. È di uso generale, quindi adatto anche alle tue esigenze.

La classe principale con il ciclo di gioco in cui il gioco viene caricato, in ingresso viene controllato, sprite vengono spostati, collisione è selezionata, il punteggio viene aggiornato ecc

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

} 

Una classe di base per gli sprite, che comprende i metodi comuni come il movimento, ecc

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

} 

sottoclassi della classe sprite come giocatore ...

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 
    } 

} 

... e nemici

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


    } 
} 

È inoltre necessario un meccanismo di ingresso per controllare il lettore 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()); 
    } 

} 

e alcune impostazioni globali

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; 

} 

È possibile utilizzare qualsiasi immagine per la sprite. Ho preso la mia da Wikipedia:

player.png

enter image description here

enemy.png

enter image description here

Se si mette tutto in un pacchetto di gioco, è possibile iniziare il gioco. Giava.Ti darà uno smiley controllabile con le faccine di zombie che scorrono verso il basso. Devi evitarli. Ho lasciato le immagini senza trasparenza per te in modo che noterai che utilizzo un semplice rilevamento delle collisioni rettangolari. Probabilmente andrai per un rilevamento di collisione per pixel.

Ecco come si presenta:

enter image description here

Non ho la pretesa che questo sia la soluzione, è solo una soluzione . Ad esempio dovresti limitare il timer dell'animazione. Oppure potresti decidere di impostare il movimento per secondi anziché per fotogramma, ecc.

Se desideri maggiori informazioni, non esitare a controllare il mio blog in cui mi scopro How to create a 2D Shoot'em'up with JavaFX. Lì troverai anche e. g. come aggiungere sprite animate (ho anche imparato che dal link che hai postato), uno sfondo a scorrimento, un livello cloud sopra gli altri livelli, ecc. Spero che ti aiuti.

+3

Wow! Grazie mille per la risposta dettagliata! Sicuramente ho l'idea di guardare il tuo codice. Molto apprezzato. – Zareh