Simple Sprite Animation in Python 2.7

a picture of the simple animation
a picture of the simple animation

the codes for this simple animation can be downloaded here.

last time i posted my sample of the snake game written in Python 2.7 using PyGame. like what i already said it was supposed to be a part of my self training on python game programming and it went pretty well. i have one comment though, i was trying to accomplish a lot in one sample project. so i decided that from this week on i’ll take it one step at a time by creating tiny projects that i could use as a template when i finally have something big to code. well not really big for i’m only programming games for leisure, the more approriate term would be complex, something more complex to code. and so for my first tiny project i’m going to have simple sprite animation.

anyway, since i’m a lover OOP i decided i’ll try creating a collection of modules that will function as a class library or, if lucky, a personal framework for creating my type of python games. my plan is to build this collection in the weeks to come. so let us begin.

THE UTILITIES LIBRARY

this library will contain classes that could be useful to most if not all other classes. classes that are not part of the game design but useful in carrying out particular tasks common to most other classes. i named the file ‘lib_utils.py’ the prefix ‘lib_’ is supposed to tell which of my python files function as libraries and which are not. it currently contains only one class which is the StaticFunction class that i use for creating static methods on my classes.

#used for creating static functions
class StaticFunction():
    def __init__(self, function):
        self.__call__ = function

THE SPRITES BASE LIBRARY

the sprites base library which i named ‘lib_spritesbase.py’ will contain classes that are essential to sprite animation. currently it contains classes such as Direction, ImageSet, and SimpleSprite

Direction class

#class enumerating valid directions
class Direction():
    UP, DOWN, RIGHT, LEFT, UPRIGHT, UPLEFT, DOWNRIGHT, DOWNLEFT = \
        'Y+', 'Y-', 'X+', 'X-', 'X+Y+', 'X-Y+', 'X+Y-', 'X-Y-'

    #checks if a given direction is valid
    def is_direction(direction):

        #compares direction to provided direction values
        if direction == Direction.UP:
            return True
        elif direction == Direction.DOWN:
            return True
        elif direction == Direction.RIGHT:
            return True
        elif direction == Direction.LEFT:
            return True
        elif direction == Direction.UPRIGHT:
            return True
        elif direction == Direction.UPLEFT:
            return True
        elif direction == Direction.DOWNRIGHT:
            return True
        elif direction == Direction.DOWNLEFT:
            return True

        #no match was found
        return False

    #makes the function 'is_direction' static to Direction class
    is_direction = StaticFunction(is_direction)

if you checked out my last post, this class and the SnakeMove class mentioned there are basically the same. the only thing i did was to give it a name that is not tied into any game and add other possible directions of movement in a game like the up right, up left, down right, and down left.

ImageSet class

#stores a collection of images to be used by a sprite
class ImageSet():
    def __init__(self, img_up=None, img_down=None, img_right=None \
                , img_left=None , img_upright=None, img_upleft=None \
                , img_dowright=None, img_downleft=None):

        #direction was used as key assuring that one direction corresponds to
        #one image
        self.images = {Direction.UP:img_up, Direction.DOWN:img_down \
            , Direction.RIGHT:img_right, Direction.LEFT:img_left \
            , Direction.UPRIGHT:img_upright, Direction.UPLEFT:img_upleft \
            , Direction.DOWNRIGHT:img_dowright, Direction.DOWNLEFT:img_downleft}

    #gets the image for a given direction
    def get_image(self, direction=None):
        #parameter validation
        if direction == None:
            raise Exception('Direction cannot be null.')
        if Direction.is_direction(direction) != True:
            raise Exception('Invalid direction.')

        return self.images[direction]

it’s not that i perfectly adhere to coding standards but we know how difficult it is whenever we don’t know the right parameters to a particular function provided in a third party class library, especially when working with weakly typed languages like python. because of this i always try to come up with a form of enumeration of valid values and provide some sort of validation no matter how shallow they maybe sometimes.

the ImageSet will be used as a container of all images that a simple sprite will use. since there could be a lot of ways to pass the filenames of images to a sprite class i made it a point that the my sprite classes will only accept an instance of ImageSet.

SimpleSprite class

