Sunday, January 18, 2015

Anansi: The Story So Far ...


We've achieved quite a lot in these few hours of happy coding. I have to admit that i'm still very impressed with JavaFX. That's a lot of functionality for this relatively little amount of code.



Here's what we got so far:

Main.java
package game;
 
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;


public class Main extends Application {
 
 private double SCENE_WIDTH = 400;
 private double SCENE_HEIGHT = 800;
 
 Random rnd = new Random();
 
 private AnimationTimer gameLoop;

 ImageView backgroundImageView;
 ImageView playerShip;
 Image playerBullet;
 
 double playerShipSpeed = 4.0;
 double playerShipDeltaX = 0.0;
 double playerShipDeltaY = 0.0;
 
 double backgroundScrollSpeed = 0.6;
 
 Pane backgroundLayer;
 Pane lowerCloudLayer;
 Pane bulletLayer;
 Pane playfieldLayer;
 Pane upperCloudLayer;
 Pane debugLayer;
 
 Label debugLabel;
 
 // note: ordinal may be not appropriate, but we don't have a method in keycode to get the int code, so it'll have to do
 BitSet keyboardBitSet = new BitSet();
 
 // counter for game loop
 int frameCount = 0;
 int fpsCurrent = 0;
 long prevTime = -1;
 
 // list of available cloud images
 Image clouds[];
 
 List sprites = new ArrayList();
 
 double cannonChargeTime = 6; // the cannon can fire every n frames 
 double cannonChargeCounter = cannonChargeTime; // initially the cannon is charged
 double cannonChargeCounterDelta = 1; // counter is increased by this value each frame 
 
 double cannonBullets = 5; // number of bullets which the cannon can fire in 1 shot (center, left, right)
 double cannonBulletSpread = 0.6; // dx of left and right bullets
 double cannonBulletSpeed = 8.0; // speed of each bullet
 
 @Override
 public void start(Stage primaryStage) {
  try {

   // create root node
   Group root = new Group();

   // create layers
   backgroundLayer = new Pane();
   lowerCloudLayer = new Pane();
   bulletLayer = 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( bulletLayer);
   root.getChildren().add( playfieldLayer);
   root.getChildren().add( upperCloudLayer);
   root.getChildren().add( debugLayer);
   
   // create scene
   Scene scene = new Scene( root, SCENE_WIDTH,SCENE_HEIGHT);
   
   // show stage
   primaryStage.setScene(scene);
   primaryStage.show();
   
   // load game assets
   loadGame();

   // add nodes which display debug information
   addDebugInformation();

   // keyboard control
   addInputControls( scene);
   
   // start the game
   startGameLoop();
   
  } catch(Exception e) {
   e.printStackTrace();
  }
 }
 
 private void addDebugInformation() {
  
  debugLabel = new Label();
  debugLabel.setTextFill(Color.RED);
  debugLayer.getChildren().add( debugLabel);
  
 }

 private void addInputControls( Scene scene) {
  
  // keyboard handler: key pressed
  scene.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler() {
   @Override
   public void handle(KeyEvent event) {
    
    keyboardBitSet.set(event.getCode().ordinal(), true);
    
   }
  });
  
