《从零开始PYTHON3》第十五讲
虽然看起来绘图和音乐并不相关,但是听过了上一讲的内容你一定知道,这是游戏编程中四个需要处理内容的两部分,这两部分必须同时、并行的处理,不能因为某一项计算的拖延,导致另外一方程序的停滞。要知道人对声音的断续和游戏的卡顿是很敏感的。
在Pygame中进行并行处理的主要手段,一是Pygame中的各种函数,大多是不等待工作完成,只要工作开始进行,就返回主程序,等待下一条命令,而任务会在看不到的后端继续执行,并不停止;另外则是各个并行的任务之间,会通过“消息事件”的方式跟主程序沟通,从而让主程序能够统一调度各项任务的进程。
这是复习上一讲的内容。并行:指的是在硬件的帮助下,多个任务同时进行,互不影响,最终完成任务的过程。完成的时间取决于最慢的任务。这个硬件帮助,通常是指多核CPU、显卡计算配合CPU计算以及数据传输中的多通道。
串行:指的是完成一项工作,才进行另外一项工作,最后完成的时间是所有任务完成的总和。
游戏绘图
绘图模式
同我们前面学过的科学绘图和海龟绘图相比,游戏绘图在绘图的模式上有较大的区别。
传统程序绘图是顺序方式,每画一笔可以认为这一笔一直都在,直到程序退出或者擦除画面。你可以回忆一下我们在科学绘图和海龟绘图时候所学习的内容。
游戏绘图更类似拍照,一个个角色进入画面,摆好姿态,等待快门按下,这样完成一帧。随后会根据游戏逻辑和输入,调整画面,再拍摄下一张,这样至少达到每秒30帧,才能达成一个动画的效果。
从逻辑上讲,游戏绘图采用的方式似乎应当慢于传统方式。实际上因为这种方式能够得到CPU/显卡以及很多新技术的帮助。很多绘图任务发出后,实际上是进入显卡完成运算的,这时候CPU已经在处理其它内容。这样并行计算的方式,再加上显卡更善于处理图形、图像相关的工作。最终这种方式效率才会高很多。
我们前面讲的科学绘图和海龟绘图,新版本的实现有很多是使用游戏绘图的方式,通过并行的方式完成计算。但因为用户编程接口的兼容性,所以至少从我们编程时所感受到的方式上,还是串行处理的。
坐标系
科技绘图(matplotlib):采用数学坐标系,同显示设备无关,通常原点在屏幕中心。绘图包会自动调整数学坐标系跟窗口分辨率的比例(窗口分辨率是可以在程序中设置的,只是前面的学习中我们基本使用了默认的设置),从而让显示效果最优。
海龟绘图(turtle):原点在窗口中心,跟数学坐标系方向相同,坐标是同显示设备分辨率相关的,但绘图的操作通常是用几何的方式,所以不用太担心显示设备本身的分辨率。
游戏绘图(pygame):原点在窗口左上角,x轴坐标向右侧增大,y轴坐标向下增大,最大值为屏幕分辨率。还有一些更底层的游戏绘图引擎,比如OpenGL会使用统一的1.0*1.0坐标系,然后在不同设备上映射成不同的分辨率。我们本讲的课程采用Pygame所使用的坐标体系。
颜色
在计算机中常用的颜色分类有这么几种:二值图:仅有黑白两色,比如字体库
灰度图:0-255,共256级灰度,比如黑白照片
伪彩色:0-255,共256种颜色,比如GIF动图、微信表情
真彩色:RGB红绿蓝三色图,每种颜色0-255,按二进制计算,也称为24位色
32位真彩色:RGBA四色,除了红绿蓝之外,A代表透明度,能表现更多的多种颜色互动、遮盖的效果
这些颜色格式Pygame都支持,但最新的游戏通常都已经采用32位真彩色的方式。
在游戏的显示过程中,如果不考虑透明度A的部分,所有颜色都是使用“三基色”来表达的,也就是红、绿、蓝,每个颜色分量可以的取值分为是0到255。0表示完全没有这个颜色,255表示这个颜色最强。当三个颜色都是0的时候显示为纯黑。当三个颜色都是255的时候,显示为纯白。
因为是三个颜色,所以通常的颜色都是使用三个值的“元组”的形式表达的。元组我们第九讲学过了。下面举一些例子,我们在程序中,预定义几个常用的颜色:#黑色BLACK =(0, 0, 0)#白色WHITE = (255, 255, 255)#红色RED = (255,0,0)#绿色GREEN =(0,255,0)#蓝色BLUE=(0, 0,255)
Pygame程序一般结构
上一讲和本讲开始我们都已经讲过,Pygame的主要工作模式是并行处理,其结构同传统的串行程序就必然有一些差别。这个差别并不大,很类似我们学习互联网编程时候的框架模板,即使不够理解,照抄下来用就可以。下面就是一个一般的结构:#此代码仅为架构示例,没有具体功能#作者:Andrew#引入扩展库import pygame
width=1280heigh=556color=32#pygame初始化pygame.display.init()#创建一个绘图平面,后面参数为设定的窗口分辨率及颜色screen = pygame.display.set_mode((width, heigh), 0, color)#声音系统初始化pygame.mixer.init()#1...其它自身初始化项目...#是否要退出标志requireQuit = False#程序主循环,在有退出申请之前一直循环while not requireQuit: #2...自己的绘图部分...
#处理所有事件
for event in pygame.event.get(): #用户从窗口菜单选择退出
if event.type == pygame.QUIT:
requireQuit=True
break
#用户是否有按键?
elif event.type == pygame.KEYUP: #为了可靠,只处理按键松开的动作
if event.key in [pygame.K_q,pygame.K_ESCAPE]: #用户按了q键
requireQuit=True
break
#3...其它事件处理...
#4...其它程序逻辑...#优雅的退出,释放各种资源pygame.mixer.quit()
pygame.display.quit()
上面的代码中,并不包含任何功能,只是一个模板。通常没有特殊需求的程序,只要编写其中的#1/#2/#3/#4部分的程序就可以。
为了程序更便于理解和阅读,还可以对上面的结构进一步的优化,比如把需要继续编程的部分函数化。当然函数化的时候要考虑到变量作用域,避免增加不必要的麻烦。
常用绘图功能
我们介绍几个常用的绘图功能,然后就可以代入到上面的模板代码中来实验了。
一般的几何图形绘制功能,都汇总在pygame.draw包中,比如:画圆:pygame.draw.circle
矩形:pygame.draw.rect
多边形:pygame.draw.polygon
画线:pygame.draw.line
画弧线:pygame.draw.arc
画矩形:pygame.draw.rect
正常情况下,pygame的显示是在一个窗口中显示的(也可以根据需要设置全屏),窗口可以设置一个标题来表示你当前做的工作,这个命令是:#设置窗口标题pygame.display.set_caption('Hello World!')
用于显示的窗口默认是没有颜色,也就是黑色,可以设置窗口的底色:#用白色填充窗口,既是设置窗口底色,也是把窗口清空,重新绘制下一帧#pygame绘图是像摄影师拍摄每一帧的照片,还记得吗?screen.fill(WHITE)
还有一些函数的功能,可以参考help(pygame)。help也可以查看某一个具体的子包,比如:help(pygame.draw)。下面我们通过程序示例代码来看看刚才讲的这些功能:#我们定义一个函数,来完成画面的绘制#避免过多的语句挤入到主循环中影响程序的结构def draw(screen):
#2...自己的绘图部分...
#用白色填充窗口
screen.fill(WHITE) #画多边形
pygame.draw.polygon(screen, GREEN, ((146, 0), (291, 106), (236, 277), (56, 277), (0, 106))) #画线
pygame.draw.line(screen, BLUE, (60, 60), (120, 60), 4)
pygame.draw.line(screen, BLUE, (120, 60), (60, 120))
pygame.draw.line(screen, BLUE, (60, 120), (120, 120), 4) #画圆
pygame.draw.circle(screen, BLUE, (300, 50), 20, 0) #椭圆
pygame.draw.ellipse(screen, RED, (300, 250, 40, 80), 1) #矩形
pygame.draw.rect(screen, RED, (200, 150, 100, 50)) #使用直接操作图形缓存的方法在右下角画四个点
#这个功能比较底层,除非需要很专业的操作一般用不到
pixObj = pygame.PixelArray(screen)
pixObj[480][380] = BLACK
pixObj[482][382] = BLACK
pixObj[484][384] = BLACK
pixObj[486][386] = BLACK
pixObj[488][388] = BLACK del pixObj #显示在屏幕上
pygame.display.update()
上面代码只列出了自己定义的绘图部分,其它部分需要融合到框架模板中去。完整的代码可以参考code2.py源文件。
下面的图片是绘制的效果:
程序运行之后,可以按q键退出程序,也可以从菜单选择Quit来退出。
老话题,想掌握学习的知识,只能多练习。
请在上面程序的基础,调整各项参数,增加或者减少绘图的指令,自己练练。看看谁绘制的画面最好看。
挑战
我们已经掌握了基本的绘图知识。可惜游戏没有这么简单,至少游戏需要是以动画的方式为基础,玩起来才会感觉到真实。
我们早已经说过,现代的游戏开发已经是一个团队配合的产物。不管想达成什么样的动画,一般都需要有美工专业人员完成原画的设计制作,提供成素材,随后才能由程序人员来完成让画面动起来的工作。
我们这里已经从网上下载了几个素材:
上面包含两个动画元素的素材,上面部分是一只小地鼠,仔细观察这四副图片,他们的脚在不同的位置。四张图片代表动画中的4帧,连续起来,就会出现小地鼠在跑的样子。
下面的箭比较简单,只需要一帧,箭的图片出现在屏幕不同的位置上,感觉起来就是箭飞到了那个位置。
如果你还记得第一讲的演示,你应当能看出来这些素材出自游戏Bunny。
下面我们编程序,来实现小地鼠从屏幕右侧快速跑到屏幕左侧的动画,和羽箭从屏幕左侧飞到右侧的动画。#使用pygame对图片处理的功能,载入图片到变量arrow = pygame.image.load("bullet.png")#地鼠因为包含四帧,我们使用列表格式badguy = [pygame.image.load("badguy.png"),
pygame.image.load("badguy2.png"),
pygame.image.load("badguy3.png"),
pygame.image.load("badguy4.png")]#动画动起来,需要一帧帧的变化,下面的变量用于指当前显示的第几帧badguyIndex = 0#定义x1/y1和x2/y2两组坐标,#分别用于表示羽箭和小地鼠在屏幕上的位置#坐标系还记得吧?左上角是0,y向下变大,x向右变大x1=0 #羽箭从左侧飞到右侧,开始x坐标是0,表示在左侧y1=heigh/3#y坐标,在窗口上面的1/3位置x2=width #小地鼠一开始在屏幕右侧y2=heigh/3*2#定义一个函数,用于计算向左移动时候下一个位置的坐标def moveLeft(x):
x -= dx if x
x += width return x#定义一个函数,用于计算向右移动时候下一个位置的坐标def moveRight(x):
return (x+dx) % width#绘制的函数def draw(screen):
screen.fill(WHITE) #白色填充窗口
screen.blit(arrow,(x1,y1)) #绘制羽箭
screen.blit(badguy[badguyIndex],(x2,y2)) #绘制地鼠
#显示在屏幕上
pygame.display.update()
... #4...其它程序逻辑...
#移动元素坐标位置的工作,应当放到“其它程序逻辑”中
#这样的方式使得程序逻辑,特别是绘图的逻辑干净易读
x2 = moveLeft(x2)
x1 = moveRight(x1)
badguyIndex = (badguyIndex+1) % 4 #地鼠下次使用下一帧
上面代码依然去掉了同前面重复的部分,完整的代码请参考code3.py程序。现在运行一下看看吧:
截图无法展示动态,你一定要亲自动手来试试,才能看到效果。关键点:屏幕绘制部分,根据坐标值,绘制指定的图片。
在程序逻辑运算的部分,计算下一帧画面的时候,小地鼠和羽箭在屏幕上的新位置。以及地鼠的动画图片下次绘制采用哪一帧图片。
练习时间修改上面程序的参数,让地鼠的速度加快一倍,而箭的速度保持不变
上一讲中的mp3播放器,请实现在播放器播放的时候,显示一张歌曲的封面图片
本讲小结本讲介绍了使用pygame绘制基本几何图形和绘制简单动画的方式
绘画、动画其实都不难,重要的是画面的设计,只要有了连续的图片,就可以用数组的方式来实现连续动画
对于一个规模越来越大的程序,想少出错、容易维护,就需要代码尽量规范、简洁、函数化
本课程至此就全部结束了。作为面对刚刚接触计算机软件编程的初学者课程,我们使用了15讲的篇幅,从Python的安装、命令行的互动计算开始,讲述了数学计算、程序逻辑控制、常用数据类型等基本Python编程的知识。接着又针对科技绘图、互联网编程、游戏编程等专业领域的应用做了讲解。时间所限,我们并没有能够特别深入对所有话题更进一步的学习。课程也没有对当前流行的面向对象编程做讲解,这些有待于学习者在对初级内容有了一段时间的熟悉和体验之后,继续深入学习。
希望各位同学能在当前学习的基础上,根据自己的爱好和自己的日常学习、生活需要。有选择的做进一步学习,让Python成为我们学习的好助手,生活的得力工具。
水平所限,课程内容难免疏漏、错误,敬请谅解并欢迎指正。
练习答案
请参考代码:mp3Player1.py连载正文结束,所有程序源码及练习答案将会整理后在下一期提供下载