Animate Mario using Pygame
August 14th, 2009 by Maarten Hus under Programming, Pygame, Python. 1 Comment.
My first console was the SNES and my first game ever was Super Mario World. So naturally when I encountered Pygame it sounded interesting to create my own animated Mario. Just as I dreamed of when I was a kid.
Getting started
First things first I needed a game loop.
From the file game.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | import pygame import mario import world screen = pygame.display.set_mode((336, 432)) pygame.display.set_caption("Mario Mayhem") clock = pygame.time.Clock() world = world.World() mario = mario.Mario((250, 355), world) running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False mario.handle_event(event) mario.handle_animation() screen.fill(pygame.Color('black')) screen.blit(world.image, world.rect) screen.blit(mario.image, mario.rect, mario.area) pygame.display.flip() clock.tick(15) |
Nothing special here I import Pygame and start a loop that ends when there is a pygame.QUIT event. The loop paints a new screen at 15 frames per second. And lets the Mario instance handle events on line 19, and handles animation on line 20.
Line 22, 23 and line 24 are used to draw Mario and the World. But before that happens the screen is painted black. I do this because if you don’t everything gets drawn over each other. You can comment this line out to see it, its a pretty weird effect.
World
Mario needs something to walk on so I looked for an image online and found this one:

The image has various ridges and cliffs so I needed a way to make these solid so Mario could walk on them. I decided to put all the solid locations in a list. The list stores the X positions and the Y position of the ridges. Later I’ll use these locations to calculate if Mario is standing on something.
The file world.py looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import pygame class World(object): def __init__(self): self.image = pygame.image.load("images/background.png") self.rect = self.image.get_rect() # [[x1, x2, y], [[x1, x2, y], etc] self.solids = [ [-10, 290, 355], [70, 165, 291], [38, 120, 228], [70, 149, 164], [119, 216, 116], [259, 336, 132] ] |
Mario
Now that the stage is set, I’ll show you how I made Mario.
Its a bit long but here is mario.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | import pygame from states import walking, running, jumping, falling, standing class Mario(pygame.sprite.Sprite): """This class represents Mario""" def __init__(self, position, world): pygame.sprite.Sprite.__init__(self) self.world = world self.image = pygame.image.load('images/smw_mario_sheet.png') self.rect = self.image.get_rect() self.actions = {"front": (245, 75, 20, 30), "back": (285, 75, 20, 30), "left": (165, 75, 20, 30), "left-walking1": (45, 75, 20, 30), "left-walking2": (5, 75, 20, 30), "left-jump": (165, 115, 20, 30), "left-fall": (125, 115, 20, 30), "left-run1": (165, 155, 20, 30), "left-run2": (125, 155, 20, 30), "left-run3": (85, 155, 20, 30), "left-turn": (5, 115, 20, 30), "right": (205, 75, 20, 30), "right-walking1": (325, 75, 20, 30), "right-walking2": (365, 75, 20, 30), "right-jump": (205, 115, 20, 30), "right-fall": (245, 115, 20, 30), "right-run1": (205, 155, 20, 30), "right-run2": (245, 155, 20, 30), "right-run3": (285, 155, 20, 30), "right-turn": (365, 115, 20, 30) } self.action = "left" self.area = pygame.rect.Rect(self.actions[self.action]) self.rect.topleft = position self.walking_state = walking.Walking(self) self.running_state = running.Running(self) self.jumping_state = jumping.Jumping(self) self.falling_state = falling.Falling(self) self.standing_state = standing.Standing(self) self.state = self.standing_state self.direction = "left" self.jump_frame = 0 self.jump_stop_frame = 13 self.jump_forward = 0 self.jump_air_power = 3 #sideways power in jump self.jump_speed = 5 self.fall_speed = 5 self.fall_tolerance = 3 self.walking_speed = 5 self.running_speed = 8 def handle_event(self, event): if self.state != self.jumping_state: self.check_if_falling() if event.type == pygame.KEYDOWN: self.action = self.state.handle_event(event) else: self.action = self.state.no_event() def handle_animation(self): self.check_bounds() self.area = pygame.rect.Rect(self.actions[self.action]) def check_if_falling(self): for solid in self.world.solids: if (self.rect.x >= solid[0] and self.rect.x <= solid[1] and solid[2] > (self.rect.y - self.fall_tolerance) and solid[2] < (self.rect.y + self.fall_tolerance)): self.rect.y = solid[2] return False self.state = self.falling_state return True def check_bounds(self): if self.rect.x < -10: self.rect.x = 335 if self.rect.x > 335: self.rect.x = -5 if self.rect.y > 432: self.rect.y = 0 |
Mario gets animated by using a sprite sheet. A sprite sheet contains all the different states (walking, running, flying) of a character, in this case Mario. Because a walking image of every Mario in existence is no fun, I needed to find someway to cut all these Mario’s apart. Luckily there is a way to do that, without actually splitting the sprite sheet into separate images.
Every Sprite has three things an Image a Rect and an Area. The Rect is the position of the sprite on the screen (x, y, width, height). The Area of a Sprite determines what part of the Image gets drawn. Basically an Area allows you to crop the Image of a Sprite. All I needed to do them was to calculate the Area’s of every Mario I wanted to use. But I had to save these Area’s somehow where I could easily access them. So I used a dict the key’s of the would be the name of the Area. This dict is called actions and is located on line 13.
Here’s the sprite sheet:

