user4638313
user4638313

Reputation:

Pygame: objects bounce off walls a few times then never again

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

Answers (2)

user4638313
user4638313

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

user890167
user890167

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

Related Questions