Как проверить наличие коллизий между всеми экземплярами моего класса в Pygame? - PullRequest
2 голосов
/ 10 марта 2019

Я сейчас пытаюсь распечатать на экране, когда происходит столкновение, но не знаю, как это сделать только для 1 класса. Я знаю, как заставить два объекта сталкиваться из разных классов, но я не знаю, как это сделать для одного класса с 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 ]

1 голос
/ 10 марта 2019

Обновлено : После исправления ошибки в более ранней версии, которая значительно замедлила ее, я пошел дальше и реализовал ряд других улучшений / оптимизаций, поскольку это кажется очень жизнеспособным методом..

Вот работоспособная реализация одного подхода, который, кажется, работает очень хорошо.Чтобы избежать сравнения положения каждой пары шаров на каждой итерации - почти 1 000 000 сравнений для 1000 шаров - он использует стратегию разделения экрана на M x N делений или «мусорных ведер» и «классификации» каждого из шаров на них.на основе его текущей позиции на экране.Эффективная сортировка их очень недорогим способом, поскольку при этом используются только некоторые относительно простые вычисления.

Как только это будет сделано, нужно сравнивать только пары шаров в каждой ячейке, что быстрее, поскольку каждая ячейкабудет содержать только подмножество всех из них.В этом есть компромисс, потому что, хотя вы создаете больше бункеров - так что в каждом из них меньше шаров - это также увеличивает количество бинов, которые необходимо обработать.

Цвета сталкивающихся шаров сначала изменяются на указанный цвет, чтобы пометить их для удаления на следующем шаге - цвет фактически не используется для рисования чего-либо - только в качестве простого способа пометить их.Я сделал это, чтобы избежать печати при обнаружении коллизий, которые могли бы слишком сильно замедлить процесс.

В приведенном ниже примере кода есть 8 x 8 = 64 лотка, каждый из которых первоначально будет содержать в среднем только 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()
1 голос
/ 10 марта 2019

Если вы хотите найти равные точки, вы должны сравнить координаты 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.После обновления и рисования поле должно быть очищено, потому что позиции изменились.Для этого используйте список позиций, потому что это намного быстрее, чем «очистка» всего поля.Я добавил проверки сомов, потому что некоторые из ваших объектов, кажется, выходят за пределы (возможно, эту ошибку тоже нужно исправить).

# 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...