Ship and Asteroid Collision

Hitboxes

Collisions refer to the interactions between different game objects in a game when they overlap or come into contact with each other. GameFrame uses Rectangular Collisions which involve bounding rectangles that surround objects. This rectangles are often referred to as hitboxes.

We have already been using this this concept of hitboxes. For example, in our game asteroids look like this.

asteroid

But we have used the image below to represent asteroids.

object boundaries

We have been using hitboxes to represent all our objects. For example, when the top boundary of the Zork hitbox collides with the top of the screen, we reverse Zork’s direction.

Collisions

Collisions occur between two objects when their hitboxes touch or overlap.

  • obj_1’s left boundary is less than obj_2’s right boundary and

  • obj_1’s right boundary is greater than obj_2’s left boundary and

  • obj_1’s top boundary is less than obj_2’s bottom boundary and

  • obj_1’s bottom boundary is greater than obj_2’s top boundary.

If we wanted to use the coordinates from this image:

collision coordinates

Then the code would be:

if (obj1.x < obj2.x + obj2.width and
    obj1.x + obj1.width > obj2.x and
    obj1.y < obj2.y + obj2.height and
    obj1.y + obj1.height > obj2.y):

That’s a whole heap of code to understand and remember. Fortunately, this is all just for background. Since GameFrame is based on Pygame, there are is a built in process for detecting and handling collisions.

Lets check the GameFrame docs to how it handles collisions. If we look at the RoomObject methods we will see to methods that address collisions:

  • register_collision_object(collision_object)

  • handle_collision(self, other, other_type)

Collisions Terminology

In game design there are two objects involved.

  • self → the object that contains the event handler

  • other → the object that self has collided with

Register Collision Object

Each room can be filled with many objects, which means an object can collide with many other objects. We may not be interested in all of these collisions, so need to register which collisions we want to worry. We use register_collision_object(collision_object) to do this.

This method is added on an object’s __init__() method and tells GameFrame to detect any collisions between the object (self) and the collision object.

Handle Collisions

Every tick GameFrame will check if any of the registered collisions have occurred. If a registered collision has occurred it will call the handle_collision method. GameFrame will also pass the other object (other) and the class of the object (other_type). These have distinct uses.

  • other → gives you access to the other objects attributes and methods, for example, you could change the other’s x_speed using other.x_speed = 0

  • other_type → allows for the differentiation of handling collisions with different types of objects. For example, handling collisions with the player differently to collisions with a bullet.

With that theory under our belt, let’s plan how to handle collisions between asteroids and the space ship

Planning

First, we need to decide which class we will get to handle this collision between Ship and Asteroid. At this stage, it could be handled by either class, but to make life easier later on, we will get Asteroid to handle the collision.

Let’s use the simplest outcome for the collision → ends the level. Since we only have one level, this will also end the game. Thinking back to WelcomeScreen we end a level by using self.room.running = False.

Putting this into a IPO table:

Asteroid Collision IPO

Lets add this to the program

Coding

Objects\Asteroid.py

Open Objects\Asteroid.py and add the highlighted code to the __init__() method.

 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")

Unpacking that code:

  • line 25: tell GameFrame to detect and collisions between this object and and object of the Ship class.

Move to the bottom of Asteroid.py add the following code:

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.running = False

Breaking that code down:

  • line 53: when GameFrame detects a registered collision it will call this method

  • line 58: checks if the collision is with a Ship object

  • line 59: ends the level, and therefore the game.

Save Asteroid.py and run MainController.py to test your code.

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 Ship asteroid collision.

  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/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.running = False