Add Lives¶
At the moment colliding with just one asteroid ends the game. This is a bit tough. So for the final step in our game development journey is giving our player lives.
Planning¶
We need to work out what mechanisms will use for lives. Looking at the GameFrame documentation for Global Variables you will notice there is a variable called LIVES
. This is similar to SCORE
.
We could just have the score value show as a number, but that is a bit boring. Instead we will display hearts to represent the number of lives. If you look inside the Images/Lives_frames
and you will see five images with 1 to 5 hearts. So when a player loses a life, we will change the image showing the number of lives. How can we do that?
To this point we have only used set_image
in the __init__
to assign an image to an object when it is instantiated. There is nothing stopping us from using set_image
elsewhere in response to other events, like loosing a life. You will also remember that we need to use load_image
before we can use set_image
.
Below are flowcharts of two possible ways to approach this:
Method 1 → each time
LIVES
changes, load the image and then set the imageMethod 2 → preload the images into a list once and then set the image when
LIVES
changes
Both methods require the same resource if you intend for players lives to only decrease. In this case each possible LIVES
score will only be displayed once. Therefore, each image will only be loaded once.
Method 2 is superior if you intend player’s lives go up as well as down. In this case it is possible that any given LIVES
score may be displayed multiple times. Method 1 may need to load each image multiple times, but Method 2 will still only load each image once.
When to use data structures
Data structures are used in computer science to organize, store, and manage data in a way that enables efficient operations and access. The most common Python data structures are: lists, tuples, dictionaries and sets.
Use data structures to gather values that logically belong together. For example, we use tuples to gather coordinates together. Coordinates contain two variables x
and y
that logically belong together so we place them in a tuple: (x, y)
Since we want to leave open the option of giving bonus lives, we’ll use Method 2 which will look like this in an IPO:
With all that sorted out, lets get on with the coding.
Coding¶
Objects/Hud.py
¶
Since the lives icons are still part of the HUD, we will put the Lives
class in Objects/Hud.py
.
Open Objects/Hud.py
and then add or adjust the highlighted code below:
1from GameFrame import TextObject, Globals, RoomObject
2
3class Score(TextObject):
4 """
5 A class for displaying the current score
6 """
7 def __init__(self, room, x: int, y: int, text=None):
8 """
9 Intialises the score object
10 """
11 # include attributes and methods from TextObject
12 TextObject.__init__(self, room, x, y, text)
13
14 # set values
15 self.size = 60
16 self.font = 'Arial Black'
17 self.colour = (255,255,255)
18 self.bold = False
19 self.update_text()
20
21 def update_score(self, change):
22 """
23 Updates the score and redraws the text
24 """
25 Globals.SCORE += change
26 self.text = str(Globals.SCORE)
27 self.update_text()
28
29
30class Lives(RoomObject):
31 """
32 A class for displaying the number of lives remaining
33 """
34 def __init__(self, room, x: int, y: int):
35 """
36 Intialises the lives object
37 """
38 RoomObject.__init__(self, room, x, y)
39
40 # set image
41 self.lives_icon = []
42 # load the various lives images into a live list
43 for index in range(6):
44 self.lives_icon.append(self.load_image(f"Lives_frames/Lives_{index}.png"))
45 self.update_image()
46
47
48 def update_image(self):
49 """
50 Updates the number of lives on the UI
51 """
52 self.set_image(self.lives_icon[Globals.LIVES], 125, 23)
Unpacking those changes:
line 1: the
Lives
is going to be a RoomObject, so we need to import this class.lines 30-38: defines the
Lives
class as a RoomObjectline 41: creates the
lives_icon
list that we will use to store our imagesline 43 & 44: create a loop that appends each of the loaded images to the
lives_icon
listload_image
accepts a string that is the file namewe can use the index value to create that file name
line 45: calls
update_image
to load the first imagelines 48-51: defines the
update image
methodline 52: sets the
Lives
image to the image that corresponds toGlobals.LIVES
, For example:at the beginning of the game
Globals.LIVES
is3
this will display
self.lives_icon[3]
which is the image
"Lives_frames/Lives_3.png"
Save Objects/Hud.py
.
Object/__init__.py
¶
Although we haven’t created a new file, we still need to let GameFrame know about our new Lives
class.
Open Object/__init__.py
and then adjust the highlighted code below.
1from Objects.Title import Title
2from Objects.Ship import Ship
3from Objects.Zork import Zork
4from Objects.Asteroid import Asteroid
5from Objects.Laser import Laser
6from Objects.Astronaut import Astronaut
7from Objects.Hud import Score, Lives
Save and close Object/__init__.py
.
Rooms/GamePlay.py
¶
Finally we need to add a Lives
object to the GamePlay
Room so open Rooms/GamePlay.py
, and then add or adjust the highlighted code below.
1from GameFrame import Level, Globals
2from Objects.Ship import Ship
3from Objects.Zork import Zork
4from Objects.Hud import Score, Lives
5
6class GamePlay(Level):
7 def __init__(self, screen, joysticks):
8 Level.__init__(self, screen, joysticks)
9
10 # set background image
11 self.set_background_image("Background.png")
12
13 # add objects
14 self.add_room_object(Ship(self, 25, 50))
15 self.add_room_object(Zork(self,1120, 50))
16
17 # add HUD items
18 self.score = Score(self,
19 Globals.SCREEN_WIDTH/2 - 20, 20,
20 str(Globals.SCORE))
21 self.add_room_object(self.score)
22 self.lives = Lives(self, Globals.SCREEN_WIDTH - 150, 20)
23 self.add_room_object(self.lives)
The new code is so similar to the Score
code, we won’t bother exploring it.
Save and close Rooms/GamePlay.py
then test that our mechanism correctly loads the initial 3 lives by running MainController.py
.
Objects/Asteroid.py
¶
Now we need to associate the change in Globals.LIVES
with the Asteroid and Ship collision. This collision event handler in the the Asteroid
class, so Open Objects/Asteroid.py
and then add or adjust the highlighted code below:
53 def handle_collision(self, other, other_type):
54 """
55 Handles the collision events for the Asteroid
56 """
57
58 if other_type == "Ship":
59 self.room.delete_object(self)
60 Globals.LIVES -= 1
61 if Globals.LIVES > 0:
62 self.room.lives.update_image()
63 else:
64 self.room.running = False
Exploring that code:
line 59: deletes this asteroid (
self
)line 60: reduces
Global.LIVES
by1
line 61: checks if the player still has lives left
line 62: calls
lives.update_image
to change the number of lives on displayline 64: ends the game
Save Objects/Asteroid.py
and then run MainController.py
to test our code. Make sure that the lives are reduced every time an Asteroid collides with the Ship.
Commit and Push¶
We have finished and tested another section of code so we should make a Git commit.
To do this:
In GitHub Desktop go to the bottom left-hand box and write into the summary Added lasers.
Click on Commit to main
Click on Push origin
Now the work from this lesson is committed and synced with the online repo.
Completed File States¶
Below are all the files we used in this lesson in their finished state. Use this to check if your code is correct.
Objects/Hud.py
¶
1from GameFrame import TextObject, Globals, RoomObject
2
3class Score(TextObject):
4 """
5 A class for displaying the current score
6 """
7 def __init__(self, room, x: int, y: int, text=None):
8 """
9 Intialises the score object
10 """
11 # include attributes and methods from TextObject
12 TextObject.__init__(self, room, x, y, text)
13
14 # set values
15 self.size = 60
16 self.font = 'Arial Black'
17 self.colour = (255,255,255)
18 self.bold = False
19 self.update_text()
20
21 def update_score(self, change):
22 """
23 Updates the score and redraws the text
24 """
25 Globals.SCORE += change
26 self.text = str(Globals.SCORE)
27 self.update_text()
28
29
30class Lives(RoomObject):
31 """
32 A class for displaying the number of lives remaining
33 """
34 def __init__(self, room, x: int, y: int):
35 """
36 Intialises the lives object
37 """
38 RoomObject.__init__(self, room, x, y)
39
40 # set image
41 self.lives_icon = []
42 # load the various lives images into a live list
43 for index in range(6):
44 self.lives_icon.append(self.load_image(f"Lives_frames/Lives_{index}.png"))
45 self.update_image()
46
47
48 def update_image(self):
49 """
50 Updates the number of lives on the UI
51 """
52 self.set_image(self.lives_icon[Globals.LIVES], 125, 23)
Object/__init__.py
¶
1from Objects.Title import Title
2from Objects.Ship import Ship
3from Objects.Zork import Zork
4from Objects.Asteroid import Asteroid
5from Objects.Laser import Laser
6from Objects.Astronaut import Astronaut
7from Objects.Hud import Score, Lives
Rooms/GamePlay.py
¶
1from GameFrame import Level, Globals
2from Objects.Ship import Ship
3from Objects.Zork import Zork
4from Objects.Hud import Score, Lives
5
6class GamePlay(Level):
7 def __init__(self, screen, joysticks):
8 Level.__init__(self, screen, joysticks)
9
10 # set background image
11 self.set_background_image("Background.png")
12
13 # add objects
14 self.add_room_object(Ship(self, 25, 50))
15 self.add_room_object(Zork(self,1120, 50))
16
17 # add HUD items
18 self.score = Score(self,
19 Globals.SCREEN_WIDTH/2 - 20, 20,
20 str(Globals.SCORE))
21 self.add_room_object(self.score)
22 self.lives = Lives(self, Globals.SCREEN_WIDTH - 150, 20)
23 self.add_room_object(self.lives)
Objects/Asteroid.py
¶
1from GameFrame import RoomObject, Globals
2import random
3
4class Asteroid(RoomObject):
5 """
6 A class for Zorks danerous obstacles
7 """
8
9 def __init__(self, room, x, y):
10 """
11 Initialise the Asteroid object
12 """
13 # include attributes and methods from RoomObject
14 RoomObject.__init__(self,room, x, y)
15
16 # set image
17 image = self.load_image("asteroid.png")
18 self.set_image(image,50,49)
19
20 # set travel direction
21 angle = random.randint(135,225)
22 self.set_direction(angle, 10)
23
24 # register events
25 self.register_collision_object("Ship")
26
27 def step(self):
28 """
29 Determines what happens to the asteroid on each tick of the game clock
30 """
31 self.keep_in_room()
32 self.outside_of_room()
33
34 def keep_in_room(self):
35 """
36 Keeps the asteroid inside the top and bottom room limits
37 """
38 if self.y < 0:
39 self.y = 0
40 self.y_speed *= -1
41 elif self.y > Globals.SCREEN_HEIGHT - self.height:
42 self.y = Globals.SCREEN_HEIGHT - self.height
43 self.y_speed *= -1
44
45 def outside_of_room(self):
46 """
47 removes asteroid that have exited the room
48 """
49 if self.x + self.width < 0:
50 print("asteroid deleted")
51 self.room.delete_object(self)
52
53 def handle_collision(self, other, other_type):
54 """
55 Handles the collision events for the Asteroid
56 """
57
58 if other_type == "Ship":
59 self.room.delete_object(self)
60 Globals.LIVES -= 1
61 if Globals.LIVES > 0:
62 self.room.lives.update_image()
63 else:
64 self.room.running = False