Thumbnail image

Space Warz: Java Game Inspired by Space Invaders

!
Warning: This post is over 5 years old. The information may be out of date.
Timeline:Nov 16-20, 2013
Languages Used:Java
School:Colorado College
Course:CP122 Computer Science I

Watch the Demo

In the fall of 2013, for my Computer Science I final project, I set out to build a classic: a 2D top-down space shooter. The result was Space Warz, a game heavily inspired by the timeless arcade classic Space Invaders. Built over a single weekend, it features waves of enemy ships, mini-bosses, player shields, and the satisfying chaos of laser fire.

It was a whirlwind of a project, pushing my early Java skills to their limits, and while it had its share of bugs (as any weekend project might!), it was an incredibly fun and rewarding dive into game development.

If you’re curious to try it out yourself, feel free to download the executable versions below.

💡 Tip

If you’re on Mac or Linux, use Wine to run the exe!

Download .jar Download .exe

Building Space Warz: Under the Hood

Space Warz was my first real foray into building an interactive application from the ground up, and it was a crash course in core programming concepts like game loops, event handling, and object-oriented design.

The Game’s Heartbeat: GamePanel

At the core of Space Warz was the GamePanel.java class, acting as the main stage for all the action. This is where the game’s “heartbeat” resided – a javax.swing.Timer that continuously updated the game state and redrew the screen every 15 milliseconds. This rhythmic call to actionPerformed and repaint brought everything to life.

Here’s a glimpse into the central game loop that kept everything moving:

 1// GamePanel.java snippet
 2public void actionPerformed(ActionEvent e) {
 3    // Only update game state if not paused, won, or game over
 4    if (alive && !won && !paused) {
 5        // Update player position based on input
 6        spr.moveSprite();
 7
 8        // Move player's bullets
 9        bullets = spr.getBulletsFired();
10        for (int i = 0; i < bullets.size(); i++) {
11            if (bullets.get(i).isInScreen())
12                bullets.get(i).moveSprite();
13            else {
14                bullets.remove(i);
15            }
16        }
17
18        // Move enemies and their bullets, handle their logic
19        for (int i = 0; i < enemies.size(); i++) {
20            if (!enemies.get(i).isDying()) {
21                enemies.get(i).moveSprite();
22                // Randomly fire bullets
23                // ...
24            }
25            // ... move enemy bullets ...
26        }
27
28        // Check for interactions between all game objects
29        checkForCollision();
30    } else {
31        // Handle game over, win, or paused states
32    }
33
34    // Request a redraw of the entire panel
35    this.repaint();
36}

And the paint method, responsible for rendering every sprite, star, and UI element on the screen:

 1// GamePanel.java snippet
 2public void paint(Graphics g) {
 3    super.paint(g);
 4    Graphics2D g2 = (Graphics2D) g;
 5
 6    // Draw background stars
 7    for (StarSprite star : stars)
 8        g2.drawImage(star.getSpriteImage(), star.getXPos(), star.getYPos(), this);
 9
10    // Draw player's bullets
11    bullets = spr.getBulletsFired();
12    for (BulletSprite blt : bullets)
13        g2.drawImage(blt.getSpriteImage(), blt.getXPos(), blt.getYPos(), this);
14
15    // Draw enemies, rotating them to face the player
16    for (int z = 0; z < enemies.size(); z++) {
17        // ... draw enemy bullets ...
18        double rotationDegrees = getEnemyRotAngle(enemies.get(z));
19        // ... apply rotation and draw enemy sprite ...
20    }
21
22    // Draw the player ship, also rotated towards the mouse cursor
23    double sprCenterX = (double) spr.getWidth() / 2;
24    double sprCenterY = (double) spr.getHeight() / 2;
25    AffineTransform spr_atrans = AffineTransform.getRotateInstance(angleToRotate, sprCenterX, sprCenterY);
26    AffineTransformOp spr_atop = new AffineTransformOp(spr_atrans, AffineTransformOp.TYPE_BILINEAR);
27    if (spr.isVisible())
28        g2.drawImage(spr_atop.filter(spr.getSpriteImage(), null), spr.getXPos(), spr.getYPos(), this);
29
30    // ... draw UI elements like score, lives, level ...
31
32    g.dispose();
33}

Player Control and Projectiles

The player’s ship, represented by the PlayerSprite.java class, responded to keyboard input for movement. When the player pressed a key, keyPressed would update the intended direction, and the moveSprite method would then apply that movement, gradually accelerating or decelerating for a smoother feel. Here’s how key presses initiated movement:

 1// PlayerSprite.java snippet - Handling keyboard input
 2public void keyPressed(KeyEvent e) {
 3    int keyCode = e.getKeyCode();
 4    if (!dying) { // Can't move if exploding!
 5        // Move right or 'D' key
 6        if ((keyCode == KeyEvent.VK_RIGHT || keyCode == KeyEvent.VK_D)) {
 7            if (changex == 0) accel = true; // Start accelerating if at a stop
 8            changex = PLAYER_SPEED;
 9        }
10        // Move left or 'A' key
11        else if ((keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_A)) {
12            if (changex == 0) accel = true;
13            changex = -PLAYER_SPEED;
14        }
15        // ... similar logic for UP/W and DOWN/S ...
16    }
17}

When the player fired, a BulletSprite object was created. Its trajectory was determined by the player’s position and the mouse cursor’s position, ensuring bullets flew directly where the player was aiming. The BulletSprite then simply updated its coordinates each frame based on this calculated path.

