Hydro
Hydro

Reputation: 281

Collision Detection without Coordinates in Tilemaps in Pygame

I am programming a Bomberman game, right now I don't have any sprites but I use rectangles for it. This is my code:

import pygame

pygame.init()

WinWidth = 800
WinHeight = 608
p1x = 32
p1y = 32
vel = 1
clock = pygame.time.Clock()

win = pygame.display.set_mode((WinWidth, WinHeight))

def player():
    pygame.draw.rect(win, (0, 200, 200), (p1x, p1y, 32, 32))


player_rect = pygame.Rect(p1x, p1y, tlw, tlh)


class Wall:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        pygame.draw.rect(win, (50, 50, 50), (x, y, 32, 32))


class Breakable:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        pygame.draw.rect(win, (200, 150, 100), (x, y, 32, 32))


game_map1 = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
             [0, 10, 10, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 10, 10, 0],
             [0, 10, 0, 2, 0, 2, 0, 2, 0, 0, 0, 2, 0, 2, 0, 2, 0, 10, 0],
             [0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0],
             [0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0],
             [0, 2, 2, 2, 2, 10, 10, 2, 2, 10, 2, 2, 10, 10, 2, 2, 2, 2, 0],
             [0, 2, 0, 2, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 2, 0, 2, 0],
             [0, 2, 2, 2, 2, 2, 0, 2, 2, 10, 2, 2, 0, 2, 2, 2, 2, 2, 0],
             [0, 2, 0, 2, 0, 2, 0, 2, 10, 10, 10, 2, 0, 2, 0, 2, 0, 2, 0],
             [0, 0, 0, 2, 2, 10, 10, 10, 10, 10, 10, 10, 10, 10, 2, 2, 0, 0, 0],
             [0, 2, 0, 2, 0, 2, 0, 2, 10, 10, 10, 2, 0, 2, 0, 2, 0, 2, 0],
             [0, 2, 2, 2, 2, 2, 0, 2, 2, 10, 2, 2, 0, 2, 2, 2, 2, 2, 0],
             [0, 2, 0, 2, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 2, 0, 2, 0],
             [0, 2, 2, 2, 2, 10, 10, 2, 2, 10, 2, 2, 10, 10, 2, 2, 2, 2, 0],
             [0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0],
             [0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0],
             [0, 10, 0, 2, 0, 2, 0, 2, 0, 0, 0, 2, 0, 2, 0, 2, 0, 10, 0],
             [0, 10, 10, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 10, 10, 0],
             [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]

run = True
while run:

    pygame.init()

    clock.tick(100)
    win.fill((0, 255, 200))
    y = 0
    for layer in game_map1:
        x = 0
        for tile in layer:
            if tile == 0:
                Wall(x * 32, y * 32)
                wall_rect = pygame.Rect(x * 32, y * 32, tlw, tlh)
            if tile == 10:
                pygame.Rect(x * 32, y * 32, 32, 32)
            if tile == 2:
                Breakable(x * 32, y * 32)
            x += 1
        y += 1

    player()

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            run = False
        if event.type == pygame.KEYDOWN:
    if event.key == pygame.K_d:
        if player_rect.colliderect(wall_rect):
            p1x *= -1
            p1y *= -1
        else:
            p1x += vel
    elif event.key == pygame.K_a:
        if player_rect.colliderect(wall_rect):
            p1x *= -1
            p1y *= -1
        else:
            p1x -= vel
    if event.key == pygame.K_s:
        if player_rect.colliderect(wall_rect):
            p1x *= -1
            p1y *= -1
        else:
            p1y += vel
    elif event.key == pygame.K_w:
        if player_rect.colliderect(wall_rect):
            p1x *= -1
            p1y *= -1
        else:
            p1y -= vel
        

    pygame.display.update()

The x and y are at the top-left of the player rectangle - They are p1x and p1y.

Width and Height are tlw and tlh.

The Wall and Breakable are using tlw and tlh, too and they do not have their specific coordinates like the player has.

