矩形之间有缺陷的碰撞检测

时间:2015-10-10 05:34:58

标签: python pygame collision-detection

我正在用Pygame创建一个基于物理的游戏,其中玩家控制一个球。当你控制球时,它会在指定的方向上加速(按住左箭头每帧增加x个像素到它的移动速度)。由于球是......好......一个球,而Pygame不支持球碰撞检测,我用自己的碰撞方法创建了一个新类。该方法有两个部分:如果球进入矩形的角落,或者它是否进入矩形的一侧。问题涉及到圆周碰撞。

enter image description here

球基于一个矩形物体,因此有那些讨厌的角落。我不能使用简单的colliderect方法,否则上面的情况会检测到应该没有的碰撞,并且它会与我的碰撞检测方法的第一部分重叠。相反,我选择在矩形和球的矩形每一边的中点之间使用collidepoint

最后,问题的核心。我之前提到球会加速。当球加速到(即使它看起来静止不动)时,它会移动到足够远的矩形中,以便圆圈上的另一个中点检测到“碰撞”。这个问题很可能源于这样一个事实:(对于球左侧的碰撞)我的代码将球的left设置为等于矩形的right,这样当球加速到足以进入内部时矩形,它被移动到矩形的另一面。

非常感谢您对我的支持,欢迎提出任何建议。我要么正在寻找一个解决我的具体问题,或更清洁的方式来处理碰撞检测。我的完整代码如下:

import pygame, sys, math

global Color
Color = {}
Color['white'] = (255,255,255)
Color['black'] = (  0,  0,  0)
Color['red']   = (255,  0,  0)
Color['green'] = (  0,255,  0)
Color['blue']  = (  0,  0,255)

global WINDOWWIDTH, WINDOWHEIGHT
WINDOWWIDTH, WINDOWHEIGHT = 500, 500

class Ball():
    def __init__(self, x, y, r):
        self.rect = pygame.Rect(x, y, r, r)
        self.radius = r/2
        self.speed = [0, 0]
        self.b_fact = 1
        self.move = {'left':False, 'right':False, 'up':False, 'down':False}
        self.new_dir = {'left':False, 'right':False, 'up':False, 'down':False}

    def move_self(self):
        if self.move['left']:
            self.speed[0] -= 2
        if self.move['up']:
            self.speed[1] -= 2
        if self.move['right']:
            self.speed[0] += 2
        if self.move['down']:
            self.speed[1] += 2

        if self.speed[0] < 0:
            self.speed[0] += 1
        if self.speed[1] < 0:
            self.speed[1] += 1
        if self.speed[0] > 0:
            self.speed[0] -= 1
        if self.speed[1] > 0:
            self.speed[1] -= 1

        self.rect.left += self.speed[0]
        self.rect.top  += self.speed[1]

    def bounce(self, rectList):
        for rect in rectList:
            self.collide_rect(rect)
        if self.rect.left <= 0:
            self.rect.left = 0
            self.new_dir['right'] = True
        if self.rect.right >=  WINDOWWIDTH:
            self.rect.right = WINDOWWIDTH
            self.new_dir['left'] = True
        if self.rect.top <= 0:
            self.rect.top = 0
            self.new_dir['down'] = True
        if self.rect.bottom >=  WINDOWHEIGHT:
            self.rect.bottom = WINDOWHEIGHT
            self.new_dir['up'] = True

        for key in self.new_dir:
            if self.new_dir[key] and key=='left':
                self.speed[0] *= (-1)*self.b_fact
            if self.new_dir[key] and key=='right':
                self.speed[0] *= (-1)*self.b_fact
            if self.new_dir[key] and key=='up':
                self.speed[1] *= (-1)*self.b_fact
            if self.new_dir[key] and key=='down':
                self.speed[1] *= (-1)*self.b_fact
            self.new_dir[key] = False

    def collide_rect(self, rect):
        x1, y1, r = self.rect.centerx, self.rect.centery, self.radius
        foundSide = 0
        foundCorner = 0
        side_list = ['left', 'right', 'bottom', 'top']
        corner_list = ['topleft', 'topright', 'bottomleft', 'bottomright']
        collision_list = []

        for side in side_list:
            if rect.collidepoint(eval('self.rect.mid'+side)):
                collision_list.append(side)

        for corner in corner_list:
            x2, y2 = eval('rect.'+corner)[0], eval('rect.'+corner)[1]
            dist = math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
            if dist < r:
                if corner.find('left') > -1:
                    corner = corner.replace('left','right')
                else:
                    corner = corner.replace('right','left')
                if corner.find('top') > -1:
                    corner = corner.replace('top','bottom')
                else:
                    corner = corner.replace('bottom','top')
                collision_list.append(corner)

        for direction in collision_list:
            if direction.find('left') > -1:
                self.rect.left = rect.right
                self.new_dir['left'] = True
            if direction.find('top') > -1:
                self.rect.top = rect.bottom
                self.new_dir['top'] = True
            if direction.find('right') > -1:
                self.rect.right = rect.left
                self.new_dir['right'] = True
            if direction.find('bottom') > -1:
                self.rect.bottom = rect.top
                self.new_dir['bottom'] = True

class BallGame():
    def __init__(self):
        pygame.display.set_caption("Ball is life")
        pygame.init()

        self.ball = Ball(0, 0, 30)

        self.allRects = []
        rect = pygame.Rect(60,60,50,50)
        self.allRects.append(rect)

        self.mainClock = pygame.time.Clock()
        self.screen = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
        self.basicFont = pygame.font.SysFont(None, 50)

    def drawScreen(self):
        self.screen.fill(Color['green'])
        pygame.draw.ellipse(self.screen, Color['white'], self.ball.rect)
        for rect in self.allRects:
            pygame.draw.rect(self.screen, Color['black'], rect)



    def mainloop(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            for i in range(2):
                k = (pygame.KEYUP, pygame.KEYDOWN)
                if event.type == k[i]:
                    if event.key == pygame.K_LEFT:
                        self.ball.move['left'] = i
                    elif event.key == pygame.K_UP:
                        self.ball.move['up'] = i
                    elif event.key == pygame.K_RIGHT:
                        self.ball.move['right'] = i
                    elif event.key == pygame.K_DOWN:
                        self.ball.move['down'] = i

        self.ball.move_self()
        self.ball.bounce(self.allRects)

        self.drawScreen()

        pygame.display.update()
        self.mainClock.tick(20)

Game = BallGame()
while True:
    Game.mainloop()

1 个答案:

答案 0 :(得分:0)

考虑碰撞的另一种方法是考虑黑色矩形的放大版本。这将是圆角矩形,角半径为r。球与黑色矩形之间的碰撞相当于球的中心与圆角矩形之间的碰撞。这有助于简化对情况的分析。

当它反弹时,更准确的确定新位置的方法是考虑从前一个位置到当前位置的线。您可以计算此线穿过边界的位置以及完美反射的位置。