Game Python Postgres Draw Screen

Introduction

In this tutorial, we will begin creating a game with Python and Postgres with draw the screen as part 1 of a multi-part series of lessons where the end result will be a 2D graphical game. We’ll use Python’s “Arcade” library for the many graphics-related features it provides. We are also going to use PostgreSQL to store screen data in the database and – in this lesson – display that data to the screen for the player to see and later, interact with. The lesson following this one will show how to draw sprites – one hard-coded as the player, and the rest (obstacles) based on screen data saved in Postgres. Sprites are an Arcade object type that we’ll use for a “player” icon to be controlled and moved around the screen by the game player. After that, we’ll work on player movement. In subsequent articles we will work on game physics and many more parts of what makes a fun Python-based game.

We chose Arcade for many reasons, including its efficiency and the dedication of the author to making continual improvements. You can see how recent the latest changes have occurred by looking at their github here. Arcade is based on the older Pyglet library and shares much of its syntax.

Prerequisites

Use PIP to install Flask, psycopg2, random, and the Arcade libraries. Arcade Library documentation here. The Python library we will use for much of the purely game-related functionality of this game is Arcade.

Install arcade with Windows

1
pip install arcade

Install arcade with Linux and MacOS

1
pip3 install arcade

Drawing with Python

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
# Tutorial: Draw a smiley face

import arcade

# Set up variables for screen dimensions
window_width = 600
window_height = 600

# Open the screen. Set title and size
arcade.open_window(window_width, window_height, "Drawing with Python")

# Set the bkg color to black
# For a color list see http://arcade.academy/arcade.color.html
arcade.set_background_color(arcade.color.(0,0,0,0))
# The above "arcade.color.(0,0,0,0)" could be stated like "arcade.color.black"

# Turn the render process on. Required before we use drawing commands
arcade.start_render()

# Draw the head
#   Divide window height and width by 2 to get center of window
head_x = window_width / 2 #300
head_y = window_height / 2 #300
head_radius = (window_width + window_height) / 6 #200
arcade.draw_circle_filled(head_x, head_y, head_radius, arcade.color.YELLOW)

# Draw an eye; right
x = head_x + 70 #370
y = head_y + 50 #350
radius = head_radius / 10
arcade.draw_circle_filled(x, y, radius, arcade.color.WHITE)

# Draw an eye; left
x = head_x - 70 #230
y = head_y + 50 #350
radius = head_radius / 10
arcade.draw_circle_filled(x, y, radius, arcade.color.WHITE)

# Draw a curve for a smile
smile_x = head_x #300
smile_y = head_y - 20 #280
smile_width = 120
smile_height = 100
smile_start_angle = 190
smile_end_angle = 350
arcade.draw_arc_outline(smile_x, smile_y, smile_width, smile_height, arcade.color.WHITE, smile_start_angle, smile_end_angle, 10)

# Commit and display the above draw commands
arcade.finish_render()

# Keep the window up until the user clicks close
arcade.run()

Now that you have played with some arcade features, we will begin work on creating some functions for some of the graphics parts of our Python game. From here onward, we are going to bounce back and forth between Python and Postgres because we want to first create functions to draw a couple game objects that will appear on the screen, then show how to pull those objects and their screen location from the database. Finally, we’ll go back to Python coding to put the two together so we are opening the database, pulling the screen data, and then drawing it all to a window using built-in Arcade features like you see in our simple example above.

Draw a primitive with Python

A “primitive” is a rectangle, triangle, circle, etc. Usually, if not always, a simple geometric shape. In our game, we will use circles and rectangles. A circle will represent a “good thing” like a “power up”, while a rectangle will represent a wall. We’ll build this application so that it is easy as possible for you to easily add more shapes to the database as well as the Python code. Since creating screens for a game is important, we’ll build a screen editor in the next article.

Draw circle with Python

1
2
3
4
5
circle_x = horizontal_position_for_center_of_circle
circle_y = vertical_position_for_center_of_circle
circle_radius = size_of_circle
circle_color = arcade.color.green
arcade.draw_circle_filled(circle_x, circle_y, circle_radius, circle_color)

Draw rectangle with Python

1
2
3
4
5
6
rectangle_x = horizontal_position_for_center_of_rectangle
rectangle_y = vertical_position_for_center_of_rectangle
rectangle_width = width_of_rectangle
rectangle_height = height_of_rectangle
rectangle_color = arcade.color.green
arcade.draw_rectangle_filled(rectangle_x, rectangle_y, rectangle_width, rectangle_height, rectangle_color)

Note: With the above arcade.draw_circle_filled and arcade.draw_rectangle_filled functions, you can use arcade.CREATE_circle_filled and arcade.CREATE_rectangle_filled instead, for faster rendering because Python will then use Vertex Buffer Objects, which keeps the vertices loaded on the video card for faster render times.

Get screen from PostgreSQL

Now we’ll retrieve screen data, which is objects and their locations in 2D space, from two tables. We’ll start by creating the tables. Why two? One table (“tbl_screens”) will have a row per screen, while the second table (“tbl_screens_objects”) will store all the objects and use id_screen to point back to the id field in tbl_screens.

