Creating a game with PyGlet and Python


For this tutorial I’m going to take a look at PyGlet: “a cross-platform windowing and multimedia library for Python.” The reason that I decided to take a look at PyGlet is because it is an alternative to PyGame in the Python gaming world and: “No external dependencies or installation requirements. For most application and game requirements, pyglet needs nothing else besides Python, simplifying distribution and installation.”[1]

The first step to using PyGlet is to actually download it and install it (http://pyglet.org/download.html) as of writing this PyGlet is at version 1.0 alpha 2 (as I finished this tutorial Beta 1 was released but I have been unable to try it out), so between now and the final release there are bound to be a few changes. Once you have download the correct version for your system install it an you are ready to go. I’m writing this on a Debian box so I downloaded the source distribution and installed it using the following, as per the instructions:


python setup.py install

Now like PyGame, PyGlet is a framework for developing games or other applications, it is not a game engine, therefore if you are looking to create a full game you will need to create your own, or use someone else’s. This tutorial will not going into full game creation, instead it will introduce PyGlet using a small sample application, hopefully giving you enough of the basics, or enough of a taste to continue on with it.

You can download the full source to this tutorial here.

Python Game PyGlet

Creating a Window

The first step is to get an actual window displaying. In this initial code I’m going to create a window with a main game loop and display the Frames Per Second that the game is currently running at. In order to do this I am going to subclass from the pyglet.window.Window class. You don’t have to do this in order to display a window, but given the way that events are handled this seemed to make the most sense to me.

I added the following to a file named “PyGletSpace.py”

from pyglet import window
from pyglet import clock
from pyglet import font

class SpaceGameWindow(window.Window):

	def __init__(self, *args, **kwargs):

		#Let all of the standard stuff pass through
		window.Window.__init__(self, *args, **kwargs)

	def main_loop(self):

		#Create a font for our FPS clock
		ft = font.load('Arial', 28)
		#The pyglet.font.Text object to display the FPS
		fps_text = font.Text(ft, y=10)

		while not self.has_exit:
			self.dispatch_events()
			self.clear()

			#Tick the clock
			clock.tick()
			#Gets fps and draw it
			fps_text.text = ("fps: %d") % (clock.get_fps())
			fps_text.draw()

			self.flip()

if __name__ == "__main__":
	# Someone is launching this directly
	space = SpaceGameWindow()
	space.main_loop()

So we are not doing anything that fancy here, first we import three things from the PyGlet library, the window, clock, and font modules. We then create a class called SpaceGameWindow which is a subclass of the pyglet.window.Window class.

Then we have a simple initialization function where for now we simply pass all of the keywords and arguments to the pyglet.window.Window’s __init__() function.

Next we have our main_loop() function which will serve as the main loop for the game. Here we create a pyglet.font.base.Font object and a pyglet.font.Text object that will serve as the font for our “Frames Per Second” text, and the class that will actually write out the “Frames Per Second” text.

After that we have the actual game loop where we continue to loop until the window’s has_exit boolean member variable becomes true.

In the loop we first dispatch all of the event to the event handlers, and then we clear the window to prepare it for drawing. Then we tick the clock, get our FPS text and then draw it out to the screen. Then, since windows in PyGlet are double buffered we flip the display.

Hopefully that is pretty straight forward, if you run the code you should see something similar to the following:

Python Game PyGlet

Sprites

Pretty good so far but this doesn’t really help us or do anything, so the next step is to get some stuff draw onto the screen. To do that I’m going to create a simple Sprite class. This class will be loosely based off of the PyGame’s Sprite class, but for the most part will serve as a helper for drawing and the action on the screen.

class Sprite(object):

	def __get_left(self):
		return self.x
	left = property(__get_left)

	def __get_right(self):
		return self.x + self.image.width
	right = property(__get_right)

	def __get_top(self):
		return self.y + self.image.height
	top = property(__get_top)

	def __get_bottom(self):
		return self.y
	bottom = property(__get_bottom)

	def __init__(self, image_file, image_data=None, **kwargs):

		#init standard variables
		self.image_file = image_file
		if (image_data is None):
			self.image = helper.load_image(image_file)
		else:
			self.image = image_data
		self.x = 0
		self.y = 0
		self.dead = False
		#Update the dict if they sent in any keywords
		self.__dict__.update(kwargs)

	def draw(self):
		self.image.blit(self.x, self.y)

	def update(self):
		pass

	def intersect(self, sprite):
		"""Do the two sprites intersect?
		@param sprite - Sprite - The Sprite to test
		"""
		return not ((self.left > sprite.right)
			or (self.right < sprite.left)
			or (self.top < sprite.bottom)
			or (self.bottom > sprite.top))

	def collide(self, sprite_list):
		"""Determing ther are collisions with this
		sprite and the list of sprites
		@param sprite_list - A list of sprites
		@returns list - List of collisions"""

		lst_return = []
		for sprite in sprite_list:
			if (self.intersect(sprite)):
				lst_return.append(sprite)
		return lst_return

	def collide_once(self, sprite_list):
		"""Determine if there is at least one
		collision between this sprite and the list
		@param sprite_list - A list of sprites
		@returns - None - No Collision, or the first
		sprite to collide
		"""
		for sprite in sprite_list:
			if (self.intersect(sprite)):
				return sprite
		return None

So it may look complicated but it’s really not that difficult. First off you will see that I create some positioning properties, left, right, top, and bottom. I do this so that the collision code is easier to read, plus it separates the positioning information from the implementation, in this case the pyglet.image.AbstractImage member. If, in the future we were to add a rectangle class to the Sprite we don’t have to change a the code that interacts with the positioning, just what the position properties interact with.

You will also notice that in the Sprite class can either be initialized using an image file or the actual image data, which is a pyglet.image.AbstractImage. The reason for this is instead of having to load the same image from the hard drive time and time again, and to avoid having the same image duplicated in memory in countless locations, we let the user decide which makes more sense.

Other then that it’s pretty simple stuff, some positioning information, and whether or not the sprite is Alive. If a sprite is dead it will be removed from the window. There is also a draw method where the sprites image is blited to the window, and an update function where (if necessary) the sprite should update itself (position, image, etc).

Next we have a few collision functions that will help us to determine when out sprites collide. Now that we have the sprite class we need to add three more classes based off of the Sprite class. A SpaceShip class, a Bullet class which will be fired by the SpaceShip, and a Monster class.

Python Game PyGlet

class SpaceShip(Sprite):

	def __init__(self, text_x, text_y, **kwargs):

		self.kills = 0
		Sprite.__init__(self, "ship.png", **kwargs)

		#Create a font for kill message
		self.font = font.load('Arial', 28)
		#The pyglet.font.Text object to display the FPS
		self.kill_text = font.Text(self.font, y=text_y, x=text_x)

	def draw(self):
		Sprite.draw(self)
		self.kill_text.text = ("Kills: %d") % (self.kills)
		self.kill_text.draw()

	def on_kill(self):
		self.kills += 1

The SpaceShip class doesn’t do that much above and beyond the base class. It has a data member “kills” that keeps track of the number of monsters that it kills, and a pyglet.font.Text member that will display the number of kills on the screen. The position of the “kill text” is also passed into the SpaceShip constructor.

Python Game PyGlet

class Bullet(Sprite):

	def __init__(self, parent_ship, image_data, top, **kwargs):
		self.velocity = 5
		self.screen_top = top
		self.parent_ship = parent_ship
		Sprite.__init__(self,"", image_data, **kwargs)

	def update(self):
		self.y += self.velocity
		#Have we gone off the screen?
		if (self.bottom > self.screen_top):
			self.dead = True

	def on_kill(self):
		"""We have hit a monster let the parent know"""
		self.parent_ship.on_kill()

The Bullet class will represent bullets fired by the SpaceShip class, so it adds three new data members the:

1) The velocity – which controls how fast the bullet moves.
2) The screen_top – Which represents the top of the screen is and will be used to destroy the bullet when the bullet moves past the top of the screen.
3) The parent_ship – This is the SpaceShip that fired the bullet. This will be used to let the ship know when it has made a kill.