class SimpleSprite(pygame.sprite.Sprite):
    def __init__(self, direction=None, position=None, images=None):

        #parameter validation
        if None in [direction, position, images]:
            raise Exception('None of the parameters could be equal to "None"')
        if Direction.is_direction(direction) != True:
            raise Exception('Invalid direction.')
        if isinstance(images, ImageSet) != True:
            raise Exception('Images assigned not of type ImageSet.')

        self.imageset = images
        self.position = position
        self.change_direction(direction)

    def change_direction(self, direction):
        #parameter validation
        if Direction.is_direction(direction) != True:
            raise Exception('Invalid direction.')
        if self.imageset.images[direction] == None:
            raise Exception('No image set for given direction.')

        self.image = image.load(self.imageset.get_image(direction))
        self.size = self.image.get_size()
        self.rect = self.image.get_rect()
        self.rect.topleft = self.position
        self.direction = direction

    #must be overridden in a sub-class
    def move(self):
        pass

SimpleSprite will be the base class of my simple sprites, meaning those sprites that has only one image per direction. as shown in the code it has a change_direction function and a stub of move function. i included change_direction since a change of direction doesn’t necessarily entail moving away from one’s position. the move function on the other hand may be omitted but i chose to include it to make it appear more like a base class.

THE SPRITES

i decided to put all my sprites in one file which i named ‘sprites.py’. currently it only has one class which is the Arrow class. the Arrow class is simply a sprite with an arrow for an image. since the whole point of this mini-project is to be able to move around a surface using a sprite that changes its appearance accordingly, i simply downloaded an image of an arrow here and rotated it to point to different directions.

class Arrow(SimpleSprite):
    def __init__(self, direction, position, images):
        super(Arrow, self).__init__(direction, position, images)

    def change_direction(self, direction):
        super(Arrow, self).change_direction(direction)

    def move(self, direction=None, limits=None):
        #parameter validation
        if Direction.is_direction(direction) != True:
            raise Exception('Invalid direction.')
        if limits == None:
            raise Exception('Limits must not be empty.')

        #gets size of step and creates new position
        newposition = self.position
        if direction in [Direction.UP, Direction.DOWN]:
            stepsize = self.size[1]
            if direction == Direction.UP:
                newposition = [self.position[0], (self.position[1]-stepsize)%limits[1]]
            else:
                newposition = [self.position[0], (self.position[1]+stepsize)%limits[1]]
        elif direction in [Direction.RIGHT, Direction.LEFT]:
            stepsize = self.size[0]
            if direction == Direction.RIGHT:
                newposition = [(self.position[0]+stepsize)%limits[0], self.position[1]]
            else:
                newposition = [(self.position[0]-stepsize)%limits[0], self.position[1]]

        self.position = newposition
        self.rect.topleft = newposition

as shown, initialization of this sub-class as well as the change_direction function simply calls that of the parent class or super class. also notice that the move function was overridden here. i intended the move function to be overridden because characters in game would not probably always move in the same way. once in a while we introduce characters that behave differently.

in the case of the Arrow class movement simply means moving to the next cell specified by a direction if we are going to take the whole surface as a grid.

THE CONFIGURATION FILE

i decided to have a separate file for configuration which i named ‘conf_arrow.py’. it isn’t really necessary but i decided to come up with it anyway. this will be loaded by the main game module everytime the game starts. this way, i can have multiple configurations with minimal modification to the main game module. with different configuration files i’ll be able to test different settings by simply changing a line in the main game module which loads the configuration to be used.

import lib_spritesbase
from lib_spritesbase import Direction, ImageSet, StaticFunction

class ArrowConfig:
    SCREENSIZE = [640, 480]
    INITIAL_DIRECTION = Direction.LEFT
    UPDATE_SPEED = 50
    INITIAL_POSITION = [0, 0]
    IMAGES = None
    BACKGROUND = 0

    def init():
        ArrowConfig.IMAGES = ImageSet('sprites/up2.png', 'sprites/down2.png' \
                , 'sprites/right2.png' , 'sprites/left2.png')

    init = StaticFunction(init)

in this file we set the size of the screen, the initial direction of the sprite, the speed by which the display is updated, the starting position of our sprite, the color of the background, and the set of images to be used. the ArrowConfig class will hold the configuration.

THE GAME MODULE

i named the main game module ‘arrow.py’. the filename of the module is not very crucial, i just figured i would name it after the sprite that i’m going to use in the game in it.

import pygame
from pygame import *

import sprites
from sprites import Arrow, Direction

import conf_arrow
from conf_arrow import ArrowConfig

pygame.init()
ArrowConfig.init()

