DEV Community

Kajiru
Kajiru

Posted on

Getting Started with 2D Games Using Tkinter (Part 5): Moving Sprites

Moving Sprites

In this article, we will focus on sprite movement.

We will improve the DemonSprite class that we created in the previous article.


Improving the DemonSprite Class

First, we add two new member variables, vx and vy, to the constructor of the DemonSprite class.

These variables represent the velocity of the sprite and are used to change its position on every frame.

# sprite.py (excerpt)
def __init__(self, cvs, x, y, r):
    self.x = x
    self.y = y
    self.r = r
    self.vx = 0  # velocity in the x direction
    self.vy = 0  # velocity in the y direction
    # circle
    self.oval = cvs.create_oval(
        x - r, y - r,
        x + r, y + r,
        fill="white", width=0
    )
Enter fullscreen mode Exit fullscreen mode

Next, inside the update() method of the DemonSprite class,

we add vx to the x coordinate and vy to the y coordinate.

Since update() is called every 30 milliseconds,

the position changes little by little, making the sprite appear to move.

# sprite.py (excerpt)
# update position
self.x = self.x + self.vx  # add vx to x
self.y = self.y + self.vy  # add vy to y
Enter fullscreen mode Exit fullscreen mode

Now, we add a new method called move().

This method receives:

  • spd as the speed
  • deg as the direction angle (in degrees)

Using these values, we calculate vx (x-direction velocity) and vy (y-direction velocity).

These are the components of a velocity vector.

(It may sound difficult, but for now, you can think of it as “speed with direction.”)

# sprite.py (excerpt)
def move(self, spd, deg):
    radian = deg * math.pi / 180  # convert degrees to radians
    self.vx = spd * math.cos(radian)
    self.vy = spd * math.sin(radian)
Enter fullscreen mode Exit fullscreen mode

Finally, we add a stop() method.

This method is very simple: it calls move() with speed 0 and angle 0,

which means the sprite stops moving.

# sprite.py (excerpt)
def stop(self):
    self.move(0, 0)  # speed 0, angle 0 (stop)
Enter fullscreen mode Exit fullscreen mode

Using the Improved DemonSprite

In main.py, many DemonSprite objects are created inside the init() function.

At this point, we call the newly added move() method.

We also randomize both the speed and the direction.

# main.py (excerpt)
# demon army
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)    # random speed
    deg = random.randint(0, 360)  # random direction
    demon.move(spd, deg)
    demons.append(demon)
Enter fullscreen mode Exit fullscreen mode

When you run the program now, the DemonSprite objects start moving.

However, if you watch for a while,

you will notice that all sprites eventually move off the screen…

(We will fix this in the next article!)


Complete Code

Below is the complete code with sprite movement implemented.

# sprite.py
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.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 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)
Enter fullscreen mode Exit fullscreen mode
# main.py
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():
    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 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():
    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:
        demon.update(cvs)

    root.after(F_INTERVAL, update)

def on_mouse_clicked(e):
    pass

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 keep the sprites inside the screen.

Stay tuned!

Top comments (0)