[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

Re: [pygame] Game State Switching Example



Simon Oberhammer wrote:
Thank you, kschnee... looks good to me - I will use that in my next game. But imho adding the widget/interface info+sampleclass is too much for an otherwise nice and simple cookbook-entry
 simon

ps.: And you code tells me that calling 'screen' what you call 'state' is not too far fetched ;-)
 next_screen = self.game_state.pop()

Attached is a revised version that deletes the DummyInterface class and related code, and renames "next_screen" to "next_state".
#!/usr/bin/python
## (A line needed for proper operation on Linux systems.)

"""
State Machine Demo

An example of one way to switch between the various states of a game,
such as a main gameplay screen and a status screen. There's just enough
detail to show how the idea works in practice with a fake title screen,
status screen, and a pop-up screen. You can see the number of items on
the stack flicker from 0 to 1, or 1 to 2, as you run the program.

Requirements: Python (python.org) and Pygame (pygame.org); both are free.
You could adapt this to work without Pygame if you want.

How it works:
The system switches between states by keeping a list called "game_state".
The Game class' main loop, Go(), looks at this list, pulls off the last item,
and tries to call a function named by it. This system helps prevent messy
infinite loops of function A calling B and B calling A, by having each screen
set the class' "done" flag to end its control of the program. When you want to
switch to another screen, just put the desired next screen(s) onto the stack.

"""

__author__ = "Kris Schnee"
__version__ = "2007.7"
__license__ = "Public Domain"

import os ## Imported only for the following line.
os.environ["SDL_VIDEO_CENTERED"] = "1" ## Center the graphics window.
import pygame ## Graphics/event/etc. library based on SDL.
from pygame.locals import * ## Event handling constants.

SCREEN_SIZE = (800,600)
STARTING_SCREEN = "Title Screen"

class Game:
    def __init__(self,**options):
        self.screen = options.get("screen",pygame.display.set_mode(SCREEN_SIZE))
        self.game_state = [STARTING_SCREEN] ## A stack of game screens.
        self.done = False ## Done with current screen loop?
        self.messages = [] ## Checked during event loops.

        self.font = pygame.font.Font(None,24) ## For demo purposes.
        self.clock = pygame.time.Clock() ## For FPS managment.

    def Go(self):
        """This is the main loop of the game.

        Go between various screens until the game is over.
        There's a stack of game states in self.game_state, which can have
        additional items appended and popped. If the stack is empty, or
        if this loop reaches a state not corresponding to the name of a
        function this class has, the game will end.

        The exact way this determines the function names assumes that they're
        functions of this class, named exactly as the strings appended to
        the list, minus spaces. Eg. "Title Screen" leads this function to look
        for a function called TitleScreen. Just change the "naming convention"
        line if you dislike it. If at any point no appropriate function is
        found, or if nothing is left on the stack, the game ends."""
        while True:
            if not self.game_state:
                break ## Game over!
            
            next_state = self.game_state.pop()
            print "Returned to main loop. Now switching to: "+next_state
            function_name = next_state.replace(" ","") ## Naming convention
            if hasattr(self,function_name):
                function = getattr(self,function_name)
                function()
            else:
                break ## Game over!
        print "Game over. Thanks for playing!"

    def Notify(self,message):
        """Get a message from the interface or game entities.

        These messages are handled during one of the screen loops.
        Use a dictionary format with a "headline" key, eg:
        {"headline":"Got Object","object":"Coconut"} """
        self.messages.append(message)

    def TitleScreen(self):
        """A sample screen."""

        self.done = False
        while not self.done:

            ## Handle messages from elsewhere, such as the interface.
            for message in self.messages:
                headline = message.get("headline","")
                print "Got a message: "+headline

            self.messages = []

            ## Handle events.
            for event in pygame.event.get():
                if event.type == QUIT:
                    self.done = True
                elif event.type == KEYDOWN:
                    if event.key == K_ESCAPE:
                        self.done = True

                    ## Some sample reaction to events.
                    elif event.key == K_s:
                        ## This screen will end and go to another screen.
                        self.done = True
                        self.game_state.append("Status Screen")

            ## Draw a sample set of stuff on the screen.
            self.screen.fill((0,180,180,255))
            text = "This is the Title Screen. Esc= quit, S= Status Screen."
            text_rendered = self.font.render(text,1,(255,255,255))
            self.screen.blit(text_rendered,(100,100))
            status = self.font.render("# of game states on stack: "+str(len(self.game_state)),1,(255,255,255))
            self.screen.blit(status,(100,300))

            pygame.display.update()
            self.clock.tick(20)

    def StatusScreen(self):
        """A sample screen."""

        self.done = False
        while not self.done:

            ## Handle messages from elsewhere, such as the interface.
            for message in self.messages:
                headline = message.get("headline","")
                print "Got a message: "+headline

            self.messages = []

            ## Handle events.
            for event in pygame.event.get():
                if event.type == QUIT:
                    self.done = True
                elif event.type == KEYDOWN:
                    if event.key == K_ESCAPE:
                        self.done = True

                    ## Some sample reaction to events.
                    elif event.key == K_t:
                        ## This screen will end and go to another screen.
                        self.done = True
                        self.game_state.append("Title Screen")
                    elif event.key == K_p:
                        ## Return to _this_ screen after the pop-up.
                        self.done = True
                        self.game_state.append("Status Screen")
                        self.game_state.append("PopUp Screen")

            ## Draw a sample set of stuff on the screen.
            self.screen.fill((0,0,180,255))
            text = "This is the Status Screen. Esc= quit, T= Title Screen, P= Pop-Up Screen."
            text_rendered = self.font.render(text,1,(255,255,255))
            self.screen.blit(text_rendered,(100,100))
            status = self.font.render("# of game states on stack: "+str(len(self.game_state)),1,(255,255,255))
            self.screen.blit(status,(100,300))

            pygame.display.update()
            self.clock.tick(20)

    def PopUpScreen(self):
        """A sample screen, resembling a pop-up dialog or something."""

        self.done = False
        while not self.done:

            ## Handle messages from elsewhere, such as the interface.
            for message in self.messages:
                headline = message.get("headline","")
                print "Got a message: "+headline

            self.messages = []

            ## Handle events.
            for event in pygame.event.get():
                if event.type == KEYDOWN:
                    if event.key == K_ESCAPE:
                        ## This screen ends w/o putting another on stack.
                        self.done = True

            ## Draw a sample set of stuff on the screen.
            self.screen.fill((200,200,255),(50,50,700,500))
            text = "This is a Pop-Up Screen. Esc= leave this screen."
            text_rendered = self.font.render(text,1,(0,0,0))
            self.screen.blit(text_rendered,(100,100))
            status = self.font.render("# of game states on stack: "+str(len(self.game_state)),1,(0,0,0))
            self.screen.blit(status,(100,300))

            pygame.display.update()
            self.clock.tick(20)

## Run the demo.
pygame.init()
g = Game()
g.Go()