Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Representing Objects With Game Models

00:00 In the previous lesson, I talked about assets and showed you how to load an image into the game space. In this lesson, I’ll show you how to represent the objects in your game.

00:11 Your Asteroids clone has various things inside of it: a ship, some rocks, and some bullets. Each of these things will be represented by a Python object. This allows you to write common code that stores and handles position and velocity values and methods for using the objects.

00:30 This will include things like drawing them and deciding if they’re bumping into each other. To get started, I’ll create a GameObject class.

00:39 Eventually, this will get extended into specific types of objects, but the generic version will get you going.

00:46 Each object in the game will need to represent something on the screen. You’re going to store: The sprite that will be shown—keep in mind, this is based on an image and therefore a rectangle.

00:57 This will become important in a second. The position of the object. I’m going to choose to use the center of the object to specify the position. You’ll see why in a second.

01:07 And as your objects are going to want to move around, you’ll need to store the velocity of the object. This will be a tuple representing the speed and direction of the object.

01:17 Let’s add a bit of flavor and load in a rock image. Look out, it’s coming right for us! Phew, that was close. But you see the problem, right? All sprites are rectangles.

01:30 How do you know if two things collide? The simple ways to see if their rectangles overlap, but as you can see here, that will mean near misses will get flagged as collisions.

01:41 Instead of this, you can tighten up your collision algorithm by choosing a circle of collision.

01:48 You’ll need a circle inside of your sprite. And to describe that circle, you’ll need its radius. This is why I’ve chosen to specify a position value based on the center of the object. Even though when it’s blit to the screen, you’ll need to convert it to the top-left corner coordinates. By using the center and a radius, you can quickly use circle-based collision detection.

02:11 It isn’t perfectly aligned with the sprite, but both the rocks and the ship are mostly circular, so it’ll be good enough. Okay. You’ve got the idea. Let’s write some code.

02:24 To try and keep things separated, I’m going to write all the game objects in their own file. This is models.py. Here, I’ve written a generic GameObject that will form the basis of all the models moving forward. Inside of the .__init__() method, I start out by saving the position.

02:41 The position is passed in as a point, an (<x>, <y>) tuple, but for convenience’s sake, I’m going to convert that into a Pygame Vector2 object. A vector is just a set of two numbers, but the Pygame implementation provides methods for doing math that will be useful when dealing with moving the object around and determining the distance between objects.

03:05 The second parameter to .__init__() is the sprite that will be drawn. This is being stored on line 7 for future use. With the sprite passed in, you can use the size of the sprite’s rectangle to calculate a collision circle.

03:19 For our purposes, all the assets are squares, so half the width of the sprite gives the radius of the collision circle.

03:28 On the final line of .__init__(), I’m storing the velocity. This is a tuple containing the speed and direction, and like .position, it’ll be stored as a Pygame Vector2. With the object ready to be created, you’re going to need some methods for drawing, moving, and collision detection.

03:46 The drawing method simply does a bit blit of the sprite onto a given surface. But remember that to make collision calculations easier, I’m using the center of the sprite as its position origin.

03:58 That’s not how Pygame works. It wants top-left-based coordinates. This means you have to convert from the local center-based coordinates to top-left-based before doing the blit.

04:11 A bit of vector math on the position, subtracting the radius—half the width—and you’re in the right coordinate space. This is the beauty of using vectors.

04:19 Without them, you’d have to do math on the x-coordinate then separate math on the y-coordinate. With vectors, instead, you can just subtract and you’re ready to go. With coordinate conversion complete, all you have to do to draw the asset is blit it to the surface passed in. In this case, that’ll be the screen’s surface.

04:40 As you’re trying to create moving objects, every object has a velocity, and for each frame drawn, its position must change. The .move() method updates this object’s position by moving it using the speed and direction stored in the velocity vector.

04:57 And finally, this handy little method uses the collision circle of the object to figure out if it overlaps with the collision circle of another object. The .distance_to() method on a vector returns the difference between two points. In this case, the distance is between the center of this object and the center of the object passed in to the method. With the distance known, a little math using the radii of the two objects will determine if they overlap.

05:24 This method returns a Boolean set to True if the other object passed in collides with this GameObject.

05:35 I’m back inside of game.py, and now that I’ve got the newly-minted GameObject, I’m going to add some assets to the game.

05:43 Let’s start by adding a ship. Line 15 loads the spaceship sprite, and line 16 creates a GameObject that represents that ship. The ship starts out at position (400, 300), which is the center of the screen.

06:00 It uses the freshly-loaded .ship sprite and has a velocity of 0 speed and 0 direction. That’s fancy talk for standing still. Standing probably isn’t the right term for a ship in space.

