pythonpython-3.xpygamecollisionroguelike

Pygame wall sprite collision


I am making a roguelike game, but I am beginner when it comes to coding. I already have my character moving, my wall and floor sprites, but there is some error in my code that allows the character to move through walls.

I used the block_path to choose between the floor and wall tile and I tried to use it then to recognize the wall but it didn't really work.

Next, you can see my code:

screenWidth = 800
screenHeight = 600
mapHeight = 30
mapWidth = 30
cellWidth = 32
cellHeight = 32

screen = pygame.display.set_mode((screenWidth, screenHeight))
walkRight = [pygame.image.load('model/r1.png'), pygame.image.load('model/r2.png'), pygame.image.load('model/r3.png'), pygame.image.load('model/r4.png'), pygame.image.load('model/r5.png'), pygame.image.load('model/r6.png')]
walkLeft = [pygame.image.load('model/l1.png'), pygame.image.load('model/l2.png'), pygame.image.load('model/l3.png'), pygame.image.load('model/l4.png'), pygame.image.load('model/l5.png'), pygame.image.load('model/l6.png')]
walkUp = [pygame.image.load('model/u1.png'), pygame.image.load('model/u2.png'), pygame.image.load('model/u3.png'), pygame.image.load('model/u4.png'), pygame.image.load('model/u5.png'), pygame.image.load('model/u6.png')]
Floor = pygame.image.load("map/floor.jpg")
wallRight = pygame.image.load("map/rightwall.png")

`

class struc_Tile():
    def __init__(self,block_path):
        self.block_path = block_path`

