Collision in Python and Cockroach

Have a Database Problem? Speak with an Expert for Free
Get Started >>

Introduction

This is part seven of a multi-article set of articles showing how to create a zombie shooter game. In this part we add in how to detect and handle Collision in Python and Cockroach is used for storing and reading Sprite objects and some game settings. We learn how to detect collisions between Sprites using Python’s Arcade library. In the articles after this one, we’ll learn to do music and sounds, keep the player’s score, add a system for changing game difficulty and speed, and read mouse movements.

Prerequisites

  • The Python source code, images, and mp3 files are all here for download.

  • Manual for Python Arcade. We use functions from the Arcade libraries for a big amount of the functionality in this Python Zombie Feeder game.

  • Part one through part six are must-reads, as we learned to set up a game screen with Cockroach for displaying GUI data, creating Sprites from images and Sprite Lists from those Sprites, responding to keys pressed, moving the Player’s Sprite image in the game window, moving enemies with some brains (pun intended), firing bullets – in this case brains – using the space bar, along with bullet movement.

Frameworks needed for the game

Be sure to use PIP to install the following libraries on your computer.

1
2
3
4
5
6
7
8
import arcade # Game-making library for Python.
from datetime import datetime, timedelta # Used to seed random number generation.
import math # For some math and physics-related functions.
import os # For using the file system to load sound and image files.
import sys # For getting resource files into the game.
import psycopg2 # For Cockroach data retrieval for the GUI.
import pyautogui # For finding out the monitor resolution.
import random # For generating random numbers, mostly for determining enemy movement.

A function we’ll be using in this part of the series is the check_for_collision_with_list() function. Let’s look at its syntax before diving deeper into some code that uses the function.

check_for_collision_with_list

This function provides a way to quickly find out if a Sprite has collided with any Sprite in a Sprite List and even gives us a new list of Sprites that collided!

1
sprites_hit_list = arcade.check_for_collision_with_list(one_sprite, many_sprites_list)

Now that we understand the basics of how that collision detection function works, let’s look over the code for creating some Sprites and Sprite Lists that we will be using at the end of this article.

Sprite Lists

Initialize Sprite Lists

1
2
3
4
5
6
7
# Set up all the Sprite lists
# we'll be using in the game.
self.list_all_sprites = arcade.SpriteList()
self.list_enemies = arcade.SpriteList()
self.list_bullets = arcade.SpriteList()
self.list_player_lives = arcade.SpriteList()
self.list_blocks = arcade.SpriteList()

Create Sprite for player

1
2
3
4
5
6
7
8
9
# Initialize a Sprite for the Player
# that looks like a monkey head and
# get an image for it from a PNG file.
self.player_sprite = PlayerSprite("./resources/images/monkey.png", SCALE)
# Since there is only one Player Sprite,
# we don't need a list for "player Sprites" but
# we still add this Sprite to our list of all
# Sprites below.
self.list_all_sprites.append(self.player_sprite)

One of the objects that we want to be sure we detect collisions of is the bullet, which in this game is represented by a brain PNG.

Sprite for bullet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Create Sprite for the flying brain and
# assign it an image and scale (size).
brain_sprite = bulletSprite("./resources/images/brain.png", SCALE)
# Give the bullet Sprite a unique identifier.
brain_sprite.guid = "Brain"
# Set the brain's flying speed.
brain_speed = 12
# Set the angle the bullet will fly in, based
# on the angle the Player Sprite is facing at
# the time of firing.
brain_sprite.change_y = \
    math.cos(math.radians(self.player_sprite.angle)) * brain_speed
brain_sprite.change_x = \
    -math.sin(math.radians(self.player_sprite.angle)) \
    * brain_speed
# Set the beginning coordinates of the new
# bullet Sprite to match the Player's coordinates.
brain_sprite.center_x = self.player_sprite.center_x
brain_sprite.center_y = self.player_sprite.center_y
# Update the bullet Sprite with all the above settings.
brain_sprite.update()
# Add the bullet Sprite to our list of all Sprites,
# which is a Sprite list.
self.list_all_sprites.append(brain_sprite)
# Add the brain/bullet Sprite
# to our list of bullets
self.list_brains.append(brain_sprite)

Create Sprites for obstacles

