Game Python Postgres with Sprite Collisions
Introduction
In this tutorial, we will continue creating a game with Python and Postgres with Sprite collisions as part 5 of a multi-part series of lessons where the final result will be a simple 2D graphical top-down view game like PacMan. We will use Python’s “Arcade” framework for the gaming-related features. We used PostgreSQL to read and write screen data. In part 1 we created two tables for that. In this lesson we will add: placing enemy Sprites on the screen and moving them using the random function. In the next lesson, we’ll learn how to give the monsters more intelligence for tracking the player.
Prerequisites
See part 1 through 4, where we learned how to draw a screen, create tables in Postgres for storing and reading screen data and screen objects as Sprites, reading keys to control player movement, and gave random movements to the enemies or “monsters” in the game. Please see: Create game with Python and Postgres Part 1.
Python Arcade Sprites
We’ll assume you followed the previous parts of this series, so we’ll leave out creating the two Postgres tables and we won’t re-do the detail of creating a window with Arcade, except in the final source code at the bottom.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # use Python's arcade framework import arcade # See past articles for step-by-step lessons in the following: # Set up screen dimensions variables # Open/create the window. Set size and title # Set window bkg color # Set up player's sprite arcade.PlayerSprite = arcade.Sprite("game-graphics/icon-player.png") # Change the coordinates of our player Sprite arcade.PlayerSprite.set_position(player_x, player_y) # Keep up the game window until the user taps close arcade.run() |
Get screen from PostgreSQL
To get screen and object positions and function from the database, we’ll use a database. Please refer to part 1 in this series for the CREATE TABLE we used for both PostgreSQL tables. Why two? Tbl_screens will have a row per screen, while tbl_screens_objects will store all objects (player, enemies, and obstacles) using the indexed id_screen column to point up to the id column in tbl_screens.
Postgres tables to save game screen data
Here is tbl_screens with one row for data stored as screen 1 of the game:
id | t_title | t_color | i_width | i_height |
---|---|---|---|---|
1 | Screen 1 | darkblue | 1200 | 700 |
The following table represents tbl_screens_objects for storing the Sprites on any given screen, which will be player, obstacle, powerup, or enemy. In the update function we create in the final-code-for-this-lesson, we’ll distinguish what happens to the player, based on which of these types the player Sprite collided with. The table below is related via foreign key to the above tbl_screens by id and id_screen in the following table.
id_screen | t_obj_name | t_obj_type | t_obj_img_URL | b_hit_possible | n_hit_awards | n_hit_damage | t_hit_sound |
---|---|---|---|---|---|---|---|
1 | Player | player | player.png | true | 0 | 0 | |
1 | Wall | obstacle | block.png | true | 0 | 0 | hit_block.mp3 |
1 | Tree | obstacle | bush-01.png | true | 0 | 0 | hit_bush.mp3 |
1 | Powerup | powerup | power-up.png | true | 10 | 0 | power-up.mp3 |
1 | Enemy01 | enemy | enemy-01.png | true | 0 | 10 | explode.mp3 |
1 | Enemy02 | enemy | enemy-02.png | true | 0 | 10 | explode.mp3 |
Query Postgres for screen objects
See source code below since we covered this in detail in Part 2 of this series.
Random sprite movement
Now we will write the code for handling movement of the monsters, which will be completely random until the next lesson where we give them a bit more intelligence.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | direction = random.randint(1, 8) if direction == 1: y += 1 if direction == 2: x += 1 y += 1 if direction == 3: x +=1 if direction == 4: x += 1 y -= 1 if direction == 5: y -= 1 if direction == 6: y -= 1 x -= 1 if direction == 7: x -= 1 if direction == 8: x -= 1 |
In a previous part of this multi-article series, we learned how to reposition a Sprite, so we’ll go ahead and do that in the full source code below.
Full source code
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 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 | import arcade import psycopg2 import random from flask import Flask from flask import render_template # connect to database t_host = "PostgreSQL database host address" t_port = "5432" t_dbname = "database name" t_user = "database user name" t_pw = "password" db_conn = psycopg2.connect(host=t_host, port=t_port, dbname=t_dbname, user=t_user, password=t_pw) db_cursor = db_conn.cursor() # initialize global variables id_screen = 0 t_title = "" i_width = 0 i_height = 0 t_color = "" i_enemy_hit = 0 i_hit_points = 100 i_points = 0 i_lives = 3 @app.route("/main") def getScreenFromDB(): # Get window ID, title, color, width, and height from Postgres # for first screen (select top 1) s = "" s += "SELECT TOP 1" s += " id" s += ", t_title" s += ", t_color" s += ", i_width" s += ", i_height" s += " FROM tbl_screens" s += " ORDER BY i_order" try: db_cursor.execute(s) id_screen = db_cursor.fetch("id") t_title = db_cursor.fetch("t_title") i_width = db_cursor.fetch("i_width") i_height = db_cursor.fetch("i_height") t_color = db_cursor.fetch("t_color") except psycopg2.Error as e: t_message = "Database error: " + e + "/n SQL: " + s return render_template("error.html", t_message = t_message) db_cursor.close def getObjectsFromDB(): # Get data from tbl_screens_objects s = "" s += "SELECT" s += " id" s += ", t_obj_name" s += ", t_obj_type" s += ", t_obj_img_URL" s += ", i_x" s += ", i_y" s += ", t_color_hit" s += ", t_properties" s += ", t_hit_sound" s += ", t_hit_action" s += ", b_hit_possible" s += ", b_hit_destroys_it" s += ", b_hit_awards" s += ", n_hit_awards" s += ", n_hit_damage" s += " FROM tbl_screens_objects" s += " WHERE (" s += " id_screen = " + id_screen s += ")" s += " ORDER BY i_order" try: db_cursor.execute(s) return db_cursor except psycopg2.Error as e: t_message = "Database error: " + e + "/n SQL: " + s return render_template("error.html", t_message = t_message) class gameWindow(arcade.Window): def__init__(self, i_width, i_height, t_title): super().__init__(width, height, title, resizable=false) self.set_location(100, 100) arcade.set_background_color(arcade.color.t_color) self.player_x = 100 self.player_y = 200 self.player_speed = 250 self.right = false self.left = false self.up = false self.down = false # Initialize a sprite list to store objects like walls. # This object type does no damage; only stops player motion. self.ListSpriteObjects = arcade.SpriteList() # Initialize a sprite list to store enemies. # These objects cause damage to i_hit_points. self.ListSpriteEnemies = arcade.SpriteList() # Initialize a sprite list to store powerups. # These objects award points to the player when hit. self.ListSpritePowerUps = arcade.SpriteList() # Pull data from Postgres into the 3 lists above, as well as player data. i = 0 for each db_row in db_cursor: id_screen = db_row.fetch("id") t_obj_name[i] = db_row.fetch("t_obj_name") t_obj_type[i] = db_row.fetch("t_obj_type") t_obj_img_URL = db_row.fetch("t_obj_img_URL") i_x = db_row.fetch("i_x") i_y = db_row.fetch("i_y") t_color_hit[i] = db_row.fetch("t_color_hit") t_properties[i] = db_row.fetch("t_properties") t_hit_sound[i] = db_row.fetch("t_hit_sound") t_hit_action[i] = db_row.fetch("t_hit_action") b_hit_possible[i] = db_row.fetch("b_hit_possible") b_hit_destroys_it[i] = db_row.fetch("b_hit_destroys_it") b_hit_awards[i] = db_row.fetch("b_hit_awards") n_hit_awards[i] = db_row.fetch("n_hit_awards") n_hit_damage[i] = db_row.fetch("n_hit_damage") # draw each sprite in db_row to screen # ignoring if player because we already created player as sprite1 above. # This assumes we didn't filter out "player" by changing our SQL WHERE clause. if t_object_type == "obstacle": self.SpriteObject = arcade.Sprite(t_object_image_URL, i_x, i_y) # add current obstacle to growing list of Sprites (SpriteList) self.ListSpriteObjects.append(self.SpriteObject) elif t_object_type == "enemy": self.SpriteObstacle = arcade.Sprite(t_object_image_URL, i_x, i_y) # add current enemy (sprite) to growing list of Sprites (SpriteList) self.SpriteEnemies.append(self.SpriteObject) elif t_object_type == "powerup": self.SpriteObstacle = arcade.Sprite(t_object_image_URL, i_x, i_y) # add current PowerUp to growing list of Sprites (SpriteList) self.SpritePowerUps.append(self.SpriteObject) elif t_object_type == "Player": self.SpritePlayer = arcade.Sprite(t_object_image_URL, i_x, i_y) # increment the loop counter i += 1 def enemyMovement(): # Loop through all enemy sprites (in this case, 2) # and give them a random direction out of the 8 possible directions for currentSprite in self.ListSpriteEnemies: x = currentSprite.position[0] y = currentSprite.position[1] direction = random.randint(1, 8) if direction == 1: y += 1 if direction == 2: x += 1 y += 1 if direction == 3: x +=1 if direction == 4: x += 1 y -= 1 if direction == 5: y -= 1 if direction == 6: y -= 1 x -= 1 if direction == 7: x -= 1 if direction == 8: x -= 1 y += 1 # set new position for each sprite currentSprite.set_position(x, y) # note: These Sprites probably won't move very far since # they will be changing their direction constantly.add() # In the next lesson we will address this by giving them # more "intelligence". def on_draw(self): self.SpritePlayer.draw() self.ListSpriteObjects.draw() self.ListSpriteEnemies.draw() self.ListSpritePowerUps.draw() def on_update(self, delta_time): # delta_time is last time same function was run; # used to scale movement to processing speed if self.right = true: self.player_x += self.player_speed * delta_time if self.left = true: self.player_x -= self.player_speed * delta_time if self.up = true: self.player_y += self.player_speed * delta_time if self.down = true: self.player_y -= self.player_speed * delta_time self.SpritePlayer.set_position(self.player_x, self.player_y) # call function to move all enemy Sprites enemyMovement() self.ListSpriteObjects.update() self.ListSpriteEnemies.update() self.ListSpritePowerUps.update() # Did player hit an enemy? i = 0 for SpriteEnemy in self.ListSpriteEnemies: b_collision = arcade.check_for_collision(SpriteEnemy, self.SpritePlayer) if b_collision: i_enemy_hit = i i_hit_points -= n_hit_damage[i_enemy_hit] # graphics / sound code to show crash here if i_hit_points < 1: i_lives -= 1 if i_lives < 1: # Dead - Next lesson we will write code to handle this event # For now, the player lives forever. i_hit_points = 100 i +=1 # Did player hit a power-up? i = 0 for SpritePowerUp in self.ListSpritePowerUps: b_collision = arcade.check_for_collision(SpritePowerUp, self.SpritePlayer) if b_collision: i_pu_hit = i i_points += n_hit_awards[i_pu_hit] # graphics / sound code to show powerup collision here i +=1 # Did player hit an obstacle? i = 0 for SpritePowerUp in self.ListSpritePowerUps: b_collision = arcade.check_for_collision(SpritePowerUp, self.SpritePlayer) if b_collision: i_pu_hit = i # stop player movement self.right = false self.left = false self.up = false self.down = false # graphics / sound code to show object collision here - future lesson. i +=1 def on_key_press(self, symbol, modifiers): # every time a key is pressed, this function is called if symbol == arcade.key.right: self.right = true if symbol == arcade.key.left: self.left = true if symbol == arcade.key.up: self.up = true if symbol == arcade.key.down: self.down = true def on_key_release(self, symbol, modifiers): # every time a key is released, this function is called if symbol == arcade.key.right: self.right = false if symbol == arcade.key.left: self.left = false if symbol == arcade.key.up: self.up = false if symbol == arcade.key.down: self.down = false Def main(): getScreenFromDB() db_cursor = getObjectsFromDB() gameWindow(i_width, i_height, t_title) arcade.run() |
Conclusion
In this tutorial, we continued creating a Python game with the Arcade library and Postgres. The new lesson here was learning to deal with Sprite collisions as part 5 of this multi-part series of lessons where the result will be a 2D graphical top-down view game. We used Python’s “Arcade” framework for the gaming-related features and Postgres to read and write screen data. In the next lesson, we’ll add to the intelligence of the game’s monsters.
Pilot the ObjectRocket Platform Free!
Try Fully-Managed CockroachDB, Elasticsearch, MongoDB, PostgreSQL (Beta) or Redis.
Get Started