KIVY Tutorials » Pong Game Tutorial¶

1Pong Game Tutorial — Kivy 2.3.0 documentation

Introduction

Welcome to the Pong tutorial  欢迎来到  乒乓球 导师辅导课

This tutorial will teach you how to write pong using Kivy. We’ll start with a basic application like the one described in the Create an application and turn it into a playable pong game, describing each step along the way.
这导师辅导课将教你如何写一个乒乓球使用KIVY。 我们将开始同一个基础的应用程序 像在Create an apllication描述的一样,并且将它变为一个可以玩儿的乒乓球游戏, 描述每一步伴随着进程。

Here is a check list before starting this tutorial:
这是一个检查列表 在开始这导师辅导课之前:

  • You have a working Kivy installation. See the Installing Kivy section for detailed descriptions
    你有一个正在运行的kivy 安装程序。 细节描述请看 安装kivy 节 

  • You know how to run a basic Kivy application. See Create an application if you don’t.
    你知道如何运行一个基础的kivy程序。 看 创造一个应用 如果你不知道的。

If you have read the programming guide, and understand both basic Widget concepts (A Simple Paint App) and basic concepts of the kv language (Kv language), you can probably skip the first 2 steps and go straight to step 3.
如果你读了项目知道, 并且都明白 基础组件概念(一个简单地绘画APP) 和kv语言基础概念, 你大概可以跳过一开始2步 并且直接径直去到第三步。

Note    注意

You can find the entire source code–and source code files for each step–in the Kivy examples directory under tutorials/pong/.
你可以找到整个源码-- 并且 每一步的源码文件  --- 在kivy案例文件夹的tutorials/pong/

Ready? Sweet, let’s get started!
准备好了吗? 甜心,让我们开始吧!

Getting Started 开始

Getting Started   开始

Let’s start by getting a really simple Kivy app up and running. Create a directory for the game and a file named main.py

让我们开始以一个真正的简单的kivyapp 开始和运行。 为游戏创建一个文件夹和一个叫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()

Go ahead and run the application. It should just show a black window at this point. What we’ve done is create a very simple Kivy App, which creates an instance of our PongGame Widget class and returns it as the root element for the applications UI, which you should imagine at this point as a hierarchical tree of Widgets. Kivy places this widget-tree in the default Window. In the next step, we will draw the Pong background and scores by defining how the PongGame widget looks.

继续和运行app。 它应该只展示一个黑色的窗口在这时。 我们要做的时创造一个非常简单的KivyAPP, 这app创造了一个我们PongGame组件类的案例, 并且返回它作为应用程序UI的一个根类元素, 你应该想象到在这点作为一个分等级的组件树。Kivy 放置 这个 组件树在默认的窗口。 在下一步, 我们将绘画乒乓球背景和分数通过定义如何PongGame 组件外貌。

Add Simple Graphics 添加简单的图像

Creation of pong.kv     pong.kv 的创造

We will use a .kv file to define the look and feel of the PongGame class. Since our App class is called PongApp, we can simply create a file called pong.kv in the same directory that will be automatically loaded when the application is run. So create a new file called ``pong.kv`` and add the following contents.
我们将使用一个.kv文件 来定义 PongGame类的  景象 和 使用。 自从我们App类被叫作PongApp,我们可以简单地创造一个文件叫做 pong.kv 在相同的文件夹, 这文件夹将自动的被运行当应用程序运行时。 因此创造一个新的文件 叫 “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"

Note   注意

COMMON ERROR: The name of the kv file, e.g. pong.kv, must match the name of the app, e.g. PongApp (the part before the App ending).
常见错误: kv文件的名字, 例如  pong.kv , 必须匹配app的名字, 例如 PongApp(之前App结束部分有--上段代码)

If you run the app now, you should see a vertical bar in the middle, and two zeros where the player scores will be displayed.
如果你运行app现在, 你应该看见一个垂直的块在中间, 和俩零 在玩家分数将被展示的地方。

Explaining the Kv File Syntax¶  kv文件语法的说明

Before going on to the next step, you might want to take a closer look at the contents of the kv file we just created and figure out what is going on. If you understand what’s happening, you can probably skip ahead to the next step.
在继续下一步之前,你可能像近一点看看我们刚创造的kv文件的内容,并且弄明白正在干啥勒。如果你明白目前情况,你大概可以跳过继续到下一步。

On the very first line we have:  一开头的第一步我们有的:

#:kivy 1.0.9

This first line is required in every kv file. It should start with #:kivy followed by a space and the Kivy version it is intended for (so Kivy can make sure you have at least the required version, or handle backwards compatibility later on).
这第一条线是被需求的在每个kv文件里。 它应该始同 #:kivy 被跟随着空格 和 Kivy版本。 Kivy版本是蓄意的(因此 Kivy 可以确保你至少有被要求的版本,或者操控回溯到兼容的后续版本等。)

After that, we begin defining rules that are applied to all PongGame instances:
在那之后,我们开始定义所有PongGame案例应用的规则:

<PongGame>:
    ...

Like Python, kv files use indentation to define nested blocks. A block defined with a class name inside the < and > characters is a Widget rule. It will be applied to any instance of the named class. If you replaced PongGame with Widget in our example, all Widget instances would have the vertical line and the two Label widgets inside them because it would define these rules for all Widget instances.
像Python一样,kv文件使用缩进来定义嵌套块。 一个block区块定义一个在<>里的类名,这是Widget组件规则。 它将被应用在任何被命名类的案例。如果你更换我们案例中的PongGame和组件,所有的组件案例应该有这 竖直的线 和两个标签组件,因为它们定义这些规则为了所有的组件案例。

Inside a rule section, you can add various blocks to define the style and contents of the widgets they will be applied to. You can:
在一个规则节内, 你可以添加不同的块来定义将要被应用的组件们的样式和内容。你可以:

  • set property values    设置属性值

  • add child widgets    增加子类组件

  • define a canvas section in which you can add Graphics instructions that define how the widget is rendered.    定义一个画布节, 在这个画布中你可以增加定义如何组件渲染的图形用法说明。

The first block inside the <PongGame> rule we have is a canvas block:
在<PongGame>规则内的第一块我们所拥有的是画布块:

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

So this canvas block says that the PongGame widget should draw some graphics primitives. In this case, we add a rectangle to the canvas. We set the pos of the rectangle to be 5 pixels left of the horizontal center of the widget, and 0 for y. The size of the rectangle is set to 10 pixels in width, and the widget’s height in height. The nice thing about defining the graphics like this, is that the rendered rectangle will be automatically updated when the properties of any widgets used in the value expression change.
因此这个画布块说明 这PongGame 组件应该画一些图形基本体。 在这个案例中,我们增加一个长方形到画布中。 我们设置长方形的位置是 组件水平中心的左边5像素, 并且y坐标是0. 长方形坐标是被设定为10像素宽, 并且组件的高度是 整个高度。 像这样定义图形们的好事是, 渲染图形将自动更新当任何被使用的组件的属性们在表达值改变的时候。

Note   请注意:

Try to resize the application window and notice what happens. That’s right, the entire UI resizes automatically. The standard behaviour of the Window is to resize an element based on its property size_hint. The default widget size_hint is (1,1), meaning it will be stretched 100% in both x-direction and y-direction and hence fill the available space. Since the pos and size of the rectangle and center_x and top of the score labels were defined within the context of the PongGame class, these properties will automatically update when the corresponding widget properties change. Using the Kv language gives you automatic property binding. :)
尝试着重新定义应用窗口的大小, 并且注意将要发生什么。 是的,整个UI自动重新定义大小了。 窗口的表现标准是重新定义一个大小元素基于它的  size_hint  属性。 组件size_hint的默认值是(1,1), 意味着它将被拉伸100% 在x y两个方向, 并且因此之后,填满恰当的空白。 自从长方形的位置和大小 和center_x 以及 上部的分数标签在PongGame类的内容中都被定义好了以后, 这些属性们将自动地更新到组件属性们相一致的时候。 使用kv一眼给你自动地属性绑定。

The last two sections we add look pretty similar. Each of them adds a Label widget as a child widget to the PongGame widget. For now, the text on both of them is just set to “0”. We’ll hook that up to the actual score once we have the logic implemented, but the labels already look good since we set a bigger font_size, and positioned them relatively to the root widget.
上两节我们增加了看起来相同的美丽画面。 他们的每个增加了一个标签组件作为一个子类组件到PongGame组件中。 现在开始, 它们俩的文本 都被 设置为0. 我们将其连接到真正的分数, 一旦当我们有逻辑的执行时, 但是标签们已经看起来不错自从我i们设定了一个更大的font_size, 并且相对放置在根组件上了。 [已经很好了,你还想干嘛呀?]

The root keyword can be used inside the child block to refer back to the parent/root widget the rule applies to (PongGame in this case):
root 键 可以被使用在子块内来回溯到 规则应用到的 父类/根类  组件。

<PongGame>:
    # ...

    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"

Add the Ball¶   添加球

Add the Ball

Ok, so we have a basic pong arena to play in, but we still need the players and a ball to hit around. Let’s start with the ball. We’ll add a new PongBall class to create a widget that will be our ball and make it bounce around.
好了,现在我们有一个基础的区域来玩乒乓球了,但我们仍需要玩家们和一个球来围绕着击打。 让我们从球开始。 我们将添加一个新的乒乓球类 来创建一个将是我们的球的组件,并且让这球绕着跳。

PongBall Class

Here is the Python code for the PongBall class:
这儿是PongBall类的Python代码

class PongBall(Widget):

    # velocity of the ball on x and y axis
    # 球在x 和y  轴的 速率
    velocity_x = NumericProperty(0)
    velocity_y = NumericProperty(0)

    # referencelist property so we can use ball.velocity as
    # 参考表属性 因此我们可以使用球。  速率作为一个速记的, 像  位置, x  y。。。
    # 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
    # '''move''' 功能将移动球一步, 着将被召唤有间隔的平等的来动画这球
    def move(self):
        self.pos = Vector(*self.velocity) + self.pos

And here is the kv rule used to draw the ball as a white circle:
并且这里是kv规则 被用来画球  作为一个白色的圆圈。

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

To make it all work, you also have to add the imports for the Properties Property classes used and the Vector.
为了让它都运行,你也不得不添加  imports 来导入Properties 属性类们 , 和Vector

Here is the entire updated python code and kv file for this step:

这是这步整个更新的python代码和kv文件

main.py:

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

Note that not only a <PongBall> widget rule has been added, but also a child widget PongBall in the <PongGame> widget rule.
注意,不仅仅是一个 <PongBall>组件规则已经被添加, 还有一个子类 PongBall在<PongGame>组件规则里。

这段代码定义了一个名为PongBall的类,它继承自Kivy库中的Widget类。PongBall用于表示游戏中的乒乓球,主要关注的是球的移动行为。

  1. NumericProperty(0)NumericProperty(0):这是两个数值属性,velocity_xvelocity_y分别代表球在水平和垂直方向上的速度。NumericProperty是一个Kivy的属性类型,用于存储数值值。

  2. ReferenceListProperty(velocity_x, velocity_y):这是创建了一个复合属性velocity,它结合了velocity_xvelocity_y的值,提供了一种简化的方式来访问和设置这两个方向的速度。

  3. move() 方法:这个方法用于更新球的位置。它计算新的位置(self.pos)通过将当前速度向量(Vector(*self.velocity))加到球的当前位置上。

代码中 Vector() 的详细作用 以及 *的的作用

在Kivy中,Vector 是一个用于表示二维向量的类,它提供了一些处理向量运算的方法,如加法、减法和点积等。在这个 PongBall 类中,Vector(*self.velocity) 的作用是:

  1. *: 这是一个解包操作符,它将 self.velocity(一个包含 velocity_xvelocity_y 的元组)中的元素分开,分别传递给 Vector 类的构造函数,形成一个 (velocity_x, velocity_y) 的二维向量。

  2. self.pos = Vector(*self.velocity) + self.pos: 这行代码的作用是在球的位置 self.pos 上进行平移。首先,计算球的当前速度(self.velocity)在当前位置的累积效果,然后将这个累积的位移添加到球的现有位置上。这样每调用一次 move() 方法,球就会按照其当前的速度沿x和y轴移动一步。

from kivy.vector import Vector 中的Vector 是干啥的

在 Kivy 框架中,Vector 类是用于表示和操作二维或三维向量的。Vector 类提供了许多有用的方法来处理向量,如加法、减法、点积、叉积(仅限三维)和归一化等。

在图形编程和物理模拟中,向量是非常常见的数学工具。它们可以表示位置、方向、速度、加速度等。

以下是一些 Vector 类中常见的方法和它们的作用:

1    加法:两个向量相加会产生一个新的向量,其各个分量分别是两个向量对应分量的和。

v1 = Vector((1, 2))  
v2 = Vector((3, 4))  
result = v1 + v2  # result is (4, 6)

2        减法:一个向量减去另一个向量会产生一个新的向量,其各个分量分别是两个向量对应分量的差。

v1 = Vector((1, 2))  
v2 = Vector((3, 4))  
result = v1 - v2  # result is (-2, -2)

3        点积:两个向量的点积是一个标量值,等于两个向量对应分量乘积的和。

v1 = Vector((1, 2))  
v2 = Vector((3, 4))  
dot_product = v1.dot(v2)  # dot_product is 1*3 + 2*4 = 11

4        叉积(仅限三维):两个三维向量的叉积会产生一个新的三维向量,这个向量垂直于原来的两个向量。

5        长度:向量的长度(或称为模)是一个标量值,表示向量从原点到其末端的距离。

6        归一化:将一个向量转化为单位向量(长度为1的向量),同时保持其方向不变。

v1 = Vector((3, 4))  
normalized = v1.normalize()  # normalized is (3/5, 4/5)

在 Kivy 中,Vector 类通常用于处理与图形界面相关的位置和大小信息,以及动画和物理模拟中的速度和加速度等。

Kivy的Vector类主要用于什么? 为什么在计算球的移动时要使用解包操作符*? 在Kivy中,如何用Vector类来实现对象的位移更新?

Kivy中的Vector类主要用于表示二维或三维空间中的向量,它是一个轻量级的数学类,通常用于处理游戏开发中的图形变换、动画和物理模拟等场景。Vector类提供了诸如加法、减法、点积、叉积、长度、方向等向量操作方法,方便进行位置、速度、旋转等的计算。

在计算球的移动时,使用解包操作符*是因为它可以在数学运算中将两个向量相乘,这在物理学中代表的是平移(位置变化)或速度(速率变化)。例如,如果你有一个表示球的速度矢量和时间步长,speed * dt就可以计算出球在这段时间内的位移。

在Kivy中,你可以这样使用Vector类来实现对象的位移更新:

# 假设你有一个球对象,其初始位置是 Vector(0, 0)
ball_position = Vector(x=0, y=0)

# 每一帧的位移更新
dt = Clock.tick() / 60  # 获取时间差(单位秒)
ball_speed = Vector(x=1, y=1)  # 球的移动速度

# 更新位移
new_position = ball_position + ball_speed * dt

# 设置球的新位置
ball_position = new_position

这里,ball_position += ball_speed * dt就是使用了加法操作,将速度向量按照时间步长dt应用到当前位置上,实现了位移的更新。

python * 解包案例学习

在Python中,*(星号)在解包(unpacking)参数时非常有用,特别是在函数调用、函数定义、以及序列(如列表、元组)的解包中。下面是一些详细的案例,帮助你学习*解包。

1. 函数调用中的解包

当你有一个列表或元组,并且你想将这些元素作为位置参数传递给一个函数时,你可以使用*来解包这些元素。

def greet(first_name, last_name):  
    print(f"Hello, {first_name} {last_name}!")  
  
name_parts = ["Alice", "Smith"]  
greet(*name_parts)  # 输出:Hello, Alice Smith!
2. 函数定义中的*args

在函数定义中,*args允许你接受任意数量的位置参数,并将它们作为一个元组传递给函数。

def print_args(*args):  
    for arg in args:  
        print(arg)  
  
print_args(1, 2, 3, 4)  # 输出:1 2 3 4
3. 函数定义中的**kwargs

在函数定义中,**kwargs允许你接受任意数量的关键字参数,并将它们作为一个字典传递给函数。

def greet_person(**kwargs):  
    for key, value in kwargs.items():  
        print(f"{key}: {value}")  
  
greet_person(name="Alice", age=30)  # 输出:name: Alice age: 30
4. 序列解包

你可以使用*来将序列(如列表或元组)的元素解包到单独的变量中,但这通常与已知数量的变量一起使用。

coordinates = (10, 20)  
x, y = coordinates  # 直接解包到两个变量  
  
# 如果有更多元素,并想解包到单独的变量和列表中  
coordinates_ext = (10, 20, 30, 40)  
x, y, *z = coordinates_ext  # x=10, y=20, z=[30, 40]  
print(x, y, z)  # 输出:10 20 [30, 40]
5. 列表推导式中的解包

虽然列表推导式本身不直接使用*来解包,但你可以结合其他操作来模拟解包。

# 假设你有一个二维列表,并想将其转换为一维列表  
two_d_list = [[1, 2], [3, 4], [5, 6]]  
one_d_list = [item for sublist in two_d_list for item in sublist]  
print(one_d_list)  # 输出:[1, 2, 3, 4, 5, 6]  
  
# 这虽然不是直接的解包,但展示了如何遍历嵌套列表的元素
6. 与*args**kwargs结合使用

你可以在同一函数定义中同时使用*args**kwargs

def complex_function(pos1, pos2, *args, kw1=None, **kwargs):  
    print(f"pos1: {pos1}, pos2: {pos2}, args: {args}, kw1: {kw1}, kwargs: {kwargs}")  
  
complex_function(1, 2, 3, 4, 5, kw1="value", kw2="another_value", kw3=10)  
# 输出:pos1: 1, pos2: 2, args: (3, 4, 5), kw1: value, kwargs: {'kw2': 'another_value', 'kw3': 10}

这些案例应该能帮助你更好地理解Python中*解包的概念和用法。

Adding Ball Animation¶  增加球动画

Making the ball move 让球运动

Cool, so now we have a ball, and it even has a move function… but it’s not moving yet. Let’s fix that.
太帅了, 从现在开始我们有一个球儿了, 并且一甚至有了一个移动功能, 但是虽然它还没动。 让我们修理好它!

Scheduling Functions on the Clock  钟表的规划功能

We need the move method of our ball to be called regularly. Luckily, Kivy makes this pretty easy by letting us schedule any function we want using the Clock and specifying the interval:
我们需要我们球的move 方法来常规被召唤。 幸运的是,Kivy 让着非常简单通过让我们规划任何功能 我们想使用时钟Clock 和 定义 时间间隔。

Clock.schedule_interval(game.update, 1.0/60.0)

通过Clock.schedule_interval()方法 可以指定每隔多少秒执行一次对应的函数。
上面的代码为每1/60秒执行一次game.update()方法,如果要不间断地执行,只需要把1/60设置成0即可。

This line for example, would cause the update function of the game object to be called once every 60th of a second (60 times per second).
这线为例, 可以造成update 功能-----即 游戏对象被召唤每1/60秒。

Object Properties/References¶  对象属性/参考

We have another problem though. We’d like to make sure the PongBall has its move function called regularly, but in our code we don’t have any references to the ball object since we just added it via the kv file inside the kv rule for the PongGame class. The only reference to our game is the one we return in the applications build method.
尽管我们有另一个问题。  我们像确保 乒乓球有它 移动功能 常规被召唤, 但是在我们的代码中还没有任何文献给球对象自从我们通过kv文件里的PongGame 类的kv规则创建球儿以后。唯一的文献给我们的游戏只有一个我们返回了build 方法。

Since we’re going to have to do more than just move the ball (e.g. bounce it off the walls and later the players racket), we’ll probably need an update method for our PongGame class anyway. Furthermore, given that we have a reference to the game object already, we can easily schedule its new update method when the application gets built:
自从我们打算不得不做更多的事儿,而不是只移动球儿(例如: 在玩家球拍儿后,从墙弹开球),无论如何,我们大概需要一个update方法给我们的PongGame类。

更多的是, 我们已经有一个参考给游戏对象, 我们可以简单地规划它新的update方法当应用被建立时:

class PongGame(Widget):

    def update(self, dt):
        # call ball.move and other stuff
        pass

class PongApp(App):

    def build(self):
        game = PongGame()
        Clock.schedule_interval(game.update, 1.0/60.0)
        return game

However, that still doesn’t change the fact that we don’t have a reference to the PongBall child widget created by the kv rule. To fix this, we can add an ObjectProperty to the PongGame class, and hook it up to the widget created in the kv rule. Once that’s done, we can easily reference the ball property inside the update method and even make it bounce off the edges:
然而, 那仍然不改变我们没有一个给与PongBall由kv规则创造的子类组件的事实。 为了修理这块儿,我们可以添加一个对象属性ObejectProperty 给PongGame 类, 并且把它连接到在kv规则里创造的组件。 一旦连接完成,我们可以简单地在update方法内引用球属性, 并且使它从边缘反弹开。

class PongGame(Widget):
    ball = ObjectProperty(None)

    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

Don’t forget to hook it up in the kv file, by giving the child widget an id and setting the PongGame’s ball ObjectProperty to that id:
别忘了把它和kv文件相连接,通过给与子类组件一个id 和设置PongGame一个球 对象属性到那个id上:

<PongGame>:
    ball: pong_ball

    # ... (canvas and Labels)

    PongBall:
        id: pong_ball
        center: self.parent.center

Note    注意:

At this point everything is hooked up for the ball to bounce around. If you’re coding along as we go, you might be wondering why the ball isn’t moving anywhere. The ball’s velocity is set to 0 on both x and y. In the code listing below, a serve_ball method is added to the PongGame class and called in the app’s build method. It sets a random x and y velocity for the ball, and also resets the position, so we can use it later to reset the ball when a player has scored a point.
在这点任何事物是被关联到这球来四周反弹。 如果你跟着我们一直写代码,你可以很好奇为什么这球儿没任何地方移动。 这球的速度被设置为0 在x 和y上。 在下面的代码列表中, 一个serve_ball方法被添加给PongGame类,并且被使唤在app的build 方法里。 它设定了一个随机的x 和y 速度给球, 并且也重新设置了位置, 因此当一个玩家有得分时,我们等会可以使用它来重新设置球。

Here is the entire code for this step:
这是这一步的整个代码。

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
        #这行代码将乒乓球(PongBall)的中心点设置为整个游戏界面(PongGame)
        #的中心点。这样,当游戏开始时,乒乓球就会出现在屏幕的中央。
        self.ball.velocity = Vector(4, 0).rotate(randint(0, 360))
        #这行代码首先创建了一个向量(Vector),其初始方向是向右(因为x分量为4,y分量为0)。
        #然后,它使用 rotate 方法随机旋转这个向量,得到一个随机的初始速度。这个速度被赋给
        #乒乓球(PongBall)的 velocity 属性,从而决定了乒乓球在游戏开始时的移动方向和速度。
    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()
        #这行代码创建了一个 PongGame 类的实例,并将其赋值给变量 game。
        #PongGame 类是代表整个游戏界面的类,它可能包含了一些游戏元素
        #(在这个例子中,至少有一个 PongBall 对象),以及处理游戏逻辑的方法。
        game.serve_ball()
        #这行代码调用了 PongGame 类的 serve_ball 方法。这个方法的作用是
        #初始化乒乓球(PongBall)的位置和速度。具体来说:
        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

简单一问:self.ball.center = self.center 中的self 为什么是整个游戏界面 而不是这个函数?

在 Kivy 的上下文中,self 关键字引用的是当前类的实例。当你看到 self.ball.center = self.center 这样的代码时,self 指的是调用这个方法的对象实例,也就是 PongGame 类的实例。

在这个特定的例子中,PongGame 类是一个代表整个游戏界面的 Widget,它内部可能包含了一个或多个其他的 Widget(比如 PongBall)。在 PongGame 类的 serve_ball 方法中,self.ball 引用的是 PongGame 实例内部的 ball 属性,这个属性通常是一个 PongBall 类的实例。

而 self.center 在 PongGame 的上下文中引用的是 PongGame 实例的中心点,也就是整个游戏界面的中心点。这是因为 Widget 类(PongGame 的基类)提供了 center 属性它返回的是 Widget 相对于其父布局的位置(通常是 x 和 y 坐标的中点)。

所以,self.ball.center = self.center 这行代码的意思是将 PongBall 实例(self.ball)的中心点设置为整个游戏界面(self,即 PongGame 实例)的中心点。这并不是说 self 指的是 PongBall,而是说在 PongGame 类的上下文中,self 指的是 PongGame 的实例,而 self.ball 才是对 PongBall 实例的引用。