Create PostgreSQL tables for storing screens

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CREATE TABLE public.tbl_screens (
    id serial NOT NULL,
    id_session int4 NULL DEFAULT 0,
    id_user_created int4 NULL DEFAULT 0,
    t_title VARCHAR(255) NULL,
    t_message VARCHAR(255) NULL,
    t_note VARCHAR(255) NULL,
    i_order int4 NULL DEFAULT 0,
    i_width int4 NOT NULL DEFAULT 0,
    i_height int4 NOT NULL DEFAULT 0,
    t_color VARCHAR(24) NULL,
    n_points int4 NULL DEFAULT 0,
    d_created DATE NULL DEFAULT now(),
    CONSTRAINT tbl_screens_pkey PRIMARY KEY (id)
);

Analysis

  • id serial: This is the unique index for the table that auto-increments.
  • id_session: This column is primarily for when a screen creator adds a new screen to the database and we want to most accurately and quickly retrieve the new id based on session AND “id_user_created” AND “t_title” below. (1)
  • id_user_created: We may want to give a user credit or for some other reason keep track of the user who created a given screen. (1)
  • t_title: The name of the screen. At the very least, we’ll use this to name our window.
  • t_message: A message we may or may not show the user. (1)
  • t_note: More of a placeholder for now. But the screen creator may want to put some comment here. (1)
  • i_order: In a multi-screen game, we’ll want to track the order of screens.
  • i_width & i_height: Dimensions of window.
  • t_color: Color of window.
  • n_points: Points to award the player for completion.
  • d_created: Date the screen was made.
  • NOTE: Later we’ll want to think about some condition for screen completion.
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 TABLE public.tbl_screens_objects (
    id serial NOT NULL,
    id_session int4 NULL DEFAULT 0,
    id_screen int4 NULL DEFAULT 0,
    t_object_name VARCHAR(64) NULL,
    t_object_type VARCHAR(64) NULL,
    t_object_image_URL VARCHAR(255) NULL,
    i_order int4 NOT NULL DEFAULT 1,
    i_x int4 NULL DEFAULT 0,
    i_y int4 NULL DEFAULT 0,
    i_width int4 NULL DEFAULT 0,
    i_height int4 NULL DEFAULT 0,
    i_radius int4 NULL DEFAULT 0,
    i_speed int4 NULL DEFAULT 100,
    t_color_fill VARCHAR(24) NULL,
    t_color_border VARCHAR(24) NULL,
    t_color_collision VARCHAR(24) NULL,
    t_properties VARCHAR(128) NULL,
    t_collision_sound VARCHAR(128) NULL,
    t_collision_action VARCHAR(64) NULL,
    b_collision_possible bool NULL DEFAULT FALSE,
    b_collision_destroys_it bool NULL DEFAULT FALSE,
    b_collision_awards bool NULL DEFAULT FALSE,
    n_collision_awards NUMERIC NOT NULL DEFAULT 0,
    n_collision_damage NUMERIC NOT NULL DEFAULT 0,
    CONSTRAINT tbl_screens_objects_pkey PRIMARY KEY (id)
);

Analysis

  • id and id_session: These two columns have same function as same columns in tbl_screens above. (1)
  • id_screen: Via foreign keys we will tie this table to tbl_screens. This field points to the unique id field in tbl_screens. This will be a one to many relationship, where one row in tbl_screens points to a potential of many rows in this table (tbl_screens_objects).
  • t_object_name: Name – may not be used.
  • t_object_type: What kind of object? For now this means shape and we’ll use either “circle” or “rectangle”.
  • t_object_image_URL: Image for the object.
  • i_x and i_y: X and Y coordinates of object. Something to think about: making your object dimensions and location resolution independent. For the purposes of this simple game, we will go with the window being 600 x 600 but that is limiting.
  • i_width and i_height: This will apply to rectangles, not circles.
  • i_radius: This will apply to circles, not rectangles.
  • i_speed: How fast the object moves.
  • t_color_fill: Color of object.
  • t_color_border: Color of object border IF it has a border. (1)
  • t_color_collision: Color object changes to if hit by player.
  • t_properties: Properties to add to the object. (1)
  • t_collision_sound: Sound for player to hear when they collide with the object. (1)
  • t_collision_action: Action (function) to call when player collides with the object.
  • b_collision_possible: Boolean column we will use to determine if the player can collide with the object.
  • b_collision_destroys_it: Does the object disappear if hit by player?
  • b_collision_awards: Does the player receive awards (points) when they collide with this object?
  • n_collision_awards: How many points does the player receive when they collide with this object?
  • n_collision_damage: How many points damage, if any, does the player receive when they collide with this object?

(1) The columns we marked with “(1)” are not used in the current application. They are present because they may be necessary if you choose to add more features to your game.

We’ll assume a certain amount of screen data exists so we can write SQL to get data from a screen and draw that data using Python’s Arcade library functions.

