【问题标题】:How to find perfect collision of a moving object into an obstacle如何找到移动物体与障碍物的完美碰撞
【发布时间】:2018-11-01 20:14:54
【问题描述】:

我有两个精灵:一个机器人精灵和一个障碍精灵。我正在使用 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。我如何计算剩余距离?

【问题讨论】:

  • 这并不容易实现。您可以在 this article 的“Type #3: Bitmask”下找到一些提示。我有一个原型,我设法实现了它,但是代码变得非常复杂,我不确定它是否总是有效,因此我犹豫是否发布它。明天我会尝试简化并进行更多测试。
  • 我上面的评论实际上与斜坡和滑动有关。如果您只是想尽可能靠近障碍物,那将更容易实现。戴维斯鲱鱼的回答听起来像是一个很好的解决方案。您还可以以 1px 的步长(第一次碰撞后)向相反的方向移动精灵,直到它不再接触障碍物。 Rene Dudfield 有一篇博文,他在其中描述了如何计算 collision normal,这在这里会有所帮助。

标签: python pygame


【解决方案1】:

这是我在评论中描述的。检查精灵是否发生碰撞(我在这里使用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()

【讨论】:

    【解决方案2】:

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

    请注意,这并不能解决剪裁穿过薄壁或角落(初始碰撞测试未能检测到障碍物)的问题,并且遇到复杂的障碍物会发现任意 边缘(不一定是第一个)停止。

    【讨论】:

    • 有趣的想法,我得看看我能不能做到这一点
    【解决方案3】:

    我决定采用 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。我可能会回到这个并在未来更新它。但就目前而言,这将为用户提供一些关于如何进行完美碰撞的选择。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-03
      • 1970-01-01
      • 2017-05-21
      • 2015-07-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多