Tkinter GUI not opening when compiled - python

My python program I created doesn't seem to be opening when I click compile. The reason I am confused is because I have a .mainloop() attached to the class. Through my own testing I have deduced that it has something to do with the while loop in my code. Additionally, not sure if this helps, but when I abort the program the following appears in the console:
File "C:\Users\zach\Anaconda3\lib\tkinter\__init__.py", line 2585, in move
self.tk.call((self._w, 'move') + args)
KeyboardInterrupt
here is my completed code for:
from tkinter import *
class GUI(Frame):
def __init__(self):
Frame.__init__(self)
self.master.title("Lab 8")
self.grid()
canvas_width = 800
canvas_height = 400
self.canvas = Canvas(self, width = canvas_width, height =
canvas_height, bg = "white")
self.canvas.grid()
ball_diameter = 20
top_x = 2
top_y = 2
self.canvas.create_oval(top_x, top_y, top_x + ball_diameter,
top_y + ball_diameter, fill = "black", tags = "ball")
horizontal_direction = "east"
vertical_direction = "south"
dx = 2
dy = 2
while True:
if horizontal_direction == "east":
self.canvas.move("ball", dx, 0) # move ball horizontally dx pixels to the right/east
top_x += dx # dx is 2 because the ball moves 2 pixels horizontally every 15 milliseconds
if top_x >= canvas_width - ball_diameter: # ball has hit east wall
horizontal_direction = "west" # change direction
else: # i.e., horizontal_direction is "west"
self.canvas.move("ball", -dx, 0) # move ball horizontally dx pixels to the left/west
top_x -= dx
if top_x <= 0: # ball has hit west wall
horizontal_direction = "east" # change direction
if vertical_direction == "south":
self.canvas.move("ball", 0, dy)
top_y += dy
if top_y >= canvas_height - ball_diameter:
vertical_direction = "north"
else:
self.canvas.move("ball", 0, -dy)
top_y -= dy
if top_y <= 0 :
vertical_direction = "south"
def main():
GUI().mainloop()
main()
I figured it out it was because I forgot to add the update and after methods to wait for the ball and update its position.

Your issue is that you run an infinite loop in your __init__ function. It will never reach your main loop to run the GUI. You need to let __init__ end and to call your update code, not in a tight loop but at a set time interval.
Break out the code to move the shape into a separate function, minus the infinite loop and call that function at intervals using the after method of tk widgets.
from tkinter import *
class GUI(Frame):
def __init__(self):
Frame.__init__(self)
self.master.title("Lab 8")
self.grid()
self.canvas_width = 800
self.canvas_height = 400
self.canvas = Canvas(self,
width=self.canvas_width,
height=self.canvas_height,
bg="white")
self.canvas.grid()
self.ball_diameter = 20
self.top_x = 2
self.top_y = 2
self.canvas.create_oval(self.top_x,
self.top_y,
self.top_x + self.ball_diameter,
self.top_y + self.ball_diameter,
fill = "black", tags = "ball")
self.horizontal_direction = "east"
self.vertical_direction = "south"
self.dx = 2
self.dy = 2
self.after(10, self.move)
def move(self):
if self.horizontal_direction == "east":
self.canvas.move("ball", self.dx, 0) # move ball horizontally dx pixels to the right/east
self.top_x += self.dx # dx is 2 because the ball moves 2 pixels horizontally every 15 milliseconds
if self.top_x >= self.canvas_width - self.ball_diameter: # ball has hit east wall
self.horizontal_direction = "west" # change direction
else: # i.e., horizontal_direction is "west"
self.canvas.move("ball", -self.dx, 0) # move ball horizontally dx pixels to the left/west
self.top_x -= self.dx
if self.top_x <= 0: # ball has hit west wall
self.horizontal_direction = "east" # change direction
if self.vertical_direction == "south":
self.canvas.move("ball", 0, self.dy)
self.top_y += self.dy
if self.top_y >= self.canvas_height - self.ball_diameter:
self.vertical_direction = "north"
else:
self.canvas.move("ball", 0, -self.dy)
self.top_y -= self.dy
if self.top_y <= 0 :
self.vertical_direction = "south"
self.after(10, self.move)
def main():
GUI().mainloop()
main()
Note that the number of self variables has exploded and become pretty unmanageable IMHO, which might indicate you need to break out some of those values into other classes.
But fundamentally this runs. It ain't pretty, but you can work on that next.

Related

How do I alter my code so that the projectile is able to move? (using python and tkinter)

Seen below is the code i have written for a maze game where the user controls a green square which they move around the maze using wasd, where they also have a projectile they can shoot using M1 and aiming the mouse cursor, However I am currently having issues with the moving of the projectile and I am not sure how i will be able to fix this problem.
import random
import tkinter as tk
import time
# Set the size of the maze
maze_width = 800
maze_height = 500
#Creating the character size
character_size = 20
maze = tk.Tk()
canvas = tk.Canvas(maze, width = maze_width, height = maze_height)
canvas.pack()
# Create the game over screen, making it red with text displaying that the game is over
game_over_screen = canvas.create_rectangle(0, 0, maze_width, maze_height, fill='red')
game_over_text = canvas.create_text(maze_width/2, maze_height/2, text='GAME OVER!', font=('Arial', 30), fill='white')
# Hide the game over screen so that it is only displayed when the player dies
canvas.itemconfig(game_over_screen, state='hidden')
canvas.itemconfig(game_over_text, state='hidden')
# Create the victory screen which will be siaplayed when the user escapes the maze
victory_screen = canvas.create_rectangle(0, 0, maze_width, maze_height, fill='green')
victory_text = canvas.create_text(maze_width/2, maze_height/2, text='YOU ESCAPED THE MAZE!', font=('Arial', 30), fill='white')
canvas.itemconfig(victory_screen, state='hidden')
canvas.itemconfig(victory_text, state='hidden')
# Create a 2D array to represent the whole maze
maze_array = [[0 for x in range(maze_width//20)] for y in range(maze_height//20)]
# Generate the maze, ensuring that every 20 units of the maze, there is a 50% chance of a wall being created, storing the walls as 1s and the 0 representing free space
for x in range(0, maze_width, 20):
for y in range(0, maze_height, 20):
if x == 0 or x == maze_width - 20 or y == 0 or y == maze_height - 20:
canvas.create_rectangle(x, y, x + 20, y + 20, fill='black', tags='wall')
maze_array[y//20][x//20] = 1
elif random.random() > 0.6:
canvas.create_rectangle(x, y, x + 20, y + 20, fill='black', tags='wall')
maze_array[y//20][x//20] = 1
# Making sure the path starts at the entrance point of the maze in the top left corner
x, y = 20, 20
path = [(x, y)]
# Stating the amount of vertical and horizontal units the path should move by
move_across = 37
move_down = 22
# Move the path sideways by the amount of units stated earlier
for i in range(move_across):
x += 20
path.append((x, y))
maze_array[y//20][x//20] = 0
# Move the path down by the amount of units stated earlier
for i in range(move_down):
y += 20
path.append((x, y))
maze_array[y//20][x//20] = 0
# Place squares over the path to simulate creating a path
for point in path:
x, y = point
#Fill the path using the hexadecimal colour value of the navigable part of the maze
canvas.create_rectangle(x, y, x + 20, y + 20, outline='#d9d9d9',fill='#d9d9d9')
# Adding a black square to the top right corner of the maze, so that the path is less obvious
canvas.create_rectangle(maze_width - 40, 20, maze_width - 20, 40, fill='black', tags='wall')
# Create the entrance and exit
# Change coordinates of the entrance and exit so they move one square in the diagonal direction
canvas.create_rectangle(20, 20, 40, 40, fill='blue')
exit_ = canvas.create_rectangle(maze_width - 40, maze_height - 40, maze_width -20, maze_height -20, fill='blue')
entrance = (20, 20) # top-left corner of the maze
exit = (750, 450)
projectile_x, projectile_y = (entrance)
# Algorithm for aiming the projectile with the mouse pointer
def aim_projectile(direction):
global projectile_direction
x,y = direction.x, direction.y
if x > player_x + character_size:
projectile_direction = 'right'
elif x < player_x:
projectile_direction = 'left'
elif y < player_y:
projectile_direction = 'up'
elif y > player_y + character_size:
projectile_direction = 'down'
# Define projectile so it can be called upon in the fire_projectile function
projectile = None
# Algorithm to fire the projectile
def fire_projectile(event):
global projectile
# Make it so the projectile is deleted if the user tries spawning multiple on the screen
if projectile is not None:
canvas.delete(projectile)
x1,y1,x2,y2 = canvas.coords(player)
center_x = (x1+x2) / 2
center_y = (y1 + y2) / 2
global projectile_x, projectile_y
#Shoot the projectile in the direction that has previously been stated in the movement algorithm
if projectile_direction is not None:
projectile = canvas.create_oval(center_x - 5,center_y - 5, center_x + 5,center_y + 5, fill='red')
if projectile_direction == 'up':
canvas.move(projectile, 0, -20)
projectile_y -= 20
elif projectile_direction == 'down':
canvas.move(projectile, 0, 20)
projectile_y += 20
elif projectile_direction == 'left':
canvas.move(projectile, -20, 0)
projectile_x -= 20
elif projectile_direction == 'right':
canvas.move(projectile, 20, 0)
projectile_x += 20
# Move the projectile after 1/4 seconds
maze.after(250, fire_projectile)
# Bind the canvas to the aim_projectile function
canvas.bind("<Motion>", aim_projectile)
# Bind the left mouse button to the fire_projectile function
canvas.bind("<Button-1>",fire_projectile)
# Create the player model which will be spawned at the entrance of the maze
player_x, player_y = 20, 20
player = canvas.create_rectangle(player_x, player_y, player_x+20, player_y+20, fill='green')
def check_enemy_collision():
# get player and enemy coordinates
player_coords = canvas.coords(player)
enemy_coords = canvas.coords(enemy)
# check if player and enemy coordinates align
if (player_coords[0] == enemy_coords[0] and player_coords[2] == enemy_coords[2]) and (player_coords[1] == enemy_coords[1] and player_coords[3] == enemy_coords[3]):
# Display the game over screen
canvas.itemconfigure(game_over_screen, state='normal')
canvas.itemconfigure(game_over_text, state='normal' )
# Ensure that the game over screen and text are displayed over the walls
canvas.tag_raise(game_over_screen)
canvas.tag_raise(game_over_text)
def check_exit_collision():
# Gather the coordinates of the player and of the exit to the maze
player_x1, player_y1, player_x2, player_y2 = canvas.coords(player)
exit_x1, exit_y1, exit_x2, exit_y2 = canvas.coords(exit_)
# Compare these values, and if they all match up, then the victory screen should be displayed
if player_x1 >= exit_x1 and player_x2 <= exit_x2 and player_y1 >= exit_y1 and player_y2 <= exit_y2:
# Display the victory screen
canvas.itemconfig(victory_screen, state='normal')
canvas.itemconfig(victory_text, state='normal')
#Ensure the text will be displayed over the walls
canvas.tag_raise(victory_screen)
canvas.tag_raise(victory_text)
# Delete the player once they reach the exit
canvas.delete(player)
# Algorithm for moving the player as well as adding collision to the walls of the maze
def move_player(event):
# Calls upon the previously stated x and y values of the player so they can be modified
global player_x, player_y
x1, y1, x2, y2 = canvas.coords(player)
new_x, new_y = player_x, player_y
if event.char == 'w':
new_y -= 20
elif event.char == 's':
new_y += 20
elif event.char == 'a':
new_x -= 20
elif event.char == 'd':
new_x += 20
x1,y1,x2,y2 = canvas.coords(player)
player_x, player_y = int((x1+x2)/2), int((y1+y2)/2)
# Convert new_x and new_y to indexes and store them as integers
new_x_index = int(new_x // 20)
new_y_index = int(new_y // 20)
# Check if the new position would put the player inside any of the walls
if maze_array[new_y_index][new_x_index] == 1:
# If player aligns with the maze walls do not allow them to move
return
# If there is no collision, allow the player to move
canvas.move(player, new_x - player_x, new_y - player_y)
player_x, player_y = new_x, new_y
# Check for collision between the player, enemy and exit every time the player moves
check_enemy_collision()
check_exit_collision()
#bind the 'w','a','s','d' keys to the move_player function
canvas.bind("<KeyPress-w>", move_player)
canvas.bind("<KeyPress-a>", move_player)
canvas.bind("<KeyPress-s>", move_player)
canvas.bind("<KeyPress-d>", move_player)
canvas.focus_set()
# Create the enemy at the exit of the maze
exit = (maze_width-40,maze_height-40)
enemy = canvas.create_rectangle(exit[0], exit[1], exit[0]+20, exit[1]+20, fill='red')
# Function to move the enemy towards the player
def move_enemy():
global player_x, player_y
# Gather the coordinates of the player so the enemy is able to make its way towards the user
x1,y1,x2,y2 = canvas.coords(player)
player_x, player_y = (x1+x2)/2, (y1+y2)/2
global enemy_x, enemy_y
x1,y1,x2,y2 = canvas.coords(enemy)
enemy_x, enemy_y = (x1+x2)/2, (y1+y2)/2
# Finds where the player is and appropriately moves the enemy towards this position
if player_x > enemy_x:
canvas.move(enemy, 20, 0)
elif player_x < enemy_x:
canvas.move(enemy, -20, 0)
if player_y > enemy_y:
canvas.move(enemy, 0, 20)
elif player_y < enemy_y:
canvas.move(enemy, 0, -20)
# Update the new position of the enemy coordinates
x1,y1,x2,y2 = canvas.coords(enemy)
enemy_x, enemy_y = int((x1+x2)/2), int((y1+y2)/2)
# Move the enemy towards the player once every second
maze.after(500, move_enemy)
#Check for collision between the player and the enemy every time the enemy moves
check_enemy_collision()
move_enemy()
# Create the timer text in the top right corner
timer_text = canvas.create_text(maze_width - 20, 20, text='0', font=('Arial', 20), fill='yellow')
def update_timer():
# Increment the timer value
global timer
# Increment the timer by a value of 1
timer += 1
# Update the timer text on the canvas
canvas.itemconfig(timer_text, text=timer)
# Make it so the timer updates after 1 second
canvas.after(1000, update_timer)
# Start the timer and initialise the value as 0, calling upon the funciton to update it
timer = 0
update_timer()
# Initialise the score as 0 and make it so this is displayed in the top left of the screen
score = 0
# Create the text that will be shown at the top of the screen displaying score
score_text = canvas.create_text(80, 20, text='Score: {}'.format(score), font=('Arial', 20), fill='yellow')
def update_score():
# Increase the score by 1
global score
score += 5
# Update the score text on the canvas
canvas.itemconfig(score_text, text='Score: {}'.format(score))
# Schedule the next score update
canvas.after(1000, update_score)
update_score()
# Find the player and exit coordinates and assign them values so the extra points can be assigned
player_x1, player_y1, player_x2, player_y2 = canvas.coords(player)
exit_x1, exit_y1, exit_x2, exit_y2 = canvas.coords(exit_)
# Compare these values, and if they all match up, then the victory screen should be displayed
if player_x1 >= exit_x1 and player_x2 <= exit_x2 and player_y1 >= exit_y1 and player_y2 <= exit_y2:
# Give the user an extra 1000 points if they escape the maze
score += 1000
canvas.itemconfig(score_text, text='Score: {}'.format(score))
maze.mainloop()
Seen below is the code that is problematic, as when I click M1, a projectile is spawned, however it does not move after it has been shot, and the following error message appears:
Traceback (most recent call last):
File "/nix/store/2vm88xw7513h9pyjyafw32cps51b0ia1-python3-3.8.12/lib/python3.8/tkinter/__init__.py", line 1892, in __call__
return self.func(*args)
File "/nix/store/2vm88xw7513h9pyjyafw32cps51b0ia1-python3-3.8.12/lib/python3.8/tkinter/__init__.py", line 814, in callit
func(*args)
TypeError: fire_projectile() missing 1 required positional argument: 'event'
Is there any way I can fix this, as other solutions i have found elsewhere when implemented have not actually been able to properly solve my problem. Thanks to anyone who helps
projectile_x, projectile_y = (entrance)
# Algorithm for aiming the projectile with the mouse pointer
def aim_projectile(direction):
global projectile_direction
x,y = direction.x, direction.y
if x > player_x + character_size:
projectile_direction = 'right'
elif x < player_x:
projectile_direction = 'left'
elif y < player_y:
projectile_direction = 'up'
elif y > player_y + character_size:
projectile_direction = 'down'
# Define projectile so it can be called upon in the fire_projectile function
projectile = None
# Algorithm to fire the projectile
def fire_projectile(event):
global projectile
# Make it so the projectile is deleted if the user tries spawning multiple on the screen
if projectile is not None:
canvas.delete(projectile)
x1,y1,x2,y2 = canvas.coords(player)
center_x = (x1+x2) / 2
center_y = (y1 + y2) / 2
global projectile_x, projectile_y
#Shoot the projectile in the direction that has previously been stated in the movement algorithm
if projectile_direction is not None:
projectile = canvas.create_oval(center_x - 5,center_y - 5, center_x + 5,center_y + 5, fill='red')
if projectile_direction == 'up':
canvas.move(projectile, 0, -20)
projectile_y -= 20
elif projectile_direction == 'down':
canvas.move(projectile, 0, 20)
projectile_y += 20
elif projectile_direction == 'left':
canvas.move(projectile, -20, 0)
projectile_x -= 20
elif projectile_direction == 'right':
canvas.move(projectile, 20, 0)
projectile_x += 20
# Move the projectile after 1/4 seconds
maze.after(250, fire_projectile)
# Bind the canvas to the aim_projectile function
canvas.bind("<Motion>", aim_projectile)
# Bind the left mouse button to the fire_projectile function
canvas.bind("<Button-1>",fire_projectile)

Clarification on Collision Detection and Updating Labels

I am trying to work on a paddle ball game that entails keeping a ball bouncing away from the bottom for as long as possible. In this game, there are five lives, which can get depleted when the ball hits the bottom of the screen, and resets itself once it has reached 0 lives. However, the ball does not bounce off of the paddle as it should, nor does the label for the score update as it ought to. The code for how I am trying to achieve the collision currently is tied to the vertical placement of the ball, the relative section of which is as follows:
if vertical_direction == "south":
top_y += dy
if top_y >= (self.canvas_height - ball_diameter) - 20:
if self.top_paddle <= top_y and self.top_paddle + 80 >= top_y:
top_y = self.canvas_height - ball_diameter
vertical_direction = "north"
elif top_y >= self.canvas_height - ball_diameter:
lives -= 1
if (lives >= 0):
top_x = 2
top_y = 2
self.canvas.delete("ball")
ball = self.canvas.create_oval(top_x, top_y, top_x + ball_diameter,
top_y + ball_diameter, fill = "blue", tags = "ball")
var.set("Lives: " +lives_Label)
else:
lives= 5
top_x = 2
top_y = 2
self.canvas.delete(ball)
ball = self.canvas.create_oval(top_x, top_y, top_x + ball_diameter,
top_y + ball_diameter, fill = "blue", tags = "ball")
I have looked at a bit of code which I found to be most similar to mine, and tried to implement its methods for detecting collision as displayed above. My only guess is there is some obvious logical inconsistency I failed to notice that is obvious to a more experienced coder, and that is the cause of both of my problems.
For reference, here is the complete code thus far:
from tkinter import *
import tkinter.font
class BallBounce (Frame):
def __init__(self):
Frame.__init__(self)
self.master.title("Bouncing Ball")
self.grid()
lives = 5
lives_Label = str(lives)
var = StringVar()
font = tkinter.font.Font(family = "Verdana", size = 20)
self._label = Label(self, font = font, textvariable = var)
var.set("Lives: "+lives_Label)
self._label.grid(row = 0, column = 0)
self.canvas_width = 800
self.canvas_height = 400
self.canvas = Canvas(self, width = self.canvas_width, height = self.canvas_height,
bg = "white")
self.canvas.grid(row = 1, column = 0)
frame = Frame(self)
frame.grid(row = 1, column = 0)
top_x = 0
top_y = 0
ball_diameter = 20
self.canvas.create_oval(top_x, top_y, ball_diameter, ball_diameter, fill = "blue", tags = "ball")
horizontal_direction = "east"
vertical_direction = "south"
self.canvas.create_rectangle (self.canvas_width / 2, self.canvas_height - 20, self.canvas_width / 2 + 80,
self.canvas_height, fill = "black", tags = "paddle")
self.top_paddle = self.canvas_width/2
self.canvas.bind("<Left>", lambda event: self.canvas.move("paddle", -5, 0))
self.canvas.bind("<Right>", lambda event: self.canvas.move("paddle", 5, 0))
self.canvas.focus_set()
dx = 2
dy = 2
while True:
if horizontal_direction == "east":
top_x += dx # dx is 2 because the ball moves 2 pixels horizontally every 15 milliseconds
if top_x >= self.canvas_width - ball_diameter: # ball has hit east wall
top_x = self.canvas_width - ball_diameter
horizontal_direction = "west" # change direction
self.canvas.move("ball", dx, 0) # move ball horizontally dx pixels to the right/east
else: # i.e., horizontal_direction is "west"
top_x -= dx
if top_x <= 0: # ball has hit west wall
top_x = 0 # you may need to adjust this a little
horizontal_direction = "east" # change direction
self.canvas.move("ball", -dx, 0) # move ball horizontally dx pixels to the left/west
if vertical_direction == "south":
top_y += dy
if top_y >= (self.canvas_height - ball_diameter) - 20:
if self.top_paddle <= top_y and self.top_paddle + 80 >= top_y:
top_y = self.canvas_height - ball_diameter
vertical_direction = "north"
elif top_y >= self.canvas_height - ball_diameter:
lives -= 1
if (lives >= 0):
top_x = 2
top_y = 2
self.canvas.delete("ball")
ball = self.canvas.create_oval(top_x, top_y, top_x + ball_diameter,
top_y + ball_diameter, fill = "blue", tags = "ball")
var.set("Lives: " +lives_Label)
else:
lives= 5
top_x = 2
top_y = 2
self.canvas.delete(ball)
ball = self.canvas.create_oval(top_x, top_y, top_x + ball_diameter,
top_y + ball_diameter, fill = "blue", tags = "ball")
self.canvas.move("ball", 0, dy)
else:
top_y -= dy
if top_y <= 0:
top_y = 0
vertical_direction = "south"
self.canvas.move("ball", 0, -dy)
self.canvas.after(15)
self.canvas.update()
def main():
BallBounce().mainloop()
main()

Adding obstacle generation and detection to pong game

This project is being done without pygame or other libraries that aren't built into Python 3. I have already made a pong game with a paddle and a ball moving around the screen. When the ball hits the paddle it bounces off like it normally does in any game like this. I want to generate a grid of rectangle shapes in the top middle part of the frame with the paddle and ball, and make it so when the ball hits a rectangle, the rectangle disappears. What is an effective way to do this and what would it look like, roughly speaking? Here is what I am working with currently:
from tkinter import *
import tkinter.font
import time
class Display(Frame):
def __init__(self):
Frame.__init__(self)
self.master.title("Animation")
self.grid()
horizontal_direction = "east"
vertical_direction = "south"
self.canvas_width = 800
self.canvas_height = 400
self.paddle_x = 20
self.paddle_y = 80
self.left_rect_side = 360 #forposition
self.canvas = Canvas(self, width=self.canvas_width, height=self.canvas_height, bg = "white")
self.canvas.grid(row = 1, column = 0)
self.master.bind('<Left>', lambda event: self.leftKey(self))
self.master.bind('<Right>', lambda event: self.rightKey(self))
self.x = 5
self.y = 5
diameter = 20
self.canvas.create_oval(self.x, self.y, self.x + diameter, self.y + diameter, outline="#000000"
, fill="red", tags="circle")
self.canvas.create_rectangle(self.canvas_width/2 - self.paddle_y/2, self.canvas_height - self.paddle_x,
self.canvas_width/2 + self.paddle_y/2, self.canvas_height,
fill="black", tags="paddle")
fontx = tkinter.font.Font(family = "Verdana", size = 20)
self.lives = 5
self.lifedisplay = Label(self, width = -800, height = -20,
font = fontx, text = "Lives left: " + str(self.lives))
self.lifedisplay.grid(row = 0, column = 0)
mvt = 2
while True:
if self.y + diameter > self.canvas_height:
self.lives -= 1
self.lifedisplay.configure(text = "Lives left: " + str(self.lives))
if self.lives <= 0:
self.canvas.move("circle", -self.x, -self.y)
break
if self.y + diameter >= self.canvas_height - self.paddle_x:
if self.x + diameter > self.left_rect_side and self.x < self.left_rect_side + self.paddle_y:
vertical_direction = "north"
if horizontal_direction == "east":
if self.x + diameter >= self.canvas_width:
horizontal_direction = "west"
else:
self.canvas.move("circle", mvt, 0)
self.x += mvt
else:
if self.x + diameter <= diameter:
horizontal_direction = "east"
else:
self.canvas.move("circle", -mvt, 0)
self.x -= mvt
if vertical_direction == "south":
if self.y + diameter >= self.canvas_height:
vertical_direction = "north"
self.canvas.move("circle", 0, -mvt)
self.y -= mvt
else:
self.canvas.move("circle", 0, mvt)
self.y += mvt
else:
if self.y + diameter <= diameter:
vertical_direction = "south"
else:
self.canvas.move("circle", 0, -mvt)
self.y -= mvt
self.canvas.update()
self.canvas.after(15)
#staticmethod
def leftKey(self):
if self.left_rect_side >= 10:
self.canvas.move("paddle", -5, 0)
self.left_rect_side -= 5
self.canvas.update()
#staticmethod
def rightKey(self):
if self.left_rect_side <= self.canvas_width - self.paddle_y - 5:
self.canvas.move("paddle", 5, 0)
self.left_rect_side += 5
self.canvas.update()
def main():
Display().mainloop()
main()
To do this, you should make a list of a bunch of rectangle objects that have 4 attributes:
x coordinate
y coordinate
width
height
Additionally, you will have to figure out the x/y coordinate for the center of your circle. Then, each frame, have a method that checks for a collision between the circle and each rectangle. Remove the rectangle from this list of rectangles if it is hit by the circle, then only draw the rectangles from the list onto the screen.
Checking for the collision is the hardest part. I would recommend checking out this StackOverflow answer:
Circle-Rectangle collision detection (intersection)
This next article might be a little harder to understand, but check this out too:
https://yal.cc/rectangle-circle-intersection-test/
I'm not going to write the code for you (that's not what SO is for), but I hope this helped. Let me know if you need help understanding.

Sprite moving faster left than right pygame

I think I'm having a rounding problem causing my sprite to move faster/jump farther while moving left.
My sprites update method is calling move, which calls move_single_axis for each axis. Inside this I'm doing some collision detection where I rely on pygame's rect class to both detect the collision, and set the new position.
I think this is the problem but I'm uncertain how to get around the rounding issue because pygame's rect uses integers under the hood.
Here's the update code:
def update(self, dt, game):
self.calc_grav(game, dt)
self.animate(dt, game)
self._old_position = self._position[:]
self.move(dt, game)
self.rect.topleft = self._position
def move(self, dt, game):
# Move each axis separately. Note that this checks for collisions both times.
dx = self.velocity[0]
dy = self.velocity[1]
if dx != 0:
self.move_single_axis(dx, 0, dt)
if dy != 0:
self.move_single_axis(0, dy, dt)
def move_single_axis(self, dx, dy, dt):
#print("hero_destination: ({}, {})".format(dx *dt, dy *dt))
self._position[0] += dx * dt
self._position[1] += dy * dt
#print("Game walls: {}".format(game.walls))
self.rect.topleft = self._position
body_sensor = self.get_body_sensor()
for wall in game.walls:
if body_sensor.colliderect(wall.rect):
if dx > 0: # Moving right; Hit the left side of the wall
#print(" -- Moving right; Hit the left side of the wall")
self.rect.right = wall.rect.left
if dx < 0: # Moving left; Hit the right side of the wall
#print(" -- Moving left; Hit the right side of the wall")
self.rect.left = wall.rect.right - self.COLLISION_BOX_OFFSET
if dy > 0: # Moving down; Hit the top side of the wall
#print(" -- Moving down; Hit the top side of the wall")
self.rect.bottom = wall.rect.top
if dy < 0: # Moving up; Hit the bottom side of the wall
#print(" -- Moving up; Hit the bottom side of the wall")
self.rect.top = wall.rect.bottom
self._position[0] = self.rect.topleft[0]
self._position[1] = self.rect.topleft[1]
Here is the whole source(https://github.com/davidahines/python_sidescroller):
import os.path
import pygame
from pygame.locals import *
from pytmx.util_pygame import load_pygame
import pyscroll
import pyscroll.data
from pyscroll.group import PyscrollGroup
# define configuration variables here
RESOURCES_DIR = 'data'
HERO_JUMP_HEIGHT = 180
HERO_MOVE_SPEED = 200 # pixels per second
GRAVITY = 1000
MAP_FILENAME = 'maps/dungeon_0.tmx'
# simple wrapper to keep the screen resizeable
def init_screen(width, height):
screen = pygame.display.set_mode((width, height), pygame.RESIZABLE)
return screen
# make loading maps a little easier
def get_map(filename):
return os.path.join(RESOURCES_DIR, filename)
# make loading images a little easier
def load_image(filename):
return pygame.image.load(os.path.join(RESOURCES_DIR, filename))
class Hero(pygame.sprite.Sprite):
""" Our Hero
The Hero has three collision rects, one for the whole sprite "rect" and
"old_rect", and another to check collisions with walls, called "feet".
The position list is used because pygame rects are inaccurate for
positioning sprites; because the values they get are 'rounded down'
as integers, the sprite would move faster moving left or up.
Feet is 1/2 as wide as the normal rect, and 8 pixels tall. This size size
allows the top of the sprite to overlap walls. The feet rect is used for
collisions, while the 'rect' rect is used for drawing.
There is also an old_rect that is used to reposition the sprite if it
collides with level walls.
"""
def __init__(self, map_data_object):
pygame.sprite.Sprite.__init__(self)
self.STATE_STANDING = 0
self.STATE_WALKING = 1
self.STATE_JUMPING = 2
self.FRAME_DELAY_STANDING =1
self.FRAME_DELAY_WALKING = 1
self.FRAME_DELAY_JUMPING = 1
self.FACING_RIGHT = 0
self.FACING_LEFT = 1
self.MILLISECONDS_TO_SECONDS = 1000.0
self.COLLISION_BOX_OFFSET = 8
self.time_in_state = 0.0
self.current_walking_frame = 0
self.current_standing_frame = 0
self.current_jumping_frame = 0
self.load_sprites()
self.velocity = [0, 0]
self.state = self.STATE_STANDING
self.facing = self.FACING_RIGHT
self._position = [map_data_object.x, map_data_object.y]
self._old_position = self.position
self.rect = pygame.Rect(8, 0, self.image.get_rect().width - 8, self.image.get_rect().height)
def set_state(self, state):
if self.state != state:
self.state = state
self.time_in_state = 0.0
def load_sprites(self):
self.spritesheet = Spritesheet('data/art/platformer_template_g.png')
standing_images = self.spritesheet.images_at((
pygame.Rect(0, 0, 32, 32),
), colorkey= (0,255,81))
self.standing_images = []
for standing_image in standing_images:
self.standing_images.append(standing_image.convert_alpha())
self.image = self.standing_images[self.current_standing_frame]
#property
def position(self):
return list(self._position)
#position.setter
def position(self, value):
self._position = list(value)
def get_floor_sensor(self):
return pygame.Rect(self.position[0]+self.COLLISION_BOX_OFFSET, self.position[1]+2, self.rect.width -self.COLLISION_BOX_OFFSET, self.rect.height)
def get_ceiling_sensor(self):
return pygame.Rect(self.position[0]+self.COLLISION_BOX_OFFSET, self.position[1]-self.rect.height, self.rect.width, 2)
def get_body_sensor(self):
return pygame.Rect(self.position[0]+self.COLLISION_BOX_OFFSET, self.position[1], self.rect.width -self.COLLISION_BOX_OFFSET, self.rect.height)
def calc_grav(self, game, dt):
""" Calculate effect of gravity. """
floor_sensor = self.get_floor_sensor()
collidelist = floor_sensor.collidelist(game.walls)
hero_is_airborne = collidelist == -1
if hero_is_airborne:
if self.velocity[1] == 0:
self.velocity[1] = GRAVITY * dt
else:
self.velocity[1] += GRAVITY * dt
def update(self, dt, game):
self.calc_grav(game, dt)
self._old_position = self._position[:]
self.move(dt, game)
def move(self, dt, game):
# Move each axis separately. Note that this checks for collisions both times.
dx = self.velocity[0]
dy = self.velocity[1]
if dx != 0:
self.move_single_axis(dx, 0, dt)
if dy != 0:
self.move_single_axis(0, dy, dt)
self.rect.topleft = self._position
def move_single_axis(self, dx, dy, dt):
#print("hero_destination: ({}, {})".format(dx *dt, dy *dt))
self._position[0] += dx * dt
self._position[1] += dy * dt
#print("Game walls: {}".format(game.walls))
self.rect.topleft = self._position
body_sensor = self.get_body_sensor()
for wall in game.walls:
if body_sensor.colliderect(wall.rect):
if dx > 0: # Moving right; Hit the left side of the wall
self.rect.right = wall.rect.left
if dx < 0: # Moving left; Hit the right side of the wall
self.rect.left = wall.rect.right - self.COLLISION_BOX_OFFSET
if dy > 0: # Moving down; Hit the top side of the wall
self.rect.bottom = wall.rect.top
if dy < 0: # Moving up; Hit the bottom side of the wall
self.rect.top = wall.rect.bottom
self._position[0] = self.rect.topleft[0]
self._position[1] = self.rect.topleft[1]
class Wall(pygame.sprite.Sprite):
"""
A sprite extension for all the walls in the game
"""
def __init__(self, map_data_object):
pygame.sprite.Sprite.__init__(self)
self._position = [map_data_object.x, map_data_object.y]
self.rect = pygame.Rect(
map_data_object.x, map_data_object.y,
map_data_object.width, map_data_object.height)
#property
def position(self):
return list(self._position)
#position.setter
def position(self, value):
self._position = list(value)
class Spritesheet(object):
def __init__(self, filename):
try:
self.sheet = pygame.image.load(filename).convert()
except pygame.error:
print ('Unable to load spritesheet image: {}').format(filename)
raise SystemExit
# Load a specific image from a specific rectangle
def image_at(self, rectangle, colorkey = None):
"Loads image from x,y,x+offset,y+offset"
rect = pygame.Rect(rectangle)
image = pygame.Surface(rect.size).convert()
image.blit(self.sheet, (0, 0), rect)
if colorkey is not None:
if colorkey is -1:
colorkey = image.get_at((0,0))
image.set_colorkey(colorkey, pygame.RLEACCEL)
return image
# Load a whole bunch of images and return them as a list
def images_at(self, rects, colorkey = None):
"Loads multiple images, supply a list of coordinates"
return [self.image_at(rect, colorkey) for rect in rects]
class QuestGame(object):
""" This class is a basic game.
It also reads input and moves the Hero around the map.
Finally, it uses a pyscroll group to render the map and Hero.
This class will load data, create a pyscroll group, a hero object.
"""
filename = get_map(MAP_FILENAME)
def __init__(self):
# true while running
self.running = False
self.debug = False
# load data from pytmx
self.tmx_data = load_pygame(self.filename)
# setup level geometry with simple pygame rects, loaded from pytmx
self.walls = list()
self.npcs = list()
for map_object in self.tmx_data.objects:
if map_object.type == "wall":
self.walls.append(Wall(map_object))
elif map_object.type == "guard":
print("npc load failed: reimplement npc")
#self.npcs.append(Npc(map_object))
elif map_object.type == "hero":
self.hero = Hero(map_object)
# create new data source for pyscroll
map_data = pyscroll.data.TiledMapData(self.tmx_data)
# create new renderer (camera)
self.map_layer = pyscroll.BufferedRenderer(map_data, screen.get_size(), clamp_camera=True, tall_sprites=1)
self.map_layer.zoom = 2
self.group = PyscrollGroup(map_layer=self.map_layer, default_layer=3)
# add our hero to the group
self.group.add(self.hero)
def draw(self, surface):
# center the map/screen on our Hero
self.group.center(self.hero.rect.center)
# draw the map and all sprites
self.group.draw(surface)
if(self.debug):
floor_sensor_rect = self.hero.get_floor_sensor()
ox, oy = self.map_layer.get_center_offset()
new_rect = floor_sensor_rect.move(ox * 2, oy * 2)
pygame.draw.rect(surface, (255,0,0), new_rect)
def handle_input(self, dt):
""" Handle pygame input events
"""
poll = pygame.event.poll
event = poll()
while event:
if event.type == QUIT:
self.running = False
break
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
self.running = False
break
# this will be handled if the window is resized
elif event.type == VIDEORESIZE:
init_screen(event.w, event.h)
self.map_layer.set_size((event.w, event.h))
event = poll()
# using get_pressed is slightly less accurate than testing for events
# but is much easier to use.
pressed = pygame.key.get_pressed()
floor_sensor = self.hero.get_floor_sensor()
floor_collidelist = floor_sensor.collidelist(self.walls)
hero_is_airborne = floor_collidelist == -1
ceiling_sensor = self.hero.get_ceiling_sensor()
ceiling_collidelist = ceiling_sensor.collidelist(self.walls)
hero_touches_ceiling = ceiling_collidelist != -1
if pressed[K_l]:
print("airborne: {}".format(hero_is_airborne))
print("hero position: {}, {}".format(self.hero.position[0], self.hero.position[1]))
print("hero_touches_ceiling: {}".format(hero_touches_ceiling))
print("hero_is_airborne: {}".format(hero_is_airborne))
if hero_is_airborne == False:
#JUMP
if pressed[K_SPACE]:
self.hero.set_state(self.hero.STATE_JUMPING)
# stop the player animation
if pressed[K_LEFT] and pressed[K_RIGHT] == False:
# play the jump left animations
self.hero.velocity[0] = -HERO_MOVE_SPEED
elif pressed[K_RIGHT] and pressed[K_LEFT] == False:
self.hero.velocity[0] = HERO_MOVE_SPEED
self.hero.velocity[1]= -HERO_JUMP_HEIGHT
elif pressed[K_LEFT] and pressed[K_RIGHT] == False:
self.hero.set_state(self.hero.STATE_WALKING)
self.hero.velocity[0] = -HERO_MOVE_SPEED
elif pressed[K_RIGHT] and pressed[K_LEFT] == False:
self.hero.set_state(self.hero.STATE_WALKING)
self.hero.velocity[0] = HERO_MOVE_SPEED
else:
self.hero.state = self.hero.STATE_STANDING
self.hero.velocity[0] = 0
def update(self, dt):
""" Tasks that occur over time should be handled here
"""
self.group.update(dt, self)
def run(self):
""" Run the game loop
"""
clock = pygame.time.Clock()
self.running = True
from collections import deque
times = deque(maxlen=30)
try:
while self.running:
dt = clock.tick(60) / 1000.
times.append(clock.get_fps())
self.handle_input(dt)
self.update(dt)
self.draw(screen)
pygame.display.flip()
except KeyboardInterrupt:
self.running = False
if __name__ == "__main__":
pygame.init()
pygame.font.init()
screen = init_screen(800, 600)
pygame.display.set_caption('Test Game.')
try:
game = QuestGame()
game.run()
except:
pygame.quit()
raise
I ripped out everything except for the hero and the QuestGame class and could see the incorrect movement, so the problem was not caused by pyscroll (unless there are more issues).
The reason for the movement problems is that you set the self._position in the update method of the hero to the topleft coords of the rect.
self._position[0] = self.rect.topleft[0]
self._position[1] = self.rect.topleft[1]
pygame.Rects can only store integers and truncate floats that you assign to them, so you shouldn't use them to update the actual position of the hero. Here's a little demonstration:
>>> pos = 10
>>> rect = pygame.Rect(10, 0, 5, 5)
>>> pos -= 1.4 # Move left.
>>> rect.x = pos
>>> rect
<rect(8, 0, 5, 5)> # Truncated the actual position.
>>> pos = rect.x # Pos is now 8 so we moved 2 pixels.
>>> pos += 1.4 # Move right.
>>> rect.x = pos
>>> rect
<rect(9, 0, 5, 5)> # Truncated.
>>> pos = rect.x
>>> pos # Oops, we only moved 1 pixel to the right.
9
The self._position is the exact position and should only be set to one of the rect's coords if the hero collides with a wall or another obstacle (because the rect is used for the collision detection).
Move the two mentioned lines into the if body_sensor.colliderect(wall.rect): clause in the wall collision for loop and it should work correctly.
for wall in game.walls:
if body_sensor.colliderect(wall.rect):
if dx > 0: # Moving right; Hit the left side of the wall
self.rect.right = wall.rect.left
self._position[0] = self.rect.left
if dx < 0: # Moving left; Hit the right side of the wall
self.rect.left = wall.rect.right - self.COLLISION_BOX_OFFSET
self._position[0] = self.rect.left
if dy > 0: # Moving down; Hit the top side of the wall
self.rect.bottom = wall.rect.top
self._position[1] = self.rect.top
if dy < 0: # Moving up; Hit the bottom side of the wall
self.rect.top = wall.rect.bottom
self._position[1] = self.rect.top

Why does the collide happen many times?

I'm using openGL with Pyglet which is a python package. I have to use this language and this package, it is for an assignment. I have a basic Brickbreaker style game that is basically a keep it up game.
I create a ball and a paddle.
I separately create a bounding box class that will be used to create the hit boxes for each object.
class BoundBox:
def __init__ (self, width, height, pos):
self.width = width
self.height = height
self.pos = pos
Then I create the boxes themselves
paddleBox = BoundBox(200, 20, [0,0])
ballBox = BoundBox(40, 40, [236, 236])
In the update function which is running # pyglet.clock.schedule_interval(update,1/100.0) I call the checkcollide() function which checks if there was a collision:
def checkForCollide():
global collides
if overlap(ballBox, paddleBox):
vel = 1.05
ballVel[0] = ballVel[0]*vel #Ball speeds up when collide happens
ballVel[1] = ballVel[1]*vel
ballVel[1] = -ballVel[1] #Change direction on collision
ballPos[1] = -ballPos[1]
collides += 1 #counts how many collides happen
The overlap function is returning a boolean if there's an overlap in hit boxes:
def overlap(box1, box2):
return (box1.pos[0] <= box2.width + box2.pos[0]) and(box1.width + box1.pos[0] >= box2.pos[0]) and(box1.pos[1] <= box2.height + box2.pos[1]) and(box1.height + box1.pos[1] >= box2.pos[1])
pos[0] is the minimum x
width is the maximum x
pos[1] is the minimum y
height is the maximum y
When I run the game and the ball hits the paddle it flickers about 15 times and increments the collides counter every time it detects a hit. Collides then prints in the console. Why does this flicker happen? How do I stop it?
Here is the program's full code (you must have pyglet installed to run it):
import sys, time, math
from pyglet.gl import *
from euclid import *
from pyglet.window import key
from pyglet.clock import *
window = pyglet.window.Window(512,512)
quadric = gluNewQuadric()
ballPos = Vector2(256,256)
ballVel = Vector2(200,145)
x1 = 0
bar = pyglet.graphics.vertex_list(4, ('v2f', [0,0, 0,20, 200,0, 200,20]))
startTime = time.clock()
collides = 0
#pos is minx, miny
class BoundBox:
def __init__ (self, width, height, pos):
self.width = width
self.height = height
self.pos = pos
def overlap(box1, box2):
return (box1.pos[0] <= box2.width + box2.pos[0]) and(box1.width + box1.pos[0] >= box2.pos[0]) and(box1.pos[1] <= box2.height + box2.pos[1]) and(box1.height + box1.pos[1] >= box2.pos[1])
paddleBox = BoundBox(200, 20, [0,0])
ballBox = BoundBox(40, 40, [236, 236])
#window.event
def on_draw():
glClear(GL_COLOR_BUFFER_BIT)
glPushMatrix()
glPushMatrix()
glColor3f(1,1,1)
glTranslatef(x1, 0, 0)
bar.draw(GL_TRIANGLE_STRIP)
glPopMatrix()
glTranslatef(ballPos[0], ballPos[1], 0)
glColor3f(1,0,0)
gluDisk(quadric, 0, 20, 32, 1)
glPopMatrix()
#window.event
def on_key_press(symbol, modifiers):
global x1
dist = 30
if symbol == key.RIGHT:
#print "right"
x1 += dist
elif symbol == key.LEFT:
#print "left"
x1 -= dist
def checkForBounce():
if ballPos[0] > 512.0:
ballVel[0] = -ballVel[0]
ballPos[0] = 512.0 - (ballPos[0] - 512.0)
elif ballPos[0] < 0.0:
ballVel[0] = -ballVel[0]
ballPos[0] = -ballPos[0]
if ballPos[1] > 512.0:
ballVel[1] = -ballVel[1]
ballPos[1] = 512.0 - (ballPos[1] - 512.0)
elif ballPos[1] < -100.0:
gameOver()
def gameOver():
global collides
'''global startTime
elapsed = (time.time() - startTime)
score = elapsed * .000000001
finalscore = '%.1f' % round(score, 1)'''
gostr = "GAME OVER"
print gostr
str = "Your score = "
print str
print collides
pyglet.app.exit()
def checkForCollide():
global collides
if overlap(ballBox, paddleBox):
vel = 1.05
ballVel[0] = ballVel[0]*vel #Ball speeds up when collide happens
ballVel[1] = ballVel[1]*vel
ballVel[1] = -ballVel[1] #Change direction on collision
ballPos[1] = -ballPos[1]
collides += 1 #counts how many collides happen
print collides
#glscale(0.5, 1, 1)
def update(dt):
global ballPos, ballVel, ballBox, x1, paddleBox
ballBox = BoundBox(40, 40, [ballPos[0], ballPos[1]])
paddleBox = BoundBox(200, 20, [x1,0])
#print paddleBox.pos
#print ballBox.pos
ballPos += ballVel * dt
checkForBounce()
checkForCollide()
pyglet.clock.schedule_interval(update,1/100.0)
pyglet.app.run()
I don't think you wanted to invert the position here:
def checkForCollide():
global collides
if overlap(ballBox, paddleBox):
vel = 1.05
ballVel[0] = ballVel[0]*vel #Ball speeds up when collide happens
ballVel[1] = ballVel[1]*vel
ballVel[1] = -ballVel[1] #Change direction on collision
ballPos[1] = -ballPos[1]
collides += 1 #counts how many collides happen
What were you trying to do with this line?
ballPos[1] = -ballPos[1]
I suspect that is why you are flickering.

Categories