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