Мяч Kivy PongApp проходит через весло с высокой скоростью

#python #kivy

Вопрос:

Я новичок в python и kivy и следил за учебником Kivy по построчному созданию PongApp, когда я заметил, что после того, как мяч столкнется с веслом 25 раз, он не зарегистрирует 26-е столкновение, и, таким образом, счет игрока увеличится.

Я предполагаю, что эта проблема связана со скоростью, превышающей определенную скорость вдоль оси x, где положение мяча никогда не будет взаимодействовать с положением x весла. Однако меня смущает то, что при изменении увеличения скорости за столкновение с 1,1 до 1,2 максимальное количество столкновений до увеличения очков варьируется от 11 до 18 раз.

Мой вопрос в том, что на самом деле является основной причиной этой проблемы, и как я могу гарантировать, что мяч всегда будет сталкиваться с веслом независимо от скорости?

main.py:

 from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import (NumericProperty, ReferenceListProperty, ObjectProperty)
from kivy.vector import Vector
from kivy.clock import Clock
from random import randint

class PongPaddle(Widget):
    score = NumericProperty(0)

    def bounce_ball(self, ball):
        if self.collide_widget(ball):
            vx, vy = ball.velocity
            offset = (ball.center_y - self.center_y) / (self.height / 2)
            bounced = Vector(-1 * vx, vy)
            vel = bounced * 3
            ball.velocity = vel.x, vel.y   offset


class PongBall(Widget):

    # velocity of the ball on x and y axis
    velocity_x = NumericProperty(0)
    velocity_y = NumericProperty(0)

    # referencelist property so we can use ball.velocity as
    # a shorthand, just like e.g. w.pos for w.x and w.y
    velocity = ReferenceListProperty(velocity_x, velocity_y)

    # ``move`` function will move the ball one step. This
    #  will be called in equal intervals to animate the ball
    def move(self):
        self.pos = Vector(*self.velocity)   self.pos

class PongGame(Widget):
    ball = ObjectProperty(None)
    player1 = ObjectProperty(None)
    player2 = ObjectProperty(None)

    def serve_ball(self, vel=(4, 0)):
        self.ball.center = self.center
        self.ball.velocity = vel

    def update(self, dt):
        self.ball.move()

        #bounce off paddles
        self.player1.bounce_ball(self.ball)
        self.player2.bounce_ball(self.ball)

        # bounce off top and bottom
        if (self.ball.y < 0) or (self.ball.top > self.height):
            self.ball.velocity_y *= -1

        # bounce off left and right to score point
        if self.ball.x < self.x:
            self.player2.score  = 1
            self.serve_ball(vel=(4, 0))
        if self.ball.x > self.width:
            self.player1.score  = 1
            self.serve_ball(vel=(-4, 0))

    def on_touch_move(self, touch):
        if touch.x < self.width / 3:
            self.player1.center_y = touch.y
        if touch.x > self.width - self.width / 3:
            self.player2.center_y = touch.y


class PongApp(App):
    def build(self):
        game = PongGame()
        game.serve_ball()
        Clock.schedule_interval(game.update, 1.0/60.0)
        return game

if __name__ == '__main__':
    PongApp().run()
 

понг.кв:

 #:kivy 2.0.0
<PongBall>:
    size: 50, 50
    canvas:
        Ellipse:
            pos: self.pos
            size: self.size

<PongPaddle>:
    size: 25, 200
    canvas:
        Rectangle:
            pos: self.pos
            size: self.size

<PongGame>:
    ball: pong_ball
    player1: player_left
    player2: player_right

    canvas:
        Rectangle:
            pos: self.center_x - 5, 0
            size: 10, self.height

    Label:
        font_size: 70
        center_x: root.width / 4
        top: root.top - 50
        text: str(root.player1.score)

    Label:
        font_size: 70
        center_x: root.width * 3 / 4
        top: root.top - 50
        text: str(root.player2.score)

    PongBall:
        id: pong_ball
        center: self.parent.center

    PongPaddle:
        id: player_left
        x: root.x
        center_y: root.center_y

    PongPaddle:
        id: player_right
        x: root.width - self.width
        center_y: root.center_y
 

Ответ №1:

Это фундаментальная проблема с простыми алгоритмами столкновения: столкновение проверяется только один раз в каждом кадре после перемещения объектов, и если они перемещаются друг через друга во время этого кадра, алгоритм не может этого заметить. Проблема хорошо известна, например, версии этой проблемы являются одной из причин, по которой в компьютерных играх часто можно ухитриться прорезать стены (хотя в целом в настоящее время игры используют более сложные алгоритмы и обманываются более сложными способами).

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

Эффективное выполнение общих столкновений, как правило, является сложной проблемой, с различными компромиссами, которые вы можете сделать в зависимости от того, для чего вы хотите оптимизировать (например, если вы хотите эффективно обрабатывать приблизительные столкновения многих объектов, у вас есть разные проблемы, если вы хотите точно обрабатывать небольшое количество объектов со сложными формами). В примере Киви используется простой алгоритм, чтобы избежать усложнения примера, а не потому, что это хорошее решение. Если вы действительно заботитесь об эффективном выполнении столкновений, я бы рекомендовал использовать физическую библиотеку для их обработки, существуют различные библиотеки с интерфейсами python.