The final sprite is the Monster sprite:

Python Game PyGlet

class Monster(Sprite):

	def __init__(self, image_data, **kwargs):
		self.y_velocity = 5
		self.set_x_velocity()
		self.x_move_count = 0
		self.x_velocity
		Sprite.__init__(self, "", image_data, **kwargs)

	def update(self):
		self.y -= self.y_velocity
		self.x += self.x_velocity#random.randint(-3,3)
		self.x_move_count += 1
		#Have we gone beneath the botton of the screen?
		if (self.y < 0):
			self.dead = True

		if (self.x_move_count >=30):
			self.x_move_count = 0
			self.set_x_velocity()

	def set_x_velocity(self):
		self.x_velocity = random.randint(-3,3)

The Monster sprite will be moving from the top of the screen to the bottom of the screen, which is the opposite of the Bullet and is controlled by the y_velocity data member. But unlike the bullet the Monster sprite will also have some horizontal motion built it, which is controlled by the x_velocity data member, and set in the set_x_velocity() function. What is interesting about the x_velocity is that there is a bit of randomness built into it, it will be a random number from -3 in the left direct to 3 in the right direction. It will also change every thirty updates, giving the Monster a lazy side to side motion making it a bit more difficult for the SpaceShip to shoot it.

The Monster sprite does not have a bottom data member to tell it when it has moved off of the screen. The reason for this is that since the Monster will be moving down from the top of the screen, and we will can tell if it’s off the screen when it’s y, or top, data members are less then zero.

