Reputation:
I'm making a program in which 3 squares bounce off a few lines that I drew on the screen. When I run it, the squares bounce off the lines a few times, but eventually they just plow right through the lines, and then they line up behind each other and bounce off the walls, not giving a darn about the lines.
At one point when I was coding this, the blue one just refused to care and bounced off the edges as if there were no lines. I believe I had set up my ranges correctly, but I'm not sure what happened.
import pygame, sys, time
from pygame.locals import *
pygame.init()
WINDOWWIDTH = 400
WINDOWHEIGHT = 400
window = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT), 0, 32)
pygame.display.set_caption('potato')
DOWNLEFT = 1
DOWNRIGHT = 3
UPLEFT = 7
UPRIGHT = 9
MOVESPEED = 1
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
b1 = {'rect':pygame.Rect(0, 50, 25, 25), 'color':RED, 'dir':DOWNRIGHT}
b2 = {'rect':pygame.Rect(0, 100, 25, 25), 'color':GREEN, 'dir':DOWNRIGHT}
b3 = {'rect':pygame.Rect(0, 150, 25, 25), 'color':BLUE, 'dir':DOWNRIGHT}
blocks = [b1, b2, b3]
while True:
# check for the closing of the 'x' button
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
window.fill(BLACK)
pygame.draw.line(window,BLUE,(150,0),(150,130),5)
pygame.draw.line(window,BLUE,(150,300),(150,400),5)
pygame.draw.line(window,BLUE,(200,200),(200,300),5)
pygame.draw.line(window,BLUE,(300,400),(300,250),5)
for b in blocks:
#moves the blocks
if b['dir'] == DOWNLEFT:
b['rect'].left -= MOVESPEED
b['rect'].top += MOVESPEED
if b['dir'] == DOWNRIGHT:
b['rect'].left += MOVESPEED
b['rect'].top += MOVESPEED
if b['dir'] == UPLEFT:
b['rect'].left -= MOVESPEED
b['rect'].top -= MOVESPEED
if b['dir'] == UPRIGHT:
b['rect'].left += MOVESPEED
b['rect'].top -= MOVESPEED
# check if the block has move out of the window
if b['rect'].top < 0:
# block has moved past the top
if b['dir'] == UPLEFT:
b['dir'] = DOWNLEFT
if b['dir'] == UPRIGHT:
b['dir'] = DOWNRIGHT
if b['rect'].bottom > WINDOWHEIGHT:
# block has moved past the bottom
if b['dir'] == DOWNLEFT:
b['dir'] = UPLEFT
if b['dir'] == DOWNRIGHT:
b['dir'] = UPRIGHT
if b['rect'].left < 0:
# block has moved past the left side
if b['dir'] == DOWNLEFT:
b['dir'] = DOWNRIGHT
if b['dir'] == UPLEFT:
b['dir'] = UPRIGHT
if b['rect'].right > WINDOWWIDTH:
# block has moved past the right side
if b['dir'] == DOWNRIGHT:
b['dir'] = DOWNLEFT
if b['dir'] == UPRIGHT:
b['dir'] = UPLEFT
##################CODE FOR THE BOX BOUNCING ON LINES IS BELOW#########
#Upper left
if b['dir'] == UPLEFT or b['dir'] == DOWNLEFT:
if b['rect'].left == 150 and b['rect'].top > 0 and b['rect'].top < 130:
if b['dir'] == DOWNLEFT:
b['dir'] = DOWNRIGHT
if b['dir'] == UPLEFT:
b['dir'] = UPRIGHT
if b['dir'] == DOWNRIGHT or b['dir'] == UPRIGHT:
if b['rect'].right == 150 and b['rect'].top < 130 and b['rect'].top>0:
if b['dir'] == DOWNRIGHT:
b['dir'] = DOWNLEFT
if b['dir'] == UPRIGHT:
b['dir'] = UPLEFT
#Lower left line
if b['dir'] == UPLEFT or b['dir'] == DOWNLEFT:
if b['rect'].left == 150 and b['rect'].top > 300 and b['rect'].top < 400:
if b['dir'] == DOWNLEFT:
b['dir'] = DOWNRIGHT
if b['dir'] == UPLEFT:
b['dir'] = UPRIGHT
if b['dir'] == DOWNRIGHT or b['dir'] == UPRIGHT:
if b['rect'].right == 150 and b['rect'].top > 300 and b['rect'].top < 400:
if b['dir'] == DOWNRIGHT:
b['dir'] = DOWNLEFT
if b['dir'] == UPRIGHT:
b['dir'] = UPLEFT
#middle line
if b['dir'] == UPLEFT or b['dir'] == DOWNLEFT:
if b['rect'].left == 200 and b['rect'].top < 300 and b['rect'].top > 200:
if b['dir'] == DOWNLEFT:
b['dir'] = DOWNRIGHT
if b['dir'] == UPLEFT:
b['dir'] = UPRIGHT
if b['dir'] == DOWNRIGHT or b['dir'] == UPRIGHT:
if b['rect'].right == 200 and b['rect'].top <300 and b['rect'].top >200:
if b['dir'] == DOWNRIGHT:
b['dir'] = DOWNLEFT
if b['dir'] == UPRIGHT:
b['dir'] = UPLEFT
if b['dir'] == UPLEFT or b['dir'] == DOWNLEFT:
if b['rect'].left == 300 and b['rect'].top < 250 and b['rect'].top > 400:
if b['dir'] == DOWNLEFT:
b['dir'] = DOWNRIGHT
if b['dir'] == UPLEFT:
b['dir'] = UPRIGHT
if b['dir'] == DOWNRIGHT or b['dir'] == UPRIGHT:
if b['rect'].right == 300 and b['rect'].top < 400 and b['rect'].top >250:
if b['dir'] == DOWNRIGHT:
b['dir'] = DOWNLEFT
if b['dir'] == UPRIGHT:
b['dir'] = UPLEFT
pygame.draw.rect(window, b['color'], b['rect'])
pygame.display.update()
#change speed
time.sleep(0.004)
Help is appreciated!
Upvotes: 1
Views: 1299
Reputation:
Problem solved.
I forgot to consider the thickness of the lines, so the if statments had coordinates like 150 and 250, when they should've been 155 and 255 or something like that. Here it is:
import pygame, sys, time
from pygame.locals import *
#stuff happens then the bouncing code is below
#Upper
## pygame.draw.line(screen,BLUE,(150,0),(150,130),5)
if b['dir'] == UPLEFT or b['dir'] == DOWNLEFT:
if (b['rect'].left == 155) and (b['rect'].top > 0) and (b['rect'].top < 130):
if b['dir'] == DOWNLEFT:
b['dir'] = DOWNRIGHT
if b['dir'] == UPLEFT:
b['dir'] = UPRIGHT
if b['dir'] == DOWNRIGHT or b['dir'] == UPRIGHT:
if (b['rect'].right == 150) and (b['rect'].top < 130) and (b['rect'].top>0):
if b['dir'] == DOWNRIGHT:
b['dir'] = DOWNLEFT
if b['dir'] == UPRIGHT:
b['dir'] = UPLEFT
#Lower
## pygame.draw.line(screen,BLUE,(150,300),(150,400),5)
if b['dir'] == UPLEFT or b['dir'] == DOWNLEFT:
if (b['rect'].left == 155) and (b['rect'].bottom > 300) and (b['rect'].bottom < 400):
if b['dir'] == DOWNLEFT:
b['dir'] = DOWNRIGHT
if b['dir'] == UPLEFT:
b['dir'] = UPRIGHT
if b['dir'] == DOWNRIGHT or b['dir'] == UPRIGHT:
if (b['rect'].right == 150) and (b['rect'].bottom > 300) and (b['rect'].bottom < 400):
if b['dir'] == DOWNRIGHT:
b['dir'] = DOWNLEFT
if b['dir'] == UPRIGHT:
b['dir'] = UPLEFT
# the left line
## pygame.draw.line(screen,BLUE,(200,200),(200,300),5)
if b['dir'] == UPLEFT or b['dir'] == DOWNLEFT:
if (b['rect'].left == 205) and (b['rect'].top < 300) and (b['rect'].bottom > 200):
if b['dir'] == DOWNLEFT:
b['dir'] = DOWNRIGHT
if b['dir'] == UPLEFT:
b['dir'] = UPRIGHT
if b['dir'] == DOWNRIGHT or b['dir'] == UPRIGHT:
if (b['rect'].right == 200) and (b['rect'].top <300) and (b['rect'].bottom >200):
if b['dir'] == DOWNRIGHT:
b['dir'] = DOWNLEFT
if b['dir'] == UPRIGHT:
b['dir'] = UPLEFT
# the right line
## pygame.draw.line(screen,BLUE,(300,400),(300,250),5)
if b['dir'] == UPLEFT or b['dir'] == DOWNLEFT:
if (b['rect'].left == 305) and (b['rect'].bottom > 250) and (b['rect'].bottom < 400):
if b['dir'] == DOWNLEFT:
b['dir'] = DOWNRIGHT
if b['dir'] == UPLEFT:
b['dir'] = UPRIGHT
if b['dir'] == DOWNRIGHT or b['dir'] == UPRIGHT:
if (b['rect'].right == 300) and (b['rect'].bottom < 400) and (b['rect'].bottom > 250):
if b['dir'] == DOWNRIGHT:
b['dir'] = DOWNLEFT
if b['dir'] == UPRIGHT:
b['dir'] = UPLEFT
pygame.draw.rect(screen, b['color'], b['rect'])
pygame.display.update()
#change speed
time.sleep(0.05)
thanks @Stick for your help!
Upvotes: 1
Reputation:
The trick to this is to move the object, then check that it is still in-bounds, and if it isn't then you need to act on it by pushing it in the right direction and modifying its direction to match.
If you're going to roll a lot of your own code instead of borrowing against pygame's existing classes, then might I suggest simplifying the way in which you're moving the objects around in the first place?
Since you're only working with 8 directions, then you might consider using your namespace as a series of sequences which represent the eight directions.
UP = [0, -1]
DOWN = [0, 1]
LEFT = [-1, 0]
RIGHT = [1, 0]
Diagonals are a little different because they aren't whole numbers; your objects will appear to move much faster if you use whole numbers for these. If you recall the Pythagorean Theorem (or if you don't) or if you know about unit vectors (or.. if you don't..) then you know that these are best represented as a value in the ballpark of half the square root of 2. For convenience I'll just assign this value to Q
, but that isn't an optimization or anything; Python won't care if you insert that sqrt directly into the lists, they'll be sorted out before the game begins.
Q = math.sqrt(2) / 2.0
UPLEFT = [-Q, -Q]
UPRIGHT = [Q, -Q]
DOWNLEFT = [-Q, Q]
DOWNRIGHT = [Q, Q]
Now when you check to see which direction an object should move, you don't need to have all those if
checks. Just do as follows:
for obj in my_group:
# calculate the distance to move the object
delta_x, delta_y = [MOVESPEED * i for i in obj['dir']]
# use pygame.Rect's move_ip() method to move the rect
obj['rect'].move_ip(delta_x, delta_y)
So by multiplying the values in the tuple by the object's movement speed, you basically determine how many 'spaces' to move the object. Then you just shift the object's rect
using rect.move_ip()
to move it.
Once you've moved the object, you then want to ensure that it's in-bounds or not. You've drawn four separate lines to represent these; it looks like these can also be combined into yet another rect object, which would make checking the object's proximity a lot easier. We'd do this before the event loop starts, of course --
Boundary = pygame.Rect(150, 150, 400, 400) # or whatever dimensions you like
Now you can check against whether or not the objects are completely contained within the Boundary before changing their direction.
if not Boundary.contains(obj['rect']):
The rect.contains
method checks to see that one rect completely encloses another rect. So as long as the moving object's rect is entirely inside that Boundary rect, nothing needs to change. However, if it has gone too far, we need to start correcting its behavior. Fortunately since we're just working with numbers now, this is as easy as getting the negative of those numbers.
# if a value is out of bounds, multiply it by -1
if not (Boundary.left < obj['rect'].left or
Boundary.right > obj['rect'].right):
obj['dir'][0] *= -1
if not (Boundary.top < obj['rect'].top or
Boundary.bottom > obj['rect'].bottom):
obj['dir'][1] *= -1
At this point, the object could still be manually scooted back into position, but if we're doing our checks in the right order we may not need to. In this way, the re-direction is automatically handled by simply setting the correct direction, then allowing the event loop to run back around and naturally push the object into the correct location. (This doesn't always work out depending on the way things pan out, so if an object needs to be scooted around this if
statement changes slightly, but for now it isn't totally relevant.)
At this point, we should draw the entire series of updates as opposed to one at a time, as your loop appears to do. Separating draw calls in pygame is a good path to bad performance. Since you're not using Sprite Groups yet (and you should... :D ) then you'd just do something like the following after it's all said and done:
for obj in my_group:
pygame.draw.rect(window, obj['rect'], some_color)
pygame.display.flip()
Finally -- after all this, please look at one more helpful object to handle your frame-rate instead of time.sleep
; the pygame.time.Clock
object. It's instantiated before the game begins:
MyClock = pygame.time.Clock()
FPS = 30 # or whatever framerate you're going for
...and after the draw call, it's common to just call Clock.tick()
to correctly advance the frame rate. This object is smarter than plain calls to time.sleep
and will try to keep your game at a constant FPS, so it'd be good to use it.
MyClock.tick(FPS)
Ideally we do something similar to this:
for event in pygame.event.get():
process_event(event) # however it is you plan on handling events,
# that would go here
for obj in my_group:
# calculate the distance to move the object
delta_x, delta_y = [MOVESPEED * i for i in obj['dir']]
# use pygame.Rect's move_ip() method to move the rect
obj['rect'].move_ip(delta_x, delta_y)
# check if the object is outside the boundaries
if not Boundary.contains(obj['rect']):
# if a value is out of bounds, multiply it by -1
if not (Boundary.left < obj['rect'].left or
Boundary.right > obj['rect'].right):
obj['dir'][0] *= -1
if not (Boundary.top < obj['rect'].top or
Boundary.bottom > obj['rect'].bottom):
obj['dir'][1] *= -1
for obj in my_group:
pygame.draw.rect(window, obj['rect'], some_color)
pygame.display.flip()
MyClock.tick(FPS)
Upvotes: 0