接下来我们会使用一个跨平台库: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
原本的name
和Say
都没有变化。
变量或函数的重写可以更改从父类那里继承来的东西:
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_down | on_touch_move | on_touch_up | on_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()
函数清空MainBoard
,clear_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
是啥吧?
最后return
了MainBoard
实例化的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()
我相信当你看见手机上那条蠕动的蛇时,心里会产生极大的满足感。
来源:CSDN
作者:tom12138
链接:https://blog.csdn.net/tom12138/article/details/104895940