Im trying out pygame for the first time, now ive gotten very comfortable with python as a language, and ive been trying to recreate Pong, like the old school game with 2 paddles and a ball. I cant seem to figure out how to get the ball to reflect off the paddle. Im not looking for angles and stuff yet, cos ill be able to figure that out on my own.
Buttt what ive thought of is to get a range of coordinates, which are the X & Y of the paddle and the x & y + the width and height, and if the ball enters these, it simply reflects as it does at a boundary. Ive tried doing multiple if statements, as you can see in the code below, but ive also tried doing it as a single statement, but that doesnt work. None of the debug prints ive put in actually print, but when i test the coord ranges with print they look fine :D
Ill paste my code here so you guys can run my game as is.
Id really appreciate your guys help!
import pygame
pygame.init()
win = pygame.display.set_mode((500,500))
pygame.display.set_caption("First Game")
x = 50
y = 50
width = 10 #sets variables for the main paddle
height = 60
vel = 5
ballx = 250
bally = 250
radius = 5
direction = True #True is left, False is right
bvel = 4 #sets variables for the ball
angle = 0
coordxGT = 0
coordxLT = 0 #sets variables for the coordinate ranges of the first paddle for collision
coordyGT = 0
coordyLT = 0
def setCoords():
coordxGT = x
coordxLT = x + width
coordyGT = y #This function updates the coords throughout the main loop
coordyLT = y + height
coordxLT += radius
coordyLT += radius
run = True
while run == True:
pygame.time.delay(20)
for event in pygame.event.get(): #on quit, quit the game and dont throw up an error :)
if event.type == pygame.QUIT:
run = False
setCoords()
if direction == True: #Ball movement
ballx -= bvel
else:
ballx += bvel
if ballx<0:
ballx += bvel
direction = False
elif bally<0:
bally += bvel
elif ballx>495: #if the ball hits an edge
ballx -= bvel
direction = True
elif bally>495:
bally -= bvel
if ballx<coordxLT and ballx>coordxGT:
print("S1")
if bally<coordyLT and bally>coordyGT: #THE PART I CANT FIGURE OUT. If the ball lands within these ranges of coordinates, reflect it and change its direction
print("S2")
if direction == True:
print("YES")
ballx += bvel
direction = False
keys = pygame.key.get_pressed() #gets the keys pressed at that current time
if keys[pygame.K_DOWN]:
bally += bvel #Ball control (For debugging)
if keys[pygame.K_UP]:
bally -= bvel
if keys[pygame.K_w]:
y -= vel
if keys[pygame.K_a]:
x -= vel
if keys[pygame.K_s]: #Paddle controls
y += vel
if keys[pygame.K_d]:
x += vel
if x<0:
x += vel
if y<0:
y += vel
if x>80:
x -= vel #Stops the paddle from moving if it hits a boundary
if y>440:
#440 because window height - height of cube
y -= vel
win.fill((0,0,0))
pygame.draw.circle(win, (255, 255, 255), (ballx, bally), radius) #refreshes the screen
pygame.draw.rect(win,(255,255,255),(x, y, width, height))
pygame.display.update()
pygame.quit()
You are close, but you missed to declare the variables coordxGT, coordxLT, coordxLT, coordyLT to be global.
def setCoords():
global coordxGT, coordxLT, coordxLT, coordyLT
coordxGT = x
coordxLT = x + width
coordyGT = y
coordyLT = y + height
coordxLT += radius
coordyLT += radius
Note, if you want to write to a variable in global namespace in a function, then the varible has be interpreted as global. Otherwise an new variable in the scope of the function will be created and set. See global statement.
Related
Before you criticize me for not Googling or doing research before asking, I did research beforehand but to no avail.
I am trying to create the Atari Breakout game. I am currently stuck with making the ball bounce off walls. I did research on this and I found a lot of blogs and YouTube videos (and also Stack Overflow questions: this and this) talking about PyGame's vector2 class. I also read the PyGame documentation on vector2 but I can't figure out how to make it work.
I am currently writing a script to make the ball bounce off walls. In the beginning, the player is requested to press the spacebar and the ball will automatically move towards the north-east direction. It should bounce off the top wall when it hits it, but instead, it went inside. This is my approach:
import pygame
pygame.init()
screenWidth = 1200
screenHeight = 700
window = pygame.display.set_mode((screenWidth,screenHeight))
pygame.display.set_caption('Atari Breakout')
class Circle():
def __init__(self, x, y, radius):
self.x = x
self.y = y
self.radius = radius
self.vel_x = 1
self.vel_y = 1
def check_hit():
global hit
if (((screenWidth-box.x)<=box.radius) or ((box.x)<=box.radius) or ((box.y)<=box.radius) or ((screenHeight-box.y)<=box.radius)):
# meaning hit either four walls
if (((screenWidth-box.x)<=box.radius) or ((box.x)<=box.radius)):
# hit right, left
print('hit right, left')
hit = True
elif (((box.y)<=box.radius) or ((screenHeight-box.y)<=box.radius)):
# hit top, bottom
print('hit top, bottom')
hit = True
# main loop
run = True
box = Circle(600,300,10)
hit = False
# (screenWidth-box.x)<=box.radius hit right wall
while run: # (box.x)<=box.radius hit left wall
# (box.y)<=box.radius hit top wall
pygame.time.Clock().tick(60) # (screenHeight-box.y)<=box.radius hit bottom wall
for event in pygame.event.get():
if event == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[pygame.K_SPACE] and (box.y)>box.radius:
while True:
box.y -= box.vel_y
box.x += box.vel_x
window.fill((0,0,0))
pygame.draw.circle(window, (44,176,55), (box.x, box.y), box.radius)
pygame.display.update()
check_hit()
if hit == False:
continue
elif hit == True:
break
if (box.y)<=box.radius or (screenHeight-box.y)<=box.radius:
# hit top, bottom
box.vel_x *= 1
box.vel_y *= -1
print('changed')
if (box.y)<=box.radius:
# hit top
print('hi')
while True:
box.x += box.vel_x # <-- myguess is this is the problem
box.y += box.vel_y
window.fill((0,0,0))
pygame.draw.circle(window, (44,176,55), (box.x, box.y), box.radius)
pygame.display.update()
elif (screenWidth-box.x)<=box.radius or (box.x)<=box.radius:
# hit right, left
box.vel_x *= -1
box.vel_y *= 1
window.fill((0,0,0))
pygame.draw.circle(window, (44,176,55), (box.x, box.y), box.radius)
pygame.display.update()
print('Where are you going')
pygame.quit()
I guess the problem is where I marked. Which is here:
if (box.y)<=box.radius or (screenHeight-box.y)<=box.radius:
# hit top, bottom
box.vel_x *= 1
box.vel_y *= -1
print('changed')
if (box.y)<=box.radius:
# hit top
print('hi')
while True:
box.x += box.vel_x # <-- myguess is this is the problem
box.y += box.vel_y
window.fill((0,0,0))
pygame.draw.circle(window, (44,176,55), (box.x, box.y), box.radius)
pygame.display.update()
but I don't know why. My theory is: the ball travels upwards, it hit the top wall, check_hit() kicks in and make hit = True, then the vel_x and vel_y is changed accordingly (if hit top wall, vel_x should remain the same while vel_y should be multiplied by -1). Then it will move down, hence "bounce" off the top wall.
Note: for now I only have the top wall working. The other three will be done when I can figure out how to bounce off the top wall first.
Can you help me see what's the problem? And if this kind of operation requires the use of the vector2 class, can you explain it to me or give me a place to learn it?
The issue are the multiple nested loops. You have an application loop, so use it.
Continuously move the ball in the loop:
box.y -= box.vel_y
box.x += box.vel_x
Define a rectangular region for the ball by a pygame.Rect object:
bounds = window.get_rect() # full screen
or
bounds = pygame.Rect(450, 200, 300, 200) # rectangular region
Change the direction of movement when the ball hits the bounds:
if box.x - box.radius < bounds.left or box.x + box.radius > bounds.right:
box.vel_x *= -1
if box.y - box.radius < bounds.top or box.y + box.radius > bounds.bottom:
box.vel_y *= -1
See the example:
box = Circle(600,300,10)
run = True
start = False
clock = pygame.time.Clock()
while run:
clock.tick(120)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[pygame.K_SPACE]:
start = True
bounds = pygame.Rect(450, 200, 300, 200)
if start:
box.y -= box.vel_y
box.x += box.vel_x
if box.x - box.radius < bounds.left or box.x + box.radius > bounds.right:
box.vel_x *= -1
if box.y - box.radius < bounds.top or box.y + box.radius > bounds.bottom:
box.vel_y *= -1
window.fill((0,0,0))
pygame.draw.rect(window, (255, 0, 0), bounds, 1)
pygame.draw.circle(window, (44,176,55), (box.x, box.y), box.radius)
pygame.display.update()
What I am trying to do here is make my collision detect allow me to jump on a square but it doesn't seem to work. Its a the very bottom of the main loop.
# --- COLLISION is at the bottom of main loop
# ------
# this is a pygame module that I imported
import pygame
pygame.init()
# this is just my screen I created win defines it
win = pygame.display.set_mode((500,500))
# this is my caption for my game
pygame.display.set_caption("Just Tryna learn Something")
# these are my coordinates for my enemy where it will spawn
cordx = 300
cordy = 300
heights = 70
widths = 70
# my Player Coordinate and its speed and and its Jump
x = 200
y = 200
height = 40
width = 40
speed = 5
isJump = False
jumpCount = 10
# main loop
# main loop for my game
running = True
while running:
pygame.time.delay(100)
win.fill((0,0,0))
#-----------------------------------------------------------------------------------------
# this here draws my player in my window
Player = pygame.draw.rect(win, (140, 0,150), (x, y, height, width))
#-----------------------------------------------------------------------------------------
# this here draws my enemy
Enemy = pygame.draw.rect(win, (90,90,90), (cordx, cordy, heights, widths))
#=-------------------------------------------------------------------------------------
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
#-------------------------------------------------------------------
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
x -= speed
if keys[pygame.K_RIGHT]:
x += speed
# this here is my functions for movement and Jumping
if not(isJump):
if keys[pygame.K_UP]:
y -= speed
if keys[pygame.K_DOWN]:
y += speed
if keys[pygame.K_SPACE]:
isJump = True
else:
if jumpCount >= -10:
y -= (jumpCount * abs(jumpCount)) * 0.5
jumpCount -= 1
else:
jumpCount = 10
isJump = False
# COLLISION here is my collision detect and collision
its suppose to make me stand on the square with my little box when I jump on it
but it doesnt seem to work (Enemy) is the big box and (Player) is the little box
if Player.colliderect(Enemy):
pygame.draw.rect(win, (150,0,140), (50, 50, 20, 70))
if Player.top >= 375 and Player.top <= 370:
x = 375
# ---------------------------------------------------------
pygame.display.update()
pygame.quit()
Continuously let the player fall down. Add a variable fall = 0 and the variable to y and increment fall in every frame, if the player is not jumping. A jump ends, if the player reaches the maximum jump height (jumpCount == 0):
if not isJump:
y += fall
fall += 1
# [...]
else:
if jumpCount > 0:
y -= (jumpCount * abs(jumpCount)) * 0.5
jumpCount -= 1
else:
jumpCount = 10
isJump = False
Limit the player to the bottom of the window (500), and the top of the block by setting the y coordinate of the player:
Player.topleft = (x, y)
collide = False
if Player.colliderect(Enemy):
y = Enemy.top - Player.height
collide = True
if Player.bottom >= 500:
y = 500 - Player.height
collide = True
It is only allowed to jump, if the player stands on the ground or on the block:
if collide:
if keys[pygame.K_SPACE]:
isJump = True
fall = 0
Furthermore use pygame.time.Clock() and tick(), instead of pygame.time.delay() for a smooth movement. Control the speed by the flops per second (FPS):
FPS = 60
clock = pygame.time.Clock()
running = True
while running:
clock.tick(FPS)
#pygame.time.delay(100)
See the example:
# --- COLLISION is at the bottom of main loop
# ------
# this is a pygame module that I imported
import pygame
pygame.init()
# this is just my screen I created win defines it
win = pygame.display.set_mode((500,500))
# this is my caption for my game
pygame.display.set_caption("Just Tryna learn Something")
# these are my coordinates for my enemy where it will spawn
cordx = 300
cordy = 350
heights = 70
widths = 70
# my Player Coordinate and its speed and and its Jump
x = 200
y = 200
height = 40
width = 40
speed = 5
isJump = False
jumpCount = 10
fall = 0
FPS = 60
clock = pygame.time.Clock()
# main loop
# main loop for my game
running = True
while running:
clock.tick(FPS)
#pygame.time.delay(100)
win.fill((0,0,0))
#-----------------------------------------------------------------------------------------
# this here draws my player in my window
Player = pygame.draw.rect(win, (140, 0,150), (x, y, height, width))
#-----------------------------------------------------------------------------------------
# this here draws my enemy
Enemy = pygame.draw.rect(win, (90,90,90), (cordx, cordy, heights, widths))
#=-------------------------------------------------------------------------------------
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
#-------------------------------------------------------------------
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
x -= speed
if keys[pygame.K_RIGHT]:
x += speed
# this here is my functions for movement and Jumping
if not isJump:
y += fall
fall += 1
Player.topleft = (x, y)
collide = False
if Player.colliderect(Enemy):
collide = True
y = Enemy.top - Player.height
if Player.right > Enemy.left and Player.left < Enemy.left:
x = Enemy.left - Player.width
if Player.left < Enemy.right and Player.right > Enemy.right:
x = Enemy.right
if Player.bottom >= 500:
collide = True
y = 500 - Player.height
if collide:
if keys[pygame.K_SPACE]:
isJump = True
fall = 0
else:
if jumpCount > 0:
y -= (jumpCount * abs(jumpCount)) * 0.5
jumpCount -= 1
else:
jumpCount = 10
isJump = False
pygame.display.update()
pygame.quit()
Alright so following up to my previous question I've made some progress, game has the addition of left and right movement and a simple jump script, evolution woo :D
My latest addition was an Idle animation and now the dude can't move left :D
I hope its not too silly I checked everything but can't point out the issue >.> ..
regardless here's the code, Thanks in advance, truly appreciative! (origination is awful srry >.<):
import pygame
pygame.init()
path = "C:/Users/user/Documents/Le game/"
win = pygame.display.set_mode((800, 700))
pygame.display.set_caption("Potato ultra")
bg = pygame.image.load(path + "BG.jpg")
walk_right = [pygame.image.load("C:/Users/user/Documents/Le game/R2.png"), pygame.image.load("C:/Users/user/Documents/Le game/R3.png"), pygame.image.load("C:/Users/user/Documents/Le game/R4.png"), pygame.image.load("C:/Users/user/Documents/Le game/R5.png"), pygame.image.load("C:/Users/user/Documents/Le game/R6.png"), pygame.image.load("C:/Users/user/Documents/Le game/R7.png"), pygame.image.load("C:/Users/user/Documents/Le game/R8.png"), pygame.image.load("C:/Users/user/Documents/Le game/R9.png")]
walk_left = [pygame.image.load("C:/Users/user/Documents/Le game/L2.png"), pygame.image.load("C:/Users/user/Documents/Le game/L3.png"), pygame.image.load("C:/Users/user/Documents/Le game/L4.png"), pygame.image.load("C:/Users/user/Documents/Le game/L5.png"), pygame.image.load("C:/Users/user/Documents/Le game/L6.png"), pygame.image.load("C:/Users/user/Documents/Le game/L7.png"), pygame.image.load("C:/Users/user/Documents/Le game/L8.png"), pygame.image.load("C:/Users/user/Documents/Le game/L9.png")]
Static = pygame.image.load("C:/Users/user/Documents/Le game/Idle.png")
SW = 800
SH = 700
x = 0
y = 480
width = 64
height = 64
vel = 20
isJump = False
MoveLeft = False
MoveRight = False
Idle = False
JumpCount = 10
walkCount = 0
def redrawGameWindow():
win.blit(bg, (0,0))
global walkCount
if not Idle:
if MoveRight:
if walkCount <= 7:
win.blit(walk_right[walkCount], (x, y))
elif walkCount > 7:
walkCount = 0
win.blit(walk_right[walkCount], (x, y))
if MoveLeft:
if walkCount <= 7:
win.blit(walk_left[walkCount], (x, y))
elif walkCount > 7:
walkCount = 0
win.blit(walk_left[walkCount], (x, y))
else:
win.blit(Static, (x, y))
pygame.display.update()
run = True
while run:
pygame.time.delay(50)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and x > 0:
Idle = False
MoveRight = False
MoveLeft = True
x -= vel
walkCount += 1
if keys[pygame.K_RIGHT] and x < SW - width:
Idle = False
MoveRight = True
MoveLeft = False
x += vel
walkCount += 1
else:
Idle = True
if not isJump:
if keys[pygame.K_UP] and y > 0:
y -= vel
if y < 0:
y = 0
if keys[pygame.K_DOWN] and y < SH - height:
y += vel
if y > 636:
y = 636
if keys[pygame.K_SPACE]:
isJump = True
else:
if JumpCount >= -10:
y -= (JumpCount * 4)
if y < 0:
y = 0
JumpCount -= 2
else:
isJump = False
JumpCount = 10
redrawGameWindow()
pygame.quit()
Well, the first thing I would do would be to add the following code (before the final line below, which you already have):
print("DEBUG1 keyl =", keys[pygame.K_LEFT], "x =", x)
print("DEBUG2 keyr =", keys[pygame.K_RIGHT], "SW =", Sw, "wid =", width)
print("DEBUG3 mover =", MoveRight, "movel =", MoveLeft)
print("DEBUG4 vel =", vel, "walkc =", walkCount)
print("DEBUG5 ==========")
if keys[pygame.K_LEFT] and x > 0:
That way, you'll see all the variables that take part in deciding left and right moves, and it will hopefully become obvious what is preventing the left move from functioning.
Based on a slightly deeper look at your code, it appears to be this bit:
if keys[pygame.K_LEFT] and x > 0:
Idle = False
MoveRight = False
MoveLeft = True
x -= vel
walkCount += 1
if keys[pygame.K_RIGHT] and x < SW - width:
Idle = False
MoveRight = True
MoveLeft = False
x += vel
walkCount += 1
else:
Idle = True
Since that else belongs only to the second if statement, it will fire whenever the right key is not being pressed, regardless of whether you're pressing the left key.
I suspect you can fix this simply by changing it from an if, if, else sequence to an if, elif, else sequence, so that the else fires only if neither of the keys are pressed.
A couple of possible improvements to your code:
you can get rid of all that walkCount checking and adjusting in the redraw function by simply using walkCount = (walkCount + 1) % 8 in the event loop - this will ensure it wraps from seven to zero without further effort.
you don't appear to have limiters on the x value. For example, if x == 5 and vel == 10, it's possible that a left move will set x = -5 which may not be desirable. You have more knowledge of the game play than me so I could be wrong, but it's worth checking.
you may not need both MoveLeft and MoveRight. The Idle flag decides whether you're moving or not so, if you are moving, it's either left or right. So a Idle/MoveRight combo should be enough for the code to figure out what to do. Again, this is a gameplay issue, something to look at but I may be incorrect.
not sure how your sprites look when they jump but you may be better off using constant acceleration formulae for calculating y position. I've answered similar questions before so you can have a look at this answer for guidance.
So I'm relatively new to pygame, and have started to branch off from a basic tutorial. I want the sprite to be able to double jump and have attempted to add in a variable etc so I can change it to a triple jump if they eventually gain a power up or some sort, and I can change it easily, but I'm struggling. P.S: Some of the comments might not make sense as some code has been removed, but are still useful to me.
x = 200
y = 450
width = 64
height = 66
vel = 5
screenwidth = 500
isjump = True #whether our character is jumping or not
jumpcount = 10
maxjump = 2 #double jump
maxjumpcount = 0
#we must keep track of direction, are they moving and how many steps for frames
left = False
right = False
walkcount = 0
def redrawGameWindow():
global walkcount
window.blit(background,(0,0)) #loads bg at 0,0
if walkcount +1 >= 15: #15 as we have 5 sprites, which will be displayed 3 times per second
walkcount = 0
if left:
window.blit(walkleft[walkcount//3],(x,y))#excludes remainders
walkcount+=1
elif right:
window.blit(walkright[walkcount//3],(x,y))
walkcount+=1
elif isjump:
window.blit(jumpp,(x,y))
walkcount+=1
else:
window.blit(char,(x,y))
pygame.display.update()
#now to write main loop which checks for collisions, mouse events etc
run = True
while run: #as soon as we exit this, the game ends
#main loop, gonna check for collision
clock.tick(15) #frame rate, how many pics per second, games r just a ton of pictures running very quickly
#now we check for events
for event in pygame.event.get(): #gets a list of all events that happened
print(event)
#can go through these events& check if they've happened
if event.type == pygame.QUIT: #if we try to exit the window
run = False
through the use of events. If a key has been pressed, we change the x & y of our shape
#if we want it to continuely move, we need to get a list
keys = pygame.key.get_pressed() #if these are pressed or held down, execute whatever is under these.
if keys[pygame.K_LEFT] and x > vel-vel: #if pressed we move our character by the velocity in whatever direction via grid which works from the TOP LEFT of screen
#0,0 is top left #vel-vel to equal 0, makes border better
x -= vel
left = True
right = False
elif keys[pygame.K_RIGHT] and x < screenwidth - width: #as the square co ord is top left of it
x+= vel
right = True
left = False
else:
right = False
left = False
walkcount = 0
#JUMP CODE
if not(isjump): # still lets them move left and right
if keys[pygame.K_SPACE]:
isjump = True #quadratic function for jump
right = False
left = False
walkcount = 0
maxjumpcount+=1
if maxjumpcount > 2:
isjump = False
else:
while jumpcount >= -10 and maxjumpcount < 2: #moving slower, then faster, hang, then go down
pygame.time.delay(12)
y -= (jumpcount*abs(jumpcount)) / 2#1.35? #squared, 10 ^2, /2, jumpcount = jump height
jumpcount -= 1
else: #jump has concluded if it reaches here
isjump = False
jumpcount = 10
maxjumpcount = 0 #double jump resets once jump ends
redrawGameWindow()
Apologies for quite a pathetic attempt. Any other suggestions would be greatly appreciated as I still have so much to learn. Thanks.
You have a game loop, use it. You don't need an extra loop for the jump. Use a selection (if) instead of the inner while loop, to solve the issue:
run = True
while run: #as soon as we exit this, the game ends
# [...]
if not(isjump): # still lets them move left and right
if keys[pygame.K_SPACE]:
# [...]
else:
# instead of: while jumpcount >= -10 and maxjumpcount < 2:
if jumpcount >= -10 and maxjumpcount < 2: # <---------------
y -= (jumpcount*abs(jumpcount)) / 2
jumpcount -= 1
Note, this block of code is continuously called in the main application loop.
I am creating a physics-based game with Pygame in which the player controls a ball. As you control the ball, it accelerates in the specified direction (holding the left arrow adds x pixels per frame to its movement speed). Since the ball is... well... a ball, and Pygame doesn't support ball collision detection, I created a new class with its own collision method. The method has two parts: if the ball runs into the corner of a rectangle, or if it runs into the side of the rectangle. The problem concerns the circle-to-side collision.
The ball is based on a rect object, and therefore has those pesky corners. I cannot use the simple colliderect method, otherwise the situation above would detect a collision where there should be none, and it would overlap the first part of my collision detection method. Instead, I opted to use collidepoint between the rectangle and the midpoints on each side of the ball's rectangle.
Finally, the heart of the issue. I mentioned earlier that the ball accelerates. When the ball accelerates to the point that (even though it appears to be standing still) it moves far enough into the rectangle for another midpoint on the circle to detect a "collision." This problem likely stems from the fact that (for a collision on the left side of the ball) my code sets the ball's left equal to the rectangle's right, so that when the ball accelerates enough to be inside the rectangle, it gets moved to another face of the rectangle.
Thank you so much for bearing with me, any and all suggestions are welcome. I would either be looking for a fix to my specific problem, or a cleaner way to handle the collision detection. My full code is below:
import pygame, sys, math
global Color
Color = {}
Color['white'] = (255,255,255)
Color['black'] = ( 0, 0, 0)
Color['red'] = (255, 0, 0)
Color['green'] = ( 0,255, 0)
Color['blue'] = ( 0, 0,255)
global WINDOWWIDTH, WINDOWHEIGHT
WINDOWWIDTH, WINDOWHEIGHT = 500, 500
class Ball():
def __init__(self, x, y, r):
self.rect = pygame.Rect(x, y, r, r)
self.radius = r/2
self.speed = [0, 0]
self.b_fact = 1
self.move = {'left':False, 'right':False, 'up':False, 'down':False}
self.new_dir = {'left':False, 'right':False, 'up':False, 'down':False}
def move_self(self):
if self.move['left']:
self.speed[0] -= 2
if self.move['up']:
self.speed[1] -= 2
if self.move['right']:
self.speed[0] += 2
if self.move['down']:
self.speed[1] += 2
if self.speed[0] < 0:
self.speed[0] += 1
if self.speed[1] < 0:
self.speed[1] += 1
if self.speed[0] > 0:
self.speed[0] -= 1
if self.speed[1] > 0:
self.speed[1] -= 1
self.rect.left += self.speed[0]
self.rect.top += self.speed[1]
def bounce(self, rectList):
for rect in rectList:
self.collide_rect(rect)
if self.rect.left <= 0:
self.rect.left = 0
self.new_dir['right'] = True
if self.rect.right >= WINDOWWIDTH:
self.rect.right = WINDOWWIDTH
self.new_dir['left'] = True
if self.rect.top <= 0:
self.rect.top = 0
self.new_dir['down'] = True
if self.rect.bottom >= WINDOWHEIGHT:
self.rect.bottom = WINDOWHEIGHT
self.new_dir['up'] = True
for key in self.new_dir:
if self.new_dir[key] and key=='left':
self.speed[0] *= (-1)*self.b_fact
if self.new_dir[key] and key=='right':
self.speed[0] *= (-1)*self.b_fact
if self.new_dir[key] and key=='up':
self.speed[1] *= (-1)*self.b_fact
if self.new_dir[key] and key=='down':
self.speed[1] *= (-1)*self.b_fact
self.new_dir[key] = False
def collide_rect(self, rect):
x1, y1, r = self.rect.centerx, self.rect.centery, self.radius
foundSide = 0
foundCorner = 0
side_list = ['left', 'right', 'bottom', 'top']
corner_list = ['topleft', 'topright', 'bottomleft', 'bottomright']
collision_list = []
for side in side_list:
if rect.collidepoint(eval('self.rect.mid'+side)):
collision_list.append(side)
for corner in corner_list:
x2, y2 = eval('rect.'+corner)[0], eval('rect.'+corner)[1]
dist = math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
if dist < r:
if corner.find('left') > -1:
corner = corner.replace('left','right')
else:
corner = corner.replace('right','left')
if corner.find('top') > -1:
corner = corner.replace('top','bottom')
else:
corner = corner.replace('bottom','top')
collision_list.append(corner)
for direction in collision_list:
if direction.find('left') > -1:
self.rect.left = rect.right
self.new_dir['left'] = True
if direction.find('top') > -1:
self.rect.top = rect.bottom
self.new_dir['top'] = True
if direction.find('right') > -1:
self.rect.right = rect.left
self.new_dir['right'] = True
if direction.find('bottom') > -1:
self.rect.bottom = rect.top
self.new_dir['bottom'] = True
class BallGame():
def __init__(self):
pygame.display.set_caption("Ball is life")
pygame.init()
self.ball = Ball(0, 0, 30)
self.allRects = []
rect = pygame.Rect(60,60,50,50)
self.allRects.append(rect)
self.mainClock = pygame.time.Clock()
self.screen = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
self.basicFont = pygame.font.SysFont(None, 50)
def drawScreen(self):
self.screen.fill(Color['green'])
pygame.draw.ellipse(self.screen, Color['white'], self.ball.rect)
for rect in self.allRects:
pygame.draw.rect(self.screen, Color['black'], rect)
def mainloop(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
for i in range(2):
k = (pygame.KEYUP, pygame.KEYDOWN)
if event.type == k[i]:
if event.key == pygame.K_LEFT:
self.ball.move['left'] = i
elif event.key == pygame.K_UP:
self.ball.move['up'] = i
elif event.key == pygame.K_RIGHT:
self.ball.move['right'] = i
elif event.key == pygame.K_DOWN:
self.ball.move['down'] = i
self.ball.move_self()
self.ball.bounce(self.allRects)
self.drawScreen()
pygame.display.update()
self.mainClock.tick(20)
Game = BallGame()
while True:
Game.mainloop()
Another way to think about the collision is to consider an enlarged version of the black rectangle. This would be a rounded rectangle with corner radius r. The collision between the ball and black rectangle is equivalent to the collision between the center of the ball and the rounded rectangle. This can help make the analysis of the situation easier.
When it bounces a more accurate way of determining the new position is to consider the line from the previous position to the current position. You can calculate where this line crosses the boundary and where a prefect reflection should be.