I still have a little bit of time left for today, so let's add something to the scenery: Clouds.
We add 2 layers:
- lower layer is beetween the background and the player ship
- upper layer is above the player ship
The upper layer would cover the player ship, but we want the player ship to be always visible. So we change the opacity of the images in the upper layer.
In my example I use 4 semi-transparent PNG files as cloud layer. Setting the position and opacity to random makes a bit more of diversity. We could also rotate the clouds, but we can do that always later.
Adding the images and layers is the usual process which we learned in the previous lessons.
The layers for now:
Pane backgroundLayer; Pane lowerCloudLayer; Pane playfieldLayer; Pane upperCloudLayer; Pane debugLayer; ... // create layers backgroundLayer = new Pane(); lowerCloudLayer = new Pane(); playfieldLayer = new Pane(); upperCloudLayer = new Pane(); debugLayer = new Pane(); ... // add layers to scene root root.getChildren().add( backgroundLayer); root.getChildren().add( lowerCloudLayer); root.getChildren().add( playfieldLayer); root.getChildren().add( upperCloudLayer); root.getChildren().add( debugLayer);Load the clouds into an array of images. Create a reference
// list of available cloud images Image clouds[];And load the images in our loadGame() method:
// clouds // -------------------------------- clouds = new Image[4]; clouds[0] = new Image( getClass().getResource( "assets/environment/cloud-01.png").toExternalForm()); clouds[1] = new Image( getClass().getResource( "assets/environment/cloud-02.png").toExternalForm()); clouds[2] = new Image( getClass().getResource( "assets/environment/cloud-03.png").toExternalForm()); clouds[3] = new Image( getClass().getResource( "assets/environment/cloud-04.png").toExternalForm());The clouds are supposed to be positioned randomly. For that use the Random class
Random rnd = new Random();We use randomness to determine
- if we should show a cloud at all
- if we should put it on the upper or the lower layer
- which cloud we should display
- the movement speed of the clouds
And since each cloud should have their own movement logic, it's time we introduce a class for this. Let's call it "Sprite". Here's the code which we put into the AnimationTimer's handle method:
// add random clouds // --------------------------- if( rnd.nextInt(100) == 0) { // determine random layer Pane layer; if( rnd.nextInt(2) == 0) { layer = lowerCloudLayer; } else { layer = upperCloudLayer; } // speed double speed = rnd.nextDouble() * 1.0 + 1.0; // determine random image Image image = clouds[ rnd.nextInt( clouds.length)]; // create sprite ImageView imgv = new ImageView( image); // clouds in the upper layer are less opaque than the ones in the lower layer so that the player ship is always visible // and the upper layer clouds are faster if( layer == upperCloudLayer) { imgv.setOpacity(0.5); speed +=1.0; } // create a sprite and position it horizontally so that half of it may be outside of the view, vertically so that it enters the view with the next movement Sprite sprite = new Sprite( Sprite.Type.CLOUD, layer, imgv, rnd.nextDouble() * SCENE_WIDTH - imgv.getImage().getWidth() / 2.0, -imgv.getImage().getHeight(), 0, speed); sprites.add( sprite); }For now the Sprite class is a very simple class which also holds the layer information. Basically that's the parent of the node. We add the sprite to the layer in the constructor. That may change later as we code along.
The class has a method move(). In it we simply advance the Sprite from position x/y by dx/dy. Clouds are moving downwards, so their dx will be 0 and the dy will be some random positive value.
package game; import javafx.scene.image.ImageView; import javafx.scene.layout.Pane; public class Sprite { Pane layer; ImageView imageView; double x; double y; double dx; double dy; double rotation; boolean alive = true; public Sprite( Pane layer, ImageView imageView, double x, double y, double dx, double dy) { this.layer = layer; this.imageView = imageView; this.x = x; this.y = y; this.dx = dx; this.dy = dy; layer.getChildren().add( imageView); imageView.relocate(x, y); } public Pane getLayer() { return layer; } public void setLayer(Pane layer) { this.layer = layer; } public ImageView getImageView() { return imageView; } public void setImageView(ImageView imageView) { this.imageView = imageView; } 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 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 void move() { x += dx; y += dy; } }We need to hold the Sprites in a list so that we can process them all at once for
- calculating their new position
- movement in the view
- removing them from the layer when they are outside of the view
We simply create a list for further references.
ListIn the AnimationTimer's handle code we iterate through all of the sprites and move them:sprites = new ArrayList ();
// move sprites // --------------------------- // move sprites internally for( Sprite sprite: sprites) { sprite.move(); } // move sprites on screen for( Sprite sprite: sprites) { sprite.getImageView().relocate( sprite.getX(), sprite.getY()); }We also need to remove the sprites once they're not visible anymore:
// check sprite visibility // remove every sprite that's not visible anymore // --------------------------- IteratorAnd we'd like to see the number of sprites on the screen, so we modify our debug information:iter = sprites.iterator(); while( iter.hasNext()) { Sprite sprite = iter.next(); // check lower screen bounds if( sprite.getY() > SCENE_HEIGHT) { iter.remove(); continue; } // check upper screen bounds if( (sprite.getY() + sprite.getImageView().getImage().getHeight()) < 0) { iter.remove(); continue; } // check right screen bounds if( sprite.getX() > SCENE_WIDTH) { iter.remove(); continue; } // check left screen bounds if( (sprite.getX() + sprite.getImageView().getImage().getWidth()) < 0) { iter.remove(); continue; } }
debugLabel.setText("FPS: " + fpsCurrent + "\nSprites: " + sprites.size());
Here's a screenshot about what we've achieved so far. Of course it looks better when it's animated.
I have to admit I'm really impressed with what JavaFX offers. Looking forward to adding enemies and heat seeking missiles!
That's it for today ... and keep on coding!
No comments:
Post a Comment