背景
这篇文章将学到:
1,root和self
2,画布的创建
3,CoCk对象对函数的延时
介绍
欢迎来到乒乓球教程
本教程将教您如何使用 Kivy 编写 pong。我们将从 一个基本应用程序,如创建应用程序并转动中所述的应用程序 它变成了一个可玩的乒乓球游戏,描述了一路上的每一步。
以下是开始本教程之前的检查清单:
-
您有一个有效的 Kivy 安装。
-
您知道如何运行基本的 Kivy 应用程序。
准备?好了吗,让我们开始吧!
开始
让我们从启动并运行一个非常简单的 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 , 它创建了 Widget 类的实例并将其返回为 应用程序 UI 的根元素,您应该在此想象 点作为 Widgets 的分层树。Kivy 将这个小部件树放在 默认窗口。在下一步中,我们将绘制 乒乓球的背景和分数通过定义外观。PongGame
PongGame widget
添加简单的图形
创建 pong.kv
我们将使用 .kv 文件来定义类的外观。 由于我们的类被称为 ,我们可以简单地创建一个文件 在将自动加载的同一目录中调用 当应用程序运行时。因此,创建一个名为“pong.kv”的新文件并添加 以下内容。PongGamePongApppong.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 文件的名称(例如 pong.kv)必须与应用程序的名称匹配, 例如 PongApp(应用程序结束之前的部分)。
如果您现在运行该应用程序,您应该会在中间看到一个垂直条,以及两个 显示玩家分数的零。
解释 Kv 文件语法
在继续下一步之前,您可能需要仔细查看 我们刚刚创建的 KV 文件的内容,并弄清楚发生了什么。 如果你了解发生了什么,你可能会跳到下一个 步。
在第一行,我们有:
#:kivy 1.0.9
每个 kv 文件中都需要第一行。它应该以开头,后跟一个空格和它所针对的 Kivy 版本(以便 Kivy 可以制作 确保您至少具有所需的版本,或处理向后兼容性 稍后)。#:kivy
之后,我们开始定义应用于所有实例的规则:PongGame
<PongGame>:
...
与 Python 一样,kv 文件使用缩进来定义嵌套块。定义的块 在 和 字符内有一个类名是一条规则。它将应用于任何实例 命名类。如果在我们的示例中替换为 ,则所有 Widget 实例将包含垂直线和两个 Label 小部件 它们,因为它将为所有 Widget 实例定义这些规则。<
>
PongGameWidget
在规则部分中,您可以添加各种块来定义样式和 它们将应用于的小部件的内容。您可以:
-
设置属性值
-
添加子小组件
-
定义一个画布部分,您可以在其中添加图形指令 定义小组件的呈现方式。
我们拥有的规则中的第一个块是画布块:<PongGame>
<PongGame>:
canvas:
Rectangle:
pos: self.center_x - 5, 0
size: 10, self.height
个人见解:
canvas中文翻译为画布、Rectangle中文翻译为矩形,Canvas是用于绘制图形的一种方式,此代码就是在窗口中绘制了一个矩形的画布。
pos: self.center_x - 5, 0
固定位置:x坐标为窗口中心x坐标后移5个单位,y坐标为0
size: 10, self.height
:
固定位置:宽度大小为5个单位,高度大小为窗口的高度
所以这个画布块说小部件应该画一些 图形基元。在本例中,我们在画布上添加一个矩形。我们设置 矩形的 POS 位于 水平中心左侧 5 个像素处 小部件,0 表示 y。矩形的大小设置为 10 像素 宽度,以及小部件的高度高度。定义 像这样的图形,是渲染的矩形将自动 当值表达式中使用的任何小部件的属性发生更改时更新。PongGame
注意
尝试调整应用程序窗口的大小并注意发生的情况。那是 对整个 UI 会自动调整大小的标准行为 Window 是根据元素的属性size_hint调整元素的大小。这 默认小部件size_hint是 (1,1),这意味着它将在两者中 100% 拉伸 x 方向和 y 方向,因此填充可用空间。 由于矩形的位置和大小以及分数的center_x和顶部 标签在 类的上下文中,这些属性将自动 当相应的小组件属性更改时进行更新。使用 Kv language 为您提供自动属性绑定。:)PongGame
我们添加的最后两个部分看起来非常相似。他们每个人都添加了一个标签 Widget 作为子 Widget 添加到 Widget。目前,文本 它们都只是设置为“0”。我们会将其与实际情况联系起来 一旦我们实现了逻辑,但标签已经得分 看起来不错,因为我们设置了更大的font_size,并将它们相对定位 到根小部件。关键字可以在子块内使用 请回溯到规则适用的父/根小部件( 在此 案例):PongGamerootPongGame
<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"
个人见解
Widget中文翻译是部件,root指的是当前widget(部件)的根节点,也就是根部件,也可以理解为是窗口本身。在此kv文件中<PongGame>是一个自定义的widget(部件),所以root代指<PongGame>本身。懂了吗小朋友?
翻译一下:
center_x: root.width / 4
水平居中:<PongGame>的四分之一
center_x: root.width * 3 / 4
水平居中:<PongGame>的四分之三
添加球
添加球
好的,所以我们有一个基本的乒乓球竞技场可以玩,但我们仍然需要球员和 一个可以击球的球。让我们从球开始。我们将添加一个新的 PongBall 类来创建一个小部件,该小部件将成为我们的球并使其弹跳。
乒乓球课
下面是 PongBall 类的 Python 代码:
class PongBall(Widget):
# 小球在x和y轴上的速度
velocity_x = NumericProperty(0)
velocity_y = NumericProperty(0)
# 引用列表属性,这样我们可以使用 ball.velocity 来代替 velocity_x 和 velocity_y,
# 就像使用 w.pos 来代替 w.x 和 w.y 一样
velocity = ReferenceListProperty(velocity_x, velocity_y)
# ``move`` 函数将使小球移动一步。这个函数将在相等的时间间隔内调用,以动画方式移动小球
def move(self):
self.pos = Vector(*self.velocity) + self.pos
这是用于将球绘制为白色圆圈的 kv 规则:
<PongBall>:
size: 50, 50
canvas:
Ellipse:
pos: self.pos
size: self.size
要使一切正常,您还必须添加所使用的 Properties Property 类的导入和 . 以下是此步骤的完整更新的 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
请注意,不仅添加了<PongBall>小部件规则,还添加了 <PongGame> 小部件规则中的子部件 PongBall。
个人见解
Ellipse是创建椭圆形的意思,与上文中的
Rectangle矩形有点区别哦,那么问题来了还有没有别的创建图形的代码?
当然有:
线段
Canvas:
#线段
Line:
points: 100, 100, 200, 200, 300, 100
三角形
Canvas:
#三角形
Triangle:
points: 100, 100, 200, 200, 300, 100
组合图形
Canvas:
InstructionGroup:
#椭圆形
Ellipse:
pos: 100, 100
size: 50, 50
#矩形
Rectangle:
pos: 200, 200
size: 50, 50
接下来我们来看看self
<PongBall>:
size: 50, 50
canvas:
Ellipse:
pos: self.pos
size: self.size
self是部件本身的意思,理论上此代码的self是不是就是画布?但画布不属于部件,所有这里的self指的是<PongBall>本身。也就是和<PongBall>的size相同。
很棒啊,来我们继续,同理
<PongGame>:
canvas:
Rectangle:
pos: self.center_x - 5, 0
size: 10, self.height
中的self就是<PongGame>,self.center_x - 5就是<PongGame>中心点x往左偏移5个单位,self.height就是<PongGame>的高度。
Label:
font_size: 70
center_x: root.width / 4
top: root.top - 50
text: "0"
中的root承接上文,代指窗口。所以top:root.top-50就是y的顶部坐标为窗口顶部坐标往上偏移50个单位。
PongBall:
center: self.parent.center
中self
指的是PongBall
控件的实例,而self.parent
指的是PongBall
控件的父级对象。也就是<PongGame>,self.parent.center就是<PongGame>的中心点。切记pongball指向的是直接父级而不是根父级
添加球动画
让球移动
很酷,所以现在我们有一个球,它甚至有一个功能......但事实并非如此 搬家了。让我们解决这个问题。move
在时钟上调度功能
我们需要定期调用我们的球的方法。幸运的是,Kivy 通过让我们使用 并指定间隔来安排我们想要的任何函数,使这变得非常容易:move
Clock.schedule_interval(game.update, 1.0/60.0)
例如,这行将导致游戏对象的功能 每 60 秒调用一次(每秒 60 次)。update
个人见解
在Kivy中,Clock
对象提供了一系列用于调度和管理时间事件的方法。
对于这个CoCk对象还有其他方法,这里简单介绍一下其他方法。
延时执行函数,延时单位为秒:
Clock.schedule_once(函数, 延时)
循环延时执行函数,延时单位为秒:
Clock.schedule_interval(game.update, 1.0/60.0)
返回当前的时间,单位为秒:
time = Clock.get_time()
返回当前应用程序的帧率:
fps = Clock.get_fps()
对象属性/引用
不过,我们还有另一个问题。我们希望确保 PongBall 的函数被定期调用,但在我们的代码中,我们没有任何引用 添加到球对象,因为我们刚刚通过 kv 文件添加了它 在类的 KV 规则内。唯一参考我们的 游戏是我们在应用程序构建方法中返回的那个。movePongGame
因为我们要做的不仅仅是移动球(例如 把它从墙上弹开,然后从球员球拍上弹开),我们可能需要 无论如何,我们班的方法。此外,鉴于 我们已经有了一个对游戏对象的引用,我们可以在构建应用程序时轻松安排它的新方法:update
PongGameupdate
class PongGame(Widget):
def update(self, dt):
# 调用球的移动等操作
pass
class PongApp(App):
def build(self):
game = PongGame()
Clock.schedule_interval(game.update, 1.0/60.0)
return game
个人见解
class是创建类的标识,所有此代码中,创建了,两个类;其中第一个类PongGame是继承widget自定义部件,主要用于创建部件内的部件与其交互逻辑,说白了就是写与UI交互的代码的。第二个类PongApp是继承kivy的App程序类,用于配置和启动kivy的应用程序。
class PongGame(Widget):
def update(self, dt):
# 调用球的移动等操作
pass
如果有阅读多篇kivy项目,你会发现很多继承部件的类中,函数基本都带self参数,这是为什么嘞?原因是self所代指的是继承部件类本身,通过这个参数可以访问部件部件中的绝大部分参数,比如获取与设置文本的text值等等,还有添加部件到布局等,所以大都包含self的函数是为来方便对继承部件内部件进行交互操作。其次dt 代表时间间隔(delta time),这里并未声明时间间隔。
但是,这仍然没有改变这样一个事实,即我们没有对 kv 规则创建的子小部件的引用。为了解决这个问题,我们可以在 PongGame 类中添加一个,并将其连接到在 KV 规则。完成后,我们可以很容易地引用 ball 属性 在方法内部,甚至让它从边缘反弹:PongBall
update
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 文件中挂接它,给子小部件一个 id 并将 PongGame 的 ObjectProperty 设置为该 id:ball
python
<PongGame>:
ball: pong_ball
# ... (画布和标签等)
PongBall:
id: pong_ball
# 设置乒乓球的中心位置为其父部件(PongGame)的中心位置
center: self.parent.center
个人见解
if (self.ball.y < 0) or (self.ball.top > self.height):
self.ball.velocity_y *= -1
ball示是PongBall冰棒球的id,所以self.ball.y是id为ball的PongBall自定义部件的y坐标,其他同理。对了这里所指的自定义部件指的是在py中那个用来继承部件的类。
center: self.parent.center
中的parent是父部件,不是根部件,所以center: self.parent.center就是设置乒乓球的居中位置在父部件的中中心。
self.ball.velovity_y
中的 velocity_y
表示乒乓球(PongBall)在Y轴方向上的速度。这是一个自定义属性,说白了就是继承部件类里面的一个变量,参数而已。并不是通用的参数啊!
注意
在这一点上,一切都被连接起来,让球反弹。如果 你一边编码一边走,你可能想知道为什么球没有 随处移动。球的速度在 x 和 y 上都设置为 0。 在下面的代码列表中,一个方法是 添加到类中并在应用的方法中调用。它设置了一个 随机 x 和 y 速度,并且还重置了位置,所以我们 稍后可以使用它来重置球员得分时的球。serve_ball
PongGamebuild
下面是此步骤的完整代码:
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) # x轴速度属性
velocity_y = NumericProperty(0) # y轴速度属性
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() # 更新球的位置
# 球与顶部和底部的碰撞
if (self.ball.y < 0) or (self.ball.top > self.height):
self.ball.velocity_y *= -1 # 反转y轴速度
# 球与左侧和右侧的碰撞
if (self.ball.x < 0) or (self.ball.right > self.width):
self.ball.velocity_x *= -1 # 反转x轴速度
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 规则的细节,因为这些概念是 前面的步骤中已经介绍过。相反,让我们专注于如何移动 响应用户输入的播放器小部件。您可以获取整个代码和 kv 本节末尾的类规则。PongPaddle
在 Kivy 中,小部件可以通过实现 、 和 方法对输入做出反应。默认情况下,Widget 类 通过在其所有 子小部件传递事件,直到其中一个子项返回。True
乒乓球很简单。球拍只需要上下移动。事实上,它是 如此简单,我们甚至不需要让播放器小部件处理 事件本身。我们将只实现该类的函数,并让它根据左或右玩家的位置设置 关于触摸是发生在屏幕的左侧还是右侧。on_touch_movePongGame
检查处理程序: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
我们将把每个玩家的分数保存在一个 .的分数标签通过更改 NumericProperty 来保持更新,这反过来又会更新 更新子 Labels 文本属性。此绑定 发生是因为 Kivy 自动绑定到任何引用 在它们相应的 KV 文件中。当球 从边路逃脱,我们将更新比分并发球 再次通过更改类中的方法。该类还实现了一个方法,以便球反弹 根据它击中球拍的位置而有所不同。以下是 PongPaddle 类的代码:PongGame
score
PongGame
update
PongGame
PongPaddle
bounce_ball
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) # x轴速度属性
velocity_y = NumericProperty(0) # y轴速度属性
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) # 球拍1对象属性
player2 = ObjectProperty(None) # 球拍2对象属性
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
# 球出界得分
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
好的朋友,到这一步你已经很棒了。运行后就将得到这个程序
如下是打包后在安卓端运行的效果