Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Implementing a Finite State Machine for Zombie Behavior in Plants vs. Zombies

Tech 1

Abstract State Class Implemantation

The foundation of zombie behavior relies on a abstract state class that defines core functionality shared across all states.

package step3.CharacterSystem.ZombieFSMSystem;

import step3.CharacterSystem.Character.ICharacter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class IZombieState {
    protected Map<EnemyTransition, EnemyStateID> transitionMap = new HashMap<>();
    protected EnemyStateID stateIdentifier;
    protected ICharacter entity;
    protected ZombieFSM stateMachine;

    public IZombieState(ZombieFSM machine, ICharacter character) {
        stateMachine = machine;
        entity = character;
    }

    public void registerTransition(EnemyTransition transition, EnemyStateID targetState) {
        if (transition == EnemyTransition.NullTansition || targetState == EnemyStateID.NullState || transitionMap.containsKey(transition)) {
            return;
        }
        transitionMap.put(transition, targetState);
    }

    public void removeTransition(EnemyTransition transition) {
        transitionMap.remove(transition);
    }

    public EnemyStateID getNextState(EnemyTransition transition) {
        return transitionMap.getOrDefault(transition, EnemyStateID.NullState);
    }

    public abstract void onEnter();
    public abstract void onExit();
    public abstract void evaluateConditions(List<ICharacter> targets);
    public abstract void executeActions(List<ICharacter> targets);

    public EnemyStateID getStateId() {
        return stateIdentifier;
    }
}

Combat State Implementation

The attack state manages zombie combat behavior including damage calculation and target validation.

package step3.CharacterSystem.ZombieFSMSystem;

import step3.CharacterSystem.Character.ICharacter;
import java.util.List;

public class ZombieAttackState extends IZombieState {
    private float combatInterval = 1;
    private float combatTimer = 0;

    public ZombieAttackState(ZombieFSM machine, ICharacter character) {
        super(machine, character);
        stateIdentifier = EnemyStateID.Attack;
        combatTimer = combatInterval;
    }

    @Override
    public void onEnter() {}

    @Override
    public void onExit() {}

    @Override
    public void executeActions(List<ICharacter> targets) {
        if (targets == null || targets.isEmpty()) return;
        
        combatTimer += 0.1f;
        if (combatTimer >= combatInterval) {
            processTargets(targets);
            combatTimer = 0;
        }
    }

    private void processTargets(List<ICharacter> targets) {
        for(ICharacter target : targets) {
            if (target.getPosRow() == entity.getPosRow()) {
                int horizontalDistance = entity.getPosition().x - target.getPosition().x;
                if (horizontalDistance <= entity.getAttr().getmBaseAttr().getAtkdistance() && horizontalDistance >= 0) {
                    entity.Attack(target);
                }
            }
        }
    }

    @Override
    public void evaluateConditions(List<ICharacter> targets) {
        if (targets == null || targets.isEmpty()) {
            stateMachine.PerformTransition(EnemyTransition.LostSoldier);
            return;
        }
        
        if (!isTargetInRange(targets)) {
            stateMachine.PerformTransition(EnemyTransition.LostSoldier);
        }
    }
    
    private boolean isTargetInRange(List<ICharacter> targets) {
        for (ICharacter target : targets) {
            if (target.getPosRow() == entity.getPosRow()) {
                int distance = entity.getPosition().x - target.getPosition().x;
                if (distance >= 0 && distance <= entity.getAttr().getmBaseAttr().getAtkdistance()) {
                    return true;
                }
            }
        }
        return false;
    }
}

Movement State Implementation

The chase state handles zombie movement toward targets and determines when to transition to attacking.

package step3.CharacterSystem.ZombieFSMSystem;

import step3.CharacterSystem.Character.ICharacter;
import java.awt.*;
import java.util.List;

public class ZombieChaseState extends IZombieState {
    private Point destinationPoint;

    public ZombieChaseState(ZombieFSM machine, ICharacter character) {
        super(machine, character);
        stateIdentifier = EnemyStateID.Chase;
    }

    @Override
    public void onEnter() {
        destinationPoint = new Point(50, entity.getPosition().y);
    }

    @Override
    public void onExit() {}

    public void executeActions(List<ICharacter> targets) {
        if (entity.getPosition().x > destinationPoint.x) {
            entity.MoveTo(destinationPoint.x);
        }
    }

    @Override
    public void evaluateConditions(List<ICharacter> targets) {
        if (targets != null && !targets.isEmpty()) {
            int closestTarget = findClosestTarget(targets);
            if (closestTarget <= entity.getAttr().getmBaseAttr().getAtkdistance()) {
                stateMachine.PerformTransition(EnemyTransition.CanAttack);
            }
        }
    }
    
    private int findClosestTarget(List<ICharacter> targets) {
        int minimumDistance = Integer.MAX_VALUE;
        for (ICharacter target : targets) {
            if(target.getPosRow() == entity.getPosRow()) {
                int currentDistance = entity.getPosition().x - target.getPosition().x;
                if (minimumDistance > currentDistance && currentDistance >= 0) {
                    minimumDistance = currentDistance;
                }
            }
        }
        return minimumDistance;
    }
}

State Machine Controller

The finite state machine orchestrates state transitions and manages the active state lifecycle.

package step3.CharacterSystem.ZombieFSMSystem;

import java.util.ArrayList;
import java.util.List;

public class ZombieFSM {
    private List<IZombieState> availableStates = new ArrayList<>();
    private IZombieState activeState;

    public IZombieState getCurrentState() { return activeState; }

    public void registerStates(IZombieState[] states) {
        for(IZombieState state : states) {
            registerState(state);
        }
    }

    public void registerState(IZombieState state) {
        if (state == null) return;
        
        if (availableStates.isEmpty()) {
            initializeFirstState(state);
            return;
        }
        
        if (!stateExists(state.getStateId())) {
            availableStates.add(state);
        }
    }
    
    private void initializeFirstState(IZombieState state) {
        availableStates.add(state);
        activeState = state;
        activeState.onEnter();
    }
    
    private boolean stateExists(EnemyStateID stateId) {
        return availableStates.stream().anyMatch(s -> s.getStateId() == stateId);
    }

    public void removeState(EnemyStateID stateID) {
        if (stateID == EnemyStateID.NullState) return;
        
        availableStates.removeIf(state -> state.getStateId() == stateID);
    }

    public void PerformTransition(EnemyTransition transition) {
        if (transition == EnemyTransition.NullTansition) return;
        
        EnemyStateID nextIdentifier = activeState.getNextState(transition);
        if (nextIdentifier == EnemyStateID.NullState) return;
        
        switchToState(nextIdentifier);
    }
    
    private void switchToState(EnemyStateID targetStateId) {
        for (IZombieState state : availableStates) {
            if (state.getStateId() == targetStateId) {
                activeState.onExit();
                activeState = state;
                activeState.onEnter();
                return;
            }
        }
    }
}

Zombie Entity Integration

The concrete zombie implementation integrates the state machine with character behavior.

package step3.CharacterSystem.Character;

import step3.CharacterSystem.Attr.CharacterAttr;
import step3.CharacterSystem.ZombieFSMSystem.*;
import java.awt.*;
import java.util.List;

public class ZombieNormal extends ICharacter {
    private ZombieFSM behaviorController;

    public ZombieNormal(CharacterAttr attributes, Point location, int rowPosition) {
        super(attributes, location, rowPosition);
        attackimg = "/data/workspace/myshixun/images/Zombies/Zombie/ZombieAttack.gif";
        chaseimg = "/data/workspace/myshixun/images/Zombies/Zombie/Zombie.gif";
        GetImageSize(chaseimg);
    }

    @Override
    public void Killed() {}

    @Override
    public void MakeFSM() {
        behaviorController = new ZombieFSM();
        
        ZombieChaseState pursuitState = new ZombieChaseState(behaviorController, this);
        pursuitState.registerTransition(EnemyTransition.CanAttack, EnemyStateID.Attack);
        
        ZombieAttackState combatState = new ZombieAttackState(behaviorController, this);
        combatState.registerTransition(EnemyTransition.LostSoldier, EnemyStateID.Chase);
        
        behaviorController.registerState(pursuitState);
        behaviorController.registerState(combatState);
    }

    @Override
    public void UpdateFSMAI(List<ICharacter> targets) {
        if (mIsKilled) return;
        behaviorController.getCurrentState().evaluateConditions(targets);
        behaviorController.getCurrentState().executeActions(targets);
    }

    @Override
    public void Attack(ICharacter opponent) {
        PlayAnim(attackimg);
        opponent.UnderAttack(attr.getmBaseAttr().getDamage());
    }
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.