5
\$\begingroup\$

In the current code, the animation is very unclear, and not "polished" (it doesn't run smooth).

Can I make the animation smoother? And/or, why it doesn't run smooth?

from tkinter import *
import time

WIDTH = 800
HEIGHT = 500
SIZE = 50
tk = Tk()
canvas = Canvas(tk, width=WIDTH, height=HEIGHT, bg="grey")
canvas.pack()
color = 'black'


class Ball:
    def __init__(self):
        self.shape = canvas.create_oval(0, 0, SIZE, SIZE, fill=color)
        self.speedx = 9 # changed from 3 to 9
        self.speedy = 9 # changed from 3 to 9
        self.active = True
        self.move_active()

    def ball_update(self):
        canvas.move(self.shape, self.speedx, self.speedy)
        pos = canvas.coords(self.shape)
        if pos[2] >= WIDTH or pos[0] <= 0:
            self.speedx *= -1
        if pos[3] >= HEIGHT or pos[1] <= 0:
            self.speedy *= -1

    def move_active(self):
        if self.active:
            self.ball_update()
            tk.after(40, self.move_active) # changed from 10ms to 30ms

ball = Ball()
tk.mainloop()
\$\endgroup\$
1
  • 1
    \$\begingroup\$ What do you mean "not working as expected"? I provided a working code, and I'm asking for a code review. \$\endgroup\$ – sheldonzy Sep 16 '17 at 18:11
5
\$\begingroup\$

Your animation isn't smooth because the ball is jumping 9 pixels for every frame. If you want smooth animation, change your "speed" to a much smaller number.

Animation is a set of trade-offs. The speed is a simple multiplier of how many frames you display each second times how many pixels you move in each frame. As the number of pixels you move goes up, the more jerky the animation will be. Likewise, the more often you update, the faster your animation will go. You need to decide which is more important: smoothness or speed.

\$\endgroup\$
2
  • \$\begingroup\$ The thing is, when I decrease the speed, and decrease the stop between each update, the animation still doesn't look good. For example, run with speed 2, and update each 10ms and see what happens. \$\endgroup\$ – sheldonzy Sep 16 '17 at 18:19
  • \$\begingroup\$ Those are the only two things you can change to improve the animation in this code. \$\endgroup\$ – Bryan Oakley Sep 16 '17 at 18:27
4
\$\begingroup\$

This question was old, but I found it in a search because it was related to an issue I am having. My attempt at fixing this was to try to implement "double-buffering". On my computer, my version of code fixes the clipping that occurs causing 1/4 of the circle to be a square. It's still not as smooth as I'd like though.

from tkinter import *
import time

WIDTH = 800
HEIGHT = 500
SIZE = 50
tk = Tk()
canvas = Canvas(tk, width=WIDTH, height=HEIGHT, bg="blue")
canvas.pack()
color = 'black'

class Ball:
    def __init__(self, tag):
        self.shape = canvas.create_oval(0, 0, SIZE, SIZE, fill=color, tags=tag)
        self.speedx = 9 # changed from 3 to 9
        self.speedy = 9 # changed from 3 to 9
        self.active = True

    def ball_update(self):
        canvas.move(self.shape, self.speedx, self.speedy)
        pos = canvas.coords(self.shape)
        if pos[2] >= WIDTH or pos[0] <= 0:
            self.speedx *= -1
        if pos[3] >= HEIGHT or pos[1] <= 0:
            self.speedy *= -1

global switcher
switcher = True
def cycle():
    global switcher
    canvas.tag_raise("bg")
    if switcher:
        ball2.ball_update()
        ball2.ball_update()
        canvas.tag_raise("ball")
    else:
        ball.ball_update()
        ball.ball_update()
        canvas.tag_raise("ball2")
    tk.update_idletasks()
    switcher = not switcher
    tk.after(40, cycle)

bg = canvas.create_rectangle(0, 0, WIDTH+1, HEIGHT+1, fill="gray", tags="bg")
ball = Ball("ball")
ball.ball_update()
ball2 = Ball("ball2")

tk.after(0, cycle)
tk.mainloop()
\$\endgroup\$
2
  • \$\begingroup\$ I’m confused, I thought I posted a better answer. \$\endgroup\$ – Drew Cunningham May 8 '19 at 14:22
  • \$\begingroup\$ I have wronged you. I'll delete that comment. \$\endgroup\$ – Jeremy Taylor May 8 '19 at 23:03
2
\$\begingroup\$

I know this is an old question, but one extrememly important aspect of this problem hasn't been explored. If the FPS of a program is under 60, then the human eye will be able to see individual frames, resulting in a "choppy" appearance. I wrote this small fps indicator to inspect the OP's code, and also the code in @Drew Cunningham's answer.

from tkinter import *
import time

class FpsIndicator:
    def __init__(self, master):
        self.textBox = Text(master, height=1, width=8)
        self.fpsStart = time.time()
        self.current_fps = 0
        self.last_tick_num = 0

    def pack(self):
        self.textBox.pack(side=RIGHT)
        self.textBox.insert(END, "...")

    def update_fps(self, tick_num):
        now = time.time()
        if now > self.fpsStart + 1:
            if self.last_tick_num == 0:
                self.last_tick_num = tick_num
                self.fpsStart = now
            else:
                self.current_fps = (tick_num - self.last_tick_num) / (now - self.fpsStart)
                self.textBox.delete('1.0', END)
                self.textBox.insert(END, f"{self.current_fps:.2f}fps")

                self.last_tick_num = tick_num
                self.fpsStart = now

# Use
global tickNum
tickNum = 0

def cycle():
    global tickNum
    tickNum += 1
    fpsIndicator.update_fps(tickNum)
    tk.after(5, cycle)

tk = Tk()
fpsIndicator = FpsIndicator(tk)
fpsIndicator.pack()
tk.after(0, cycle)
tk.mainloop()

For @Drew Cunningham's code and the OPs code, I got around 20-25 fps. This is one third of the frames required for a smooth animation. Making one small change to their code fixes this issue:

# change this
tk.after(40, cycle)
# to
tk.after(10, cycle)

The fps is now at 60+, so the animation is smooth. However, when I do this to the OPs code, parts of the moving circle aren't rendered properly. The code given by @Drew Cunningham doesn't have this issue, and the circle is always a circle.

One last note, when the fps changes, the speed of the animation changes along with it. One possible improvement would be to interpolate the distance based on the amount of time that's elapsed since the last update.

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.