MHB
MHB

Reputation: 1

I want to make a ball animation continoulsy moving from top left to bottom right in python canvas

I am learning Python canvas Tkinter, I am trying to make a ball animation continuously moving from top left corner to bottom right.

The problem is that there is a ball when it leaves the top corner and another in the bottom right.

the ball in the middle is moving from top left to bottom right

Here is the code:

from graphics import Canvas
    
CANVAS_WIDTH = 400
CANVAS_HEIGHT = 400
BALL_SIZE = 50
DELAY =0.001   
def main():
    canvas = Canvas(CANVAS_WIDTH, CANVAS_HEIGHT)
    ball = canvas.create_oval(0,0 ,BALL_SIZE,BALL_SIZE, 'blue')
    change_x = 1
    change_y = 1

   
    while (True):
        left_x = canvas.get_left_x(ball)
        top_y  = canvas.get_top_y(ball)

        
        if left_x < 0 or left_x + BALL_SIZE >= CANVAS_WIDTH:
            ball = canvas.create_oval(change_x,change_y ,BALL_SIZE,BALL_SIZE, 'blue')
           
        if top_y < 0  or top_y + BALL_SIZE >= CANVAS_HEIGHT:

            ball = canvas.create_oval(change_x,change_y ,BALL_SIZE,BALL_SIZE, 'blue')
           
       
        
        canvas.move(ball,change_x, change_y)

        time.sleep(DELAY)
    
   

if __name__ == '__main__':
    main()

I want the ball to move from top left to bottom right without being in the top left or bottom right.

Upvotes: 0

Views: 111

Answers (1)

furas
furas

Reputation: 143206

Don't create new ball but only change values in change_x, change_y to change direction.

But you have to use if with different values

    if left_x < 0 or top_y < 0 :
         change_x = 1
         change_y = 1
    if  left_x + BALL_SIZE >= CANVAS_WIDTH or top_y + BALL_SIZE >= CANVAS_HEIGHT:
         change_x = -1
         change_y = -1

And don't use while-loop and time.sleep() but put code in function e.g. move() and add canvas.after(DELAY, move) in this function this function to inform tkinter that it has to execute it again after DELAY milliseconds.

(after needs milliseconds instead of seconds - 0.001s = 1ms)

(usually we put after() at the end but it can be in any place because it doesn't block code and it doesn't exit function - it only inform tkinter when it has to run this function again)

def move():
    # ... code ... 

    # run `move()` after `DELAY` milliseconds
    canvas.after(DELAY, move) 

And it needs to run mainloop() to keep running all code. mainloop checks if there are new mouse/key event, redraw elements in windows, and it executes move after DELAY milliseconds.


Full working code:

from graphics import Canvas
    
CANVAS_WIDTH = 400
CANVAS_HEIGHT = 400
BALL_SIZE = 50
DELAY = 1  # milliseconds instead of seconds

def move():
    global change_x
    global change_y
    
    left_x = canvas.get_left_x(ball)
    top_y  = canvas.get_top_y(ball)
    
    if left_x < 0 or top_y < 0 :
         change_x = 1
         change_y = 1
    if  left_x + BALL_SIZE >= CANVAS_WIDTH or top_y + BALL_SIZE >= CANVAS_HEIGHT:
         change_x = -1
         change_y = -1
    
    canvas.move(ball,change_x, change_y)

    # run `move()` after `DELAY` milliseconds
    canvas.after(DELAY, move)  # it needs milliseconds instead of seconds
    
def main():
    global change_x
    global change_y
    global canvas
    global ball
    
    change_x = 1
    change_y = 1
    
    canvas = Canvas(CANVAS_WIDTH, CANVAS_HEIGHT)
    ball = canvas.create_oval(0, 0, BALL_SIZE, BALL_SIZE, 'blue')
   
    move()    
   
    canvas.mainloop() # keep running application (check mouse/key events and redraw elements in window

if __name__ == '__main__':
    main()

Version which uses wider canvas and ball changes direction in more interesting way.
And ball changes color when it touches border.
And ball stops for 500ms when it touches border.

from graphics import Canvas
    
CANVAS_WIDTH = 1200
CANVAS_HEIGHT = 400
BALL_SIZE = 50
DELAY = 1  # milliseconds instead of seconds
LONGER_DELAY = 500

def move():
    global change_x
    global change_y

    delay = DELAY
    
    left_x = canvas.get_left_x(ball)
    top_y  = canvas.get_top_y(ball)
    
    # left - right
    if left_x < 0:
        change_x = -change_x
        canvas.set_fill_color(ball, 'blue')
        delay = LONGER_DELAY
    elif left_x + BALL_SIZE >= CANVAS_WIDTH: 
        change_x = -change_x
        canvas.set_fill_color(ball, 'red')
        delay = LONGER_DELAY
        
    # top - bottom        
    if top_y < 0:         
        change_y = -change_y
        canvas.set_outline_color(ball, 'green')
        delay = LONGER_DELAY
    elif top_y + BALL_SIZE >= CANVAS_HEIGHT:
        change_y = -change_y
        canvas.set_outline_color(ball, 'yellow')
        delay = LONGER_DELAY
    
    canvas.move(ball, change_x, change_y)

    # run `move()` after `DELAY` milliseconds
    canvas.after(delay, move)  # it needs milliseconds instead of seconds
    
def main():
    global change_x
    global change_y
    global canvas
    global ball
    
    change_x = 1
    change_y = 1
        
    canvas = Canvas(CANVAS_WIDTH, CANVAS_HEIGHT)
    ball = canvas.create_oval(0, 0, BALL_SIZE, BALL_SIZE, 'blue', 'black', 15)
   
    move()    
    #canvas.after(DELAY, move)
   
    canvas.mainloop()

if __name__ == '__main__':
    main()

And last idea - many balls with random speeds

from graphics import Canvas
import random
    
CANVAS_WIDTH = 1200
CANVAS_HEIGHT = 400
BALL_SIZE = 50
DELAY = 1  # milliseconds instead of seconds

def move():
    
    for item in all_items:
        left_x = canvas.get_left_x(item['id'])
        top_y  = canvas.get_top_y(item['id'])

        # left - right        
        if left_x < 0:
            item['change_x'] = -item['change_x']
            canvas.set_fill_color(item['id'], 'blue')
        elif left_x + BALL_SIZE >= CANVAS_WIDTH: 
            item['change_x'] = -item['change_x']
            canvas.set_fill_color(item['id'], 'red')
            
        # top - bottom
        if top_y < 0:         
            item['change_y'] = -item['change_y']
        elif top_y + BALL_SIZE >= CANVAS_HEIGHT:
            item['change_y'] = -item['change_y']
        
        canvas.move(item['id'], item['change_x'], item['change_y'])

    # run `move()` after `DELAY` milliseconds
    canvas.after(DELAY, move)  # it needs milliseconds instead of seconds
    
def main():
    global canvas
    global all_items
    
    all_items = []
    
    canvas = Canvas(CANVAS_WIDTH, CANVAS_HEIGHT)
    for x in range(0, 1200, 200):
        ball = canvas.create_oval(x, 0, x+BALL_SIZE, BALL_SIZE, 'blue')
        item = {
            'id': ball, 
            'change_x': random.randint(1, 3), 
            'change_y': random.randint(1, 3),
        }
        all_items.append(item)
        
    move()    
    #canvas.after(DELAY, move)
   
    canvas.mainloop()

if __name__ == '__main__':
    main()

Upvotes: 0

Related Questions