What a state to get into…



This week we are going to be adding our game state code to the basic (and reusable) game loop code from last week. The structure of the code is going to be very simple. In the Update function we are going to choose a particular block of code according to current game state (using the If…, Elif…, Elif…, Else… conditional structure you have encountered previously). Each block of code will check input and game data and simply change the current game state to a new one if necessary. Essentially all of our game logic will be captured in this function – although we will create some other ‘helper’ functions to complete repetitive tasks without having to copy and paste code (if you find yourself doing this, it is a sure sign that your code needs a better structure – time to ‘refactor’).

The Draw code can now be extremely simple. It will just dumbly draw items according to the current game state.

image

The diagram above shows a simple design of the 7 new game states we need (not including the existing ‘Quit’ state) and the events we will be looking for in the Update function to trigger transitions between the states. When you design a game you should definitely have something like this diagram early on to help you plan your project – pretty much all games have some sort of state structure within them.

In our simple game, the transition events will either be mouse clicks from the player, or internal timers. The timers will determine how long it takes for Sam to draw his guns, how quickly he fires and also the duration of the animation of firing (essentially how long we remain in the ‘Fire’ game state, where we blit the gun flashes, before moving to the ‘Lose’ game state, where we don’t).

Code Snippets

OK well to be honest that is the hard bit done, the rest is just typing Winking smile

Update

The update function captures the diagram above in code form:

def Update():
global current_keys, last_keys, current_mouse, last_mouse, \
gamestate, time_to_draw, time_to_fire, total_time

last_keys = current_keys
current_keys = pygame.key.get_pressed()

last_mouse = current_mouse
current_mouse = pygame.mouse.get_pressed()

elapsed_ms = pygame.time.Clock().tick(
30)
total_time = total_time + elapsed_ms

if gamestate == "Start":
if LeftMouseClicked():
gamestate =
"Ready"
elif
gamestate == "Ready":
if total_time > time_to_draw:
total_time =
0
gamestate = "Draw"
elif
LeftMouseClicked():
gamestate =
"TooEarly"
elif
gamestate == "Draw":
if total_time > time_to_fire:
total_time =
0
gamestate = "Fire"
if
LeftMouseClicked():
gamestate =
"Win"
elif
gamestate == "Fire":
gamestate =
"Lose"
elif
gamestate == "TooEarly":
if LeftMouseClicked():
Reset()
gamestate =
"Start"
elif
gamestate == "Win":
if LeftMouseClicked():
Reset()
gamestate =
"Start"
elif
gamestate == "Lose":
if LeftMouseClicked():
Reset()
gamestate =
"Start"
else
:
print("Update Error: Unknown gamestate")

Draw

The Draw function simply draws the screen according to the current game state:

def Draw():
global background_surface, screen, sam_surface_set

screen.blit(background_surface, (
0,0))

if gamestate == "Start":
DrawTitle(
"Click to Start")
elif gamestate == "Ready":
DrawSam(
"Ready")
elif gamestate == "Draw":
DrawSam(
"Draw")
elif gamestate == "Fire":
DrawSam(
"Fire")
elif gamestate == "TooEarly":
DrawTitle(
"You'll hang for this!")
DrawSam(
"Innocent")
elif gamestate == "Win":
DrawTitle(
"Got me you varmint")
DrawSam(
"Shot")
elif gamestate == "Lose":
DrawTitle(
"Take that!")
DrawSam(
"Ready")
else:
print("Draw Error: Unknown gamestate")

pygame.display.flip()
return

Additional Helper Functions

To avoid the noob error of cutting and pasting repeated code, and to make the code easier to understand and manage, several additional helper functions are handy to add. The first checks for a mouse click by seeing of the left mouse button is newly pressed – i.e. it is pressed this time around the game loop, but not last time – this prevents a single mouse click triggering loads of mouse click transitions in one go.

def LeftMouseClicked():
global current_mouse, last_mouse
return current_mouse[0] and not last_mouse[0]

Note that this function returns the boolean True or False so you can use it anywhere that python needs a boolean. This is an example of a function with a return type and they are very handy things!

We also can draw a nice centred bit of large text using the DrawTitle function:

def DrawTitle(string):
global big_font, screen
text = big_font.render(string,
1, (255, 128, 128))
shadow = big_font.render(string,
1, (0, 0, 0))
textpos = text.get_rect()
textpos.centerx = screen.get_rect().centerx
shadowpos = shadow.get_rect()
shadowpos.center = (textpos.centerx +
2, textpos.centery + 2)
screen.blit(shadow, shadowpos)
screen.blit(text, textpos)

Finally, Sam himself is represented by a set of surfaces – different ones for the game states. To store these I use a dictionary structure to allow us to get the surface we are after using a nice, friendly, string name. I create this dictionary just once inside the Init function, along with the global fonts used above:

# Load the other sprite images
sam_surface_set = { \
"Ready" : pygame.image.load("images/YosemiteSamReady.png").convert_alpha(), \
"Draw" : pygame.image.load("images/YosemiteSamAim.png").convert_alpha(), \
"Fire" : pygame.image.load("images/YosemiteSamFire.png").convert_alpha(), \
"Innocent" : pygame.image.load("images/YosemiteSamInnocent.png").convert_alpha(), \
"Shot" : pygame.image.load("images/YosemiteSamShot.png").convert_alpha() \
}


# Create some reusable fonts
big_font = pygame.font.Font(None, 72)
little_font = pygame.font.Font(
None, 36)

then I use it inside a handy DrawSam function that will draw the correct Sam according to the name that you pass in:

def DrawSam(state_string):
global screen
sam_surface = sam_surface_set[state_string]
target = sam_surface.get_rect()
target.center = screen.get_rect().center
screen.blit(sam_surface, target)

That’s it! In your version of the game try adding some new game states or swap out the images for a different theme of game. Perhaps a high score screen showing the fastest reaction time achieved in this session of the game?

Comments


Comments powered by Disqus