DEV Community

Kajiru
Kajiru

Posted on

Getting Started with 2D Games Using Tkinter (Part 9): Display a Counter

Display a Counter

In this article, we will display a counter that shows how many demons remain on the screen.

1. Prepare a Counter Variable

First, create a variable named counter to keep track of the remaining number of demons.

# main.py (excerpt)
# Demon counter
counter = TOTAL_DEMONS
Enter fullscreen mode Exit fullscreen mode

2. Decrease the Counter

Each time a demon is captured, we decrement the counter inside the on_mouse_clicked() function.

Since counter is a global variable, don’t forget to declare it using global.

# main.py (excerpt)
def on_mouse_clicked(e):
    global counter

    # Demon group
    for demon in demons:
        if demon.is_inside(e.x, e.y):
            if demon.is_dead():
                continue
            demon.die(cvs)
            counter = counter - 1  # Decrease demon counter
            break
Enter fullscreen mode Exit fullscreen mode

3. Display the Counter

Inside the update() function, draw the counter on the screen using the counter variable.

# main.py (excerpt)
def update():
    """ Update function """

    cvs.delete("hud")

    # Draw mouse coordinates
    msg = "x:{}, y:{}".format(mx, my)
    cvs.create_text(
        mx, my,
        text=msg,
        fill="white",
        font=FONT,
        tag="hud"
    )

    # Draw demon counter
    msg = "Remaining Demons: {}".format(counter)
    cvs.create_text(
        20, 20,
        text=msg,
        fill="white",
        font=FONT,
        tag="hud",
        anchor="nw"
    )

    # Demon group
    for demon in demons:
        overlap_area(demon)  # Screen wrap check
        demon.update(cvs)

    root.after(F_INTERVAL, update)
Enter fullscreen mode Exit fullscreen mode

After implementing this, the number of remaining demons will be displayed on the screen.

Complete Code

Below is the complete code with the counter feature implemented.

# 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: r, g, b
        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):
        radian = deg * math.pi / 180
        self.vx = spd * math.cos(radian)
        self.vy = spd * math.sin(radian)

    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 = []

# Demon counter
counter = TOTAL_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")

    # Draw mouse position
    msg = "x:{}, y:{}".format(mx, my)
    cvs.create_text(mx, my, text=msg, fill="white", font=FONT, tag="hud")

    # Draw demon counter
    msg = "Remaining Demons: {}".format(counter)
    cvs.create_text(20, 20, text=msg, fill="white", font=FONT, tag="hud", anchor="nw")

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

    root.after(F_INTERVAL, update)

def overlap_area(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):
    global counter

    for demon in demons:
        if demon.is_inside(e.x, e.y):
            if demon.is_dead():
                continue
            demon.die(cvs)
            counter -= 1
            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 the remaining time on the screen.

Stay tuned!

Top comments (0)