如何在 Pygame 中为平台游戏添加碰撞检测

69 阅读4分钟

您正在开发一款小型平台游戏,您可以在其中放置积木来创建关卡,然后玩它。您已经实现了重力、跳跃、向左和向右移动,但您不确定如何让玩家在左右移动时与墙壁碰撞。您希望它按以下方式工作:

if key[K_LEFT]:
    if not block to the left:
        move to the left

您该如何实现这一效果(相对于提供的源代码)?

2、解决方案 一种常见的方法是将水平和垂直碰撞处理分成两个独立的步骤。如果您这样做并同时跟踪玩家的速度,即可轻松了解碰撞发生在哪个方面。

首先,我们为玩家添加一些属性来跟踪他的速度:

class Player(object):
    ...
    def __init__(self, x, y):
        self.rect = self.sprite.get_rect(centery=y, centerx=x)
        # 指示我们正站在地面上
        # 因此可以跳跃
        self.on_ground = True 
        self.xvel = 0
        self.yvel = 0
        self.jump_speed = 10
        self.move_speed = 8

现在,我们需要一个方法来实际检查碰撞。如前所述,为了简化操作,我们使用 xvel 和 yvel 来了解我们是否与左侧或右侧等发生碰撞。将其放入 Player 类中:

def collide(self, xvel, yvel, blocks):
    # 我们碰撞到的所有方块
    for block in [blocks[i] for i in self.rect.collidelistall(blocks)]:

        # 如果 xvel > 0,我们知道我们的右侧碰撞
        # 到了一个块的左侧,依此类推
        if xvel > 0: self.rect.right = block.rect.left
        if xvel < 0: self.rect.left = block.rect.right

        # 如果 yvel > 0,我们正在下落,所以如果发生碰撞
        # 我们知道我们撞到了地面(请记住,我们已经将
        # 水平和垂直碰撞检查分开了,因此如果 yvel != 0,xvel 为 0)
        if yvel > 0:
            self.rect.bottom = block.rect.top
            self.on_ground = True
            self.yvel = 0
        # 如果 yvel < 0 并且发生了碰撞,我们在上面碰到一个块
        if yvel < 0: self.rect.top = block.rect.bottom

接下来,我们将移动处理移到 Player 类中。因此,我们创建一个用来跟踪输入的对象。这里,我使用了一个命名元组,因为我们可以做到。

from collections import namedtuple
...
max_gravity = 100
Move = namedtuple('Move', ['up', 'left', 'right'])
while True:
    screen.fill((25,30,90))
    mse = pygame.mouse.get_pos()
    key = pygame.key.get_pressed()

    for event in pygame.event.get():
       ...

    move = Move(key[K_UP], key[K_LEFT], key[K_RIGHT])
    for p in player:
        p.update(move, blocklist)
        screen.blit(p.sprite, p.rect)

我们将其 blocklist 传递给 Player 的 update 方法,以便我们可以检查碰撞情况。使用 move 对象,我们现在知道玩家应该移动到哪里,因此让我们实现 Player.update:

def update(self, move, blocks):

    # 检查我们是否可以跳跃
    if move.up and self.on_ground: 
        self.yvel -= self.jump_speed

    # 简单向左/向右移动
    if move.left: self.xvel = -self.move_speed
    if move.right: self.xvel = self.move_speed

    # 悬浮时向下落
    if not self.on_ground:
        self.yvel += 0.3
        # 但不要过快
        if self.yvel > max_gravity: self.yvel = max_gravity

    # 如果未向左/向右移动,x 速度当然为 0
    if not (move.left or move.right):
        self.xvel = 0

    # 水平移动并检查水平碰撞
    self.rect.left += self.xvel
    self.collide(self.xvel, 0, blocks)

    # 垂直移动并检查垂直碰撞
    self.rect.top += self.yvel
    self.on_ground = False;
    self.collide(0, self.yvel, blocks)

最后,我们还需要使用 Clock 来限制帧速率,使游戏以恒定速度运行。就是这样。

以下是完整代码:

import pygame,random
from pygame.locals import *
from collections import namedtuple

pygame.init()
clock=pygame.time.Clock()
screen=pygame.display.set_mode((640,480))

max_gravity = 100

class Block(object):
    sprite = pygame.image.load("dirt.png").convert_alpha()
    def __init__(self, x, y):
        self.rect = self.sprite.get_rect(centery=y, centerx=x)

class Player(object):
    sprite = pygame.image.load("dirt.png").convert()
    sprite.set_colorkey((0,255,0))
    def __init__(self, x, y):
        self.rect = self.sprite.get_rect(centery=y, centerx=x)
        # 指示我们正站在地面上
        # 因此可以跳跃
        self.on_ground = True
        self.xvel = 0
        self.yvel = 0
        self.jump_speed = 10
        self.move_speed = 8

    def update(self, move, blocks):

        # 检查我们是否可以跳跃
        if move.up and self.on_ground: 
            self.yvel -= self.jump_speed

        # 简单向左/向右移动
        if move.left: self.xvel = -self.move_speed
        if move.right: self.xvel = self.move_speed

        # 悬浮时向下落
        if not self.on_ground:
            self.yvel += 0.3
            # 但不要过快
            if self.yvel > max_gravity: self.yvel = max_gravity

        # 如果未向左/向右移动,x 速度当然为 0
        if not (move.left or move.right):
            self.xvel = 0

        # 水平移动并检查水平碰撞
        self.rect.left += self.xvel
        self.collide(self.xvel, 0, blocks)

        # 垂直移动并检查垂直碰撞
        self.rect.top += self.yvel
        self.on_ground = False;
        self.collide(0, self.yvel, blocks)

    def collide(self, xvel, yvel, blocks):
        # 我们碰撞到的所有方块
        for block in [blocks[i] for i in self.rect.collidelistall(blocks)]:

            # 如果 xvel > 0,我们知道我们的右侧碰撞
            # 到了一个块的左侧,依此类推
            if xvel > 0: self.rect.right = block.rect.left
            if xvel < 0: self.rect.left = block.rect.right

            # 如果 yvel > 0,我们正在下落,所以如果发生碰撞
            # 我们知道我们撞到了地面(请记住,我们已经将
            # 水平和垂直碰撞检查分开了,因此如果 yvel != 0,xvel 为 0)
            if yvel > 0:
                self.rect.bottom = block.rect.top
                self.on_ground = True
                self.yvel = 0
            # 如果 yvel < 0 并且发生了碰撞,我们在上面碰到一个块
            if yvel < 0: self.rect.top = block.rect.bottom

blocklist = []
player = []
colliding = False
Move = namedtuple('Move', ['up', 'left', 'right'])
while True:
    screen.fill((25,30,90))
    mse = pygame.mouse.get_pos()
    key = pygame.key.get_pressed()

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

        if key[K_LSHIFT]:
            if event.type==MOUSEMOTION:
                if not any(block.rect.collidepoint(mse) for block