Почему виджет шара отклоняется перед столкновением с виджетом прямоугольника в kivy? - PullRequest
0 голосов
/ 26 января 2020

Я работаю над одним игровым приложением, в котором мне нужно, чтобы виджет шара отклонялся под углом 90 к направлению движения виджета прямоугольника после столкновения. В приведенном ниже коде мяч делает то, что я сказал выше, но он отклоняется на некоторое расстояние до столкновения с прямоугольником. Это было бы связано с функционированием в коде collide_widget (). Пожалуйста, помогите решить эту проблему!

from kivy.app import App
from kivy.graphics import Rotate, Rectangle, Ellipse, Color
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, CardTransition
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty, NumericProperty, ReferenceListProperty, ListProperty, 
DictProperty
from kivy.uix.floatlayout import FloatLayout
from kivy.clock import Clock
from kivy.vector import Vector
from kivy.graphics.context_instructions import PopMatrix, PushMatrix

Builder.load_string('''

<PongBall>:
    size: 50, 50
    canvas:
        Color:
            rgba: 0,0,1,1
        Ellipse:
            pos: self.pos
            size: self.size


<Game>:
    ball: pong_ball
    object: Object
    FloatLayout:
        Button:
            pos_hint:{"x":2.6,"y":0}
            size_hint: 3, 1
            text:"Throw"
            background_color: 2,1,190,1
            border: 30,30,30,30
            on_release:
                root.start()

        Button:
            pos_hint:{'x':7.3, 'y':5.3}
            size_hint: 0.5,0.5
            text:'restart'
            on_release:
                root.serve_ball()

    PongBall:
        id: pong_ball
        center: self.center

    Object:
        id: Object
        center: self.rotate_origin

<Game1>:
    ball: pong_ball
    object1: Object1
    object2: Object2
    FloatLayout:
        Button:
            pos_hint:{"x":2.6,"y":0}
            size_hint: 3, 1
            text:"Throw"
            background_color: 2,1,190,1
            border: 30,30,30,30
            on_release:
                root.start()

        Button:
            pos_hint:{'x':7.3, 'y':5.3}
            size_hint: 0.5,0.5
            text:'restart'
            on_release:
                root.serve_ball()

    PongBall:
        id: pong_ball
        center: self.center

    Object1:
        id: Object1
        center: self.rotate_origin
    Object2:
        id: Object2
        center: self.rotate_origin

<Manager>:
    id: screen_manager

    Screen:
        name:"P"
        FloatLayout:

            Button:
                pos_hint:{"x":0.2,"y":0.05}
                size_hint: 0.6, 0.2
                font_size: (root.width**2 + root.height**2) / 13**4
                text: "Play"
                background_color: 255,0,1,1
                on_release:
                    root.transition.direction = "up";s3.serve_ball()        
                    root.current = "again"

    Screen:
        name: 'again'
        Game1:
            id:s3

''')

class Object(Widget):
    def __init__(self, *args, **kwargs):
        Widget.__init__(self, *args, **kwargs)
        self.rect_pos_x = 500
        self.rect_pos_y = 370
        self.rect_pos = self.rect_pos_x, self.rect_pos_y
        self.rect_width = 200
        self.rect_height = 30
        self.rect_size = self.rect_width, self.rect_height
        self.rotate_origin_x = self.rect_pos_x + self.rect_width / 2
        self.rotate_origin_y = self.rect_pos_y + self.rect_height / 2
        self.rotate_origin = self.rotate_origin_x, self.rotate_origin_y
        self.angle = 135
        print('rect 1')
        with self.canvas:
            PushMatrix()
            Rotate(origin=self.rotate_origin, angle=self.angle)
            Color(rgb=(0,197,68))
            Rectangle(pos=self.rect_pos, size=self.rect_size)
            PopMatrix()

    def rotate(self):
        self.canvas.clear()
        self.angle += 90
        if (self.angle > 315):
            self.angle = 225
        with self.canvas:
            PushMatrix()
            Rotate(origin=self.rotate_origin, angle=self.angle)
            Color(rgb=(0, 255, 100))
            Rectangle(pos=self.rect_pos, size=self.rect_size)
            PopMatrix()

    def deflect_ball(self, ball):
        if self.collide_widget(ball):
            if not ball.collided:
                vx, vy = ball.velocity
                if self.angle == 135:
                    ball.velocity = Vector(-vx, vy).rotate(90)
                if self.angle == 225:
                    ball.velocity = Vector(-vx, vy).rotate(270)
                if self.angle == 315:
                    ball.velocity = Vector(-vx, vy).rotate(90)
                ball.collided = True
        else:
            ball.collided = False


    def on_touch_up(self, touch):
        if self.collide_point(*touch.pos):
            self.rotate()
            print(self.angle)

