Porting from CodeSkulptor to PyGame


Hello and welcome.

The basic thing to understand when thinking about porting your CodeSkulptor code over to Python and PyGame is that the vast majority of the code will migrate over with no changes or fairly minor changes. I say this up front so that you can know that you're not going to have to rewrite large chunks of code from the ground up. Most of the time, if a change is needed it will simply be in the form of replacing a call to a simpleGUI function with a call to the PyGame equivalent function. Rest assured that you will find an equivalent function in PyGame -- it may be called a bit diferently or behave slightly differently, but if you are familiar with simpleGUI calls it will likely seem eerily familiar.

Having said that, there are a few differences that I will try to explain as best as I can. One of the main differences is that we need to create our own while() loop which runs continually while the program is running (until the program is ended by the user) -- in effect, this keeps our event queue running, our draw handler drawing and the program "on." I'll get into the specifics later. Another main difference is in how rendering is handled in PyGame: in PyGame, we need to be more explicit in our instructions to the renderer in telling it to refresh our scene with new images while discarding old images. Again, I'll get into the specifics later. Okay, let's begin.

Python and PyGame:

The first thing you need is Python and PyGame installed on your computer. So, head off to Python to grab the latest release of Python and PyGame to grab the latest matching release of PyGame. Install Python and then Pygame. You should now be ready to begin. So, run IDLE from your Python menu, choose "file --> new window" and from the new window that appears choose "file --> save as" choosing a location and filename. I would choose a location away from the main Python install directory for simplicity sake to keep everything related to your project tidy and isolated. If you are using images and sounds, then it's best to keep all of that stuff away from the main Python install path. Also, don't forget to save with a ".py" extension. So, save as "my_project.py" to directory "c:\cs_port\my_project", for example. You now have your .py file ready for use -- this is equivalent to a blank CodeSkulptor window.

Imports and Initialization:

We begin with our imports as normal -- excluding simplegui, of course. We also need to include PyGame and Operating System speific imports:

# import modules
import os
import pygame

# pygame specific locals/constants
from pygame.locals import *

# some resource related warnings
if not pygame.font: print('Warning, fonts disabled')
if not pygame.mixer: print('Warning, sound disabled')
You can copy the above into the top of your file and also include the other libraries your CodeSkulptor code uses. That's essentially PyGame ready for use except for a call to intialize the library. I don't really know exactly what the call does, but it doesn't really matter from our perspective -- we'll just trust PyGame needs to be initialzed and this is how it's done:
# initializations
The next thing we need to do is create a window for our program to run in -- we can think of this like a frame and canvas in CodeSkulptor:
# a bit similar to CodeSkulptor frame creation -- we'll call the window the canvas
canvas = pygame.display.set_mode((640, 480))
The parameters for the display are its size and the caption sets the window title. I've named the display variable "canvas" here for similarity to CodeSkulptor since quite often we will be making calls to draw on it like we do in CodeSkulptor -- elsewhere, often times you'll see it called the screen.

Helper Functions:

I have two helper functions I pulled from another tutorial and updated to work with Python 3. They are for loading images and loading sounds and perform some sanity checks, generate error meesages and do file path checking. You can just copy them in after the previous section:

# Pygame Wrapper functions -- resource loading sanity checks
# Taken from the "Monkey tutorial" and updated for 3.3 by me
# load Image:
# A colorkey is used in graphics to represent a color of the image
# that is transparent (r, g, b). -1 = top left pixel colour is used.
def load_image(name, colorkey=None):
    fullname = os.path.join('data\images', name)
        image = pygame.image.load(fullname)
    except pygame.error as message:
        print('Cannot load image:', name)
        raise SystemExit(message)
    if colorkey is not None:
        image = image.convert()
        if colorkey is -1:
            colorkey = image.get_at((0,0))
        image.set_colorkey(colorkey, RLEACCEL)
        image = image.convert_alpha()
    return image, image.get_rect()