We will now look at some Python script copied from the full project source code that uses the SCREEN_FROM_DATABASE constant to determine if we are getting our first screen’s data from the database or if we want to use the randint() function to place obstacles (lego blocks) on the GUI.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
if SCREEN_FROM_DATABASE == False:
    # Place lego blocks on the screen using random.
    for i in range(BLOCKS_NUMBER):
        block = arcade.Sprite("./resources/images/block.png", SCALE)
        block.center_x = random.randint(10, SCREEN_WIDTH)
        block.center_y = random.randint(10, SCREEN_HEIGHT)
        self.list_all_sprites.append(block)
        self.blocks_sprite_list.append(block)
else:
    # Place lego blocks on the
    # screen from Cockroach.
    # -------------------------
    # Database connection setup
    # -------------------------
    import psycopg2
    t_dbname = "dbase name"
    t_name_user = "dbase user"
    t_sslmode = "auto"
    t_sslrootcert = 'certis/ca.crt'
    t_sslkey = 'certis/client.maxroach.key'
    t_sslcert = 'certis/client.maxroach.crt'
    t_host = "localhost"
    t_port = "26231"
    crdb_conn = psycopg2.connect(database=t_dbname, user=t_name_user, sslmode=t_sslmode, sslrootcert=t_sslrootcert, sslkey=t_sslkey, sslcert=t_sslcert, host=t_host, port=t_port)
    crdb_cursor = crdb_conn.cursor()

    # Query to retrieve lego blocks configuration
    # for the screen from Cockroach.
    s = ""
    s += "SELECT"
    s += " i_y"
    s += ", i_x"
    s += " FROM tbl_gui_objects"
    s += " WHERE ("
    s += " id_gui = " + ID_SCREEN
    s += " AND t_obj_type = 'lego block'"
    s += ")"
    try:
        crdb_cursor.execute(s)
        # Return a database cursor.
    except psycopg2.Error as e:
        t_msg = "SQL error: " + e + "/n SQL: " + s
        return render_template("error.html", t_msg = t_msg)
    # Iterate through the database rows returned,
    # set a block on the GUI for each row according
    # to the x and y coordinates provided from that row.
    for each row in crdb_cursor:
        i_y = row[0]
        i_x = row[1]
        # Create a Sprite for each lego
        # block and assign an image to it.
        block = arcade.Sprite("./resources/images/block.png", SCALE)
        # Set the block's position on the screen.
        block.center_x = i_x
        block.center_y = i_y
        # Add this block Sprite to our list of all Sprites.
        self.list_all_sprites.append(block)
        # Add this block to our Sprite list of just blocks.
        self.blocks_sprite_list.append(block)

Next, we’ll create the Sprites for the zombies:

Create Sprites for enemies

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
image_list = ("./resources/images/char-z-fem-01-blue-front.png",
                "./resources/images/char-z-fem-01-purple-front.png",
                "./resources/images/char-z-fem-01-yellow-front.png",
                "./resources/images/char-z-guy-01-green-front.png",
                "./resources/images/char-z-guy-01-orange-front.png",
                "./resources/images/char-z-guy-02-front.png",
                "./resources/images/char-z-guy-03-front.png",
                "./resources/images/char-z-guy-04-front.png")
# If no enraged sound for this character,
# place "z" in front of name.
# We used "char" to mean character
# and "z" to represent zombie.
name_list = ("char-z-fem-01-blue",
                "char-z-fem-01-purple",
                "char-z-fem-01-yellow",
                "char-z-guy-01-green",
                "char-z-guy-01-orange",
                "char-z-guy-02",
                "char-z-guy-03",
                "char-z-guy-04")
for i in range(ENEMY_COUNT_INITIAL):
    image_no = i
    enemy_sprite = EnemySprite(image_list[image_no], SCALE)
    enemy_sprite.guid = name_list[image_no]
    enemy_sprite.speed = 2 + (DIFFICULTY/12)
    enemy_sprite.immunity = False
    enemy_sprite.enraged = False
    enemy_sprite.frustration = 0
    enemy_sprite.which = "self.sound_char_" + str(i).zfill(2) + "_enraged"
    enemy_sprite.scale = SCALE
    enemy_sprite.timer_rand = 0
    enemy_sprite.timer_smart = 0
    enemy_sprite.url_image = image_list[image_no]
    enemy_sprite.center_y = 800
    enemy_sprite.center_x = int((SCREEN_WIDTH/8 * (i+1))-(SCREEN_WIDTH/13.9))
    # If you want enemies to start out
    # moving in random directions, uncomment
    # the following two lines of code:
    # enemy_sprite.change_x = int(random.random() * 2 + (DIFFICULTY/10) - 1)
    # enemy_sprite.change_y = int(random.random() * 2 + (DIFFICULTY/10) - 1)
    # The following two lines of code
    # start the enemy off as not moving.
    enemy_sprite.change_x = 0
    enemy_sprite.change_y = 0
    self.list_all_sprites.append(enemy_sprite)
    self.list_enemies.append(enemy_sprite)

