Kivy: Object motion/updating 问题(Kivy Pong教程改编)
Kivy: Object motion/updating issue (Kivy Pong tutorial adaptation)
我改编了 kivy pong 教程 (https://kivy.org/doc/stable/tutorials/pong.html) 的一部分来创建一个每秒更新 60 次的球 class,球在屏幕上移动。同样,当球击中侧面时,应该向相反的方向反射。然而球只是坐在屏幕的一角一动不动。我犯的 syntax/logic 错误是什么?
这是我的代码:
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
问题有两种解决方法。
方法 1 - kv 文件
- 删除
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
Kivy Canvas » source
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
方法 2 - kv 和 py 文件
- 将球定义从 kv 文件移动到 Python 脚本中
- 创建球图像的纹理
- 向 canvas
声明一个包含球纹理的矩形
- 绑定矩形,
self.rect
到一个方法,update_ball()
只要有 pos
或 size
变化。
片段 - py
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
片段 - kv
Builder.load_string('''
<SettingsScreen>:
Kivy Canvas » texture
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.
您的代码大部分工作正常。一个相当简单的解决方法是将 Ball
更改为扩展 Image
(而不是 Widget
),然后添加 size_hint: None, None
.
因此,Ball
class 声明变为:
class Ball(Image):
class本身可以保持不变
kv
文件中 Ball
的规则简化为:
<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
并在 <Ball>:
规则中将 pos: self.pos
更改为 pos: root.pos
为:
<Ball>:
Image:
source: '58-Breakout-Tiles.png'
size: 15, 15
pos: root.pos
您的原始代码的主要问题是,将 Image
添加到 Widget
只是将子项添加到 Ball
Widget
。不是 Layout
的 Widget
不处理绘制其子项。最初的 Pong
游戏通过将球图像放在 Ball
Widget
的 Canvas
中来解决这个问题。 Image
class 基本上可以为您做到这一点。
我改编了 kivy pong 教程 (https://kivy.org/doc/stable/tutorials/pong.html) 的一部分来创建一个每秒更新 60 次的球 class,球在屏幕上移动。同样,当球击中侧面时,应该向相反的方向反射。然而球只是坐在屏幕的一角一动不动。我犯的 syntax/logic 错误是什么?
这是我的代码:
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
问题有两种解决方法。
方法 1 - kv 文件
- 删除
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
Kivy Canvas » source
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
方法 2 - kv 和 py 文件
- 将球定义从 kv 文件移动到 Python 脚本中
- 创建球图像的纹理
- 向 canvas 声明一个包含球纹理的矩形
- 绑定矩形,
self.rect
到一个方法,update_ball()
只要有pos
或size
变化。
片段 - py
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
片段 - kv
Builder.load_string('''
<SettingsScreen>:
Kivy Canvas » texture
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.
您的代码大部分工作正常。一个相当简单的解决方法是将 Ball
更改为扩展 Image
(而不是 Widget
),然后添加 size_hint: None, None
.
因此,Ball
class 声明变为:
class Ball(Image):
class本身可以保持不变
kv
文件中 Ball
的规则简化为:
<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
并在 <Ball>:
规则中将 pos: self.pos
更改为 pos: root.pos
为:
<Ball>:
Image:
source: '58-Breakout-Tiles.png'
size: 15, 15
pos: root.pos
您的原始代码的主要问题是,将 Image
添加到 Widget
只是将子项添加到 Ball
Widget
。不是 Layout
的 Widget
不处理绘制其子项。最初的 Pong
游戏通过将球图像放在 Ball
Widget
的 Canvas
中来解决这个问题。 Image
class 基本上可以为您做到这一点。