Starting from line 40 you can see that I’m assigning states. These states represent what Mario can do such as jumping or running. I use states to determine how Mario reacts to user input, and which Mario to use for animation. But more on that later.
The function handle_event on line 61 handles all user input or lack thereof by passing them over to the current state of Mario. The states will decide what animation Mario should be using by returning a string which responds to a key in the actions dict. This action is then used by the function handle_animation on line 70 which picks the right Area from the actions dictionary.
As you can see in the constructor Mario holds a reference to the World he’s in. In the function check_if_falling on line 74 you can see that Mario uses this reference to check if he is standing. This is where the “solid list” from world.py comes in. If Mario is between X1 and X2, and Y is almost the same height Mario will stop falling.
Mario also checks his bounds in the function check_bounds. If he exceeds the limits of the screen he is teleported to the opposite side like in Super Mario Bros 2.
States
Like I said before Mario’s animation and user input is handled by a version of the state pattern. Before I’ll explain how let’s do the why first.
Handling Mario events may seem easy at first, when you press the spacebar he jumps, and when you press “d” he goes right. But Its not that simple! What happens when you press the spacebar twice, surly Mario doesn’t double jump. The solution is to create a massive amount of if-statements to accommodate for every situation. This, and I’ve tried, leads to an unreadable wall of code, and most importantly you just can’t see where to add new functionality.
I needed a way to make the code more intuitive. Enter the state pattern, a pattern according to the GoF to be applicable when: “Operations have large, multipart conditional statements that depend on the object’s state”. My interpretation: use this when you have a massive switch-statement.
Let’s look at my implementation: state.py in package “states”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | class State(object): """This is the template for all the other states""" def __init__(self, mario): self.mario = mario def jump(self): self.mario.state = self.mario.jumping_state return self.mario.state.jump() def fall(self): self.mario.state = self.mario.falling_state return self.mario.state.fall() def walk(self): self.mario.state = self.mario.walking_state return self.mario.state.walk() def run(self): self.mario.state = self.mario.running_state return self.mario.state.run() def stand(self): self.mario.state = self.mario.standing_state return self.mario.state.stand() def get_frame(self, frame_set): self.frame += 1 if self.frame > (len(frame_set) - 1): self.frame = 0 return frame_set[self.frame] def __str__(self): return "Default state" |
This is the template state, It is supposed to be extended by the other states. It provides defaults actions such as walk, run and jump that the child should override when needed.
Lets look at the function run on line 19. Run changes Mario’s internal state to running_state, and then asks Mario’s state to call the run method. The run method will then return the correct animation.
It’s pretty simple when you call jump, fall, walk, run or stand it changes Mario’s state and returns the right animation.
State also provides a way to handle frames of animation in the function get_frame. This function takes a frame_set: a list of strings each representing a Mario, and returns its current frame. When the last frame is returned it resets self.frame to 0. This creates a loop of animations. Some states later such as Running and Walking use get_frame to make Mario run/walk.
Standing
Lets look at the simplest state: Standing in standing.py in package “states”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | import pygame import state class Standing(state.State): def __init__(self, mario): super(Standing, self).__init__(mario) self.left_stand = "left" self.right_stand = "right" def handle_event(self, event): if event.key == pygame.K_a: self.mario.direction = "left" return self.walk() elif event.key == pygame.K_d: self.mario.direction = "right" return self.walk() elif event.key == pygame.K_SPACE: return self.jump() elif event.key == pygame.K_j: return self.run() else: return self.no_event() def no_event(self): return self.stand() def stand(self): if self.mario.direction == "left": return self.left_stand else: return self.right_stand def __str__(self): return "standing" |
The __init__ calls the parent __init__ with mario as an argument. This way the state has a reference to the Mario instance. The constructor also assigns some animation variables, left_stand and right_stand. The values are key’s in the actions dictionary in mario.py.
The next function is handle_event this function handles all the keyboard events for the Standing state. Because standing is a neutral state, every action changes the state of Mario. Spacebar makes him jump, “a” makes him walk and so does “d”, pressing and holding “j” makes Mario run.
When there are no events Standing just returns its own stand function. The stand function handles the actual animating part, it just returns left_stand or right_stand based on the direction of Mario.
There is also a __str__ function this function is used for debugging. I used this to determine if Mario reacted properly to events.
Walking
Here’s is walking.py in the package “states”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | import pygame import state class Walking(state.State): def __init__(self, mario): super(Walking, self).__init__(mario) self.left_walking_frames = ["left", "left-walking1", "left-walking2"] self.right_walking_frames = ["right", "right-walking1", "right-walking2"] self.frame = 0 def handle_event(self, event): if event.key == pygame.K_a: self.mario.direction = "left" return self.walk() elif event.key == pygame.K_d: self.mario.direction = "right" return self.walk() elif event.key == pygame.K_SPACE: if self.mario.direction == "left": self.mario.jump_forward = -5 else: self.mario.jump_forward = 5 return self.jump() elif event.key == pygame.K_j: return self.run() else: return self.no_event() def no_event(self): return self.stand() def walk(self): if self.mario.direction == "left": self.mario.rect.move_ip(-self.mario.walking_speed, 0) return self.get_frame(self.left_walking_frames) else: self.mario.rect.move_ip(self.mario.walking_speed, 0) return self.get_frame(self.right_walking_frames) def __str__(self): return "walking" |
Walking has the exact same functions as Standing, the difference is the implementation. handle_event its just as you expect it to be, when Mario is already walking and you press “a” you continue walking. Jumping is a little different this time around. When you’re walking and you jump you also jump forwards, instead of just upwards. Thats what the variable mario.jump_forward is for.
When no events are fired Mario will stop Walking and change state to Stand.
Now the actual walking happens in the function walk on line 30. To move Mario forward I move Mario’s Rect to the left or right depending on Mario’s direction. After that its a matter of returning the right animation frame by calling State’s get_frame.
Running
Lets make Mario run: this is running.py in the package “states”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | import pygame import state class Running(state.State): def __init__(self, mario): super(Running, self).__init__(mario) self.left_running_frames = ["left-run1", "left-run2", "left-run3"] self.right_running_frames = ["right-run1", "right-run2", "right-run3"] self.frame = 0 def handle_event(self, event): if event.key == pygame.K_a: self.mario.direction = "left" return self.run() elif event.key == pygame.K_d: self.mario.direction = "right" return self.run() elif event.key == pygame.K_SPACE: if self.mario.direction == "left": self.mario.jump_forward = -8 else: self.mario.jump_forward = 8 return self.jump() elif event.key == pygame.K_j: return self.run() else: return self.no_event() def no_event(self): return self.walk() def run(self): if self.mario.direction == "left": self.mario.rect.move_ip(-self.mario.running_speed, 0) return self.get_frame(self.left_running_frames) else: self.mario.rect.move_ip(self.mario.running_speed, 0) return self.get_frame(self.right_running_frames) def __str__(self): return "running" |
Running is not that much different from Walking. The frames are different and it has a slightly higher jump_forward power.
Jumping
This is Jumping in jumping.py in the package “states”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | import pygame import state class Jumping(state.State): def __init__(self, mario): super(Jumping, self).__init__(mario) self.left_jump = "left-jump" self.right_jump = "right-jump" def handle_event(self, event): if event.key == pygame.K_a: self.mario.jump_forward = 0 self.mario.rect.move_ip(-self.mario.jump_air_power, 0) elif event.key == pygame.K_d: self.mario.jump_forward = 0 self.mario.rect.move_ip(self.mario.jump_air_power, 0) return self.jump() def no_event(self): return self.jump() def jump(self): self.mario.jump_frame += 1 self.mario.rect.move_ip(self.mario.jump_forward, -self.mario.jump_speed) if self.mario.jump_frame > self.mario.jump_stop_frame: if self.mario.jump_forward != 0: if self.mario.direction == "left": self.mario.jump_forward = -4 else: self.mario.jump_forward = 4 return self.fall() if self.mario.direction == "left" : return self.left_jump else: return self.right_jump def __str__(self): return "jumping" |
Lets start with handle_event, this time its smaller than usual and thats because most input doesn’t affect Mario during jumping. It is possible however to steer Mario while he is jumping. The power of this movement is called mario.jump_air_power.
The actual jumping happens in jump, this function moves Mario upwards and depending on events left or right. Mario only jumps a specific height, line 26 checks if that height is met. If that happens jumping is aborted and the state is changed to Fall. If the height is not met then Mario continues to jump.
Falling
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | import pygame import state class Falling(state.State): def __init__(self, mario): super(Falling, self).__init__(mario) self.left_fall = "left-fall" self.right_fall = "right-fall" def handle_event(self, event): if event.key == pygame.K_a: self.mario.jump_forward = 0 self.mario.rect.move_ip(-self.mario.jump_air_power, 0) elif event.key == pygame.K_d: self.mario.jump_forward = 0 self.mario.rect.move_ip(self.mario.jump_air_power, 0) return self.fall() def no_event(self): return self.fall() def fall(self): self.mario.rect.move_ip(self.mario.jump_forward, self.mario.fall_speed) if not self.mario.check_if_falling(): self.mario.jump_frame = 0 self.mario.jump_forward = 0 return self.stand() if self.mario.direction == "left": return self.left_fall else: return self.right_fall def __str__(self): return "falling" |
Falling shares handle_event with Jumping, the only difference is that Falling returns fall instead of jump. Fall itself makes Mario move down. Each time fall is called, Fall will check if Mario has hit solid ground by calling mario.check_if_falling. If he is standing some variables are reset and the state is set to Standing. If Mario is not hitting solid ground he’ll continue falling.
Conclusion
Well thats it thats how I made a animated Mario. Its not perfect however, some variables could use a better name, and it could use more animations such as turning. I also regret that I didn’t save a version of the insane if-else-tree that I used for state previously. Its the first time that I’ve actually used a pattern, and it shows. I think the code is pretty clear, Its really easy to add a ducking Mario for instance. Maybe one day I’ll create the whole level. That means figuring out how they move the screen through the level, as Mario walks by. But thats something for another post.
Here is the complete package:

