create account

Game Making with Python and Pygame Part 7 by amos1969

View this thread on: hive.blogpeakd.comecency.com
· @amos1969 ·
$0.22
Game Making with Python and Pygame Part 7
## Adding Alien Sprites and Collision Detection

![Game Making with Python and Pygame Part7](https://cdn.steemitimages.com/DQmYu5DYKgaXx3xztbM8o1jkFZDUUtLUSR8F5DmAGbb9DHG/gmwithpp-p7.png "Game Making with Python and Pygame Part7")

See the previous parts linked at the bottom of this post if you haven't already.

---

In this part we're going to add some alien sprites which will move randomly and detect when they're in collision with the player. For now, if they collide we'll make it so that the player sprite resets to a starting point. In later parts we'll add in some lives as well as start and end screens to make it a complete game.

I've gone to the [OpenGameArt](https://opengameart.org) website and downloaded an Alien Sprite Sheet that looks like this:

![Alien Sprite Sheet](https://cdn.steemitimages.com/DQmSQUa31Nq6uv6knUbBAbVoGTD5iAAGfBf3JShsFbFEUoN/alienSpriteSheet.png "Alien Sprite Sheet")

At a later point we'll investigate using sprite sheets for our image assets as this is something that we should do, but which I have no experience of. Instead I've cropped the first image from the sprite sheet to make an alien to use, that looks like this: ![Alien](https://cdn.steemitimages.com/DQmSBaSBZmRU1om1bMLcBHD3HpLnuzMshcAwuJutPJ8zGP9/alien.png "Alien")

Here's the code from last time, with just the **Player** and the **MainWindow** classes:

```python
import sys
import pygame

WIDTH = 800
HEIGHT = 600

class Player:
    def __init__(self, x=0, y=0):
        self.image = pygame.image.load("player.png")
        self.image_rect = self.image.get_rect()
        self.image_rect.topleft = (x, y)

    def move_right(self):
        self.image_rect = self.image_rect.move((1, 0))
        if self.image_rect.right > WIDTH:
            self.image_rect.right = WIDTH

    def move_left(self):
        self.image_rect = self.image_rect.move((-1, 0))
        if self.image_rect.left < 0:
            self.image_rect.left = 0

    def move_up(self):
        self.image_rect = self.image_rect.move((0, -1))
        if self.image_rect.top < 0:
            self.image_rect.top = 0

    def move_down(self):
        self.image_rect = self.image_rect.move((0, 1))
        if self.image_rect.bottom > HEIGHT:
            self.image_rect.bottom = HEIGHT

    def draw(self, a_surface):
        a_surface.blit(self.image, self.image_rect)

class MainWindow:
    def __init__(self):
        pygame.init()
        pygame.key.set_repeat(3, 3)
        self.player = Player(50, 50)

        self.DISPLAYSURF = pygame.display.set_mode((WIDTH, HEIGHT))
        self.purple = (150, 0, 220)
        self.DISPLAYSURF.fill(self.purple)
        pygame.display.set_caption('Dodge the Aliens - Game Making Part 7')

        self.clock = pygame.time.Clock()
        self.time_counter = 0

    def main_game_loop(self):
        while True:
            self.time_counter += self.clock.tick()
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    sys.exit()
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_RIGHT:
                        self.player.move_right()
                    if event.key == pygame.K_LEFT:
                        self.player.move_left()
                    if event.key == pygame.K_UP:
                        self.player.move_up()
                    if event.key == pygame.K_DOWN:
                        self.player.move_down()
            if self.time_counter > 15:
                self.DISPLAYSURF.fill(self.purple)
                self.player.draw(self.DISPLAYSURF)
                self.time_counter = 0
            pygame.display.update()

game = MainWindow()
game.main_game_loop()
```

We're going to add in an Alien class that will be similar to the ball class we had before, but called **Alien**. In fact we'll probably just copy that one in and modify it slightly, to begin with. Add in an `import random` line at the top of the code, then add in the following class definition for the alien:

```python
class Alien:
    def __init__(self, x=0, y=0):
        if x == 0 and y ==0:
            x = random.randint(0, WIDTH - 100)
            y = random.randint(0, HEIGHT - 100)
        self.image = pygame.image.load("alien.png")
        self.image_rect = self.image.get_rect()
        self.image_rect.topleft = (x, y)
        self.velocity = [random.randint(-7, 7), random.randint(-7, 7)]

    def update(self):
        self.image_rect = self.image_rect.move(self.velocity)

        if self.image_rect.left < 0 or self.image_rect.right > WIDTH:
            self.velocity[0] = -self.velocity[0]
        if self.image_rect.top < 0 or self.image_rect.bottom > HEIGHT:
            self.velocity[1] = -self.velocity[1]

    def draw(self, a_surface):
        a_surface.blit(self.image, self.image_rect)
```

Which is literally the ball class but loading a different image. Modify the **MainWindow** class to create an instance of the alien class, in the constructor and to call the update and draw methods in the main game loop, as follows:

```python
class MainWindow:
    def __init__(self):
        pygame.init()
        pygame.key.set_repeat(3, 3)
        self.player = Player(50, 50)

        self.alien = Alien()

        self.DISPLAYSURF = pygame.display.set_mode((WIDTH, HEIGHT))
        self.purple = (150, 0, 220)
        self.DISPLAYSURF.fill(self.purple)
        pygame.display.set_caption('Dodge the Aliens - Game Making Part 7')

        self.clock = pygame.time.Clock()
        self.time_counter = 0

    def main_game_loop(self):
        while True:
            self.time_counter += self.clock.tick()
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    sys.exit()
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_RIGHT:
                        self.player.move_right()
                    if event.key == pygame.K_LEFT:
                        self.player.move_left()
                    if event.key == pygame.K_UP:
                        self.player.move_up()
                    if event.key == pygame.K_DOWN:
                        self.player.move_down()
            if self.time_counter > 15:
                self.DISPLAYSURF.fill(self.purple)
                self.player.draw(self.DISPLAYSURF)
                self.alien.update()
                self.alien.draw(self.DISPLAYSURF)
                self.time_counter = 0
            pygame.display.update()
```

I tried adding the update call outside of the check on the counter but it updated so quickly the alien was just a blur. I think there is a way of adding the timer check to the game loop directly, but I'll need to research it and add it into a later post. Run the code, you should see one alien bounce around randomly, and still be able to move the player.

We're going to refactor the Alien and Player class to both inherit from a common base class (probably called GameObject), which will have the same constructor and draw methods. Each will then have its' own methods to deal with movement (although these won't be called the same) and the constructor for each will pass the correct image to the superclass when the superclass constructors are called. One difference is that the **Alien** constructor also creates the **velocity** attribute whereas the **Player** doesn't have this.

```python
class GameObject:
    def __init__(self, image, x=0, y=0):
        if x == 0 and y ==0:
            x = random.randint(0, WIDTH - 100)
            y = random.randint(0, HEIGHT - 100)
        self.image = pygame.image.load(image)
        self.image_rect = self.image.get_rect()
        self.image_rect.topleft = (x, y)

    def draw(self, a_surface):
        a_surface.blit(self.image, self.image_rect)

class Alien(GameObject):
    def __init__(self, x=0, y=0):
        super().__init__("alien.png", x, y)
        self.velocity = [random.randint(-7, 7), random.randint(-7, 7)]

    def update(self):
        self.image_rect = self.image_rect.move(self.velocity)

        if self.image_rect.left < 0 or self.image_rect.right > WIDTH:
            self.velocity[0] = -self.velocity[0]
        if self.image_rect.top < 0 or self.image_rect.bottom > HEIGHT:
            self.velocity[1] = -self.velocity[1]

class Player(GameObject):
    def __init__(self, x=0, y=0):
        super().__init__("player.png", x, y)

    def move_right(self):
        self.image_rect = self.image_rect.move((1, 0))
        if self.image_rect.right > WIDTH:
            self.image_rect.right = WIDTH

    def move_left(self):
        self.image_rect = self.image_rect.move((-1, 0))
        if self.image_rect.left < 0:
            self.image_rect.left = 0

    def move_up(self):
        self.image_rect = self.image_rect.move((0, -1))
        if self.image_rect.top < 0:
            self.image_rect.top = 0

    def move_down(self):
        self.image_rect = self.image_rect.move((0, 1))
        if self.image_rect.bottom > HEIGHT:
            self.image_rect.bottom = HEIGHT
```

We're going to want to test whether our **Player** sprite and an **Alien** sprite are colliding (in other words whether their **rect**s are overlapping). **Pygame rect**s have a built-in method called `colliderect()` which checks if the rect that it belongs to, overlaps with a rect we pass into it. There are also methods to check if the current rect is colliding with 1 or all of the **rect**s in a list, which is what we're probably going to want to use as it should be optimised for checking multiple things efficiently. To begin with we'll use the **colliderect** method and then change to the list versions afterwards.

For flexibility, we're going to implement the collision detection in the superclass (**GameObject**) and allow the sub-classes (currently **Player** and **Alien**) to use it. We're also going to add a `reset()` method to the **Player** class that at the minute will reset the coordinates of the player object to (50, 50) and a later point will allow us to add other functionality too (deducting a life etc).

First off the collision detection in the **GameObject** class:

```python
class GameObject:
    def __init__(self, image, x=0, y=0):
        if x == 0 and y ==0:
            x = random.randint(0, WIDTH - 100)
            y = random.randint(0, HEIGHT - 100)
        self.image = pygame.image.load(image)
        self.image_rect = self.image.get_rect()
        self.image_rect.topleft = (x, y)

    def colliding(self, thing):
        if self.image_rect.colliderect(thing.image_rect):
            return True
        return False

    def draw(self, a_surface):
        a_surface.blit(self.image, self.image_rect)
```

The method checks if the object is colliding with another object and returns `True` if it is and `False` otherwise, we can then use this in the main game loop.

Next, we have the slightly updated **Player** class which now has a `reset()` method.

```python
class Player(GameObject):
    def __init__(self, x=0, y=0):
        super().__init__("player.png", x, y)

    def reset(self):
        self.image_rect.topleft = (50, 50)

    def move_right(self):
        self.image_rect = self.image_rect.move((1, 0))
        if self.image_rect.right > WIDTH:
            self.image_rect.right = WIDTH

    def move_left(self):
        self.image_rect = self.image_rect.move((-1, 0))
        if self.image_rect.left < 0:
            self.image_rect.left = 0

    def move_up(self):
        self.image_rect = self.image_rect.move((0, -1))
        if self.image_rect.top < 0:
            self.image_rect.top = 0

    def move_down(self):
        self.image_rect = self.image_rect.move((0, 1))
        if self.image_rect.bottom > HEIGHT:
            self.image_rect.bottom = HEIGHT
```

Finally, we have the updated **MainWindow** class which checks if the player object is colliding and resets it if it is.

```python
class MainWindow:
    def __init__(self):
        pygame.init()
        pygame.key.set_repeat(3, 3)
        self.player = Player(50, 50)

        self.alien = Alien()

        self.DISPLAYSURF = pygame.display.set_mode((WIDTH, HEIGHT))
        self.purple = (150, 0, 220)
        self.DISPLAYSURF.fill(self.purple)
        pygame.display.set_caption('Dodge the Aliens - Game Making Part 7')

        self.clock = pygame.time.Clock()
        self.time_counter = 0

    def main_game_loop(self):
        while True:
            self.time_counter += self.clock.tick()
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    sys.exit()
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_RIGHT:
                        self.player.move_right()
                    if event.key == pygame.K_LEFT:
                        self.player.move_left()
                    if event.key == pygame.K_UP:
                        self.player.move_up()
                    if event.key == pygame.K_DOWN:
                        self.player.move_down()
            if self.time_counter > 15:
                self.DISPLAYSURF.fill(self.purple)
                self.alien.update()
                self.alien.draw(self.DISPLAYSURF)
                if self.player.colliding(self.alien):
                    self.player.reset()
                self.player.draw(self.DISPLAYSURF)
                self.time_counter = 0
            pygame.display.update()
```

Running this now should cause the player to reset when it collides with the alien. We're going to modify it slightly so that we have a list of aliens, to begin with, it will still do each of the checks individually using the existing collision method. Which only requires changes to the **MainWindow** class for now.

```python
class MainWindow:
    def __init__(self):
        pygame.init()
        pygame.key.set_repeat(3, 3)
        self.player = Player(50, 50)

        self.aliens = []
        for _ in range(20):
            self.aliens.append(Alien())

        self.DISPLAYSURF = pygame.display.set_mode((WIDTH, HEIGHT))
        self.purple = (150, 0, 220)
        self.DISPLAYSURF.fill(self.purple)
        pygame.display.set_caption('Dodge the Aliens - Game Making Part 7')

        self.clock = pygame.time.Clock()
        self.time_counter = 0

    def main_game_loop(self):
        while True:
            self.time_counter += self.clock.tick()
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    sys.exit()
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_RIGHT:
                        self.player.move_right()
                    if event.key == pygame.K_LEFT:
                        self.player.move_left()
                    if event.key == pygame.K_UP:
                        self.player.move_up()
                    if event.key == pygame.K_DOWN:
                        self.player.move_down()
            if self.time_counter > 15:
                self.DISPLAYSURF.fill(self.purple)
                for alien in self.aliens:
                    alien.update()
                    alien.draw(self.DISPLAYSURF)
                    if self.player.colliding(alien):
                        self.player.reset()
                self.player.draw(self.DISPLAYSURF)
                self.time_counter = 0
            pygame.display.update()
```

Running this gives you quite a tricky game to play. You could make it easier by reducing the range of speed values that the `random.randint()` function call uses in the constructor for the **Alien**s, but playability isn't uppermost in our minds at the minute (although if you were making a decent game you would probably playtest it with a variety of speeds to see which ones worked the best).

The alternative method of doing the collision detection is to change the `colliding()` method in the **GameObject** class to take a list of objects and return true if one at least of them is colliding with the player. In all probability, this method will have been optimised in some way to make it far more efficient than our loop through the objects in the alien list (it may not have, but it is an obvious place to make optimisations). We first need to change the **GameObject** class and make the `colliding()` method take a list rather than a single object.

At least that's what I was thinking until I came to implement it, when I realised that because my list was a list of my own objects rather than a list of **Pygame rects** it wasn't going to work the way I want it to, and essentially I'd just be moving my loop into the `colliding()` method rather than in the main game loop, and I would need to build a new list of the **rect**s to pass to it. I then came up with a very **hacky** way of solving this. my **GamObject**s have the **rect** of their image exposed as an attribute called `image_rect`, I decided to see what would happen if I changed the name of this attribute to just `rect` and also changed every occurrence of it in the code. I did it, and it works the way I was thinking my code would work, removing the loop (or at least hiding it away somewhere in the Pygame plumbing).

This is my **hacky** solution, which appears to work, but probably will break at some unspecified point in the future as I haven't implemented things correctly. Here's the full version of the program in all of its ignominy.

```python
import sys
import pygame
import random

WIDTH = 800
HEIGHT = 600

class GameObject:
    def __init__(self, image, x=0, y=0):
        if x == 0 and y ==0:
            x = random.randint(0, WIDTH - 100)
            y = random.randint(0, HEIGHT - 100)
        self.image = pygame.image.load(image)
        self.rect = self.image.get_rect()
        self.rect.topleft = (x, y)

    def colliding(self, things):
        if self.rect.collidelist(things) != -1:
            return True
        return False

    def draw(self, a_surface):
        a_surface.blit(self.image, self.rect)

class Alien(GameObject):
    def __init__(self, x=0, y=0):
        super().__init__("alien.png", x, y)
        self.velocity = [random.randint(-7, 7), random.randint(-7, 7)]

    def update(self):
        self.rect = self.rect.move(self.velocity)

        if self.rect.left < 0 or self.rect.right > WIDTH:
            self.velocity[0] = -self.velocity[0]
        if self.rect.top < 0 or self.rect.bottom > HEIGHT:
            self.velocity[1] = -self.velocity[1]

class Player(GameObject):
    def __init__(self, x=0, y=0):
        super().__init__("player.png", x, y)

    def reset(self):
        self.rect.topleft = (50, 50)

    def move_right(self):
        self.rect = self.rect.move((1, 0))
        if self.rect.right > WIDTH:
            self.rect.right = WIDTH

    def move_left(self):
        self.rect = self.rect.move((-1, 0))
        if self.rect.left < 0:
            self.rect.left = 0

    def move_up(self):
        self.rect = self.rect.move((0, -1))
        if self.rect.top < 0:
            self.rect.top = 0

    def move_down(self):
        self.rect = self.rect.move((0, 1))
        if self.rect.bottom > HEIGHT:
            self.rect.bottom = HEIGHT

class MainWindow:
    def __init__(self):
        pygame.init()
        pygame.key.set_repeat(3, 3)
        self.player = Player(50, 50)

        self.aliens = []
        for _ in range(20):
            self.aliens.append(Alien())

        self.DISPLAYSURF = pygame.display.set_mode((WIDTH, HEIGHT))
        self.purple = (150, 0, 220)
        self.DISPLAYSURF.fill(self.purple)
        pygame.display.set_caption('Dodge the Aliens - Game Making Part 7')

        self.clock = pygame.time.Clock()
        self.time_counter = 0

    def main_game_loop(self):
        while True:
            self.time_counter += self.clock.tick()
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    sys.exit()
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_RIGHT:
                        self.player.move_right()
                    if event.key == pygame.K_LEFT:
                        self.player.move_left()
                    if event.key == pygame.K_UP:
                        self.player.move_up()
                    if event.key == pygame.K_DOWN:
                        self.player.move_down()
            if self.time_counter > 15:
                self.DISPLAYSURF.fill(self.purple)
                for alien in self.aliens:
                    alien.update()
                    alien.draw(self.DISPLAYSURF)
                if self.player.colliding(self.aliens):
                    self.player.reset()
                self.player.draw(self.DISPLAYSURF)
                self.time_counter = 0
            pygame.display.update()

game = MainWindow()
game.main_game_loop()
```

This is where I'm going to leave things for Part 7. What I'm going to do for the next part is look into the **Pygame Sprite** class and see if what I should be doing is creating my **GameObject** class as a subclass of that one. As it may also provide us with some useful tools later on. So to summarise Part 8 will be a look at the Sprite class and refactoring our code to take advantage of it. A quick read of the documentation suggests that the Sprite Group may also be the thing to use rather than just a basic list of sprites.

---

Like and follow if you're enjoying these. If you have any comments or suggestions for improvements or things you'd like it to do, let me know in the comments.

---

Previous parts:

Part 1 - How to get set up with an up-to-date version of **Pygame**: [Part 1](https://steemit.com/python/@amos1969/game-making-with-python-and-pygame-part-1)

Part 2 - How to make a window appear using basic code: [Part 2](https://steemit.com/python/@amos1969/game-making-with-python-and-pygame-part-2)

Part 3 - Refactoring the code into a class and adding a background image: [Part 3](https://steemit.com/python/@amos1969/game-making-with-python-and-pygame-part-3)

Part 4 - Added a moving sprite to the window: [Part 4](https://steemit.com/python/@amos1969/game-making-with-python-and-pygame-part-4)

Part 5 - Refactoring the ball sprite into a class of its own: [Part 5](https://steemit.com/python/@amos1969/game-making-with-python-and-pygame-part-5)

Part 6 - Adding player controls to our Sprite: [Part 6](https://steemit.com/python/@amos1969/game-making-with-python-and-pygame-part-6)
👍  , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,
properties (23)
authoramos1969
permlinkgame-making-with-python-and-pygame-part-7
categorypython
json_metadata{"tags":["python","pygame","game-making","programming","coding"],"image":["https://cdn.steemitimages.com/DQmYu5DYKgaXx3xztbM8o1jkFZDUUtLUSR8F5DmAGbb9DHG/gmwithpp-p7.png","https://cdn.steemitimages.com/DQmSQUa31Nq6uv6knUbBAbVoGTD5iAAGfBf3JShsFbFEUoN/alienSpriteSheet.png","https://cdn.steemitimages.com/DQmSBaSBZmRU1om1bMLcBHD3HpLnuzMshcAwuJutPJ8zGP9/alien.png"],"links":["https://opengameart.org","https://steemit.com/python/@amos1969/game-making-with-python-and-pygame-part-1","https://steemit.com/python/@amos1969/game-making-with-python-and-pygame-part-2","https://steemit.com/python/@amos1969/game-making-with-python-and-pygame-part-3","https://steemit.com/python/@amos1969/game-making-with-python-and-pygame-part-4","https://steemit.com/python/@amos1969/game-making-with-python-and-pygame-part-5","https://steemit.com/python/@amos1969/game-making-with-python-and-pygame-part-6"],"app":"steemit/0.1","format":"markdown"}
created2019-04-22 20:53:42
last_update2019-04-22 20:53:42
depth0
children2
last_payout2019-04-29 20:53:42
cashout_time1969-12-31 23:59:59
total_payout_value0.174 HBD
curator_payout_value0.041 HBD
pending_payout_value0.000 HBD
promoted0.000 HBD
body_length22,660
author_reputation4,275,692,944,622
root_title"Game Making with Python and Pygame Part 7"
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id83,575,142
net_rshares460,039,876,295
author_curate_reward""
vote details (56)
@amos1969 ·
Part 8 can be found here: https://steemit.com/python/@amos1969/game-making-with-python-and-pygame-part-8
properties (22)
authoramos1969
permlinkre-amos1969-game-making-with-python-and-pygame-part-7-20190424t213501249z
categorypython
json_metadata{"tags":["python"],"links":["https://steemit.com/python/@amos1969/game-making-with-python-and-pygame-part-8"],"app":"steemit/0.1"}
created2019-04-24 21:35:00
last_update2019-04-24 21:35:00
depth1
children0
last_payout2019-05-01 21:35:00
cashout_time1969-12-31 23:59:59
total_payout_value0.000 HBD
curator_payout_value0.000 HBD
pending_payout_value0.000 HBD
promoted0.000 HBD
body_length104
author_reputation4,275,692,944,622
root_title"Game Making with Python and Pygame Part 7"
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id83,702,155
net_rshares0
@steemitboard ·
Congratulations @amos1969! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :

<table><tr><td>https://steemitimages.com/60x60/http://steemitboard.com/notifications/postallweek.png</td><td>You published a post every day of the week</td></tr>
</table>

<sub>_You can view [your badges on your Steem Board](https://steemitboard.com/@amos1969) and compare to others on the [Steem Ranking](http://steemitboard.com/ranking/index.php?name=amos1969)_</sub>
<sub>_If you no longer want to receive notifications, reply to this comment with the word_ `STOP`</sub>


To support your work, I also upvoted your post!


###### [Vote for @Steemitboard as a witness](https://v2.steemconnect.com/sign/account-witness-vote?witness=steemitboard&approve=1) to get one more award and increased upvotes!
👍  
properties (23)
authorsteemitboard
permlinksteemitboard-notify-amos1969-20190422t212838000z
categorypython
json_metadata{"image":["https://steemitboard.com/img/notify.png"]}
created2019-04-22 21:28:39
last_update2019-04-22 21:28:39
depth1
children0
last_payout2019-04-29 21:28:39
cashout_time1969-12-31 23:59:59
total_payout_value0.000 HBD
curator_payout_value0.000 HBD
pending_payout_value0.000 HBD
promoted0.000 HBD
body_length841
author_reputation38,975,615,169,260
root_title"Game Making with Python and Pygame Part 7"
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id83,576,537
net_rshares733,076,231
author_curate_reward""
vote details (1)