/** The bullets that the enemies fire */
public class EnemyBullet extends Bullet {
protected int _height;
public EnemyBullet(VisibleObject shooter, HasSize world) {
super(shooter, new MoveStraight(0,4));
y=shooter.y+shooter.height;
_color = Color.RED;
_height = world.getHeight();
}
public void move() {
super.move();
if (y>_height) _alive=false;
}
}
Notice that we added a new constructor for the normal bullet, so that we don't have to create two MoveControls. Here's how we changed the constructor code.
/** The bullets that the spaceship fires **/
public class Bullet extends VisibleObject {
(...)
public Bullet(VisibleObject shooter) {
this(shooter, new MoveStraight(0,-8));
}
protected Bullet(VisibleObject shooter, MoveControl move) {
(...)
_move = move;
}
}
As you can see we created the new two-argument constructor to allow the caller to prevent us from creating the MoveStraight. All the code that was in the old constructor is now in the new two-argument constructor.
The next step is to allow the enemies to fire. We use a FireControl as we did for the spaceship, but this time we use the FireRandom class. It fires at random, with the probability we give in the constructor. I used a fairly high number so we don't have to wait to see that the enemies fire. Use a smaller number to make the enemies shoot less often. The new fire() method creates an EnemyBullet and adds it to the game.
public class Enemy extends VisibleObject {
(...)
protected MyGame _world;
protected FireControl _fire;
public Enemy2(MyGame world, int x0, int y0) throws Exception {
(...)
_world = world;
_fire = new FireRandom(0.02);
}
public void move() {
// change x and y
_move.move(this);
if (_fire.shouldFire()) fire();
}
public void fire() {
_world._enemyBullets.add(new EnemyBullet(this,_world));
}
(...)
}
To fire, we add the EnemyBullet to the _enemyBullets list in the main class. For this to work we must of course add this list and make sure we draw and move all these new bullets. So we change the code of MyGame as follows.
public class MyGame7 extends BaseApp {
Collection _enemyBullets;
(...)
/** main game loop **/
public void run() {
(...)
_enemyBullets = new ArrayList<VisibleObject>();
while (_gameRunning) {
// move both kinds of bullets
for (Iterator<VisibleObject> it = new UnionIterator<VisibleObject>(_bullets,_enemyBullets);it.hasNext();) {
VisibleObject b = it.next();
b.move();
if (!(b._alive)) it.remove();
}
(...)
}
}
/** draw what the player sees **/
public void drawWorld() {
(...)
for (VisibleObject b : _enemyBullets) {
b.draw(_dbg);
}
(...)
}
}
The code above creates the new list and goes through it as expected. The interesting part is that we use the JGT's
UnionIterator
to go through both lists at once. We could also go through them separately if we prefered.
Now we must detect when the ship is hit. We use CollisionDetection.
again for that purpose. The only catch is that it requires a list, so we create _shipList, a list that only contains our ship. Now we can use it to find collisions.
public class MyGame extends BaseApp {
Collection _shipList;
(...)
/** main game loop **/
public void run() {
try {
_ship = new Ship(this,getKeyboardStatus());
_shipList = new ArrayList();
_shipList.add(_ship);
(...)
collision = com.jpemartin.jgt.CollisionDetection.findCollisionListSlow(_enemyBullets,_shipList);
if (null!=collision && !collision._a.isEmpty()) {
System.out.println("Boom! You died");
}
(...)
}
}
The code above does nothing much when we're hit. Instead, we want it to start the game over. To do this we split the main loop into its own method and call it from within run(). We add a loop, so now if you're hit all we have to do is call return for the game to start over.
/** game entry point **/
public void run() {
try {
while (_gameRunning) {
(...)
playLevel();
}
(...)
}
/** main game loop **/
public void playLevel() {
while (_gameRunning) {
(...)
collision = com.jpemartin.jgt.CollisionDetection.findCollisionListSlow(_enemyBullets,_shipList);
if (null!=collision && !collision._a.isEmpty()) {
System.out.println("Boom! You died");
// done with the level
return;
}
}
}
Very little of what we did is visual, so all that the screenshot has to show is the new red shots from the enemies. But every other lesson has a screenshot at the end, so let's follow tradition.