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 basicMonster.py and inside of that I created a class called Monster:

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:

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
    else:
        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:

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:

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.rect.move_ip(-xMove,-yMove)
        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:

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
        else:
            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:

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:

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"""
        return
    """All right we must be moving!"""
    self.rect.move_ip(self.xMove,self.yMove)
    
    if pygame.sprite.spritecollideany(self, block_group):
        """IF we hit a block, don't move - reverse the movement"""
        self.rect.move_ip(-self.xMove,-self.yMove)
    
    """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!"""
        self.MonsterCollide(lst_monsters)
    else:
        """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
            pygame.event.post(pygame.event.Event(SUPER_STATE_START,{}))
            """Start a timer to figure out when the super state ends"""
            pygame.time.set_timer(SUPER_STATE_OVER,0)
            pygame.time.set_timer(SUPER_STATE_OVER,3000)

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:

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"""
            pygame.event.post(pygame.event.Event(SNAKE_EATEN,{}))

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 PyMan.py 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.

elif event.type == SUPER_STATE_OVER:
    self.snake.superState = False
    """Stop the timer"""
    pygame.time.set_timer(SUPER_STATE_OVER,0)
    for monster in self.monster_sprites.sprites():
        monster.SetScared(False)
elif event.type == SUPER_STATE_START:
    for monster in self.monster_sprites.sprites():
        monster.SetScared(True)
elif event.type == SNAKE_EATEN:
    """The snake is dead!"""
    """For now kist quit"""
    sys.exit()

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:

"""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"""
pygame.display.update(reclist)

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.

selsine

del.icio.us del.icio.us

35 Responses to “Creating a Game in Python Using PyGame – Part 3 – Adding the bad guys”

  1. L. Humbert
    Says:

    I decided to put your example on the Nokia 770 – this comes with batteries included — and more: it is possible to run PyGame-based applications on it. At my website you’ll find other examples, which are based on PyGame to:
    the wellknown Pong, the Huhnspiel – you may take a look at:
    http://haspe.homeip.net:8080/cgi-bin/pyblosxom.cgi/bilder/2006/
    TNX for this tutorial …

    Ludger

  2. selsine

    selsine
    Says:

    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!

  3. Luis Eduardo
    Says:

    Somebody help me?

  4. selsine

    selsine
    Says:

    Hey Luis, what sort of help are you looking for? If it’s with Python or PyGame I could try to help you.

  5. Gmac
    Says:

    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.

  6. selsine

    selsine
    Says:

    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.

  7. Malcolm
    Says:

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

    Keep up the great work.

  8. selsine

    selsine
    Says:

    Hey Malcolm, thanks for saying that! I’m working on a new PyGame tutorial after catching the bug again during this years PyWeek. Lots of fun!

  9. James
    Says:

    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?

  10. James
    Says:

    Actually, I just downloaded the example .zip file and found it. Thanks again!

  11. selsine

    selsine
    Says:

    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 snakeSprite.py:

    SUPER_STATE_START = pygame.USEREVENT + 1
    SUPER_STATE_OVER = SUPER_STATE_START + 1
    SNAKE_EATEN = SUPER_STATE_OVER + 1
  12. tube
    Says:

    work’s done the way it must be..! ^^

  13. sfondi
    Says:

    Ich fand gute und wichtige Informationen – dir zu danken.

  14. michelle
    Says:

    Interessare, molto interessante. Come avete fatto questo?

  15. commento
    Says:

    Luogo molto buon:) Buona fortuna!

  16. inno
    Says:

    luogo fine, sapete..

  17. francesco totti
    Says:

    Interfaccia comoda, colori piacevoli, buoni!

  18. hentay
    Says:

    Guter Aufstellungsort, ja!

  19. arcuri manuela
    Says:

    Interesting comments.. :D

  20. anna tatangelo
    Says:

    Was kann ich sagen? Wirklich gute Arbeit erledigt mit dem AufstellungsortÂ…

  21. verga
    Says:

    luogo interessante, soddisfare interessante, buon!

  22. racconti
    Says:

    Grand emplacement! La conception est merveilleuse!

  23. pagine bianche
    Says:

    Very valuable information you have here. Thanks..

  24. nudo
    Says:

    WOW!! I like it!

  25. amore
    Says:

    um… buoni, realmente buoni luogo e molto utile;)

  26. gogle
    Says:

    pagine piuttosto informative, piacevoli =)

  27. googie
    Says:

    Luogo interessante. Info molto importante, grazie molto!

  28. goole
    Says:

    The information I found here was rather helpful. Thank you for this.

  29. jahoo
    Says:

    Nice site you have!

  30. seso
    Says:

    Great site! Good luck to it’s owner!

  31. Bhaaluu
    Says:

    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:
    55
    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)

  32. Seboss
    Says:

    How could I insert a delay before a monster respawns ?

  33. josh
    Says:

    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

  34. Pygame Tutorials and Pygame Assignment 1 « SCHS - Computer Engineering and Programming
    Says:

    [...] Part Three – Adding the Bad Guys [...]

  35. Sunny
    Says:

    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!

Leave a Reply

 

Popular Posts