class player(object):
    def __init__(self,x,y,width,height):
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.vel = 5
        self.left = False
        self.right = False
        self.up = False
        self.down = False
        self.walkCount = 0


    def draw(self,screen):
        if self.walkCount + 1 >= 18:
            self.walkCount = 0

        elif self.left:
            screen.blit(walkLeft[self.walkCount//3], (self.x,self.y))
            self.walkCount += 1
        elif self.right:
            screen.blit(walkRight[self.walkCount//3], (self.x,self.y))
            self.walkCount += 1
        elif self.up:
            screen.blit(walkUp[self.walkCount//3], (self.x,self.y))
            self.walkCount += 1
        elif self.down:
            screen.blit(walkDown[self.walkCount//3], (self.x,self.y))
            self.walkCount += 1
        else:
            screen.blit(Standing[self.walkCount//3], (self.x,self.y))
            self.walkCount = 0

    def move(self,dx,dy):
        if gamemap[self.x + dx][self.y + dy].block_path == False:
            self.x += dx
            self.y += dy

def createmap():
    newmap = [[struc_Tile(False) for y in range(0,mapHeight)] for x in range (0,mapWidth) ]
    newmap[10][10].block_path = True
    newmap[10][15].block_path = True

    return newmap
        def drawmap(maptodraw):

    for x in range(0,mapWidth):
        for y in range(0,mapHeight):
            if maptodraw[x][y].block_path == True:
                screen.blit(wallRight, (x*cellWidth, y*cellHeight))
            else:
                screen.blit(Floor, (x*cellWidth, y*cellHeight)
def redrawgamewindow():
screen.blit(bg, (0, 0))
drawmap(gamemap)
character.draw(screen)
pygame.display.update()

pygame.init()
gamemap = createmap()
clock = pygame.time.Clock()
character = player(0, 0, 32,32)

run = True
while run:
clock.tick(18)

for event in pygame.event.get():
    if event.type == pygame.QUIT:

        run = False

keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and character.x > character.vel:
    character.x -= character.vel
    character.left = True
    character.right = False
    character.up = False
    character.down = False
    character.standing = False

elif keys[pygame.K_RIGHT] and character.x < 800 -character.width - character.vel:
    character.x += character.vel
    character.left = False
    character.right = True
    character.up = False
    character.down = False
    character.standing = False

elif keys[pygame.K_UP] and character.y > character.vel:
    character.y -= character.vel
    character.left = False
    character.right = False
    character.up = True
    character.down = False
    character.standing = False
elif keys[pygame.K_DOWN] and character.y < 600 - character.height - character.vel:
    character.y += character.vel
    character.left = False
    character.right = False
    character.up = False
    character.down = True
    character.standing = False
else:
    character.right = False
    character.left = False
    character.up = False
    character.down = False
    character.standing = True

redrawgamewindow()

Solution

  • Changing your createmap() function to something like this will create a 5 pixel buffer on the boreder of your map. The reason I made it 5 pixels is because that's what your character movement is.

    I would suggest putting in some actual character collision to test whether you're out of bounds regardless of speed though.

    EDIT: I've included the full code since the changes I've made are easier to understand if I show them.

    I've changed your image lists to a dict of list since they're easier to call that way. The grid you asked for is 30x30. Since you showed a 32x32 cell width I changed the map to 960 x 960.

    I've extended your move method to check for collision to see if it can move before it does. I've also removed your redraw() function since it was easier to just move your redraw down. If you wish, you can add it back in but for this example, I removed it.

    import pygame
    
    # Based on the paths in your image
    # This should work for your image paths
    image = pygame.image.load('model/standing1.png')
    Standing = pygame.transform.scale(image, (32, 32))
    image = pygame.image.load('map/floor.jpg')
    Floor = pygame.transform.scale(image, (32, 32))
    image = pygame.image.load('map/rightwall.png')
    wallRight = pygame.transform.scale(image, (32, 32))
    
    walkLeft = []
    walkRight = []
    walkUp = []
    walkDown = []
    
    for i in range(1, 19):
        image = pygame.image.load('model/r' + str(i) + '.png')
        walkRight.append(pygame.transform.scale(image, (32, 32)))
    
        image = pygame.image.load('model/l' + str(i) + '.png')
        walkLeft.append(pygame.transform.scale(image, (32, 32)))
    
        image = pygame.image.load('model/u' + str(i) + '.png')
        walkUp.append(pygame.transform.scale(image, (32, 32)))
    
        image = pygame.image.load('model/d' + str(i) + '.png')
        walkDown.append(pygame.transform.scale(image, (32, 32)))
    
    class struc_Tile():
        def __init__(self, block_path):
            self.block_path = block_path
    
    class player(object):
        def __init__(self,x,y,width,height):
            self.x = x
            self.y = y
            self.width = width
            self.height = height
            self.vel = 5
            self.walkCount = 0
    
            # do something like this with your images
            # if you keep your lists in a dict you can avoid
            # all your boolean direction variables and 
            # the lengthy if else statement
            self.images = {}
            self.images['walkleft'] = walkLeft[:]
            self.images['walkright'] = walkRight[:]
            self.images['walkup'] = walkUp[:]
            self.images['walkdown'] = walkDown[:]
    
    
        def draw(self, screen, direction):
            if self.walkCount + 1 >= 18:
                self.walkCount = 0
    
            # since standing is not in your dict check for that first
            if direction == 'standing':
                screen.blit(Standing, (self.x,self.y))
                self.walkCount = 0
            else:
                screen.blit(self.images[direction][self.walkCount], (self.x,self.y))
                self.walkCount += 1
    
    
        def can_move(self, dx, dy):
            # with the buffer created around the border of the map
            # you shouldn't have issues with
            # index out of bounds exceptions
            # EDIT: added better collision
            new_x = self.x + dx
            new_y = self.y + dy
            if gamemap[new_x][new_y].block_path == False:
                if gamemap[new_x + cellWidth][new_y].block_path == False:
                    if gamemap[new_x][new_y + cellHeight].block_path == False:
                        if gamemap[new_x + cellWidth][new_y + cellHeight].block_path == False:
                            self.x += dx
                            self.y += dy
                            return True
    
    def createmap():
        newmap = [[struc_Tile(False) for y in range(0, mapHeight)] for x in range (0,mapWidth)]
    
        # give our upper/left borders a cell width buffer
        # and our bottom/right borders a 2 cell width buffer
        # since blit uses the upper left corner this should account
        # for the sprite width
        # EDIT: Fixed this to accommodate the better collision
        for x in range(0, mapWidth):
            for y in range (0, mapHeight):
                if y < 32 or y + cellWidth >= mapHeight:
                    newmap[x][y].block_path = True
                elif x < 32 or x + cellWidth >= mapWidth:
                    newmap[x][y].block_path = True
    
    
        return newmap
    
    def drawmap(maptodraw):
        # only blit at cellwidth and height intervals
        for x in range(0, mapWidth, cellWidth):
            for y in range(0, mapHeight, cellHeight):
                if maptodraw[x][y].block_path == True:
                    screen.blit(wallRight, (x, y))
                else:
                    screen.blit(Floor, (x, y))
    
    # Added this function which lets you block or unblock a cell
    # simply call like gamemap = block_element(5, 10, gamemap)
    # this will block the 6th cell on the x axis and 11th on the y axis
    # to unblock a cell call it with block=False
    def block_element(x, y, maptoblock, block=True):
        x_cells = int(mapWidth / cellWidth)
        y_cells = int(mapHeight / cellHeight)
    
        start_x = int(x * cellWidth)
        start_y = int(y * cellHeight)
    
        end_x = start_x + cellWidth
        end_y = start_y + cellHeight
    
        print(start_x, end_x)
    
        if x >= 0 and x < x_cells:
            if y >= 0 and y < y_cells:
                for x in range(start_x, end_x):
                    for y in range(start_y, end_y):
                        maptoblock[x][y].block_path = block
    
        return maptoblock   
    
    pygame.init()
    
    mapHeight = 960
    mapWidth = 960
    
    cellWidth = 32
    cellHeight = 32
    
    screen = pygame.display.set_mode((mapWidth, mapHeight))
    
    gamemap = createmap()
    # blocking/unblocking example
    gamemap = block_element(5, 10, gamemap)
    gamemap = block_element(0, 8, gamemap, block=False)
    gamemap = block_element(0, 9, gamemap, block=False)
    
    clock = pygame.time.Clock()
    character = player(64, 64, 32, 32)
    
    run = True
    while run:
        clock.tick(18)
        pygame.display.update()
    
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
    
                run = False
        # I just used fill since I didn't have a bg image handy
        screen.fill((0, 0, 0))
        drawmap(gamemap)
    
        # handle the keypresses
        # first check if the character can move that much
        # then draw the movement
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT] and character.can_move(0 - character.vel, 0):
            character.draw(screen, 'walkleft')
        elif keys[pygame.K_RIGHT] and character.can_move(character.vel, 0):
            character.draw(screen, 'walkright')
        elif keys[pygame.K_UP] and character.can_move(0, 0 - character.vel):
            character.draw(screen, 'walkup')
        elif keys[pygame.K_DOWN] and character.can_move(0, character.vel):
            character.draw(screen, 'walkdown')
        else:
            character.draw(screen, 'standing')