在PyGame中,如何避免圆和矩形之间的碰撞?

2024-09-30 18:20:28 发布

您现在位置:Python中文网/ 问答频道 /正文

乒乓球拍移动得如此之快,以至于在检测到碰撞之前,球会在球拍内卷起。问题是用户的输入移动了一个像素,所以我不知道如何减慢速度。解决办法是什么?代码如下:

import pygame, sys, os
from pygame import*
from pygame.locals import*

WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
BLUE = (0, 0, 128)
RED = (255,0,0)
os.environ["SDL_VIDEO_CENTERED"]="1"
displaysize=600
DISPLAYSURF = pygame.display.set_mode((displaysize,displaysize))
rectwidth = 50
rectheight= 50
rectposx =0
rectposy =0

class Player(object):
    def __init__(self):
        self.rect = pygame.rect.Rect((rectposx, rectposy, rectwidth, rectheight))
    def handle_keys(self):
        key = pygame.key.get_pressed()
        dist = 1
        if key[pygame.K_LEFT] and (player.rect.x>0):
            self.rect.move_ip(-1, 0)
        if key[pygame.K_RIGHT] and (player.rect.x<600-rectwidth):
            self.rect.move_ip(1, 0)
        if key[pygame.K_UP] and (player.rect.y>0):
            self.rect.move_ip(0, -1)
        if key[pygame.K_DOWN] and (player.rect.y<600-rectheight):
            self.rect.move_ip(0, 1)
    def draw(self, DISPLAYSURF):
        pygame.draw.rect(DISPLAYSURF, BLUE, self.rect)
    def postext(self):
        pygame.image.load(self.rect).convert_alpha()

pygame.init()
player =Player()
pygame.display.set_caption('Hello World!')

clock=pygame.time.Clock()
fontObj = pygame.font.Font(None,32)
textSurfaceObj = fontObj.render('Hello World!', True, GREEN, BLUE)
#textPosition =
dt=0.1
v = pygame.math.Vector2(5,5)
ballposx=200
ballposy=200
ballrad=10
#DISPLAYSURF.fill(WHITE)
#x=10
#y=10
#dx=5
#rectpos = pygame.Rect(x,y,50,50)
#rect = pygame.draw.rect(DISPLAYSURF, BLUE, rectpos)
pygame.display.update()
running = True
n=0
while running:
    for event in pygame.event.get():
        if event.type == KEYDOWN:
            if event.key == K_ESCAPE:
                running = False
        if event.type==QUIT:
            pygame.quit()
            sys.exit
    player.handle_keys()
    ballposx=ballposx+v[0]*dt
    ballposy=ballposy+v[1]*dt
    DISPLAYSURF.fill(WHITE)
    DISPLAYSURF.blit(textSurfaceObj,(0,0))
    player.draw(DISPLAYSURF)
    ball=pygame.draw.circle(DISPLAYSURF, GREEN, (int(ballposx),int(ballposy)), ballrad)
    rectposx1=player.rect.x
    rectposy1=player.rect.y
    rectvelx=-(rectposx-rectposx1)/dt
    rectvely=-(rectposy-rectposy1)/dt
    if ballposx-ballrad<0:
        v[0]=-v[0]
    if ballposy-ballrad<0:
        v[1]=-v[1]
    if ballposx+ballrad>600:
        v[0]=-v[0]
    if ballposy+ballrad>600:
        v[1]=-v[1]
    if player.rect.colliderect(ball):
        pygame.math.Vector2.reflect_ip(v,-v+5*pygame.math.Vector2(rectvelx,rectvely)) 

    #print (player.rect.x, rectposy, ball.x, ball.y)
    ballmass=1
    rectmass=5
    rectposx=rectposx1
    rectposy=rectposy1
    print (v)
            #raise SystemExit("You win!")
    pygame.display.update()
    clock.tick(120)

Tags: keyrectselfipeventifdtpygame
3条回答

