Creating a Game in Python Using PyGame – Part 3 – Adding the bad guys

All right in this section of the tutorial we are going to start adding the bad guys. If you are familiar with the changes that we made in part two it should be pretty clear to you how are are going to create these bad guys. If you haven’t already you should check out part one and part two. If you would like the full source and all of the images you can get it here.

First we are going have to create a new class based on our basicSprite class. I created a new file called and inside of that I created a class called Monster:

[code lang=”python”]
class Monster(basicSprite.Sprite):

For now we’re not going to go too crazy in our enemy AI, we’re just going to get them to choose a random direction to move and move in that direction until they hit a wall. We’ll also add in another option where after a certain amount of moves the monster will choose another random direction. This is just to add a little but more randomness into the Monster, it’s not really what we’d like to use at the end of the day but it works out all right.

Now we’ll take a look at the Monsters __init__ function:

[code lang=”python”]
def __init__(self, centerPoint, image, scared_image=None):

basicSprite.Sprite.__init__(self, centerPoint, image)
“””Save the original rect”””
self.original_rect = pygame.Rect(self.rect)
self.normal_image = image
if scared_image !=None:
self.scared_image = scared_image
self.scared_image = image

self.scared = False
“””Initialize the direction”””
self.direction = random.randint(1,4)
self.dist = 3
self.moves = random.randint(100,200)
self.moveCount = 0;

You can see that there is a bit more happening here then there is in out basic sprites. The first thing you’ll notice is the fact that the Monster is going to have two images, a normal image and a scared image. The scared image is what we will be drawn as the monster when the snake is in it’s “super” state, which is caused when it eats a super pellet.

You’ll also notice that we save a copy of the original rect, the reason that we do this is because that is where the Monster will go if it has been eaten by the snake. This probably isn’t how we will want to handle it in the end, but it’s a pretty good start.

Then we start laying out the Monster AI, the first thing to do is choose an initial direction:

[code lang=”python”]
self.direction = random.randint(1,4)

The direction is simply a random integer between 1 and 4, each integer being a direction from the Monster to travel in (1=left, 2=up, 3=right, and 4=down) the monster will then travel self.dist pixels every-time it is updated until it hits a wall, at which point it will choose another random direction.

After that we initialize the idea of moves and moveCount, this is the second degree of randomness that we add into the Monster. The idea is that after 100-200 moves the Monster will choose another random direction.

If you’ve read part 2 of this PyGame series the structure of our Monsters update function should come as no surprise:

[code lang=”python”]
def update(self,block_group):
“””Called when the Monster sprit should update itself”””
xMove,yMove = 0,0

if self.direction==1:
xMove = -self.dist
elif self.direction==2:
yMove = -self.dist
elif self.direction==3:
xMove = self.dist
elif self.direction==4:
yMove = self.dist

self.rect.move_ip(xMove,yMove) #Move the Rect
self.moveCount += 1 #Update the Move count

if pygame.sprite.spritecollideany(self, block_group):
“””IF we hit a block, don’t move – reverse the movement”””
self.direction = random.randint(1,4)
elif self.moves == self.moveCount:
“””If we have moved enough, choose a new direction”””
self.direction = random.randint(1,4)
self.moves = random.randint(100,200)
self.moveCount = 0;

The next thing that we are going to create is a function that will let us set the Monsters scared state:

[code lang=”python”]
def SetScared(self, scared):
“””Tell the monster to be scared or not”””
“””Should we update out scared image?”””
if self.scared != scared:
self.scared = scared
if scared:
self.image = self.scared_image
self.image = self.normal_image

This function is pretty self-explanatory, we just set the current state and the current image for the monster based on that state.

The next function that we need is a “eaten” function, this is the function that will be called when the monster has been eaten by the snake. This function is, again, very simply and simply moves the monster back to it’s original position and resets its state:

[code lang=”python”]
def Eaten(self):
“””Well looks like we’ve been eaten!, reset to the original
position and stop being scared”””
self.rect = self.original_rect
self.scared = False
self.image = self.normal_image

The next thing that we need to do is make sure that the snakes update function takes into account these new monsters:

[code lang=”python”]
def update(self,block_group,pellet_group,super_pellet_group,monster_group):
“””Called when the Snake sprit should update itself”””

if (self.xMove==0)and(self.yMove==0):
“””If we arn’te moving just get out of here”””
“””All right we must be moving!”””

if pygame.sprite.spritecollideany(self, block_group):
“””IF we hit a block, don’t move – reverse the movement”””

“””Check to see if we hit a Monster!”””
lst_monsters =pygame.sprite.spritecollide(self, monster_group, False)
if (len(lst_monsters)>0):
“””Allright we have hit a Monster!”””
“””Alright we did move, so check collisions”””
“””Check for a snake collision/pellet collision”””
lstCols = pygame.sprite.spritecollide(self
, pellet_group
, True)
if (len(lstCols)>0):
“””Update the amount of pellets eaten”””
self.pellets += len(lstCols)
“””if we didn’t hit a pellet, maybe we hit a SUper Pellet?”””
elif (len(pygame.sprite.spritecollide(self, super_pellet_group, True))>0):
“””We have collided with a super pellet! Time to become Super!”””
self.superState = True,{}))
“””Start a timer to figure out when the super state ends”””

You’ll notice that this function is relatively similar to what happened in part 2, except that some of the code has been moved here, and we now check to see if we have collided with any monsters.

The first thing we check is if we have collided with a wall, if so we reverse our movement, so that the snake doesn’t move. Instead of leaving the function, we continue to test to see if the snake has hit a monster. If the snake has hit a monster we call the MonsterCollide function to see what should happen. If no monster was hit we then check to see if any pellets or super pellets were hit.

If the snake eats a super pellet, we set its state to be super, then we post an event to our main loop telling it that the snake has become super, and then we start a three second time. The three second timer is basically how long the snake will stay in it’s super state and be able to eat monsters.

The MonsterCollide function is detailed below:

[code lang=”python”]
def MonsterCollide(self, lstMonsters):
“””This Function is called when the snake collides with the a Monster
lstMonsters is a list of Monster sprites that it has hit.”””

if (len(lstMonsters)< =0): """If the list is empty, just get out of here""" return """Loop through the monsters and see what should happen""" for monster in lstMonsters: if (monster.scared): monster.Eaten() else: """Looks like we're dead""",{})) [/code] The MonsterCollide function is relatively simple, It loops trough the list of monsters that the snake has collided with and decides what to do with each of them. If the monster is scared it gets eaten, and if the monster is not scared then we post the snake eaten even. No we need to look at the changes that we need to make to the LoadSprites function in the file. The only think that we are going to change is add the code to load the monsters, it will be identical to the code that we use to load the other sprites, except that monsters have a normal and scared image, and we will also switch to using RenderUpdates for our pygame groups, RenderUpdates are just like normal PyGame sprite groups except that they keep track of the rects that they have drawn to. So when you call RenderUpdates.Draw it will draw all of the sprites in the groups and then return a list of “dirty” rects. You then take the list of rects and pass it into pygame.display.update.

This way you save time by only redrawing only those sections of the screen that have changed.

The next changes that we need to make are in the MainLoop function. The first thing we have to do is pay attention to the events and timers that we have created in out other code.

[code lang=”python”]
elif event.type == SUPER_STATE_OVER:
self.snake.superState = False
“””Stop the timer”””
for monster in self.monster_sprites.sprites():
elif event.type == SUPER_STATE_START:
for monster in self.monster_sprites.sprites():
elif event.type == SNAKE_EATEN:
“””The snake is dead!”””
“””For now kist quit”””

