Animating an object to move in a circular path in

2019-07-25 09:32发布

问题:

I am trying to model a simple solar system in Tkinter using circles and moving them around in canvas. However, I am stuck trying to find a way to animate them. I looked around and found the movefunction coupled with after to create an animation loop. I tried fidgeting with the parameters to vary the y offset and create movement in a curved path, but I failed while trying to do this recursively or with a while loop. Here is the code I have so far:

import tkinter

class celestial:
    def __init__(self, x0, y0, x1, y1):
        self.x0 = x0
        self.y0 = y0
        self.x1 = x1
        self.y1 = y1

sol_obj = celestial(200, 250, 250, 200)
sx0 = getattr(sol_obj, 'x0')
sy0 = getattr(sol_obj, 'y0')
sx1 = getattr(sol_obj, 'x1')
sy1 = getattr(sol_obj, 'y1')
coord_sol = sx0, sy0, sx1, sy1

top = tkinter.Tk()

c = tkinter.Canvas(top, bg='black', height=500, width=500)
c.pack()

sol = c.create_oval(coord_sol, fill='black', outline='white')

top.mainloop()

回答1:

Here's something that shows one way to do what you want using the tkinter after method to update both the position of the object and the associated canvas oval object. It uses a generator function to compute coordinates along a circular path representing the orbit of one of the Celestial instances (named planet_obj1).

import math
try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk  # Python 2

DELAY = 100
CIRCULAR_PATH_INCR = 10

sin = lambda degs: math.sin(math.radians(degs))
cos = lambda degs: math.cos(math.radians(degs))

class Celestial(object):
    # Constants
    COS_0, COS_180 = cos(0), cos(180)
    SIN_90, SIN_270 = sin(90), sin(270)

    def __init__(self, x, y, radius):
        self.x, self.y = x, y
        self.radius = radius

    def bounds(self):
        """ Return coords of rectangle surrounding circlular object. """
        return (self.x + self.radius*self.COS_0,   self.y + self.radius*self.SIN_270,
                self.x + self.radius*self.COS_180, self.y + self.radius*self.SIN_90)

def circular_path(x, y, radius, delta_ang, start_ang=0):
    """ Endlessly generate coords of a circular path every delta angle degrees. """
    ang = start_ang % 360
    while True:
        yield x + radius*cos(ang), y + radius*sin(ang)
        ang = (ang+delta_ang) % 360

def update_position(canvas, id, celestial_obj, path_iter):
    celestial_obj.x, celestial_obj.y = next(path_iter)  # iterate path and set new position
    # update the position of the corresponding canvas obj
    x0, y0, x1, y1 = canvas.coords(id)  # coordinates of canvas oval object
    oldx, oldy = (x0+x1) // 2, (y0+y1) // 2  # current center point
    dx, dy = celestial_obj.x - oldx, celestial_obj.y - oldy  # amount of movement
    canvas.move(id, dx, dy)  # move canvas oval object that much
    # repeat after delay
    canvas.after(DELAY, update_position, canvas, id, celestial_obj, path_iter)

top = tk.Tk()
top.title('Circular Path')

canvas = tk.Canvas(top, bg='black', height=500, width=500)
canvas.pack()

sol_obj = Celestial(250, 250, 25)
planet_obj1 = Celestial(250+100, 250, 15)
sol = canvas.create_oval(sol_obj.bounds(), fill='yellow', width=0)
planet1 = canvas.create_oval(planet_obj1.bounds(), fill='blue', width=0)

orbital_radius = math.hypot(sol_obj.x - planet_obj1.x, sol_obj.y - planet_obj1.y)
path_iter = circular_path(sol_obj.x, sol_obj.y, orbital_radius, CIRCULAR_PATH_INCR)
next(path_iter)  # prime generator

top.after(DELAY, update_position, canvas, planet1, planet_obj1, path_iter)
top.mainloop()

Here's what it looks like running: