Subscribe to Dogstopper's Code Mania        RSS Feed
-----

State Machines

Icon 6 Comments
Often, in games, a single screen needs to do many things, from having a title screen to actually playing the game, to having a game over screen, or a pause screen. Well, unlike all the other games that I have showed you thus far have only had one state: the game state. The Game State, up to this point, has been hardcoded into the host JPanel, but in order to perform the tasks of a state machine, we must now allow the JPanel to have a series of "states" so that it can independently call methods of each state and each one performs differently.

So, let's define a simple abstract GameState class that can be used by each state.
import java.awt.Graphics;


public abstract class GameState {

	public abstract void update();
	public abstract void render(Graphics g);
	public abstract void handleInput();
	
}



Simple enough? Now, this allows the JPanel to have a currentState, of which it calls these three methods. If you move from one part of the game to another, you simply change states. The only qualification is that the new state must subclass GameState and implement the methods. Let me show you a quick example of a title screen (Uses InputManager class from my last blog entry). All this code does is presents a screen and then waits for the user to push the right arrow key to move to the next state (LevelState).

public class TitleState extends GameState {

        // This is our custom JPanel
	private SpacePanel root;

        // This is the background.
	private Image background;
	
	public TitleState(SpacePanel master) {
		root = master;
                 
                // * Note, look into my Image Loader blog entry to find out
                // How to do this.
		background = Loader.loadImage("images/space-2.jpg", root);
	}
	
        // We handle input here, and if the user presses the right 
        // arrow key, then we move to the level screen
	@Override
	public void handleInput() {
		if (InputManager.rightPressed)
			root.changeState(SpacePanel.LEVEL);
	}

        // Let's just paint some instructions
	@Override 
	public void render(Graphics g) {
		// Paint the background
		g.drawImage(background, 0, 0, null);
		
		// Paint the options
		Graphics2D g2d = (Graphics2D)g;
		g2d.setColor(Color.blue);
		
		Font font = new Font("Courier New", Font.BOLD, 16);
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
				RenderingHints.VALUE_ANTIALIAS_ON);
		g2d.setFont(font);
		FontMetrics fm = root.getFontMetrics(font);
		
		// Print title and author
		String str = "Attack!";
		int x = SpacePanel.PANELWIDTH/2 - fm.stringWidth(str)/2;
		int y = 2*fm.getHeight();
		g2d.drawString(str, x, y);
		
		font = font.deriveFont(26f);
		g2d.setFont(font);
		fm = root.getFontMetrics(font);
		
		str = "Written by Stephen Schwahn";
		x = SpacePanel.PANELWIDTH/2 - fm.stringWidth(str)/2;
		y += fm.getHeight();
		g2d.drawString(str, x, y);
		
		g2d.setColor(Color.red);
		
		// Paint the text
		str = "Right Arrow - Begin Single Player";
		x = SpacePanel.PANELWIDTH/2 - fm.stringWidth(str)/2;
		y = SpacePanel.PANELHEIGHT/2 - fm.getHeight()/2;
		g2d.drawString(str, x, y);
		
		// Print escape text
		str = "Escape - Quit";
		y += fm.getHeight()*2;
		g2d.drawString(str, x, y);
	}

        // In other states, this would update things like player positions,
        // But there is nothing to update in a Title State
	@Override
	public void update() {

	}
}



Now, if that whole render() business is difficult, do not fear, I will be writing an in-depth tutorial on the Font class and how it is used. Next, we need to define the SpacePanel, or the one that calls the states' methods() on a Timer (see blog entry on "Frame Rate in Java"). Also, we have a series of constants and a state changer method.

public class SpacePanel extends JPanel implements ActionListener {

	private static final long serialVersionUID = -7410866854040854373L;
	
	// The constants are defined in constructor.
	public static final int PANELWIDTH;
	public static final int PANELHEIGHT;

