В цьому туторіалі ми напишемо гру 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
* ```
Ще немає коментарів