sooo... I watched a tutorial (https://www.youtube.com/watch?v=HCWI2f7tQnY&t=2s) and even tried to make a collision detection on my own. But I just can't do it. I don't have coordinates for the rectangles in the tilemap and there are very much rectangles in the tilemap, too. How could you make a collision detection with a tilemap with no coordinates and many rectangles? And yeah, the game doesn't have much stuff going on for now... I tried it with the pygame.Rect's player_rect and wall_rect and then using (colliderect) but it didn't work and how can you make it the player_rect collides with ALL wall_rect's and not only with one.

My question is: How do I make a collision detection with the tilemaps with colliderect?

Upvotes: 1

Views: 161

Answers (1)

user12291970
user12291970

Reputation:

I have refactored the entire code because the code you posted throws an error and i couldn't test my code. The way we have implemented collision detection here works but is not really effective. If you want to know which side is colliding so you can implement the game logic, then colliderect is quite pointless. I suggest you ask a new question on how to build custom collision detection, since it's an entirely new topic but the following code solves the original question of how to implement collision detection using colliderect. The following example also shows how draw functions should be a separate method, which is the better way to do it. Btw, if tile == 10 you had put pygame.Rect(x * 32, y * 32, 32, 32) in your question which basically does nothing, so i put pass. If you want it to be a new type of wall, you can make a new class similar to Breakable and Wall. Final answer, hope it helped :).

import pygame

pygame.init()

WinWidth = 800
WinHeight = 608
clock = pygame.time.Clock()

win = pygame.display.set_mode((WinWidth, WinHeight))

class Player:
    def __init__(self):
        self.x = 32
        self.y = 32
        self.width = 20
        self.height = 20
        self.vel = 0.1

    def draw(self):
        pygame.draw.rect(win, (0, 200, 200), (self.x, self.y, 32, 32))

    def getRect(self):
        return pygame.Rect(self.x, self.y, self.width, self.height)


class Wall:
    def __init__(self, rect):
        self.rect =  rect
        self.x = rect.x
        self.y = rect.y
   
    def draw(self):
        pygame.draw.rect(win, (50, 50, 50), self.rect)

    def getRect(self):
        return pygame.Rect(self.x, self.y, 32, 32)


class Breakable:
    def __init__(self, rect):
        self.rect = rect
        self.x = rect.x
        self.y = rect.y

    def draw(self):
        pygame.draw.rect(win, (200, 150, 100), self.rect)

    def getRect(self):
        return pygame.Rect(self.x, self.y, 32, 32)


game_map1 = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
             [0, 10, 10, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 10, 10, 0],
             [0, 10, 0, 2, 0, 2, 0, 2, 0, 0, 0, 2, 0, 2, 0, 2, 0, 10, 0],
             [0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0],
             [0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0],
             [0, 2, 2, 2, 2, 10, 10, 2, 2, 10, 2, 2, 10, 10, 2, 2, 2, 2, 0],
             [0, 2, 0, 2, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 2, 0, 2, 0],
             [0, 2, 2, 2, 2, 2, 0, 2, 2, 10, 2, 2, 0, 2, 2, 2, 2, 2, 0],
             [0, 2, 0, 2, 0, 2, 0, 2, 10, 10, 10, 2, 0, 2, 0, 2, 0, 2, 0],
             [0, 0, 0, 2, 2, 10, 10, 10, 10, 10, 10, 10, 10, 10, 2, 2, 0, 0, 0],
             [0, 2, 0, 2, 0, 2, 0, 2, 10, 10, 10, 2, 0, 2, 0, 2, 0, 2, 0],
             [0, 2, 2, 2, 2, 2, 0, 2, 2, 10, 2, 2, 0, 2, 2, 2, 2, 2, 0],
             [0, 2, 0, 2, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 2, 0, 2, 0],
             [0, 2, 2, 2, 2, 10, 10, 2, 2, 10, 2, 2, 10, 10, 2, 2, 2, 2, 0],
             [0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0],
             [0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0],
             [0, 10, 0, 2, 0, 2, 0, 2, 0, 0, 0, 2, 0, 2, 0, 2, 0, 10, 0],
             [0, 10, 10, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 10, 10, 0],
             [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]