Now we’ll write some more Python code to look at how to detect and deal with collisions for all of the Sprites and Sprite Lists we created above.

Sprite collision detection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# Cycle through all brains currently on GUI.
for brain in self.list_brains:
    # Does this brain coincide in 2D space with any of the enemies?
    # If so, create a new Sprite list called "enemies".
    enemies = arcade.check_for_collision_with_list(brain, self.list_enemies)
    # Cycle through all enemies hit by this brain.
    # If there are none, the following few lines are skipped.
    for enemy in enemies:
        # A sound will go here.
        # Increment score for hitting an
        # enemy based on difficulty level.
        self.score += int(100 * self.player_sprite.difficulty)
        if REMOVE_ENEMY_WHEN_HIT:
            enemy.remove_from_sprite_lists()
        brain.remove_from_sprite_lists()

for enemy in self.list_enemies:
    # See if collision between this zombie
    # and all lego blocks in the GUI.
    if enemy.immunity == False:
        any_collisions = arcade.check_for_collision_with_list(enemy, self.list_blocks)
        if len(any_collisions) > 0:
            # THIS enemy collided with an obstruction.
            if enemy.enraged == True:
                enemy.frustration += 1
                if enemy.frustration > 160 - (self.player_sprite.difficulty * 15):
                    enemy.immunity = True
            # Reverse direction with added random factor.
            # The added random factor helps the enemy
            # get around the block they hit.
            random.seed(datetime.now() + timedelta(0,enemy_number))
            x_rand = random.randint(1, 50)
            if x_rand < 25:
                dir_x *= -2
            else:
                dir_y *= -2
            # Change enemy direction based on above and enemy speed.
            enemy.change_x = dir_x * (enemy.speed)
            enemy.change_y = dir_y * (enemy.speed)
            # Change enemy position based on the above direction change.
            enemy.center_x += enemy.change_x
            enemy.center_y += enemy.change_y
            enemy.timer_rand = 0
            # Set smart timer to ~4 seconds of this direction so the
            # enemy for sure gets away from the ice block they hit.
            enemy.timer_smart = int(fps * 4)
    enemy.update()

# Check for collision between
# player and all blocks.
if PLAYER_IMMUNE_TO_BLOCKS == False:
    # Check for collision between player and all blocks.
    any_collisions = arcade.check_for_collision_with_list(self.player_sprite, self.list_blocks)
    if len(any_collisions) > 0:
        # Player hit a block, so change facing
        # angle to opposite of what it was.
        self.player_sprite.angle *= -1
        self.player_sprite.change_x = -math.sin(math.radians(self.player_sprite.angle)) * self.player_sprite.speed
        self.player_sprite.change_y = math.cos(math.radians(self.player_sprite.angle)) * self.player_sprite.speed
        self.player_sprite.center_x += self.player_sprite.change_x
        self.player_sprite.center_y += self.player_sprite.change_y

# Check for collision between player and all enemies
enemies = arcade.check_for_collision_with_list(self.player_sprite, self.list_enemies)
#   If list of enemies hit created above has at least one enemy:
if len(enemies) > 0:
    arcade.play_sound(self.sound_gasp_of_death)
    arcade.play_sound(self.sound_munch)
    arcade.play_sound(self.sound_burp)
    self.eaten = True
    # Remove a life from count
    if self.lives > 0:
        self.lives -= 1
        self.player_sprite.respawn()
        # If you want the collision to destroy the enemy, add this code:
        #   enemies[0].remove_from_sprite_lists()
        self.list_player_lives.pop().remove_from_sprite_lists()
    else:
        self.game_over = True
else:
    self.eaten = False

Conclusion

In this part seven of the multi-article set of tutorials showing how to build a video game of a Monkey “feeding” brains to zombies, we added how to use Python to detect Collisions between Sprites and Cockroach for storing and reading Sprite objects, in this case, lego blocks. In this part, we learned all how to detect a collision between Sprites. In the articles following this one, we’ll learn to play game sounds, keep score, manage game difficulty, and read mouse input.

Pilot the ObjectRocket Platform Free!

Try Fully-Managed CockroachDB, Elasticsearch, MongoDB, PostgreSQL (Beta) or Redis.

Get Started

Keep in the know!

Subscribe to our emails and we’ll let you know what’s going on at ObjectRocket. We hate spam and make it easy to unsubscribe.