5类与对象:kivy库


接下来我们会使用一个跨平台库:kivy。这个库的厉害之处在于同样的代码可以直接运行在手机和电脑上。所以这一节会同时在手机和电脑上实现贪吃蛇游戏。
想想看,随手掏出个手机就可以愉快的做游戏了,简直美滋滋。但为了真的能够愉快,我们还要学习一些新东西。

面向对象编程

如果你还没有对象,现在赶紧去找一个

对象,听起来是个有血有肉的实体。想想看那么多人追求纸片人老婆,有血有肉这一条也不怎么重要了,但必须是切实存在的实体。

定义类

Python有提供这种“切实存在实体”的关键字:class

试一试:

class Dog:
    name = "狗"
    breed = "中华田园犬"  #品种
    def Say(self):
        print("汪汪!")

看起来class就是变量和函数的容器,这就是的定义。不同的是类的函数定义def Say(self):中有一个参数self,当外部调用类的函数时会默认传入一个参数,所以定义类的函数时总要写个参数接收。

像函数的定义一样,只定义不执行,它就像是一个架子,还没有灵魂,需要继续这样操作:

oneDog = Dog()

这一过程被称为类的实例化。注意oneDog只是一个变量名,你想怎么起名都可以,不要在变量名上纠结。

实例化后我们有了Dog的一个实例oneDog,我们就可以与它”交流“了:

print(oneDog.name)
print(oneDog.breed)
oneDog.Say()
狗
中华田园犬
汪汪!

很好,名字、品种和它怎么叫我们都看到了,只是这个名字“狗”太简单了,不如换个名字:

oneDog.name = "旺财"
print(oneDog.name)
旺财

嗯,这样就大气了很多。

我们再实例化一条狗:

anotherDog = Dog()
print(anotherDog.name)

我们不是把名字改了吗?

实际上我们改的是示例的oneDog,它的改动不会影响原来的“框架”Dog

名字可以谁便改,那品种呢?当然可以,这俩都是变量只是名字不同罢了。

你还可以这样做:

class Cat(Dog):
    breed = "加菲猫"

oneCat = Cat()
print(oneCat.name)
print(oneCat.breed)
oneCat.Say()
狗
加菲猫
汪汪!

这一过程叫做类的继承,新的Cat类会继承括号里Dog类的东西,这里的Dog就叫做Cat父类,不过我们把breed手动更改了,但Dog原本的nameSay都没有变化。

变量或函数的重写可以更改从父类那里继承来的东西:

class Cat(Dog):
    breed = "加菲猫"
    name = "喵喵"
    def Say(self):
        print("喵~喵~")
oneCat = Cat()
print(oneCat.name)
print(oneCat.breed)
oneCat.Say()
喵喵
加菲猫
喵~喵~

好,现在它看起来是只猫,叫起来也像只猫,那它就是只猫。

看看上面的.操作:oneCat.name拿出了oneCat里的变量;oneCat.Say()调用执行了oneCat里的函数。还记得上一节pygame里的事件吗,按键按下a的事件pygame.K_a是一个变量,还有我们获取事件列表的pygame.event.get()函数,都是从pygame里拿出来的,这说明pygame也是一个类,他就像Cat类一样,里面定义了各种变量和函数,等待使用的人们自行取用,就像一个大仓库。

random实际上也是个类,如果你进了Python安装目录的lib文件夹里用编辑器打开random.py文件后,你第一眼就会看见最上面的class random(),再往下翻你会看到它里面定义的randint函数。

刺激的来了,实际上Python的列表、元组、字典甚至字符串这些可以用.操作的对象都是类,实际上Python的所有都是对象,你的所有操作都是对这些对象操作,这就是标题所说的面向对象编程

是不是开始迷糊了,如果迷糊了不要急,知道怎么用、多用就行了。还是那句话,编程的世界不是死记硬背而是熟能生巧。

不用愁找不到对象了,在Python里挑一个吧。

在类内部调用自己的东西

在外面我们可以实例化后用.调用里面的变量或者函数,那在定义类的时候呢:

class Dog():
    name = "狗"
    def showInfo(self):
        print(name)
