zoukankan      html  css  js  c++  java
  • 从零开始学习PYTHON3讲义(十五)让画面动起来

    《从零开始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=1280
    heigh=556
    color=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源文件。
    下面的图片是绘制的效果:
    pygameDraw1
    程序运行之后,可以按q键退出程序,也可以从菜单选择Quit来退出。

    老话题,想掌握学习的知识,只能多练习。
    请在上面程序的基础,调整各项参数,增加或者减少绘图的指令,自己练练。看看谁绘制的画面最好看。


    挑战

    我们已经掌握了基本的绘图知识。可惜游戏没有这么简单,至少游戏需要是以动画的方式为基础,玩起来才会感觉到真实。

    我们早已经说过,现代的游戏开发已经是一个团队配合的产物。不管想达成什么样的动画,一般都需要有美工专业人员完成原画的设计制作,提供成素材,随后才能由程序人员来完成让画面动起来的工作。

    我们这里已经从网上下载了几个素材:
    pygameDraw2
    上面包含两个动画元素的素材,上面部分是一只小地鼠,仔细观察这四副图片,他们的脚在不同的位置。四张图片代表动画中的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 < 0:
            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程序。现在运行一下看看吧:
    pygameDraw3
    截图无法展示动态,你一定要亲自动手来试试,才能看到效果。关键点:

    1. 屏幕绘制部分,根据坐标值,绘制指定的图片。
    2. 在程序逻辑运算的部分,计算下一帧画面的时候,小地鼠和羽箭在屏幕上的新位置。以及地鼠的动画图片下次绘制采用哪一帧图片。

    练习时间

    1. 修改上面程序的参数,让地鼠的速度加快一倍,而箭的速度保持不变
    2. 上一讲中的mp3播放器,请实现在播放器播放的时候,显示一张歌曲的封面图片

    本讲小结

    • 本讲介绍了使用pygame绘制基本几何图形和绘制简单动画的方式
    • 绘画、动画其实都不难,重要的是画面的设计,只要有了连续的图片,就可以用数组的方式来实现连续动画
    • 对于一个规模越来越大的程序,想少出错、容易维护,就需要代码尽量规范、简洁、函数化

    本课程至此就全部结束了。作为面对刚刚接触计算机软件编程的初学者课程,我们使用了15讲的篇幅,从Python的安装、命令行的互动计算开始,讲述了数学计算、程序逻辑控制、常用数据类型等基本Python编程的知识。接着又针对科技绘图、互联网编程、游戏编程等专业领域的应用做了讲解。时间所限,我们并没有能够特别深入对所有话题更进一步的学习。课程也没有对当前流行的面向对象编程做讲解,这些有待于学习者在对初级内容有了一段时间的熟悉和体验之后,继续深入学习。
    希望各位同学能在当前学习的基础上,根据自己的爱好和自己的日常学习、生活需要。有选择的做进一步学习,让Python成为我们学习的好助手,生活的得力工具。
    水平所限,课程内容难免疏漏、错误,敬请谅解并欢迎指正。


    练习答案

    请参考代码:mp3Player1.py

    • 连载正文结束,所有程序源码及练习答案将会整理后在下一期提供下载。
  • 相关阅读:
    hdu 4002 Find the maximum
    hdu 2837 坑题。
    hdu 3123
    zoj Treasure Hunt IV
    hdu 2053 Switch Game 水题一枚,鉴定完毕
    poj 1430 Binary Stirling Numbers
    hdu 3037 Saving Beans
    hdu 3944 dp?
    南阳oj 求N!的二进制表示最低位的1的位置(从右向左数)。
    fzu 2171 防守阵地 II
  • 原文地址:https://www.cnblogs.com/andrewwang/p/10213811.html
Copyright © 2011-2022 走看看