Adding the Sprites to the Game

Now that we have our sprite classes we need to use them in the main SpaceGameWindow class. There are a few ways to do this, but what I ended up doing was creating three new data members: ‘bullets’ and ‘monsters’, both lists of sprites, and ‘ship’ which will be the one and only ShapShip sprite.

The Sprites and some other data is initialized in the init_sprites() function:

def init_sprites(self):
	self.bullets = []
	self.monsters = []
	self.ship = SpaceShip(self.width - 150, 10, x=100,y=100)
	self.bullet_image = helper.load_image("bullet.png")
	self.monster_image = helper.load_image("monster.png")

All we do is create the two lists to store our sprites, the SpaceShip sprites, and use our helper to load the images that we will use for the Bullet and Monster sprites.

The next step is to change the draw loop so that we actually draw our sprites. There are a few more changes to be made in the main_loop() function but overall it remains pretty simple:

def main_loop(self):

	#Create a font for our FPS clock
	ft = font.load('Arial', 28)
	#The pyglet.font.Text object to display the FPS
	fps_text = font.Text(ft, y=10)

	#Schedule the Monster creation
	clock.schedule_interval(self.create_monster, 0.3)
	clock.set_fps_limit(30)

	while not self.has_exit:
		self.dispatch_events()
		self.clear()

		self.update()
		self.draw()

		#Tick the clock
		clock.tick()
		#Gets fps and draw it
		fps_text.text = ("fps: %d") % (clock.get_fps())
		fps_text.draw()
		self.flip()

There is quite a bit happening here so I’ll take a little bit of time to explain everything that is new. One of the things that I want to happen is to have monsters created after a certain amount of time has passed. To to this I use the clock.schedule_interval function.

#Schedule the Monster creation
clock.schedule_interval(self.create_monster, 0.3)

What this will do is call the create_monster() function every 0.3 seconds. The create_monster() function is pretty simple, it simply creates a Monster sprite in a random location provided that there is less then the maximum amount of Monsters currently around:


def create_monster(self, interval):
if (len(self.monsters) < self.max_monsters):
self.monsters.append(Monster(self.monster_image
, x=random.randint(0, self.width) , y=self.height))

