DEV Community

Kajiru
Kajiru

Posted on

Getting Started with 2D Games Using Tkinter (Part 8): Add Demon Images to Sprites

Getting Started with 2D Games Using Tkinter (Part x): Add Demon Images to Sprites

In this article, we will apply demon images to our sprites.

1. Prepare Demon Images

Please copy and use the following demon images in your game.

Type Alive File Name Dead File Name
Blue Demon  dmn_alive_b.png  dmn_dead_b.png
Green Demon  dmn_alive_g.png  dmn_dead_g.png
Red Demon  dmn_alive_r.png  dmn_dead_r.png

Place all image files inside the images folder.

# Folder structure
project_folder/
├ main.py
├ sprite.py
└ images/
   ├ bg_jigoku.png
   ├ dmn_alive_b.png  # Blue demon (alive)
   ├ dmn_alive_g.png  # Green demon (alive)
   ├ dmn_alive_r.png  # Red demon (alive)
   ├ dmn_dead_b.png   # Blue demon (dead)
   ├ dmn_dead_g.png   # Green demon (dead)
   └ dmn_dead_r.png   # Red demon (dead)
Enter fullscreen mode Exit fullscreen mode

2. Improve the DemonSprite Class

Add image-loading logic inside the constructor of the DemonSprite class.

Image objects are created using tkinter.PhotoImage() together with create_image().

Here, we randomly select one type from "r" (red), "g" (green), or "b" (blue) to determine the demon color.

  • The alive image is stored in self.photo_alive
  • The dead image is stored in self.photo_dead
  • The currently displayed image is stored in self.image

Since the demon starts alive, self.photo_alive is used initially.

# sprite.py (excerpt)
def __init__(self, cvs, x, y, r):
    self.x = x
    self.y = y
    self.r = r
    self.vx = 0
    self.vy = 0
    self.dead = False

    # Circle (collision reference)
    self.oval = cvs.create_oval(
        x - r, y - r, x + r, y + r,
        fill="white", width=0
    )

    # Randomly choose demon type: r, g, or b
    self.type = random.choice(("r", "g", "b"))

    file_alive = f"images/dmn_alive_{self.type}.png"  # Alive image
    self.photo_alive = tkinter.PhotoImage(file=file_alive)

    file_dead = f"images/dmn_dead_{self.type}.png"   # Dead image
    self.photo_dead = tkinter.PhotoImage(file=file_dead)

    self.image = cvs.create_image(x, y, image=self.photo_alive)
Enter fullscreen mode Exit fullscreen mode

Finally, update the image inside the die() method using cvs.itemconfig().

# sprite.py (excerpt)
def die(self, cvs):
    self.dead = True
    self.stop()

    # Update circle color
    cvs.itemconfig(self.oval, fill="red")

    # Update sprite image
    cvs.itemconfig(self.image, image=self.photo_dead)
Enter fullscreen mode Exit fullscreen mode

After implementing this, clicking a sprite will stop it and switch its image.

Complete Code

Below is the complete implementation with all features applied.

# sprite.py (complete code)
import math
import random
import tkinter

class DemonSprite:

    def __init__(self, cvs, x, y, r):
        self.x = x
        self.y = y
        self.r = r
        self.vx = 0
        self.vy = 0
        self.dead = False

        # Circle for collision detection
        self.oval = cvs.create_oval(
            x - r, y - r, x + r, y + r,
            fill="white", width=0
        )

        # Random demon type
        self.type = random.choice(("r", "g", "b"))

        file_alive = f"images/dmn_alive_{self.type}.png"
        self.photo_alive = tkinter.PhotoImage(file=file_alive)

        file_dead = f"images/dmn_dead_{self.type}.png"
        self.photo_dead = tkinter.PhotoImage(file=file_dead)

        self.image = cvs.create_image(x, y, image=self.photo_alive)

    def update(self, cvs):
        self.x += self.vx
        self.y += self.vy

        # Update circle position
        cvs.coords(
            self.oval,
            self.x - self.r, self.y - self.r,
            self.x + self.r, self.y + self.r
        )

        # Update image position
        cvs.coords(self.image, self.x, self.y)

    def set_x(self, x):
        self.x = x

    def set_y(self, y):
        self.y = y

    def move(self, spd, deg):
        rad = deg * math.pi / 180
        self.vx = spd * math.cos(rad)
        self.vy = spd * math.sin(rad)

    def stop(self):
        self.move(0, 0)

    def die(self, cvs):
        self.dead = True
        self.stop()
        cvs.itemconfig(self.oval, fill="red")
        cvs.itemconfig(self.image, image=self.photo_dead)

    def is_dead(self):
        return self.dead

    def is_inside(self, x, y):
        dx = (self.x - x) ** 2
        dy = (self.y - y) ** 2
        dist = (dx + dy) ** 0.5
        return dist < self.r
Enter fullscreen mode Exit fullscreen mode
# main.py (complete code)
import random
import sprite
import tkinter

W, H = 480, 320
F_RATE = 30
F_INTERVAL = int(1000 / F_RATE)
FONT = ("Arial", 16)

mx, my = 0, 0
bg_photo, bg_image = None, None
TOTAL_DEMONS = 10
demons = []

def init():
    global bg_photo, bg_image

    bg_photo = tkinter.PhotoImage(file="images/bg_jigoku.png")
    bg_image = cvs.create_image(W / 2, H / 2, image=bg_photo)

    # Create demon sprites
    for _ in range(TOTAL_DEMONS):
        x = random.random() * W
        y = random.random() * H
        demon = sprite.DemonSprite(cvs, x, y, 20)
        demon.move(random.randint(1, 4), random.randint(0, 360))
        demons.append(demon)

def update():
    cvs.delete("hud")

    msg = f"x:{mx}, y:{my}"
    cvs.create_text(mx, my, text=msg, fill="white", font=FONT, tag="hud")

    for demon in demons:
        wrap_screen(demon)
        demon.update(cvs)

    root.after(F_INTERVAL, update)

def wrap_screen(obj):
    if obj.x < 0: obj.set_x(W)
    if obj.x > W: obj.set_x(0)
    if obj.y < 0: obj.set_y(H)
    if obj.y > H: obj.set_y(0)

def on_mouse_clicked(e):
    for demon in demons:
        if demon.is_inside(e.x, e.y) and not demon.is_dead():
            demon.die(cvs)
            break

def on_mouse_moved(e):
    global mx, my
    mx, my = e.x, e.y

root = tkinter.Tk()
root.title("Hello, Tkinter!!")
root.resizable(False, False)
root.bind("<Button>", on_mouse_clicked)
root.bind("<Motion>", on_mouse_moved)

cvs = tkinter.Canvas(width=W, height=H, bg="black")
cvs.pack()

init()
update()
root.mainloop()
Enter fullscreen mode Exit fullscreen mode

What's Next?

Thank you for reading!
In the next article, we will display a counter on the screen.

Stay tuned!

Top comments (0)