2D Fighter Game — Java (OOP)
Arcade-style 2D side-view fighter supporting PvP (same keyboard) , with rounds, health bars, timers, and snappy animations. Built as a Bachelor project to practice real-time input, sprite handling, and collision/hitbox logic.
Stack: Java + Java2D (AWT/Swing), sprite/animation sheets, lightweight SFX. Java’s event loop and robust input handling make it ideal for clean game-loop architecture, per-frame collision checks, and sprite animation control.
Code Map
- game.java — entry point, window init, main loop
- input.java — keyboard input (pressed/held state)
- player.java / player2.java — player actors, state & animation
- object.java — base entity (pos/vel/size, update/draw)
- processor.java — update pipeline (physics, collisions)
- menu.java — main menu / pause / round UI
- res.java — sprite & sound loading (assets/)
- ID.java — enums (EntityID, State, Animation)
- bar.java — health/timer UI elements
- assets/ — spritesheets, backgrounds
- sound/ — sfx/music (optional)
Note: filenames derived from project; exact responsibilities may vary slightly.
Features
Responsive Controls
Keyboard mapping for move, jump, crouch, light/heavy attack, block. Debounce for taps + hold detection.
Combo & Hit Detection
Per-frame hitboxes apply damage/knockback on contact; blocking reduces or nullifies damage (if enabled).
Health & Rounds
HP bars, round timer, KO detection, and best-of-N round logic.
Sprite Animation
Idle/run/jump/attack/hurt/KO states with frame timing, state-driven animation switching.
Audio
Punch/kick SFX and optional background music.
Pause & Restart
Game state transitions for pause, restart, and next round.
Architecture & Code
High-Level Design
- Game Loop: handleInput → update → physics/collision → render (~60 FPS).
- Entities: Player, Enemy extend Object (pos/vel/size, update/draw).
- State Machine: Idle / Run / Jump / Attack / Hit / KO, with cooldowns.
- Systems: Input, Collision/Hitboxes, UI (health bars & timer), Audio.
Implementation Notes
- Composition: entities hold current animation, state, direction, and physics params.
- Hitboxes: per-frame rectangles (or masks) checked each update.
- Animation: spritesheet slicing; frame index advances at fixed ticks; resets on state change.
- Input: pressed/held maps support simultaneous keys; optional tap vs hold differentiation.
Game loop — window, timing, and render cycle
public class Game implements Runnable {
private boolean running = true;
private final int targetFps = 60;
private final Input input = new Input(); // keyboard listener
private final Processor processor = new Processor(); // physics/collisions
@Override public void run() {
long nsPerTick = 1_000_000_000L / targetFps;
long last = System.nanoTime(), acc = 0;
while (running) {
long now = System.nanoTime();
acc += (now - last); last = now;
// Handle input events (non-blocking)
input.poll();
// Fixed updates (catch up if needed)
while (acc >= nsPerTick) {
update();
acc -= nsPerTick;
}
render();
}
}
private void update() {
processor.updateAll(); // moves entities, resolves hits, timers, round logic
}
private void render() {
// draw background → entities → UI (health bars, timer)
}
}
Fixed-step update keeps physics stable; rendering occurs as often as possible.
Finite State Machine — player actions & transitions
enum State { IDLE, RUN, JUMP, ATTACK_LIGHT, ATTACK_HEAVY, HIT, KO }
public class Player extends Entity {
private State state = State.IDLE;
private int facing = 1; // 1: right, -1: left
private int attackCooldown = 0;
@Override public void update() {
// resolve input → desired state
if (hp <= 0) state = State.KO;
else if (wasHit()) state = State.HIT;
else if (input.attackHeavy() && attackCooldown == 0) { state = State.ATTACK_HEAVY; attackCooldown = 20; }
else if (input.attackLight() && attackCooldown == 0) { state = State.ATTACK_LIGHT; attackCooldown = 10; }
else if (input.jump()) state = State.JUMP;
else if (input.left() ^ input.right()) state = State.RUN;
else state = State.IDLE;
// physics + animation
applyPhysics();
updateAnimationFor(state, facing);
if (attackCooldown > 0) attackCooldown--;
}
}
Transitions are input-driven with cooldowns to gate combos and maintain readable combat.
Hitbox collision — damage & knockback
public class Processor {
public void resolveHit(Player attacker, Player defender){
Rectangle hit = attacker.getActiveHitbox();
Rectangle hurt = defender.getHurtbox();
if (hit != null && hurt != null && hit.intersects(hurt)) {
int dmg = attacker.isHeavy() ? 16 : 8;
if (defender.isBlocking(attacker.facing())) dmg /= 3; // block mitigates
defender.applyDamage(dmg);
defender.applyKnockback(attacker.facing() * (attacker.isHeavy() ? 6 : 4), 2);
defender.markHit(); // state → HIT
}
}
}
Simple rectangle checks per frame; blocking reduces damage; heavier attacks add knockback.
Animation update — spritesheet frames
public class Animator {
private BufferedImage[] frames; // current state's frames
private int idx = 0, tick = 0, ticksPerFrame = 5;
public void setClip(BufferedImage[] newFrames){
frames = newFrames; idx = 0; tick = 0;
}
public BufferedImage next(){
if (frames == null || frames.length == 0) return null;
if (++tick >= ticksPerFrame){ tick = 0; idx = (idx + 1) % frames.length; }
return frames[idx];
}
}
State changes swap animation clips; frame index advances at fixed cadence for consistent timing.
UI — health bar & round timer
public class HUD {
public void draw(Graphics2D g, int p1hp, int p2hp, int time, int round, int bestOf){
int w = 280, h = 16;
g.setColor(Color.darkGray);
g.fillRoundRect(40, 28, w, h, 8, 8);
g.fillRoundRect(720-w-40, 28, w, h, 8, 8);
g.setColor(new Color(34,197,94)); // emerald
g.fillRoundRect(40, 28, (int)(w * Math.max(0, p1hp)/100.0), h, 8, 8);
g.fillRoundRect(720-w-40 + (w - (int)(w * Math.max(0, p2hp)/100.0)), 28,
(int)(w * Math.max(0, p2hp)/100.0), h, 8, 8);
g.setColor(Color.white);
g.drawString("Round " + round + " / " + bestOf, 360, 24);
g.setFont(g.getFont().deriveFont(Font.BOLD, 16f));
g.drawString(String.valueOf(time), 380, 44);
}
}
Lightweight immediate-mode UI: draw rects/text each frame for bars & timers.
Demo & How to Run
Run Locally
# Clone repo
git clone https://github.com/sabers13/bachelor-projects.git
cd "bachelor-projects/semester 3/2D Fighter Game"
# Compile & run (Java 17+, adjust if using older JDK)
javac -d out src/*.java
java -cp out Game
Controls (example)
- P1: A/D = move, W = jump, S = crouch, J = light, K = heavy, L = block
- P2 (optional): arrows + numpad 1/2/3
- Esc = pause menu, R = restart round
Exact keys depend on input.java; adjust here if mappings differ.