RL Tutorial Parts 01 and 02 - Introduction and Graphics
The rest of this blog is going to go into extensive detail, but here is the complete code1 as of this tutorial:
If you’re looking to follow along with my code examples (as opposed to using whatever language/graphics framework you’re already comfortable with), the following represents a complete list of what you need. Before you start downloading things, as of writing this post, there have been issues getting libtcod working with python 64bit, so to be safe I recommend you download 32bit2. In addition, pygame is only built for 32bit at this time, so if you use 64bit it’s highly likely that you’ll get a bunch of errors. The vast-majority of build errors related to using these tools is due to using the wrong version.
- Python version 2.7.XX [Available here]
- The Doryen Library (libtcod) 1.6.X [Available here]
- Pygame 1.9.1 for python 2.7 [Available here]
- Some type of python editor or IDE, I use the text editor Sublime Text 3 [Available here]3. A better option for a newer programmer would be to use IDLE, which comes prepackaged with your python installation.
For most of these things, you simply use the intaller to install the library or program. The doryen library is a little different. A lot of people have trouble using libtcod because the main tutorial out there recommends putting the libtcodpy folder (available in the python folder of the downloaded zip file) into the project folder with your program. This tends to be hit or miss. Instead, I usually recommend copying the libtcodpy folder into the python directory where python is installed. The proper address would look something like this /Python Installation/Lib/site-packages/libtcodpy/
Once you’ve installed the libtcod library, you should copy the SDL2.dll and libtcod.dll files from the libtcod zip download into the project directory with your program.
There are a near infinite number of ways for you to split your project into different files (in python, they’re called modules), but in order to keep things simple we’re only going to have two:
- main.py which will hold all function definitions and the game loop, itself
- constants.py which will hold all ‘magic number’ defintions like screen width and height
On your own, you can decide how to split your project up if you want it to be more compartmentalized. But for now, having a file dedicated to easy to change variables that affect the whole game, and a file dedicated to the game itself, will be fine.
Getting Started Coding
The first thing we want to do is boot up our IDE and create our two files, main.py and constants.py. They can be empty for now.
A good practice to get into is to have both the files open on your screen somehow. Sublime Text uses a tab-based system, similar to a modern web-browser, which allows me to easily switch back and forth between project files. Whatever IDE you use will probably have it’s own way of dealing with multiple project files.
First, we’re going to import the required modules into our main.py file. Which is easy enough.
1 2 3 4 5 6 #3rd party modules import libtcodpy as libtcod import pygame #game files import constants
A couple of things to note here, you probably noticed that we’re also going to import the empty constants file. We’re going to start adding to that file pretty soon so we might as well import it right now. It’s also important to touch on how the import function works, it sort of copy and pastes the module into the top of the file (it’s a bit more complicated than that, but close enough for our purposes). The import function will make the code for those modules available to us under their respective module names, this will be more obvious in the next part.
The first function we’re goint to write is going to be called
game_initialize, and its job will be to do all the start-up business that allows our game to actually run. Things we put into this function will run only once, usually at startup.
1 2 3 4 5 6 7 8 9 def game_initialize(): '''This function initializes the main window, and pygame''' global SURFACE_MAIN #initialize pygame pygame.init() SURFACE_MAIN = pygame.display.set_mode( (constants.GAME_WIDTH, constants.GAME_HEIGHT) )
Let’s unpack this function a little bit. First, we make a new global variable called SURFACE_MAIN. Global variables can be very useful, but they’re dangerous since they exist in every scope. My personal rule of thumb is to minimize using them where possible, but if I do need to use one, make it obvious. The way we do this is by using Google’s style guide regarding global variables. Basically make them all-caps and use the underscore.
The next thing the
game_initialize function does is call the
init function from within the pygame module. This function prepares the modules for use in your program. If you want a more thorough introduction to the purpose of
init you should check the pygame documentation.
The last thing that our
game_initialize function does is create a new surface and assigns our global variable to it. This creates what is called the ‘display surface’ which is a special kind of surface.
About Pygame Surfaces
Surfaces are probably the most misunderstood part of pygame for newer programmers. A surface represents any visual element that can be drawn, even text!
The process of drawing one surface onto another is referred to as blitting. In order for anything we draw to be displayed to the player, we’ll need to blit it onto the display surface. More on this below.
pygame.display.set_mode function takes a tuple, which is a type of data structure, in this case it wants two integers, a game width and height in pixels. Here we pass in our first constant from the constants module.
Which brings us to the constants.py file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import pygame pygame.init() #Game sizes GAME_WIDTH = 800 GAME_HEIGHT = 600 #Color definitions COLOR_BLACK = (0, 0, 0) COLOR_WHITE = (255, 255, 255) COLOR_GREY = (100, 100, 100) #Game colors COLOR_DEFAULT_BG = COLOR_GREY #SPRITES S_PLAYER = pygame.image.load("data/python.png")
Let’s go through this line by line. First we’re going to import pygame and then intialize it (
lines 2 and 4). We need to do this because we’re going to create a constant that is a pygame surface so we can change it later if we want to.
lines 7 and 8), we define the two values we’re using in the
pygame.display.set_mode function in the
game_initialize function of our main.py document. By setting GAME_WIDTH to 800 and GAME_HEIGHT to 600, our game display will be 800 pixels wide and 600 pixels high.
Next we’re going to define some color presets. these are tuples and are integers representing the RGB format. You can use an online color generator to make up your own colors, but for now we don’t need anything too complicated.
line 16, we’re going to define a ‘game color’. A color definition is just us defining the colors, like black or white. By creating a variable called COLOR_DEFAULT_BG and assigning a color to it, if we later decide we want all the backgrounds to be white instead of grey, we only need to change it in one place.
line 19 we’re creating a sprite using
the pygame.image.load function. You can use any image you like, the one I’m using there is a sprite I converted from the Dawnlike tileset. Feel free to download a sprite you want to use, or you can use the one I’m using4.
Like I mentioned above, any visual element in pygame is a surface. So we’ll need to draw this sprite onto the display surface whenever we want to show a representation of where the player is. More on that later.
The Last Two Functions
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 def game_draw(): '''This function draws the game''' #clear the surface SURFACE_MAIN.fill(constants.COLOR_DEFAULT_BG) #TODO draw the map #draw the character SURFACE_MAIN.blit(constants.S_PLAYER, ( 200, 200 )) #update the display pygame.display.flip() def game_main_loop(): '''In this function, we loop the main game''' game_quit = False while not game_quit: #get player input events_list = pygame.event.get() #TODO process input for event in events_list: if event.type == pygame.QUIT: game_quit = True #draw the game draw_game() #quit the game pygame.quit() exit()
game_draw function for now and let’s talk about the
game_main_loop function. This function incorporates a while loop, so the first thing we do is define a way of breaking out of it. By creating a game_quit variable and setting it to false, making our
while loop conditional on game_quit staying false gives us an easy way to break out. Which we do if the player pressues the escape button at the upper right of the display window.
In the while loop, the first thing we do is process player input. The
events_list = pygame.event.get function returns a list of every thing the player has done since the last time the loop ran. Any keys that were pushed, if the player resizes the screen (which they can’t do), etc. For now, all we do is loop through every event in that list and look to see if the player exited. If they did, we break the loop.
Next in the while loop, we simply call the
draw_game function. We’ll define this next.
Finally, if the loop is broken, we want the game to close. We start by calling the
pygame.quit function, and then the
exit function. Calling these functions, as opposed to simply letting the program end, ensures that everything closes out properly and we don’t have any resources being tied up for no reason.
Next, the draw function.
This function is actually pretty simple. first, we clear the surface by filling it with our default background color. For now we have a comment that we want to draw a map, which we’ll do in another tutorial. Next, we draw the character by blitting our character sprite onto the display surface, SURFACE_MAIN. Finally, we update the display surface with the
pygame.display.flip function, which updates the screen to show what we’ve drawn on SURFACE_MAIN.
Finally, the last thing we need to do are actually call the functions
game_main_loop, whereby we’ll run the actual game. This is done simply by adding a couple of lines to the bottom of our main.py file.
1 2 3 4 5 ##EXECUTE GAME## if __name__ == '__main__': game_initialize() game_main_loop()
For now we’re going to ignore the if statement5. By calling these two functions, we’re executing all the code we’ve written up until now. If you’ve followed along (or copied the code from the links at the top of the page), when you run main.py you should get something that looks like this:
And that’s everything we’re going to cover this time, next we’ll talk about the map system and how we make everything up of tiles.
These are html pages, so if you want to use them, don’t right-click and ‘save-as’ instead, go to the page and copy and paste. ↩
If your system is a 64bit system, don’t worry, you can use 32bit installations without a problem. ↩
Sublime Text is a paid program. You can download and use it to try it out, similar to winrar. But it’s a fantastic program that deserves your support if you intend to use it regularly. The price is a little steep ($70), but a small price to pay if you can swing it. ↩
The original tileset is called the Dawnlike tileset created by DragonDePlatino and DawnBringer (who created the color palette). So far I’ve only used this tile, but it’s possible I’ll use more. The original set is in 16x16, but this one has been enlarged to 32x32. My edited tile is [Available here]. DrawnLike, the project, lives [here]. ↩
In truth, you could safely leave this out of the program and probably be fine, however, it’s a good idea to get into the habit of wrapping the execution of your main function up in this if-statement because it prevents that code from running if you were to import this file as a module into a different python program. If all of that seems scary to you, simply take it out if you want (remember to unindent the
game_main_loopfunctions) or ignore it. If you want a more in depth explaination of why python programmers use this, check out this explaination on stack exchange. ↩