总的来说,这是一个子函数,而不是一个子组件。如果这是一个子组件,应该self就发生改变了,是这样吗?

不完全是这样的。在 Kivy 中,Widget 类及其子类(如 PongGame 和 PongBall)都可以被视为组件,但通常我们使用“子组件”这个术语来描述在布局层次结构中嵌套在另一个组件内部的组件。

在面向对象编程中,self 总是引用当前类的实例,不论这个实例是作为一个独立的对象存在,还是作为另一个对象的一部分(即“子组件”)。因此,在 PongGame 类的 serve_ball 方法中,self 总是引用 PongGame 的实例,而不是 PongBall 的实例。

在 Kivy 的布局和组件体系中,PongGame 可能是一个包含 PongBall 的容器或布局。但是,这并不意味着 self 在 PongGame 的方法中就变成了 PongBall 的实例。相反,PongGame 的方法可以通过其属性(如 self.ball)来访问和操作 PongBall 实例。

简单来说:

  • self 总是引用当前类的实例。
  • PongGame 是一个组件(或容器),它可能包含 PongBall 作为其子组件。
  • 在 PongGame 的方法中,self 引用 PongGame 的实例,而 self.ball 引用 PongBall 的实例(如果 PongBall 是 PongGame 的一个属性)。
  • “子组件”是一个布局层次结构中的概念,用于描述一个组件如何嵌套在另一个组件内部。但在面向对象编程的上下文中,self 并不因为组件的嵌套关系而改变其含义。
继续简单一问: 
kv代码中<PongBall> 是什么?   后面出现的PongBall又是什么?

这是一个使用Kivy框架编写的Pong游戏的部分代码片段。Kivy是一个用于开发多触摸应用程序的Python库。让我们逐步分析这段代码:

  1. PongBall 类:

    • 这是一个自定义的类(从代码中我们不能直接看到类的定义,但可以推测)。
    • 它有两个主要的属性:size 和 canvas
    • size 设置为 (50, 50),这意味着这个PongBall对象的大小是50x50像素。
    • canvas 描述了如何绘制这个对象。在这里,它使用了一个Ellipse(椭圆)来绘制。这个椭圆的位置(pos)和大小(size)都引用自self(即PongBall对象自身)。
  2. PongGame 类:

    • 这也是一个自定义的类(从代码中我们不能直接看到类的定义,但可以推测)。
    • 它有几个主要的属性和一个子部件(PongBall)。
    • ball: pong_ball 是一个类属性,它引用了一个PongBall实例(其ID为pong_ball)。
    • canvas 描述了如何绘制PongGame的背景或主要内容。在这里,它使用了一个Rectangle(矩形)来绘制。这个矩形从屏幕的中心线稍微偏移,并贯穿整个屏幕的高度。
    • 接下来是两个Label部件,它们被用作分数显示。第一个标签位于屏幕的左四分之一处,第二个位于屏幕的右四分之一处。它们的文本都被设置为"0",表示开始时两队的分数都是0。
    • PongBall: id: pong_ball 定义了PongGame的一个子部件,它是一个PongBall对象。这个PongBall对象的中心(center)被设置为PongGame的中心。

综上所示

.kv文件主要是用来描述界面布局和样式的, kv文件里<PongBall> 定义了该类的样式
PongBall则是实例化该类得以呈现。

上段代码中 ball=ObjectProperty(None)是什么意思?

在Kivy框架中,ObjectProperty 是一种特殊的属性,用于在Kivy的.kv文件与Python代码之间建立连接。当您在Kivy的Python代码中定义一个部件(如Widget或其子类),并且您想在.kv文件中引用该部件的某个子部件或属性时,您可以使用ObjectProperty

在您提供的代码中,PongGame 类有一个ball属性,它被定义为ObjectProperty(None)。这表示ball属性是一个可以指向另一个Kivy部件(如PongBall实例)的引用。初始时,这个引用被设置为None,但您可以在.kv文件中或Python代码中设置它。

例如,在.kv文件中,您可以这样设置ball属性:

<PongGame>:  
    ball: pong_ball  # 这里假设 PongBall 的实例在 PongGame 中被命名为 pong_ball  
    PongBall:  
        id: pong_ball  
        # 其他 PongBall 的属性和设置

在上面的.kv文件中,我们使用了:语法来声明PongGame类的规则,并通过ball: pong_ball来声明ball属性将引用具有id: pong_ballPongBall实例。

在Python代码中,您可以直接设置ball属性:

class PongGame(Widget):  
    ball = ObjectProperty(None)  
  
    def __init__(self, **kwargs):  
        super(PongGame, self).__init__(**kwargs)  
        self.ball = PongBall()  # 创建一个 PongBall 实例并将其赋值给 ball 属性  
        self.add_widget(self.ball)  # 将 PongBall 添加到 PongGame 的子部件列表中  
        # 其他初始化代码...