# Load Sound
def load_sound(name):
    class NoneSound:
        def play(self): pass
    if not pygame.mixer:
        return NoneSound()
    fullname = os.path.join('data\sounds', name)
        sound = pygame.mixer.Sound(fullname)
    except pygame.error as message:
        print('Cannot load sound:', name)
        raise SystemExit(message)
    return sound
You can change the os.path.join('data\images', name) and os.path.join('data\sounds', name) to point where you want. These currently say, "look where the .py file is being run from and then go to data\images or data\sounds beneath that location for the files."

Fonts and Colours:

PyGame requires us to create font and colour objects before they can be used. So, I found it best to create them here along with all of the other Pygame specific code we're adding:

# need to create fonts and colour objects in PyGame
#fontObj = pygame.font.Font('ARBERKLEY.ttf', 32)
#fontObj2 = pygame.font.Font('ARBERKLEY.ttf', 24)
fontObj3 = pygame.font.Font(pygame.font.match_font('timesnewroman'), 32)

gold_color = pygame.Color(255, 215, 0)
white_color = pygame.Color(255, 255, 255)
These are just examples of the ones I needed, so you can create whatever you might need or leave them as placeholders until you see what you might need. The fonts files are loaded without a path, so I'm loading them from the same directory that the .py file is in. I've commented out the calls to specific font files since it will generate an error if the file can not be found. Each font size is a separate object, so you need an object for each size you will need even if it's the same font. There is also the pygame.font.match_font() function you can call passing in a font name and it will look for it on the system, so that function call can be substituted for the string font filename above. That's how fontObj3 is getting its font. You can call pygame.font.get_fonts() in IDLE and it will return a list of all the fonts available (hint, then call .sort() on the list). If it can't find the font, then a default one is used. If you plan to distribute your project, then I recommend you do as above in the first two cases and actually provide the font file along with the code and other resources -- unless you know your font will be available on all systems. Times New Roman, for example, is not installed on Linux by default. So, fontObj3 should use the PyGame default font when this is run on a Linux machine that has not installed the font.

Moving To the Bottom Of Our File:

Okay, that should complete everything we need to do for the top of our file and we should be clear to start copying over our CodeSkulptor code following on. However, let's just hold up on that task for the moment and move to the bottom of the file. Add in a good number of line breaks to give us a bit of distance and then create a function defintion like the following:

# call this function to start everything
# could be thought of as the implemntation of the CodeSkulptor frame .start() method.
def main():
    # initialize loop until quit variable
    running = True
    # create our FPS timer clock
    clock = pygame.time.Clock()    

#---------------------------Frame is now Running-----------------------------------------
    # doing the infinte loop until quit -- the game is running
    while running:
        # event queue iteration
        for event in pygame.event.get():
            # window GUI ('x' the window)
            if event.type == pygame.QUIT:
                running = False

            # input - key and mouse event handlers
            elif event.type == pygame.MOUSEBUTTONDOWN:
                # just respond to left mouse clicks
                #if pygame.mouse.get_pressed()[0]:
            elif event.type == pygame.KEYDOWN:

            # timers
            #elif event.type == timer_example:
        # the call to the draw handler
        # FPS limit to 60 -- essentially, setting the draw handler timing
        # it micro pauses so while loop only runs 60 times a second max.
#-----------------------------Frame Stops------------------------------------------

    # quit game -- we're now allowed to hit the quit call
    pygame.quit ()
This is one of the places we differ from CodeSkulptor in that we need to actually write the function that "makes things go." In CodeSkulptor we just call frame.start() and the draw handler starts drawing, the event handlers start looking for events, etc. In PyGame, we need to be more explicit and write a function main() that will accomplish this for us. The meat of this function is an infinite (until exited by the user) while loop. So, we initialize a boolean "running" to True and then create a while loop that runs as long as that boolean is True. Within this loop we iterate over an event queue using for event in pygame.event.get() and then check for the event type and execute some code based on that event -- here is where we execute our CodeSkulptor functions we created as our various event handlers. We can call those same event handler functions from our CodeSkulptor code to handle the same events in PyGame. We may need to tweak the code in those handlers, but hopefully you can see how the structure of the events is the same -- we're just doing a bit of extra work to explicitly check for the events on a continual basis, but once we have an event we deal with it in the same way we do in CodeSkulptor -- with our event handlers.