Handling these events is pretty straight forward, the only one that will eventually change is the SNAKE_EATEN event. For now we just quite the game but in the future we will use up lives and things like that.

The next, and final change for this tutorial is the section of the code where we actually draw the sprites. As detailed above we are using RenderUpdate groups this time instead of the normal groups that we have used in the past. As a result our drawing code has to change slightly in order to pay attention to the rects that we have dirtied:

[code lang=”python”]
“””Do the Drawing”””
textpos = 0
self.screen.blit(self.background, (0, 0))
if pygame.font:
font = pygame.font.Font(None, 36)
text = font.render(“Pellets %s” % self.snake.pellets
, 1, (255, 0, 0))
textpos = text.get_rect(centerx=self.background.get_width()/2)
self.screen.blit(text, textpos)

reclist = [textpos]
reclist += self.pellet_sprites.draw(self.screen)
reclist += self.super_pellet_sprites.draw(self.screen)
reclist += self.snake_sprites.draw(self.screen)
reclist += self.monster_sprites.draw(self.screen)
“””Now Update the Display”””

As you can see not much has actually changed except for the fact that we are now paying attention to the rectangles that we have dirtied and are only updating those rather then redrawing the entire screen.

If you would like the full source of this tutorial, you can get it here.

Well that’s it for this tutorial, sorry it’s been such a long time coming, it seems that real life got in the way of my learning python. Hopefully I will get back on my schedule of updating this site once a week soon.

39 thoughts on “Creating a Game in Python Using PyGame – Part 3 – Adding the bad guys”

  1. Hey that’s really cool! I’m hoping to go back to the PyGame tutorials soon. I really enjoyed working on them when I did!

  2. first of all, this tutorial is very helpfull, great work!
    I have noticed that if u dont move the snake (i.e. u dont press any key), u’ll never hit a monster.

  3. Hi Gmac,

    Thanks for the information, I’m looking into writing a new PyGame tutorial so when I do I’ll be sure to fix that problem.

  4. Hi, I really liked this series of tutorials, and this website itself is such a great resource.

    Keep up the great work.

  5. Great tutorial — just working through it and am really turned on to Pygame thanks to it.

    One question though — I’m trying to find where SUPER_STATE_OVER gets defined since I’m getting an error that it isn’t. I haven’t been able to find it in the posted examples and am looking for a good place to put it. Thoughts?

  6. I’m glad you found your answer James. If anyone else has a similar problem SUPER_STATE is defined as follows near the top of

    [CODE lang=”python”]

  7. This is really well done! Thank you very much!
    On my machine (1GHz AMD Athlon) it runs way too fast.
    So I added the pygame.time lines to make it playable.

    50 pygame.display.flip()
    51 #################################################
    52 self.clock = pygame.time.Clock()
    53 #################################################
    54 while 1:
    56 #################################################
    57 self.clock.tick(30)
    58 #################################################
    59 self.snake_sprites.clear(self.screen,self.background)
    60 self.monster_sprites.clear(self.screen,self.background)

  8. I noticed when you kill a monster twice, it doesn’t respawn in its original space the second time. Nice game and tutorial! Thank you

  9. can you please help with the idea of creating a reflex action using pacman to eat all the food in the pellet and yet don.t get beaten by the ghost, say two ghost. I would need your assistance in python.

    Thanks sire!

  10. Can I just say what a comfort to discover a person that actually
    knows what they are talking about on the net.

    You certainly know how to bring a problem to light and make it important.

    More people have to read this and understand this side of
    your story. It’s surprising you aren’t more popular given that you certainly have the gift.

  11. By Zar May 16, 2013 – 4:12 amUse Torrent sites.First Download a Torrent downloaderdownload files / games from troernt sites.Read the comments because some of the troernts are hoax.Most of the software in the troernt sites has patches/serial/keygens or other software piracy tools. Download and use them at your own risk =’)

Leave a Reply

Your email address will not be published. Required fields are marked *