1// PlayerSprite.java snippet - Firing a bullet
2public void fireBullet() {
3    bulletsFired.add(new BulletSprite(panel, x + PLAYERSPRITE_WIDTH / 2, y + PLAYERSPRITE_HEIGHT / 2, bulletDistX, bulletDistY, 1, 0));
4}
 1// BulletSprite.java snippet - Bullet movement
 2public void moveSprite() {
 3    // Update position based on pre-calculated X and Y increases
 4    xPos = xPos - (int) xIncrease;
 5    yPos = yPos - (int) yIncrease;
 6
 7    // Make bullet invisible if it goes off-screen
 8    if (yPos < 0 || yPos > GAMESCREEN_HEIGHT || xPos < 0 || xPos > GAMESCREEN_WIDTH)
 9        bulletVisible = false;
10}

Enemy AI and Behavior

The Enemy1Sprite.java class defined our standard enemy ships. They had basic vertical movement, often cycling from top to bottom or vice-versa. A more interesting element was their “gravitational pull” – a subtle AI that nudged them towards the player if they were within a certain distance, making them feel a bit more dynamic. They also had a simple random chance to fire bullets, adding to the challenge.

 1// Enemy1Sprite.java snippet - Movement and 'gravitational' pull
 2public void moveSprite() {
 3    // Basic vertical scrolling movement
 4    if (direction.equals("down")) {
 5        if (yPos > GAME_HEIGHT)
 6            yPos = 0 - GAME_HEIGHT * 1; // Loop back to top
 7        yPos = yPos + ENEMY_SPEED;
 8    } else {
 9        if (yPos < 0 - height)
10            yPos = GAME_HEIGHT + GAME_HEIGHT * 1; // Loop back to bottom
11        yPos = yPos - ENEMY_SPEED;
12    }
13
14    // If enemy is on screen, apply a "gravitational" pull towards the player
15    if (yPos > 0 && yPos < GAME_HEIGHT) {
16        sprXPos = sprPlayer.getXPos();
17        sprYPos = sprPlayer.getYPos();
18        sprDistX = sprXPos - xPos;
19        sprDistY = sprYPos - yPos;
20
21        // If within gravitation range, calculate and apply pull
22        if (((sprDistX < 0 && sprDistX > -DIST_TO_GRAVITATE) || (sprDistX > 0 && sprDistX < DIST_TO_GRAVITATE)) &&
23            ((sprDistY < 0 && sprDistY > -DIST_TO_GRAVITATE) || (sprDistY > 0 && sprDistY < DIST_TO_GRAVITATE))) {
24            // ... complex gravity calculation based on distance ...
25            yPos += gravPullY;
26            xPos += gravPullX;
27        }
28    }
29}

Collision Detection

Crucial for any game, the checkForCollision method in GamePanel.java was a workhorse. It systematically checked for intersections between different game entities – player, player bullets, enemies, and enemy bullets – and triggered appropriate consequences like health reduction, explosions, or score updates.

 1// GamePanel.java snippet - Collision detection logic
 2public void checkForCollision() {
 3    Rectangle shipSize = spr.getSpriteSize();
 4
 5    // Check Player vs. Enemy collisions
 6    for (Enemy1Sprite enm : enemies) {
 7        Rectangle enmSize = enm.getSpriteSize();
 8        if (enm.isCollidable() && shipSize.intersects(enmSize) && !playerDying) {
 9            killEnemy(enm); // Enemy explodes
10            if (!spr.isHurting())
11                killPlayer(); // Player takes damage or dies
12        }
13    }
14
15    // Check Player vs. Enemy Bullet collisions
16    for (ArrayList<BulletSprite> enemyBulletsList : enemyBullets) {
17        for (BulletSprite blt : enemyBulletsList) {
18            Rectangle bltSize = blt.getSpriteSize();
19            if (bltSize.intersects(shipSize) && !playerDying) {
20                blt.setVisible(false); // Bullet disappears
21                if (!spr.isHurting())
22                    killPlayer();
23            }
24        }
25    }
26
27    // Check Player Bullet vs. Enemy collisions
28    for (BulletSprite playerBullet : bullets) {
29        Rectangle bltSize = playerBullet.getSpriteSize();
30        for (Enemy1Sprite enm : enemies) {
31            if (enm.isCollidable() && bltSize.intersects(enmSize)) {
32                soundExplode.playSound();
33                playerBullet.setVisible(false); // Player bullet disappears
34                killEnemy(enm); // Enemy explodes
35                score += 15; // Earn points!
36            }
37        }
38    }
39    // ... similar checks for mini-bosses and other interactions ...
40}

Lessons Learned

Looking back, Space Warz was more than just a game; it was a foundational learning experience. Much like Duct-O, it provided a rich environment for practical lessons, though its focus was purely technical rather than business-oriented.

  • Game Loop Fundamentals: Building a robust actionPerformed and paint cycle taught me the core rhythm of real-time applications. Understanding how state updates and rendering combine is critical for any interactive software.
  • Object-Oriented Design in Practice: Creating separate classes for PlayerSprite, Enemy1Sprite, BulletSprite, etc., wasn’t just academic; it was essential for managing complexity in a dynamic system. It showed me the power of abstraction and encapsulation.
  • Event-Driven Programming: Handling keyboard and mouse inputs, and tying them to game actions, solidified my understanding of event listeners and responders.
  • Debugging and Iteration: Weekend projects are brutal but effective teachers. Every bug was a puzzle, and fixing it iteratively refined both the code and my problem-solving skills.
  • The Joy of Creation: While Duct-O had a distinct physical component, Space Warz was a pure dive into bringing a creative vision to life with code. The satisfaction of seeing sprites move, lasers fire, and enemies explode was immense, setting the stage for future projects and reinforcing my passion for building.

Screenshots

Source Code

View Source Code

Space Warz, though simple, was a testament to what can be built in a short time with focused effort, and it laid crucial groundwork for my later, more complex endeavors.

Related Posts

Comments