How to Build a Simple Plants vs Zombies Game in Java: Design, Code, and Optimization
This article walks through creating a lightweight Plants vs Zombies‑style game in Java, covering object‑oriented design of plants, zombies and bullets, pause handling, collision detection, movement and attack logic, as well as several code‑level optimizations and background music integration.
Game Design
Plants vs Zombies includes a mini‑level where a rolling wheel randomly generates plant cards; the author extracted this mechanic and built a simplified version using Java.
Pause Implementation
The simplest pause method is to listen for mouse movement outside the game window and change the game state accordingly.
public void mouseMoved(MouseEvent e) {
if (status == start) {
int x = e.getX();
int y = e.getY();
if (x > Game.WIDTH || y > Game.HEIGHT) {
status = pause;
}
}
}Keyboard listeners can also be used for pausing.
Game Objects
Three main object families—Plant, Zombie, Bullet—are defined as abstract parent classes. Common attributes (width, height, life, position) and abstract methods such as step() are placed in the parents.
public abstract class Zombie {
protected int width;
protected int height;
protected int live;
protected int x;
protected int y;
public static final int LIFE = 0;
public static final int ATTACK = 1;
public static final int DEAD = 2;
protected int state = LIFE;
public abstract void step();
}A Shoot interface extracts the shooting behavior shared by some plant subclasses.
public interface Shoot {
Bullet[] shoot();
}Game Content
Plants and zombies are stored in separate List collections. Zombies are generated randomly with different probabilities and entered into the game at a fixed interval.
private List<Zombie> zombies = new ArrayList<>();
public Zombie nextOneZombie() {
Random rand = new Random();
int type = rand.nextInt(20);
if (type < 5) return new Zombie0();
else if (type < 10) return new Zombie1();
else if (type < 15) return new Zombie2();
else return new Zombie3();
}
public void zombieEnterAction() {
zombieEnterTime++;
if (zombieEnterTime % 300 == 0) {
zombies.add(nextOneZombie());
}
}Plants are managed in two lists—one for the rolling wheel and one for the battlefield—to simplify collision detection and movement.
private List<Plant> plants = new ArrayList<>();
private List<Plant> plantsLife = new ArrayList<>();
public void plantBangAction() {
for (int i = 1; i < plants.size(); i++) {
if (plants.get(0).getY() > 0 && plants.get(0).isStop()) {
plants.get(0).goWait();
}
if ((plants.get(i).isStop() || plants.get(i).isWait()) &&
(plants.get(i-1).isStop() || plants.get(i-1).isWait()) &&
plants.get(i).getY() <= plants.get(i-1).getY() + plants.get(i-1).getHeight()) {
plants.get(i).goStop();
} else if (plants.get(i).isStop() &&
plants.get(i).getY() > plants.get(i-1).getY() + plants.get(i-1).getHeight()) {
plants.get(i).goWait();
}
}
}Zombie Attack Logic
The attack range is calculated by comparing the bounding boxes of a zombie and a plant.
public boolean zombieHit(Plant p) {
int x1 = this.x - p.getWidth();
int x2 = this.x + this.width;
int y1 = this.y - p.getHeight();
int y2 = this.y + this.width;
int x = p.getX();
int y = p.getY();
return x >= x1 && x <= x2 && y >= y1 && y <= y2;
}During each attack cycle zombies first switch to a moving state, then check every plant on the battlefield; if a collision is detected the zombie changes to ATTACK and the plant loses life.
public void zombieHitAction() {
if (zombieHitTime++ % 100 == 0) {
for (Zombie z : zombies) {
if (!z.isDead()) {
z.goLife();
}
for (Plant p : plantsLife) {
if (z.isLife() && !p.isDead() && z.zombieHit(p) && !(p instanceof Spikerock)) {
z.goAttack();
p.loseLive();
}
}
}
}
}Game Optimization
Plant placement can be reduced to a simple boolean check, and a shovel object is introduced to remove plants. The shovel is stored in a list and removed together with the target plant when used.
private List<Shovel> shovels = new ArrayList<>();
public void shovelEnterAction() {
if (shovels.size() == 0) {
shovels.add(new Shovel());
}
}
public void useShovel() {
Iterator<Shovel> it = shovels.iterator();
Iterator<Plant> it2 = plantsLife.iterator();
while (it.hasNext()) {
Shovel s = it.next();
if (s.isMove()) {
while (it2.hasNext()) {
Plant p = it2.next();
// collision check omitted for brevity
it2.remove();
it.remove();
}
}
}
}Background music is played in a separate thread that loads a WAV file.
Runnable r = new zombieAubio("bgm.wav");
Thread t = new Thread(r);
t.start();Source code is available at https://github.com/llx330441824/plant_vs_zombie_simple.git
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
