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

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)

Related

Random Brick Color -> TypeError: 'pygame.Color' object is not callable

so this is my first time as a registered user here, so please bear with me if I was searching wrong,
but with the tags [python] or [pygame] I could not find an answer to this question of a random color.
So my problem is the following:
The Game
In this game I want the bricks to be random colors. To do so I made a function to generate a random color:
def brick_color():
color_var = random.randint(0,3)
if color_var == 0: #red
brick_color1 = pygame.Color(198,44,58)
elif color_var == 1:#blue
brick_color1 = pygame.Color(1,128,181)
elif color_var == 2:#yellow
brick_color1 = pygame.Color(255,211,92)
elif color_var == 3:#green
brick_color1 = pygame.Color(0,157,103)
return brick_color1
and implemented the function into this lines:
# brick init
brick = pygame.Surface([brick_width, brick_height]),brick_color # surface for a single brick
pygame.draw.rect(brick[0], brick[1], [0, 0, brick_width, brick_height])
bricks = [] # list of *coordinates* of the bricks
# initialize coordinates of bricks
for y in range(num_brick_rows):
brickY = (y * brick_row_height) + brick_offset_y
for x in range(num_bricks_in_row):
brickX = (x * brick_column_width) + brick_offset_x
color_of_brick = brick_color()
bricks.append((brickX, brickY),color_of_brick) # coordinates are in fact tuples (x,y)
But i keep getting this error:
File "C:\_____\unbenannt0.py", line 146, in <module>
color_of_brick = brick_color()
TypeError: 'pygame.Color' object is not callable
And this is the full code.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Jul 2 09:33:50 2020
#author:
"""
# example from the Sloan Kelly book
# very slightly modified
# imports (or makes usable) external packages that may be 3rd party (such as pygame)
import pygame, sys, random
# enable "short-hand" notation for imported stuff
from pygame.locals import QUIT, MOUSEBUTTONUP, MOUSEMOTION, KEYDOWN, K_ESCAPE
from pygame import display
#Functions
def draw_bricks():
for b in bricks:
windowSurfaceObj.blit(brick[0], b) # makes a copy of brick image acording to coordinates, where stuff is drawn!
def draw_bat():
windowSurfaceObj.blit(bat, batRect)
def draw_ball():
windowSurfaceObj.blit(ball, ballRect)
def brick_color():
color_var = random.randint(0,3)
if color_var == 0: #red
brick_color = pygame.Color(198,44,58)
elif color_var == 1:#blue
brick_color = pygame.Color(1,128,181)
elif color_var == 2:#yellow
brick_color = pygame.Color(255,211,92)
elif color_var == 3:#green
brick_color = pygame.Color(0,157,103)
return brick_color
pygame.init()
fpsClock = pygame.time.Clock()
# new and improved:
# create surface object aka. the main window
inf = display.Info()
screen_width = inf.current_w - 200 # make window sizea little bit smaller than actual screen
screen_height = inf.current_h - 200
# windowSurfaceObj = display.set_mode(size=(screen_width, screen_height), flags=pygame.FULLSCREEN) # initialize window
windowSurfaceObj = display.set_mode(size=(screen_width, screen_height)) # initialize window
display.set_caption('New and improved Bricks'); # set window title
# brick layout
brick_width = 50
brick_height = 20
num_bricks_in_row = 7
num_brick_rows = 5
brick_row_height = 2 * brick_height
brick_offset_y = 100
brick_column_width = 2 * brick_width
brick_offset_x = int(screen_width/2 - brick_column_width*num_bricks_in_row/2) # place it in the middle of the screen
brick_color = brick_color()
# ball related stuff
ball_radius = int(screen_height/200)
# more game constants!
fps = 60 # desired frames per second
background_colour = pygame.Color(0, 0, 0) # background is black
# used variables for bat dimensions
bat_width = 100
bat_height = 10
# ball related stuff
ball_start_x = 24 # somehwere near the left of the window
ball_start_y = 200 # initial ball position when new ball is released
ball_speed = int(fps*0.15) # speed of ball in pixel per frame! use fps var. here to make real ball speed independent of frame rate
# bat init
# replace bat with crude hand drawn one
batcolor = pygame.Color(0, 0, 255) # bat color: blue!
bat = pygame.Surface([bat_width, bat_height]) # this Surface is for drawing the bat upon
pygame.draw.rect(bat, batcolor, [0, 0, bat_width, bat_height]) # draw bat. It's a simple rectangle.
bat = bat.convert_alpha() # deal with transparency
# place the bat somewhere near the bottom of the screen/window
player_start_x = 0 # initial position is on left
player_start_y = screen_height - 6 * bat_height # this is used as Y coordinate for bat, near the bottom of the screen
batRect = bat.get_rect() # rectangle around bat, used to move it around later
mousex = player_start_x
mousey = player_start_y # mousex and mousey later used for moving the bat around, not actual mouse coordinates at this point
# ball init
ball_color = pygame.Color(255, 255, 255) # white
ball = pygame.Surface([ball_radius*2, ball_radius*2]) # Surface for drawing the ball upon
pygame.draw.circle(ball, ball_color, (ball_radius, ball_radius), ball_radius) # draw circle on ball surface
ballRect = ball.get_rect() # rectangle around ball, use to move it around later
ballServed = False
bx = ball_start_x # bx is actual ball postion
by = ball_start_y # by is actual (current) ball position
sx = ball_speed # current ball speed in horizontal direction
sy = ball_speed # current ball speed vertical
ballRect.topleft = (bx, by) # move ball rectangle to initial position
# brick init
brick = pygame.Surface([brick_width, brick_height]),brick_color # surface for a single brick
pygame.draw.rect(brick[0], brick[1], [0, 0, brick_width, brick_height])
bricks = [] # list of *coordinates* of the bricks
# initialize coordinates of bricks
for y in range(num_brick_rows):
brickY = (y * brick_row_height) + brick_offset_y
for x in range(num_bricks_in_row):
brickX = (x * brick_column_width) + brick_offset_x
color_of_brick = brick_color()
bricks.append((brickX, brickY),color_of_brick) # coordinates are in fact tuples (x,y)
while True: # main loop, run once per frame (i.e. fps times per second)
windowSurfaceObj.fill(background_colour) # clear the screen
# brick draw
# for b in bricks: # remember: bricks is a list of brick coordinates, not surfaces
# windowSurfaceObj.blit(brick, b) # make copy of brick image and place it on screen, b = brick coordinates
draw_bricks()
# bat and ball draw, rectangles around bat and ball are used for positioning
# windowSurfaceObj.blit(bat, batRect) # copy surface with image of bat to screen
# windowSurfaceObj.blit(ball, ballRect) # same for ball
draw_bat()
draw_ball()
# main event loop
# process user interaction
for event in pygame.event.get():
# quit the game if window is closed or escape key is pressed
if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
pygame.quit()
sys.exit()
elif event.type == MOUSEBUTTONUP and not ballServed:
ballServed = True # start game, when mouse is clicked
elif event.type == MOUSEMOTION: # mouse has moved
mousex, mousey = event.pos # set mouse coordinate variables to actual mouse coordinates
if mousex < screen_width - bat_width:
if mousex < 0: # may occur in full screen / large window mode
batRect.topleft = (0, player_start_y)
else:
batRect.topleft = (mousex, player_start_y)
else:
batRect.topleft = (screen_width - bat_width, player_start_y)
# main game logic
if ballServed: # if game is in progress
ballRect.topleft = (bx, by) # position the ball using its rectangle
bx += sx # sx = speed of the ball in X direction
by += sy # sy = speed of the ball in Y direction
if (by >= screen_height): # ball below bottom of screen
ballServed = False # game not in progess, ball lost!
bx, by = (ball_start_x, ball_start_y) # ball is reset to start position
ballRect.topleft = (bx, by) # move the rectangle around ball to correct position
if by <= 0: # ball hits top
by = 0
sy *= -1 # reflect
if bx <= 0: # ball hits left side of window
bx = 0
sx *= -1 # reflect
if bx >= screen_width - ball_radius*2: # ball hits right side of window
bx = screen_width - ball_radius*2
sx *= -1 # reflection
# collision detection
brickForRemoval = None
for b in bricks: # remember: bricks is list of coordinates of bricks; iterating all bricks and check each one for collision
briX, briY = b # tuple unwrapping: x and y coordinates of top left of brick
if bx + ball_radius*2 >= briX and bx <= briX + brick_width: # is x coordinate of ball inside brick (or touching brick)
if (by + ball_radius*2 >= briY and by <= briY + brick_height): # same for y coordinate
brickForRemoval = b # brick was hit and is scheduled for removal
if bx <= briX + ball_radius*2: # ball hit brick from left
sx *= -1 # reflect
elif bx >= briX + brick_width - ball_radius*2: # ball hit brick from right
sx *= -1 # reflect
if by <= briY + ball_radius * 2: # ball hit brick from top
sy *= -1 # reflect
elif by >= briY + brick_height - ball_radius*2: # ball hit brick from below
sy *= -1 # reflect
break # ball hit a brick and cannot hit another one at the same time
if brickForRemoval != None: # if a brick is scheduled for removal…
bricks.remove(brickForRemoval) # … remove it!
# collision check: does bat hit ball?
if (bx >= mousex and bx <= mousex + bat_width): # using bat_width variable
if(by >= player_start_y - bat_height and by <= player_start_y):
sy *= -1 # reflect
pygame.display.update() # show updated screen
fpsClock.tick(fps) # limit fps
Any help is highly appreciated!
As per #jasonharper's comment, when you assign a value to a variable name that is the same as the function name, that name becomes "re-assigned", in this case - making the function no longer accessible.
Using any other variable name inside your function would fix it:
def brick_color():
color_var = random.randint(0,3)
if color_var == 0: #red
new_color = pygame.Color(198,44,58)
elif color_var == 1:#blue
new_color = pygame.Color(1,128,181)
elif color_var == 2:#yellow
new_color = pygame.Color(255,211,92)
elif color_var == 3:#green
new_color = pygame.Color(0,157,103)
return new_color
Your function could also use a python list to be a bit more flexible and compact:
def getRandomColor():
# red blue yellow green
color_list = [ (198,44,58), (1,128,181), (255,211,92), (0,157,103) ]
random_choice = random.randrange( len( color_list ) )
return color_list[ random_choice ]
This form is more flexible because you can easily add more colours by appending them to the list (no more to do).

How to simulate more realistic movement of an object?

I am writing a game environment, in which a person (worker) should move inside the area in a random direction, until it crosses with one of green-coloured obstacles (defined as pygame.draw.rect(screen, GREEN, [510,150,75,52]) and pygame.draw.rect(screen, GREEN, [450,250,68,40])).
Until now I can simulate a random movement of a worker, but it moves somehow irregularly and non-smoothly, jumping around the same area and slowly shifting to the right bottom corner.
How can I update the function create_randomPATH to support a more realistic smooth movement of a worker inside the screen area? I tried to increase a tick size till 70 or even more (clock.tick(70)) as it is indicated in one of threads, but it does not seem to solve the problem.
import pygame, random
import sys
WHITE = (255, 255, 255)
GREEN = (20, 255, 140)
GREY = (210, 210 ,210)
RED = (255, 0, 0)
PURPLE = (255, 0, 255)
SCREENWIDTH=1000
SCREENHEIGHT=578
class Worker(pygame.sprite.Sprite):
def __init__(self, image_file, location):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(image_file)
self.rect = self.image.get_rect()
self.rect.left, self.rect.top = location
def create_randomPATH(self,x,y):
randomX = random.randint(1,5)
randomY = random.randint(1,5)
if random.uniform(0,1)>0.5:
valX = x + randomX
valY = y + randomY
else:
valX = x - randomX
valY = y - randomY
return valX, valY
class Background(pygame.sprite.Sprite):
def __init__(self, image_file, location):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(image_file)
self.rect = self.image.get_rect()
self.rect.left, self.rect.top = location
pygame.init()
size = (SCREENWIDTH, SCREENHEIGHT)
screen = pygame.display.set_mode(size)
screen_rect=screen.get_rect()
pygame.display.set_caption("TEST")
worker = Worker("worker.png", [0,0])
w_x = worker.rect.left
w_y = worker.rect.top
bg = Background("background.jpg", [0,0])
carryOn = True
clock=pygame.time.Clock()
while carryOn:
for event in pygame.event.get():
if event.type==pygame.QUIT:
carryOn=False
pygame.display.quit()
pygame.quit()
quit()
# Draw floor layout
screen.blit(pygame.transform.scale(bg.image, (SCREENWIDTH, SCREENHEIGHT)), bg.rect)
# Draw obstacles
pygame.draw.rect(screen, GREEN, [510,150,75,52])
pygame.draw.rect(screen, GREEN, [450,250,68,40])
w_x,w_y = worker.create_randomPATH(w_x,w_y)
# worker should not go outside the screen area
worker.rect.clamp_ip(screen_rect)
screen.blit(worker.image, (w_x,w_y))
# Refresh Screen
pygame.display.flip()
clock.tick(5)
pygame.display.quit()
pygame.quit()
quit()
Take screen.blit(worker.image, (w_x,w_y)) and put it in it's own method. Then you can create a loop to loop from the oldX and oldY to the new position found by create_randomPATH You can even make the direction to move random by making them move in the x or y direction randomly each time.
Below is a rough example, but you probably have to modify it to match your code.
def UpdatePerson(image, x, y):
screen.blit(image, x, y)
while carryOn:
...
...
oldX = w_x
oldY = w_y
w_x,w_y = worker.create_randomPATH(w_x,w_y)
while(oldX < w_x || oldY < w_y):
randomDir = random.randint(1,2)
if randomDir == 1 && oldX < w_x:
UpdatePerson(worker.image, oldX, w_y)
oldX += 1
elif randomDir == 2 && oldY < w_x
UpdatePerson(worker.image, w_x, oldY)
oldY += 1
You could use an extra direction variable in your code which says in which direction the player is currently moving. One of four values: up, right, down or left. Every once in a while update that direction variable. Meanwhile move only one coordinate at a time in that direction.
def create_randomPATH(self, x, y, dir):
if random.uniform(0,1)>0.8:
# there is a 20% chance every time that direction is changed
dir = random.randInt(1,4)
if dir == 1:
return x, y+1, dir # up
if dir == 2:
return x+1, y, dir # right
if dir == 3:
return x, y-1, dir # down
if dir == 4:
return x-1, y, dir # left
In your code you would also need a global direction variable, initially it should also have a value 1, 2, 3 or 4:
w_x,w_y,w_dir = worker.create_randomPATH(w_x,w_y,w_dir)
By moving only one step on every iteration, we assure that the movement speed is always constant. In your code, it was varying from 1 to 5 steps. Also if you move a few steps in a given direction straight, it would seem more natural than changing the direction very suddenly very often. Try to play with the percentage (currently 20% chance) of how often the direction is changed and see if it makes the movement even smoother.
EDIT:
Because of the randomness of the movement, eventually the moving object will reach the borders of the game and might leave the screen. Adjust the position after each movement:
w_x,w_y,w_dir = worker.create_randomPATH(w_x,w_y,w_dir)
if (w_x + worker.rect.width > SCREENWIDTH): w_x = SCREENWIDTH - worker.rect.width
if (w_x < 0): w_x = 0
if (w_y + worker.rect.height > SCREENHEIGHT): w_y = SCREENHEIGHT - worker.rect.height
if (w_y < 0): w_y = 0
Then the object will not exit the screen and will eventually randomly move in other directions again. Because each direction is equally likely, it will move all around the screen properly.

Tkinter GUI not opening when compiled

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.

Pygame. How to make a rect change direction on collision (boundary check)

Part of an assignment I'm working on is making a ball bounce around the screen, I can make it move, but my boundary test doesn't seem to be working: the ball simply moves in direction instead of changing direction. So to clarify, what I want to ball to do is change direction as it hits the screen edge.
import sys
import pygame
SCREEN_SIZE = 750, 550
BALL_DIAMETER = 16
BALL_RADIUS = BALL_DIAMETER // 2
MAX_BALL_X = SCREEN_SIZE[0] - BALL_DIAMETER
MAX_BALL_Y = SCREEN_SIZE[1] - BALL_DIAMETER
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
LEFT = 11
RIGHT = 12
pygame.init()
clock = pygame.time.Clock()
pygame.display.init()
font = pygame.font.SysFont("impact", 20)
pygame.display.set_caption("Breakout")
screen = pygame.display.set_mode(SCREEN_SIZE)
class Ball:
def __init__(self):
''' '''
self.ball = pygame.Rect(300, 730 -
BALL_DIAMETER,
BALL_DIAMETER, BALL_DIAMETER)
# Draw ball
def draw_ball(self):
pygame.draw.circle(screen,
WHITE, (self.ball.left
+ BALL_RADIUS, self.ball.top +
BALL_RADIUS), BALL_RADIUS)
# Updates the coordinates by adding the speed components
def move_ball(self, x, y):
self.xspeed = x
self.yspeed = y
self.ball = self.ball.move(self.xspeed, self.yspeed)
# bounds check
if self.ball.left <= 0:
self.ball.left = 0
self.xspeed = -self.xspeed
elif self.ball.left >= MAX_BALL_X:
self.ball.left = MAX_BALL_X
self.xspeed = -self.xspeed
if self.ball.top < 0:
self.ball.top = 0
self.yspeed = -self.yspeed
elif self.ball.top >= MAX_BALL_Y:
self.ball.top = MAX_BALL_Y
self.yspeed = -self.yspeed
# shows a message on screen, for testing purposes
class Text:
def show_message(self, message):
self.font = pygame.font.SysFont("impact", 20)
font = self.font.render(message,False, WHITE)
screen.blit(font, (200, 400))
class Game:
def __init__(self):
''' '''
def run(self):
b = Ball()
while 1:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
keys = pygame.key.get_pressed()
# fps lock, screen fill and method call for input
clock.tick(60)
screen.fill(BLACK)
b.draw_ball()
b.move_ball(5, -5)
# used to keep track of various elements
# Text().show_message("P: " + str(p))
pygame.display.flip()
# Creates instance of the game class, and runs it
if __name__ == "__main__":
Game().run()
Your only call to move_ball uses a constant vector.
Since you never change the call parameters, the ball moves only that way.
b.move_ball(5, -5)
Yes, you change the vector components within move_ball when you hit a wall. However, on the next call, you change them back to the original values and move the ball in the original direction.
You have to initialize the vector outside move_ball, and then let the routine access the existing vector when it's called.

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