I have created a program that made a ball move around a canvas and bounce of the edge of the canvas. This worked very well so I tried to add another ball but instead of creating 2 balls it created 4, 2 of the balls move and the other two stay still.
This is the code:
#imports
from tkinter import *
import random
from random import randint
#Creating random x and y for the balls to move along
x = random.randint(1,10)
y = random.randint(1,10)
print(x, " Ball 1y")
print(y, " Ball 1x")
x0 = random.randint(1,10)
y0 = random.randint(1,10)
print(y0," Ball2y")
print(x0, " Ball2x")
class Ball:
#creates balls
def __init__(self, canvas, x1,y1,x2,y2):
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
self.canvas = canvas
self.ball = canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill="red")
self.ball2 = canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill="red")
def move_ball(self, x, y):
#function to move ball
coords = canvas.coords(self.ball)
#Because coord is stored in a list I need to call which coord is bigger than the edge of the canvas
if coords[0] >= 280:
#multiplying by a negative number makes the ball "bounce"
x *= -1
if coords[0] <= 0:
x *= -1
if coords[3] <= 20:
y *= -1
if coords[3] >= 300:
y *= -1
print(coords, " Ball1")
#makes ball move
self.canvas.move(self.ball, x, y)
self.canvas.after(50, self.move_ball, x, y)
def move_ball2(self, x0, y0):
#same as previous different variables
coords2 = canvas.coords(self.ball2)
if coords2[0] >= 280:
x0 *= -1
if coords2[0] <= 0:
x0 *= -1
if coords2[3] <= 20:
y0 *= -1
if coords2[3] >= 300:
y0 *= -1
print(coords2, " Ball2")
self.canvas.move(self.ball2, x0, y0)
self.canvas.after(50, self.move_ball2, x0, y0)
#changes window titles etc.
root = Tk()
root.title("Balls")
root.resizable(False, False)
canvas = Canvas(root, width = 300, height = 300)
canvas.pack()
#creates ball with dimensions of the ball
ball1 = Ball(canvas, 10, 10, 30, 30)
ball2 = Ball(canvas, 60, 60, 80, 80)
#calls move ball function
ball1.move_ball(x, y)
ball2.move_ball2(x0,y0)
root.mainloop()
The problem is that you changed the class Ball in addition to adding a second ball. The idea of object oriented programming (i.e. in simple words classes in python) is to create a class, here Ball, that defines how ONE generic ball works. You can from this class create as many objects (here ball1, ball2, etc.) as you want.
In your code you just have to
remove the line
self.ball2 = canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill="red")
remove the complete move_ball2 function
Change
ball2.move_ball2(x0,y0)
to
ball2.move_ball(x0,y0)
and it will work as expected.
You are initializing two balls in your __init()__ method.
self.ball = canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill="red")
self.ball2 = canvas.create_oval(self.x1, self.y1, self.x2, self.y2,fill="red")
When the object is created for the class two balls instead of one. So changing the init method in the class should fix it. You need to create two different objects for two different balls, rather than two different variables in the class itself.
class Ball:
#creates balls
def __init__(self, canvas, x1,y1,x2,y2):
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
self.canvas = canvas
self.ball = canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill="red")
Also, you have two different methods for moving balls 1 and 2. One method should work for both balls.
ball1 = Ball(canvas, 10, 10, 30, 30)
ball2 = Ball(canvas, 60, 60, 80, 80)
When the two statements are executed they return objects of the class Ball to the variables ball1 and ball2. These objects are independent of each other and the move_ball method would be called on them individually without affecting each other. Creating two functions for two different variables defeats the purpose of creating a class.
You might want to read a little more on classes and objects from here and here. There are video tutorials on classes and objects and how they're implemented on tkinter.
Related
I am trying to create a program that draws a rectangle in the center of the canvas. The rectangle is supposed to get wider when the right arrow key is pressed, and narrower when the left arrow key is pressed. I seem to have the rectangle, but there is no movement.
So far, the code I have is:
from tkinter import *
root = Tk()
canvas = Canvas(root, width=400, height=300, bg="#000000")
canvas.pack()
x1 = 150
y1 = 100
x2 = 250
y2 = 200
class ResizeRect:
def __init__(self, x1, y1, x2, y2):
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
self.rect = canvas.create_rectangle(0,0,1,1)
def draw(self):
canvas.delete(self.rect)
self.rect = canvas.create_rectangle(x1, y1, x2, y2,
outline="#00B000", width=2)
def narrower(self):
self.x1 = self.x1 + 5
self.x2 = self.x2 - 5
def wider(self):
self.x1 = self.x1 - 5
self.x2 = self.x2 + 5
r = ResizeRect(150, 100, 250, 200)
r.draw()
def left():
r.narrower()
def right():
r.wider()
canvas.bind_all('<KeyPress-Left>', left)
canvas.bind_all('<KeyPress-Right>', right)
WHen I run this code, a rectangle appears but does not move when I press the arrow keys. How can I fix this without altering my original code too much?
You have two problems. First, left and right will automatically be passed an object representing the event. You need to make sure that these functions accept this parameter even if you don't use it.
def left(event):
r.narrower()
def right(event):
r.wider()
Second, simply setting the coordinate won't cause the rectangle to move. You must configure the rectangle with new coordinates using the coords method if you want the coordinates of the actual object to change.
def narrower(self):
self.x1 = self.x1 + 5
self.x2 = self.x2 - 5
canvas.coords(self.rect, self.x1, self.y1, self.x2, self.y2)
def wider(self):
self.x1 = self.x1 - 5
self.x2 = self.x2 + 5
canvas.coords(self.rect, self.x1, self.y1, self.x2, self.y2)
I have to build a simple program like bouncing ball simulator with oop in python and I have built a function movement to move the balls but I have one more requirement is ball collision together, my idea is check the position between the balls but I don't know how, please help me, thanks
And here is my code:
from tkinter import *
import random
root = Tk()
root.title('Bouncing Ball Simulator')
root.resizable(False, False)
WIDTH = 800
HEIGHT = 600
canvas = Canvas(root, width=WIDTH, height=HEIGHT, bg='black')
colors = ['#82ffec', 'red', '#82ffda', '#fa867f', '#ce5fed', '#ff73a4', 'pink', '#ff9912', '#FFB90F', '#BF3EFF',
'#FFD700', '#00FF00']
gravity = 0.5
def create_circle(x, y, r, canvas_name):
x0 = x - r
y0 = y - r
x1 = x + r
y1 = y + r
return canvas_name.create_oval(x0, y0, x1, y1, outline='#82ffda', fill=random.choice(colors))
class Ball:
def __init__(self, speedx, speedy, radius):
top_left_corner = random.randint(20, 100)
bottom_right_corner = random.randint(5, 70)
self.radius = radius
self.speedx = speedx
self.speedy = speedy
self.ball = create_circle(top_left_corner, bottom_right_corner, radius, canvas)
self.movement()
def movement(self):
self.speedy += gravity
canvas.move(self.ball, self.speedx, self.speedy)
pos = canvas.coords(self.ball)
if WIDTH <= pos[2] or 0 >= pos[0]:
self.speedx *= -1
if 0 >= pos[1] or HEIGHT <= pos[3]:
self.speedy *= -1
canvas.after(30, self.movement)
ball_list = [Ball(random.randint(2, 9), random.randint(5, 10), random.randint(10, 20)) for _ in range(50)]
canvas.pack()
root.mainloop()
I think my idea was right, but I don't know how to calculate distance between two objects (Ball) and how can I compare that with two sum of radius ? Sorry but this is the first time I learn Python and Tkinter lib
Thanks for reading and more thanks for help :)
This is a very basic program with which I want to make two moving balls, but only one of them actually moves.
I have tried some variations as well but can't get the second ball moving; another related question - some people use the move(object) method to achieve this, while others do a delete(object) and then redraw it. Which one should I use and why?
This is my code that is only animating/moving one ball:
from Tkinter import *
class Ball:
def __init__(self, canvas, x1, y1, x2, y2):
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
self.canvas = canvas
self.ball = canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill="red")
def move_ball(self):
while True:
self.canvas.move(self.ball, 2, 1)
self.canvas.after(20)
self.canvas.update()
# initialize root Window and canvas
root = Tk()
root.title("Balls")
root.resizable(False,False)
canvas = Canvas(root, width = 300, height = 300)
canvas.pack()
# create two ball objects and animate them
ball1 = Ball(canvas, 10, 10, 30, 30)
ball2 = Ball(canvas, 60, 60, 80, 80)
ball1.move_ball()
ball2.move_ball()
root.mainloop()
You should never put an infinite loop inside a GUI program -- there's already an infinite loop running. If you want your balls to move independently, simply take out the loop and have the move_ball method put a new call to itself on the event loop. With that, your balls will continue to move forever (which means you should put some sort of check in there to prevent that from happening)
I've modified your program slightly by removing the infinite loop, slowing down the animation a bit, and also using random values for the direction they move. All of that changes are inside the move_ball method.
from Tkinter import *
from random import randint
class Ball:
def __init__(self, canvas, x1, y1, x2, y2):
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
self.canvas = canvas
self.ball = canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill="red")
def move_ball(self):
deltax = randint(0,5)
deltay = randint(0,5)
self.canvas.move(self.ball, deltax, deltay)
self.canvas.after(50, self.move_ball)
# initialize root Window and canvas
root = Tk()
root.title("Balls")
root.resizable(False,False)
canvas = Canvas(root, width = 300, height = 300)
canvas.pack()
# create two ball objects and animate them
ball1 = Ball(canvas, 10, 10, 30, 30)
ball2 = Ball(canvas, 60, 60, 80, 80)
ball1.move_ball()
ball2.move_ball()
root.mainloop()
This function seems to be the culprit
def move_ball(self):
while True:
self.canvas.move(self.ball, 2, 1)
self.canvas.after(20)
self.canvas.update()
You deliberately put yourself in an infinite loop when you call it.
ball1.move_ball() # gets called, enters infinite loop
ball2.move_ball() # never gets called, because code is stuck one line above
Its only moving one because the program reads only one variable at a time. If you set the program to read when the ball gets to a certain spot, say the end of the canvas, you could then code the program to read the next line and trigger the second ball to move. But, this will only move one at a time.
Your program is literally stuck on the line:
ball1.move_ball()
And it will never get to line:
ball2.move_ball()
Because there isn't a limit to where the loop should end.
Otherwise, the answer by "sundar nataraj" will do it.
try
instead of self.canvas.move(self.ball, 2, 1) use
self.canvas.move(ALL, 2, 1)
All this used to move all the objects in canvas
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()
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:
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()
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: