第一章 Python Kivy 学习 – Kivy介绍及环境安装
第二章 Python Kivy 学习 – Kivy项目开发原理(待编辑)
第三章 Python Kivy 学习 – Kivy官方入门教程Pong Game
介绍了基本的设计模式和应用程序开发过程。
官方说法:是整个学习进程图中最重要的应用实例。
说明:本人运行的环境已经预先安装了
Python3.10 编译器
Anaconda python环境管理器
PyCharm python代码编辑运行环境python中包有:
kivy 今天讲解的主角,python编辑android的为数不多的工具
python-for-android 目前还没有用到请参考使用。
该教程使用的是逐步细化的开发模式,会多次重新编辑和完善代码。以求循序渐进的讲解kivy知识。
首先:利用Anaconda 新建环境learn
其次:利用PyCharm 新建项目gameAndroid并配置其编译环境为learn
再次:使用PyCharm新建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()
继续运行应用程序。此时它应该只显示一个黑色窗口。
1、它创建了PongGame类,其继承了超级类Widget ,Widget 是一个控件(kivy一般叫做小部件,我不太顺口,都叫控件了)的标准类,所有控件类都是Widget 的子类。
2、主文件的引导过程:详见最后详解。
3、此文件所创造的类中,app类只是负责引导和设置属性作用,真正的控件,也就是屏幕显示的东西是Widget类。
4、一般kivy中是以 Widget,Screen,Layout三种控件类作为屏幕的图形显示的根类。Screen是屏幕管理器类型,Layout是多种布局类的类型集合。
接下来 我们将通过定义外观来绘制 游戏的 背景和分数。
在项目目录中新建文件"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"
1、pong.kv由你所定义的类 PongApp(App)自动引用,仅由文件名即可自动执行。当然是指app类才有这个待遇,而一个kivy才有一个app类,其它文件想要套用,请使用Builder方法。例如:Builder.load_file(‘Speech.kv’) 语句
2、在kivy中 .kv 文件用来定义类的外观和相关属性设置
下面分别介绍其语法
#:kivy 1.0.9
此行在.kv文件中必须含有,它应该且必须以空格开头,而一般下面都空一行。(python编程最讲究的是优雅,最大的优雅是代码的规范性。)
这行代码向kivy声明此kv文件用到的最低版本号,便于打包时将足够的版本内容打包进应用来支持运行。
<PongGame>:
...
<>中间定义的是Widget型PongGame类,利用换行缩进来表示以下都是编辑这个类的内容。
本示例中:是PongGame类的外形定义。
如果你使用则会重新定义Widget类的外形和属性,将导致所有Widget的子类的外形均为此定义。这个方法一般用于定义label等常用控件,且一个app中要求有一致性外观的情况。
在此文件中,你可以如下操作:
1、设置类的控件类型的属性值定义
2、添加子小部件
3、定义一个画布部分,您可以在其中添加定义小部件如何呈现的图形指令。
4、定义动作的连接方法(实质是设置属性)
<PongGame>:
canvas:
Rectangle:
pos: self.center_x - 5, 0
size: 10, self.height
canvas: 创建画布
Rectangle:画布内创建矩形
pos: self.center_x - 5, 0pos为一个要引用的位置,是常用的属性,用于获取/设置矩形位置的属性。设置为小部件水平中心左侧 5 个像素,垂直为0。
self.为本控件或本小部件size: 10, self.height
矩形大小为宽度10个像素,高度为小部件高度
1、最好以相对或变量来定义部件的大小,当值表达式中使用的任何小部件的属性发生变化时,渲染的矩形将自动更新。
2、在kivy中我没看到渲染的功能,现在是只要属性变化,它自动渲染,这个很酷!
3、kivy中有三个常用和一个不常用的称呼语:
root,指本控件的父控件
self,指本控件
app,永远都是指向你的app类的,没啥用
children,指本控件的子控件,是个list,需要慢慢研究他的功能和用法。
<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"
lable标签控件
font_size 字号
center_x 中心X轴位置
root.width 父部件宽度的3/4位置
top 上方位置
text 文字
位置、大小、颜色及连接方法属性是kivy中的重要属性设置
位置和大小,详见kivy 控件的代码新建及属性设置
颜色正在研究,连接方法有些坑也正在踩,时间戳:20220604
class PongBall(Widget):
# 定义球在X,Y轴上的速度
velocity_x = NumericProperty(0)
velocity_y = NumericProperty(0)
# 用ball.velocity 作为参考属性
# 表示ball在两个维度的综合速度
velocity = ReferenceListProperty(velocity_x, velocity_y)
# ``move`` 函数将以*self.velocity等距移动的形式使球移动
def move(self):
self.pos = Vector(*self.velocity) + self.pos
class kivy.properties.NumericProperty()
单个数字属性,一般做为坐标的单值属性,必须是数字格式。
ReferenceListProperty (NumericProperty(),NumericProperty())
属性集合,可含有多个相同的属性单元,数据类型元组
a = kivy.vector(x,y)
2D的矢量坐标,数据类型为python list,可以用a[0],访问x
Property类的属性,是kivy的一个特征,很好用,主要作用是可以定义一个类型已知,但内容未知的属性,这个属性可以被其它类或方法调用,可以有kv文件定义其规则和内容,可以在类内的方法被调用,
这里有个坑 就是在内容未知的时候,请不要在类内调用,可以定义方法有在类外调用,但类内直接调用会出错,因为他没有定义完 是个空的无类型的属性,会被报错。
from kivy.properties import NumericProperty, ReferenceListProperty
from kivy.vector import Vector
import kivy.properties # kivy的属性包
不要将Kivy 的属性与Python 的属性混淆。
Kivy 的属性类支持:
1、值检查/验证当您为属性分配新值时,会根据验证约束检查该值。例如,一个NumericProperty将检查您的值是否为数字类型。这可以在早期防止许多错误。
2、观察者模式
您可以指定属性值更改时应该发生的情况。您可以将自己的函数绑定为对Property的更改和回调。例如,如果您希望在小部件的属性更改时调用一段代码,您可以bind为它创建一个函数连接。
3、更好的内存管理
同一属性的实例在多个小部件实例之间共享。
<PongBall>:
size: 50, 50
canvas:
Ellipse:
pos: self.pos
size: self.size
Ellipse 圆形
pos: self.pos 设定本控件的标的位置为本控件位置
这里的本控件不是指Ellipse 圆形,也不是canvas画布,而是PongBall的Widget控件。一定注意哦!
size大小设置为本控件大小
上节定义的球并没有按照方法move()来移动,实际上我们只是定义了类,并未实例化和调用其方法。现在我们有一个函数,使得一定的时间间隔来调用某方法。
Clock.schedule_interval(game.update, 1.0/60.0)
使game.update对象的函数每 60 秒被调用一次。
初始时球在一个位置向一侧屏幕运动,
当触碰屏幕边缘时判定本方输,对方积一分。
当碰到球拍时可以弹起,弹起的方向需要计算。
球拍可以移动(由触屏自行移动)
class PongPaddle(Widget):
score = NumericProperty(0)
"""定义记录得分字段属性,数字格式"""
def bounce_ball(self, ball):
if self.collide_widget(ball):
"""kivy.uix.widget.Widget
def collide_widget(self, wid: {x, right, y, top}) -> bool
检查另一个控件同本控件是否碰撞接触"""
vx, vy = ball.velocity
offset = (ball.center_y - self.center_y) / (self.height / 2)
"""kivy.uix.widget.Widget.center_y
获取控件的中心点y坐标
"kivy.uix.widget.Widget.height
获取控件的高度坐标
两个控件的距离除以球拍高度的一半,反弹y轴变量"""
bounced = Vector(-1 * vx, vy)
"""将球的X轴反向"""
vel = bounced * 1.1
"""增加速度1.1倍"""
ball.velocity = vel.x, vel.y + offset
"""返回给球的综合速度值,y轴需要增加反弹变量"""
kv文件中插入对PongPaddle的定义
<PongPaddle>:
size: 25, 200
canvas:
Rectangle:
pos: self.pos
size: self.size
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)
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)):
"""定义球的待服务函数,用于发球,以(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()
代码中的引用关系如下:
代码结尾
PongApp().run(),调用了页面主类PongApp
PongApp()
实例化PongGame类和其serve_ball()方法,实现背景,发球
定义时钟调度功能,调用执行PongGame.update()方法
最终返回实例化的PongGame类,记得一定要返回,否则无法显示已经定义的类。PongGame类
方法中定义了一个名字为ball和player1、player2的3个属性值
1、ball的kv文件定义中进行了PongBall的直接实例化引用,请详见kv文件。所以ball具有了PongBall一切属性并且使其内置为PongGame类的一个成员属性值。
2、同样player1、player2同样方法将PongPaddle类引用并实例化serve_ball()
此方法用于发球和重新发球来调用
PongGame.update()
1、作为本游戏的主调度函数,起到连续操作的作用
2、self.ball.move(),首先调用球移动,使得球动起来
3、bounce_ball(),调用判断是否碰撞的函数
4、判断打到顶部和底部的处理方法
5、判断得分并给得胜方发球on_touch_move(self, touch)
在Kivy中,小部件可以通过编辑和执行on_touch_down,on_touch_move和on_touch_up方法来对输入做出反应。
默认情况下,小部件类仅调用其所有子小部件上的相应方法来传递事件,直到其中一个返回true,从而实现了这些方法。
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
一个类的定义可以直接被另一个类定义中的内容引用为小控件。
id是一个控件在本控件中的名称,可以被本定义使用,也可以外部引用。
Kivy官方教程 https://kivy.org/doc/stable/tutorials/pong.html