Stopping Sprites with Mouse Clicks
In this article, we will implement mouse click detection for sprites and introduce basic state management.
By clicking on a sprite, we will stop it and change its state.
Improving the DemonSprite Class
First, we add a new member variable called dead to the DemonSprite constructor.
This flag represents whether the sprite is alive or dead.
The initial value is set to False (alive).
When a demon sprite is clicked, the value will be changed to True (dead).
# 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 # death flag
# draw a circle
self.oval = cvs.create_oval(
x - r, y - r,
x + r, y + r,
fill="white", width=0
)
Next, we add a die() method.
This method changes the sprite into a dead state by setting the dead flag to True,
stopping the sprite, and changing its color to red.
# sprite.py (excerpt)
def die(self, cvs):
self.dead = True # set death flag
self.stop() # stop movement
# update circle color
cvs.itemconfig(self.oval, fill="red")
Then, we add an is_dead() method that returns the current death state of the sprite.
If it returns False, the sprite is alive.
If it returns True, the sprite is dead.
# sprite.py (excerpt)
def is_dead(self):
return self.dead # return death flag
Finally, we add an is_hit() method.
This method receives the mouse click coordinates and calculates the distance between
the sprite and the clicked position.
If the calculated distance is smaller than the sprite’s radius, the method returns True.
Otherwise, it returns False.
# sprite.py (excerpt)
def is_hit(self, x, y):
dist_x = (self.x - x) ** 2 # horizontal distance
dist_y = (self.y - y) ** 2 # vertical distance
dist = (dist_x + dist_y) ** 0.5 # distance to sprite
return dist < self.r # hit if inside radius
Updating main.py
In the on_mouse_clicked() function implemented in main.py,
we can access the mouse click coordinates.
At this point, we check each sprite to see if it was clicked.
There is no need to check sprites that are already dead, so we skip them using is_dead().
If a living sprite is hit, we call the die() method and stop checking further sprites.
# main.py (excerpt)
def on_mouse_clicked(e):
print("Clicked:", e.x, e.y)
# demon group
for demon in demons:
if demon.is_dead():
continue # skip already dead sprites
if demon.is_hit(e.x, e.y):
demon.die(cvs) # kill the sprite
break # stop checking
With this implementation, you can now stop sprites by clicking on them.
Complete Code
Below is the complete code with all features implemented so far.
# sprite.py (complete)
import math
import random
import tkinter
# Demon sprite class
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 # death flag
# draw a circle
self.oval = cvs.create_oval(
x - r, y - r,
x + r, y + r,
fill="white", width=0
)
def update(self, cvs):
self.x = self.x + self.vx
self.y = self.y + self.vy
cvs.coords(
self.oval,
self.x - self.r, self.y - self.r,
self.x + self.r, self.y + self.r
)
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")
def is_dead(self):
return self.dead
def is_hit(self, x, y):
dist_x = (self.x - x) ** 2
dist_y = (self.y - y) ** 2
dist = (dist_x + dist_y) ** 0.5
return dist < self.r
# main.py (complete)
import math
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():
""" initialization """
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 i in range(TOTAL_DEMONS):
x = random.random() * W
y = random.random() * H
demon = sprite.DemonSprite(cvs, x, y, 20)
spd = random.randint(1, 4)
deg = random.randint(0, 360)
demon.move(spd, deg)
demons.append(demon)
def update():
""" update loop """
cvs.delete("hud")
msg = "x:{}, y:{}".format(mx, my)
cvs.create_text(
mx, my,
text=msg,
fill="white",
font=FONT,
tag="hud"
)
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 W < obj.x:
obj.set_x(0)
if obj.y < 0:
obj.set_y(H)
if H < obj.y:
obj.set_y(0)
def on_mouse_clicked(e):
print("Clicked:", e.x, e.y)
for demon in demons:
if demon.is_dead():
continue
if demon.is_hit(e.x, e.y):
demon.die(cvs)
break
def on_mouse_moved(e):
global mx, my
mx, my = e.x, e.y
# Tkinter setup
root = tkinter.Tk()
root.title("Hello, Tkinter!!")
root.resizable(False, False)
root.bind("<Button>", on_mouse_clicked)
root.bind("<Motion>", on_mouse_moved)
# Canvas
cvs = tkinter.Canvas(width=W, height=H, bg="black")
cvs.pack()
init()
update()
root.mainloop()
What’s Next?
Thank you for reading!
In the next article, we will attach demon images to the sprites.
Stay tuned!

Top comments (0)