Mechanic - Audio¶
In the Game Design lesson, we identified adding audio feedback would enhance the interactivity of our game. Below are the identified events that we could add sound effects to:
Event |
Sound Effect |
---|---|
Shooting laser |
Positive |
Shooting asteroid |
Positive |
Saving astronaut |
Positive |
Ship collides with asteroid |
Negative |
Shooting astronaut |
Negative |
It is also common for computer games to have a backing track being played throughout the entire game.
In this lesson we will implement all the sound effects and add a backing track.
Planning¶
Mechanisms¶
First we will need to work out what mechanisms are available for playing sounds. As always, our first place to look is the GameFrame Documentation. Once your on the documentation page, perform a search for sound (ctrl
+ F
or command
+ F
) to find all the sound related GameFrame features.
You will find:
Sounds folder → holds all the sound files. Check your sounds folder you will notice that you have been provided with all the required sound effect, along with some extra ones.
load_sounds()
which is a Rooms Method → we will use this to add our sounds.PyGame sound methods is something new. This is the first time that we use methods from the Pygame layer of our stack. From the Pygame methods, we can find methods for using sounds that we have loaded.
So there are two steps to playing sounds in GameFrame.
Load the sound → the sound is an attribute of the Room/Level object, therefore it must happen within the Room
__ init__()
Play the sound → this can be used by any object with the Room/Level, including RoomObjects, but they need to access the attribute of the Room they are in.
We now have our mechanisms, so let’s plan the code.
IPO tables¶
Background Music¶
The first question we need to resolve, is what Room/Level does the sound reside in? Normally background music starts playing as soon as the game has loaded, therefore, our music should be loaded and played from into the WelcomeScreen
Room/Level.
Sound Effects¶
The sound effects are a bit more complicated. They all occur in the GamePlay
Room/Level, but they are triggered by events tied to various RoomObjects within that room. Therefore, we need to split the loading and the playing over different objects:
loading will occur in the
GamePlay
classplay()
call will occur wherever the relevant event is handled
Now we know what we have to, lets start coding.
Coding¶
Rooms/WelcomeScreen.py
¶
We’ll start with the background music.
Open Rooms/WelcomeScreen.py
and then add or adjust the highlighted code below:
1from GameFrame import Level
2from Objects.Title import Title
3
4class WelcomeScreen(Level):
5 """
6 Intial screen for the game
7 """
8 def __init__(self, screen, joysticks):
9 Level.__init__(self, screen, joysticks)
10
11 # set background image
12 self.set_background_image("Background.png")
13
14 # add title object
15 self.add_room_object(Title(self, 240, 200))
16
17 # load sounds
18 self.bg_music = self.load_sound("Music.mp3")
19
20 # play background music
21 self.bg_music.play(loops=1)
Let’s investigate this code:
line 18: loads the
"Music.mp3"
sound file and saves it asbg_music
line 21: starts playing the music. Note
loops=1
means the music will restart once it reaches the end.
Save Rooms/WelcomeScreen.py
and the run MainController.py
to test if the music plays.
Rooms/GamePlay.py
¶
Now we need to load all the sound effects that will be used in the GamePlay room.
Open Rooms/GamePlay.py
and the 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)
24
25 # load sound files
26 self.shoot_laser = self.load_sound("Laser_shot.ogg")
27 self.asteroid_shot = self.load_sound("Asteroid_shot.wav")
28 self.astronaut_saved = self.load_sound("Astronaut_saved.ogg")
29 self.asteroid_collision = self.load_sound("Ship_damage.ogg")
30 self.astronaut_shot = self.load_sound("Astronaut_hit.ogg")
Unpacking the new code:
line 26: loads the
"Laser_shot.ogg"
sound file and saves it asshoot_laser
line 27: loads the
"Asteroid_shot.wav"
sound file and saves it asasteroid_shot
line 28: loads the
"Astronaut_saved.ogg"
sound file and saves it asastronaut_saved
line 29: loads the
"Ship_damage.ogg"
sound file and saves it asasteroid_collision
line 30: loads the
"Astronaut_hit.ogg"
sound file and saves it asastronaut_shot
Save and close Rooms/GamePlay.py
.
We now need to insert the sound effects into their respective event handlers.
Objects/Laser.py
¶
Open Objects/Laser.py
and the add or adjust the highlighted code below:
39 # --- Event handlers
40 def handle_collision(self, other, other_type):
41 """
42 Handles laser collisions with other registered objects
43 """
44 if other_type == "Asteroid":
45 self.room.asteroid_shot.play()
46 self.room.delete_object(other)
47 self.room.score.update_score(5)
48 elif other_type == "Astronaut":
49 self.room.astronaut_shot.play()
50 self.room.delete_object(other)
51 self.room.score.update_score(-10)
Looking at the code:
line 45: plays the
asteroid_shot
sound when a laser collides with an asteroid.since the
asteroid_shot
is an attribute of theGamePlay
Room the laser needs to access an attribute of another object.all RoomObjects have an attribute called
room
which identifies which room it belongs to.self.room.asteroid_shot.play()
should be read as:play the
asteroid_shot
soundthe belongs to the room
this asteroid is in
line 46: plays the
astronaut_shot
sound when a laser collides with an astronaut.
Save and close Rooms/Laser.py
.
Objects/Ship.py
¶
Open Objects/Ship.py
and the add or adjust the highlighted code below:
52 def shoot_laser(self):
53 """
54 Shoots a laser from the ship
55 """
56 if self.can_shoot:
57 new_laser = Laser(self.room,
58 self.x + self.width,
59 self.y + self.height/2 - 4)
60 self.room.add_room_object(new_laser)
61 self.can_shoot = False
62 self.set_timer(10,self.reset_shot)
63 self.room.shoot_laser.play()
Looking at the code:
line 63: plays the
shoot_laser
sound when the space bar is pressed
Save and close Objects/Ship.py
Objects/Asteroid.py
¶
Open Objects/Asteroid.py
and the 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 self.room.asteroid_collision.play()
61 Globals.LIVES -= 1
62 if Globals.LIVES > 0:
63 self.room.lives.update_image()
64 else:
65 self.room.running = False
Looking at the code:
line 60: plays the
asteroid_collision
sound when asteroid collides with the ship
Save and close Objects/Asteroid.py
Objects/Astronaut.py
¶
Open Objects/Astronaut.py
and the add or adjust the highlighted code below:
31 # --- Event Handlers
32 def handle_collision(self, other, other_type):
33 """
34 Handles the collision event for Astronaut objects
35 """
36 # ship collision
37 if other_type == "Ship":
38 self.room.astronaut_saved.play()
39 self.room.delete_object(self)
40 self.room.score.update_score(50)
Looking at the code:
line 60: plays the
astronaut_saved
sound when astronaut collides with the ship
Save and close Objects/Astronaut.py
Testing¶
Now it is time to test that the sounds effects are working.
In testing, use a testing table to make sure that you check all five of the sound effects:
Event |
Expected Sound |
Actual Sound |
Resolution |
---|---|---|---|
Shooting laser |
Laser_shot |
||
Shooting asteroid |
Asteroid_shot |
||
Saving astronaut |
Astronaut_saved |
||
Ship collides with asteroid |
Ship_damage |
||
Shooting astronaut |
Astronaut_hit |
Adjusting volume¶
After testing the game, you may want to adjust the mix of the sound. If you are using the sound file provided you will find the background music is too loud and overpowers the sound effect. Fortunately, we can adjust the volume of individual sounds by using the Pygame .volume()
method.
Rooms/WelcomeScreen.py
¶
Open Rooms/WelcomeScreen.py
and then add or adjust the highlighted code below:
1from GameFrame import Level
2from Objects.Title import Title
3
4class WelcomeScreen(Level):
5 """
6 Intial screen for the game
7 """
8 def __init__(self, screen, joysticks):
9 Level.__init__(self, screen, joysticks)
10
11 # set background image
12 self.set_background_image("Background.png")
13
14 # add title object
15 self.add_room_object(Title(self, 240, 200))
16
17 # load sounds
18 self.bg_music = self.load_sound("Music.mp3")
19
20 # play background music
21 self.bg_music.set_volume(0.1)
22 self.bg_music.play(loops=1)
Save and close Rooms/WelcemeScreen.py
and test that the volume levels are now correct using MainController.py
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.
Rooms/WelcomeScreen.py
¶
1from GameFrame import Level
2from Objects.Title import Title
3
4class WelcomeScreen(Level):
5 """
6 Intial screen for the game
7 """
8 def __init__(self, screen, joysticks):
9 Level.__init__(self, screen, joysticks)
10
11 # set background image
12 self.set_background_image("Background.png")
13
14 # add title object
15 self.add_room_object(Title(self, 240, 200))
16
17 # load sounds
18 self.bg_music = self.load_sound("Music.mp3")
19
20 # play background music
21 self.bg_music.set_volume(0.1)
22 self.bg_music.play(loops=1)
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)
24
25 # load sound files
26 self.shoot_laser = self.load_sound("Laser_shot.ogg")
27 self.asteroid_shot = self.load_sound("Asteroid_shot.wav")
28 self.astronaut_saved = self.load_sound("Astronaut_saved.ogg")
29 self.asteroid_collision = self.load_sound("Ship_damage.ogg")
30 self.astronaut_shot = self.load_sound("Astronaut_hit.ogg")
Objects/Laser.py
¶
1from GameFrame import RoomObject, Globals
2
3class Laser(RoomObject):
4 """
5 Class for the lasers shot by the Ship
6 """
7
8 def __init__(self, room, x, y):
9 """
10 Inistialise the laser
11 """
12 # include attributes and methods from RoomObject
13 RoomObject.__init__(self, room, x, y)
14
15 # set image
16 image = self.load_image("Laser.png")
17 self.set_image(image, 33, 9)
18
19 # set movement
20 self.set_direction(0, 20)
21
22 # handle events
23 self.register_collision_object("Asteroid")
24 self.register_collision_object("Astronaut")
25
26 def step(self):
27 """
28 Determine what happens to the laser on each tick of the game clock
29 """
30 self.outside_of_room()
31
32 def outside_of_room(self):
33 """
34 removes laser if it has exited the room
35 """
36 if self.x > Globals.SCREEN_WIDTH:
37 self.room.delete_object(self)
38
39 # --- Event handlers
40 def handle_collision(self, other, other_type):
41 """
42 Handles laser collisions with other registered objects
43 """
44 if other_type == "Asteroid":
45 self.room.asteroid_shot.play()
46 self.room.delete_object(other)
47 self.room.score.update_score(5)
48 elif other_type == "Astronaut":
49 self.room.astronaut_shot.play()
50 self.room.delete_object(other)
51 self.room.score.update_score(-10)
Objects/Ship.py
¶
1from GameFrame import RoomObject, Globals
2from Objects.Laser import Laser
3import pygame
4
5class Ship(RoomObject):
6 """
7 A class for the player's avitar (the Ship)
8 """
9
10 def __init__(self, room, x, y):
11 """
12 Initialise the Ship object
13 """
14 RoomObject.__init__(self, room, x, y)
15
16 # set image
17 image = self.load_image("Ship.png")
18 self.set_image(image,100,100)
19
20 # register events
21 self.handle_key_events = True
22
23 self.can_shoot = True
24
25 def key_pressed(self, key):
26 """
27 Respond to keypress up and down
28 """
29
30 if key[pygame.K_w]:
31 self.y_speed = -10
32 elif key[pygame.K_s]:
33 self.y_speed = 10
34 if key[pygame.K_SPACE]:
35 self.shoot_laser()
36
37 def keep_in_room(self):
38 """
39 Keeps the ship inside the room
40 """
41 if self.y < 0:
42 self.y = 0
43 elif self.y + self.height> Globals.SCREEN_HEIGHT:
44 self.y = Globals.SCREEN_HEIGHT - self.height
45
46 def step(self):
47 """
48 Determine what happens to the Ship on each click of the game clock
49 """
50 self.keep_in_room()
51
52 def shoot_laser(self):
53 """
54 Shoots a laser from the ship
55 """
56 if self.can_shoot:
57 new_laser = Laser(self.room,
58 self.x + self.width,
59 self.y + self.height/2 - 4)
60 self.room.add_room_object(new_laser)
61 self.can_shoot = False
62 self.set_timer(10,self.reset_shot)
63 self.room.shoot_laser.play()
64
65 def reset_shot(self):
66 """
67 Allows ship to shoot again
68 """
69 self.can_shoot = True
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 self.room.asteroid_collision.play()
61 Globals.LIVES -= 1
62 if Globals.LIVES > 0:
63 self.room.lives.update_image()
64 else:
65 self.room.running = False
Objects/Astronaut.py
¶
1from GameFrame import RoomObject
2
3class Astronaut(RoomObject):
4 """
5 Class for the astronauts escaping from Zork
6 """
7
8 def __init__(self,room,x,y):
9 """
10 Initialise the astronaut instance
11 """
12 # include attirbutes and method from RoomObject
13 RoomObject.__init__(self,room,x,y)
14
15 # set image
16 image = self.load_image("Astronaut.png")
17 self.set_image(image,50,49)
18
19 # set travel direction
20 self.set_direction(180, 5)
21
22 # handle events
23 self.register_collision_object("Ship")
24
25 def step(self):
26 """
27 Determines what happend to the astronaut on each tick of the game clock
28 """
29 self.outside_of_room()
30
31 # --- Event Handlers
32 def handle_collision(self, other, other_type):
33 """
34 Handles the collision event for Astronaut objects
35 """
36 # ship collision
37 if other_type == "Ship":
38 self.room.astronaut_saved.play()
39 self.room.delete_object(self)
40 self.room.score.update_score(50)
41
42 def outside_of_room(self):
43 """
44 removes astronauts that have exited the room
45 """
46 if self.x + self.width < 0:
47 self.room.delete_object(self)