pygame collision detection with walls

2019-08-05 13:43发布

问题:

Currently I've been spending around half an hour trying to figure out how to implement this script better. I've tried a few methods, but they didn't quite work. When running the script, the player glitches a bit, and sometimes the player can only move in the blocks once it comes in contact.

x = 64*3
y = 0
xvel = 0
yvel = 0
grounded = True

playerRect = pygame.Rect ((x, y, 64, 64))

collidelist = []

level = ["#=======##=========================",
         "#=======#==========================",
         "#==###############=========###=====",
         "#===============#####==============",
         "#==================================",
         "###################################"]

def makelevel (level):
    x = y = 0
    def checkline (line, x, y):
        for character in line:
            if character == "#":
                block = pygame.draw.rect (screen, (50, 50, 255), (x * 64, y * 64, 64, 64))
                collidelist.append (block)
            x += 1
    for line in level:
        checkline (line, x, y)
        y += 1
def move (xvel, yvel):
    global x
    global y
    global playerRect
    global collideList
    x += xvel
    y += yvel
    for block in collidelist:
        if playerRect.colliderect(block):
            x += -xvel * 2
            y += -yvel * 2
            break
makelevel (level)

while True:
    screen.fill ([0, 0, 0])
    makelevel (level)
    playerRect = pygame.Rect ((x, y, 64, 64))
    pygame.draw.rect (screen, (255, 255, 255), playerRect)
    for event in pygame.event.get ():
        if event.type == pygame.QUIT:
            pygame.quit ()
            sys.exit ()
            exit ()
    pressed = pygame.key.get_pressed ()
    if pressed [pygame.K_RIGHT]:
        move (5, 0)
    if pressed [pygame.K_LEFT]:
        move (-5, 0)
    if pressed [pygame.K_UP]:
        move (0, -5)
    if pressed [pygame.K_DOWN]:
        move (0, 5)
    pygame.display.update ()

回答1:

To handle collisions with walls, the easiest solution is to move the player rect or sprite along the x-axis first, check if it collides with a wall and then set its rect.right = block.left if we're moving to the right or rect.left = block.right if we're moving to the left. Afterwards you do the same with the y-axis. We have to do it separately, otherwise we wouldn't know the direction and how to reset the position of the rect.

Here's an example:

import sys
import pygame


def makelevel(level):
    collidelist = []
    for y, line in enumerate(level):
        for x, character in enumerate(line):
            if character == "#":
                block = pygame.Rect(x*64, y*64, 64, 64)
                collidelist.append(block)
    return collidelist


def move(xvel, yvel, player_rect, collideList):
    # Move the rect along the x-axis first.
    player_rect.x += xvel
    # Check if it collides with a block.
    for block in collidelist:
        if player_rect.colliderect(block):
            if xvel < 0:  # We're moving to the left.
                # Move the player out of the block.
                player_rect.left = block.right
            elif xvel > 0:  # We're moving to the right.
                player_rect.right = block.left
            break

    # Now do the same for the y-axis.
    player_rect.y += yvel
    for block in collidelist:
        if player_rect.colliderect(block):
            if yvel < 0:
                player_rect.top = block.bottom
            elif yvel > 0:
                player_rect.bottom = block.top
            break


pygame.init()
screen = pygame.display.set_mode((800, 600))
BLOCK_COLOR = (50, 50, 255)
BG_COLOR = (0, 0, 0)

level = ["#=======##=========================",
         "#=======#==========================",
         "#==###############=========###=====",
         "#===============#####==============",
         "#==================================",
         "###################################",]

collidelist = makelevel(level)
player_rect = pygame.Rect((64*3, 0, 64, 64))
# A clock to limit the frame rate.
clock = pygame.time.Clock()

while True:
    for event in pygame.event.get ():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

    pressed = pygame.key.get_pressed()
    if pressed[pygame.K_RIGHT]:
        move(5, 0, player_rect, collidelist)
    if pressed[pygame.K_LEFT]:
        move(-5, 0, player_rect, collidelist)
    if pressed[pygame.K_UP]:
        move(0, -5, player_rect, collidelist)
    if pressed[pygame.K_DOWN]:
        move(0, 5, player_rect, collidelist)

    # Draw everything.
    screen.fill(BG_COLOR)

    for rect in collidelist:
        pygame.draw.rect(screen, BLOCK_COLOR, rect)

    pygame.draw.rect(screen, (255, 255, 255), player_rect)

    pygame.display.update()
    clock.tick(30)  # Limit frame rate to 30 fps.

There were some other issues with your code that I need to address (even though codereview is offtopic (check out http://codereview.stackexchange.com/)):

  • The most severe problem is that you call makelevel in your main loop, so you append more and more rects to the collidelist until you run out of memory. Create the list inside this function and then return and assign it to a variable above the while loop and just reuse it.

  • It's also not necessary to define the checkline function inside of makelevel.

  • Global variables can have a bad effect on the comprehensibility and maintainability of the code. Rather pass the needed variables to the functions that need them and return the results.

  • Use enumerate instead of incrementing y and x by 1 in makelevel.