Sunday, January 20, 2013

An Updated Guide to Implementing 2D JRPG-style Animation in Python Using Pygame

Update 11/2015: The source files can now be fetched from github.

In a previous blog post, I attempted to give you a general idea of how to implement 2D animation in Python. In that post, however, I did very little explaining of the code. In this short guide I will attempt to explain more thoroughly the process of implementing 2D JRPG-style animation in Python using Pygame so that all the major concepts are covered.

1: A Brief Introduction to 2D Graphics

Most 2D animation consists of the following components:
  1. Moving a sprite across the screen (moving its x-y coordinates on the screen)
  2. Cycling through a series of animation frames depending on which direction the figure is moving
The first step is very easy. Most tutorials you find on the internet relating to the topic of ‘Pygame animation’ cover this point. In order to move the character across the screen, you can simply utilize the built-in features of Pygame’s sprite.Sprite class, namely the .x and .y values, to move the figure. We will cover this later in our actual code.
The second point is somewhat more complicated, and it is the core concept of this tutorial. In order to animate our figure, we need a series of frames to represent the animation. These frames are usually stored in sprite sheets and can be found on many websites. If you are reading this tutorial, chances are you already have a sprite sheet in mind. If you don’t, just do a Google search. The sprite sheets can have SNES-style graphics (source):

Or more modern graphics [click the image for full size] (source):

We will be using the latter image in this tutorial; you should open the full-sized image and save it as serge_walk.png in the directory where you will be saving all your code. Don't worry, though. Once we are done with this tutorial, our little game engine will be able to use any type of 2D sprite sheet.
Now that we have our sprite sheet, we need to think about how the animation will actually work. We will essentially need four states to represent different directions of movement – left, right, up, and down. Looking at our sprite sheet above, there are a total 15 frames for each direction of movement. Before we can even begin coding, then, we have to get a set of values for each frame: the x and y values of the upper left point of each frame, the width (in pixels) of each frame, and the height (in pixels) of each frame.
You could store these values either in lists or tuples, but I prefer dictionaries for this purpose because each set of values is stored together and because the hash value becomes very convenient when cycling through animation. We want to start with the left-most frame in each set and move rightward.
Thus, we have these values for the left walking frames:
{0: (0, 109.5, 73, 109.5), 1: (73, 109.5, 73, 109.5), 2: (146, 109.5, 73, 109.5), 3: (219, 109.5, 73, 109.5), 4: (292, 109.5, 73, 109.5), 5: (365, 109.5, 73, 109.5), 6: (438, 109.5, 73, 109.5), 7: (511, 109.5, 73, 109.5), 8: (584, 109.5, 73, 109.5), 9: (657, 109.5, 73, 109.5), 10: (730, 109.5, 73, 109.5), 11: (803, 109.5, 73, 109.5), 12: (876, 109.5, 73, 109.5), 13: (949, 109.5, 73, 109.5), 14: (1022, 109.5, 73, 109.5)} 
Values for the right walking frames:
{0: (0, 219, 73, 109.5), 1: (73, 219, 73, 109.5), 2: (146, 219, 73, 109.5), 3: (219, 219, 73, 109.5), 4: (292, 219, 73, 109.5), 5: (365, 219, 73, 109.5), 6: (438, 219, 73, 109.5), 7: (511, 219, 73, 109.5), 8: (584, 219, 73, 109.5), 9: (657, 219, 73, 109.5), 10: (730, 219, 73, 109.5), 11: (803, 219, 73, 109.5), 12: (876, 219, 73, 109.5), 13: (949, 219, 73, 109.5), 14: (1022, 219, 73, 109.5)}
Values for the upward walking frames:
{0: (0, 328.5, 73, 109.5), 1: (73, 328.5, 73, 109.5), 2: (146, 328.5, 73, 109.5), 3: (219, 328.5, 73, 109.5), 4: (292, 328.5, 73, 109.5), 5: (365, 328.5, 73, 109.5), 6: (438, 328.5, 73, 109.5), 7: (511, 328.5, 73, 109.5), 8: (584, 328.5, 73, 109.5), 9: (657, 328.5, 73, 109.5), 10: (730, 328.5, 73, 109.5), 11: (803, 328.5, 73, 109.5), 12: (876, 328.5, 73, 109.5), 13: (949, 328.5, 73, 109.5), 14: (1022, 328.5, 73, 109.5)}
Values for the downward walking frames:
{0: (0, 0, 73, 109.5), 1: (73, 0, 73, 109.5), 2: (146, 0, 73, 109.5), 3: (219, 0, 73, 109.5), 4: (292, 0, 73, 109.5), 5: (365, 0, 73, 109.5), 6: (438, 0, 73, 109.5), 7: (511, 0, 73, 109.5), 8: (584, 0, 73, 109.5), 9: (657, 0, 73, 109.5), 10: (730, 0, 73, 109.5), 11: (803, 0, 73, 109.5), 12: (876, 0, 73, 109.5), 13: (949, 0, 73, 109.5), 14: (1022, 0, 73, 109.5)}   
You can hardcode these values into your code, but with this many frames it does take up a very large amount of space. And in general, hardcoding these kinds of values is considered bad practice. Nonetheless, you can see an example of hardcoding these values into the class in my previous guide.

A better idea would be to save these values in separate files and parse them at runtime. There are various ways to do this, but the easiest is simply to use Pickle. In order to do this, from the console navigate to whatever directory you want to save your files in -- it should be the same directory where the image is stored and where you intend to write all your code -- then type:
$ python
>>> import pickle
>>> left_frames = {0: (0, 109.5, 73, 109.5), 1: (73, 109.5, 73, 109.5), 2: (146, 109.5, 73, 109.5), 3: (219, 109.5, 73, 109.5), 4: (292, 109.5, 73, 109.5), 5: (365, 109.5, 73, 109.5), 6: (438, 109.5, 73, 109.5), 7: (511, 109.5, 73, 109.5), 8: (584, 109.5, 73, 109.5), 9: (657, 109.5, 73, 109.5), 10: (730, 109.5, 73, 109.5), 11: (803, 109.5, 73, 109.5), 12: (876, 109.5, 73, 109.5), 13: (949, 109.5, 73, 109.5), 14: (1022, 109.5, 73, 109.5)}
>>> pickle.dump(left_frames, open("ls.dat", "wb"))
Continue the process until you have saved all the frame values listed previously into rs.dat (right frames), us.dat (up frames), and ds.dat (down frames). We now have four files that store all our frame data, and that means we are also ready to start coding.

2. Writing the Code
We could from this point quite easily create our own class for our character, but that would require some unnecessary work. The easiest way to implement our animation is to use the built-in pygame.sprite.Sprite class. Let's create a new file named player.py in which we will save our player class data, and enter the following code (see the comments for explanations):

Now we have a class that will allow us to animate our character. The next step is simply to create a player object and start our main loop, both of which are very simple to do. In the same directory as your other files, create main.py and enter the following code:


If you run your code, you should get the following output:



The source files are available here.

There are obviously a number of ways this could can be improved. For instance, diagonal movement could be added very easily. But this guide has hopefully given you a brief introduction to animated graphics using Python and Pygame. If you have any questions, please feel free to ask. 

Happy coding!