有两种策略可以消除这种空虚感

  1. 在检测到碰撞后,将球移动到与球员接触但不与球员相交的位置。e、 g:

    dx = ballposx - player.rect.centerx
    dy = ballposy - player.rect.centery
    
    if abs(dx) > abs(dy):
        ballposx = player.rect.left-ballrad if dx < 0 else player.rect.right+ballrad
    else:
        ballposy = player.rect.top-ballrad if dy < 0 else player.rect.bottom+ballrad
    
  2. 仅当球的运动矢量指向球的“反”方向时,才反映球的运动。e、 g:

    if abs(dx) > abs(dy):
        if (dx < 0 and v[0] > 0) or (dx > 0 and v[0] < 0):
            v.reflect_ip(pygame.math.Vector2(1, 0))
    else:
        if (dy < 0 and v[1] > 0) or (dy > 0 and v[1] < 0):
            v.reflect_ip(pygame.math.Vector2(0, 1))
    

另见Sometimes the ball doesn't bounce off the paddle in pong game

将这两个修正应用到代码中,球将正确地反映在球员身上。e、 g:

ball = pygame.Rect((0,0), (ballrad*2, ballrad*2))
ball.center = int(ballposx),int(ballposy)
if player.rect.colliderect(ball):
    dx = ballposx - player.rect.centerx
    dy = ballposy - player.rect.centery
    if abs(dx) > abs(dy):
        ballposx = player.rect.left-ballrad if dx < 0 else player.rect.right+ballrad
        if (dx < 0 and v[0] > 0) or (dx > 0 and v[0] < 0):
            v.reflect_ip(pygame.math.Vector2(1, 0))
    else:
        ballposy = player.rect.top-ballrad if dy < 0 else player.rect.bottom+ballrad
        if (dy < 0 and v[1] > 0) or (dy > 0 and v[1] < 0):
            v.reflect_ip(pygame.math.Vector2(0, 1))

如果要避免球员将球推出窗外,需要将球限制在窗口区域,并将球像台球一样反射到窗口边缘:

min_x, min_y, max_x, max_y = 0, 0, window.get_width(), window.get_height()

ballposx = ballposx + v[0]*dt
ballposy = ballposy + v[1]*dt
if ballposx-ballrad < min_x:
    ballposx = ballrad+min_x
    v[0]=-v[0]
if ballposy-ballrad < min_y:
    ballposy = ballrad+min_y
    v[1]=-v[1]
if ballposx + ballrad > max_x:
    ballposx = max_x-ballrad
    v[0]=-v[0]
if ballposy + ballrad > max_y:
    ballposy = max_y-ballrad
    v[1]=-v[1]

另见Use vector2 in pygame. Collide with the window frame and restrict the ball to the rectangular area分别How to make ball bounce off wall with Pygame?

当检测到碰撞时,必须限制球员的位置,以便球可以在寡妇边界和球员之间进行:

if abs(dx) > abs(dy):
    if dx < 0:
        ballposx = max(player.rect.left-ballrad, ballrad+min_x)
        player.rect.left = int(ballposx)+ballrad
    else:
        ballposx = min(player.rect.right+ballrad, max_x-ballrad)
        player.rect.right = int(ballposx)-ballrad

通过这些更改,球甚至可以“挤压”在窗口边缘和球员之间:

最简单的例子:

import pygame

class Player(object):
    def __init__(self, x, y, w, h):
        self.rect = pygame.rect.Rect(x, y, w, h)
    def handle_keys(self):
        key = pygame.key.get_pressed()
        if key[pygame.K_LEFT]:
            self.rect.left = max(20, self.rect.left - 1)
        if key[pygame.K_RIGHT]:
            self.rect.right = min(window.get_height() - 20, self.rect.right + 1)
        if key[pygame.K_UP]:
            self.rect.top = max(20, self.rect.top - 1)
        if key[pygame.K_DOWN]:
            self.rect.bottom = min(window.get_width() - 20, self.rect.bottom + 1)
    def draw(self, surface):
        pygame.draw.rect(surface, (0, 0, 128), self.rect)

pygame.init()
window = pygame.display.set_mode((240, 240))
clock=pygame.time.Clock()

player = Player(20, 20, 50, 50)
v, vel = pygame.math.Vector2(1, 1), 0.5
ballPosX, ballPosY, ballRadius = 120, 120, 10