I've commented out the calls to my specific input handlers and the example timer (more on timers below), but hopefully you can see the structure of what's going on. The call to pygame.time.Clock() after the boolean intialization creates a clock which gets set to 60 frames per second at the bottom of the while loop -- this keeps the while loop from running beyond that limit and is also set to 60 to match the rate at which the CodeSkulptor renderer runs at (recall the CodeSkulptor draw handler runs 60 times per second). An indeed, right above clock.tick(60) we can see our call to our draw_handler(canvas) in PyGame. Again, we just need to go one step further by explicitly calling the draw handler function in PyGame, but once we call it our CodeSkulptor function and code will do the same job as it did in CodeSkulptor -- we will need to tweak some function calls, but the basic structure remains the same.

A couple of more steps and we'll have a skeleton file that we can use to base our CodeSkulptor port on. We next need to actually call the main() function we created above. We can do that with the following bit of code:
# this calls the 'main' function when this script is executed
# could be thought of as a call to frame.start() of sorts
if __name__ == '__main__': main() 	 	
As the comment indicates, this can be likened to a frame.start() call in CodeSkulptor. The only difference is we've had to write the contents of the function that .start() represents in the form of our main() function above. So, copy the code into your file as the last line. It simply calls the main() function when the script is run.

Rendering (Drawing):

Okay, we are now very close to having a skeleton file that we can actually run and have it create something like a frame with canvas in CodeSkulptor terms. If you're anything like me, then you want to be able to draw something on the canvas so you can actually see that you can accomplish the same thing in PyGame that you can in CodeSkulptor. We're already calling draw_handler(canvas) in our main() function above, so if we can implement it then we can see about getting something drawn.

This is the other main difference in PyGame -- we must be more explicit in our instructions when drawing. The first thing to understand is that once we render something onto a surface it stays there. This took me a long time to get my head around because I'm used to making a call to draw something and having it appear and then stopping that call and having it disappear. This is not quite so in PyGame -- in PyGame, it's like you have a blackboard and everytime you draw on it, or on top of other things already drawn, the only way to remove that item you've drawn is to erase the whole area and redraw only the things you didn't want to erase. I know, this seems weird, but this is how it works. We know the draw handler runs at 60 frames per second, so the eye doesn't catch this process, but that's what's going on. With that in mind, let's look at simple draw handler implementation:

count = 0
draw_colour = white_color
def draw_handler(canvas):

    # clear canvas -- fill canvas with uniform colour, then draw everything below.
    # this removes everything previously drawn and refreshes 
    canvas.fill((0, 0, 0))

    # draw example
    global count
    count += 1
    text_draw = fontObj3.render("CodeSkulptor Port", True, draw_colour)
    text_draw2 = fontObj3.render("Tutorial", True, draw_colour)

    if count % 90 < 45:
        canvas.blit(text_draw, (190, 220))
        canvas.blit(text_draw2, (250, 220))

    # update the display
I've included a couple global variables so I can do an example of a simple animation to help illustrate how rendering works. If you paste this code block into your file, above the main() function, for example, you should be able to now run it from within IDLE and have a window with some animating text appear. Choose "run --> run module" from the menu at the top of the code window. Or you can use the provided skeleton.py file. A nice simple text animation, but we now have something happening.

The first thing that likely stands out in the draw handler impementation is that there is an immendiate call to something called canvas.fill() which is something we never saw in CodeSkulptor. This fill operation does just what it sounds like it might do -- it fills the entire screen with a colour. We're using black. This is the equivalent of our blackboard erasure I mentioned earlier. We wipe everything away that was previously drawn as our first task in the draw handler. We then go on to draw what we need to draw on this refresh and end by calling pygame.display.update() which completes our draw operation. To see why we need to do this simply comment out the call to the fill method and then run the code again. Strange, isn't it? We're no longer wiping away what was previously drawn so things just get drawn on top of other things creating a mess. I should add that this is the most inefficient way of clearing away and updating the screen we can use. The smart and efficient way to do it is to only wipe and update the parts of the screen that have changed since the last refresh -- there's no point in wiping away and redrawing something that hasn't changed. However, this is the easiset way to do it. Don't expect to be able port graphically intensive games like Rice Rocks using this draw method -- it will grind to a halt. For that you want to look into the sprite class and the concept of "dirty sprites." This is something I'm just looking at myself and is therefore beyond the scope of this tutorial. I thought it worth mentioning, though.

