In the previous part we added a background image to the Players area of the Settings screen. Today we’ll see how to add graphical assets to our project.
But before we delve into the topic, here’s some info for you.
*****
Table of Contents
Book Info
I just published my Kivy book, GUI Programming with Python and Kivy. It’s pretty long (over 800 pages) and comprehensive. And, which also counts, easy to read. The book contains lots of illustrations.
This book covers all the basics that you need to know to start programming GUI applications with Python and Kivy. Throughout the book we are building a GUI application from scratch, a fully functional game using all kinds of tools that Kivy has to offer. It’s our Slugrace project, but covered in a much more in-depth manner.
Each part of the book starts with a theoretical introduction of a topic or idea that we then implement in the project. I assume you have no prior knowledge of the Kivy library, but you should have at least some basic knowledge of the Python programming language, including the object-oriented programming paradigm as this is what we will be using a lot in this book.
The book covers all the basic elements of Kivy that you have to know, like widgets, layouts, Kivy ids and properties, graphics, screens, animation, sound. Finally we’ll deploy the app to Windows. It is pretty comprehensive and after you finish it, I’m sure you’ll be able to create your own awesome GUI apps of any kind, not just games.
I hope you will have at least as much fun reading the book as I had writing it.
As far as this Kivy series is concerned, the following parts will contain the most important elements covered in the book. However, some elements will be presented in a simplified way here on my blog or omitted completely.
____________
If you are interested, you can purchase the book in four versions. Here are the links:
1) ebook – pdf version on my website – in full color
Here you can see the description of the book, sample graphics from the book and the full table of contents.
2) ebook – Kindle version on Amazon – in full color
3) paperback version on Amazon – in black and white
4) paperback version on Amazon – in full color
*****
And Now Let’s Move On…
There are graphical assets in the Race screen. Here’s what they look like in the final version:
So, there is the track image, the four images of the slugs in top view and the silhouette image of the winning slug. Besides, there are some labels on top of the track, which we are going to add as well.
The Track Image
Let’s start with the racetrack.png image. You can find the file in the assets folder. If you hover your mouse over the file, you can see how big the image is. It’s 1000 x 200 px. And this is the size we want it to be at all times during the game. Even if you resize the app window, the size of the racetrack image shouldn’t change. So, we need a fixed size for the widget.
Now, what widget are we actually going to use? Well, there are several options, but the easiest one seems to be the Image widget, which is available in Kivy out of the box. All you have to do is set its source property to the path to the image file.
So, let’s try to implement the track image. At this moment we have some placeholder code in the Track area of the Race screen that displays a label with the word ‘TRACK’. Here’s the kv code:
# File name: race.kv
...
<RaceScreen>:
...
### THE TRACK ###
BoxLayout:
...
# track
Label:
color: .2, .1, 0, 1
text: 'TRACK'
# winner
...
### THE BETS ###
...
And this is what the screen looks like after resizing the app window slightly:
So, let’s replace the label with an Image widget:
# File name: race.kv
...
### THE TRACK ###
BoxLayout:
...
# track
Image:
# Here's the path to the image file.
source: 'assets/racetrack.png'
# The image should be a fixed size
size_hint: None, None
size: 1000, 200
# Let's also center the image vertically within its layout.
pos_hint: {'center_y': .5}
# winner
...
### THE BETS ###
...
Now run the app and try resizing the window. As you can see, the image is always the same size and it’s always vertically centered:
Now, the track image is not the only thing we need in the track subarea. There are also the top-view images of the four slugs and some labels. So, it would be convenient to enclose all these elements in another container. It will be easier for us to use coordinates relative to the track subarea when we add the other widgets, so a good candidate for the container is the RelativeLayout. So, let’s add the layout and put the track image in it:
# File name: race.kv
...
### THE TRACK ###
BoxLayout:
...
# track
# Let's put the track image in a RelativeLayout, along with some
# other widgets that we are going to add in a minute.
RelativeLayout:
# Let's move the sizing and positioning code to the container.
size_hint: None, None
size: 1000, 200
pos_hint: {'center_y': .5}
Image:
source: 'assets/racetrack.png'
# size_hint: None, None
# size: 1000, 200
# pos_hint: {'center_y': .5}
# winner
...
### THE BETS ###
...
You may have noticed that I moved the sizing and positioning code to the layout now. There will be no difference if you run the app now, but it will make our life easier when we then add the other widgets… Speaking of which, let’s add the white labels and the four slug images.
The White Labels
Now, with the track image in place, let’s position the labels on it. First of all, let’s add some class rules. There are three types of labels: the medium-sized ones for the names of the slugs, the small ones for the wins and the big ones for the odds. Let’s name the classes WhiteNameLabel, WhiteWinsLabel and WhiteOddsLabel respectively. Here are the class rules:
# File name: race.kv
### CLASS RULES ###
<RegularLabel@Label>:
color: .2, .1, 0, 1
text_size: self.size
halign: 'left'
valign: 'center'
<BoldLabel@RegularLabel>:
bold: True
# the white labels
<WhiteOddsLabel@BoldLabel>:
font_size: 32
color: 1, 1, 1, 1
<WhiteNameLabel@BoldLabel>:
font_size: 18
color: 1, 1, 1, 1
<WhiteWinsLabel@BoldLabel>:
font_size: 14
color: 1, 1, 1, 1
...
As you can see, these are just simple labels that inherit from the BoldLabel class, each of them with a different font size. Later in the series, when win discuss properties, we will create a custom widget that will hold the slug name and wins information, but for now let’s just create four separate BoxLayouts, one for each slug.
Now, the positions of the widgets in the RelativeLayout will be relative to the container. To better understand where the vertical positions that I’m using come from, here’s a little image of the RelativeLayout with the track image and the proportions that you can see in the code:
We just want each BoxLayout with the two slug info labels to be centered on the slug’s own lane. By the way, the four lanes are separated by white lines, which are part of the image. The starting line and the finish line are also there.
And here’s the code:
# File name: race.kv
...
### THE TRACK ###
BoxLayout:
...
# track
RelativeLayout:
size_hint: None, None
size: 1000, 200
pos_hint: {'center_y': .5}
Image:
source: 'assets/racetrack.png'
# labels with slug info
# We'll use a vertical BoxLayout for each slug.
BoxLayout:
orientation: 'vertical'
# The BoxLayout will have a fixed size that will help
# us keep the text where we want it. To position the
# BoxLayout we're using the pos_hint property with
# values that will place the layout just slightly to
# the right of the left border of the parent layout
# (RelativeLayout) and near the top. These values are
# relative to the RelativeLayout.
size_hint: None, None
size: 100, 50
pos_hint: {'x': .004, 'center_y': .875}
WhiteNameLabel:
text: 'Speedster'
WhiteWinsLabel:
text: '0 wins'
BoxLayout:
orientation: 'vertical'
size_hint: None, None
size: 100, 50
pos_hint: {'x': .004, 'center_y': .625}
WhiteNameLabel:
text: 'Trusty'
WhiteWinsLabel:
text: '0 wins'
BoxLayout:
orientation: 'vertical'
size_hint: None, None
size: 100, 50
pos_hint: {'x': .004, 'center_y': .375}
WhiteNameLabel:
text: 'Iffy'
WhiteWinsLabel:
text: '0 wins'
BoxLayout:
orientation: 'vertical'
size_hint: None, None
size: 100, 50
pos_hint: {'x': .004, 'center_y': .125}
WhiteNameLabel:
text: 'Slowpoke'
WhiteWinsLabel:
text: '0'
# winner
...
### THE BETS ###
...
If you now run the app and resize the window, you will see the name and win info on the track:
Even if you resize the window, the labels will remain on the track, relative to the RelativeLayout their containing BoxLayouts are in.
The last thing to do, as far as labels are concerned, are the big odds labels. They should be positioned near the finish line. Here’s the code:
# File name: race.kv
...
### THE TRACK ###
BoxLayout:
...
# track
RelativeLayout:
...
# labels with slug info
...
# the odds labels
WhiteOddsLabel:
text: '1.42'
pos_hint: {'x': .77, 'center_y': .875}
WhiteOddsLabel:
text: '1.61'
pos_hint: {'x': .77, 'center_y': .625}
WhiteOddsLabel:
text: '2.53'
pos_hint: {'x': .77, 'center_y': .375}
WhiteOddsLabel:
text: '2.89'
pos_hint: {'x': .77, 'center_y': .125}
# winner
...
### THE BETS ###
...
Now we have all the labels in place:
Now we need the top view images of the four slugs.
The Top View Slug Images
You can find the images in the slugs subfolder of the assets folder. There are actually two images for each slug: body (235 x 49 px) and eye (54 x 12 px). Each slug image should then actually consist of three smaller images: the body and two eyes. To make the code less repetitive, we will later create a custom widget and then add four instances of it in the track subarea, but for now, let’s add just four RelativeLayouts each containing the respective body and two eyes.
As you can see in the final version of the Race screen, the slugs will rotate their eyes. For now, the eyes will be immobile, but still, we will rotate them. To do that, we’ll use some canvas context instructions like PushMatrix and PopMatrix to save and restore the context respectively, as well as Rotate, to actually rotate the eyes. And one more thing, when I use the word ‘eye’ in the context of our project, I mean the whole tentancle with the eye at the end. Sorry for not being biologically correct.
Here’s the code with explanations in comments. It’s lengthy, but pretty straightforward, so just take your time to examine it carefully:
# File name: race.kv
...
### THE TRACK ###
BoxLayout:
...
padding: 10
# track
RelativeLayout:
...
# labels with slug info
...
# the odds labels
...
# slug images
# Speedster - the first slug
# By putting the body image and two instances of the eye
# image in a RelativeLayout, we will be able to position
# the eyes relative to the body easily.
# The exact values that I'm using in the size and pos_hint
# properties were determined by trial and error and just
# seem to be working fine for our purposes.
RelativeLayout:
pos_hint: {'x': .09, 'center_y': .875}
size_hint: None, None
size: 143, 30
# the body image
Image:
source: 'assets/slugs/speedsterBody.png'
# the left eye image
Image:
canvas.before:
# Let's save the context before rotation.
PushMatrix
# Let's rotate the eye 30 degrees counterclockwise
# on the Z axis around the point which is horizontally
# on the left border of the eye image and vertically
# halfway its height.
Rotate:
angle: 30
axis: 0, 0, 1
origin: self.x, self.center_y
canvas.after:
# Let's restore the context.
PopMatrix
source: 'assets/slugs/speedsterEye.png'
# The position of the eye is relative to the RelativeLayout
# it's in along with the body image and the other eye image.
pos_hint: {'x': .95, 'y': .45}
# Let's also scale the eye image down to look well-proportioned.
size_hint: 0.25, 0.25
# the right eye image
Image:
canvas.before:
PushMatrix
Rotate:
angle: -30
axis: 0, 0, 1
origin: self.x, self.center_y
canvas.after:
PopMatrix
source: 'assets/slugs/speedsterEye.png'
pos_hint: {'x': .95, 'y': .3}
size_hint: 0.25, 0.25
# Trusty - the second slug
RelativeLayout:
pos_hint: {'x': .09, 'center_y': .625}
size_hint: None, None
size: 143, 30
# the body image
Image:
source: 'assets/slugs/trustyBody.png'
# the left eye image
Image:
canvas.before:
PushMatrix
Rotate:
angle: 30
axis: 0, 0, 1
origin: self.x, self.center_y
canvas.after:
PopMatrix
source: 'assets/slugs/trustyEye.png'
pos_hint: {'x': .95, 'y': .45}
size_hint: 0.25, 0.25
# the right eye image
Image:
canvas.before:
PushMatrix
Rotate:
angle: -30
axis: 0, 0, 1
origin: self.x, self.center_y
canvas.after:
PopMatrix
source: 'assets/slugs/trustyEye.png'
pos_hint: {'x': .95, 'y': .3}
size_hint: 0.25, 0.25
# Iffy - the third slug
RelativeLayout:
pos_hint: {'x': .09, 'center_y': .375}
size_hint: None, None
size: 143, 30
# the body image
Image:
source: 'assets/slugs/iffyBody.png'
# the left eye image
Image:
canvas.before:
PushMatrix
Rotate:
angle: 30
axis: 0, 0, 1
origin: self.x, self.center_y
canvas.after:
PopMatrix
source: 'assets/slugs/iffyEye.png'
pos_hint: {'x': .95, 'y': .45}
size_hint: 0.25, 0.25
# the right eye image
Image:
canvas.before:
PushMatrix
Rotate:
angle: -30
axis: 0, 0, 1
origin: self.x, self.center_y
canvas.after:
PopMatrix
source: 'assets/slugs/iffyEye.png'
pos_hint: {'x': .95, 'y': .3}
size_hint: 0.25, 0.25
# Slowpoke - the fourth slug
RelativeLayout:
pos_hint: {'x': .09, 'center_y': .125}
size_hint: None, None
size: 143, 30
# the body image
Image:
source: 'assets/slugs/slowpokeBody.png'
# the left eye image
Image:
canvas.before:
PushMatrix
Rotate:
angle: 30
axis: 0, 0, 1
origin: self.x, self.center_y
canvas.after:
PopMatrix
source: 'assets/slugs/slowpokeEye.png'
pos_hint: {'x': .95, 'y': .45}
size_hint: 0.25, 0.25
# the right eye image
Image:
canvas.before:
PushMatrix
Rotate:
angle: -30
axis: 0, 0, 1
origin: self.x, self.center_y
canvas.after:
PopMatrix
source: 'assets/slugs/slowpokeEye.png'
pos_hint: {'x': .95, 'y': .3}
size_hint: 0.25, 0.25
# winner
...
### THE BETS ###
...
If you now run the code, you will see the images of the slugs on the track:
This looks much better. The only thing that we still have to take care of is the winner image.
The Winner Image
At this moment we have the following code that creates the winner subarea of the Track area:
# File name: race.kv
...
### THE TRACK ###
...
# track
...
# winner
BoxLayout:
orientation: 'vertical'
size_hint: (.18, 1)
Label:
color: .2, .1, 0, 1
text: "The winner is"
font_size: 24
size_hint: 1, .2
bold: True
Label:
color: .2, .1, 0, 1
text: "Trusty"
font_size: 32
size_hint: 1, .2
bold: True
Label:
color: .2, .1, 0, 1
text: 'WINNER'
### THE BETS ###
...
And this is what it looks like when you run the app:
Let’s replace the label with the text ‘WINNER’ with an image of a slug. Which slug exactly appears here will later depend on which one has won a particular race, but for now let’s just choose one of them as a placeholder, for example Trusty. You will find the image file in the silhouettes subfolder of the assets folder. The name of the file is Trusty.png.
Here’s the code:
# File name: race.kv
...
### THE TRACK ###
...
# winner
BoxLayout:
orientation: 'vertical'
size_hint: (.18, 1)
Label:
color: .2, .1, 0, 1
text: "The winner is"
font_size: 24
size_hint: 1, .2
bold: True
Label:
color: .2, .1, 0, 1
text: "Trusty"
font_size: 32
size_hint: 1, .2
bold: True
Image:
source: 'assets/silhouettes/Trusty.png'
### THE BETS ###
...
And you should now see the silhouette of Trusty on the right. This slug is going to be a permanent winner for some time:
To be honest, this tutorial is the best of its sort that I’ve found anywhere, and I’ve scoured YouTube and Google. Your explanations make ‘building the clock’ possible: the other tutorials merely tell you what time it is, metaphorically speaking. Thank you for it! (subscribed)
I was wondering if it were possible to see the entire ‘race.kv’ file at this stage. I’ve been following this closely, and am really struggling with the positioning of the racetrack image and the white labels. pos_hint: {whatever} produces the same result regardless of what I replace ‘whatever’ with, none of it like your ‘final race screen’ screenshot at the top of this page.
Hey, here’s the link:
https://github.com/prospero-apps/python/tree/master/slugrace_code/slugrace%2031
Copy the Assets folder here: https://github.com/prospero-apps/python/tree/master/slugrace_code and paste it into the slugrace 31 folder.