max_monsters is defined in the __int__() function to be 30. The next new feature is the limiting of the frame rate. For such a simple game I limit it to 30 frames per second using the set_fps_limit function.

Other then that there are only two new calls in the main main loop and that is:

self.update()
self.draw()

update() is basically a function that is used to update everything that is happening on the screen: sprite positions, scores whatever you want. Then the draw() function is basically the function where everything that needs to be drawn onto the screen gets drawn.

def update(self):

	to_remove = []
	for sprite in self.monsters:
		sprite.update()
		#Is it dead?
		if (sprite.dead):
			to_remove.append(sprite)
	#Remove dead sprites
	for sprite in to_remove:
		self.monsters.remove(sprite)

	#Bullet update and collision
	to_remove = []
	for sprite in self.bullets:
		sprite.update()
		if (not sprite.dead):
			monster_hit = sprite.collide_once(self.monsters)
			if (monster_hit is not None):
				sprite.on_kill()
				self.monsters.remove(monster_hit)
				to_remove.append(sprite)
		else:
			to_remove.append(sprite)
	#Remove bullets that hit monsters
	for sprite in to_remove:
		self.bullets.remove(sprite)

	self.ship.update()
	#Is it dead?
	monster_hit = self.ship.collide_once(self.monsters)
	if (monster_hit is not None):
		self.ship.dead = True
		self.has_exit = True

I won’t explain this function too much since in general it’s pretty straight forward. We are basically updating all of the sprites, checking for collisions or them being “dead” and if the sprites need to be removed then we do.

The first step is to update all of the monsters. The second step is to update all of the Bullet sprites and if they are not dead (off the screen) check for a collision between them at the Monster sprites. If the Bullet hits a Monster sprite then it and the monster are both dead and will be removed.

Then we check to see if the SpaceShip sprite has collided with any of the Monster sprites. If it has we signal the end of the game by setting the has_exit window handler.

The draw function is very simple, we simply draw all of the sprites:

def draw(self):

	for sprite in self.bullets:
		sprite.draw()
	for sprite in self.monsters:
		sprite.draw()
	self.ship.draw()

Events

The next step to setup some event handling, which was the whole reason that we made our main class use pyglet.window.Window as the base class way back before we got sidetracked on these sprites and game play.

Fortunately for us adding event handlers is very simple, we simply have to define one of the event functions

Since we want to use mouse motion to move the space ship we need to catch the mouse motion event.

def on_mouse_motion(self, x, y, dx, dy):
	self.ship.x = x
	self.ship.y = y

Pretty simple! We just need to add the function for the even and as long as we call dispatch_events our event will be handed.

We also want the spaceship to fire when we click the left mouse button. So to do that we need to handle the mouse press event.

def on_mouse_press(self, x, y, button, modifiers):

	if (button == 1):
		self.bullets.append(Bullet(self.ship
				, self.bullet_image
				, self.height
				, x=x + (self.ship.image.width / 2) - (self.bullet_image.width / 2)
				, y=y))

Here we simply make sure that it was the left mouse button that was pressed, and it it was we create a bullet in the correct location using the correct image.

PyGlet does something interesting it gives you a mouse drag event, which is sent when a mouse button is down and the mouse is moved. During the “mouse drag” event the “mouse motion” event will not be sent. Sine the used will be clicking a lot we need to keep the Space Ship moving by handling the mouse drag event, if we don’t the ship will seem to pause if we move the mouse and click the left mouse button.

def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
	self.ship.x = x
	self.ship.y = y

This is exactly the same as the mouse motion handler, and in truth it could call on_mouse_motion() itself.

Helper

You might have noticed that I use a helper to load this images. This is pretty simple stuff located in a file called “helper.py” in the same directory as PyGletSpace.py:

import os
from pyglet import image

