Animate Mario using Pygame

Programming, Pygame, Python

Getting started

First things first I needed a game loop.

From the file game.py:

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:

background

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:

Mario

Now that the stage is set, I’ll show you how I made Mario.

Its a bit long but here is mario.py:

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:

smw_mario_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”

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”

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”

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”

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”

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

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.

 

Leave a Reply

Your email address will not be published. Required fields are marked *


6 − = one

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">