oneDog = Dog()
oneDog.showInfo()
    print(name)
NameError: name 'name' is not defined

名字“name”没有定义,类里的函数是感觉不到外面的变量的,但是上面说过外部调用类的函数是会默认传入一个参数,其实这个参数就是类本身,我们这里用self变量名来接收,试着这样写:

class Dog():
    name = "狗"
    def showInfo(self):
        print(self.name)

oneDog = Dog()
oneDog.showInfo()#这里隐形传入了一个参数,这个参数其实就是oneDog

类的初始化(构造函数)

类里有一个专属函数__init__,它会在类实例化时自动调用:

class Dog():
    def __init__(self):
        print("你把我实例化了。")
oneDog = Dog()
你把我实例化了。

我们能在实例化时传入的参数也会被__int__函数接收:

class Dog():
    def __init__(self,value1,value2):
        print("实例化后你还传入了:",value1,value2)

oneDog = Dog("狗",2333)
实例化后你还传入了: 狗 2333

现在我们来做些更有意思的。

Kivy

fresh、fast、flexible、focused、funded、free。没错它就是FFFFFF团。

安装

手机端

手机端打开Pydroid3就行了。kivy已经在里面安好了。

电脑端

就像安装Pygame一样,我们使用pip进行kivy安装,由于kivy包体比较大和国内强大的禁制,我们直接选择换源安装,打开cmd,依次运行这三行代码:

  • pip install -i https://pypi.tuna.tsinghua.edu.cn/simple docutils pygments pypiwin32 kivy.deps.sdl2 kivy.deps.glew

  • pip install -i https://pypi.tuna.tsinghua.edu.cn/simple kivy.deps.gstreamer

  • pip install -i https://pypi.tuna.tsinghua.edu.cn/simple kivy

kivy在电脑上的安装有很多不确定性,比如网络原因的超时(timeout),需要找个信号好的地方重新安装。

官方安装教程:https://kivy.org/doc/stable/installation/installation-windows.html

积极使用搜索引擎查询自己的问题,你会发现搜索引擎就是最好的老师。

开始

同一个世界,同一个梦想。

无论你是手机端还是电脑端,接下来我们会编写同样的代码。

第一个kivy应用
from kivy.app import App 
from kivy.uix.label import Label 

class TestApp(App):
    def build(self):
        return Label(text="Hello World!")

TestApp().run()

from kivy.app import App:从kivy.app里导入App,现在我们能猜到,这应该是个类。实际上它是一切kivy应用的基本类。

from kivy.uix.label import Label:导入Label类,这是一个显示文本的类。

class TestApp(App):创建了一个叫TestApp的类,继承了App

def build(self)::继承App的类会自动调用build函数,所以这个函数名是固定的。

return Label(text="Hello World!"):返回了显示文本的Label类,text参数是显示的字符串,return会使这个类直接显示在窗口上。

TestApp().run()实例化我们创建的类并且执行它的run函数,很明显这个run是继承自App的,它的作用就是执行我们写好的类。

保存、运行,你会在窗口中间再次看见那个魔咒。

事件

kivy有一系列的事件,每一个事件发生时就会自动调用相应名字的函数,我们只要知道什么事件会触发什么函数,然后写相应的函数就行了:

让我们尝试一下:

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.clock import Clock

FPS = 30
WIDTH = Window.size[0]  #窗口宽

HEIGHT = Window.size[1] #窗口高


class MainBoard(Widget):
	num = 0
	def on_touch_down(self,touch):
		self.num += 1

	def update(self,dt):
		self.clear_widgets()
		lab = Label(text=str(self.num),pos=(WIDTH/3,HEIGHT/2),font_size=50)

		self.add_widget(lab)


class TestApp(App):
	def build(self):
		board = MainBoard()
		Clock.schedule_interval(board.update,1/FPS)
		return board

TestApp().run()

看看上面这一大段都干了什么:

from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.clock import Clock

导入Widget,这是一个窗口类,我们不再直接返回一个Label,而是把Label放进Widget里,然后返回Widget

导入Window,用来获取窗口大小,毕竟手机和电脑大小不同。

导入Clock,类似pygame里的pygame.time.Clock(),都是用来控制帧率的。

