Python Arcade Collision Detection and Postgres
Introduction
This is part 7 of a multi-part set of articles showing how to create a from-the-top view video game. In this part 7 we add how to use Python Arcade for Collision Detection and Postgres for storing and reading Sprite objects. In this part, we learn all about how to detect collisions between objects; in this case Sprites, using Python’s Arcade library. In the parts (articles) following this one, we’ll learn to create game sounds, keep score, and add a system for tracking and changing game difficulty.
Prerequisites
The full Python source code, image files, and mp3 files are all here for download.
Online manual for Python Arcade. We use the Arcade framework for quite a bit of the functionality in this Python 8-bit looking game.
Be sure to study part 1 through part 6 where we learned to build and set up a game screen with PostgreSQL for storing that data, how to create Sprites from images and Sprite Lists from Sprites, how to respond to key presses, how to move the Player’s Sprite icon on the screen, how to move enemies with some intelligence, and how the player can fire bullets (snowballs) with the space bar and bullet movement.
Python frameworks for the game
Be sure to use PIP to install the following libraries.
1 2 3 4 5 6 7 8 | import arcade # Game-oriented library for Python. from datetime import datetime, timedelta # For seeding random number generation. import math # For some math / movement physics-related functions. import os # For accessing the file system to load images and sounds. import psycopg2 # For Postgres data retrieval for screens. import pyautogui # For getting monitor resolution to make sure our window is not too large. import random # For generating random numbers for enemy movement. import sys # For getting resource files into the game. |
An important function we’ll be using in this part of the multi-part series is the check_for_collision_with_list() function. Let’s look at it before going deeper into code that uses it.
Syntax of check_for_collision_with_list
This function allows us to easily find out if a Sprite has collided with any Sprite in a Sprite List.
1 | list_of_sprites_hit = arcade.check_for_collision_with_list(individual_sprite, list_of_sprites) |
Now that we can see the basics of how the function works, let’s go over the code for creating the Sprites and Sprite Lists we’ll be addressing at the end of this part.
Creating Sprites and Sprite Lists
Initialize all Sprite Lists
1 2 3 4 5 6 | # Set up all Sprite lists. self.list_all_sprites = arcade.SpriteList() self.list_enemies = arcade.SpriteList() self.list_snowballs = arcade.SpriteList() self.list_player_lives = arcade.SpriteList() self.list_blocks = arcade.SpriteList() |
Create Sprite for player
1 2 3 4 5 6 | # Initialize a Sprite for the Player and # set it's image icon from a PNG file. self.player_sprite = PlayerSprite("./resources/images/penguin.png", SCALE) # Since there is only one Player Sprite, we don't need a list for "player Sprites". # but we still do add the Player Sprite to our list of all Sprites. self.list_all_sprites.append(self.player_sprite) |
Create Sprite for bullets
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | # Create Sprite for bullet and assign it an image and size (scale) snowball_sprite = SnowballSprite("./resources/images/snowball.png", SCALE) # Give the bullet Sprite an id snowball_sprite.guid = "Snowball" # Set the speed of the bullet snowball_speed = 12 # Set the angle (direction) the bullet will fly # based on the angle the Player Sprite is facing snowball_sprite.change_y = \ math.cos(math.radians(self.player_sprite.angle)) * snowball_speed snowball_sprite.change_x = \ -math.sin(math.radians(self.player_sprite.angle)) \ * snowball_speed # Set the beginning coordinates of this new Sprite # to match the Player Sprite's coordinates snowball_sprite.center_x = self.player_sprite.center_x snowball_sprite.center_y = self.player_sprite.center_y # Update Arcade on all the above settings. snowball_sprite.update() # Add the bullet Sprite to our list of all Sprites, which is a sprite list. self.list_all_sprites.append(snowball_sprite) # Add the bullet Sprite to our list of bullets self.list_snowballs.append(snowball_sprite) |
Create Sprites for obstacles
Below we will see a Python script pulled from the full project source code that uses the SCREEN_FROM_DATABASE constant to determine if we are getting our screen 1 data from the database or not. If not, we are using the randint() function to randomly place obstacles (ice blocks) on the screen, while creating them.
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 | if SCREEN_FROM_DATABASE == False: # Place ice blocks on the screen using random. for i in range(BLOCKS_NUMBER): block = arcade.Sprite("./resources/images/block-ice.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.list_blocks.append(block) else: # Place ice blocks on the screen # using Postgres. Log in to # the database. t_host = "db host address" t_port = "5432" # default port number. t_dbname = "database name" t_user = "database user name" t_pw = "password" # Create connection object. db_conn = psycopg2.connect(host=t_host, port=t_port, dbname=t_dbname, user=t_user, password=t_pw) # Create cursor object. db_cursor = db_conn.cursor() # SQL to get ice blocks configuration for the screen from the database. s = "" s += "SELECT" s += " i_y" s += ", i_x" s += " FROM tbl_screens_objects" s += " WHERE (" s += " id_screen = " + ID_SCREEN s += " AND t_object_type = 'ice block'" s += ")" try: db_cursor.execute(s) # return db_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 recordset rows returned, # place a block for each row according to the # x and y coordinates provided in that row. for each row in db_cursor: i_y = row[0] i_x = row[1] # Create a Sprite for the ice block and give it an image. block = arcade.Sprite("./resources/images/block-ice.png", SCALE) # Set the block's x and y coordinates. block.center_x = i_x block.center_y = i_y # Add this block to our list of all Sprites. self.list_all_sprites.append(block) # Add this block to our Sprite list of blocks. self.list_blocks.append(block) |
Next, we’ll create the Sprites to represent the enemies:
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 | image_list = ("./resources/images/walrus-red.png", "./resources/images/walrus-blue.png", "./resources/images/walrus-purple.png", "./resources/images/walrus-green.png") # Make the enemy Sprites for i in range(6): # For random colors image_no = random.randint(0,3) enemy_sprite = EnemySprite(image_list[image_no], SCALE) enemy_sprite.guid = "Enemy" # timer_rand initialized for later use in movement # which was explored in part 5 of this series. enemy_sprite.timer_rand = 0 # timer_smart initialized for later use... enemy_sprite.timer_smart = 0 # Set this Sprite's coordinates to be random enemy_sprite.center_y = random.randint(LIMIT_BOTTOM, LIMIT_TOP+1) enemy_sprite.center_x = random.randint(LIMIT_LEFT, LIMIT_RIGHT+1) # Set this Sprite's movement direction to be random enemy_sprite.change_x = int(random.random() * 2 + (DIFFICULTY/10) - 1) enemy_sprite.change_y = int(random.random() * 2 + (DIFFICULTY/10) - 1) # Set the size of this Sprite. enemy_sprite.size = 4 # Add this Sprite to both applicable Sprite lists self.list_all_sprites.append(enemy_sprite) self.list_enemies.append(enemy_sprite) |
Now we’ll finish with some more actual game code where we look at how to detect and handle 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 | # Cycle through all snowballs currently on screen. for snowball in self.list_snowballs: enemies = arcade.check_for_collision_with_list(snowball, self.list_enemies) # Cycle through all (if any) enemies hit by current snowball. for enemy in enemies: # A sound will eventually go in this spot. # Award points for hitting enemy based on difficulty level. self.score += int(100 * self.player_sprite.difficulty) enemy.remove_from_sprite_lists() snowball.remove_from_sprite_lists() for enemy in self.list_enemies: # Check for collision between current enemy and all ice blocks. any_collisions = arcade.check_for_collision_with_list(enemy, self.list_blocks) if len(any_collisions) > 0: # THIS enemy hit a block. # Reverse direction with added random factor. # The added random factor helps the enemy not # get stuck at 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_speedy) enemy.change_y = dir_y * (enemy_speedy) # 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 ~2 seconds of this direction so the # enemy for sure gets away from the ice block they hit. enemy.timer_smart = int(fps * 2) enemy.update() # Check for collision between player and all blocks. # NOTE: You can remove this section if you want the player to NOT # be impeded by ice blocks while only the enemies are. But this # will reduce challenge. 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: # Sound will eventually go in this spot # to represent player getting killed and eaten. 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 7 of the multi-part set of articles showing how to create a video game of a skating Penguin in Python, we added how to use Python’s Arcade library for Collision Detection between Sprites and PostgreSQL for storing and reading Sprite objects such as ice blocks in this case. In this part, we learn all about how to detect collisions between objects. In the articles following this one, we’ll learn to set up and play game sounds, keep score, and add a system for managing game difficulty.
Pilot the ObjectRocket Platform Free!
Try Fully-Managed CockroachDB, Elasticsearch, MongoDB, PostgreSQL (Beta) or Redis.
Get Started