Jose Vaz
Jose Vaz

Reputation: 33

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()

Upvotes: 2

Views: 446

Answers (1)

Axe319
Axe319

Reputation: 4365

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')

Upvotes: 2

Related Questions