walls = []
y = 0
for layer in game_map1:
    x = 0
    for tile in layer:
        if tile == 0:
            walls.append(Wall(pygame.Rect([x*32, y*32, 32, 32])))
        if tile == 10:
            pass
        if tile == 2:
            walls.append(Breakable(pygame.Rect([x*32, y*32, 32, 32])))
        x += 1
    y += 1


player = Player()
pygame.init()
run = True
while run:

    clock.tick(100)
    win.fill((0, 255, 200))

    for i in range(len(walls)):
        walls[i].draw()

    player.draw()
        
    for wall in walls:
        if player.getRect().colliderect(wall.getRect()): #getRect is the method we added to wall class earlier
             print("they are colliding")    

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

        if event.type == pygame.KEYDOWN:
            for wall in walls:
                wall_rect = wall.getRect()
                if event.key == pygame.K_d:
                    if player.getRect().colliderect(wall_rect):
                        player.x *= -1
                        player.y *= -1
                    else:
                        player.x += player.vel
                elif event.key == pygame.K_a:
                    if player.getRect().colliderect(wall_rect):
                        player.x *= -1
                        player.y *= -1
                    else:
                        player.x -= player.vel
                if event.key == pygame.K_s:
                    if player.getRect().colliderect(wall_rect):
                        player.x *= -1
                        player.y *= -1
                    else:
                        player.y += player.vel
                elif event.key == pygame.K_w:
                    if player.getRect().colliderect(wall_rect):
                        player.x *= -1
                        player.y *= -1
                    else:
                        player.y -= player.vel
                    

    pygame.display.update()

Regarding your last question:

#Checks if right side of rect1 is colliding with left side of rect2
def rightCheck(rect1, rect2):
    if rect1.x + rect1.width > rect2.x:
        if (rect1.y > rect2.y and rect1.y < rect2.y + rect2.height) or (rect1.y + rect1.height < rect2.y + rect2.height and rect1.y + rect1.height> rect2.y):
            if rect1.x < rect2.x:
                return True
    return False

#Checks if top side of rect1 is colliding with bottom side of rect2
def topCheck(rect1, rect2):
    if rect1.y < rect2.y + rect2.height:
        if (rect1.x > rect2.x and rect1.x < rect2.x + rect2.width) or (rect1.x + rect1.width > rect2.x and rect1.x + rect1.width < rect2.x + rect2.width):
            if rect1.y > rect2.y:
                return True
    return False
 
#Checks if bottom side of rect1 is colliding with top side of rect2
def botCheck(rect1, rect2):
    if rect1.y + rect1.height > rect2.y:
        if (rect1.x > rect2.x and rect1.x < rect2.x + rect2.width) or (rect1.x + rect1.width > rect2.x and rect1.x + rect1.width < rect2.x + rect2.width):
            if rect1.y < rect2.y:
                return True
    return False

These are custom collision detection functions you can use. I haven't got the one for left side of the player (rect1) but i will try to make it later or you can try and make one as well. You can try to improve the ones i have provided to fit your game as well. Using these you can figure out exactly which side is colliding, and add useful logic. Example:

#have different velocities for every side
player_right_speed = 2
player_left_speed = 2

# Say that your player moves according to the following key presses
key = pygame.key.get_pressed()
if key[pygame.K_RIGHT]:
   playerx += player_right_speed
if key[pygame.K_LEFT]:
   playerx -= player_left_speed

#you can call the collision detection functions to check if they are colliding and if they are set the speed in that direction to 0
#notice these functions take pygame.Rect as an argument
right_colliding = rightCheck(playerRect, tileRect)
if right_colliding:
   player_right_speed = 0
left_colliding = leftCheck(playerRect, tileRect)
if left_colliding:
   player_left_speed = 0

Upvotes: 2

Related Questions