Keeping Ship in the Room

By now you would have noticed that the ship can move both below the bottom of the screen and above the top of the screen. Lets prevent this from happening.

Planning

Before we write the code to prevent our ship from flying off the screen, we first have to work out how we can tell the program that this is happening.

In English this it is quite simple:

  • if the top of the ship is above the screen → move it to inside the screen

  • if the bottom of the ship is below the screen → move it to inside the screen

Some of those concepts we need to translate into Python, so lets look at how we can talk to our program about the concepts:

  • top of the ship

  • bottom of the ship

  • above the screen

  • below the screen

In order to do this we need to consider the screen and how we can reference these different concepts.

Out of bounds screen

Each of the concepts is related to a value we use in program.

  • top of the ship → the origin y value → y

  • bottom of the ship → the origin y value + the height of the object → y + self.height

  • above the screen → a y value less than zero → y < 0

  • below the screen → a y value greater than the screen height → y > Globals.SCREEN_HEIGHT

Therefore we need to regularly check that:

  • ship.y > 0

  • ship.y + ship.height < Globals.SCREEN_HEIGHT

What we need is a way to do this. Let’s consult the docs. Remember we said that the game logic resides in the objects, so let’s look specifically at the RoomObject methods.

Notice the step method? According to the docs, we can use it to run code for the object on each tick of the game clock.

Game Clock

In the early days of computers, games use to run as fast as the hardware they were running on wouild let them. This meant that the same game could run faster or slower depending on the computer you were using. This also presented a problem when computers rapidly increased their power, resulting in games that were unplayable. The game clock was created to address this issue.

Game clocks ensure that each run of the game loop takes a consistent time. During the run the game loop will, handle inputs, update the game state, update the screen. If all this is finished with time to spare, the computer (sleeps) waits until the next tick of the game clock.

In GameFrame the game clock is the same as our frame rate, so there is a tick of the game clock every 1/30 of a second.

Now we know the mechanism and values we can use, we can plan our code.

First, what output do we want from this process?

Out of bounds IPO output

Next, what inputs will trigger this process?

Out of bounds IPO input

Finally, what process can get us from the input to the output?

Out of bounds IPO process

In summary, every tick of the game clock we will check if the ship is outside the screen, and if it is, we will move it back in.

Coding

Object/Ship.py

All this code needs to be implemented inside the Ship class so we need to open Objects/Ship.py.

Now we need to work out where. We could put this all inside the step method, and it would work, but what if we want to do other things with the ship on each tick of the clock? It could start getting really messy.

To make our code more maintainable, we’re going to keep our step method as small and clean as possible. We will do this by creating methods for the game logic and then calling those methods in step.

keep_in_room

So, lets put the game logic into a method called keep_in_room. Add the code below to the bottom of the Ship class.

32    def keep_in_room(self):
33        """
34        Keeps the ship inside the room
35        """
36        if self.y < 0:
37            self.y = 0
38        elif self.y + self.height> Globals.SCREEN_HEIGHT:
39            self.y = Globals.SCREEN_HEIGHT - self.height

Let’s break down lines 36-39 (it might be useful to check the screen diagram in the planning):

  • line 36: checks if the top of the ship (self.y) is less then the top of the screen (0)

  • line 37: sets the ship’s origin position to the top of the screen

  • line 38: checks if bottom of the ship (self.y + self.height) is less then the bottom of the screen (Globals.SCREEEN_HEIGHT)

  • line 39: sets the ship’s origin position to far enough above the bottom that the ship will fit (Globals.SCREEN_HEIGHT - self.height)

Notice that we are using Global.SCREEN_HEIGHT but the squiggly line underneath it tells us the VS Code can’t find it. That’s because we haven’t imported the Globals module.

So at the top of Ship.py and change the highlighted code below:

1from GameFrame import RoomObject, Globals
2import pygame

step

Now we have to call the keep_in_room() method from the step method.

Go to the bottom of the Ship class and add the code below:

41    def step(self):
42        """
43        Determine what happens to the Ship on each click of the game clock
44        """
45        self.keep_in_room()

Testing

Now go to MainController.py and run the program and make sure that our spaceship stays within the window.

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 Kept ship on screen.

  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/Ship.py

 1from GameFrame import RoomObject, Globals
 2import pygame
 3
 4class Ship(RoomObject):
 5    """
 6    A class for the player's avitar (the Ship)
 7    """
 8    
 9    def __init__(self, room, x, y):
10        """
11        Initialise the Ship object
12        """
13        RoomObject.__init__(self, room, x, y)
14        
15        # set image
16        image = self.load_image("Ship.png")
17        self.set_image(image,100,100)
18        
19        # register events
20        self.handle_key_events = True
21        
22    def key_pressed(self, key):
23        """
24        Respond to keypress up and down
25        """
26        
27        if key[pygame.K_w]:
28            self.y_speed = -10
29        elif key[pygame.K_s]:
30            self.y_speed = 10
31            
32    def keep_in_room(self):
33        """
34        Keeps the ship inside the room
35        """
36        if self.y < 0:
37            self.y = 0
38        elif self.y + self.height> Globals.SCREEN_HEIGHT:
39            self.y = Globals.SCREEN_HEIGHT - self.height
40            
41    def step(self):
42        """
43        Determine what happens to the Ship on each click of the game clock
44        """
45        self.keep_in_room()