Я адаптировал часть руководства по kivy pong (https://kivy.org/doc/stable/tutorials/pong.html), чтобы создать класс мяча, который должен обновляться 60 раз в секунду, перемещая мяч по экрану. Точно так же, когда мяч ударяется о борта, он должен отражаться в противоположном направлении. Однако мяч просто неподвижно сидит в углу экрана. Какую синтаксическую/логическую ошибку я делаю?
Вот мой код:
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
from kivy import Config
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition,\
SlideTransition
from kivy.uix.widget import Widget
from kivy.animation import Animation
from kivy.properties import NumericProperty, ReferenceListProperty,\
ObjectProperty
from kivy.clock import Clock
from kivy.vector import Vector
from random import randint
Builder.load_string('''
<Ball>:
Image:
source: '58-Breakout-Tiles.png'
size: 15, 15
pos: self.pos
<SettingsScreen>:
close: close
AnchorLayout:
anchor_x: 'left'
anchor_y: 'top'
Image:
id: close
size_hint: .03, .03
source: 'grey_crossGrey.png'
GridLayout:
cols: 2
Label:
font_name: 'vgafix.fon'
text: 'Music: '
Switch:
active: True
Label:
font_name: 'vgafix.fon'
text: 'Sounds: '
Switch:
active: True
<MenuScreen>:
cog: cog
AnchorLayout:
anchor_x: 'right'
anchor_y: 'top'
Image:
id: cog
size_hint: .03, .03
source: 'settings-cog.png'
BoxLayout:
orientation: 'vertical'
Image:
source: 'brickbreaker log.png'
Label:
font_name: 'vgafix.fon'
text: 'Tap to start'
<GameScreen>:
ball: ball
cog: cog
AnchorLayout:
anchor_x: 'right'
anchor_y: 'top'
Image:
id: cog
size_hint: .03, .03
source: 'settings-cog.png'
Ball:
id: ball
center: self.parent.center
''')
Config.set('graphics', 'multisamples', '0')
class Ball(Widget):
velocityX, velocityY = NumericProperty(0), NumericProperty(0)
velocity = ReferenceListProperty(velocityX, velocityY)
def move(self):
self.pos = Vector(*self.velocity) + self.pos
class Player(Widget):
pass
class Brick(Widget):
pass
class SettingsScreen(Screen):
def __init__(self, **kwargs):
super(SettingsScreen, self).__init__(**kwargs)
self.previous = False
def on_touch_down(self, touch):
if self.close.collide_point(*touch.pos):
sm.transition = SlideTransition(direction = 'right')
sm.current = self.previous
class MenuScreen(Screen):
def __init__(self, **kwargs):
super(MenuScreen, self).__init__(**kwargs)
def on_touch_down(self, touch):
if self.cog.collide_point(*touch.pos):
sm.transition = SlideTransition(direction = 'left')
sm.get_screen('settings').previous = 'menu'
sm.current = 'settings'
else:
sm.transition = FadeTransition()
sm.current = 'game'
class GameScreen(Screen):
ball = ObjectProperty(None)
def __init__(self, **kwargs):
super(GameScreen, self).__init__(**kwargs)
self.initBall()
def on_touch_down(self, touch):
if self.cog.collide_point(*touch.pos):
sm.transition = SlideTransition(direction = 'left')
sm.get_screen('settings').previous = 'game'
sm.current = 'settings'
def initBall(self):
self.ball.center = self.center
self.ball.velocity = Vector(4, 0).rotate(randint(0, 360))
def update(self, dt):
self.ball.move()
if (self.ball.y < 0) or (self.ball.top > self.height):
self.ball.velocityY *= -1
# bounce off left and right
if (self.ball.x < 0) or (self.ball.right > self.width):
self.ball.velocityX *= -1
sm = ScreenManager(transition = FadeTransition())
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(GameScreen(name='game'))
sm.add_widget(SettingsScreen(name='settings'))
class BrickBreakerInsanityApp(App):
def build(self):
Clock.schedule_interval(sm.get_screen('game').update, 1.0/60.0)
return sm
if __name__ == '__main__':
BrickBreakerInsanityApp().run()
активы кода:
https://i.stack.imgur.com/rR799.png
https://i.stack.imgur.com/ngYvL.png
https://i.stack.imgur.com/AuxI3.png
https://i.stack.imgur.com/ypd7C.png
https://drive.google.com/open?id=1GAnv5DfjNUuAXTybmsan90Dm0OuSVOfb
Есть два решения проблемы.
Image:
size_hint: None, None
, чтобы перезаписать размер по умолчанию (1, 1) или (100, 100)canvas:
Builder.load_string('''
<Ball>:
size_hint: None, None
size: 15, 15
canvas:
Rectangle:
source: '58-Breakout-Tiles.png'
pos: self.pos
size: self.size
source
This property represents the filename to load the texture from. If you want to use an image as source, do it like this:
with self.canvas: Rectangle(source='mylogo.png', pos=self.pos, size=self.size)
Here’s the equivalent in Kivy language:
<MyWidget>: canvas: Rectangle: source: 'mylogo.png' pos: self.pos size: self.size
self.rect
к методу update_ball()
всякий раз, когда есть изменения pos
или size
.from kivy.core.image import Image
from kivy.graphics import Rectangle
...
class Ball(Widget):
velocityX, velocityY = NumericProperty(0), NumericProperty(0)
velocity = ReferenceListProperty(velocityX, velocityY)
def __init__(self, **kwargs):
super(Ball, self).__init__(**kwargs)
texture = Image('58-Breakout-Tiles.png').texture
self.size_hint = None, None
self.size = (15, 15)
with self.canvas:
self.rect = Rectangle(texture=texture, pos=self.pos, size=self.size)
self.bind(pos=self.update_ball, size=self.update_ball)
def update_ball(self, *args):
self.rect.pos = self.pos
self.rect.size = self.size
def move(self):
self.pos = Vector(*self.velocity) + self.pos
Builder.load_string('''
<SettingsScreen>:
texture
Property that represents the texture used for drawing this Instruction. You can set a new texture like this:
from kivy.core.image import Image texture = Image('logo.png').texture with self.canvas: Rectangle(texture=texture, pos=self.pos, size=self.size)
Usually, you will use the source attribute instead of the texture.
Есть два решения проблемы. Я только что обновил свой пост альтернативным решением, похожим на pygame, то есть манипулирование игровыми объектами.
Ваш код в основном работает. Довольно простое решение — просто изменить Ball
на расширение Image
(вместо Widget
) и добавить size_hint: None, None
.
Итак, объявление класса Ball
становится:
class Ball(Image):
Сам класс может остаться прежним
Правило для Ball
в вашем файле kv
упрощается до:
<Ball>:
source: '58-Breakout-Tiles.png'
И в вашем правиле GameScreen
раздел Ball
становится:
Ball:
id: ball
size_hint: None, None
center: self.parent.center
Просто добавьте size_hint
.
Я думаю, что этого достаточно, чтобы заставить его работать.
Кроме того, вы можете просто добавить size_hint
к Ball
как:
Ball:
id: ball
size_hint: None, None
center: self.parent.center
и измените pos: self.pos
на pos: root.pos
в вашем правиле <Ball>:
следующим образом:
<Ball>:
Image:
source: '58-Breakout-Tiles.png'
size: 15, 15
pos: root.pos
Основная проблема с исходным кодом заключается в том, что добавление Image
к Widget
— это просто добавление дочернего элемента к Ball
Widget
. Widget
, который не является Layout
, не обрабатывает свои дочерние элементы. Оригинальная Pong
игра обходит это, помещая изображение мяча в Canvas
Ball
Widget
. Класс Image
в основном делает это за вас.
ах, я вижу, в этом есть большой смысл - использование мяча в качестве объекта изображения, а не виджета, содержащего изображение, является более логичным подходом, если его необходимо постоянно обновлять. Спасибо!
из интереса, в чем преимущество создания прямоугольника для каждого объекта вместо прямого перемещения изображения? я делаю это в pygame для управления игровыми объектами - применимо ли такое же использование?