You are here: JGT Tutorial > 11. Formation Flying

Levels and Formation Flying

Now that we have a player-controlled spaceship and enemies that interact properly, it is time to build levels. The difference between the levels will be for now the number and the position of the enemies; of course later we would want to add different kinds of enemies and maybe even bosses.

Formation Flying

What we want is to have a group of enemies that fly together, always keeping the same position relative to each other. The group (as a whole) should move left to right.

The trick for doing this is to move the responsability for moving outside of the Enemy class. Instead we create a new class, Formation, that looks at the position of all of the enemies in the formation and then moves them all in the same direction. To know whether to go left or right, it simply looks at the leftmost (or rightmost) enemy in the formation.

We then add the Formation to the _animations list so that it's move method is called for every frame of animation and we done! Well, almost. We also have to modify the Enemy class so it doesn't try to move as well. Here's the new Formation class:

/**
 * Moves a group of VisibleObjects rigidly from left to right*/
public class Formation extends VisibleObject {
   
   protected List _obj;
   protected int _dx = 1;
   protected int _minX = 0;
   protected int _maxX = 0;
   
   /** create a formation that will go from left to right in the given range */
   public Formation(int minX, int maxX) {
      _minX = minX;
      _maxX = maxX;
      _obj = new ArrayList();
   }
  
   /** Add a VisibleObject from the formation */
   public void add(VisibleObject noob) {
      _obj.add(noob);
   }
   
   /** Set the initial horizontal velocity */
   public void setSpeed(int dx) {
      _dx = dx;
   }
   
   /** Move all objects in the formation */
   public void move() {
      // change direction if needed
      if (_dx<0 && minX()+_dx<_minX) _dx=Math.abs(_dx);
      else if (_dx>0 && maxX()+_dx>_maxX) _dx=-Math.abs(_dx);
      // move all objects
      for (Iterator ir = _obj.iterator(); ir.hasNext(); ) {
         VisibleObject o = ir.next();
         o.x += _dx;
         if (!o._alive) ir.remove();
      }
   }
   
   /** does nothing: you should draw the objects yourself */
   public void draw(Graphics g) { }

   /** the leftmost point in a group of rectangles */
   protected int minX() {
      int mx = Integer.MAX_VALUE;
      for (Rectangle r : _obj) {
         if (r.x < mx) mx = r.x;
      }
      return mx;
   }
   
   /** the rightmost point in a group of rectangles */
   protected int maxX() {
      int mx = Integer.MIN_VALUE;
      for (Rectangle r : _obj) {
         if (r.x+r.width > mx) mx = r.x+r.width;
      }
      return mx;
   }
   
}
We also modify Enemy so that the move() method does not change the enemy's position (since that's now the responsibility of Formation). We still want move() to be called, though, because that's where the enemy decides whether to fire or not.
public class Enemy extends VisibleObject {
    
    public void move() {
        if (_fire.shouldFire()) fire();
    }
    
    (...)
}
So now when we initialize the level, we create a 4x3 grid of enemies and add them to the formation. The code (shown below) should be pretty clear.
   public void buildLevel() throws Exception {
      _enemies.clear();
      _animations.clear();
      Formation f = new Formation(0,getWidth());
      f.setSpeed(2);
      for (int x=0; x<4; x++) {
         for (int y=0; y<3; y++) {
            Enemy baddie = new Enemy(this, 10+x*50, 10+y*40);
            f.add(baddie);
            _enemies.add(baddie);
         }
      }
      _animations.add(f);
   }
We call this new method at beginning of playLevel(), and we're set! As you can see in the screenshot below, the enemies are arranged in a nice grid. What you cannot see is that they move together from left to right in a very nice motion.

Next: (later)