Today we will learn how to write graphical applications in Python.
We will use a library that is not built into Python (similar to pytest
which we used for testing). So we have to install it first. Open
your virtual environment and then use
pip - specifically the command
python -m pip install pyglet.
It looks like this:
(venv)$ python -m pip install pyglet Collecting pyglet Downloading pyglet-1.2.4-py3-none-any.whl (964kB) Installing collected packages: pyglet Successfully installed pyglet-1.2.4
If you have installed Pyglet successfully, try to run the following program. A black window should appear.
import pyglet window = pyglet.window.Window() pyglet.app.run() print('Done!')
If your window is black but there is some rubbish don't mind it now. Before we will start to draw in the window, we will clean it.
Done? Let's explain what is exactly happening.
Let's have a look what the program for 1D tic tac toe looks like. You can see in the comments what every line of code does.
def tictactoe1d(): field = '-' * 20 # Game preparation while True: # Loop: field = player_move(field) # 1. Ask player for their move if evaluate(field) != '-': # 2. Evaluate move break print(field) # 3. Print what the game looks like now # And again: field = ai_move(field) # 1. Ask computer for its move if evaluate(field) != '-': # 2. Evaluate move break print(field) # 3. Print what the game looks like now
We have two types of actions/events in this program, which alternate regularly. When an action is called, it's then evaluated and printed.
We already had a similar structure of reactions, for example: rock, paper, scissors.
A lot of different programs work similarly, they respond to input or other actions/events.
A web server is waiting for a request for a web page. When it gets a request, it processes for example a page that is saved on the disk, and as output, it sends some response.
More complex programs are responding to many different actions/events, not only to a "request" or "player move"/"computer move". What happens after the input evaluation depends on the type of the action.
For example your web browser is waiting for a mouse click or a keystroke and it will behave depending on the type of key you press or where you clicked - maybe it sends a request to the remote server. And then it's waiting for another action. A response may come from the server and then the web browser renders the page to the screen. Or the user can press "STOP" and the request is canceled.
Or, a text editor waits for different kidns of input from the keyboard or mouse, and it has to evaluate every input.
So a similar program structure - a loop that reads the input, processes it, and produces output - it is very useful. It is called event loop and programs built on it are called event-driven.
When there is something useful for more programs it is not usual that each programmer will write it all over from the beginning but some people write it once, pack it as a library and then everyone can use it.
One of these libraries is Pyglet. It contains an event loop and some functions for 2D graphics (with help from another library - OpenGL) and also for retrieving keyboard and mouse events.
Let's go back to the program that opens a window:
import pyglet window = pyglet.window.Window() pyglet.app.run() print('Done!')
The whole event loop is hidden in the function
Detecting input (e.g. from keyboard) is something that Pyglet can do
by itself, but the evaluation and drawing of the result is different
for each program, so you will have to program it by yourself.
Currently Pyglet is processing only two events:
Closing the window (by pressing the "x" button which is added by the
operating system) and pressing the Esc key,
which also closes the window.
After the window is closed, the event loop (function
ends and the program continues to the next line of code.
The Esc key is not interesting so let's have a look at the other keys.
In Pyglet when you want to respond to some event, you have to
write a function and then you register it - you tell
Pyglet to call this function at the right time.
An event that happens when the user is writing something
on the keyboard is called
on_text in Pyglet and it's
processed this way:
import pyglet window = pyglet.window.Window() def process_text(text): print(text) window.push_handlers(on_text=process_text) pyglet.app.run()
What does it do?
tells Pyglet that when the user writes something into our
window, Pyglet has to call a function
function gets one argument containing what the user wrote.
Notice that when we register our function we are not writing parentheses, although we once said that functions have to be called that way. Do you remember this example? Maybe you found it weird back then.
from math import sin print(sin(1)) print(sin) print(sin + 1)
Apart from numbers, strings,
True/False, we now also know
files, lists, tuples and others. And we can say that a function
in Python is a value like any other.
Numbers can be multiplied, strings can be written into a file,
we can read from files, and functions are only different in that
that they can be called.
But before we call a function, we can store the function
in a variable:
write = print write("Hello world!")
Or we can pass the function to another function as an argument:
And the function
window.push_handlers was directly writen to
process a function. Why? Pyglet doesn't need the result
of the function
process_text - it is useless for it.
And we also can't call the function cause we don't have
text as an argument.
That's why we give the function itself to Pyglet, and it
is called everytime the user press some key.
Before we move to the real graphics, we will have a look at another type of event.
It's a clock tick. That's an event, which is happening regularly after some time.
Registering a function for ticks is done differently than
import pyglet window = pyglet.window.Window() def tick(t): print(t) pyglet.clock.schedule_interval(tick, 1/30) def process_text(text): print(text) window.push_handlers(on_text=process_text) pyglet.app.run()
What does it do?
tells Pyglet that it should call the function
1/30 of a second.
tick gets only one argument -
how much time has elapsed since the last call.
Mostly it is not exactly 1/30 of a second, it's a bit
more. Our computer has also other things to do, so it
doesn't get to our program immediately, and it also
takes some time for Python to call our function.
And why 1/30 of a second? Because we will
create an animation later. When 30 images per second
are displayed in front of our eyes,
the brain connects them to create an illusion of smooth motion.
Most of the movies are using only 24 pictures per second and realistic 3D games have up to 60.
A program that prints a lot of numbers to the command line is not really interesting. But today's topic is graphics so we will slowly get rid off the command line. Let's draw! :)
Find some picture on the internet. Not too big so we still have
some room in our window. And PNG format would be the best.
You can start for example here.
And don't pick any dark picture because you might not
see it in your dark window.
Save it to the same folder where you have your program. For
example I have saved the picture as
Then draw the picture (use the name of your image):
import pyglet window = pyglet.window.Window() def tick(t): print(t) pyglet.clock.schedule_interval(tick, 1/30) def process_text(text): print(text) image = pyglet.image.load('snake.png') snake = pyglet.sprite.Sprite(image) def draw(): window.clear() snake.draw() window.push_handlers( on_text=process_text, on_draw=draw, ) pyglet.app.run()
Let's explain what is happening:
image = pyglet.image.load('snake.png')loads the picture from a file
snake = pyglet.sprite.Sprite(image)creates a special object, a Sprite), which specifies that we want to "place" our image at a specific place in the window. If we didn't do anything else, the image would sit in the left corner.
draw()takes care of rendering (drawing into) the window. It is called everytime when the window needs to be redrawn - e.g. when you minimize the window and then you open it again, or when you move the window out of the screen and then you move it back in. Or when we animate something.
Some operating systems remember the contents of windows that are not visible, but you shouldn't count on it.
window.clear()cleans the window - "paints the window black" and deletes everything that was there before.
This is not needed on a lot of computers but it's better to write programs so they run correctly everywhere.
snake.draw()draws a picture with our prepared
window.push_handlers(on_draw=draw)registers the function
draw– and tells Pyglet to call it everytime it is needed.
push_handlersfunction like that ↑.
Any drawing must be done within the drawing function that
Pyglet calls from
on_draw. Functions like
won't work anywhere else.
Let's play with the Sprite a bit.
Write the following in the function
def process_text(text): snake.x = 150
Our Sprite has an attribute
x which determines its
x coordinate - how far to the right it is
from the window edge.
You can set this attribute how ever you want - mostly as
reaction to some event, but it can be also set in the beginning.
Try to add something to
x everytime the clock ticks.
Are you able to guess how this piece of code will behave?
def tick(t): snake.x = snake.x + t * 20
If you are not scared of maths, import
and let the picture move according to some function:
def tick(t): snake.x = snake.x + t * 20 snake.y = 20 + 20 * math.sin(snake.x / 5)
What will happen when you change those numbers?
What will happen when you try to set a
rotation attribute in a similar way?
Pyglet also can call a function after some time.
Download another picture. I have another snake which is a bit different than the first one.
Once you have the picture in the folder with your
program, add this piece of code right before
image2 = pyglet.image.load('snake2.png') def change(t): snake.image =image2 pyglet.clock.schedule_once(change, 1)
schedule_once(change, 1) tells Pyglet that it
should call the function
change after one second.
And this function changes the image - in a similar way as we were
changing the coordinates.
schedule_once can be also called when you are handling another
event. Try to replace the function
change like that:
def change(t): snake.image = image2 pyglet.clock.schedule_once(change_back, 0.2) def change_back(t): snake.image = image pyglet.clock.schedule_once(change, 0.2)
The last event we will learn to handle is clicking.
Write this function right before the
def click(x, y, button, mode): snake.x = x snake.y = y
… and then register it in the
Try to find out what each argument means by yourself.
We wrote enough code, so we can end this lesson:
import math import pyglet window = pyglet.window.Window() def tick(t): snake.x = snake.x + t * 20 pyglet.clock.schedule_interval(tick, 1/30) def process_text(text): snake.x = 150 snake.rotation = snake.rotation + 10 image = pyglet.image.load(snake.png) snake = pyglet.sprite.Sprite(image, x=10, y=10) def draw(): window.clear() snake.draw() def click(x, y, button, mode): print(button, mode) snake.x = x snake.y = y window.push_handlers( on_text=process_text, on_draw=draw, on_mouse_press=click, ) image2 = pyglet.image.load('snake2.png') def change(t): snake.image = image2 pyglet.clock.schedule_once(change_back, 0.2) def change_back(t): snake.image = image pyglet.clock.schedule_once(change, 0.2) pyglet.clock.schedule_once(change, 0.2) pyglet.app.run()
With keystroke and mouse input, timing, and rendering of Sprites, you can create a simple game or graphics application.
When you write some game, try to keep the state of the application (basically how the window should look) in lists and tuples (or in dictionaries or classes). One function should draw this state, and another function should manipulate (change) it. To avoid confusion, you can also have those two functions in different files.
If you are interested in this topic, you can try to play and examine this Pong code, which shows more Pyglet options: writing text, drawing rectangles and handling specific keys (e.g. arrows). It may seem difficult, but just look at the comments (currently with A LOT of grammar errors) and try to understand it. If the comments are not enough for you to understand, we will also translate the more detailed materials for it
You can find the things that we've learned today (and some more) in the Pyglet cheatsheet, which you can download and print out.
And if you want to dive deeper into Pyglet, there is also documentation. If anything in there isn't clear, just ask!