def get_image_dir():
	"""Get the directory used to store the images
	@returns string - the directory
	"""
	directory = os.path.abspath(os.path.dirname(__file__))
	directory = os.path.join(directory, 'data')
	return directory

def load_image(image_file_name):

	full_path = os.path.join(get_image_dir(), image_file_name)
	return image.load(full_path)

Done!

Python Game PyGlet

You can download the full source to this tutorial here.

That’s it for this wonderfully short game and lengthy tutorial. What’s I’ve found is that PyGlet is an extremely interesting library that I will continue to keep an eye on. At a certain point I had heard that it was going to be including some Sprite classes and collision classes as well, but it seems as though this may be on the back burner for the initial release.

It will be interesting to see where this goes, what I would like to see would be a simple 2d (since that’s all I’m interested in fooling around with for the most part) engine built on top of PyGlet. A simple way to work with all of the different features that a 2d game needs. Hopefully something like that will come along at some point, heck maybe it’s already out there and I just don’t know about it yet. But if anyone is working on one, or interested in setting something like this up drop me a line.

[1] http://www.pyglet.org/

selsine

del.icio.us del.icio.us

23 Responses to “Creating a game with PyGlet and Python”

  1. Richard
    Says:

    Looks nice. Just a word about the FPS display: consider just using the default font, as Arial might not be available, and the default font on non-Windows will probably look much nicer than Arial.

    Also, the clock module has an FPS display helper :)

    fps_display = clock.ClockDisplay()
    fps_display.draw()

  2. Alex
    Says:

    Great stuff. pyglet doesn’t have any high-level graphics functions yet… it’s very impressive that you can create a nifty little game without knowing any OpenGL.

  3. cucu
    Says:

    The miracles of technology – 25 years ago on a ZX Spectrum about 5000 times slower you could achieve the same FPS … how reassuring that the same can be still done now with the latest computers by ‘modern programmers’ :))

  4. Steve
    Says:

    Great tutorial, the code is really easy to follow, I opened it up and started hacking on it straight away (multishot :D). Pyglet really looks like it has a nice balance between minimal-ness and usefulness.

  5. Not quite there yet » Blog Archive » PAL Space
    Says:

    [...] I noticed a nice little example about writing a game in PyGlet at http://www.learningpython.com/2007/11/10/creating-a-game-with-pyglet-and-python/ and it seemed like something worth stealing so I decided to do a PAL version of it. The final results of this little experiment can be found here [...]

  6. selsine

    selsine
    Says:

    Hi Richard,

    Thanks for the great tip about the default font! I didn’t know that was possible. When I first wrote the tutorial I tried the default FPS display but for whatever reason I couldn’t get it working? Of course when I tried your code it worked properly! I’ll update the tutorial when I get a chance. Thanks!

  7. selsine

    selsine
    Says:

    Hi Alex,

    Thanks for saying that, I’d like to see some higher level functionality in PyGlet in the future but it’s pretty good already.

  8. selsine

    selsine
    Says:

    Hi Cucu,

    Thanks for the “kind” words! I agree technology is an amazing thing!

  9. selsine

    selsine
    Says:

    Hi Steve,

    Thanks, I’m glad you were able to follow the code and add you own twist to it. If you keep working on it let me know what you come up with.

  10. Spieleentwicklung mit pyglet « Wolfgang Schoch
    Says:

    [...] Auf learning Python gibt es ein neues Tutorial, das anhand eines kleinen Spiels das Python-Framework pyglet vorstellt. pyglet ist im Vergleich zu PyGame kein spezielles Spieleframework sondern ein generelleres Framework für GUI und Multimedia. Im Tutorial wird ein einfacher Space Invaders Klon entwickelt und dabei die Grundlagen von pyglet erklärt. Leider lässt mir meine Diplomarbeit gerade nicht soviel Zeit, um mich umfangreicher mit dem Thema Spieleentwicklung unter Python zu beschäftigen. Gerade das angesprochene Framework PyGame sieht sehr interessant aus (und auf learning Python finden sich auch Artikel dazu.) Tags: pygame, pyglem, spieleentwicklung « Brandneu: die Handytastatur für den USB Port [...]

  11. Web 2.0 Announcer
    Says:

    learning python Âť Blog Archive Âť Creating a game with PyGlet and Python

    [...][...]

  12. jonathan hartley
    Says:

    Hey, I loved the tutorial. FYI, it doesn’t appear to be listed on your site’s index of tutorials, unless I am just being dumb. Cheers!

  13. Stephen Johnson
    Says:

    I tweaked it so it had time-based animation, instead of frame rate-limited. Perhaps you could add that to your version? I’ve never considered frame rate limiting to be good game programming practice.

  14. Peter
    Says:

    CPU Utilisation:

    I changed the FPS limit in …clock.set_fps_limit(30)… to 10, 15, 30, 60, 90 and also uncommented the entire line and noticed some interesting benchmarks on CPU utilization. On a 1.4Mhz Pentium M laptop running Ubuntu 7.10 I got the following average CPU utilization percentages from top running in another terminal window of the python process running this game:

    10 FPS = 9.3% CPU
    15 FPS = 15.3% CPU
    30 FPS = 24% CPU
    60 FPS = 39.6% CPU
    90 FPS = 16.3% CPU
    Uncommented = 7.3% CPU

    It is noted that at 90 FPS and Uncommented the game window maxed out at 60 FPS.

    Also noted, pyglet as it stands has a bug on linux, at least on Ubuntu 7.10, with Compiz/Beryl running. The window border frame and widget bar is not drawn and you only see the windows canvas on the screen. This severely limits the users ability to quit execution of the program without an explicit menu option programmed into the main program.

    With Compiz/Beryl being enabled more and more by default on the Linux distros to provide advanced windowing features / eye-candy this may be a more serious problem.

    I wonder what people on other platforms observe when trying out the same experiment.

  15. selsine

    selsine
    Says:

    Hi Jonathan,

    Thanks for letting me know! I’ve added it to the tutorial list.

    Hi Stephen,

    Do you still have the code around? Or could you let me know what you tweaked? If you have some improvements I would use them to update this tutorial or write a new one.

  16. SeaRider
    Says:

    I found that on running the code from the first code box, the window fails to close until you tell it to once you’ve exited the loop (at least, running under Windows). I put

    space.close()

    after the call to space.main_loop(), and it then works properly.

  17. selsine

    selsine
    Says:

    Thanks for the tip SeaRider. I wrote this code using the Beta version of pyglet so I wouldn’t be surprised if a few things have changed since then.

    Thanks for letting me and the other readers know.

  18. Olberg
    Says:

    Check Cocos2d – http://cocos2d.org/

  19. Blake
    Says:

    hey i am new to using python and i have been trying to study and understand it but i copied the coding you have and when i run it the only thing that comes up is a black screen and the FPS. Is there advise you can give me for this?

  20. Billy Bob
    Says:

    When you start modifying the main loop about two thirds into the tutorial you begin to add function like crazy, but never identify where they are to be placed.

  21. Matt
    Says:

    Billy Bob,
    try looking in the source code, it’s just code there. that’ll show you where.

    PS: great tutorial!
    PSS: i heard that there’s an update for pyglet that adds more sprite functionallity, a sprite class, maybe? downloading the new one and checking the documentation now…

  22. Michael
    Says:

    Thanks a lot for this tutorial. It’s very clear, well written and nicely laid out. I’m sure things have changed a lot in pyglet by now, but I know very little about game programming, and your tutorial was a great introduction. I really feel like I’ve gotten a good insight, so, again, thanks very much.

  23. Weeks Unknown – Scaling Troubles | Import Soul
    Says:

    [...] good use of hardware surfaces by default. In learning pylet i stumbled upon some basic guides on game desgin and another guide that provides a basic camera class for me to base my scrolling and zooming [...]

Leave a Reply

 

Popular Posts