如何找到运动物体与障碍物的完美碰撞



我有两个精灵:一个机器人精灵和一个障碍精灵。我使用mask.overlap来确定是否存在重叠,以防止机器人移动到障碍物区域(它起到阻挡障碍物的作用(。以下是运动评估代码的一部分。它测试移动是否会导致碰撞:

if pressed_keys[pygame.K_s]:
temp_position = [robot.rect.x, robot.rect.y]
temp_position[1] += speed
offset_x = temp_position[0] - obstacle.rect.x
offset_y = temp_position[1] - obstacle.rect.y
overlap = obstacle.mask.overlap(robot.mask, (offset_x, offset_y))
if overlap is None:
robot.rect.y += speed
else:
# adjust the speed to make the objects perfectly collide

此代码有效。如果移动会导致碰撞,那么它会阻止机器人移动。

问题

对于高速,该代码可以防止像它应该的那样移动,但它在机器人和障碍物之间留下了视觉间隙。

例如:如果速度为30,两个障碍物相距20像素,代码将阻止移动,因为会导致碰撞。但留下了20个像素的间隙。

目标

如果发生碰撞,将速度调整到剩余的像素距离(如本例中的20px(,使机器人和障碍物完美碰撞。机器人不能移动30,但他能移动20。我如何计算剩余距离?

以下是我在评论中描述的内容。检查精灵是否发生碰撞(我在这里使用spritecollidepygame.sprite.collide_mask函数(,然后使用归一化的负速度向量向后移动玩家,直到它不再与障碍物碰撞。

import pygame as pg
from pygame.math import Vector2

pg.init()
screen = pg.display.set_mode((800, 600))
GRAY = pg.Color('gray12')
CIRCLE_BLUE = pg.Surface((70, 70), pg.SRCALPHA)
pg.draw.circle(CIRCLE_BLUE, (0, 0, 230), (35, 35), 35)
CIRCLE_RED = pg.Surface((170, 170), pg.SRCALPHA)
pg.draw.circle(CIRCLE_RED, (230, 0, 0), (85, 85), 85)

class Player(pg.sprite.Sprite):
def __init__(self, pos, key_left, key_right, key_up, key_down):
super().__init__()
self.image = CIRCLE_BLUE
self.mask = pg.mask.from_surface(self.image)
self.rect = self.image.get_rect(topleft=pos)
self.vel = Vector2(0, 0)
self.pos = Vector2(self.rect.topleft)
self.dt = 0.03
self.key_left = key_left
self.key_right = key_right
self.key_up = key_up
self.key_down = key_down
def handle_event(self, event):
if event.type == pg.KEYDOWN:
if event.key == self.key_left:
self.vel.x = -230
elif event.key == self.key_right:
self.vel.x = 230
elif event.key == self.key_up:
self.vel.y = -230
elif event.key == self.key_down:
self.vel.y = 230
elif event.type == pg.KEYUP:
if event.key == self.key_left and self.vel.x < 0:
self.vel.x = 0
elif event.key == self.key_right and self.vel.x > 0:
self.vel.x = 0
elif event.key == self.key_down and self.vel.y > 0:
self.vel.y = 0
elif event.key == self.key_up and self.vel.y < 0:
self.vel.y = 0
def update(self, dt):
self.pos += self.vel * dt
self.rect.center = self.pos

class Obstacle(pg.sprite.Sprite):
def __init__(self, pos):
super().__init__()
self.image = CIRCLE_RED
self.mask = pg.mask.from_surface(self.image)
self.rect = self.image.get_rect(topleft=pos)

class Game:
def __init__(self):
self.done = False
self.clock = pg.time.Clock()
self.screen = screen
self.player = Player((100, 50), pg.K_a, pg.K_d, pg.K_w, pg.K_s)
obstacle = Obstacle((300, 240))
self.all_sprites = pg.sprite.Group(self.player, obstacle)
self.obstacles = pg.sprite.Group(obstacle)
def run(self):
while not self.done:
self.dt = self.clock.tick(60) / 1000
self.handle_events()
self.run_logic()
self.draw()
pg.quit()
def handle_events(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.done = True
elif event.type == pg.MOUSEBUTTONDOWN:
if event.button == 2:
print(BACKGROUND.get_at(event.pos))
self.player.handle_event(event)
def run_logic(self):
self.all_sprites.update(self.dt)
collided_sprites = pg.sprite.spritecollide(
self.player, self.obstacles, False, pg.sprite.collide_mask)
for obstacle in collided_sprites:
# The length of the velocity vector tells us how many steps we need.
for _ in range(int(self.player.vel.length())+1):
# Move back. Use the normalized velocity vector.
self.player.pos -= self.player.vel.normalize()
self.player.rect.center = self.player.pos
# Break out of the loop when the masks aren't touching anymore.
if not pg.sprite.collide_mask(self.player, obstacle):
break
def draw(self):
self.screen.fill(GRAY)
self.all_sprites.draw(self.screen)
pg.display.flip()

if __name__ == '__main__':
Game().run()

通过平分搜索,您可以很容易地获得精确的(如果不是精确的(解决方案:一旦在整个步骤结束时检测到碰撞,请尝试半步,然后四分之一或三步,等等。这是将碰撞测试视为移动距离的布尔值函数,并寻找"零"(实际上是从未命中到命中的过渡(。

请注意,这并不能解决通过薄壁或拐角(初始碰撞测试未能检测到障碍物(夹住的问题,并且对于复杂的障碍物,会发现任意边缘(不一定是第一个(需要停下来。

我决定采用skrx在评论中建议的方法:基本上备份1px,直到不再发生冲突。

if pressed_keys[pygame.K_s]:
temp_position = [robot.rect.x, robot.rect.y]
temp_position[1] += speed
offset_x = temp_position[0] - obstacle.rect.x
offset_y = temp_position[1] - obstacle.rect.y
overlap = obstacle.mask.overlap(robot.mask, (offset_x, offset_y))
if overlap is None:
robot.rect.y += speed
else:
for step_speed in range(1, speed - 1):
collision[1] -= 1
offset_x = collision[0] - obstacle.rect.x
offset_y = collision[1] - obstacle.rect.y
overlap_adj = obstacle.mask.overlap(robot.mask, (offset_x, offset_y))
if overlap_adj is None:
robot.rect.y += (speed - step_speed)
break

这是一个有点笨拙的方法,但它将满足我现在的需求,并阻止向量数学。对于那些正在寻找使用归一化向量等正确方法的人,我建议使用提供的答案skrx。我很可能会回到这一点,并在未来更新它。但就目前而言,这将为用户提供如何进行完美碰撞的几个选项。

最新更新