Python & Pygame: столкновение мяча с внутренностью круга - PullRequest
7 голосов
/ 06 января 2011

Я создаю игру, в которой шары подпрыгивают вокруг внутри гораздо большего круга.Большой круг не двигается.

Вот код, который я сейчас использую для этих столкновений:

def collideCircle(circle, ball):
    """Check for collision between a ball and a circle"""
    dx = circle.x - ball.x
    dy = circle.y - ball.y

    distance = math.hypot(dx, dy)

    if distance >= circle.size + ball.size:
        # We don't need to change anything about the circle, just the ball
        tangent = math.atan2(dy, dx)
        ball.angle = 2 * tangent - ball.angle
        ball.speed *= elasticity + 0.251

        angle = 0.5 * math.pi + tangent
        ball.x -= math.sin(angle)
        ball.y += math.cos(angle)

Он основан на замечательном учебнике Питера Коллингриджа надздесь .

Объекты окружности и шара являются классами, с (x, y), радиусом, углом и скоростью.

Однако у меня возникли две проблемы с этим методом:

  1. мяч отскакивает от (я подозреваю) является его «узловой точкой», которая, как представляется, в верхнем правом углу круга.

Взглянув на возможные решения уже здесь, в частности, «Быстрый кругобнаружение столкновений "[Ссылка удалена из-за ограничения количества спам-ссылок], в которой, хотя в Java используется один и тот же метод, все они имеют дело с внешними столкновениями, тогда как я смотрю на отскок мяча вокруг круга.

Вот также определения классов Ball () и Circle ():

class Ball():
    def __init__(self, (x,y), size):
        """Setting up the new instance"""
        self.x = x
        self.y = y
        self.size = size
        self.colour = (0,128,255)
        self.thickness = 0
        self.speed = 0.01
        self.angle = math.pi/2

    def display(self):
        """Draw the ball"""
        pygame.draw.circle(screen, self.colour, (int(self.x), int(self.y)), self.size, self.thickness)

    def move(self):
        """Move the ball according to angle and speed"""
        self.x += math.sin(self.angle) * self.speed
        self.y -= math.cos(self.angle) * self.speed
        (self.angle, self.speed) = addVectors((self.angle, self.speed), gravity)
        self.speed *= drag

class Circle():
    def __init__(self, (x,y), size):
        """Set up the new instance of the Circle class"""
        self.x = x
        self.y = y
        self.size = size
        self.colour = (236, 236, 236)
        self.thickness = 0
        self.angle = 0 # Needed for collision...
        self.speed = 0 # detection against balls

    def display(self):
        """Draw the circle"""
        pygame.draw.circle(screen, self.colour, (int(self.x), int(self.y)), self.size, self.thickness

Заранее спасибо, Натан

Ответы [ 3 ]

13 голосов
/ 07 января 2011

Не отвечая на ваш вопрос, я бы хотел прокомментировать вашу стратегию реализации и рекомендовать новый подход.Вы представляете скорость мяча в форме полярных координат, как ball.angle и ball.speed.

Я думаю, что это будет вообще неудобно для вас.Например, в коде столкновения вы в итоге вызываете atan2, чтобы превратить вектор (dx, dy) в угол, а затем вы вызываете sin и cos, чтобы превратить угол обратно в векторснова.(Кроме того, если вы когда-нибудь попытаетесь обобщить свой код на три измерения, вы окажетесь в мире боли.) Поэтому, если у вас нет особых требований, требующих полярных координат, я рекомендую вам делать то, что делают все остальные, а именно представлятьскорость шара в декартовых координатах как вектор (vx, vy).

Я также рекомендую изменить свой физический подход с static one ("в настоящее время это объект A")столкновение с объектом B? ") с динамическим единицей (" будет ли объект A сталкиваться с объектом B во время его следующего шага движения? ").В системе статической физики вы часто сталкиваетесь с объектами, пересекающими друг друга в конце шага движения, и затем вам нужно найти лучший способ заставить их снова разделиться, что трудно понять правильно.

Если вы сделаете оба из них, отскок мяча без какой-либо тригонометрии будет простым.

Шаг 1. Преобразуйте столкновение окружности / окружности в столкновение точки / окружности, используя сложение Минковского :

Original problem at left shows small circle moving inside a large circle. Transformed problem at right shows point moving in circle whose radius is the difference between the radii of the circles in the original problem.

Шаг 2. Рассмотрим временной отрезок, в котором шар начинается с p = (px, py) и перемещается на v = (VX, VY).Это пересекается с кругом?Для этого вы можете использовать стандартный тест отрезка / круга , за исключением того, что смысл теста перевернут.

Шаг 3. Найти точку столкновения c =(сх, су).Мяч отскакивает от круга так же, как и от линии t , касающейся круга в этой точке.Для окружности с центром в начале координат касательный вектор просто (-cy, cx), и я уверен, что вы можете определить, как его вычислить для других окружностей.

Figure described above

См. Этот ответ , чтобы узнать, как рассчитать новый путь мяча на основе коэффициентов трения и восстановления.

Шаг 4. Не забывайте, что мяч все еще может иметь некоторое расстояние додвигаться по новому вектору w .Если шаг по времени достаточно велик или скорость достаточно высока, он может снова столкнуться в течение того же отрезка времени.

12 голосов
/ 07 января 2011

Я рад, что вам понравился мой урок. Мне нравится ваш вариант, на самом деле он должен быть проще.

Во-первых, я думаю, вам нужно изменить тест на столкновение на:

if distance >= circle.size - ball.size:

Поскольку чем больше размер шарика, тем меньше расстояние между его центром и центром круга. Это должно заставить шары отскочить в нужном месте (внутри круга).

Тогда я думаю, что вам просто нужно поменять местами знаки для x и y, и все должно работать.

ball.x += math.sin(angle)
ball.y -= math.cos(angle)

Чтобы переместить мяч на правильное расстояние, вы можете рассчитать перекрытие:

overlap = math.hypot(dx, dy) - (circle.size - ball.size)

if overlap >= 0:
  tangent = math.atan2(dy, dx)
  ball.angle = 2 * tangent - ball.angle
  ball.speed *= elasticity

  angle = 0.5 * math.pi + tangent
  ball.x += math.sin(angle)*overlap
  ball.y -= math.cos(angle)*overlap

Удачи

2 голосов
/ 06 января 2011

Большинство графических пакетов используют верхний левый угол в качестве начала для рисования кода.Скорее всего, вам нужны 2 набора координат: тот, с которым вы сталкиваетесь / перемещаете / и т. Д., И один для рисования (x-радиус, y-радиус).

Кроме того, не слишком задумываясь об этом, следуетпроверка на пересечение будет distance + ball.size >= circle.size?Расстояние от центра до шара плюс его радиус должны быть меньше радиуса круга, если я правильно понял настройку.

...