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:

idt_titlet_colori_widthi_height
1Screen 1darkblue1200700

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_screent_obj_namet_obj_typet_obj_img_URLb_hit_possiblen_hit_awardsn_hit_damaget_hit_sound
1Playerplayerplayer.pngtrue00
1Wallobstacleblock.pngtrue00hit_block.mp3
1Treeobstaclebush-01.pngtrue00hit_bush.mp3
1Poweruppoweruppower-up.pngtrue100power-up.mp3
1Enemy01enemyenemy-01.pngtrue010explode.mp3
1Enemy02enemyenemy-02.pngtrue010explode.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

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.