Bouncing ball game tkinter canvas - python

I write a game in python in which the goal is to bounce the ball off the platform.
Everything works pretty well, but the platform's movement is not that smooth. Could you help me make the platform movement more smooth? If the code isn't too clear, I'm sorry, but I'm new in python
import tkinter as tk
import random
root = tk.Tk()
width = 900
height = 500
canvas = tk.Canvas(root, bg='white', width=width, height=height)
canvas.pack()
x = random.randrange(700)
ball = canvas.create_oval(x+10, 10, x+50, 50, fill='green')
platform_y = height - 20
platform = canvas.create_rectangle(width//2-50, platform_y, width//2+50, platform_y+10, fill='black')
xspeed = 2
yspeed = 2
skore = 0
body = 0
def move_ball():
global xspeed
global yspeed
x1, y1, x2, y2 = canvas.coords(ball)
if x1 <= 0 or x2 >= width:
xspeed = -xspeed
if y1 <= 0:
yspeed = 10
elif y2 == platform_y:
cx = (x1 + x2) // 2
px1, _, px2, _ = canvas.coords(platform)
if px1 <= cx <= px2:
yspeed = -10
else:
canvas.create_text(width//2, height//2, text='Game Over', font=('Arial Bold', 32), fill='red')
return
canvas.move(ball, xspeed, yspeed)
canvas.after(20, move_ball)
def board_right(event):
x1, y1, x2, y2 = canvas.coords(platform)
if x2 < width:
dx = min(width-x2, 10)
canvas.move(platform, dx, 0)
def board_left(event):
x1, y1, x2, y2 = canvas.coords(platform)
if x1 > 0:
dx = min(x1, 10)
canvas.move(platform, -dx, 0)
canvas.bind_all('<Right>', board_right)
canvas.bind_all('<Left>', board_left)
move_ball()
root.mainloop()

The problem is that the speed of the platform is dependent on the auto-repeat speed of your keyboard.
Instead of moving once for each <Right> or <Left> event, use a key press to start the platform moving in the desired direction and a key release to stop the platform moving. Then, use after to repeatedly move the platform in the given direction.
Example:
after_id = None
def platform_move(direction):
"""
direction should be -1 to move left, +1 to move right,
or 0 to stop moving
"""
global after_id
speed = 10
if direction == 0:
canvas.after_cancel(after_id)
after_id = None
else:
canvas.move(platform, direction*speed, 0)
after_id = canvas.after(5, platform_move, direction)
canvas.bind_all("<KeyPress-Right>", lambda event: platform_move(1))
canvas.bind_all("<KeyRelease-Right>", lambda event: platform_move(0))
canvas.bind_all("<KeyPress-Left>", lambda event: platform_move(-1))
canvas.bind_all("<KeyRelease-Left>", lambda event: platform_move(0))
The above code doesn't handle the case where you might press both keys at the same time, but that can be handled with a little additional logic. The point is to show how you can use the keys to start and stop an animation.

Related

How can I check two circle collision in my tkinter code?

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 :)

How to move objects in loops?

I want to move the pipes and the ground of my "Flappy Bird" in a loop but it doesn't work. What do I have to do?
I've tried to move the pipes and the ground with an "if" but it doesn't work.
I expect the pipes and the ground to move in a loop.
def deplacement():
global tuyx,tuyx2,h,H,oisx,oisy,solx,sol2x
x0, y0, x1, y1 = canvas.bbox(image_oiseau)
if y1 < 510:
canvas.move(image_oiseau, 0, DY)
canvas.coords(image_sol,solx,512)
if solx >= -144:
solx=solx-5
else:
solx=144
canvas.coords(image_sol2,sol2x,512)
if sol2x >= -144:
sol2x=sol2x-5
else:
sol2x=432
canvas.coords(image_tuyau_haut,tuyx,h)
canvas.coords(image_tuyau_bas,tuyx,h-241)
h = randint(128,385)
if tuyx>=-28:
tuyx=tuyx-5
else:
tuyx=316
canvas.coords(image_tuyau_haut2,tuyx2,H)
canvas.coords(image_tuyau_bas2,tuyx2,H-241)
H = randint(128,385)
if tuyx2>=-28:
tuyx2=tuyx-5
else:
tuyx2=488
canvas.after(40,deplacement)
You can use the canvas.move method to change the position of a canvas item by dx, dy; With the help of after, this move can be called repeatedly, creating a continuous movement.
Here is an example where the images you did not provide were replaced with a canvas item, but the principle for moving objects on the canvas remains the same:
import random
import tkinter as tk
WIDTH, HEIGHT = 500, 500
def create_pipes():
pipes = []
for x in range(0, WIDTH, 40):
y1 = random.randrange(50, HEIGHT - 50)
y0 = y1 + 50
pipes.append(canvas.create_line(x, 0, x, y1))
pipes.append(canvas.create_line(x, y0, x, HEIGHT))
return pipes
def move_pipes():
for pipe in pipes:
canvas.move(pipe, -2, 0)
x, y0, _, y1 = canvas.coords(pipe)
if x < 0: # reset pipe to the right of the canvas
canvas.coords(pipe, WIDTH+20, y0, WIDTH+20, y1)
root.after(40, move_pipes)
root = tk.Tk()
tk.Button(root, text='start', command=move_pipes).pack()
canvas = tk.Canvas(root, width=WIDTH, height=HEIGHT, bg="cyan")
canvas.pack()
pipes = create_pipes()
root.mainloop()

How to display and update a score on screen?

My question is about displaying and updating text, in order to display the score on screen.
I would like to create a score like the real game that would appear on the screen. But after researching Google, I have not found anyone wishing to increase a score on the screen ...
Indeed, I would like the score to increase each time the bird passes between the pipes and therefore whenever the pipes have an X of 67 pixels. So does anyone know how to do this?
from tkinter import *
import random
from random import randint
def sauter(event):
canvas.move(image_oiseau, 0, -10*DY)
def deplacement():
global tuyx,tuyx2,h,H,oisx,oisy,solx,sol2x
x0, y0, x1, y1 = canvas.bbox(image_oiseau)
if y1 < 416:
canvas.move(image_oiseau, 0, DY)
canvas.coords(image_sol,solx,512)
if solx >= -144:
solx=solx-5
else:
solx=144
canvas.coords(image_sol2,sol2x,512)
if sol2x >= 144:
sol2x=sol2x-5
else:
sol2x=432
canvas.coords(image_tuyau_haut,tuyx,h)
canvas.coords(image_tuyau_bas,tuyx,h-241)
if tuyx>=-28:
tuyx=tuyx-5
else:
tuyx=316
h=randint(256,505)
canvas.coords(image_tuyau_haut2,tuyx2,H)
canvas.coords(image_tuyau_bas2,tuyx2,H-241)
if tuyx2>=-28:
tuyx2=tuyx2-5
else:
tuyx2=316
H=randint(256,505)
canvas.after(40,deplacement)
LARGEUR = 286
HAUTEUR = 510
DY = 5
tuyx=316
tuyx2=488
h=randint(256,505)
H=randint(256,505)
oisx=67
oisy=244
solx=144
sol2x=432
fenetre = Tk()
canvas = Canvas(fenetre, width=LARGEUR, height=HAUTEUR)
fond = PhotoImage(file="background-day.png")
fond2 = PhotoImage(file="background-night.png")
fond=[fond,fond2]
F= random.choice(fond)
canvas.create_image(144,256, anchor=CENTER,image=F)
tuyau_haut = PhotoImage(file="tuyau_vers_le_haut.png")
image_tuyau_haut = canvas.create_image(tuyx,h,anchor=CENTER,image=tuyau_haut)
image_tuyau_haut2 = canvas.create_image(tuyx2,H,anchor=CENTER,image=tuyau_haut)
tuyau_bas = PhotoImage(file="tuyau_vers_le_bas.png")
image_tuyau_bas = canvas.create_image(tuyx,h,anchor=CENTER,image=tuyau_bas)
image_tuyau_bas2 = canvas.create_image(tuyx2,H,anchor=CENTER,image=tuyau_bas)
sol = PhotoImage(file="sol-day.png")
image_sol = canvas.create_image(144,512, anchor=S,image=sol)
image_sol2 = canvas.create_image(432,512, anchor=S,image=sol)
oiseau = PhotoImage(file="yellowbird-midflap.png")
oiseau2 = PhotoImage(file="bluebird-midflap.png")
oiseau3 = PhotoImage(file="redbird-midflap.png")
oiseau=[oiseau,oiseau2,oiseau3]
O=random.choice(oiseau)
image_oiseau=canvas.create_image(oisx,oisy, anchor=W,image=O)
deplacement()
canvas.pack()
canvas.focus_set()
canvas.bind("<space>",sauter)
fenetre.mainloop()
Could someone explain the problem to me because I thought it would be easy :(
Here are the pictures of the game :)
Here are the pictures of the game
Here is one approach to display the scores: It uses a tk.Label, that is updated at the same time the score increases.
The trigger that increases the score is currently a random call to on_change; you can modify this to be a test if a pipe x coordinates becomes lower than the bird x coordinates (the bird successfully crossed the obstacle)
You can, if you want relocate the score label on the canvas.
import random
import tkinter as tk
WIDTH, HEIGHT = 500, 500
def create_pipes():
pipes = []
for x in range(0, WIDTH, 40):
y1 = random.randrange(50, HEIGHT - 50)
y0 = y1 + 50
pipes.append(canvas.create_line(x, 0, x, y1))
pipes.append(canvas.create_line(x, y0, x, HEIGHT))
return pipes
def move_pipes():
for pipe in pipes:
canvas.move(pipe, -2, 0)
x, y0, _, y1 = canvas.coords(pipe)
if x < 0:
canvas.coords(pipe, WIDTH+20, y0, WIDTH+20, y1)
if random.randrange(0, 20) == 10:
on_change()
root.after(40, move_pipes)
def on_change():
global score
score += 1
score_variable.set(f'score: {score}')
root = tk.Tk()
tk.Button(root, text='start', command=move_pipes).pack()
score = 0
score_variable = tk.StringVar(root, f'score: {score}')
score_lbl = tk.Label(root, textvariable=score_variable)
score_lbl.pack()
canvas = tk.Canvas(root, width=WIDTH, height=HEIGHT, bg="cyan")
canvas.pack()
pipes = create_pipes()
root.mainloop()

Tkinter Canvas game needs a slight rework

Here's the Code firstly - I'll get into the problems after. Although I'm sure you can spot plenty without needing to scroll to far down.
from tkinter import *
import random
import math
import time
test = random.randint(10,40)
print(test)
class Game():
global x0,y0,x1,y1,Game,debug, Player, mousex0, mousey0, mousex1,
mousey1, moveTowardMouse, rayCast, speed, frayCast, fx0, fy0,
fx1, fy1, Food
def move(event):
global x0,y0,x1,y1,mouseX0,mouseY0,mouseX1,mouseY1,fx0, fy0,
fx1, fy1,Food
mouseX0 = event.x - 10
mouseY0 = event.y - 10
mouseX1 = event.x + 10
mouseY1 = event.y + 10
Game.coords(rayCast, x0, y0, mouseX1, mouseY1)
if x0 != mouseX0 and x0 < mouseX0:
x0 = x0 + speed
x1 = x1 + speed
Game.coords(Player, x0, y0, x1, y1)
if x0 != mouseX0 and x0 > mouseX0:
x0 = x0 - speed
x1 = x1 - speed
Game.coords(Player, x0, y0, x1, y1)
if y0 != mouseY0 and y0 < mouseY0:
y0 = y0 + speed
y1 = y1 + speed
Game.coords(Player, x0, y0, x1, y1)
if y0 != mouseY0 and y0 > mouseY0:
y0 = y0 - speed
y1 = y1 - speed
Game.coords(Player, x0, y0, x1, y1)
Game.coords(frayCast, x0,y0, fx0,fy0)
if fx0 > x0 and (fx0 - x0) < 20:
fx0 = fx0 + 0.5
fx1 = fx1 + 0.5
Game.coords(Food, fx0,fy0,fx1,fy1)
if fx0 < x0 and (fx0 + x0) < 20:
fx0 = fx0 - 0.5
fx1 = fx1 - 0.5
Game.coords(Food, fx0,fy0,fx1,fy1)
if fy0 > y0 and (fy0 - y0) < 20:
fy0 = fy0 + 0.5
fy1 = fy1 + 0.5
Game.coords(Food, fx0,fy0,fx1,fy1)
if fy0 < y0 and (fy0 - y0) < 20:
fy0 = fy0 - 0.5
fy1 = fy1 - 0.5
Game.coords(Food, fx0,fy0,fx1,fy1)
if fx0 > x0 and (fx0 - x0) < 5:
if fy0 > y0 and (fy0 - y0) <5:
Game.delete(Food)
x0 = x0 - fx1
y0 = y0 - fy1
Game.coords(Player, x0,y0,x1,y1)
fx0 = 20
fy0 = 20
fx1 = test + 20
fy1 = test + 20
x0 = -50
y0 = -50
x1 = 50
y1 = 50
speed = 1
mouseX0 = 0
mouseY0 = 0
mouseX1 = 0
mouseY1 = 0
debug = "DEBUGGED"
module = Tk()
Game = Canvas(module, width=1000, height=1000)
Player = Game.create_oval(x0,y0,x1,y1,fill="blue")
Food = Game.create_oval(fx0, fy0, fx1, fy1, fill="red")
rayCast = Game.create_line(x0,y0,mouseX1,mouseY1)
frayCast = Game.create_line(x0,y0,mouseX1,mouseY1)
module.bind('<Motion>', move)
Game.pack()
module.mainloop()
So I'm having just a slight "oh snap" just a moment ago when I realised that my code was basically useless.
In the game I'm creating, I'm wanting the Player controlled sprite on the canvas to move at a slow speed towards the mouse. I googled how to get the mouse coordinates, it told me that I could use the event to get the coords in a function. However since getting those coords I've slowly put all the major sprite movement calculations in the same function resulting in a functional game... that only does something as long as your moving the mouse.
The idea is that the NPC-sprite is a random size and spawns in a random space on the canvas. It moves in random directions in a slow speed until it is within "20" of the player controlled sprite, in which case it moves (faster) away from the player-controlled sprite.
Aside from the fact that all this only happens when you move the mouse (and that I'm still using raycasting to get a trajectory for the sprites to follow, there are a few more issues I need help with.
Firstly, the random size of the NPC sprite works great. But it spawns exactly the same place every time.
Secondly, the NPC's "avoid the player" code seems to be... less than functional. Basically it works fine, but then it just keeps working even after the player has moved "20" away from the sprite.
Lastly, I'm having an issue with the coords of the sprites themselves. You see the raycasting reveals that the true coords for the sprites are not in the centre of their canvas representation of a circle, but instead in the top left corner of what would be an invisible square around said circle. I need this to be in the centre of the sprite rather than not otherwise the gameplay mechanics become a little bit buggy.
Back to the biggest issue (With the way updates work for sprite coords) I'm fine with setting up like Updates per Tick within the game and run all my calculations every tick, but then I wouldn't know how to get the mouse coords outside of using that event.
Long story short some help would be much appreciated.
Here is your improved code. I explained everything in the comments.
from tkinter import *
import random
import math
import time
class Game():
# This __init__ will run first when you run Game()
# Learn to always set up your programs this way.
def __init__(self):
test = random.randint(10,40)
print(test)
# Put self. before the name of every variable you might want to use outside this __init__ function.
# this way you don't need to define them separately in every function
# You defined these xs and ys by hand so of course it remains in the same position.
# define them randomly and you'll be fine.
self.fx0 = 20
self.fy0 = 20
self.fx1 = test + 20
self.fy1 = test + 20
self.x0 = -50
self.y0 = -50
self.x1 = 50
self.y1 = 50
self.speed = 1
self.mouseX0 = 0
self.mouseY0 = 0
self.mouseX1 = 0
self.mouseY1 = 0
self.debug = "DEBUGGED"
# Added these two variables
self.tick_intervals = 10
self.food_exists = True
self.module = Tk()
#Game is the name of your class don't use it here: (use game instead)
self.game = Canvas(self.module, width=1000, height=1000)
self.Player = self.game.create_oval(self.x0,self.y0,self.x1,self.y1,fill="blue")
self.Food = self.game.create_oval(self.fx0, self.fy0, self.fx1, self.fy1, fill="red")
self.rayCast = self.game.create_line(self.x0,self.y0,self.mouseX1,self.mouseY1)
self.frayCast = self.game.create_line(self.x0,self.y0,self.mouseX1,self.mouseY1)
self.game.pack()
self.move()
self.module.mainloop()
def move(self):
# To get mouse position on your whole screen: .winfo_pointerx() and .winfo_pointery()
# To get position of widget (self.game) on screen .winfo_rootx()
# x and y below are the same as mouse event.x and event.y without the need to bind anything to self.module
x = self.game.winfo_pointerx()-self.game.winfo_rootx()
y = self.game.winfo_pointery()-self.game.winfo_rooty()
# If you have a value you use more than 1 time,
# it's best to define it first then put that here
# instead of writing it out every time.
# this way you can change it very easily
# better define these in __init__ function with self.something = ...
self.mouseX0 = x - 10 # define var for 10
self.mouseY0 = y - 10
self.mouseX1 = x + 10
self.mouseY1 = y + 10
# You should also design a (visible or invisible) wall around the screen
# so your Player and Food can't run off the screen.
# Basically it's numbers and some if statements.
# If you don't put elif here x and y might get resized then resized again.
# but you only need to resize them once a tick.
# You don't need != here. < > are enough.
# Look up += -= *= /= functions.
if self.x0 < self.mouseX0:
self.x0 += self.speed
self.x1 += self.speed
elif self.x0 > self.mouseX0:
self.x0 -= self.speed
self.x1 -= self.speed
if self.y0 < self.mouseY0:
self.y0 += self.speed
self.y1 += self.speed
elif self.y0 > self.mouseY0:
self.y0 -= self.speed
self.y1 -= self.speed
# Need to call these once a tick and not every time you change x or y
self.game.coords(self.rayCast, self.x0, self.y0, self.mouseX1, self.mouseY1)
self.game.coords(self.Player,self.x0,self.y0,self.x1,self.y1)
# After you eat food this shouldn't run any more.
# This is why Player kept getting bigger and bigger
if self.food_exists:
if self.fx0 > self.x0 and (self.fx0 - self.x0) < 20: # define var for 20
self.fx0 += 0.5 # define var for 0.5
self.fx1 += 0.5
elif self.fx0 < self.x0 and (self.fx0 + self.x0) < 20:
self.fx0 -= 0.5
self.fx1 -= 0.5
if self.fy0 > self.y0 and (self.fy0 - self.y0) < 20:
self.fy0 += 0.5
self.fy1 += 0.5
elif self.fy0 < self.y0 and (self.fy0 - self.y0) < 20:
self.fy0 -= 0.5
self.fy1 -= 0.5
if self.fx0 > self.x0 and (self.fx0 - self.x0) < 5: # define var for 5
if self.fy0 > self.y0 and (self.fy0 - self.y0) <5:
self.game.delete(self.Food)
self.x0 -= self.fx1
self.y0 -= self.fy1
self.food_exists = False
self.game.coords(self.Food,self.fx0,self.fy0,self.fx1,self.fy1)
self.game.coords(self.frayCast, self.x0,self.y0, self.fx0,self.fy0)
# This automatically runs self.move after (self.tick_intevals) miliseconds
self.game.after(self.tick_intervals,self.move)
# This IF runs the Game() only if you run the script yourself.
# This way if you imported this script into another program it wouldn't run Game()
# Learn to always use this if for your programs
if __name__=='__main__':
Game()

How do i move an object on the screen using the mousex and mousey coordinates in tkinter

I am trying to move the green object called char relative to the mouse x and mouse y coordinates but I don't know how. Can anyone help me? In case you are wondering i am trying to make a version of single player agario.
from tkinter import *
import random
from random import uniform, randrange
import time
#left,top,right,bottom
tk = Tk()
canvas = Canvas(tk,width=600,height=600)
canvas.pack()
pointcount = 0
char = canvas.create_oval(400,400,440,440,fill="green")
pos1 = canvas.coords(char)
x = canvas.canvasx()
y = canvas.canvasy()
class Ball:#ball characteristics#
def __init__(self,color,size):
self.shape = canvas.create_oval(10,10,50,50,fill=color)
self.xspeed = randrange(-5,7)
self.yspeed = randrange(-5,7)
def move(self):#ball animation#
global pointcount
canvas.move(self.shape,self.xspeed,self.yspeed)
pos = canvas.coords(self.shape)
if pos[0] <= 0 or pos[2] >= 600:#if ball hits the wall#
self.xspeed = -self.xspeed
if pos[1] <= 0 or pos[3] >= 600:
self.yspeed = -self.yspeed
left_var = abs(pos[0] - pos1[0])
top_var = abs(pos[1] - pos1[1])
if left_var == 0 and top_var == 0:
pointcount = pointcount + 1
print(pointcount)
colors = ["red","blue","green","yellow","purple","orange"]
balls = []
for i in range(10):
balls.append(Ball(random.choice(colors),50))
while True:
for ball in balls:
ball.move()
tk.update()
time.sleep(0.01)
To move the green circle with the mouse, you need to bind the position of the circle to mouse motion event. Here is an example where the circle is always centered on the mouse when the mouse is in the canvas:
from tkinter import *
root = Tk()
canvas = Canvas(root)
canvas.pack(fill="both", expand=True)
char = canvas.create_oval(400,400,440,440,fill="green")
def follow_mouse(event):
""" the center of char follows the mouse """
x, y = event.x, event.y
canvas.coords(char, x - 20, y - 20, x + 20, y + 20)
# bind follow_mouse function to mouse motion events
canvas.bind('<Motion>', follow_mouse)
root.mainloop()

Categories