首页 > 解决方案 > PyGame 碰撞系统每隔一段时间才工作一次

问题描述

当我发现一个奇怪的错误时,我正试图为我的简单游戏制作一个有效的碰撞系统。有一个随机位置的矩形,以恒定的速度向上移动。然后还有一个位置随机的球,但可以使用箭头移动。当球击中矩形时,它应该与矩形一起上升,就好像矩形“抓住了球”一样,当球离开矩形时,它应该再次落下。

这是我的代码:

import time
import random
import pygame
from pygame.locals import *

pygame.init()

# Window size.
screen_width = 800
screen_height = 400

# Rectangle size and position.
position_x = random.randint(0, screen_width-150)
position_y = random.randint(0, screen_height-50)
r_width = random.randint(150, (screen_width - position_x))
r_height = 10
rect_gap = 50
velocity_r = 1

# Ball properties.
radius = 10
xcoor = random.randint((radius + 2), screen_width-r_width)
ycoor = 20
gravity = 1
velocity_b = 1


# colors
white = (255, 255, 255,)
black = (0, 0, 0)
red = (255, 0, 0)
green = (0, 255, 0)
blue = (0, 0, 255)
light_blue = (78, 231, 245)
dark_green = (37, 125, 0)

# Window.
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Keep Falling")
screen.fill(light_blue)

# Class for generating the Ball.
class Ball:
    def __init__(self, screen, color, xcoor, ycoor, radius, ):
        self.screen = screen
        self.color = color
        self.xcoor = xcoor
        self.ycoor = ycoor
        self.radius = radius

    def draw(self, color, xcoor, ycoor, radius):
        pygame.draw.circle(screen, color, (xcoor, ycoor), radius)
    
# Class for generating the 
class Rectangle:
    def __init__(self, screen, color, position_x, position_y, r_width, r_height):
        self.screen = screen
        self.color = color
        self.position_x = position_x
        self.position_y = position_y
        self.r_width = r_width
        self.r_height = r_height

    def draw(self, color, position_x, position_y, r_width, r_height):
        pygame.draw.rect(screen, dark_green, (position_x,
                                              position_y, r_width, r_height))


# game loop
run = True
while run:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

    screen.fill(light_blue)
    time.sleep(0.005)

    # Upward movement of the rectangle.
    position_y -= velocity_r

    # Teleportation of the triangle to the bottom.
    if position_y < 0 - r_height:
        position_y = screen_height+radius

    key = pygame.key.get_pressed()

    # Ball controls and collisions with the rectangle from the sides.
    if key[pygame.K_LEFT]:
        xcoor -= velocity_b
        if xcoor < 0:
            xcoor = screen_width
        if ycoor > position_y and ycoor < (position_y + r_height) and xcoor == (position_x + r_width + radius):
            xcoor += velocity_b
        
    if key[pygame.K_RIGHT]:
        xcoor += velocity_b
        if xcoor > screen_width:
            xcoor = 0
        if ycoor > position_y and ycoor < (position_y + r_height) and xcoor == (position_x - radius):
            xcoor -= velocity_b

    # Teleporation of the ball to the top.
    if ycoor > screen_height:
        ycoor = 0

    # Collision system.
    if ycoor == (position_y+r_height+radius) and xcoor >= position_x and xcoor <= (position_x + r_width):
        ycoor += velocity_b

    if ycoor == (position_y - radius) and xcoor >= position_x and xcoor <= (position_x + r_width):
        gravity = 0
        ycoor -= velocity_r
        if ycoor < 0:
            ycoor = screen_height

    if ycoor == (position_y - radius) and xcoor <= position_x or xcoor >= (position_x + r_width):
        gravity = 1
    
    # Falling of the ball.
    ycoor += gravity

    # Ball and rectangle display.
    Rectangle.draw(screen, dark_green, position_x, position_y, r_width, r_height)
    Ball.draw(screen, white, xcoor, ycoor, radius)

    pygame.display.update()
pygame.quit()

我的碰撞系统基于球和矩形的坐标,如下所示:

    # Collision system.
    if ycoor == (position_y+r_height+radius) and xcoor >= position_x and xcoor <= (position_x + r_width):
        ycoor += velocity_b

    if ycoor == (position_y - radius) and xcoor >= position_x and xcoor <= (position_x + r_width):
        gravity = 0
        ycoor -= velocity_r
        if ycoor < 0:
            ycoor = screen_height

    if ycoor == (position_y - radius) and xcoor <= position_x or xcoor >= (position_x + r_width):
        gravity = 1
    

由于未知原因,在这种情况下,球只会每隔一次“被抓住”,否则它会从三角形中落下。

我想出了一个同样基于坐标的替代方案

while ycoor == (position_y - radius) and xcoor >= position_x and xcoor <= (position_x + r_width):
        gravity = 0
        ycoor -= velocity_r
        if ycoor < 0:
            ycoor = screen_height

使用这种方法也会导致错误。

  1. 球在第一次与三角形碰撞时落下,从第二个矩形开始,碰撞正常工作。
  2. 当球离开矩形时,它不会再次开始下落。在循环之后添加gravity = 1会导致球根本不与矩形碰撞。

是否有错误或我的代码在逻辑上存在缺陷,我应该重做整个碰撞检测系统?
感谢您的任何建议。

标签: pythonpython-3.xpygamecollision-detection

解决方案


问题是,矩形在每一帧中移动,球移动一个。结果,球的底部并不总是准确地碰到矩形的顶部。有时条件ycoor == (position_y - radius)不满足。
您必须评估球的底部是否在矩形顶部的“范围内”:

if ycoor == (position_y - radius) and ...

if (position_y - radius - 1) <= ycoor <= (position_y - radius + 1) and ...

例如:

while run:
    # [...]

    if  (position_y - radius - 1) <= ycoor <= (position_y - radius + 1) and xcoor >= position_x and xcoor <= (position_x + r_width):
        gravity = 0
        ycoor -= velocity_r
        if ycoor < 0:
            ycoor = screen_height

    if (position_y - radius - 1) <= ycoor <= (position_y - radius + 1) and xcoor <= position_x or xcoor >= (position_x + r_width):
        gravity = 1

无论如何,我建议使用一个pygame.Rect对象,或者.collidepoint()找到colliderect()一个矩形和一个对象之间的碰撞。

rect1 = pygame.Rect(x1, y1, w1, h1)
rect2 = pygame.Rect(x2, y2, w2, h2)
if rect1.colliderect(rect2):
    # [...]

例如:

run = True
while run:
    # [...]

    # Teleporation of the ball to the top.
    if ycoor > screen_height:
        ycoor = 0

    rect_rect = pygame.Rect(position_x, position_y, r_width, r_height)
    ball_rect = pygame.Rect(xcoor-radius, ycoor-radius, radius*2, radius*2)
    if rect_rect.colliderect(ball_rect):
        ycoor = position_y - radius
    else:
        # Falling of the ball.
        ycoor += gravity

    # Ball and rectangle display.
    # [...] 

推荐阅读