Pygame 中的 Tilemaps 中没有坐标的碰撞检测

Collision Detection without Coordinates in Tilemaps in Pygame

我正在编写一个炸弹人游戏,现在我没有任何精灵,但我使用了矩形。 这是我的代码:

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

xy 位于播放器矩形的左上角 - 它们是 p1xp1y

宽度和高度分别为 tlwtlh

The Wall 和 Breakable 也在使用 tlwtlh,它们没有像玩家那样的特定坐标。

sooo...我看了教程 (https://www.youtube.com/watch?v=HCWI2f7tQnY&t=2s),甚至尝试自己进行碰撞检测。但我就是做不到。我没有 tilemap 中矩形的坐标,而且 tilemap 中也有很多矩形。如何使用没有坐标且有许多矩形的瓦片地图进行碰撞检测?是的,游戏目前没有太多内容......我尝试使用 pygame.Rect 的 player_rectwall_rect 然后使用 (colliderect) 但是它没有用,你怎么能让 player_rect 与所有 wall_rect 发生碰撞,而不仅仅是与一个发生碰撞。

我的问题是:如何使用 colliderect 对瓦片地图进行碰撞检测?

我已经重构了整个代码,因为你发布的代码抛出了一个错误,我无法测试我的代码。我们在这里实现碰撞检测的方法有效但不是很有效。如果你想知道哪一侧发生碰撞以便实现游戏逻辑,那么 colliderect 就毫无意义了。我建议你问一个关于如何构建自定义碰撞检测的新问题,因为这是一个全新的主题,但下面的代码解决了如何使用 colliderect 实现碰撞检测的原始问题。下面的示例还展示了绘制函数应该如何成为一个单独的方法,这是更好的方法。顺便说一句,如果 tile == 10 你在你的问题中输入了 pygame.Rect(x * 32, y * 32, 32, 32) 基本上什么都不做,所以我输入 pass。如果你想让它成为一种新型的墙,你可以制作一个类似于BreakableWall的新class。最后的回答,希望对你有帮助:).

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

关于你的最后一个问题:

#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

这些是您可以使用的自定义碰撞检测函数。我还没有播放器左侧 (rect1) 的那个,但我稍后会尝试制作它,或者您也可以尝试制作一个。您也可以尝试改进我提供的内容以适合您的游戏。使用这些你可以准确地找出哪一侧发生碰撞,并添加有用的逻辑。示例:

#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