Pygame sprites - 获取矩形碰撞面
Pygame sprites - get the rect collision side
我是 pygame 的新人,我正在努力学习基础知识。我想创建障碍物并检测玩家矩形(代表精灵的碰撞框)的哪一侧与障碍物矩形发生碰撞。有了这个,我可以在游戏中创建基本物理。
这是我所做的:
import pygame
class Player (pygame.sprite.Sprite):
def __init__(self, x=0, y=0, s=100):
super(Player,self).__init__()
self.image = pygame.transform.scale(pygame.image.load("player.png"), (s, s))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
class Block (pygame.sprite.Sprite):
def __init__(self, x=0, y=500, s=100):
super(Block, self).__init__()
self.image = pygame.transform.scale(pygame.image.load("wall.png"),(s, s))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
pygame.init()
display = pygame.display.set_mode((800,600))
player = Player()
block = Block()
sprites = pygame.sprite.Group()
living = pygame.sprite.Group()
noliving = pygame.sprite.Group()
sprites.add(player)
sprites.add(block)
living.add(player)
noliving.add(block)
gravity = 1
xCh = 0
yCh = 0
speed = 1
while True:
display.fill((159, 159, 159))
for liv in living:
for noliv in noliving:
if(not pygame.sprite.collide_rect(liv, noliv)):
player.rect.y += gravity
for event in pygame.event.get():
if(event.type == pygame.KEYDOWN):
if(event.key == pygame.K_ESCAPE):
quit()
elif(event.key == pygame.K_a or event.key == pygame.K_LEFT):
xCh = -speed
elif(event.key == pygame.K_d or event.key == pygame.K_RIGHT):
xCh = speed
elif(event.type == pygame.KEYUP):
xCh = 0
yCh = 0
elif(event.type == pygame.QUIT):
quit()
player.rect.x += xCh
sprites.draw(display)
pygame.display.update()
玩家在碰到方块时停止掉落,但如果我向左或向右移动然后进入方块,玩家会直接进入方块并从那里停止掉落。我希望玩家无法穿过街区。怎么做?
最后一个版本是这样的:
import pygame
class GameObj(pygame.sprite.Sprite):
def __init__(self, image, x, y, s):
super(GameObj, self).__init__()
self.image = pygame.transform.scale(pygame.image.load("img/"+ image), (s, s))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
class Player(GameObj):
leftUnable = False
rightUnable = False
jumpHeight = 200
jumpSpeed = 5
image = "player.png"
inAir = True
def __init__(self, x, y, s=100):
super(Player,self).__init__(self.image, x, y, s)
class Block(GameObj):
image = "wall.png"
def __init__(self, x, y, s=100):
super(Block, self).__init__(self.image, x, y, s)
def collideNum(sprite, group):
total = 0
for member in group:
if(pygame.sprite.collide_rect(sprite, member)):
total += 1
return total
def setup():
pygame.init()
global display, player, block, sprites, living, noliving, clock
display = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Test")
clock = pygame.time.Clock()
player = Player(100, 0)
block = Block(100, 300)
block1 = Block(200, 400)
block2 = Block(400, 400)
sprites = pygame.sprite.Group()
living = pygame.sprite.Group()
noliving = pygame.sprite.Group()
noliving.add(block)
noliving.add(block1)
noliving.add(block2)
living.add(player)
for liv in living:
sprites.add(liv)
for noliv in noliving:
sprites.add(noliv)
main()
def main():
speed = 5
gravity = 5
xCh, yCh = 0, 0
player.leftUnable = False
player.rightUnable = False
while True:
clock.tick(60)
display.fill((184, 184, 184))
yCh = gravity
for event in pygame.event.get():
if(event.type == pygame.QUIT):
quit()
elif(event.type == pygame.KEYDOWN):
if(event.key == pygame.K_ESCAPE):
quit()
elif((event.key == pygame.K_a or event.key == pygame.K_LEFT) and not player.leftUnable):
for noliv in noliving:
if(pygame.sprite.collide_rect(player, noliv)):
if(noliv.rect.left < player.rect.left < noliv.rect.right and player.rect.bottom > noliv.rect.top + 5):
player.leftUnable = True
break
else:
xCh = -speed
player.leftUnable = False
else:
xCh = -speed
player.leftUnable = False
elif((event.key == pygame.K_d or event.key == pygame.K_RIGHT) and not player.rightUnable):
for noliv in noliving:
if(pygame.sprite.collide_rect(player, noliv)):
if(noliv.rect.left < player.rect.right < noliv.rect.right and player.rect.bottom > noliv.rect.top + 5):
player.rightUnable = True
break
else:
xCh = speed
player.rightUnable = False
else:
xCh = speed
player.rightUnable = False
elif(event.key == pygame.K_SPACE or event.key == pygame.K_w or event.key == pygame.K_UP):
oldPos = player.rect.bottom
xCh = 0
if(not player.inAir):
while player.rect.bottom > oldPos - player.jumpHeight:
clock.tick(60)
display.fill((184, 184, 184))
for ev in pygame.event.get():
if(ev.type == pygame.KEYDOWN):
if(ev.key == pygame.K_d or ev.key == pygame.K_RIGHT):
xCh = speed
elif(ev.key == pygame.K_a or ev.key == pygame.K_LEFT):
xCh = -speed
elif(ev.type == pygame.KEYUP):
xCh = 0
player.rect.x += xCh
player.rect.y -= player.jumpSpeed
player.inAir = True
sprites.draw(display)
pygame.display.update()
elif(event.type == pygame.KEYUP):
xCh = 0
for liv in living:
for noliv in noliving:
if(pygame.sprite.collide_rect(liv, noliv)):
liv.inAir = False
break
else:
liv.inAir = True
for noliv in noliving:
if(pygame.sprite.collide_rect(player, noliv)):
if(noliv.rect.left < player.rect.left < noliv.rect.right and player.rect.bottom > noliv.rect.top + 5):
player.leftUnable = True
if(collideNum(player, noliving) == 1):
player.inAir = True
if(xCh < 0):
xCh = 0
elif(noliv.rect.left < player.rect.right < noliv.rect.right and player.rect.bottom > noliv.rect.top + 5):
player.rightUnable = True
if(collideNum(player, noliving) == 1):
player.inAir = True
if(xCh > 0):
xCh = 0
else:
player.leftUnable = False
player.rightUnable = False
else:
player.leftUnable = False
player.rightUnable = False
if(not player.inAir):
yCh = 0
if(player.rect.top > display.get_size()[1]):
setup()
player.rect.x += xCh
player.rect.y += yCh
sprites.draw(display)
pygame.display.update()
setup()
它运行良好。
这是您的代码和建议的修复,以及其他一些改进建议。
首先要注意的是:错误的是你从来没有检查水平移动是否会使块碰撞。你只有一个检查,它实际上只在 发生任何碰撞后才做任何事情。在这种情况下,它会阻止 "gravity" 做任何事情。由于您是逐像素更新播放器,因此碰撞已经发生的事实几乎没有引起注意。解决方法是创建一个检查来验证移动块 是否可以 移动到某个地方,然后才允许它。并且对所有运动都这样做,而不仅仅是重力运动。
所以,在这里我将解释可以让这个示例代码演变成一个完整游戏的改进。
在模块级别消除 "floating code" 并将所有内容放入函数中。这对于未来任何游戏来说都是必不可少的,即使是像开始屏幕一样简单的东西(也可能是 "play again" 屏幕)。我做了两个功能,一个创建设置
你的游戏环境和创建你的对象,以及主要的游戏功能。以后你应该把 "setup" 阶段进一步分开,
因为能够创建更多游戏对象或不同的游戏关卡。
重用您的代码。您的 __init__
方法中的行大部分在两个 class 之间重复,但对于初始位置和图像名称常量。所以您实际上只需要一个 class,并为这些属性传递不同的值。由于您暗示您将通过使用组等获得两种不同的游戏类型的对象,因此我保留了两个 classes,但分解出了通用代码。
帧延迟:您的代码会简单地移动 "as fast as possible" - 这会在不同的计算机上创建不同的游戏速度,并使用 100% 的 CPU - 导致可能的机器减速和过度用电。我加入了 30 毫秒的硬编码暂停 - 这将使您的游戏以接近 30FPS 的速度移动,在不同设备上保持速度稳定。另一个需要改变的是你硬编码的“1”重力和速度值,这将使玩家一次移动 1px 必须更改为更大的值。
最后但并非最不重要的一点是,"can I move there" 逻辑:它实际上看起来很广泛 - 但感觉 "can this object move to that position" 检查的简单英语描述很冗长。该方法本质上是:"Store the current position"。 "Fake a position on with the given x and y displacements"。 "check if I am colliding with something"。 "if not, restore the previous position, and say the movement is allowed"。 "otherwise, restore the y position, check if there is a collision in the X direction, if so, restrict horizontal movement"。 "Restore the x position, move the y position by desired amount, check if collision happens. If so, restrict movement in the y direction"。 "If movement is not restricted in either x or y, it is just the combined movement that causes the collision: restrict movement in both directions to be fair"。 "Restore original position, return allowed changes to x and y positions, and the object we'd collide with"。所有这些逻辑都放在 "living" 对象 class 本身中,因此可以随时使用,主游戏逻辑中只有一行代码。
代码:
import pygame
class GameObject(pygame.sprite.Sprite):
def __init__(self, x=0, y=0, s=100, image=""):
super(GameObject, self).__init__()
if not image:
image = self.image
self.image = pygame.transform.scale(pygame.image.load(image), (s, s))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
class Player(GameObject):
image = "player.png"
def can_move(self, xCh, yCh, group):
old_pos = self.rect.x, self.rect.y
self.rect.x += xCh
self.rect.y += yCh
collided_with = pygame.sprite.spritecollideany(self, group)
if not collided_with:
# No Collisions at all - allow movement
self.rect.x = old_pos[0]
self.rect.y = old_pos[1]
return True, xCh, yCh, None
# Check if the colision was due to horizontal movement:
self.rect.y = old_pos[1]
if pygame.sprite.spritecollideany(self, group):
# Yes, then indicate horizontal movement should be 0
xCh = 0
# check if collision was due to vertical movement:
self.rect.y += yCh
self.rect.x = old_pos[0]
if pygame.sprite.spritecollideany(self, group):
# Yes - indicate vertical movemnt should be 0
yCh = 0
# if only diagonal movement causes collision, then
# cancel movrment in both axes:
# (i.e. just the diagonal movement would hit the
# obstacle )
if not xCh == 0 and not yCh == 0:
xCh = 0
yCh = 0
self.rect.x = old_pos[0]
self.rect.y = old_pos[1]
return False, xCh, yCh, collided_with
class Block(GameObject):
image = "wall.png"
def setup():
global display, player, living, noliving, gravity, speed, sprites
pygame.init()
display = pygame.display.set_mode((800,600))
player = Player()
block = Block(y=500)
sprites = pygame.sprite.Group()
living = pygame.sprite.Group()
noliving = pygame.sprite.Group()
sprites.add(player)
sprites.add(block)
living.add(player)
noliving.add(block)
gravity = 10
speed = 10
def main():
xCh = 0
yCh = 0
while True:
display.fill((159, 159, 159))
for event in pygame.event.get():
if(event.type == pygame.KEYDOWN):
if(event.key == pygame.K_ESCAPE):
quit()
elif(event.key == pygame.K_a or event.key == pygame.K_LEFT):
xCh = -speed
elif(event.key == pygame.K_d or event.key == pygame.K_RIGHT):
xCh = speed
elif(event.type == pygame.KEYUP):
xCh = 0
yCh = 0
elif(event.type == pygame.QUIT):
quit()
yCh = gravity
for liv in living:
if liv == player:
check_x_ch = xCh
else:
check_x_ch = 0
can_move, xCh, yCh, obstacle = liv.can_move(xCh, yCh, noliving)
liv.rect.x += xCh
liv.rect.y += yCh
# Do other actions if "can_move" indicates a block was hit...
sprites.draw(display)
pygame.display.update()
pygame.time.delay(30)
setup()
main()
我是 pygame 的新人,我正在努力学习基础知识。我想创建障碍物并检测玩家矩形(代表精灵的碰撞框)的哪一侧与障碍物矩形发生碰撞。有了这个,我可以在游戏中创建基本物理。
这是我所做的:
import pygame
class Player (pygame.sprite.Sprite):
def __init__(self, x=0, y=0, s=100):
super(Player,self).__init__()
self.image = pygame.transform.scale(pygame.image.load("player.png"), (s, s))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
class Block (pygame.sprite.Sprite):
def __init__(self, x=0, y=500, s=100):
super(Block, self).__init__()
self.image = pygame.transform.scale(pygame.image.load("wall.png"),(s, s))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
pygame.init()
display = pygame.display.set_mode((800,600))
player = Player()
block = Block()
sprites = pygame.sprite.Group()
living = pygame.sprite.Group()
noliving = pygame.sprite.Group()
sprites.add(player)
sprites.add(block)
living.add(player)
noliving.add(block)
gravity = 1
xCh = 0
yCh = 0
speed = 1
while True:
display.fill((159, 159, 159))
for liv in living:
for noliv in noliving:
if(not pygame.sprite.collide_rect(liv, noliv)):
player.rect.y += gravity
for event in pygame.event.get():
if(event.type == pygame.KEYDOWN):
if(event.key == pygame.K_ESCAPE):
quit()
elif(event.key == pygame.K_a or event.key == pygame.K_LEFT):
xCh = -speed
elif(event.key == pygame.K_d or event.key == pygame.K_RIGHT):
xCh = speed
elif(event.type == pygame.KEYUP):
xCh = 0
yCh = 0
elif(event.type == pygame.QUIT):
quit()
player.rect.x += xCh
sprites.draw(display)
pygame.display.update()
玩家在碰到方块时停止掉落,但如果我向左或向右移动然后进入方块,玩家会直接进入方块并从那里停止掉落。我希望玩家无法穿过街区。怎么做?
最后一个版本是这样的:
import pygame
class GameObj(pygame.sprite.Sprite):
def __init__(self, image, x, y, s):
super(GameObj, self).__init__()
self.image = pygame.transform.scale(pygame.image.load("img/"+ image), (s, s))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
class Player(GameObj):
leftUnable = False
rightUnable = False
jumpHeight = 200
jumpSpeed = 5
image = "player.png"
inAir = True
def __init__(self, x, y, s=100):
super(Player,self).__init__(self.image, x, y, s)
class Block(GameObj):
image = "wall.png"
def __init__(self, x, y, s=100):
super(Block, self).__init__(self.image, x, y, s)
def collideNum(sprite, group):
total = 0
for member in group:
if(pygame.sprite.collide_rect(sprite, member)):
total += 1
return total
def setup():
pygame.init()
global display, player, block, sprites, living, noliving, clock
display = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Test")
clock = pygame.time.Clock()
player = Player(100, 0)
block = Block(100, 300)
block1 = Block(200, 400)
block2 = Block(400, 400)
sprites = pygame.sprite.Group()
living = pygame.sprite.Group()
noliving = pygame.sprite.Group()
noliving.add(block)
noliving.add(block1)
noliving.add(block2)
living.add(player)
for liv in living:
sprites.add(liv)
for noliv in noliving:
sprites.add(noliv)
main()
def main():
speed = 5
gravity = 5
xCh, yCh = 0, 0
player.leftUnable = False
player.rightUnable = False
while True:
clock.tick(60)
display.fill((184, 184, 184))
yCh = gravity
for event in pygame.event.get():
if(event.type == pygame.QUIT):
quit()
elif(event.type == pygame.KEYDOWN):
if(event.key == pygame.K_ESCAPE):
quit()
elif((event.key == pygame.K_a or event.key == pygame.K_LEFT) and not player.leftUnable):
for noliv in noliving:
if(pygame.sprite.collide_rect(player, noliv)):
if(noliv.rect.left < player.rect.left < noliv.rect.right and player.rect.bottom > noliv.rect.top + 5):
player.leftUnable = True
break
else:
xCh = -speed
player.leftUnable = False
else:
xCh = -speed
player.leftUnable = False
elif((event.key == pygame.K_d or event.key == pygame.K_RIGHT) and not player.rightUnable):
for noliv in noliving:
if(pygame.sprite.collide_rect(player, noliv)):
if(noliv.rect.left < player.rect.right < noliv.rect.right and player.rect.bottom > noliv.rect.top + 5):
player.rightUnable = True
break
else:
xCh = speed
player.rightUnable = False
else:
xCh = speed
player.rightUnable = False
elif(event.key == pygame.K_SPACE or event.key == pygame.K_w or event.key == pygame.K_UP):
oldPos = player.rect.bottom
xCh = 0
if(not player.inAir):
while player.rect.bottom > oldPos - player.jumpHeight:
clock.tick(60)
display.fill((184, 184, 184))
for ev in pygame.event.get():
if(ev.type == pygame.KEYDOWN):
if(ev.key == pygame.K_d or ev.key == pygame.K_RIGHT):
xCh = speed
elif(ev.key == pygame.K_a or ev.key == pygame.K_LEFT):
xCh = -speed
elif(ev.type == pygame.KEYUP):
xCh = 0
player.rect.x += xCh
player.rect.y -= player.jumpSpeed
player.inAir = True
sprites.draw(display)
pygame.display.update()
elif(event.type == pygame.KEYUP):
xCh = 0
for liv in living:
for noliv in noliving:
if(pygame.sprite.collide_rect(liv, noliv)):
liv.inAir = False
break
else:
liv.inAir = True
for noliv in noliving:
if(pygame.sprite.collide_rect(player, noliv)):
if(noliv.rect.left < player.rect.left < noliv.rect.right and player.rect.bottom > noliv.rect.top + 5):
player.leftUnable = True
if(collideNum(player, noliving) == 1):
player.inAir = True
if(xCh < 0):
xCh = 0
elif(noliv.rect.left < player.rect.right < noliv.rect.right and player.rect.bottom > noliv.rect.top + 5):
player.rightUnable = True
if(collideNum(player, noliving) == 1):
player.inAir = True
if(xCh > 0):
xCh = 0
else:
player.leftUnable = False
player.rightUnable = False
else:
player.leftUnable = False
player.rightUnable = False
if(not player.inAir):
yCh = 0
if(player.rect.top > display.get_size()[1]):
setup()
player.rect.x += xCh
player.rect.y += yCh
sprites.draw(display)
pygame.display.update()
setup()
它运行良好。
这是您的代码和建议的修复,以及其他一些改进建议。
首先要注意的是:错误的是你从来没有检查水平移动是否会使块碰撞。你只有一个检查,它实际上只在 发生任何碰撞后才做任何事情。在这种情况下,它会阻止 "gravity" 做任何事情。由于您是逐像素更新播放器,因此碰撞已经发生的事实几乎没有引起注意。解决方法是创建一个检查来验证移动块 是否可以 移动到某个地方,然后才允许它。并且对所有运动都这样做,而不仅仅是重力运动。
所以,在这里我将解释可以让这个示例代码演变成一个完整游戏的改进。
在模块级别消除 "floating code" 并将所有内容放入函数中。这对于未来任何游戏来说都是必不可少的,即使是像开始屏幕一样简单的东西(也可能是 "play again" 屏幕)。我做了两个功能,一个创建设置 你的游戏环境和创建你的对象,以及主要的游戏功能。以后你应该把 "setup" 阶段进一步分开, 因为能够创建更多游戏对象或不同的游戏关卡。
重用您的代码。您的
__init__
方法中的行大部分在两个 class 之间重复,但对于初始位置和图像名称常量。所以您实际上只需要一个 class,并为这些属性传递不同的值。由于您暗示您将通过使用组等获得两种不同的游戏类型的对象,因此我保留了两个 classes,但分解出了通用代码。帧延迟:您的代码会简单地移动 "as fast as possible" - 这会在不同的计算机上创建不同的游戏速度,并使用 100% 的 CPU - 导致可能的机器减速和过度用电。我加入了 30 毫秒的硬编码暂停 - 这将使您的游戏以接近 30FPS 的速度移动,在不同设备上保持速度稳定。另一个需要改变的是你硬编码的“1”重力和速度值,这将使玩家一次移动 1px 必须更改为更大的值。
最后但并非最不重要的一点是,"can I move there" 逻辑:它实际上看起来很广泛 - 但感觉 "can this object move to that position" 检查的简单英语描述很冗长。该方法本质上是:"Store the current position"。 "Fake a position on with the given x and y displacements"。 "check if I am colliding with something"。 "if not, restore the previous position, and say the movement is allowed"。 "otherwise, restore the y position, check if there is a collision in the X direction, if so, restrict horizontal movement"。 "Restore the x position, move the y position by desired amount, check if collision happens. If so, restrict movement in the y direction"。 "If movement is not restricted in either x or y, it is just the combined movement that causes the collision: restrict movement in both directions to be fair"。 "Restore original position, return allowed changes to x and y positions, and the object we'd collide with"。所有这些逻辑都放在 "living" 对象 class 本身中,因此可以随时使用,主游戏逻辑中只有一行代码。
代码:
import pygame
class GameObject(pygame.sprite.Sprite):
def __init__(self, x=0, y=0, s=100, image=""):
super(GameObject, self).__init__()
if not image:
image = self.image
self.image = pygame.transform.scale(pygame.image.load(image), (s, s))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
class Player(GameObject):
image = "player.png"
def can_move(self, xCh, yCh, group):
old_pos = self.rect.x, self.rect.y
self.rect.x += xCh
self.rect.y += yCh
collided_with = pygame.sprite.spritecollideany(self, group)
if not collided_with:
# No Collisions at all - allow movement
self.rect.x = old_pos[0]
self.rect.y = old_pos[1]
return True, xCh, yCh, None
# Check if the colision was due to horizontal movement:
self.rect.y = old_pos[1]
if pygame.sprite.spritecollideany(self, group):
# Yes, then indicate horizontal movement should be 0
xCh = 0
# check if collision was due to vertical movement:
self.rect.y += yCh
self.rect.x = old_pos[0]
if pygame.sprite.spritecollideany(self, group):
# Yes - indicate vertical movemnt should be 0
yCh = 0
# if only diagonal movement causes collision, then
# cancel movrment in both axes:
# (i.e. just the diagonal movement would hit the
# obstacle )
if not xCh == 0 and not yCh == 0:
xCh = 0
yCh = 0
self.rect.x = old_pos[0]
self.rect.y = old_pos[1]
return False, xCh, yCh, collided_with
class Block(GameObject):
image = "wall.png"
def setup():
global display, player, living, noliving, gravity, speed, sprites
pygame.init()
display = pygame.display.set_mode((800,600))
player = Player()
block = Block(y=500)
sprites = pygame.sprite.Group()
living = pygame.sprite.Group()
noliving = pygame.sprite.Group()
sprites.add(player)
sprites.add(block)
living.add(player)
noliving.add(block)
gravity = 10
speed = 10
def main():
xCh = 0
yCh = 0
while True:
display.fill((159, 159, 159))
for event in pygame.event.get():
if(event.type == pygame.KEYDOWN):
if(event.key == pygame.K_ESCAPE):
quit()
elif(event.key == pygame.K_a or event.key == pygame.K_LEFT):
xCh = -speed
elif(event.key == pygame.K_d or event.key == pygame.K_RIGHT):
xCh = speed
elif(event.type == pygame.KEYUP):
xCh = 0
yCh = 0
elif(event.type == pygame.QUIT):
quit()
yCh = gravity
for liv in living:
if liv == player:
check_x_ch = xCh
else:
check_x_ch = 0
can_move, xCh, yCh, obstacle = liv.can_move(xCh, yCh, noliving)
liv.rect.x += xCh
liv.rect.y += yCh
# Do other actions if "can_move" indicates a block was hit...
sprites.draw(display)
pygame.display.update()
pygame.time.delay(30)
setup()
main()