#global variable initialization
updatetime = pygame.time.get_ticks() + ArrowConfig.UPDATE_SPEED
screen = pygame.display.set_mode(ArrowConfig.SCREENSIZE)
isdone = False

#sprite initialization
arrow = Arrow(ArrowConfig.INITIAL_DIRECTION, ArrowConfig.INITIAL_POSITION \
            , ArrowConfig.IMAGES)

while isdone == False:
    screen.fill(ArrowConfig.BACKGROUND) #color screen with default color
    direction = None
    for e in event.get():
        #checks key press and sets direction or end game
        if e.type == KEYUP:
            if e.key == K_ESCAPE:
                isdone = True
            elif e.key == K_UP:
                direction = Direction.UP
            elif e.key == K_DOWN:
                direction = Direction.DOWN
            elif e.key == K_RIGHT:
                direction = Direction.RIGHT
            elif e.key == K_LEFT:
                direction = Direction.LEFT

            #if sprite is facing wrong direction replace with appropriate sprite
            #else move sprite by one step to specified direction
            if arrow.direction != direction:
                arrow.change_direction(direction)
            else:
                arrow.move(direction, ArrowConfig.SCREENSIZE)

    currenttime = pygame.time.get_ticks()
    global updatetime
    if currenttime >= updatetime:
        screen.blit(arrow.image, arrow.rect)
        pygame.display.update()
        updatetime = currenttime + ArrowConfig.UPDATE_SPEED

since the contents of this module is pretty long. let’s try to take it few lines at a time.

import pygame
from pygame import *

import sprites
from sprites import Arrow, Direction

import conf_arrow
from conf_arrow import ArrowConfig

the first few lines are used for importing classes from the other modules mentioned earlier. observing the import lines, it is noticeable that i used * when importing from pygame. to be honest here i still don’t know the classes i need for a game by their names so i simply included all. however the following import lines are generally preferred since it specify the classes to be used from one class library and/or module. it will reduce ambiguity and possible overhead.

pygame.init()
ArrowConfig.init()

what follows is the initialization for pygame and our configuration.

#global variable initialization
updatetime = pygame.time.get_ticks() + ArrowConfig.UPDATE_SPEED
screen = pygame.display.set_mode(ArrowConfig.SCREENSIZE)
isdone = False

#sprite initialization
arrow = Arrow(ArrowConfig.INITIAL_DIRECTION, ArrowConfig.INITIAL_POSITION \
            , ArrowConfig.IMAGES)

next we initialize some variables to be used in the game. ‘updatetime’ holds the next time by which the display will update, the ‘screen’ represents the game screen, while ‘isdone’ states whether the game is over or not. ‘arrow’ on the other hand represents our sprite.

the implementation of the game flow is just a little simpler than the sample snake game.

    screen.fill(ArrowConfig.BACKGROUND) #color screen with default color
    direction = None
    for e in event.get():
        #checks key press and sets direction or end game
        if e.type == KEYUP:
            if e.key == K_ESCAPE:
                isdone = True
            elif e.key == K_UP:
                direction = Direction.UP
            elif e.key == K_DOWN:
                direction = Direction.DOWN
            elif e.key == K_RIGHT:
                direction = Direction.RIGHT
            elif e.key == K_LEFT:
                direction = Direction.LEFT

            #if sprite is facing wrong direction replace with appropriate sprite
            #else move sprite by one step to specified direction
            if arrow.direction != direction:
                arrow.change_direction(direction)
            else:
                arrow.move(direction, ArrowConfig.SCREENSIZE)

first, the screen will be filled by the set background color in our configuration file. next, we are going to check if any key was pressed. if ‘esc’ was pressed, we need to end the game by setting the ‘isdone’ variable to True. Otherwise, we get the direction then change a sprites direction or move it.

	
    currenttime = pygame.time.get_ticks()
    global updatetime
    if currenttime >= updatetime:
        screen.blit(arrow.image, arrow.rect)
        pygame.display.update()
        updatetime = currenttime + ArrowConfig.UPDATE_SPEED

we then check if it’s already time to update the screen. if it is, we update the screen and set the next update time. else we’re going to do nothing.

if you got Python2.7 installed with PyGame. you can try running this game template by downloading the code here. by default .py files open with python so you can just double click on ‘arrow.py’. or you can open console, redirect to the folder where you saved the files then type ‘python arrow.py’.

feel free to comment on this post. have a nice day everyone!

One thought on “Simple Sprite Animation in Python 2.7

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s