Change Image in Python and Cockroach

Introduction

This is part twelve of a multi-article group of tutorials showing how to build a 2D zombie videogame. In this one we add how to change an image in Python with Cockroach being used for storing and reading obstacles and storing and retrieving scores. We learn here how to swap a Sprite’s image using functions from Python’s Arcade library.

Prerequisites

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

  • Part 1 through part 11 are must-reads because we learned to set up a game screen with Cockroach for displaying GUI data, create Sprites, respond to keyboard presses, move the Player and enemies, fire bullets, track those bullets, handle collisions, and even added sound and a song!

Frameworks needed for the game

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.

An essential function we’ll be using in this article is the check_for_collision_with_list() function because the image swap occurs once a bullet has collided with a zombie a certain number of times. We call that being “enraged” in the code. Let’s look at the syntax of this function briefly before going deeper into scripts that utilize that function.

check_for_collision_with_list

This Arcade function allows for a way to find out if a Sprite has collided with any object in a given Sprite List and even provides us with a new list of Sprites that have collided!

1
list_of_sprites_collided = arcade.check_for_collision_with_list(individual_sprite, list_of_sprites)

Now that we see how that function for collision detection, we will look over the code for building some Sprites and Sprite Lists that we will be using a bit later in this article.

Sprite Lists

Initialize sprite lists

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

Crucial to understanding the image swap process is to first see how we initialize all the images:

Load images

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
# Set the zombie PNGs
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")

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))
    enemy_sprite.change_x = 0
    enemy_sprite.change_y = 0
    self.list_all_sprites.append(enemy_sprite)
    self.list_enemies.append(enemy_sprite)

Create player sprite

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

Because collisions are relevant to the enemy getting enraged where we will swap images, we’ll review a bit about both bullets and the obstacles on the screen. One of the types of Sprite that we want to detect collision for is the brain/bullet with enemies in the game. We’ll start by initializing the bullet sprite here:

Bullet sprite

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)

Now we’ll zoom in on the code that swaps out images!

Sprite image swap

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
# Cycle through the list of bullets.
if not self.game_over:
    self.list_all_sprites.update()
    # Iterate through all bullets (brains)
    for bullet in self.list_bullets:
        # Check for bullet collision with all enemies.
        enemies = arcade.check_for_collision_with_list(bullet, self.list_enemies)
        # Cycle through all enemies hit by the current flying brain.
        i = -1
        for enemy in enemies:
            i += 1
            # Give the Player points for hitting an enemy,
            # influenced by difficulty level.
            self.score += int(100 * self.player_sprite.difficulty)
            # For enemy that was hit, set speed to zero.
            enemy.change_x = 0
            enemy.change_y = 0
            # Get the enemy image url into a temporary variable.
            url_image = enemy.url_image
            # Increase the enemy's size.
            new_scale = enemy.scale + 0.07
            # When the enemy gets to a certain
            # size, the enemy becomes "enraged".
            if new_scale > 0.59:
                new_scale = 0.6
                enemy.enraged = True
                # Is enraged sound enabled for THIS enemy?
                # This is where we utilize the "z" we added
                # before "char" in a previous article in this
                # series where we initialized the enemy Sprite
                # images. If we put the "z" in, then the "if"
                # below does not play the enraged sound.
                if url_image.find(enemy.guid) > -1:
                    # following builds this: "self.sound_char_02_enraged"
                    build_sound_name = enemy.which
                    arcade.play_sound(eval(build_sound_name))
                else:
                    arcade.play_sound(self.sound_powerup)
                # Change image for "enraged" version where in
                # this case the zombie now has red eyes.
                url_image = url_image.replace("front","enraged")
            else:
                # NOTE the power-up sound played here.
                arcade.play_sound(self.sound_powerup)

            # Initialize the new "enraged" Sprite we
            # are using to replace the non-enraged one.
            new_enemy_sprite = EnemySprite(url_image, new_scale)
            # Check enemy size.
            # If enraged size, use our "immunity"
            # constant to assign it to this particular enemy.
            if new_scale > 0.59:
                new_enemy_sprite.immunity = ENEMY_ENRAGED_IMMUNE_TO_BLOCKS
                new_enemy_sprite.guid = enemy.guid
                new_enemy_sprite.which = enemy.which
                new_enemy_sprite.center_x = enemy.center_x
                new_enemy_sprite.center_y = enemy.center_y
                new_enemy_sprite.speed = enemy.speed
                # Note that frustration increments when
                # the enemy collides with blocks if not immune.
                new_enemy_sprite.frustration = enemy.frustration
                new_enemy_sprite.enraged = enemy.enraged
                new_enemy_sprite.change_x = 0
                new_enemy_sprite.change_y = 0
                new_enemy_sprite.timer_rand = int(fps * (12 - self.player_sprite.difficulty))
                new_enemy_sprite.timer_smart = 0
                enemy.update()
                new_enemy_sprite.update()
                # Remove the enemy we replaced.
                enemy.remove_from_sprite_lists()
                # Add our new enemy sprite, the replacement,
                # to our Sprite lists.
                self.list_all_sprites.append(new_enemy_sprite)
                self.list_enemies.append(new_enemy_sprite)
                # Delete the bullet.
                bullet.remove_from_sprite_lists()

Conclusion

In this part twelve of the multi-article set of lessons to learn how to build a 2D video game of a Monkey “feeding” brains to zombies, we added how to change a Sprite image with Python and CockroachDB for storing and reading Sprite objects as well as saving high scores. Code samples are included.

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.