通过使用ObjectProperty,当ball属性在Python代码中发生变化时,Kivy的绑定机制会确保任何在.kv文件中依赖于这个属性的规则都会得到更新。同样地,如果在.kv文件中通过id修改了引用的部件,Python代码中的相应属性也会得到更新(尽管在大多数情况下,您不会在.kv文件中直接修改通过ObjectProperty设置的属性)。

总之,ObjectProperty(None)在这里是用来声明一个可以在.kv文件Python代码之间共享的引用,以便能够方便地在不同部分之间传递和引用Kivy部件。

kivy中 ObjectProperty

在Kivy中,ObjectProperty是一个特殊的属性,用于在Kivy的.kv文件和Python代码之间建立连接。ObjectProperty允许您在Kivy的.kv模板文件中引用Python代码中定义的属性,同时也允许在Python代码中引用在.kv文件中定义的部件。

ObjectProperty是Kivy属性系统的一部分,它提供了自动绑定和更新机制,使得当属性发生变化时,相关的界面元素能够自动更新。

以下是ObjectProperty在Kivy中的常见用法:

1        在Python代码中定义属性

在Python代码中,您可以使用ObjectProperty来定义一个属性,该属性将引用另一个Kivy部件。例如:

        

from kivy.properties import ObjectProperty  
from kivy.uix.boxlayout import BoxLayout  
  
class MyWidget(BoxLayout):  
    my_button = ObjectProperty(None)  
  
    def __init__(self, **kwargs):  
        super(MyWidget, self).__init__(**kwargs)  
        # 可以在这里创建并设置my_button属性  
        # self.my_button = Button(text='Click Me')  
        # self.add_widget(self.my_button)

2        在.kv文件中引用属性

.kv文件中,您可以使用id来引用部件,并通过Python代码中的ObjectProperty来访问它。例如:

<MyWidget>:  
    BoxLayout:  
        Button:  
            id: my_button  
            text: 'Click Me'  
            on_release: root.button_released()  
  
# 在Python代码中,您可以通过self.my_button来访问这个Button

但是,请注意,在.kv文件中定义的id并不会自动绑定到Python代码中的ObjectProperty。如果您想在Python代码中访问这个id,您通常需要在Python代码中创建一个方法来处理这个部件,或者通过其他方式(如事件绑定)来访问它。

然而,Kivy提供了一个Builder机制,它允许您在.kv文件中定义整个界面,并通过Python代码加载它。在这种情况下,.kv文件中定义的部件可以通过ObjectProperty(或其他Kivy属性)在Python代码中引用。但这不是通过id直接绑定的,而是通过.kv文件的加载和解析过程来建立的。

3        自动绑定和更新

ObjectProperty引用的部件发生变化时(例如,被重新赋值或从父部件中移除),Kivy的属性系统会自动更新所有依赖于这个属性的地方。这包括在.kv文件中使用这个属性的任何规则,以及在Python代码中直接引用这个属性的任何地方。

总之,ObjectProperty是Kivy中一个强大的工具,它允许您在.kv文件和Python代码之间建立连接,从而实现更灵活、更易于维护的用户界面。

Vector
self.ball.velocity = Vector(4, 0).rotate(randint(0, 360))

个人总结:  Vector(4,0)就是 一个从(0.,0)到(4,0)方向上的向量:

Vector — Kivy 2.3.0 documentation

代码中 -1 是什么方向

在这段代码中,self.ball.velocity_x *= -1 是一种常见的方式来反转一个物体(在这个例子中是“球”)在 x 轴方向上的速度。-1 并不直接代表一个方向,而是用于改变速度的符号,从而实现方向的反转。

假设self.ball.velocity_x原先是一个正数(比如 4),那么乘以 -1 后,它变成了负数(比如 -4)。这意味着球原先是向右移动的(因为速度是正的),但现在它会向左移动(因为速度是负的)。

相反,如果self.ball.velocity_x原先是一个负数(比如 -4),乘以 -1 后,它会变成正数(比如 4)。这意味着球原先是向左移动的(因为速度是负的),但现在它会向右移动(因为速度是正的)。

简而言之,-1 在这里的作用是反转速度的方向,而不是直接指定一个固定的方向。这是二维或三维物理模拟中常用的技巧,用于处理碰撞、反弹等场景。

Connect Input Events¶  连接输入事件

Adding Players and reacting to touch input  增加玩家和输入的触摸反应

Sweet, our ball is bouncing around. The only things missing now are the movable player rackets and keeping track of the score. We won’t go over all the details of creating the class and kv rules again, since those concepts were already covered in the previous steps. Instead, let’s focus on how to move the Player widgets in response to user input. You can get the whole code and kv rules for the PongPaddle class at the end of this section.
甜心,我们的球正在四处弹跳。现在唯一欠缺的事是移动的玩家轨迹,和保持分数的轨迹。 我们不想再检查所有的创造类的细节和kv规则的细节, 自从这些概念都已经被先前的步骤覆盖。 像替代的,让我们专注如何移动玩家的组件来反映我们的输入。 再这节的末尾你可以获取整个pongPaddle 类的代码 和kv规则。