run = True
while run:
    clock.tick(120)
    for event in pygame.event.get():
        if event.type==pygame.QUIT:
            run = False
    player.handle_keys()  

    min_x, min_y, max_x, max_y = 20, 20, window.get_width()-20, window.get_height()-20
    ballPosX += v[0] * vel
    ballPosY += v[1] * vel
    if ballPosX - ballRadius < min_x:
        ballPosX = ballRadius + min_x
        v[0] = -v[0]
    if ballPosY - ballRadius < min_y:
        ballPosY = ballRadius + min_y
        v[1] = -v[1]
    if ballPosX + ballRadius > max_x:
        ballPosX = max_x - ballRadius
        v[0] = -v[0]
    if ballPosY + ballRadius > max_y:
        ballPosY = max_y - ballRadius
        v[1] = -v[1]

    ball = pygame.Rect((0,0), (ballRadius*2, ballRadius*2))
    ball.center = int(ballPosX),int(ballPosY)
    if player.rect.colliderect(ball):
        dx = ballPosX - player.rect.centerx
        dy = ballPosY - player.rect.centery
        if abs(dx) > abs(dy):
            if dx < 0:
                ballPosX = max(player.rect.left-ballRadius, ballRadius+min_x) 
                player.rect.left = int(ballPosX)+ballRadius
            else:
                ballPosX = min(player.rect.right+ballRadius, max_x-ballRadius)
                player.rect.right = int(ballPosX)-ballRadius
            if (dx < 0 and v[0] > 0) or (dx > 0 and v[0] < 0):
                v.reflect_ip(pygame.math.Vector2(1, 0))
        else:
            if dy < 0:
                ballPosY = max(player.rect.top-ballRadius, ballRadius+min_y) 
                player.rect.top = int(ballPosY)+ballRadius
            else:
                ballPosY = min(player.rect.bottom+ballRadius, max_y-ballRadius)
                player.rect.bottom = int(ballPosY)-ballRadius
            ballPosY = player.rect.top-ballRadius if dy < 0 else player.rect.bottom+ballRadius
            if (dy < 0 and v[1] > 0) or (dy > 0 and v[1] < 0):
                v.reflect_ip(pygame.math.Vector2(0, 1))


    window.fill((255, 255, 255))
    pygame.draw.rect(window, (255,0,0), (18, 18, 203, 203), 2)
    player.draw(window)
    pygame.draw.circle(window, (0, 255, 0), (round(ballPosX), round(ballPosY)), ballRadius)
    pygame.display.update()

pygame.quit()
exit()

事实上,碰撞检测期望球接触挡板,但你的球每帧移动5个像素,因此它可以在一次移动中跳过挡板的边缘

这就是我进行碰撞检测的方法:

  • 跟踪当前和上一个球位,至少是垂直位置
  • 在每一帧上,检查球的先前位置是否在桨叶上方,以及当前位置是否在桨叶下方
  • 如果球在桨上,反射很小:只需反转垂直速度即可
  • 如果球在桨下方,反射就不那么简单了,但仍然很容易:反转垂直速度,在垂直位置加上5(或任何垂直单位速度)

一种选择是使用如下内容减少fps:clock.tick(30)。我怀疑你打乒乓球需要120 FPS

但是如果你这样做了,你需要考虑像素间的移动(即使你降低了帧率,你也应该这样做)。这将涉及一些变化,以下是我注意到需要改变的:

台词:

ball=pygame.draw.circle(DISPLAYSURF, GREEN, (int(ballposx),int(ballposy)), ballrad)
        rectposx1=player.rect.x
        rectposy1=player.rect.y

确保将位置[ballposx,ballposy]转换为图形的整数,并将其保留为计算的整数。这将提供比以下更少的控制:

ball=pygame.draw.circle(DISPLAYSURF, GREEN, (int(ballposx),int(ballposy)), ballrad)
        rectposx1=ballposx
        rectposy1=ballposy

在这里,我们仍然使用整数绘制矩形,但在计算中,我们保留了球位置的更精确值。这样,如果速度为每帧1/2像素,则每2帧移动一次,而不是完全不移动或每帧移动1像素

相关问题 更多 >