{
"metadata": {
"name": ""
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "heading",
"level": 1,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"A very basic Pyglet tutorial"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In this tutorial we will look over the very basics of [Pyglet](http://pyglet.org/), a cross-platform windowing and multimedia library for Python.\n",
"\n",
"I came across Pyglet while looking for a library with which I could make a simple game. Of course I first found and tested out [Pygame](http://www.pygame.org/), but for some reason, it didn't exactly click with me, so I continued looking. I later came across the before-mentioned Pyglet and found it to be really nice and very suited for my needs and tastes. So, let's look at how we can make a very simple game with Pyglet.\n",
"\n",
"Pyglet also has no external dependencies, which was a plus for me since it meant a very easy installation/usage in places where you didn't have admin rights and is powered by OpenGL, meaning everything is hardware accelerated and you can even use Pyglet to do some 3D graphical rendering, which was what I wanted to continue with."
]
},
{
"cell_type": "heading",
"level": 1,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"Pyglet basics"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The very basics that we usually do in a Pyglet game are the following:\n",
"\n",
"* Define a window object\n",
"* Override the `on_draw()` function\n",
"* Schedule your game logic using `pyglet.clock`\n",
"* Run the application with `pyglet.app.run()`\n",
"\n",
"I doubt this needs an extra explanation, so let us just look at the bare bones program. "
]
},
{
"cell_type": "heading",
"level": 2,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Sample bare bones program"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"import pyglet\n",
"\n",
"game_window = pyglet.window.Window()\n",
"\n",
"@game_window.event\n",
"def on_draw():\n",
" game_window.clear()\n",
" \n",
"def update(dt):\n",
" pass\n",
"\n",
"if __name__ == '__main__':\n",
" pyglet.clock.schedule_interval(update, 0.5)\n",
" pyglet.app.run()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"First, a picture of what this does.\n",
"\n",
"\n",
"\n",
"You might say \"This is just a blank screen, that's not very impressive\" but think about it a little more. In just 14 lines of code, we made a new window that is integrated with our window manager (meaning it can be moved, resized, minimized etc.), which calls an update function twice per second, we overrode the the `on_draw()` function into which we can later input our screen drawing. On top of all that, the application already listens for key presses and clicks (try pressing *ESC* to see how it works). So I believe that even this is a good start. "
]
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"Sample bare bones program - in depth"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We start by making a game window, which is of the `pyglet.window.Window` class. \n",
"\n",
"On line 5, we override the `on_draw` function using a `game_window.event` decorator, and make sure the screen is cleared using the game window `clear` function. \n",
"\n",
"On line 9, we make a dummy `update` function, which expects the `dt` argument. The `dt` stands for delta time, and will tell us how much time passed since the function was last called. You will see where this comes in handy later on. \n",
"\n",
"Just before calling the `pyglet.app.run()` command, which actually starts the application up, we call the `pyglet.clock.schedule_interval` function, which expects two parameters - the function that is called, and the minimum time that passes between each call. We set it to call our update function every half second, or in other words, twice per second (of course if our application slows down it might not be called as many times, but don't worry, the `dt` parameter will still be correct). "
]
},
{
"cell_type": "heading",
"level": 1,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Resources"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's talk about resources and more specifically, resource management. Since we do not want to load the same resource (image, sound, ...) more then once, Pyglet provides us with the `pyglet.resource` helpers. Using those, we can load the resource just once and then continue to use it through our application. We usually do this in a separate file, so we have all the resources loaded in the same place. \n",
"\n",
"In our example, we keep all our resources under the *resource* folder. To load them, we first call `pyglet.resource.path` to set the path to our resources folder, call `pyglet.resource.reindex` so the system finds our images and then we load both images we will use using `pyglet.resource.image`. We do this in the **resources.py** file."
]
},
{
"cell_type": "code",
"collapsed": true,
"input": [
"import pyglet\n",
"\n",
"pyglet.resource.path = [\"resources\"]\n",
"pyglet.resource.reindex()\n",
"\n",
"player = pyglet.resource.image(\"player.png\")\n",
"enemy = pyglet.resource.image(\"enemy.png\")"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Making the game"
]
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"Preparation"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In our simple game we will use two different sprites - one for our player and the other for enemies. For now the enemies will remain stationary and there will be no collision detection. We will be able to move our player using the arrow keys.\n",
"\n",
"Let's look over the things we will need."
]
},
{
"cell_type": "heading",
"level": 3,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Sprites"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As elsewhere, the most basic object we will use in our game is the `Sprite`. Sprites are basically images that represent parts of our game (be it parts of the world, the player, monsters, effects, ... ), which can be moved, scaled, rotated, ... As such, sprites have variables `x`, `y`, `scale`, `rotation` etc.\n",
"\n",
"Our more complex Player or Monster classes will usually be a subclass of `pyglet.sprite.Sprite`. \n",
"\n",
"Every sprite can be drawn in the `on_draw` function using it's `draw()` method. \n",
"\n",
"Let's look at an example of drawing a sprite on the screen."
]
},
{
"cell_type": "code",
"collapsed": true,
"input": [
"import pyglet\n",
"import resources\n",
"\n",
"game_window = pyglet.window.Window()\n",
"\n",
"@game_window.event\n",
"def on_draw():\n",
" game_window.clear()\n",
" sprite.draw()\n",
"\n",
"def update(dt):\n",
" pass\n",
"\n",
"if __name__ == '__main__':\n",
" sprite = pyglet.sprite.Sprite(resources.enemy, x=100, y=100)\n",
"\n",
" pyglet.clock.schedule_interval(update, 0.5)\n",
" pyglet.app.run()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Rendered, it looks like this:\n",
"\n",
""
]
},
{
"cell_type": "heading",
"level": 4,
"metadata": {},
"source": [
"Drawing multiple sprites"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Of course when drawing more then one sprite, we do not have to do it by hand. Pyglet supports *batches*, with which you can easily (and quickly) draw all the sprites in the batch using a single command by using the `draw()` method of the batch.\n",
"\n",
"To use batches, you first create the `pyglet.graphics.Batch`, then pass it when creating the sprite as the optional `batch` argument, like this:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"batch = pyglet.graphics.Batch()\n",
"player = pyglet.sprite.Sprite(resources.enemy, x=400, y=400, batch=batch)"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"More information about sprites can be found in the [official documentation](http://www.pyglet.org/doc/api/pyglet.sprite-module.html). "
]
},
{
"cell_type": "heading",
"level": 3,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Handling key presses"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In Pyglet, you handle keyboard and mouse presses with game window events. We will only use the `on_key_press` one, but many more exist. For more information, look at the official documentation for [working with the keyboard](http://www.pyglet.org/doc/programming_guide/working_with_the_keyboard.html) and [working with the mouse](http://www.pyglet.org/doc/programming_guide/working_with_the_mouse.html). "
]
},
{
"cell_type": "heading",
"level": 2,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Story"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Of course, every game needs a story. Our very simple game will also have a very simple story, which goes as follows:\n",
"\n",
"> You are a simple housewife, but when your husband (a mafia crime lord) hasn't been home for dinner in two days it's time to go get him and show him who wears the pants in your house!"
]
},
{
"cell_type": "heading",
"level": 2,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Putting it all together"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now that we learned everything we need for this story, we can go and make everything work together. First, we will look at the full code, then parts will be explained in depth. "
]
},
{
"cell_type": "heading",
"level": 3,
"metadata": {},
"source": [
"Full code"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from __future__ import division\n",
"\n",
"from pyglet.window import key\n",
"import pyglet\n",
"import resources\n",
"\n",
"fps_display = pyglet.clock.ClockDisplay(\n",
" format='%(fps).1f',\n",
" color=(0.5, 0.5, 0.5, 1)\n",
")\n",
"\n",
"game_window = pyglet.window.Window(\n",
" width=800,\n",
" height=650,\n",
" caption=\"Catch your husband!\"\n",
")\n",
"\n",
"game_window.set_mouse_visible(False)\n",
"pyglet.gl.glClearColor(0.4, 0.4, 1, 1)\n",
"\n",
"player = pyglet.sprite.Sprite(resources.player, x=400, y=0)\n",
"player_x = 0\n",
"player_y = 0\n",
"player_speed = 150\n",
"\n",
"enemies_batch = pyglet.graphics.Batch()\n",
"enemies_sprites = []\n",
"enemies_sprites.append(pyglet.sprite.Sprite(\n",
" resources.enemy,\n",
" x=250,\n",
" y=100,\n",
" batch=enemies_batch)\n",
")\n",
"enemies_sprites.append(pyglet.sprite.Sprite(\n",
" resources.enemy,\n",
" x=550,\n",
" y=100,\n",
" batch=enemies_batch)\n",
")\n",
"enemies_sprites.append(pyglet.sprite.Sprite(\n",
" resources.enemy,\n",
" x=300,\n",
" y=300,\n",
" batch=enemies_batch)\n",
")\n",
"enemies_sprites.append(pyglet.sprite.Sprite(\n",
" resources.enemy,\n",
" x=500,\n",
" y=300,\n",
" batch=enemies_batch)\n",
")\n",
"enemies_sprites.append(pyglet.sprite.Sprite(\n",
" resources.enemy,\n",
" x=150,\n",
" y=450,\n",
" batch=enemies_batch)\n",
")\n",
"enemies_sprites.append(pyglet.sprite.Sprite(\n",
" resources.enemy,\n",
" x=650,\n",
" y=450,\n",
" batch=enemies_batch)\n",
")\n",
"\n",
"boss = pyglet.sprite.Sprite(resources.boss, x=400, y=550)\n",
"boss.scale = 0.4\n",
"\n",
"@game_window.event\n",
"def on_draw():\n",
" game_window.clear()\n",
" enemies_batch.draw()\n",
" boss.draw()\n",
" player.draw()\n",
" fps_display.draw()\n",
"\n",
"\n",
"@game_window.event\n",
"def on_key_press(symbol, modifiers):\n",
" global player_x, player_y\n",
" if symbol == key.LEFT:\n",
" player_x = -player_speed\n",
" player_y = 0\n",
" elif symbol == key.RIGHT:\n",
" player_x = player_speed\n",
" player_y = 0\n",
" elif symbol == key.UP:\n",
" player_x = 0\n",
" player_y = player_speed\n",
" elif symbol == key.DOWN:\n",
" player_x = 0\n",
" player_y = -player_speed\n",
" elif symbol == key.SPACE:\n",
" player_x = 0\n",
" player_y = 0\n",
"\n",
"\n",
"def update(dt):\n",
" player.x += player_x * dt\n",
" player.y += player_y * dt\n",
"\n",
"\n",
"if __name__ == '__main__':\n",
" pyglet.clock.schedule_interval(update, 1/120)\n",
" pyglet.app.run()\n"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Our \"finished\" game now looks like this:\n",
"\n",
"\n",
"\n",
"Everything is more or less self-explanatory, but if you need or want more in-depth information, continue reading. "
]
},
{
"cell_type": "heading",
"level": 3,
"metadata": {},
"source": [
"FPS display"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"fps_display = pyglet.clock.ClockDisplay(\n",
" format='%(fps).1f',\n",
" color=(0.5, 0.5, 0.5, 1)\n",
")"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `pyglet.clock.ClockDisplay` is used to show the current FPS. You can see it on the lower-left part of the last screenshot."
]
},
{
"cell_type": "heading",
"level": 3,
"metadata": {},
"source": [
"Game window"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"game_window = pyglet.window.Window(\n",
" width=800,\n",
" height=650,\n",
" caption=\"Catch your husband!\"\n",
")\n",
"\n",
"game_window.set_mouse_visible(False)\n",
"pyglet.gl.glClearColor(0.4, 0.4, 1, 1)"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We create a `Window`, setting width to 800px, height to 650px and the caption to \"Catch your husband!\". We also disable mouse cursor visibility inside the game and set the `glClearColor` (the color we clear to when using `Window.clear()`) to a bluish one. "
]
},
{
"cell_type": "heading",
"level": 3,
"metadata": {},
"source": [
"Player sprite"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"player = pyglet.sprite.Sprite(resources.player, x=400, y=0)\n",
"player_x = 0\n",
"player_y = 0\n",
"player_speed = 150"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We create the player sprite and set it's position to (400, 0). We also create the (global) variables we will use later in the code. "
]
},
{
"cell_type": "heading",
"level": 3,
"metadata": {},
"source": [
"Enemy sprites"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"enemies_batch = pyglet.graphics.Batch()\n",
"enemies_sprites = []\n",
"enemies_sprites.append(pyglet.sprite.Sprite(\n",
" resources.enemy,\n",
" x=250,\n",
" y=100,\n",
" batch=enemies_batch)\n",
")\n",
"enemies_sprites.append(pyglet.sprite.Sprite(\n",
" resources.enemy,\n",
" x=550,\n",
" y=100,\n",
" batch=enemies_batch)\n",
")\n",
"enemies_sprites.append(pyglet.sprite.Sprite(\n",
" resources.enemy,\n",
" x=300,\n",
" y=300,\n",
" batch=enemies_batch)\n",
")\n",
"enemies_sprites.append(pyglet.sprite.Sprite(\n",
" resources.enemy,\n",
" x=500,\n",
" y=300,\n",
" batch=enemies_batch)\n",
")\n",
"enemies_sprites.append(pyglet.sprite.Sprite(\n",
" resources.enemy,\n",
" x=150,\n",
" y=450,\n",
" batch=enemies_batch)\n",
")\n",
"enemies_sprites.append(pyglet.sprite.Sprite(\n",
" resources.enemy,\n",
" x=650,\n",
" y=450,\n",
" batch=enemies_batch)\n",
")"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We create an *enemies_batch* `Batch` and an *enemies_sprites*. We do not actually use this anywhere, but we would usually use something like this so we can iterate over all enemies. \n",
"\n",
"This code could of course be shortened using a for loop and a list of position tuples or something similar."
]
},
{
"cell_type": "heading",
"level": 3,
"metadata": {},
"source": [
"Boss sprite"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"boss = pyglet.sprite.Sprite(resources.boss, x=400, y=550)\n",
"boss.scale = 0.4"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We load the boss sprite and scale it down, since it's much bigger then our player and enemy sprites. "
]
},
{
"cell_type": "heading",
"level": 3,
"metadata": {},
"source": [
"Handling key presses"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"@game_window.event\n",
"def on_key_press(symbol, modifiers):\n",
" global player_x, player_y\n",
" if symbol == key.LEFT:\n",
" player_x = -player_speed\n",
" player_y = 0\n",
" elif symbol == key.RIGHT:\n",
" player_x = player_speed\n",
" player_y = 0\n",
" elif symbol == key.UP:\n",
" player_x = 0\n",
" player_y = player_speed\n",
" elif symbol == key.DOWN:\n",
" player_x = 0\n",
" player_y = -player_speed\n",
" elif symbol == key.SPACE:\n",
" player_x = 0\n",
" player_y = 0"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here we set the global `player_x` and `player_y` variables to the appropriate value, depending on which key we pressed. If we press one of the arrow keys, we start moving in the direction we pressed and if we press space we stop. "
]
},
{
"cell_type": "heading",
"level": 3,
"metadata": {},
"source": [
"The update function"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def update(dt):\n",
" player.x += player_x * dt\n",
" player.y += player_y * dt"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here we finally use the delta time we mentioned in the beginning. We multiply the player's speed by the delta time to make sure the movement speed is constant, even when the FPS drops a bit or climbs up. If we didn't do this, our movement would be *per frame*, meaning the game would actually be slower on slow computers and faster on fast ones, which is a problem that old DOS games had. "
]
},
{
"cell_type": "heading",
"level": 3,
"metadata": {},
"source": [
"The on_draw function"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"@game_window.event\n",
"def on_draw():\n",
" game_window.clear()\n",
" enemies_batch.draw()\n",
" boss.draw()\n",
" player.draw()\n",
" fps_display.draw()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"First, we clear the window - if we do not do this, the images from the previous frame would still stay there. This would mean our player wouldn't be *moving* as such, but rather *copying* all over the place. Comment out the line and test it out, it's fun (you can also try it with different values in `pyglet.clock.schedule_interval`). \n",
"\n",
"Later on we draw first the enemies, then the boss, then the player and last but not least the FPS. The order here is important, as sprites are drawn one over another - so the later the `draw()` method is called, the *higher* up or *closer* to the screen our sprite will be. \n",
"\n",
"This way, the player is drawn across the boss and the enemies with the FPS always on top of everything else. "
]
},
{
"cell_type": "heading",
"level": 3,
"metadata": {},
"source": [
"The main function"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"if __name__ == '__main__':\n",
" pyglet.clock.schedule_interval(update, 1/120)\n",
" pyglet.app.run()"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"All that remains is to actually start up the application, which is done with the `pyglet.app.run()` command. Before that we schedule an interval call to our update function and set it to $\\frac{1}{120}$, meaning we call the command 120 times per second. Since vertical sync is automatically enabled, we will not see more then 60 FPS, but the update function will still be called 120 times per second. "
]
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"The end"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"With this, we have come to the end of my very first tutorial. The next few tutorials will be linked to this one, and will show how to use Cocos2d (a library built over Pyglet) to turn this into a real game with menus, collision detection, background music etc. \n",
"\n",
"Feel free to leave your comments and opinions below. "
]
}
],
"metadata": {}
}
]
}