Build Java Space Shooter Step by Step
Each step adds a feature. Write the code, watch it come alive in the canvas.
Set up the project and get your ship sliding along the bottom of space.
- 1New folder SpaceShooter, same Main.java + GamePanel.java template (600ยท640 window, near-black background).
- 2Ship variables: shipX centred, width 40, sitting at y = 580.
- 3Arrow keys strafe left/right, clamped to the screen, you have done this twice now, so it should feel easy!
- 4Draw the ship as a triangle using fillPolygon, three points: nose and two wing tips.
- 5Sprinkle some stars: draw 40 small white dots at random-but-fixed positions.
int shipX = 280, shipW = 40;
boolean leftHeld, rightHeld;
void update() {
if (leftHeld) shipX -= 6;
if (rightHeld) shipX += 6;
shipX = Math.max(0, Math.min(600 - shipW, shipX));
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(new Color(77, 217, 224));
int[] xs = { shipX + shipW/2, shipX, shipX + shipW };
int[] ys = { 580, 620, 620 };
g.fillPolygon(xs, ys, 3);
}
Press Space to fire lasers that travel up and disappear off screen.
- 1Bullets come and go constantly, so an array will not do, use ArrayList<Rectangle> bullets.
- 2On Space keyPressed: add a new Rectangle at the ship nose (width 4, height 12).
- 3In update(), move every bullet up 9 pixels per frame.
- 4Remove bullets that leave the top with removeIf, never let dead objects pile up, or the game slowly chokes!
- 5Draw them as bright yellow slivers.
java.util.List<Rectangle> bullets = new java.util.ArrayList<>();
public void keyPressed(KeyEvent e) {
// ... arrows ...
if (e.getKeyCode() == KeyEvent.VK_SPACE)
bullets.add(new Rectangle(shipX + shipW/2 - 2, 578, 4, 12));
}
void update() {
// ... ship movement ...
for (Rectangle b : bullets) b.y -= 9;
bullets.removeIf(b -> b.y + b.height < 0);
}
// paintComponent:
// g.setColor(new Color(250, 204, 21));
// for (Rectangle b : bullets) g.fillRect(b.x, b.y, b.width, b.height);
for (Rectangle b : bullets) b.y for (Rectangle b : bullets) b.y 9; bullets. 9; bullets.(b -> b.y + b.height < (b -> b.y + b.height < ););
Fill the sky with aliens that march side-to-side and step down like Space Invaders.
- 1Aliens also live in an ArrayList<Rectangle>, spawn a 6ยท4 grid of 36ยท26 aliens in the constructor.
- 2One shared direction: int alienDir = 1;, every frame each alien moves alienDir * 2 horizontally.
- 3If ANY alien touches a side wall, flip alienDir and move the whole wave DOWN 14 pixels.
- 4Do the wall check first with a flag, then apply movement, otherwise half the wave turns and half does not!
- 5Draw them in classic green.
java.util.List<Rectangle> aliens = new java.util.ArrayList<>();
int alienDir = 1;
// constructor:
for (int r = 0; r < 4; r++)
for (int c = 0; c < 6; c++)
aliens.add(new Rectangle(60 + c*80, 60 + r*52, 36, 26));
void update() {
boolean hitWall = false;
for (Rectangle a : aliens)
if (a.x + alienDir*2 < 0 || a.x + a.width + alienDir*2 > 600)
hitWall = true;
if (hitWall) {
alienDir = -alienDir;
for (Rectangle a : aliens) a.y += 14;
}
for (Rectangle a : aliens) a.x += alienDir * 2;
}
Lasers destroy aliens on contact, with score, and both objects removed cleanly.
- 1Loop bullets with an Iterator, and for each bullet loop the aliens.
- 2On intersect: remove the alien, remove the bullet, score += 20, and stop checking that bullet (it is gone!).
- 3Removing from a list you are looping is the classic beginner crash, the Iterator pattern below is the safe way.
- 4Draw the score top-left, same as always.
int score = 0;
var bi = bullets.iterator();
while (bi.hasNext()) {
Rectangle b = bi.next();
var ai = aliens.iterator();
while (ai.hasNext()) {
Rectangle a = ai.next();
if (b.intersects(a)) {
ai.remove(); // alien destroyed
bi.remove(); // bullet used up
score += 20;
break; // this bullet is gone
}
}
}
if (b.intersects(a)) {
ai.if (b.intersects(a)) {
ai.();
bi.();
bi.();
score += 20;
();
score += 20;
;
};
}
When a wave is cleared, spawn the next one, faster and worth more.
- 1Add int wave = 1; and move the grid-spawning code into a method spawnWave().
- 2In update(): if aliens.isEmpty(), wave++, spawnWave().
- 3Make speed scale with the wave: aliens move alienDir * (1 + wave) pixels.
- 4Score per kill can scale too: score += 20 * wave;.
- 5Show Wave: 3 next to the score, players love seeing how far they got.
int wave = 1;
void spawnWave() {
for (int r = 0; r < 4; r++)
for (int c = 0; c < 6; c++)
aliens.add(new Rectangle(60 + c*80, 60 + r*52, 36, 26));
}
void update() {
// ... everything so far ...
if (aliens.isEmpty()) {
wave++;
spawnWave();
}
for (Rectangle a : aliens) a.x += alienDir * (1 + wave);
}
Lose when aliens reach your ship line, show a proper end screen, and tune the fun.
- 1Add boolean gameOver = false;. If any alienยทs bottom passes y = 560, the invasion succeeded: gameOver = true.
- 2Also end the game if an alien rectangle intersects the shipยทs bounding box.
- 3When gameOver: freeze update() with an early return and draw GAME OVER + score + wave reached.
- 4Balance pass: bullet speed vs alien speed vs fire rate. Try limiting shots: only fire if bullets.size() < 3, suddenly aiming matters!
- 5Optional flourish: give aliens a chance to drop bombs, a second ArrayList moving down. You already know every technique needed. That is the point. โญ
boolean gameOver = false;
void update() {
if (gameOver) return;
// ... everything ...
for (Rectangle a : aliens)
if (a.y + a.height > 560) gameOver = true;
}
// paintComponent:
if (gameOver) {
g.setColor(Color.WHITE);
g.setFont(new Font("Arial", Font.BOLD, 44));
g.drawString("GAME OVER", 160, 300);
g.setFont(new Font("Arial", Font.BOLD, 20));
g.drawString("Score: " + score + " Wave: " + wave, 200, 340);
}
if (e.getKeyCode() == KeyEvent.VK_SPACE
&& bullets.if (e.getKeyCode() == KeyEvent.VK_SPACE
&& bullets.() < () < )
bullets.add(new Rectangle(shipX + shipW/2 - 2, 578, 4, 12));)
bullets.add(new Rectangle(shipX + shipW/2 - 2, 578, 4, 12));
โ See the complete finished Space Shooter , all 6 steps assembled
A triangle ship, Space-to-fire lasers in an ArrayList, a marching Space-Invaders alien wave that steps down at the walls, bullet-vs-alien collisions with scoring, plus win and lose states , one runnable file. Save as Main.java, run javac Main.java && java Main, and play. Stuck? Compare your file against this.
// Complete Java Space Shooter. Run with: javac Main.java then java Main
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.List;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame("Defend Earth");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new GamePanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
class GamePanel extends JPanel implements ActionListener, KeyListener {
Timer timer = new Timer(16, this);
int shipX = 280, shipW = 40;
boolean leftHeld, rightHeld;
int alienDir = 1, score = 0;
String state = "PLAY"; // PLAY / WIN / LOSE
List<Rectangle> bullets = new ArrayList<>();
List<Rectangle> aliens = new ArrayList<>();
public GamePanel() {
setPreferredSize(new Dimension(600, 650));
setBackground(new Color(10, 12, 26));
addKeyListener(this);
setFocusable(true);
spawnAliens();
timer.start();
}
void spawnAliens() {
aliens.clear();
for (int r = 0; r < 4; r++)
for (int c = 0; c < 6; c++)
aliens.add(new Rectangle(60 + c*80, 60 + r*52, 36, 26));
}
void reset() {
shipX = 280; score = 0; alienDir = 1; state = "PLAY";
bullets.clear(); spawnAliens();
}
public void actionPerformed(ActionEvent e) { update(); repaint(); }
void update() {
if (!state.equals("PLAY")) return;
if (leftHeld) shipX -= 6;
if (rightHeld) shipX += 6;
shipX = Math.max(0, Math.min(600 - shipW, shipX));
for (Rectangle b : bullets) b.y -= 9;
bullets.removeIf(b -> b.y + b.height < 0);
// march the wave, step down and reverse at a wall
boolean hitWall = false;
for (Rectangle a : aliens)
if (a.x + alienDir*2 < 0 || a.x + a.width + alienDir*2 > 600) hitWall = true;
if (hitWall) {
alienDir = -alienDir;
for (Rectangle a : aliens) a.y += 14;
} else {
for (Rectangle a : aliens) a.x += alienDir * 2;
}
// bullets vs aliens
for (int i = aliens.size() - 1; i >= 0; i--) {
Rectangle a = aliens.get(i);
for (int j = bullets.size() - 1; j >= 0; j--) {
if (a.intersects(bullets.get(j))) {
aliens.remove(i);
bullets.remove(j);
score += 10;
break;
}
}
}
// lose if the wave reaches the ship line, win when cleared
for (Rectangle a : aliens)
if (a.y + a.height >= 580) state = "LOSE";
if (aliens.isEmpty()) state = "WIN";
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(new Color(163, 88, 245));
for (Rectangle a : aliens) g.fillRect(a.x, a.y, a.width, a.height);
g.setColor(new Color(250, 204, 21));
for (Rectangle b : bullets) g.fillRect(b.x, b.y, b.width, b.height);
g.setColor(new Color(77, 217, 224));
int[] xs = { shipX + shipW/2, shipX, shipX + shipW };
int[] ys = { 580, 620, 620 };
g.fillPolygon(xs, ys, 3);
g.setColor(Color.WHITE);
g.setFont(new Font("Arial", Font.BOLD, 18));
g.drawString("Score: " + score, 16, 26);
if (!state.equals("PLAY")) {
g.setFont(new Font("Arial", Font.BOLD, 44));
g.drawString(state.equals("WIN") ? "EARTH SAVED!" : "GAME OVER", 130, 320);
g.setFont(new Font("Arial", Font.PLAIN, 18));
g.drawString("Press R to play again", 210, 360);
}
}
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_LEFT) leftHeld = true;
if (e.getKeyCode() == KeyEvent.VK_RIGHT) rightHeld = true;
if (e.getKeyCode() == KeyEvent.VK_SPACE && state.equals("PLAY"))
bullets.add(new Rectangle(shipX + shipW/2 - 2, 578, 4, 12));
if (e.getKeyCode() == KeyEvent.VK_R && !state.equals("PLAY")) reset();
}
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_LEFT) leftHeld = false;
if (e.getKeyCode() == KeyEvent.VK_RIGHT) rightHeld = false;
}
public void keyTyped(KeyEvent e) {}
}