Next we have our little text animation: we create two text objects by using our previously created font object and calling a .render() method passing in the text, a Boolean for Anti-Aliasing (smoothing) and one of our previously created colours. We then come across the .blit() method called from the canvas. This is way we draw things in PyGame -- the .blit() method does the actual "putting on screen" -- as you can see it takes an object to draw and an x, y coordinate location for where to draw it. We do the same thing when drawing images -- we make the same call, but instead of passing a text object we pass an image object we have loaded previously using the supplied load_image() function. It's important to note that PyGame draws using the upper left (0, 0) corner of the object and not the center of the object like CodeSkulptor does. This means when porting your code over you will need to adjust locations of draw (.blit()) calls to account for the difference. Finally, we have a call to pygame.display.update() which actually updates the canvas (screen) with all of our changes for this refresh cycle.


Timers are a fair bit different in Pygame. They have no .start() and .stop() methods, for example. They are simply running when they are given a time of something greater than 0 and stopped when they are given a time of 0. Also, they hold a user defined event number that is given to them upon creation which is then used when iterating over the event queue to determine which timer event is happening. As was seen above in the main() implementation I commented out the handling of the example timer, but you can see what's happening -- when the event is found there is then a call to the timer handler function as we normally would do in CodeSkulptor. In the elif statement I'm simply checking against a number I created for the timer. Here's timer creation code:

# pygame has no start() and stop() methods -- 0 time is off any other value is on
# set some on/off constants for readability with each timer

# timer for example -- 1500 milliseconds when on
# set the timer name to its user event for readability
timer_example = USEREVENT + 1
pygame.time.set_timer(timer_example, TIMER_EXAMPLE_ON) 		
I create a constant to turn timers off in the form of TIMER_OFF, so I pass that to the timer when I want it stopped. TIMER_EXAMPLE_ON is set to the rate at which I wish the timer to fire -- just like in CodeSkulptor. However, I must pass this to the timer function each time I want to start it running. timer_example is set to USEREVENT + 1 which simply adds one to the constant which holds where the user events numbers begin (it's in the 30's, I think). This is giving our timer an unique I.D. so it can be picked up on the event queue and then the proper timer handler function can be called. If you want to experiment with timers you can uncomment the example in the main() loop, copy the above code into your file just above the main() loop implementation and then above that define the timer handler by copying in the code below:
def t_example():
    global draw_colour
    if draw_colour == white_color:
        draw_colour = gold_color
        draw_colour = white_color
Calls to start and stop this timer look like the code below:
pygame.time.set_timer(timer_example, TIMER_OFF) 
pygame.time.set_timer(timer_example, TIMER_EXAMPLE_ON) 			
Notice we don't create a variable and assign it to the timer -- we simply call the timer function and pass it a user event number and a time of 0 or more -- the constants just make it more obvious what's going on.

Finishing Up:

You're free to remove the globals "count" and "draw_colour" as well as everything between canvas.fill() and pygame.display.update() in the draw handler function implementation now as it was just there for illustrative purposes. Those two calls (canvas.fill() and pygame.display.update()), however, form the "bookends" of the draw handler and need to remain. If you have a background image that fills the entire canvas, then you can replace the canvas.fill() call with a .blit() of that image which will do the same thing -- remove everything previously drawn, but replace it with the background image instead of a colour. You then go on and draw the rest of your content following on up to the pygame.display.update() call.

Equivalences and Pitfalls:

I'll try and go through some of the pitfalls and equivalent/similar functions I've encountered so far.



Don't forget the docs! Go to PyGame and read the documentation on the functions -- the comments below are often very helpful with sample code as well. Good Luck!