06:13 Hovering? Floating? Maintaining zero velocity with respect to the inertial reference frame specified by the game screen? Either way, “still.”

06:24 Having a ship is fine and all, but what is our space rocks game without some rocks? Lines 18 and 19 load the asteroid sprite and create a second GameObject.

06:34 This one is a space rock at position (50, 300) and uses the 'asteroid' image. The asteroid has a velocity of 1, direction 0.

06:45 This means for every frame of the game, this object will move one pixel to the right. To help test the collision code, line 21, just below the definition of our rock, creates a counter that will be used to track how many collisions have happened. Let me just scroll down here.

07:03 The previously empty ._game_logic() method now has some logic in it. The ship’s .move() method is called and then the rock’s .move() method is called.

07:14 To be able to see some results, our game objects have to be drawn. Lines 40 and 41 call our object’s .draw() methods, passing in the background screen as the surface to be blitted upon. Note that the drawing of these objects has to be after line 39, the drawing of the background. If the order were different, the background image would overwrite the sprites.

07:37 The final bit of code here is just a quick little test. During each frame, a collision detection is done between the ship and the rock. If there’s a collision, the collision counter will be incremented and the new count will be printed to the terminal. Let’s see our progress and fire up Pygame.

07:57 Okay, there’s our ship. Dead center, um, standing still! And on the left is our rock. Through the magic of editing, the rock is standing still as well. I’ve frozen the frame for a second.

08:09 Once I let it go, it will quickly move in to squash the ship. As each frame happens, the velocity of the rock will cause it to advance by one pixel to the right.

08:19 During each of these frames, the collision detection code will also be run. In the case of a collision, the collision counter will update and a message will appear on the terminal.

08:29 Ready to go? Okay, action. There’s our rock. There’s a collision happening, and the rock has passed, so the collision detection alerts stops. In a real game, you’d want something to happen to the ship in the case of a collision.

08:44 But for now, all I’ve done is detect it. Also, in a real game, you’d want to figure out what to do with the rock when it went offscreen. Currently, as I’m yammering along here, the game is still running and the rock is still moving. It just keeps going right.

08:59 The longer I talk, the further away it goes. This is a waste of computing resources. If you’ve got a lot of assets, computing all the stuff not on the screen becomes expensive, so in the future, let’s do something about that. In the meantime, go little rock, go!

09:15 Happy trails! Up until now, things have been moving as fast as your computer will allow. Next up, I’ll show you how to control your frames per second rate.

Avatar image for Rok Kužner

Rok Kužner on Dec. 27, 2021

Hello! I have a problem. I am on video obout >Representing Objects With Game Models< on 8:27. And on my screen I see only space but in terminal I see thet there is Collision PHOTO HERE–>photos.app.goo.gl/Se893zfJ25EysBeS9. This is my game.py:

import pygame
from models import GameObject
from utils import load_sprite

class SpaceRocks:
    def __init__(self):
        pygame.init()
        pygame.display.set_caption('Asteroid game')

        self.screen = pygame.display.set_mode((800, 600))
        self.background = load_sprite('space', False)

        sprite = load_sprite('spaceship')
        self.ship = GameObject((400, 300), sprite, (0, 0))

        sprite = load_sprite('asteroid')
        self.rock = GameObject((50, 300), sprite, (1, 0))

        self.collision_count = 0

    def main_loop(self):
        while True:
            self._handle_input()
            self._game_logic()
            self._draw()

    def _handle_input(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                quit()

    def _game_logic(self):
        self.ship.move()
        self.rock.move()
        pygame.display.flip()

        if self.ship.collides_with(self.rock):
            self.collision_count += 1
            print(f'Collision #{self.collision_count}')

    def _draw(self):
        self.screen.blit(self.background, (0, 0))
        pygame.display.flip()

THANKS!

Avatar image for Christopher Trudeau

Christopher Trudeau RP Team on Dec. 28, 2021

Hi Rok,

Looks like you’re missing some code inside of SpaceRocks.draw(). You have the line that blits the background and the line that pushes that to the display, but no calls for drawing your ships or rocks.

You’ll want to add two more lines, after the background blit but before the pygame.display.flip():

    def _draw(self):
        self.screen.blit(self.background, (0, 0))
        self.ship.draw(self.screen)
        self.rock.draw(self.screen)
        pygame.display.flip()

BTW, sample code is available in a zip file in the supporting materials drop-down menu near the top of this section. You can always take a look there if you get stuck.

Happy coding!

Avatar image for Rok Kužner

Rok Kužner on Dec. 30, 2021

Thanks!

Become a Member to join the conversation.