Add Laser

Since Zork is hurling Asteroids at our defenceless space ship, we better give it something to fight back. In this lesson will will arm the space ship with a laser.

Shooting the Laser

Planning

We want to ship to spawn a laser whenever we press the space key.

Laser IPO

This involves:

  • creating a new laser class

  • adding an event handler of the space key which create a laser object

  • make the laser object move across the room when spawned

  • deleting the laser object when it leaves the room

We already know how to do all this, so lets get to the coding.

Coding

Objects/Laser.py

Create a new file in the Objects folder called Laser.py and then add the following code to it.

 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    def step(self):
23        """
24        Determine what happens to the laser on each tick of the game clock
25        """
26        self.outside_of_room()
27        
28    def outside_of_room(self):
29        """
30        removes laser if it has exited the room
31        """
32        if self.x > Globals.SCREEN_WIDTH:
33            self.room.delete_object(self)

The only change worth noting is:

  • line 32: since the laser is moving right, we delete it after it’s x has moved past the screen width

Save Objects/Laser.py

Objects/__init__.py

We need to tell GameFrame that we have added a new RoomObject to the Objects folder.

Open Objects/__init__.py and add 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

Save and close Objects/__init__.py

Objects/Ship.py

Open Objects/Ship.py and add the highlighted code below:

 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    def key_pressed(self, key):
24        """
25        Respond to keypress up and down
26        """
27        
28        if key[pygame.K_w]:
29            self.y_speed = -10
30        elif key[pygame.K_s]:
31            self.y_speed = 10
32        if key[pygame.K_SPACE]:
33            self.shoot_laser()
34            
35    def keep_in_room(self):
36        """
37        Keeps the ship inside the room
38        """
39        if self.y < 0:
40            self.y = 0
41        elif self.y + self.height> Globals.SCREEN_HEIGHT:
42            self.y = Globals.SCREEN_HEIGHT - self.height
43            
44    def step(self):
45        """
46        Determine what happens to the Ship on each click of the game clock
47        """
48        self.keep_in_room()
49        
50    def shoot_laser(self):
51        """
52        Shoots a laser from the ship
53        """
54        new_laser = Laser(self.room, 
55                          self.x + self.width, 
56                          self.y + self.height/2 - 4)
57        self.room.add_room_object(new_laser)

Lets break that down:

  • line 2: since we want the Ship to create a Laser object, we need to import the Laser class

  • lines 32-33: handles the event of the space key being pressed

  • lines 50-57:

    • creates a new laser object at the middle of the right side of the ship

    • adds the laser object to the Room the Ship is in

Spliting instructions over multiple lines

Lines 54 - 56 are actually one instruction split over 3 lines to make it easier to read.

The Python Style Guide recommends that lines of code shouldn’t be more than 79 characters long. Although this isn’t followed religiously, it is frown upon to write lines of code that are two long to read without scrolling sideway.

You can split arugements by placing a linebreak (pressing Enter) after any , (like in the code above). You can also place line breaks after , in list, dictionaries and tuples.

Placing long expressions inside parenthesis ( ) allows you to place a linebreak after any alrogithmic or Boolean operand.

This method can also be used to logically group operations in a exteremely long expression.

Save Ship.py and then test the code by running MainController.py.

Restricting the laser

When testing, did you notice what happens if you hold down the space key? There is a constant stream of lasers flowing across the screen. You might even notice that some of the other object freeze. What is that?

Since our event lister is tied to the frame rate, it is detecting the space key press 30 times a second, and, consequently, spawning 30 lasers a second. That’s a lot of lasers. We need to work out a way to restrict how many lasers can be shot a second.

Planning

To address this issue we will limit how frequently the Ship can spawn a Laser. To do this we are going to use a flag variable.

Flag variables

Flag variables, also known as Boolean flags, are variables used in computer programming to represent the state of a condition or a specific situation. Flag variables act as signals, indicating whether a particular condition is true or false, and they help control the flow of a program.

Specifically we are going to create a flag variable called can_shoot initialised to True. A laser can only spawn when can_shoot is True. Every time a laser is spawned, can_shoot will be set to False and a timer is started. When that timer ends, can_shoot is set back to True.

Lets look at that in an IPO table.

laser with flag IPO

Coding

Objects/Ship.py

Restricting the number of lasers is all about laser spawning, which happens in the Ship class so go back to Objects/Ship.py and change the highlighted code.

 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            
64    def reset_shot(self):
65        """
66        Allows ship to shoot again
67        """
68        self.can_shoot = True

Investigating those changes:

  • line 23: creates our flag variable and initializes it to True

  • line 56: checks whether the Ship is allows to spawn a laser

  • lines 57-60: these haven’t changed, but their indentation has increased one level to place them inside the if statement.

  • line 61: after spawning a laser, the can_shoot flag is set to False to prevent other lasers being spawned

  • line 62: sets a timer which will call the reset_shot function when finished.

  • lines 64-68: this function will be called when the timer has reached 0. It changed the can_shoot flag back to True.

Save Ship.py then run MainController.py to test that everything is working as planned.

Commit and Push

We have finished and tested another section of code so we should make a Git commit.

To do this:

  1. In GitHub Desktop go to the bottom left-hand box and write into the summary Added lasers.

  2. Click on Commit to main

  3. 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/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    def step(self):
23        """
24        Determine what happens to the laser on each tick of the game clock
25        """
26        self.outside_of_room()
27        
28    def outside_of_room(self):
29        """
30        removes laser if it has exited the room
31        """
32        if self.x > Globals.SCREEN_WIDTH:
33            self.room.delete_object(self)

Objects/__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

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            
64    def reset_shot(self):
65        """
66        Allows ship to shoot again
67        """
68        self.can_shoot = True