← Back to Demo

Project 002 — Code

Java • OOP • Bagel engine • Coursework (Individual)

What this page shows

This project contains many classes. Instead of listing everything, I selected representative classes that explain the game architecture and highlight key mechanics (rooms/doors, player state & UI, enemies, projectiles, and the “KeyBulletKin → Key → TreasureBox” interaction loop).

Quick Navigation

Architecture (high level)

The game is structured around rooms and transitions. A main controller class initializes rooms, updates the current room each frame, and handles room changes through doors. Combat rooms manage enemies/projectiles, while UI draws stats and messages.

Core Structure

  • Rooms: Room → BattleRoom / PrepRoom / EndRoom
  • Player: movement, health, coins, keys, weapon level
  • Enemies: Enemy base + ShootingEnemy + BulletKin variants
  • Projectiles: Projectile → Fireball / Bullet
  • Objects: TreasureBox, River, Wall, etc.

Design Sketch

I also created an early UML sketch to plan the class structure and responsibilities.

Download UML (PDF)

Game Loop & Room Update Order

A key part of the implementation is the consistent update order in a battle room: doors & environment first, then the player, then enemies and projectiles, and finally unlocking doors when enemies are cleared. This keeps interactions predictable and easier to debug.

Doors (anti-bounce & lock logic)

Doors use a justEntered flag to prevent immediate re-trigger after switching rooms, and can lock again when entering a battle room.

(Implementation detail lives in Door and the main controller logic.)

Room cleanup on exit

On room change, transient objects like projectiles are cleared so they don’t leak into the next room.

Combat & Projectiles

Shooting enemies generate Fireball projectiles at a set frequency, while projectiles deactivate when leaving the screen. This creates a simple and robust projectile lifecycle.

Shooting frequency & tracking (ShootingEnemy)

public Fireball update (Player player) {
    Fireball fireball = null;
    if (numFramesSinceLastFire == shootFrequency) {
        fireball = shoot(player.getPosition());
        numFramesSinceLastFire = 0;
        return fireball;
    }
    numFramesSinceLastFire++;
    return null;
}

From ShootingEnemy.

Projectile lifecycle (out-of-screen deactivation)

public void update() {
    position = position.asVector().add(vector).asPoint();

    // out of screen
    Rectangle rect = image.getBoundingBoxAt(position);
    Point topLeft = rect.topLeft();
    Point bottomRight = rect.bottomRight();
    if (topLeft.x < 0 || bottomRight.x >= Window.getWidth() ||
        topLeft.y < 0 || bottomRight.y >= Window.getHeight()) {
        active = false;
    }
}

From Projectile.

Fireball setup (speed, damage)

public Fireball(Point position, Point target) {
    super(position, target);
    image = new Image("res/fireball.png");
    speed = Double.parseDouble(ShadowDungeon.getGameProps().getProperty("fireballSpeed"));
    vector = vector.mul(speed);
    damage = Integer.parseInt(ShadowDungeon.getGameProps().getProperty("fireballDamage"));
}

From Fireball.

Mechanic Spotlight: KeyBulletKin → Key → TreasureBox

This mechanic is designed as a small “reward loop”. The KeyBulletKin does not shoot the player; instead it patrols a route. When defeated, it drops a key which can be consumed to open treasure boxes for coins.

KeyBulletKin patrol route (movement along waypoints)

// move to next route position
Vector2 delta = routes.get(nextIndex()).asVector()
    .sub(position.asVector()).normalised().mul(speed);
position = position.asVector().add(delta).asPoint();

if (position.equals(routes.get(nextIndex()))) {
    routeIndex = nextIndex();
} else if (position.distanceTo(routes.get(nextIndex())) <= speed/2) {
    routeIndex = nextIndex();
}

From KeyBulletKin.update(Player).

Drop a Key on death (collision with Bullet)

if (projectile.isActive() && projectile instanceof Bullet && hasCollidedWith(projectile)) {
    projectile.setInactive();
    health -= (projectile.getDamage());
    if (health <= 0) {
        dead = true;
        return new Key(position);
    }
}

From KeyBulletKin.update(projectiles).

Key pickup (adds to player keys)

public void update(Player player) {
    if (hasCollidedWith(player)) {
        active = false;
        player.gainKey();
    }
}

From Key.

TreasureBox unlock (requires K + at least 1 key)

public void update(Input input, Player player) {
    if (hasCollidedWith(player) && input.wasPressed(Keys.K) && player.getKeys() > 0) {
        player.earnCoins(coin, false);
        active = false;
        player.consumeKey();
    }
}

From TreasureBox.

UI & Displayed Messages

The UI is responsible for showing live stats (health, coins, keys, weapon level) and displaying messages. The character selection screen is also handled through UI rendering and input.

(Code lives in UserInterface and integrates with the main update loop.)

Representative Files Used on This Page

Download and View Full Source (Java)