如何在pygame中检查类的所有实例之间的冲突?

时间:2019-03-10 08:09:15

标签: python pygame

我目前正在尝试在发生碰撞时在屏幕上打印,但不知道如何仅在1个班级上进行打印。我知道如何使2个对象从不同的类中发生碰撞,但是我不知道如何对具有1000个不同对象的一个​​类进行处理

我尝试在游戏pygame中使用一些功能,例如pygame.rect.contain,但我不知道从那里去哪里。 感谢您对我的帮助。

下面列出了代码:

import pygame
import random
import sys
import time

Height = 800
Width = 800
Steps = 0
running = True

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)

pygame.init()
display = pygame.display.set_mode((Height, Width))
clock = pygame.time.Clock()
pygame.display.set_caption("Game")


class Ball(object):

    def __init__(self, x, y, delta_x, delta_y):
        self.x = x
        self.y = y
        self.delta_x = delta_x
        self.delta_y = delta_y

    def draw(self):

        pygame.draw.rect(display, WHITE, (self.x, self.y, 1, 1))

    def update(self):
        self.x += self.delta_x
        self.y += self.delta_y

        if self.x < 0:
            self.delta_x = self.delta_x * -1
        if self.x > Width - 5:
            self.delta_x = self.delta_x * -1

        if self.y < 0:
            self.delta_y = self.delta_y * -1
        if self.y > Height - 5:
            self.delta_y = self.delta_y * -1


list = []
for i in range(1000):
    ball = Ball(random.randrange(0, Width - 5), random.randrange(0, Height - 5),
                random.randint(-10, 10), random.randint(-10, 10))

    list.append(ball)

while running:
    display.fill(BLACK)
    clock.tick(60)

    for event in pygame.event.get():

        if event.type == pygame.QUIT:
            pygame.quit()

    # Update

    # Draw
    for ball in list:

        ball.draw()
        ball.update()

    pygame.display.update()

    pygame.display.update()
    print(clock.tick(60))

2 个答案:

答案 0 :(得分:1)

如果要找到相等的点,则必须比较这些点的x和y坐标。

例如

for i in range(len(list)):
    eq_list = [j for j in range(len(list)) if i != j and list[i].x == list[j].x and list[i].y == list[j].y]

例如,您可以使用这种方法绘制读取时的碰撞点:

class Ball(object):

    # [...]

    def draw(self, color):
        pygame.draw.rect(display, color, (self.x, self.y, 1, 1))
for i in range(len(list)):
    ball = list[i]

    collide = any([j for j in range(len(list)) if i != j and list[i].x == list[j].x and list[i].y == list[j].y])
    ball.draw(RED if collide else WHITE)

    ball.update()

请注意,这种方法在1000点上比较慢,因此需要进行1000 * 1000碰撞测试。

更好的解决方案是使用1000x1000字段并在该字段中设置状态(如果有对象)。不管是否设置了字段中的状态,这都会将碰撞测试简化为某种测试。

创建一个具有布尔状态的字段:

obj_grid = [[False for i in range(Height)] for j in range(Width)]

获取对象位置的列表,并在字段中设置相应的状态。将字段传递给Ball.update方法。更新和绘制后,由于位置已更改,必须清除字段。为此,请使用职位列表,因为这比“清除”整个字段要快得多。我添加了Som边界检查,因为您的某些对象似乎超出范围(可能也已修复了该错误)。

# collect the postions of the items
poslist = [(ball.x, ball.y) for ball in list]

# set the positions in the field
for pos in poslist:
    if pos[0] < Width and pos[1] < Height:
        obj_grid[pos[0]][pos[1]] = True

# update and draw
for ball in list:
    ball.update(obj_grid)
    ball.draw()

# set the positions in the field
for pos in poslist:
    if pos[0] < Width and  pos[1] < Height:
        obj_grid[pos[0]][pos[1]] = False

Ball.update方法中,必须测试路径挂起方式上的位置。如果声明了任何字段,则对象发生碰撞。我将状态存储在属性self.collide中。如果设置了状态,则将对象放大并涂成红色,以使碰撞可视化。当然,您也可以执行其他操作,例如更改方向:

class Ball(object):

    def __init__(self, x, y, delta_x, delta_y):
        self.x = x
        self.y = y
        self.delta_x = delta_x
        self.delta_y = delta_y
        self.collide = False

    def draw(self):
        color, size = (RED, 5) if self.collide else (WHITE, 1)
        pygame.draw.rect(display, color, (self.x, self.y, size, size))

    def update(self, obj_grid):

        # check if the object is colliding on his way
        pos = [self.x, self.y] 
        new_pos = [self.x + self.delta_x, self.y + self.delta_y]
        self.collide = False
        while not self.collide and pos != new_pos:
            if abs(pos[0]-new_pos[0]) > abs(pos[1]-new_pos[1]):
                pos[0] += 1 if self.delta_x > 0 else -1
            else:
                pos[1] += 1 if self.delta_y > 0 else -1
            self.collide = pos[0] < Width and pos[1] < Height and obj_grid[pos[0]][pos[1]]

        self.x += self.delta_x
        self.y += self.delta_y
        if self.x < 0:
            self.delta_x = self.delta_x * -1
        if self.x > Width - 5:
            self.delta_x = self.delta_x * -1
        if self.y < 0:
            self.delta_y = self.delta_y * -1
        if self.y > Height - 5:
            self.delta_y = self.delta_y * -1

答案 1 :(得分:1)

已更新在修复了较早版本中的一个会大大降低其速度的错误之后,我走得更远,并实施了许多其他改进/优化,因为这似乎非常可行技术。

在这里,一种方法的可运行实现似乎很有效。为了避免在每次迭代中比较每对球的位置(对1,000个球进行近1,000,000次比较),它采用了将屏幕细分为M x N个分区或“箱”并将每个球“分类”到其中的策略根据其在屏幕上的当前位置。由于只涉及一些相对简单的计算,因此可以以非常廉价的方式对它们进行有效排序。

一旦完成,仅需要将每个仓中的成对球相互比较,这会更快,因为每个仓仅容纳所有这些球的一个子集。这样做需要权衡取舍,因为,尽管您创建了更多的料箱-因此每个料箱中的球数量较少-它也增加了需要处理的料箱数。

碰撞球的颜色首先更改为指定的颜色,以在后续步骤中将其标记为删除-该颜色实际上并没有用于绘制任何内容-仅是一种简单的标记方法。我这样做是为了避免在检测到碰撞时将打印速度减慢太多的打印。

在下面的示例代码中,有8 x 8 = 64个bin,假设随机分布,每个容器最初平均仅包含1000/64(15.625)个球。

结果似乎运行很快。

from itertools import combinations
from copy import deepcopy
from math import sqrt
import pygame
from random import randint

BLACK = 0, 0, 0
WHITE = 255, 255, 255
RED = 255, 0, 0
GREEN = 0, 255, 0
BLUE = 0, 0, 255

NUM_BALLS = 1000
MARKED = RED  # Color used to indicte ball collided.
WIDTH, HEIGHT = 800, 800
M, N = 8, 8  # Number of screen sub-divisions in each dimension.

MARGIN = 5  # Size of space around edges.
MAX_SPEED = 10
MAX_DELTA = round(sqrt(2 * MAX_SPEED**2))
MAX_DELTAX_X, MAX_DELTAX_Y = MAX_DELTA, MAX_DELTA
MAX_X, MAX_Y = WIDTH-MARGIN, HEIGHT-MARGIN
EMPTY_BINS = [[[] for i in range(M)] for j in range(N)]
WM, WN = WIDTH // M, HEIGHT // N  # Dimensions of each sub-division.


class Ball(object):
    def __init__(self, x, y, delta_x, delta_y, color=WHITE):
        self.x, self.y = x, y
        self.delta_x, self.delta_y = delta_x, delta_y
        self.color = color

    def draw(self, display):
        # Using Surface.fill() can be faster than pygame.draw.rect().
        display.fill(self.color, (self.x, self.y, 1, 1))

    def update(self):
        self.x += self.delta_x
        self.y += self.delta_y

        if self.x < 0:
            self.x = 0
            self.delta_x = -self.delta_x
        elif self.x > MAX_X:
            self.x = MAX_X
            self.delta_x = -self.delta_x

        if self.y < 0:
            self.y = 0
            self.delta_y = -self.delta_y
        elif self.y > MAX_Y:
            self.y = MAX_Y
            self.delta_y = -self.delta_y


def classify(balls):
    """ Sort balls in bins. """
    bins = deepcopy(EMPTY_BINS)
    for ball in balls:
        m, n = ball.x // WM, ball.y // WN
        try:
            bins[m][n].append(ball)
        except IndexError:
            raise IndexError(f'bins[{m}][{n}] -> {ball.x}, {ball.y}')
    return bins

def detect_collisions(balls):
    """ Find all colliding balls and return whether any were found.
    """
    bins = classify(balls)  # Separate balls into bins.
    collisions = False
    for m in range(M):
        for n in range(N):
            if bins[m][n]:  # Non-empty?
                for a, b in (pair for pair in combinations(bins[m][n], 2)):
                    if(a.x == b.x and a.y == b.y and (a.color != MARKED or
                                                      b.color != MARKED)):
                        a.color = b.color = MARKED
                        collisions = True
    return collisions

def main():
    pygame.init()
    display = pygame.display.set_mode((HEIGHT, WIDTH))
    clock = pygame.time.Clock()
    pygame.display.set_caption("Game")

    balls = [
        Ball(randint(MARGIN, MAX_X), randint(MARGIN, MAX_Y),
             randint(-MAX_DELTAX_X, MAX_DELTAX_X), randint(-MAX_DELTAX_Y, MAX_DELTAX_Y))
           for _ in range(NUM_BALLS)
    ]

    # Main loop.
    remove_collisions = False  # No collisions first iteration.
    while len(balls):
        display.fill(BLACK)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    return

        # Remove any collisions found.
        if remove_collisions:
            balls[:] = [ball for ball in balls if ball.color != MARKED]

        # Update display.
        for ball in balls:
            ball.draw(display)
            ball.update()

        # Check after ball updates.
        remove_collisions = detect_collisions(balls)

        pygame.display.flip()
        clock.tick(60)

main()