[pygame] 4-dir movement with animation.

[pygame] 4-dir movement with animation.

Postby Mekire » Wed Feb 20, 2013 12:46 pm

The following creates a sprite that we can only move orthogonally. In order to make key-overlaps work to my satisfaction, I add key presses to a list when they are pressed. The most recently added press is the one active. Presses are removed from the list when the pertinent key is unpressed (note that pygame.key.get_pressed() would not be sufficient for this as the order is crucial). The following also animates the sprite as it walks and changes the frames appropriately when the player changes direction.

This is the sprite sheet I use (custom so please don't use in other projects without permission):
There is however, no need to download it. The code will load it from the interwebs.

Code: Select all
This script implements a basic sprite that can only move orthogonally.
Orthogonal-only movement is slightly trickier than 8-direction movement
because you can't just create a simple movement vector.
Extra work must be done to make key overlaps execute cleanly.

Written by Sean J. McKiernan 'Mekire'
import os, sys #used for os.environ and sys.exit
#Don't use from pygame.locals import *; take it from a reformed user.
import pygame as pg #I'm lazy, sue me; but, no from * import.

#This global constant serves as a very useful convenience for me.
DIRECTDICT = {pg.K_LEFT  : (-1, 0),
              pg.K_RIGHT : ( 1, 0),
              pg.K_UP    : ( 0,-1),
              pg.K_DOWN  : ( 0, 1)}

class Player:
    """This class will represent our user controlled character.
    Arguments are a Surface to draw the Player to, a rect representing the
    Players location and dimension, the speed (in pixels/frame) of the
    Player, and the players starting direction"""
    def __init__(self,rect,speed,direction=pg.K_RIGHT):
        self.rect = pg.Rect(rect)
        self.speed = speed
        self.direction = direction
        self.oldy = None #the characters previous direction every frame
        self.walk = [] #held arrow keys, in the order they were pressed
        self.redraw = False #force redraw if needed

        self.spritesheet = pg.image.load(SKEL).convert() #image loaded here
        self.image = None
        self.frame_inds = [[0,0],[1,0],[2,0],[3,0]] #loc of frames on sheet
        self.frame  = 0
        self.frames = [] #all frames off the sprite sheet
        self.timer = 0.0 #timer for animation
        self.fps   = 7.0 #fps of animation
        self.walkframes = [] #walkframes for given direction
        self.get_images() #rip images from the sprite sheet

    def get_images(self):
        """Get the desired images from the sprite sheet."""
        for cell in self.frame_inds:
            loc = ((self.rect.width*cell[0],self.rect.height*cell[1]),self.rect.size)

    def adjust_images(self):
        """update the sprites walkframes as the sprites direction changes"""
        if self.direction != self.oldy:
            if self.direction == pg.K_LEFT:
                self.walkframes = [self.frames[0],self.frames[1]]
            elif self.direction == pg.K_RIGHT:
                self.walkframes = [pg.transform.flip(self.frames[0],True,False),
            elif self.direction == pg.K_DOWN:
                self.walkframes = [self.frames[3],pg.transform.flip(self.frames[3],True,False)]
            elif self.direction == pg.K_UP:
                self.walkframes = [self.frames[2],pg.transform.flip(self.frames[2],True,False)]
            self.oldy = self.direction
            self.redraw = True

    def make_image(self):
        """update the sprites animation as needed"""
        if self.redraw or pg.time.get_ticks()-self.timer > 1000/self.fps:
            if self.walk:
                self.frame = (self.frame+1) % len(self.walkframes)
                self.image = self.walkframes[self.frame]
            self.timer = pg.time.get_ticks()
        if not self.image:
            self.image = self.walkframes[self.frame]
        self.redraw = False

    def update(self,Surf):
        """Updates our player appropriately every frame."""
        if self.walk:
            self.rect.x += self.speed*DIRECTDICT[self.walk[-1]][0]
            self.rect.y += self.speed*DIRECTDICT[self.walk[-1]][1]

def quit_game():
    """Call this anytime the program needs to close cleanly."""

def game(Player):
    """Our event loop goes here."""
    for event in pg.event.get():
        if event.type == pg.QUIT:
            quit_game() #lets us exit cleanly
        elif event.type == pg.KEYDOWN: #all key press events here.
            if event.key in DIRECTDICT:
                Player.direction = Player.walk[-1]
            elif event.key == pg.K_ESCAPE:
                quit_game() #Quit with escape key. Real games should give some warning.
        elif event.type == pg.KEYUP: #all key-up events here
            if event.key in DIRECTDICT:
                if Player.walk:
                    Player.direction = Player.walk[-1]

def main(Player,Surf):
    """The main function calls the draw functions in the order they are required.
    Then updates the entire screen."""
    game(Player) #run the event loop every frame
    Surf.fill((100,100,100)) #redraw background before player
    Player.update(Surf) #update the player
    pg.display.update() #now update the screen

def image_from_url(url):
    """This function loads an image from an url.
    Ignore this if you just care about Pygame functionality.
    If you are interested however, the importing nonsense is there
    to make this work in both py2 and py3."""
        from urllib2 import urlopen
        from cStringIO import StringIO as inout
    except ImportError:
        from urllib.request import urlopen
        from io import BytesIO as inout
    myurl = urlopen(url)
    return inout(myurl.read()) #Return value can be loaded by pygame.image.load

if __name__ == "__main__":
    #This is the file object loaded in the Player init.
    SKEL = image_from_url("http://i1192.photobucket.com/albums/aa340/Mekire/skelly.png")
    os.environ['SDL_VIDEO_CENTERED'] = '1' #Center window.
    Screen = pg.display.set_mode((500,500))
    Myclock = pg.time.Clock() #This clock will let us restrict fps.
    Myplayer = Player((250,250,50,50),3)  #Our Player instance
    while 1:
        main(Myplayer,Screen) #run main in an infinite loop
        Myclock.tick(64) #limit program FPS

(Edit: Minor changes to code.)
New Users, Read This
  • Use code tags when posting code.
  • Include any errors with your post (in code tags).
  • Describe your problem; not your chosen solution.
  • Make examples the minimum length to demonstrate your issue.
User avatar
Posts: 1711
Joined: Thu Feb 07, 2013 11:33 pm
Location: Tucson, Arizona

Return to Game Development

Who is online

Users browsing this forum: No registered users and 5 guests