When I first started building a small Java combat game between a Human and an Orc, I was excited to see them fight with swords and axes. But instead of battle cries, I kept getting a frustrating error in the console:
Exception in thread "main" java.lang.NullPointerException
at Player.attack(Player.java:72)
at Main.main(Main.java:15)
This was my first major blocker. But after some digging and learning, I figured out what was going wrong, fixed the issue, and even made my code more robust using best practices like getters/setters and null checks.
Let me walk you through the issue, how I solved it, and what improvements I made along the way!
Error Diagnosis:
The error pointed me to this line in my Player
class:
return player.receiveDamage(weapon.useWeapon());
I was calling weapon.useWeapon()
— but weapon
was still null
! That was the root of the NullPointerException.
What I Did Wrong:
In my Main.java
, I wrote:
Mensch.equip(Mensch.weapon); // WRONG
Ork.equip(Ork.weapon); // WRONG
But Mensch.weapon
and Ork.weapon
were never set! I had already created Weapon
objects (MenschW
, OrkW
), but I wasn’t using them properly.
Fix Error:
The solution was simple: pass the actual weapon objects to the equip()
method:
Mensch.equip(MenschW); // CORRECT
Ork.equip(OrkW); // CORRECT
Once I did that, the error disappeared and my characters could finally attack each other properly!
From Working to Well Written
After fixing the issue, I took the chance to clean up my code and apply a few important Object-Oriented Programming (OOP) practices:
- Encapsulation: I made all fields
private
and used setters/getters. - Null Safety: I added null checks to prevent runtime errors.
- Readable Logic: Better method names (
isDead()
instead ofdead()
), and clear error messages if weapons are missing or broken. - Extendability: My game is now easier to expand i could add critical hits, item durability, or healing in the future!
Final Improve Code:
Player.java
public class Player {
private String name;
private String race;
private int hp;
private int power;
private int armour;
private Weapon weapon;
public Player(String name, String race, int hp, int power, int armour) {
this.name = name;
this.race = race;
this.hp = hp;
this.power = power;
this.armour = armour;
}
// Getters and Setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getRace() { return race; }
public void setRace(String race) { this.race = race; }
public int getHP() { return hp; }
public void setHP(int hp) { this.hp = hp; }
public int getPower() { return power; }
public void setPower(int power) { this.power = power; }
public int getArmour() { return armour; }
public void setArmour(int armour) { this.armour = armour; }
public boolean isDead() {
return hp <= 0;
}
public boolean equip(Weapon weapon) {
if (weapon == null) return false;
this.weapon = weapon;
return true;
}
public boolean receiveDamage(int damage) {
int effectiveDamage = Math.max(damage - armour, 0);
hp = Math.max(hp - effectiveDamage, 0);
return hp > 0;
}
public boolean attack(Player target) {
if (weapon == null) {
System.out.println(name + " has no weapon to attack!");
return false;
}
int damage = weapon.useWeapon();
System.out.println(name + " attacks " + target.getName() + " with " + weapon.getName() + " for " + damage + " damage.");
return target.receiveDamage(damage);
}
}
Weapon.java
import java.util.concurrent.ThreadLocalRandom;
public class Weapon {
private String name;
private int damage;
private int hp;
public Weapon(String name, int damage, int hp) {
this.name = name;
this.damage = damage;
this.hp = hp;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getDamage() { return damage; }
public void setDamage(int damage) { this.damage = damage; }
public int getHP() { return hp; }
public void setHP(int hp) { this.hp = hp; }
public int useWeapon() {
if (isBroken()) {
System.out.println(name + " is broken!");
return 0;
}
hp -= 5;
return (damage / 2) + ThreadLocalRandom.current().nextInt(1, damage + 1);
}
private boolean isBroken() {
return hp <= 0;
}
}
Main.java
public class Main {
public static void main(String[] args) {
Player mensch = new Player("Mensch", "Human", 85, 12, 10);
Player ork = new Player("Shrek", "Ork", 50, 14, 6);
Weapon menschW = new Weapon("Mächtiges Schwert", 15, 100);
Weapon orkW = new Weapon("Große Axt", 7, 100);
mensch.equip(menschW);
ork.equip(orkW);
while (!mensch.isDead() && !ork.isDead()) {
mensch.attack(ork);
if (ork.isDead()) break;
ork.attack(mensch);
}
System.out.println("\nKampf vorbei!");
System.out.println("Mensch ist tot: " + mensch.isDead());
System.out.println("Ork ist tot: " + ork.isDead());
}
}
Final Thoughts
I’m really glad I ran into that NullPointerException early on it forced me to slow down, understand Java better, and write more reliable code. These small bugs often teach the most valuable lessons.
If you’re working on your first game in Java like I was, don’t be afraid of errors. Use them as signposts that guide you to better code. Now my characters swing swords, lose health, and die like real (virtual) warriors and your game can too.