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.
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
- ShadowDungeon.java — main controller / room orchestration
- Room.java, BattleRoom.java — room logic & update order
- Player.java — movement, state, shooting, damage handling
- Door.java — room transition logic & “just entered” guard
- Enemy.java, ShootingEnemy.java — enemy base + fireball shooting loop :contentReference[oaicite:10]{index=10}
- Projectile.java, Fireball.java — projectile lifecycle and configuration
- KeyBulletKin.java, Key.java, TreasureBox.java — reward loop mechanic