FPS = 30
WIDTH = Window.size[0]
HEIGHT = Window.size[1]

FPS,一个整型变量,后面用来设置帧率的。

Window.size是一个两个元素的元组,储存着窗口的宽和高。

class MainBoard(Widget):
    num = 0
    def on_touch_down(self,touch):
        self.num += 1

创建一个新的类MainBoard,继承了Widget

神秘的变量num,话说这变量名好眼熟。

每当你点击屏幕时(无论鼠标点击电脑还是手指点击手机),kivy会自动调用on_touch_down函数,self参数传入的是类自己,touch参数传入的是事件信息,touch.pos存着点击的位置。

这里没有用到这两个参数,只是把num增加了1。

部分函数名与其对应的事件:

on_touch_downon_touch_moveon_touch_upon_joy_axis
开始点击事件滑动事件点击结束事件游戏手柄摇杆

键盘事件是另一种实现方式,我们暂不了解。可以在官方文档里查看键盘绑定

class MainBoard(Widget):
	......
	def update(self,dt):
		self.clear_widgets()
		lab = Label(text=str(self.num),pos=(WIDTH/2,HEIGHT/2),font_size=50)

		self.add_widget(lab)

MainBoard类里创建了一个update函数来显示具体的东西。

self.clear_widgets()函数清空MainBoardclear_widgets()函数当然是继承自Widget

Label这个文本类的参数text是显示的字符串,pos是位置,font_size是字体大小。

通过self.add_widget(lab)函数,把这个类添加进MainBoard

class TestApp(App):
    def build(self):
        board = MainBoard()
        Clock.schedule_interval(board.update,1/FPS)
        return board

build函数里把MainBoard实例化。

Clock.schedule_interval(board.update,1/FPS)函数会每1/FPS秒调用一次update函数,相当于一秒钟刷新FPS次,也就是帧率为FPS帧,没忘记FPS是啥吧?

最后returnMainBoard实例化的board,就把它显示在了窗口中。

如果你没迷糊的话就会发现:这又是一个丧心病狂的“手指抽筋模拟器”。我把它保存为手指抽筋模拟器kivy版.py

坐标系

kivy的坐标原点在窗口左下角,右方向为x轴正方向,上方向为y轴的正方向,实际上就是笛卡尔坐标系的第一象限。不要和Pygame的坐标系搞混了。

绘画
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Color, Rectangle
from kivy.core.window import Window
from kivy.clock import Clock

FPS = 30
WIDTH = Window.size[0]  #窗口宽

HEIGHT = Window.size[1] #窗口高

class MainBoard(Widget):
    def update(self):
        with self.canvas:
            Color(1, 1, 1)
            Rectangle(pos=(WIDTH / 2, HEIGHT / 2), size=(40, 40))

class SnakeApp(App):
    def build(self):
        board = MainBoard()
        Clock.schedule_interval(board.update,1/FPS)
        return p
SnakeApp().run()

运行后你可以在在窗口中间看见一个熟悉的方块。

class MainBoard(Widget):
    def update(self):
        with self.canvas:
            Color(1, 1, 1)
            Rectangle(pos=(WIDTH / 2, HEIGHT / 2), size=(40, 40))

要在kivy里绘画需要with self.canvas:,这相当于打开了一个画板,Color(1, 1, 1)函数选择颜色。

Rectangle(pos=(WIDTH / 2, HEIGHT / 2), size=(40, 40))函数画一个方形,参数pos是位置,size是大小:一个数组,包含宽和高。

kivy的颜色

kivy和pygame一样使用rgb颜色,只不过pygame颜色值的范围是0-255,而kivy是0-1。都是值越大颜色越深。上面的Color(1, 1, 1)就表示白色。

完成kivy版贪吃蛇

我们知道了怎么方块,贪吃蛇的游戏逻辑我们也在上一节完成了,还缺少一个控制方式,这里为了统一电脑和手机的操控,选择使用滑动来控制方向。

我们只要知道点击(触摸)开始的位置和结束的位置,就可以确定滑动方向:

修改代码如下:

class MainBoard(Widget):

	direction = "left"
	touchDown = []

	def on_touch_down(self,touch): #点击开始
		self.touchDown = [touch.pos[0],touch.pos[1]]

	def on_touch_up(self,touch): #点击结束
		hor = touch.pos[0] - self.touchDown[0] #水平方向的滑动
		ver = touch.pos[1] - self.touchDown[1] #竖直方向的滑动
		if abs(hor)>abs(ver):  #水平滑动
			if hor>0:
				self.direction = "right"
			else:
				self.direction = "left"
		else: #竖直滑动
			if ver>0:
				self.direction = "up"
			else:
				self.direction = "down"

touch.pos[0]是x轴的位置,touch.pos[1]是y轴的位置。

abs函数返回绝对值,即abs(-2)的值就是正数2。

on_touch_up函数里的判断逻辑图解:

依此类推。

现在,我们也知道了kivy的控制方式,看下面的代码前试着用kivy实现贪吃蛇。

kivy版贪吃蛇完整代码
# 贪吃蛇_kivy.py

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Color, Rectangle
from kivy.core.window import Window
from kivy.clock import Clock
from kivy.uix.label import Label
import random


FPS = 3
SPEED = 40
WIDTH = Window.size[0]
HEIGHT = Window.size[1]

class MainBoard(Widget):

	head = [(WIDTH/2)//SPEED*SPEED, (HEIGHT/2)//SPEED*SPEED]
	body = [[(WIDTH/2)//SPEED*SPEED + SPEED,(HEIGHT/2)//SPEED*SPEED]]
	apples = []

	direction = "left"
	touchDown = []
	GameOver = False

	def on_touch_down(self,touch): #点击开始
		self.touchDown = [touch.pos[0],touch.pos[1]]

	def on_touch_up(self,touch): #点击结束
		hor = touch.pos[0] - self.touchDown[0] #水平方向的滑动
		ver = touch.pos[1] - self.touchDown[1] #竖直方向的滑动
		if abs(hor)>abs(ver):  #水平滑动
			if hor>0:
				self.direction = "right"
			else:
				self.direction = "left"
		else: #竖直滑动
			if ver>0:
				self.direction = "up"
			else:
				self.direction = "down"

	def createApple(self):
		while True:
			newApple = [random.randint(0,WIDTH-SPEED)//SPEED*SPEED,random.randint(0,HEIGHT-SPEED)//SPEED*SPEED]
			if newApple not in self.apples and newApple != self.head and newApple not in self.body:
				self.apples.append(newApple)
				break

	def update(self,dt):
		self.canvas.clear()
		self.clear_widgets()
		if self.GameOver:
			self.add_widget(Label(text="GameOver! score: %d"%(len(self.body)-1),pos=(WIDTH/3,HEIGHT/2),font_size=50))
		else:	
			self.body.insert(0,[self.head[0],self.head[1]])
			if self.direction=="left":
				self.head[0]-=SPEED
			elif self.direction=="right":
				self.head[0]+=SPEED
			elif self.direction=="up":
				self.head[1]+=SPEED
			elif self.direction=="down":
				self.head[1]-=SPEED
			if self.head[0]<0 or self.head[0]>WIDTH-SPEED or self.head[1]<0 or self.head[1]>HEIGHT-SPEED or self.head in self.body:
				self.GameOver = True
			if self.head not in self.apples:
				self.body.pop(-1)
			else:
				self.apples.remove(self.head)
				self.createApple()
			with self.canvas:
				Color(0.7, 0.7, 0.7)
				Rectangle(pos=self.head, size=(SPEED, SPEED))
			with self.canvas:
				Color(1, 1, 1)
				for i in self.body:
					Rectangle(pos=i, size=(SPEED, SPEED))
			with self.canvas:
				Color(1, 0, 0)
				for apple in self.apples:
					Rectangle(pos=apple, size=(SPEED,SPEED))


class SnakeApp(App):
	def build(self):
		board = MainBoard()
		board.createApple()
		Clock.schedule_interval(board.update,1/FPS)
		return board


SnakeApp().run()

我相信当你看见手机上那条蠕动的蛇时,心里会产生极大的满足感。

本系列地址:https://gitee.com/ZiKang12138/course/wikis

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值