class PongBall(Widget):
    velocity_x = NumericProperty(0)
    velocity_y = NumericProperty(0)
    velocity = ReferenceListProperty(velocity_x, velocity_y)
    collided = DictProperty(False)


    def move(self):
        self.pos = Vector(*self.velocity) + self.pos

class Game(Widget):
    ball = ObjectProperty(None)
    object = ObjectProperty(None)

    def start(self):
        Clock.schedule_interval(self.update, 1.0 / 60.0)

    def serve_ball(self, vel=(5, 0)):
        Clock.unschedule(self.update)
        self.ball.center = 40, 380
        self.ball.velocity = vel

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

        if (self.ball.y < self.y+50) or self.ball.y > 500:
            self.ball.velocity_y = 0
            Clock.unschedule(self.update)
            print('tested')
            self.serve_ball()
            sm.current= 'again'

class Object1(Widget):
    def __init__(self, *args, **kwargs):
        Widget.__init__(self, *args, **kwargs)
        self.rect_pos_x = 500
        self.rect_pos_y = 400
        self.rect_pos = self.rect_pos_x, self.rect_pos_y
        self.rect_width = 200
        self.rect_height = 30
        self.rect_size = self.rect_width, self.rect_height
        self.rotate_origin_x = self.rect_pos_x + self.rect_width / 2
        self.rotate_origin_y = self.rect_pos_y + self.rect_height / 2
        self.rotate_origin = self.rotate_origin_x, self.rotate_origin_y
        self.angle = 135
        print('rect 1')
        with self.canvas:
            PushMatrix()
            Rotate(origin=self.rotate_origin, angle=self.angle)
            Color(rgb=(0,197,68))
            Rectangle(pos=self.rect_pos, size=self.rect_size)
            PopMatrix()

    def rotate(self):
        self.canvas.clear()
        self.angle += 90
        if (self.angle > 315):
            self.angle = 225
        with self.canvas:
            PushMatrix()
            Rotate(origin=self.rotate_origin, angle=self.angle)
            Color(rgb=(0, 255, 100))
            Rectangle(pos=self.rect_pos, size=self.rect_size)
            PopMatrix()

    def deflect_ball(self, ball):
        if self.collide_widget(ball):
            if not ball.collided[self]:
                vx, vy = ball.velocity
                if self.angle == 135:
                    ball.velocity = Vector(-vx, vy).rotate(90)
                if self.angle == 225:
                    ball.velocity = Vector(-vx, vy).rotate(270)
                if self.angle == 315:
                    ball.velocity = Vector(-vx, vy).rotate(90)
                ball.collided[self] = True
        else:
            ball.collided[self] = False


    def on_touch_up(self, touch):
        if self.collide_point(*touch.pos):
            self.rotate()
            print(self.angle)

class Object2(Widget):
    def __init__(self, *args, **kwargs):
        Widget.__init__(self, *args, **kwargs)
        self.rect_pos_x = 500
        self.rect_pos_y = 170
        self.rect_pos = self.rect_pos_x, self.rect_pos_y
        self.rect_width = 200
        self.rect_height = 30
        self.rect_size = self.rect_width, self.rect_height
        self.rotate_origin_x = self.rect_pos_x + self.rect_width / 2
        self.rotate_origin_y = self.rect_pos_y + self.rect_height / 2
        self.rotate_origin = self.rotate_origin_x, self.rotate_origin_y
        self.angle = 135
        print('rect 1')
        with self.canvas:
            PushMatrix()
            Rotate(origin=self.rotate_origin, angle=self.angle)
            Color(rgb=(0,197,68))
            Rectangle(pos=self.rect_pos, size=self.rect_size)
            PopMatrix()

    def rotate(self):
        self.canvas.clear()
        self.angle += 90
        if (self.angle > 315):
            self.angle = 225
        with self.canvas:
            PushMatrix()
            Rotate(origin=self.rotate_origin, angle=self.angle)
            Color(rgb=(0, 255, 100))
            Rectangle(pos=self.rect_pos, size=self.rect_size)
            PopMatrix()
    def deflect_ball(self, ball):
        if self.collide_widget(ball):
            if not ball.collided[self]:
                vx, vy = ball.velocity
                if self.angle == 135:
                    ball.velocity = Vector(-vx, vy).rotate(90)
                if self.angle == 225:
                    ball.velocity = Vector(-vx, vy).rotate(270)
                if self.angle == 315:
                    ball.velocity = Vector(-vx, vy).rotate(90)
                ball.collided[self] = True
        else:
            ball.collided[self] = False


    def on_touch_up(self, touch):
        if self.collide_point(*touch.pos):
            self.rotate()
            print(self.angle)

