Saturday, September 25, 2010

Creating a 2D RPG using pygame: Part 2

Now on to creating the main character. The first thing that went through my mind when I decided to create a character for this game was, "what will he look like?". Well considering  the time and effort it would take to create an image for a main character that I would be happy with, I decided it would be better to settle with a square representation in place of an animated sprite.

One thing about python is that it really emphasizes simplicity and readability when coding, so I will try to reflect that in the code that I write. This helps make debugging easier as each function is like a check point which reduces the code you need to search through to find your error. So whenever you have a block of code that is used to reach a small goal, that code should probably go into a function. Here's an example of what I mean:



def set_background(screen, color = (0,0,0)):
    '''Takes a surface object screen that is meant to be the display surface.
   
    Returns a surface.
    '''
    background = pygame.Surface(screen.get_size())
    background.fill(color)
    background = background.convert()
    screen.blit(background,(0,0))
    return background
This background can represent the future back drop of the game. If a background is not generated, the sprite's image will be present every where it had previously been. And since nothing is wiping it clean, the display gets nice colorful streaks across it.

OK so now that we have a background, we want to draw something too that background. Well the easiest thing to do is to create test shapes that will be switched out for real images at a later time. A quick way to do this is to build a function like this:
def create_test_shape(size = (32,32), color = (255,255,255)):
    '''Creates a surface object that we can use in place of real images.
    
    Returns a surface.
    '''
    surface = pygame.Surface(size)
    surface.fill(color)
    surface = surface.convert()
    return surface
This will create a white square that can be used in place of what will be the main character. One this I want to mention about this function is the convert method. Using this method changes the pixels of the object to match those of the display surface for faster blitting. So its usually a good idea to do this by default.

Well now there is a function to create a test surface that can represent the main character, now all I need is the actual class that I'm going to use.

MOVEMENTS = {pygame.K_LEFT : (-1,0),
                        pygame.K_RIGHT : (1,0),
                        pygame.K_UP : (0,-1),
                        pygame.K_DOWN : (0,1),
                        }
class Main_Character(pygame.sprite.Sprite):
    '''The main character of the game.
    
    Attributes
        pos     -- position on the screen
        image   -- surface object representing the player
        rect    -- rectangle of the the main character
    '''
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.pos = [0,0]
        self.image = create_test_shape()
        self.rect = self.image.get_rect()
        self.size = self.rect.size
        
    def update(self):
        self.rect.topleft = self.pos
        
    def move_char(self,keys_pressed, rect):
        '''For each key that is currently pressed down, move
        the rectangle of the player in the right direction
        '''
        pixels = 2
        
        for key in keys_pressed.list:
            self.pos[0] = self.pos[0] + MOVEMENTS[key][0] * pixels        
            self.pos[1] = self.pos[1] + MOVEMENTS[key][1] * pixels
        self.rect.move(self.pos)
        self.update()

There are many important lines of code here and I'll highlight the ones that I believe are important:

  1. line: 15 - When initializing your sprite class you must always remember to call the __init__ function of the parent class. The main character clas will not function properly if this line is left out.
  2. line: 21 - Every sprite should have an update method. When I get to groups I'll explain a bit more in detail why this is important.
  3. Each sprite needs to have a self.rect and a self.image. If you know what duck typing is then you'll know why its important for each derivative of a sprite to have one. basically, for other pygame functions to interact properly with the child sprites, they need to have these properties.
  4. line: 1 - This is the dictionary I used to define my movement directions. I basically just mapped each key to a tuple that represents the unit vector of the direction I want my character to move when that key is pressed.
  5. line: 24 - If you're not sure what keys_pressed is, I'll explain that after I show my up dated while loop.
Now that I've got all these nice new functions and classes I'm gonna take it for a test run and see what happens with this loop:
def main():
    '''Starting point of this game'''
    pygame.init()
    screen = pygame.display.set_mode((640,480))
    background = set_background(screen)
    character = Main_Character()
    allsprites = pygame.sprite.RenderPlain((character))
    
    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit(0)
            if event.type == KEYDOWN:
                KEYSPRESSED.add_event(event)
            if event.type == KEYUP:
                KEYSPRESSED.remove_event(event)
            else:
                pass
        
        if len(KEYSPRESSED.list):
            character.move_char(KEYSPRESSED, background.get_rect())
        allsprites.update()
        screen.blit(background,(0,0))
        allsprites.draw(screen)
        pygame.display.flip()
Now you can see that the KEYSPRESSED is basically just a specialized list. I'm not sure i even really need it at this point. I was thinking that i might do some sorting on the keys that are pressed when they are added into sub lists so I can better handle when a character is trying to do something like access their inventory or something and the user doesn't want to move their character. Baby steps.

One of the cool things included in the pygame libraries are the sprite groups. Basically they are specialized lists that can perform methods related to the sprites on all sprites in that group. Say you want to keep track of all the bad guys on the screen and have a sub group with all the bad guys within visible range of the player. Currently I have not made extensive use of groups so I can not really go into much more detail.

If you're following this and want to look into what I'm working on before my next post, I'm learning to use Tiled map editor with pyknic's map parser to have an easy way to create maps.


To be continued...

3 comments:

  1. This is pretty great information, Thanks! I am just wondering when you are planning on continuing? Also should your main display also have an update function?

    ReplyDelete
  2. This is pretty great information, Thanks! I am just wondering when you are planning on continuing? Also should your main display also have an update function?

    ReplyDelete