In the previous part we added some Kivy properties to the Settings screen. In this part we’ll add some to the Race screen.
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…
The Slugs’ Stats
Open the race.kv file and scroll down until you find the Slugs’ Stats section:
# File name: race.kv
...
<RaceScreen>:
...
### INFO, STATS AND BUTTONS ###
GridLayout:
...
# Slugs' Stats
BoxLayout:
...
BoxLayout:
spacing: 10
RegularLabel:
text: "Speedster"
RegularLabel:
text: '7 wins'
RegularLabel:
text: '70%'
BoxLayout:
spacing: 10
RegularLabel:
text: "Trusty"
RegularLabel:
text: '1 win'
RegularLabel:
text: '10%'
BoxLayout:
spacing: 10
RegularLabel:
text: "Iffy"
RegularLabel:
text: '0 wins'
RegularLabel:
text: '0%'
BoxLayout:
spacing: 10
RegularLabel:
text: "Slowpoke"
RegularLabel:
text: '2 wins'
RegularLabel:
text: '20%'
# Players' Stats
...
Here the code is pretty simple and actually we could leave it as is, but we can still simplify it slightly with some properties. You can see four BoxLayouts, one for each slug. Each contains three RegularLabels. So, in the race.py file we’ll add the SlugStats class that inherits from BoxLayout and define three Kivy properties in it, a StringProperty and two NumericProperties. We’ll name the StringProperty name and the NumericProperties wins and win_percent and set their default values to an empty string, 0 and 0 respectively. We have to import the NumericProperty and StringProperty classes first.
So, here’s the code:
# File name: race.py
import kivy
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.config import Config
# Import the classes that you will need.
from kivy.properties import NumericProperty, StringProperty
# Configuration
...
class SlugStats(BoxLayout):
name = StringProperty('')
wins = NumericProperty(0)
win_percent = NumericProperty(0)
class RaceScreen(BoxLayout):
...
Then, in the kv file we have to add the SlugStats rule and set the text properties of the three RegularLabels to name, wins and win_percent respectively.
Now, as for the three properties, we must make sure that:
– the text on the first RegularLabel displays just the value of the name property
– the text on the second RegularLabel displays the value of the wins property followed by the word ‘win’ or ‘wins’ depending on whether the value of wins is 1 or anything else. We can use concatenation to add the ‘win’ or ‘wins’ word and use the ternary if statement to decide which of the two should be chosen.
– the text on the third RegularLabel displays the value of the win_percent property followed by the % sign.
Finally we’ll replace the BoxLayouts in the Slugs’ Stats subarea by instances of the SlugStats class. We’ll use the same values as before. And here’s the kv file:
# File name: race.kv
...
<SlugStats>:
spacing: 10
RegularLabel:
text: root.name
RegularLabel:
text: str(root.wins) + (' win' if root.wins == 1 else ' wins')
RegularLabel:
text: str(root.win_percent) + '%'
<RaceScreen>:
...
### INFO, STATS AND BUTTONS ###
...
# Slugs' Stats
...
BoldLabel:
text: "Slugs' Stats"
SlugStats:
name: 'Speedster'
wins: 7
win_percent: 70
SlugStats:
name: 'Trusty'
wins: 1
win_percent: 10
SlugStats:
name: 'Iffy'
wins: 0
win_percent: 0
SlugStats:
name: 'Slowpoke'
wins: 2
win_percent: 20
# Players' Stats
...
If we run the program, it will work as before.
The Players’ Stats
Just below the Slug’s Stats section is the Players’ Stats section. Let’s simplify this part too. In the Python file let’s add the PlayerStats class that inherits from BoxLayout and add two properties to it: the StringProperty name and the NumericProperty money. Let’s initialize them to an empty string and 0 respectively:
# File name: race.py
...
class SlugStats(BoxLayout):
...
class PlayerStats(BoxLayout):
name = StringProperty('')
money = NumericProperty(0)
class RaceScreen(BoxLayout):
...
Then, in the kv file, let’s add the corresponding rule. There are two RegularLabels. The first one’s text should be set to the name property and the second one’s text should read: ‘has $’ followed by the value of the money property. Finally, let’s use instances of the PlayerStats class in the code instead of the BoxLayouts. We’ll set the properties to the same values for now:
# File name: race.kv
...
<SlugStats>:
...
<PlayerStats>:
RegularLabel:
text: root.name
RegularLabel:
text: 'has $' + str(root.money)
<RaceScreen>:
...
### INFO, STATS AND BUTTONS ###
...
# Players' Stats
BoxLayout:
...
BoldLabel:
text: "Players' Stats"
PlayerStats:
name: 'Player 1'
money: 1000
PlayerStats:
name: 'Player 2'
money: 800
PlayerStats:
name: 'Player 3'
money: 1300
PlayerStats:
name: 'Player 4'
money: 1200
# Buttons
...
Slug Info in the Track Area
We’re still in the Race screen. If you scroll down, you will find the Track area with the labels with slug info. They look like they can be simplified as well:
# File name: race.kv
...
<RaceScreen>:
...
### THE TRACK ###
...
# track
...
# labels with slug info
BoxLayout:
orientation: 'vertical'
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 wins'
# the odds labels
...
As you can see, they look pretty much the same. There are some differences, though:
– the center_y property inside the pos_hint property is different,
– the names of the slugs are different,
– the wins stats are different.
When defining our class, we have to take the differences into account. So, in the Python file, let’s add the SlugInfo class that inherits from BoxLayout and let’s add three properties to it:
– the NumericProperty y_position, initialized to 0,
– the StringProperty name, initialized to an empty string,
– the NumericProperty wins, initialized to 0.
Here’s the Python file:
# File name: race.py
...
class SlugStats(BoxLayout):
...
class PlayerStats(BoxLayout):
...
class SlugInfo(BoxLayout):
y_position = NumericProperty(0)
name = StringProperty('')
wins = NumericProperty(0)
class RaceScreen(BoxLayout):
...
Then, in the kv file let’s add the corresponding rule. In the rule you should set the orientation, size_hint and size properties to the same values as they are in the BoxLayouts. You should also set the pos_hint property, which requires a dictionary. So, in the dictionary x should be set to .004, so just as it is in the BoxLayouts, but center_y should be set to the y_position property.
Then, in the two WhiteLabels, we must set the text properties to the name and wins properties respectively. In the latter case we’ll use the same concatenated string as in the SlugStats rule (so with the ternary if statement).
Finally, let’s replace the four BoxLayouts in the code with instances of the SlugInfo class. For the name and y_position properties use the same values as before, but the wins properties for the particular slugs should be set to 7, 1, 0 and 2 respectively so that they match the numbers in the Slugs’ Stats subarea. Here’s the kv file:
# File name: race.kv
...
<SlugStats>:
...
<PlayerStats>:
...
<SlugInfo>:
orientation: 'vertical'
size_hint: None, None
size: 100, 50
pos_hint: {'x': .004, 'center_y': root.y_position}
WhiteNameLabel:
text: root.name
WhiteWinsLabel:
text: str(root.wins) + (' win' if root.wins == 1 else ' wins')
<RaceScreen>:
...
### THE TRACK ###
...
# track
...
# labels with slug info
SlugInfo:
y_position: .875
name: 'Speedster'
wins: 7
SlugInfo:
y_position: .625
name: 'Trusty'
wins: 1
SlugInfo:
y_position: .375
name: 'Iffy'
wins: 0
SlugInfo:
y_position: .125
name: 'Slowpoke'
wins: 2
# the odds labels
...
The Slug Images
We’re not yet done with the Race screen. Here come the four slug images:
# File name: race.kv
...
<RaceScreen>:
...
### THE TRACK ###
BoxLayout:
...
# track
...
# slug images
# Speedster
RelativeLayout:
pos_hint: {'x': .09, 'center_y': .875}
size_hint: None, None
size: 143, 30
Image:
source: 'atlas://assets/slugs/slugs/speedsterBody'
# 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: 'atlas://assets/slugs/slugs/speedsterEye'
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: 'atlas://assets/slugs/slugs/speedsterEye'
pos_hint: {'x': .95, 'y': .3}
size_hint: 0.25, 0.25
# Trusty
RelativeLayout:
pos_hint: {'x': .09, 'center_y': .625}
size_hint: None, None
size: 143, 30
# the body image
Image:
source: 'atlas://assets/slugs/slugs/trustyBody'
# 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: 'atlas://assets/slugs/slugs/trustyEye'
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: 'atlas://assets/slugs/slugs/trustyEye'
pos_hint: {'x': .95, 'y': .3}
size_hint: 0.25, 0.25
# Iffy
RelativeLayout:
pos_hint: {'x': .09, 'center_y': .375}
size_hint: None, None
size: 143, 30
# the body image
Image:
source: 'atlas://assets/slugs/slugs/iffyBody'
# 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: 'atlas://assets/slugs/slugs/iffyEye'
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: 'atlas://assets/slugs/slugs/iffyEye'
pos_hint: {'x': .95, 'y': .3}
size_hint: 0.25, 0.25
# Slowpoke
RelativeLayout:
pos_hint: {'x': .09, 'center_y': .125}
size_hint: None, None
size: 143, 30
# the body image
Image:
source: 'atlas://assets/slugs/slugs/slowpokeBody'
# 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: 'atlas://assets/slugs/slugs/slowpokeEye'
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: 'atlas://assets/slugs/slugs/slowpokeEye'
pos_hint: {'x': .95, 'y': .3}
size_hint: 0.25, 0.25
# winner
...
These look pretty complex, right? That’s mainly because they are. But don’t worry, your task is easier than it looks.
Each slug is represented by a RelativeLayout that contains three images, the body image and two eye images. Now, what are the differences? If we know this, we’ll know what properties we need. So, the four slug images differ in the following:
– the body and eye images have different sources,
– just like with the SlugInfo widgets, the center_y property inside the pos_hint property of the RelativeLayout for each slug is different.
And that’s essentially it. All the other properties are shared in some way. So, we will need three properties, two for the sources of the body and eye images and one for the center_y value inside the pos_hint property. So, in the Python file let’s add the SlugImage class that inherits from RelativeLayout (let’s not forget to import the RelativeLayout class first) and let’s add the following properties:
– the StringProperty body_image, initialized to an empty string,
– the StringProperty eye_image, initialized to an empty string,
– the NumericProperty y_position, initialized to 0.
Here’s the Python file:
# File name: race.py
import kivy
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.config import Config
from kivy.properties import NumericProperty, StringProperty
# Configuration
...
class SlugStats(BoxLayout):
...
class PlayerStats(BoxLayout):
...
class SlugInfo(BoxLayout):
...
class SlugImage(RelativeLayout):
body_image = StringProperty('')
eye_image = StringProperty('')
y_position = NumericProperty(0)
class RaceScreen(BoxLayout):
...
Then, in the kv file let’s add the SlugImage rule. Let’s follow these guidelines:
1. The root’s size_hint and size properties should be set like before, so to (None, None) and (143, 30) respectively.
2. In the root’s pos_hint property x should be set like before, so to .09, and center_y should be set to the y_position property.
3. To make the code more concise later in the code, let’s set the source of the body image to the string ‘assets/slugs/’ followed by the value of the body_image property and the file format extension. This will allow us to set the body_image property later in the code to something like ‘speedsterBody’ instead of the full path.
4. We’ll use the same strategy to set the source properties of the eye images accordingly. The rest of the eye images should be implemented exactly like before.
Finally, let’s replace the four RelativeLayouts in the code by instances of the SlugImage class. Let’s set the properties to the same values. Remember to set the body_image and eye_image properties just to the names of the particular images, not the full paths. Here’s the kv file:
# File name: race.kv
### CLASS RULES ###
...
<SlugStats>:
...
<PlayerStats>:
...
<SlugInfo>:
...
<SlugImage>:
pos_hint: {'x': .09, 'center_y': root.y_position}
size_hint: None, None
size: 143, 30
# the body image
Image:
source: 'assets/slugs/' + root.body_image + '.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/' + root.eye_image + '.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/' + root.eye_image + '.png'
pos_hint: {'x': .95, 'y': .3}
size_hint: 0.25, 0.25
<RaceScreen>:
...
### THE TRACK ###
BoxLayout:
...
# track
RelativeLayout:
...
# slug images
# Speedster
SlugImage:
body_image: 'speedsterBody'
eye_image: 'speedsterEye'
y_position: .875
# Trusty
SlugImage:
body_image: 'trustyBody'
eye_image: 'trustyEye'
y_position: .625
# Iffy
SlugImage:
body_image: 'iffyBody'
eye_image: 'iffyEye'
y_position: .375
# Slowpoke
SlugImage:
body_image: 'slowpokeBody'
eye_image: 'slowpokeEye'
y_position: .125
# 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: 'atlas://assets/silhouettes/silhouettes/Trusty'
### THE BETS ###
Label:
canvas:
Color:
rgba: .2, .1, 0, 1
Line:
rounded_rectangle: self.x, self.y, self.width, self.height, 10
width: 2
color: .2, .1, 0, 1
text: 'The Bets'
That’s it as far as the Race screen is concerned. Run the app again to make sure everything works as before. And in the next part we’ll move on to the Bets screen and add some Kivy properties there.