class Game1(Widget):
    ball = ObjectProperty(None)
    object1 = ObjectProperty(None)
    object2 = ObjectProperty(None)
    def start(self):
        Clock.schedule_interval(self.update, 1.0 / 60.0)

    def serve_ball(self, vel=(5, 0)):
        Clock.unschedule(self.update)
        self.ball.center = 40, 380
        self.ball.velocity = vel

    def update(self, dt):
        self.ball.move()
        self.object1.deflect_ball(self.ball)
        self.object2.deflect_ball(self.ball)

        if (self.ball.y < self.y+50) or self.ball.x <0:
            self.ball.velocity_y = 0
            Clock.unschedule(self.update)
            print('tested')
            self.serve_ball()

class Manager(ScreenManager):
    pass

sm = Manager()

class ScreensApp(App):
    def build(self):
        return sm

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

1 Ответ

1 голос
/ 26 января 2020

Поскольку метод collide_widget() сравнивает pos и size задействованного Widgets, инструкции рисования холста не влияют на обнаружение столкновений. Итак, я думаю, вам нужно определить свой собственный метод для обнаружения столкновения. Вот моя попытка такого метода:

def collide_ball(self, ball):
    # get vector from center of rect to the ball
    to_ball = Vector((ball.x - self.rotate_origin_x), (ball.y - self.rotate_origin_y))

    # get x and y coordinates of above vector in rotated system
    x = to_ball.dot(self.rot_x_dir)   # along rect width
    y = to_ball.dot(self.rot_y_dir)   # along rect height

    # test for collision
    if x < -self.rect_width/2 - ball.size[0]/2:
        return False
    if x > self.rect_width/2 + ball.size[0]/2:
        return False
    if y < -self.rect_height/2 - ball.size[1]/2:
        return False
    if y > self.rect_height/2 + ball.size[1]/2:
        return False
    return True

Этот метод должен присутствовать в каждом Object.

. Для этого требуется определение self.rot_x_dir и self.rot__dir, которые представляют собой система координат, которая вращается с прямоугольником Object1 или Object2. Они должны быть переопределены везде, где изменяется угол поворота для Object. Например, в Object1.__init__():

    self.angle = 135
    self.rot_x_dir = Vector(1,0).rotate(self.angle)
    self.rot_y_dir = Vector(0,1).rotate(self.angle)

тот же код входит в Object2.__init__(). Аналогичным образом, в методе rotate():

def rotate(self):
    self.canvas.clear()
    self.angle += 90
    if (self.angle > 315):
        self.angle = 225
    self.rot_x_dir = Vector(1,0).rotate(self.angle)
    self.rot_y_dir = Vector(0,1).rotate(self.angle)
    with self.canvas:
        PushMatrix()
        self.rot = Rotate(origin=self.rotate_origin, angle=self.angle)
        Color(rgb=(0, 255, 100))
        Rectangle(pos=self.rect_pos, size=self.rect_size)
        PopMatrix()

Затем, где бы вы ни использовали

self.collide_widget(ball)

, замените его на:

self.collide_ball(ball)

Кроме того, в вашем «kv» настроить рисунок PongBall:

<PongBall>:
    size: 50, 50
    canvas:
        Color:
            rgba: 0,0,1,1
        Ellipse:
            # adjust Ellipse position to center it
            pos: self.x - self.size[0]/2, self.y - self.size[1]/2
            size: self.size
...