In Kivy, a widget can react to input by implementing the on_touch_down, the on_touch_move and the on_touch_up methods. By default, the Widget class implements these methods by just calling the corresponding method on all its child widgets to pass on the event until one of the children returns True.
在Kivy中, 一个组件可以对输入执行on_touch_down, on_touch_move, on_touch_up方法作出反应。默认的, 组件类执行这些方法通过召唤相关的方法在所有它的子类组件上来传递事件知道 子类返回True。

Pong is pretty simple. The rackets just need to move up and down. In fact it’s so simple, we don’t even really need to have the player widgets handle the events themselves. We’ll just implement the on_touch_move function for the PongGame class and have it set the position of the left or right player based on whether the touch occurred on the left or right side of the screen.

乒乓球非常简单。 踪迹只需要移动上下。事实上,它就是这么简单,我们甚至不需要真正的拥有玩家组件操控事件他们自己。  我们只需要执行 on_touch_move 功能给PongGame类 并且让它设置左右玩家,  基于是否触摸显露在左边或者右边屏幕。

Check the on_touch_move handler: 检查on_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

We’ll keep the score for each player in a NumericProperty. The score labels of the PongGame are kept updated by changing the NumericProperty score, which in turn updates the PongGame child labels text property. This binding occurs because Kivy properties automatically bind to any references in their corresponding kv files. When the ball escapes out of the sides, we’ll update the score and serve the ball again by changing the update method in the PongGame class. The PongPaddle class also implements a bounce_ball method, so that the ball bounces differently based on where it hits the racket. Here is the code for the PongPaddle class:
在一个NumericProperty中,我们将保持分数给每位玩家。PongGame分数表保持更新伴随着NumericProperty分数的变动, 这分数相应的更新PongGame 子类标签的text 属性。 这关系显露因为Kivy属性自动地绑定给任何相关的kv文件的任何文献。当球儿从边上逃离了, 我们将更新分数并且通过改变PongGame类中的update方法再次发球。

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)

Note

This algorithm for ball bouncing is very simple, but will have strange behavior if the ball hits the paddle from the side or bottom…this is something you could try to fix yourself if you like.
这个算法让球弹跳非常简单,但是由奇异的行为如果球碰到了球拍从边或者底部。。。 这是啥事你可以试试来修理它如果你想的话。

And here it is in context. Pretty much done: 并且这是在内容里的。 几乎完成:

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)
            #这行代码计算一个偏移量。它基于球的中心和挡板的中心的y坐标差异,然后除以挡板高度的一                            
            #半。这个偏移量可能会影响球在y方向上的速度,使得球在击中挡板的不同位置时会有不同
            #的反弹角度。
            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()

        # bounce off paddles
        self.player1.bounce_ball(self.ball)
        self.player2.bounce_ball(self.ball)

        # bounce ball off bottom or top
        if (self.ball.y < self.y) or (self.ball.top > self.top):
            self.ball.velocity_y *= -1

        # went off 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.right > 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

Where To Go Now?¶  现在还要干啥?

Have some fun 享受其中的乐趣

Well, the pong game is pretty much complete. If you understood all of the things that are covered in this tutorial, give yourself a pat on the back and think about how you could improve the game. Here are a few ideas of things you could do:
好了, 乒乓球游戏已经非常完整了。 如果你明白这节导师辅导课涵盖的所有的事情了, 给你自己个背部一个安慰的轻拍并且想想如果你能够提升这个游戏。

这儿有一些主意你可以做的。

  • Add some nicer graphics / images. (Hint: check out the source property on the graphics instructions like circle or Rectangle, to set an image as the texture.)
    增加一些更好点的图形 / 图片(线索: 检查像 circle 圆圈/ Rectangle矩形图形构造里的source 属性, 来设置一个图片作为文本)

  • Make the game end after a certain score. Maybe once a player has 10 points, you can display a large “PLAYER 1 WINS” label and/or add a main menu to start, pause and reset the game. (Hint: check out the Button and Label classes, and figure out how to use their add_widget and remove_widget functions to add or remove widgets dynamically.)
    让这个游戏在一定明确的分数后结束。 也许一旦一个玩家有了10分, 你可以展示一个巨大的标签“PLAYER 1 WINS”标签 并且/或者 增加一个menu菜单来start, pause 和重新开始游戏。(线索: 检查Button  Label类,弄明白如何使用它们add_widget 和remove_widget功能来增加或者动态地移除组件)

  • Make it a 4 player Pong Game. Most tablets have Multi-Touch support, so wouldn’t it be cool to have a player on each side and have four people play at the same time?
    制作一个4名玩家的乒乓球游戏。 更多的桌子有多个触摸支持,因此有一个玩家在每个边上并不太酷, 并且有4个玩家同时玩这个?

  • Fix the simplistic collision check so hitting the ball with an end of the paddle results in a more realistic bounce.
    修理 过分简化的碰触, 因此用球拍的一端击球会产生更真实的反弹。

Note

You can find the entire source code–and source code files for each step–in the Kivy examples directory under tutorials/pong/.
你可以找到整个的源码--和源码文件给每一步的, 在Kivy 案例文件夹的tutorials/pong下面

  • 30
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xinzheng新政

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值