Friday, January 23, 2015

Anansi: Putting it all together


We have all the pieces for the game, now we have to put them all together.

In the game loop we have to update sprite objects of various types. We keep references to them in dedicated lists.


List backgroundList = new ArrayList<>();
List cloudList = new ArrayList<>();
List enemyList = new ArrayList<>();
List playerBulletList = new ArrayList<>();
List playerList = new ArrayList<>();

ps: please excuse the html tags that blogspot adds for an unknown reason

We create the game and start it ...
// create game level (background, player, etc)
createLevel( scene);

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

// create game loop
createGameLoop();

// start the game
startGame();

... using these methods ...
private void createLevel( Scene scene) {
 
 // load game assets
 loadResources();
 
 // create level structure
 createBackground();
 
 // create player, including input controls
 createPlayer( scene);
 
}

private void createBackground() {
 
 Image image = backgroundImage;
 
 double x = 0;
 double y = -image.getHeight() + Settings.SCENE_HEIGHT;
   
 Background background = new Background( backgroundLayer, backgroundImage, x, y, Settings.BACKGROUND_SPEED);
 
 backgroundList.add( background);

}

private void createPlayer( Scene scene) {
 
 // player input
 Input input = new Input( scene);
 
 // register input listeners
 input.addListeners();

 Image image = playerShipImage;
 
 // 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, playerShipImage, x, y, 0, 0, 0, 0, Settings.PLAYER_SHIP_HEALTH, 0, Settings.PLAYER_SHIP_SPEED, input);
 
 // register player
 playerList.add( player);
 
}


We add more attributes to the settings class
public class Settings {

 public static double SCENE_WIDTH = 400;
 public static double SCENE_HEIGHT = 800;
 
 public static double BACKGROUND_SPEED = 0.6;
 
 public static double PLAYER_SHIP_SPEED = 4.0;
 public static double PLAYER_SHIP_HEALTH = 100.0;
 
 public static int ENEMY_SPAWN_RANDOMNESS = 50;
 public static int CLOUD_SPAWN_RANDOMNESS = 100;
}

We spawn the clouds at random intervals ...
private void spawnClouds( boolean random) {
 
 if( random && rnd.nextInt(Settings.CLOUD_SPAWN_RANDOMNESS) != 0) {
  return;
 }
 
 Pane layer;
 Image image;
 double speed;
 double opacity;
 double x;
 double y;

 // determine random layer
 // 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( rnd.nextInt(2) == 0) {
  layer = lowerCloudLayer;
  opacity = 1.0;
    speed = rnd.nextDouble() * 1.0 + 1.0;
 } else {
  layer = upperCloudLayer;
  opacity = 0.5;
    speed = rnd.nextDouble() * 1.0 + 2.0;
 }

 // determine random image
 image = cloudImages[ rnd.nextInt( cloudImages.length)]; 
 
 // set position horizontally so that half of it may be outside of the view, vertically so that it enters the view with the next movement
 x = rnd.nextDouble() * Settings.SCENE_WIDTH - image.getWidth() / 2.0;
 y = -image.getHeight();
 
 // create a sprite 
 Cloud cloud = new Cloud( layer, image, x, y, speed, opacity);
 
 // manage sprite
 cloudList.add( cloud);
  
}

... and the enemies ...
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 + 1.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
 enemyList.add( enemy);
   
}

... and the bullets whenever the player demands it.
private void spawnPrimaryWeaponObjects( Player player) {
 
 player.chargePrimaryWeapon();

   if( player.isFirePrimaryWeapon()) {
    
    PlayerBullet playerBullet;

    Image image = playerBulletImage;
    
    // primary weapon gives horizontal center of the cannon, but we also need to consider the center of the bullet
    double x = player.getPrimaryWeaponX() - image.getWidth() / 2.0; 
    double y = player.getPrimaryWeaponY();

    double spread = player.getPrimaryWeaponBulletSpread();
    double count = player.getPrimaryWeaponBulletCount();
    double speed = player.getPrimaryWeaponBulletSpeed();
    
    // create sprite
    playerBullet = new PlayerBullet( bulletLayer, image, x, y, 0, -speed);
    playerBulletList.add( playerBullet);
    
    // left/right: vary x-axis position
    for( int i=0; i < count / 2.0; i++) {

        // left
        playerBullet = new PlayerBullet( bulletLayer, image, x, y, -spread * i, -speed);
        playerBulletList.add( playerBullet);

        // right
        playerBullet = new PlayerBullet( bulletLayer, image, x, y, spread * i, -speed);
        playerBulletList.add( playerBullet);

    }
    
    player.unchargePrimaryWeapon();
   }
 
}

We then have to process various methods like move() on every sprite. We create helper methods which do that for us. There's an alternative to the for loop by using forEach in Java 8, but we keep the for loop for now. That way the game remains playable with Java 7. The main advantage of Java 8 would be how easy it is to parallelize code by using streams. Maybe we'll come back to that later. There's no performance problem, so let's not make things too complicated for now.
private void moveSprites( List spriteList) {

 for( SpriteBase item: spriteList) {
  item.move();
 }
 
}

private void updateSpritesUI( List spriteList) {

 for( SpriteBase item: spriteList) {
  item.updateUI();
 }
 
}

private void checkRemovability( List spriteList) {

 for( SpriteBase item: spriteList) {
  item.checkRemovability();
 }
 
}

In case you are curious, instead of a method with the for loop, you could as well write in Java 8
spriteList.forEach( player -> player.move());

Now we invoke these methods in the game loop
private void createGameLoop() {
 
    gameLoop = new AnimationTimer() {
     
        @Override
        public void handle(long l) {
         
         // player AI (player input)
         // ---------------------------
         for( Player player: playerList) {
          player.processInput();
         }
         
         // add random sprites
         // --------------------------
         spawnClouds( true);
         spawnEnemies( true);

         // spawn bullets of players
         // ---------------------------
         for( Player player: playerList) {
          spawnPrimaryWeaponObjects( player);
         }
         
         // move sprites internally
         // ---------------------------
         moveSprites( playerList);
         moveSprites( backgroundList);
         
         moveSprites( playerBulletList);
         moveSprites( cloudList);
         moveSprites( enemyList);

         // move sprites on screen
         // ---------------------------
         updateSpritesUI(playerList);
         updateSpritesUI(backgroundList);
         
         updateSpritesUI(playerBulletList);
         updateSpritesUI(cloudList);
         updateSpritesUI(enemyList);

         // check if sprite can be removed
         // ------------------------------
         checkRemovability( playerBulletList);
         checkRemovability( cloudList);
         checkRemovability( enemyList);
         
         // remove removables from list, layer, etc
         // ------------------------------
         removeSprites( playerBulletList);
         removeSprites( cloudList);
         removeSprites( enemyList);
         

         // calculate fps
         // ---------------------------
         updateFps();
         
         // show debug information (fps etc)
         // --------------------------------
         updateDebugOverlay();
        }

    };
    
}

And that was about it. We now have a game engine with which we can proceed.

Finally here's a screenshot about what the game looks like.


Maybe not much of a difference to you compared to the prototype, but you'll see how easy it will be to add other kinds of stuff. Next time we spawn heat seeking missiles and make things go booom.


No comments:

Post a Comment