Как найти идеальное столкновение движущегося объекта с препятствием - PullRequest
0 голосов
/ 01 ноября 2018

У меня есть два спрайта: спрайт робота и спрайт препятствия. Я использую 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 пикселей.

ЗАДАЧА

Если должно было произойти столкновение, отрегулируйте скорость до оставшегося расстояния в пикселях (20 пикселей, как в примере), чтобы робот и препятствие идеально сталкивались. Робот не может двигаться 30, но он может двигаться 20. Как я могу рассчитать оставшееся расстояние?

Ответы [ 3 ]

0 голосов
/ 02 ноября 2018

Вот что я описал в комментарии. Проверьте, не сталкиваются ли спрайты (я использую здесь функции spritecollide и pygame.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()
0 голосов
/ 02 ноября 2018

Я решил придерживаться подхода, предложенного 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. Я, вероятно, вернусь к этому и обновлю его в будущем. Но на данный момент это даст пользователям несколько вариантов того, как продолжать совершенное столкновение.

0 голосов
/ 02 ноября 2018

Вы можете довольно легко получить точное (если не точное) решение с помощью поиска деления пополам : как только столкновение обнаружено в конце полного шага, попробуйте выполнить полшага, а затем либо один, либо три четверти и так далее. Это рассматривает тест столкновения как булевозначную функцию расстояния перемещения и ищет «ноль» (на самом деле переход от промаха к удару).

Обратите внимание, что это никак не решает проблему отсечения через тонкую стену или угол (где первоначальный тест на столкновение не может обнаружить препятствие), и со сложными препятствиями найдет произвольное край (не обязательно первый ), чтобы остановиться на.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...