        public static final int TITLE = 0;
        public static final int LEVEL = 1;
        public static final int GAME_OVER = 2;
	
        // Represents the current state
	GameState currentState;

        // Our input manger class.
	InputManager input;
	
        // This is called a static constructor. Basically,
        // it grabs the window size constants form our JFrame
        // and set them to the final static ints.
	static {
		PANELWIDTH = SpaceFrame.FRAMEWIDTH;
		PANELHEIGHT = SpaceFrame.FRAMEHEIGHT;
	}
	
	public SpacePanel(SpaceFrame master) {
		
		input = new InputManager(master);
		
		currentState = new TitleState(this); 
	
                // Lets KeyListener work correctly
		setFocusable(true);
		requestFocus();
		
                // Sets our InputManager as the listener
		addKeyListener(input);
		
		// Set a timer to go off every 15 milliseconds. That gives a frame rate 
		// of about 67.
		Timer time = new Timer(15, this);
		time.start();
	}

        // Every time Timer executes, update everything
	@Override
	public void actionPerformed(ActionEvent arg0) {		
		updateAll();
	}
	
	private void updateAll() {
                // Tell the current state to handle
                // input however it needs to.
		currentState.handleInput();
		
		// Tell the current state to handle
                // update however necessary.
		currentState.update();
		
                // Calls paintComponent
		repaint();
	}

        @Override
        public void paintComponent(Graphics g) {
                // Tell current state to paint!
		currentState.render(g);
	}
	
	public void changeState(int command) {
		if (command == TITLE) {
			currentState = new TitleState(this);
		}
		else if (command == LEVEL) {
			currentState = new LevelState(this);
		}
		// And so on...
                else {
                    System.out.println("That state does not exist");
                    System.exit(0);
                } 
	}	
}



See how that code just simply allows for tons of possible game states and all it has to do is swap the one that we use. That's very handy! So, now all your input management, updates, and paints should go into their own state classes and switched in the way that I have shown.

I know it's a hard topic, but I hope you understand. Good Luck!
:)

6 Comments On This Entry

Page 1 of 1

erik.price 

02 March 2010 - 06:18 PM
This method is so much better than the tangled mess of booleans and ifs that I use to designate pause screens, game over, and the playing state of games. Good tutorial :)
1

Dogstopper 

03 March 2010 - 04:40 AM
I just realized that more states can be managed better through the use of a switch statement...
public void changeState(int command) {
        switch(command) {
            case TITLE:
                currentState = new TitleState(this);
                break;
            case: LEVEL
                currentState = new LevelState(this);
                break;
                
            // And so on...
            default:
                System.out.println("That state does not exist");
                System.exit(0);
                 
         }
}      

0

cfoley 

04 March 2010 - 02:14 AM
I like it but why pass an int to the changeState Method? Why not:

public void changeState(LevelState newState) {
    currentState = newState;
}



This way when you create a new state, you just implement the interface and don't have to worry about updating other parts of the code.
0

Dogstopper 

04 March 2010 - 04:24 AM
That was actually my original design. However, I found that it has several extensibility flaws. Later, I want to potentially implement a "State Stack" which allows me to "push" or "pop" states based on which one. But, actually, there is no advantage or disadvantage to this over the integers, and I kinda like using flags. :online2long:
0

SixOfEleven 

04 March 2010 - 09:11 AM
Looking good. State management is something that beginning game programmers have a problem with. Look forward to seeing more tutorials.
1

ShroomiaCo 

08 March 2013 - 06:13 PM
Can somone give me a main class for such a program, I like this set up a lot and want to use it but I cannot figure out how to add a main class x.x
Thanks
0
Page 1 of 1

August 2020

S M T W T F S
      1
2345678
9 101112131415
16171819202122
23242526272829
3031     

Recent Entries

Search My Blog

Recent Comments

2 user(s) viewing

2 Guests
0 member(s)
0 anonymous member(s)