DEV Community

Kajiru
Kajiru

Posted on

Getting Started with 2D Games Using Tkinter (Part 10): Display the Remaining Time

Getting Started with 2D Games Using Tkinter (Part x): Display the Remaining Time

In this final article, we will display the remaining time on the screen and implement game-over conditions.

(This is the last part of the series!)

1. Prepare a Timer Variable

First, create a variable named timer to store the remaining time.

Here, we calculate a value equivalent to about 10 seconds using the frame rate. (A bit tricky!)

# main.py (excerpt)
# Timer
timer = F_RATE * 10  # Remaining time (10 seconds)
Enter fullscreen mode Exit fullscreen mode

2. Decrease the Timer

Inside the update() function, display the timer in the top-right corner of the screen and decrement the timer value by 1 every frame.

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

    cvs.delete("hud")

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

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

    # Draw timer
    msg = "Time Left: {:.2f}s".format(timer / F_RATE)
    cvs.create_text(
        W - 20, 20,
        text=msg,
        fill="white",
        font=FONT,
        tag="hud",
        anchor="ne"
    )

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

    # Timer countdown
    timer = timer - 1

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

3. Game Over Conditions

Finally, complete the game logic by checking the game state.

  • If the remaining demon count reaches 0, display GAME CLEAR
  • If the timer reaches 0, display GAME OVER
# main.py (excerpt)
# Screen update
if 0 < counter and 0 <= timer:
    root.after(F_INTERVAL, update)
else:
    # Game result
    msg = "GAME CLEAR" if counter <= 0 else "GAME OVER"
    cvs.create_text(
        W / 2, H - 40,
        text=msg,
        fill="white",
        font=FONT,
        tag="hud"
    )
Enter fullscreen mode Exit fullscreen mode

Complete Code

Below is the complete code with the timer and game-ending logic 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 (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

        cvs.coords(
            self.oval,
            self.x - self.r, self.y - self.r,
            self.x + self.r, self.y + self.r
        )

        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

# Canvas size
W, H = 480, 320

# Frame rate
F_RATE = 30
F_INTERVAL = int(1000 / F_RATE)

# Font
FONT = ("Arial", 16)

# Mouse position
mx, my = 0, 0

# Background image
bg_photo, bg_image = None, None

# Total number of demons
TOTAL_DEMONS = 10

# Demon list
demons = []

# Demon counter
counter = TOTAL_DEMONS

# Timer (10 seconds)
timer = F_RATE * 10

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)

    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():
    global timer

    cvs.delete("hud")

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

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

    msg = "Time Left: {:.2f}s".format(timer / F_RATE)
    cvs.create_text(W - 20, 20, text=msg, fill="white", font=FONT, tag="hud", anchor="ne")

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

    timer -= 1

    if 0 < counter and 0 <= timer:
        root.after(F_INTERVAL, update)
    else:
        msg = "GAME CLEAR" if counter <= 0 else "GAME OVER"
        cvs.create_text(W / 2, H - 40, text=msg, fill="white", font=FONT, tag="hud")

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

Final Words

Thank you very much for reading this series.

With this article, “Getting Started with 2D Games Using Tkinter” comes to an end.

Great job making it all the way through!

Although the game we created is very simple, it includes all the essential elements of game development:

  • Sprite management
  • Mouse input
  • Game loop
  • Game over conditions

If you understand everything up to this point, you should now be able to create simple games using Tkinter on your own.

I encourage you to build your own small game based on what you’ve learned here.

I hope this series becomes the starting point for your next creation!

Top comments (0)