        // keyboard handler: key up
  scene.addEventFilter(KeyEvent.KEY_RELEASED, new EventHandler() {
   @Override
   public void handle(KeyEvent event) {
    
    keyboardBitSet.set(event.getCode().ordinal(), false);
    
   }
  });
  
 }
 
 private void loadGame() {
  
  // background
  // --------------------------------
  backgroundImageView = new ImageView( getClass().getResource( "assets/maps/canyon.jpg").toExternalForm());
  
  // reposition the map. it is scrolling from bottom of the background to top of the background
  backgroundImageView.relocate( 0, -backgroundImageView.getImage().getHeight() + SCENE_HEIGHT);
  
  // add background to layer
  backgroundLayer.getChildren().add( backgroundImageView);
  
  // player ship
  // --------------------------------
  playerShip = new ImageView( getClass().getResource( "assets/vehicles/anansi.png").toExternalForm());
  
  // center horizontally, position at 70% vertically
  playerShip.relocate( (SCENE_WIDTH - playerShip.getImage().getWidth()) / 2.0, SCENE_HEIGHT * 0.7);
  
  // add to playfield layer
  playfieldLayer.getChildren().add( playerShip);
  
  // 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());
  
  // bullets
  playerBullet = new Image( getClass().getResource( "assets/bullets/bullet_01.png").toExternalForm());
 }
 
 private void startGameLoop() {

  // calculate movement bounds of the player ship
  // allow half of the ship to be outside of the screen 
  double playerShipMinX = 0 - playerShip.getImage().getWidth() / 2.0;
  double playerShipMaxX = SCENE_WIDTH - playerShip.getImage().getWidth() / 2.0;
  double playerShipMinY = 0 - playerShip.getImage().getHeight() / 2.0;
  double playerShipMaxY = SCENE_HEIGHT - playerShip.getImage().getHeight() / 2.0;
  
  // game loop
        gameLoop = new AnimationTimer() {
         
            @Override
            public void handle(long l) {
            
             // get keyboard input
             // ---------------------------
             // note: ordinal may be not appropriate, but we don't have an method in keycode to get the int code, so it'll have to do

             // evaluate keyboard events
             boolean isUpPressed = keyboardBitSet.get(KeyCode.UP.ordinal()); 
             boolean isDownPressed = keyboardBitSet.get(KeyCode.DOWN.ordinal()); 
             boolean isLeftPressed = keyboardBitSet.get(KeyCode.LEFT.ordinal()); 
             boolean isRightPressed = keyboardBitSet.get(KeyCode.RIGHT.ordinal());
             boolean isSpacePressed = keyboardBitSet.get(KeyCode.SPACE.ordinal());
             boolean isControlPressed = keyboardBitSet.get(KeyCode.CONTROL.ordinal());
             
             // vertical direction
             if( isUpPressed && !isDownPressed) {
              playerShipDeltaY = -playerShipSpeed;
             } else if( !isUpPressed && isDownPressed) {
              playerShipDeltaY = playerShipSpeed;
             } else {
              playerShipDeltaY = 0d;
             }
             
             // horizontal direction
             if( isLeftPressed && !isRightPressed) {
              playerShipDeltaX = -playerShipSpeed;
             } else if( !isLeftPressed && isRightPressed) {
              playerShipDeltaX = playerShipSpeed;
             } else {
              playerShipDeltaX = 0d;
             }
             
             // scroll background
             // ---------------------------
             // calculate new position
             double y = backgroundImageView.getLayoutY() + backgroundScrollSpeed;
             
             // check bounds. we scroll upwards, so the y position is negative. once it's > 0 we have reached the end of the map and stop scrolling
             if( Double.compare( y, 0) >= 0) {
              y = 0;
             }

             // move background
             backgroundImageView.setLayoutY( y);

             // move player ship
             // ---------------------------
             double newX = playerShip.getLayoutX() + playerShipDeltaX;
             double newY = playerShip.getLayoutY() + playerShipDeltaY;
             
             // check bounds
             // vertical
             if( Double.compare( newY, playerShipMinY) < 0) {
              newY = playerShipMinY;
             } else if( Double.compare(newY, playerShipMaxY) > 0) {
              newY = playerShipMaxY;
             }

             // horizontal
             if( Double.compare( newX, playerShipMinX) < 0) {
              newX = playerShipMinX;
             } else if( Double.compare(newX, playerShipMaxX) > 0) {
              newX = playerShipMaxX;
             }
             
             playerShip.setLayoutX( newX);
             playerShip.setLayoutY( newY);

             // limit bullet fire
             // ---------------------------
             // charge player cannon: increase a counter by some delta. once it reaches a limit, the cannon is considered charged
             cannonChargeCounter += cannonChargeCounterDelta;
             if( cannonChargeCounter > cannonChargeTime) {
              cannonChargeCounter = cannonChargeTime;
             }
             
             // fire player bullets
             // ---------------------------
             boolean isCannonCharged = cannonChargeCounter >= cannonChargeTime;
             if( isSpacePressed && isCannonCharged) {
              
              // x-position: center bullet on center of the ship
              double bulletX = playerShip.getLayoutX() + playerShip.getImage().getWidth() / 2.0 - playerBullet.getWidth() / 2.0;
              // y-position: let bullet come out on top of the ship
              double bulletY = playerShip.getLayoutY();

              // create sprite
              ImageView imgv = new ImageView( playerBullet);
              Sprite sprite = new Sprite( bulletLayer, imgv, bulletX, bulletY, 0, -cannonBulletSpeed);
              sprites.add( sprite);

              // left/right: vary x-axis position
              for( int i=0; i < cannonBullets / 2.0; i++) {

               // left
                  imgv = new ImageView( playerBullet);
                  sprite = new Sprite( bulletLayer, imgv, bulletX, bulletY, -cannonBulletSpread * i, -cannonBulletSpeed);
                  sprites.add( sprite);

               // right
                  imgv = new ImageView( playerBullet);
                  sprite = new Sprite( bulletLayer, imgv, bulletX, bulletY, cannonBulletSpread * i, -cannonBulletSpeed);
                  sprites.add( sprite);

              }
              
              // player bullet uncharged
              cannonChargeCounter = 0;
             }
             
             // check sprite visibility
             // remove every sprite that's not visible anymore
             // ---------------------------
             Iterator 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;
              }

             }
             
             // 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());
             }
             
             // 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( layer, imgv, rnd.nextDouble() * SCENE_WIDTH - imgv.getImage().getWidth() / 2.0, -imgv.getImage().getHeight(), 0, speed);
     sprites.add( sprite);
     
             }
             
             
             // calculate fps
             // ---------------------------
             frameCount++;
             
             long currTime = System.currentTimeMillis();
             
             if( currTime - prevTime >= 1000) {
              
              // get current fps
              fpsCurrent = frameCount;

              // reset counter every second
              prevTime = currTime;
              frameCount = 0;
             }
             
             // show debug info
             // ---------------------------
             debugLabel.setText("FPS: " + fpsCurrent + "\nSprites: " + sprites.size());
            }
 
        };
        
        gameLoop.start();
        
 }
 
 public static void main(String[] args) {
  launch(args);
 }
}


Sprite.java
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;
  
 }
 
}
ps: does anyone know why blogspot changes "Sprite" to "sprite" and adds tags like "</sprite></keyevent></keyevent></sprite></sprite> " to the code parts?

No comments:

Post a Comment