NOTE: In the next article of this series we will create a Python application we’ll call a “screen editor” to allow users to create screens that get saved to the database.

Query PostgreSQL for screen data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# First we get a screen ID and title from tbl_screens from FIRST screen
s = ""
s += "SELECT TOP 1"
s += " id"
s += ", t_title"
s += ", i_width"
s += ", i_height"
s += ", t_color"
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

Analysis: Read window/screen data from PostgreSQL, including screen id, title, dimensions, and color.

Now that we have the data we need for the screen, we can query tbl_screens_objects to get all the objects associated with a specific screen.

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
s = ""
s += "SELECT"
s += " id"
s += ", t_object_name"
s += ", t_object_type"
s += ", t_object_image_URL"
s += ", i_x"
s += ", i_y"
s += ", i_width"
s += ", i_height"
s += ", i_radius"
s += ", t_color_fill"
s += ", t_color_border"
s += ", t_color_collision"
s += ", t_properties"
s += ", t_collision_sound"
s += ", t_collision_action"
s += ", b_collision_possible"
s += ", b_collision_destroys_it"
s += ", b_collision_awards"
s += ", n_collision_awards"
s += ", n_collision_damage"
s += " FROM tbl_screens_objects"
s += " WHERE ("
s += " id_screen = " + id_screen
s += ")"
s += " ORDER BY i_order"
try:
    db_cursor.execute(s)
    # We'll loop through db_cursor records soon...
except psycopg2.Error as e:
    t_message = "Database error: " + e + "/n SQL: " + s
    return render_template("error.html", t_message = t_message)

Analysis: This query pulls all object data for a specific screen.

Draw screen from recordset

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
# First we need to open a window
window_width = i_width
window_height = i_height

# Open the screen. Set title and size
arcade.open_window(window_width, window_height, t_title)

# Set the bkg color to t_color from tbl_screens
arcade.set_background_color(arcade.color.t_color)

# Turn the render process on. Required before we use drawing commands
arcade.start_render()

# Now iterate through each record (row)
# and draw the object to the screen
for each db_row in db_cursor:
    # get data from database
    id_screen = db_cursor.fetch("id")
    t_object_name = db_cursor.fetch("t_object_name")
    t_object_type = db_cursor.fetch("t_object_type")
    t_object_image_URL = db_cursor.fetch("t_object_image_URL")
    i_x = db_cursor.fetch("i_x")
    i_y = db_cursor.fetch("i_y")
    i_width = db_cursor.fetch("i_width")
    i_height = db_cursor.fetch("i_height")
    i_radius = db_cursor.fetch("i_radius")
    t_color_fill = db_cursor.fetch("t_color_fill")
    t_color_border = db_cursor.fetch("t_color_border")
    t_color_collision = db_cursor.fetch("t_color_collision")
    t_properties = db_cursor.fetch("t_properties")
    t_collision_sound = db_cursor.fetch("t_collision_sound")
    t_collision_action = db_cursor.fetch("t_collision_action")
    b_collision_possible = db_cursor.fetch("b_collision_possible")
    b_collision_destroys_it = db_cursor.fetch("b_collision_destroys_it")
    b_collision_awards = db_cursor.fetch("b_collision_awards")
    n_collision_awards = db_cursor.fetch("n_collision_awards")
    n_collision_damage = db_cursor.fetch("n_collision_damage")

    # draw each object to screen
    if t_object == "circle":
        circle_x = i_x
        circle_y = i_y
        circle_radius = i_radius
        circle_color = t_color_fill
        arcade.draw_circle_filled(circle_x, circle_y, circle_radius, circle_color)

    if t_object == "rectangle":
        rectangle_x = i_x
        rectangle_y = i_y
        rectangle_width = i_width
        rectangle_height = i_height
        rectangle_color = t_color_fill
        arcade.draw_rectangle_filled(rectangle_x, rectangle_y, rectangle_width, rectangle_height, rectangle_color)

db_cursor.close

# Commit and show the results of the draw commands above
arcade.finish_render()

# Keep the window alive until the user clicks close
arcade.run()

NOTE: In the Python above we used arcade.draw_ commands but moving ahead, we want these objects to be detectable for collisions, etc., so in future parts of this series, we are going to render Sprites instead of drawn objects, both from Python’s Arcade framework.

Conclusion

In this tutorial, we begin creating a game with Python and Postgres with draw the screen as part 1 of a multi-part series of lessons where the end result will be a graphical Python game. In this part 1, we used Python’s “Arcade” library because of the graphics-drawing features it provides. We used PostgreSQL to store and retrieve screen data from the database and display that data on the screen. The lesson following this one will show how to create sprites – one Sprite will be hard-coded as the player’s icon, and the rest – obstacles like walls – will be based on screen data saved in Postgres via a screen editor we will create later. Sprites are an Arcade object that we’ll use for a “player” icon to be controlled and moved around the screen by the user. After that, we’ll work on player movement. In later articles we will work on game physics and many more parts of what makes a fun Python-based game.

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.