Розробка Pong Game за допомогою Kivy

32 хв. читання

В цьому туторіалі ми напишемо гру Pong використовуючи Kivy.

Почнемо

Давайте почнемо саме зі створення простого додатку. Створіть директорію для гри і в ній файл main.py.
Для початку ми помістимо у цей файл наступний код:

    from kivy.app import App
    from kivy.uix.widget import Widget
    
    
    class PongGame(Widget):
        pass
    
    
    class PongApp(App):
        def build(self):
            return PongGame()
    
    
    if __name__ == '__main__':
        PongApp().run()

Запускаємо додаток. На екрані з'явиться чорне вікно, не лякайтеся. Все що ми зробили, так це створили простий Kivy додаток, який повертає примірник нашого віджет класу PongGame і повертає він його в якості root елементу до UI (інтерфейсу) додатку. Але оскільки у нашому класі PongGame поки нічого немає, тому повертається просто чорний екран. У наступному кроці ми намалюємо бекграунд нашого додатку, а також бали, які визначатимуть як виглядатиме віджет PongGame.

Додаємо просту графіку

Створіть файл pong.kv
Ми будемо використовувати файл pong.kv для визначення зовнішнього вигляду класу PongGame. Оскільки клас нашого додатку називається PongApp, то ми можемо просто створити файл pong.kv у нашому каталозі і він буде автоматично завантажуватись при запуску нашої програми. Так після створення файлу додайте до нього:

    #:kivy 1.0.9
    
    <ponggame>:    
        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: "0"
            
        Label:
            font_size: 70  
            center_x: root.width * 3 / 4
            top: root.top - 50
            text: "0"

Поширена помилка: ім'я kv файлу повинна відповідати іменні додатка, наприклад PongApp (Pong, тобто частина до App).

Якщо ви запустите додаток, ви побачите білу смугу по центру, а також два нулі, які відображають очки гравців.

Пояснення синтаксису kv файлу

Перш ніж переходити до наступного кроку, ви скоріш за все захочете поближче познайомитись зі змістом kv файлу.
#:kivy 1.0.9
Ця перша лінія необхідна в кожному kv файлі. Слід почати з #: з подальшим пропуском а та версією Kivy, так Kіvy може переконатися в тому, яку версію ви використовуєте, та обробляти зворотну сумісність пізніше.

Після цього, ми встановлюємо "правила", які буду використовуватись для всіх екземплярів PongGame:
<PongGame>:
...
Як і в Python, kv файли використовують відступи для визначення вкладених блоків. Це буде застосовуватись до будь-якого екземпляру імені класу. Якщо ви приберете PongGame, то всі віджети будуть мати у собі вертикальну лінію та два лейби, оскільки такий код буде застосовуватись до всіх віджетів, що, як ви розумієте, неправильно і зовсім нам не підходить.
В середині секції правил, ви можете додавати різні блоки, щоб визначити стиль і зміст віджетів. Ви можете:

  • задавати значення властивостей,
  • додавати дочірні віджети
  • визначити розділ canvas, на якому ви зможете додавати графічні інструкції, які визначають, як віджет буде рендеритись.

Першим блоком до <PongGame> ми додали canvas:

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

Наш блок canvas каже, що віджет PongGame повинен намалювати деякі графічні примітиви. У нашому випадку ми малюємо прямокутник, встановлюємо позицію(поле pos) прямокутника відносно горизонтального центру на 5 пікселів ліворуч. Також ми встановлюємо розміри нашого прямокутника 10 пікселів в ширину та висоту рівну висоті нашого віджета. Прямокутник буде автоматично оновлюватись при зміні властивостей будь-яких використовуваних віджетів.
Спробуйте змінити розмір вікна додатка і зверніть увагу на те, що відбувається. Весь інтерфейс повинен змінюватись автоматично. Стандартна поведінка вікна - зміна розмірів елементів, базується на властивостях size_hint. За умовчуванням size_hint віджет набуває значень(1,1), тобто він розтягується на 100% в х та у напрямках, а отже заповнює весь доступний простір. Оскільки pos і size прямокутника і center_x та top лейблів були визначені в класі PongGame, то ці властивості будуть автоматично змінюватись при зміні відповідних властивостей віджету.
В останніх двох секціях ми додаємо досить схожі лейбли. Кожен з них додає віджет лейбл як дочірній до PongGame. На даний момент текст на них обох встановлений "0".

Додаємо М'яч

Окей, отже, у нас є базова pong-арена, але нам потрібен м'яч.
Ось python код для класу PongBall:

    class PongBall(Widget):
    
        # швидкість кульки на осях x та y
        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

Ось kv правила для малювання м'яча у вигляді білої кульки:

    <pongball>:
        size: 50, 50
        canvas:
            Ellipse:
                pos: self.pos
                size: self.size

Щоб змусити все це працювати, ви повинні додати імпорт для використовуваних Властивостей класів властивостей і [Вектора](http://kivy.org/docs/api- kivy.vector.html#kivy.vector.Vector).

Ось оновлений python код та kv файл для цього кроку:

    from kivy.app import App
    from kivy.uix.widget import Widget
    from kivy.properties import NumericProperty, ReferenceListProperty
    from kivy.vector import Vector
    
    
    class PongBall(Widget):
        velocity_x = NumericProperty(0)
        velocity_y = NumericProperty(0)
        velocity = ReferenceListProperty(velocity_x, velocity_y)
    
        def move(self):
            self.pos = Vector(*self.velocity) + self.pos
    
    
    class PongGame(Widget):
        pass
    
    
    class PongApp(App):
        def build(self):
            return PongGame()
    
    
    if __name__ == '__main__':
        PongApp().run()

pong.kv:

    #:kivy 1.0.9
    
    <pongball>:
        size: 50, 50 
        canvas:
            Ellipse:
                pos: self.pos
                size: self.size          
    
    <ponggame>:
        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: "0"
            
        Label:
            font_size: 70  
            center_x: root.width * 3 / 4
            top: root.top - 50
            text: "0"
        
        PongBall:
            center: self.parent.center

Зверніть увагу, що не тільки правила для віджета <PongBall> були додані, а також був доданий дочірній елемент до <PongGame>.

Додавання анімації м'яча

Круто, тепер у нас є м'яч і він має функцію move… Але він все ще нерухомий. Давайте вирішимо це питання.

Планування функції з Clock

Нам потрібно створити метод руху для нашого м'яча, який би викликався регулярно. На щастя, Kivy робить це дуже просто, дозволяючи планувати будь-яку функцію, яку ми хочемо використати за допомогою годинника та інтервалу.
Clock.schedule_interval(game.update, 1.0/60.0)
Цей код викликає оновлення ігрового об'єкта, який буде викликатись 1 раз в 60 секунд.

Об'єкт властивості/посилання

У нас є ще одна проблема. Ми б хотіли переконатись, що PongBall має свою функцію переміщення, яка б викликалась регулярно, але в нашому коді ми не маємо використовуваного об'єкта м'яча, оскільки ми просто додали його за допомогою kv файлу до класу PongGame.
Оскільки ми збираємось мати дещо більше ніж просто рух м'яча, ми повинні мати метод оновлення для класу PongGame. Крім того, враховуючи, що у нас є посилання на об'єкт гри, ми можемо легко планувати свій новий метод оновлення, коли додаток буде побудовано:

    class PongGame(Widget):
    
        def update(self, dt):
            # викликаємо ball.move та інші
            pass
    
    class PongApp(App):
    
        def build(self):
            game = PongGame()
            Clock.schedule_interval(game.update, 1.0/60.0)
            return game

Тим не менш, це ще не змінює того факту, що у нас немає посилання на дочірній віджет PongBall створеного за правилом kv. Щоб виправити це, ми можемо додати ObjectProperty до класу PongGame, і підключити його до віджета, створеного в правилі kv. Як тільки це буде зроблено, ми легко можемо посилатися на властивості м'яча, всередині методу поновлення і навіть зробити його відбивання від країв:

    class PongGame(Widget):
        ball = ObjectProperty(None)
    
        def update(self, dt):
            self.ball.move()
    
            # відскакування від верху і низу
            if (self.ball.y < 0) or (self.ball.top > self.height):
                self.ball.velocity_y *= -1
    
            # відскакування від правої і лівої сторони
            if (self.ball.x < 0) or (self.ball.right > self.width):
                self.ball.velocity_x *= -1

Не забудьте підключити його в файлі kv, даючи дочірньому віджету, і встановлення м'ячу PongGame, ObjectProperty до цього id:

    <ponggame>:
        ball: pong_ball
    
        # canvas і label
    
        PongBall:
            id: pong_ball
            center: self.parent.center

Ось повний вигляд для цього кроку:

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 PongBall(Widget):
        velocity_x = NumericProperty(0)
        velocity_y = NumericProperty(0)
        velocity = ReferenceListProperty(velocity_x, velocity_y)
    
        def move(self):
            self.pos = Vector(*self.velocity) + self.pos
    
    
    class PongGame(Widget):
        ball = ObjectProperty(None)
    
        def serve_ball(self):
            self.ball.center = self.center
            self.ball.velocity = Vector(4, 0).rotate(randint(0, 360))
    
        def update(self, dt):
            self.ball.move()
    
            #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
            if (self.ball.x < 0) or (self.ball.right > self.width):
                self.ball.velocity_x *= -1
    
    
    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()

pong.kv:

    #:kivy 1.0.9
    
    <pongball>:
        size: 50, 50 
        canvas:
            Ellipse:
                pos: self.pos
                size: self.size          
    
    <ponggame>:
        ball: pong_ball
        
        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: "0"
            
        Label:
            font_size: 70  
            center_x: root.width * 3 / 4
            top: root.top - 50
            text: "0"
        
        PongBall:
            id: pong_ball
            center: self.parent.center

З'єднання вхідних подій

Додавання гравців та реагування на дотик

В нас вже є м'яч, який рухається та відбивається від стінок, але досі немає рухливих ракеток гравця, та відстеження рахунку. Ми не будемо знову вдаватися до створення нового класу та додання правил до документа kv, так як все це вже було описано в попередніх кроках. Замість цього давайте сфокусуємось на тому, як буде рухатись віджет Player у відповідь на дії користувача. Ви можете отримати весь код і kv для класу PongPaddle в кінці цього розділу.
В Kivy, віджет може реагувати на введення шляхом реалізації методів on_touch_down, в on_touch_move і on_touch_up. За замовчуванням клас Віджету реалізує ці методи просто викликавши відповідний метод.
Наш Pong дуже простий. Ракеткам потрібно просто рухатись вгору і вниз. Насправді це просто, нам навіть не потрібен віджет гравця для обробки цих подій. Ми просто реалізуємо функцію on_touch_move для класу PongGame і встановимо позицію для правого та лівого гравця, на основі чи відбувся дотик до правої, або лівої сторони екрану.

Встановіть обробник one_touch_move:

    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

Ми будемо тримати рахунок для кожного гравця в NumericProperty. Лейбл в PongGame буде оновлюватись шляхом зміни балів в NumericProperty, який в свою чергу оновлює дитина PongGame. Коли м'яч виходить зі сторін, ми будемо оновлювати рахунок і м'яч знову з'являтиметься в грі, змінивши спосіб оновлення в класі PongGame. Клас PongPaddle також реалізує метод bounce_ball, так що м'яч відскакує по-різному в залежно від місця потрапляння на ракетку. Ось код для класу PongPaddle:

    class PongPaddle(Widget):
    
        score = NumericProperty(0)
    
        def bounce_ball(self, ball):
            if self.collide_widget(ball):
                speedup  = 1.1
                offset = 0.02 * Vector(0, ball.center_y-self.center_y)
                ball.velocity =  speedup * (offset - ball.velocity)

Цей алгоритм для м'яча, який відбивається, дуже простий.
Ось весь код:
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
    
    
    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 * 1.1
                ball.velocity = vel.x, vel.y + offset
    
    
    class PongBall(Widget):
        velocity_x = NumericProperty(0)
        velocity_y = NumericProperty(0)
        velocity = ReferenceListProperty(velocity_x, velocity_y)
    
        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()
    
            #відбивання ракеток
            self.player1.bounce_ball(self.ball)
            self.player2.bounce_ball(self.ball)
    
            #відскакування м'яча від низу або верху
            if (self.ball.y < self.y) or (self.ball.top > self.top):
                self.ball.velocity_y *= -1
    
            #went of to a side 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()

pong.kv:

    #:kivy 1.0.9
    
    <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
* ```
Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.7K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація