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() 只要有 possize 变化。

片段 - 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。不是 LayoutWidget 不处理绘制其子项。最初的 Pong 游戏通过将球图像放在 Ball WidgetCanvas 中来解决这个问题。 Image class 基本上可以为您做到这一点。