Space Shooter Application

This demonstration shows collision detection using the intersects method of a bounding rectangle in method checkCollisions of class Board. For example, when r3 and r2 are the bounding rectangles of the craft and alien, respectively, you can test for a collision with if r3.intersects(r2).

The program runs rather slowly on the Raspberry Pi, but quick enough to get a feel for the action. The following screenshot shows a game in action on the Pi.

Space shooter in action on the Pi

Space shooter in action on the Pi

The program logic is based on the Java on the zetcode collision page (and uses three images downloaded from their "basics" page), with some changes to make it more straightforward. You will learn about object-oriented code; the craft, aliens and missiles inherit variables (fields) and methods (procedures and functions) from the GameObject class from which they are derived.

The Timer causes an event to be triggered after each delay period and this is handled by the actionPerformed method of the Board class. Here calculations take place, the results of which are used in the paint method.

This program also demonstrates:

  • the handling of the key press event for the JFrame of the Board object being passed to the keyPressed method of the Craft object;
  • the use of ArrayLists for the convenient addition and removal of objects;
  • the use of FontMetrics to position text.

You can change easily the delay, the images, the number and starting positions of the aliens, and the speed of the missiles. We thank Neil for this example with changed images.

Space shooter with changed images

Space shooter with changed images

You might like to show on the "Game Over" screen the number of missiles remaining. In this skeleton demonstration each hit is totally destructive. You could instead keep track of the health of the craft and of each alien as collisions occur. Of course, we encourage you to use similar techniques to develop your own style of game.

Put these three image files into your program folder. The original sizes are shown.
  • alien.png (10 x 9)
  • craft.png (20 x 20)
  • missile.png (8 x 5)

Each class can have its own file (with an interface and implementation section), provided that the namespace remains the same. If you do use several files, remember to reference them all in the .oxygene project file.

The Code

namespace space_shooter_demo;

interface

uses
  java.awt.*, java.util.*, javax.swing.*, java.io.*, javax.imageio.*;

type
  GameObject = public class(object) //Base class for craft, missiles and aliens
  protected //Available to derived classes
    x, y, width, height : Integer;
    visible : Boolean := true;
    image : Image;
    sourceImage : File;
  public
    method getX : Integer;
    method getY : Integer;
    method getImage : Image;
    method setVisible(vis : Boolean);
    method isVisible : Boolean;
    method getBounds : Rectangle;
    method doImage(s : String);
  end;

  Craft = public class(GameObject)
  private
    const STR_CRAFT = 'craft.png';
    var
      dx, dy : Integer; //Change in x and y
      missiles : ArrayList;
  public
    constructor;
    method move;
    method getMissiles : ArrayList;
    method keyPressed(e : KeyEvent);
    method keyReleased(e : KeyEvent);
    method fire;
  end;

  Board = public class(JPanel, ActionListener, KeyListener)
  private
    timer : Timer;
    craft : Craft;
    aliens : ArrayList;
    ingame : Boolean;
    pos : array [0 .. 26] of array [0 .. 1] of Integer :=  [
      [2380, 29], [2500, 59], [1380, 89], [780, 109], [580, 139], [680, 239],
      [790, 259], [760, 50], [790, 150], [980, 209], [560, 45], [510, 70],
      [930, 159], [590, 80], [530, 60], [940, 59], [990, 30], [920, 200],
      [900, 259], [660, 50], [540, 90], [810, 220], [860, 20], [740, 180],
      [820, 128], [490, 170], [700, 30] ]; //Starting coordinates (see initAliens)
  public
    B_WIDTH, B_HEIGHT : Integer; //Board width and height
    constructor;
    method initAliens;
    method actionPerformed(e : ActionEvent);
    method paint(g : graphics); override;
    method checkCollisions;
    method keyPressed(e : KeyEvent);
    method keyReleased(e : KeyEvent);
    method keyTyped(e : KeyEvent);
  end;

  Alien = public class(GameObject)
  private
    const STR_ALIEN = 'alien.png';
  public
    constructor (xpos, ypos : Integer);
    method move;
  end;

  Missile = public class(GameObject)
  private
    const
      BOARD_WIDTH  = 390;
      MISSILE_SPEED = 2;
      STR_MISSILE = 'missile.png';
  public
    constructor(xpos, ypos : Integer);
    method move;
  end;

  SpaceShooterDemo = public class(JFrame)
  public
    constructor;
    class method main(args : array of String);
  end;

implementation

method GameObject.getX : Integer;
begin
  result := x;
end;

method GameObject.getY : Integer;
begin
  result := y;
end;

method GameObject.getImage : Image;
begin
  result := image;
end;

method GameObject.setVisible(vis : Boolean);
begin
  visible := vis;
end;

method GameObject.isVisible : Boolean;
begin
  result := visible;
end;

method GameObject.getBounds : Rectangle;
begin
  result := new Rectangle(x, y, width, height);
end;

method GameObject.doImage(s : string);
begin
  sourceImage := new File(s);
  image := ImageIO.read(sourceImage);
  width := image.getWidth(nil);
  height := image.getHeight(nil);
end;

constructor Craft;
begin
  doImage(STR_CRAFT);
  missiles := new ArrayList;
  x := 40;
  y := 60;
end;

method Craft.move;
begin
  x := x + dx;
  y := y + dy;
  if x < 1 then
   x := 1;
  if y < 1 then
   y := 1;
end;

method Craft.getMissiles : ArrayList;
begin
  result := missiles;
end;

method Craft.keyPressed(e : KeyEvent);
var
  key : Integer;
begin
  key := e.getKeyCode;
  case key of
    KeyEvent.VK_SPACE : fire;
    KeyEvent.VK_LEFT : dx := -1;
    KeyEvent.VK_RIGHT : dx := 1;
    KeyEvent.VK_UP : dy := -1;
    KeyEvent.VK_DOWN : dy := 1;
  end;
end;

method Craft.fire;
begin
  missiles.add(new Missile(x + width, y + height / 2));
end;

method Craft.keyReleased(e : KeyEvent);
var
  key : Integer;
begin
  key := e.getKeyCode;
  case key of
    KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT : dx := 0;
    KeyEvent.VK_UP, KeyEvent.VK_DOWN : dy := 0;
  end;
end;

constructor Board;
begin
  setFocusable(true);
  setBackground(Color.BLACK);
  setDoubleBuffered(true);
  ingame := true;
  setSize(400, 300);
  craft := new Craft;
  addKeyListener(self);
  initAliens;
  timer := new Timer(5, self);  //5 millisecond delay
  timer.start;
end;

method Board.initAliens;
var
  i : Integer;
begin
  aliens := new ArrayList;
  for i := 0 to length(pos) - 1 do
    aliens.add(new Alien(pos[i][0], pos[i][1]));
end;

method Board.paint(g : Graphics);
var
  g2d : Graphics2D;
  ms : ArrayList;
  i : Integer;
  m : Missile;
  a : Alien;
  msg : String;
  small : Font;
  metr : FontMetrics;
begin
  inherited;
  if ingame then
    begin
      g2d := Graphics2D(g);
      if craft.isVisible then
        g2d.drawImage(craft.getImage, craft.getX, craft.getY, self);
      ms := craft.getMissiles;
      for  i := 0 to ms.size - 1 do
        begin
          m := Missile(ms.get(i));
          g2d.drawImage(m.getImage, m.getX, m.getY, self);
        end;
      for  i := 0 to aliens.size - 1 do
        begin
          a := Alien(aliens.get(i));
          if a.isVisible then
            g2d.drawImage(a.getImage, a.getX, a.getY, self);
        end;
      g2d.setColor(Color.WHITE);
      g2d.drawString('Aliens left: ' + aliens.size, 5, 15);
    end
  else
    begin
      msg := 'Game Over';
      small := new Font('Helvetica', Font.BOLD, 14);
      metr := self.getFontMetrics(small);
      g.setColor(Color.white);
      g.setFont(small);
      //Output 'Game Over' near centre of the window.
      g.drawString(msg, (B_WIDTH - metr.stringWidth(msg)) / 2, B_HEIGHT / 2);
    end;
  Toolkit.getDefaultToolkit.sync;
  g.dispose;
end;

method Board.actionPerformed(e : ActionEvent); //Handles timer events
var
  ms : ArrayList;
  i : Integer;
  m : Missile;
  a : Alien;
begin
  if aliens.size = 0 then
    ingame := false;
  ms := craft.getMissiles;
  i := 0;
  while i < ms.size do
    begin
      m := Missile(ms.get(i));  //Cast from item in ArrayList to Missile
      if m.isVisible then
        m.move
      else
        ms.remove(i);
      inc(i);
    end;
  i := 0;
  while i < aliens.size do
    begin
      a := Alien(aliens.get(i));
      if a.isVisible then
        a.move
      else
        aliens.remove(i);
      inc(i);
    end;
  craft.move;
  checkCollisions;
  repaint;
end;

method Board.checkCollisions;
var
  r1, r2, r3 : Rectangle;
  i, j : Integer;
  ms : ArrayList;
  m : Missile;
  a : Alien;
begin
  r3 := craft.getBounds; //Bounding rectangle of alien
  for j := 0 to aliens.size - 1 do
    begin
      a := Alien(aliens.get(j));  //Cast from item in ArrayList to Alien
      r2 := a.getBounds; //Bounding rectangle of alien
      if r3.intersects(r2) then
        begin
          craft.setVisible(false);
          a.setVisible(false);
          ingame := false;
        end;
    end;
  ms := craft.getMissiles;
  for i := 0 to ms.size - 1 do
    begin
      m := Missile(ms.get(i));  //Cast from item in ArrayList to Missile
      r1 := m.getBounds;
      for j := 0 to aliens.size - 1 do
        begin
          a := Alien(aliens.get(j));   //Cast from item in ArrayList to Alien
          r2 := a.getBounds;
          if r1.intersects(r2) then
            begin
              m.setVisible(false);
              a.setVisible(false);
            end;
        end;
    end;
end;

method Board.keyPressed(e : KeyEvent);
begin
  craft.keyPressed(e);
end;

method Board.keyReleased(e : KeyEvent);
begin
  craft.keyReleased(e);
end;

method Board.keyTyped(e : KeyEvent);  //Must provide implementation even if empty
begin
end;

constructor Alien(xpos, ypos : Integer);
begin
  doImage(STR_ALIEN);
  x := xpos;
  y := ypos;
end;

method Alien.move;
begin
  if x < 0 then
    x := 400;
  dec(x);
end;

constructor Missile(xpos, ypos : Integer);
begin
  doImage(STR_MISSILE);
  x := xpos;
  y := ypos;
end;

method Missile.move;
begin
  x := x + MISSILE_SPEED;
  if x > BOARD_WIDTH then
    visible := false;
end;

constructor SpaceShooterDemo;
var
  board : Board;
begin
  board := new Board;
  board.B_WIDTH := board.getWidth;
  board.B_HEIGHT := board.getHeight;
  add(board);
  setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  setSize(400, 300);
  setLocationRelativeTo(nil);  //Place in centre of screen
  setTitle('SpaceShooterDemo');
  setResizable(false);
  setVisible(true);
end;

class method SpaceShooterDemo.main(args : array of String);
begin
  new SpaceShooterDemo;
end;

end.
Programming - a skill for life!

How to write games in Oxygene for Java