用Python和Pygame写游戏-从入门到精通(6)

By | 2011/05/21

掌握了小小的像素,我们可以使用更加复杂一点的东西了,对,就是图像,无数的像素的集合~还记得上次我们为了生成的一张图片,花了无数时间,还好一般游戏不会在游戏的过程中动态生成图像,都是将画好的作为资源封装到游戏中。对2D游戏,图像可能就是一些背景、角色等,而3D游戏则往往是大量的贴图。

虽然是基础,这里还是要罗嗦一下,之前说的RBG图像,在游戏中我们往往使用RGBA图像,这个A是alpha,也就是表示透明度的部分,值也是0~255,0代表完全透明,255是完全不透明,而像100这样的数字,代表部分透明。你可以使用多种软件创建含有Alpha通道的图片,具体的网上查查吧……

这个世界上有很多存储图像的方式(也就是有很多图片格式),比如JPEG、PNG等,Pygmae都能很好的支持,具体支持的格式如下:

  • JPEG(Join Photograhpic Exper Group),极为常用,一般后缀名为.jpg或者.jpeg。数码相机、网上的图片基本都是这种格式。这是一种有损压缩方式,尽管对图片质量有些损坏,但对于减小文件尺寸非常棒。优点很多只是不支持透明。
  • PNG(Portable Network Graphics)将会大行其道的一种格式,支持透明,无损压缩。对于网页设计,软件界面设计等等都是非常棒的选择!
  • GIF 网上使用的很多,支持透明和动画,只是只能有256种颜色,软件和游戏中使用很少
  • BMP Windows上的标准图像格式,无压缩,质量很高但尺寸很大,一般不使用
  • PCX
  • TGA
  • TIF
  • LBM, PBM
  • XPM

使用Surface对象

对于Pygame而已,加载图片就是pygame.image.load,给它一个文件名然后就还给你一个surface对象。尽管读入的图像格式各不相同,surface对象隐藏了这些不同。你可以对一个Surface对象进行涂画、变形、复制等各种操作。事实上,屏幕也只是一个surface,pygame.display.set_mode就返回了一个屏幕surface对象。

创建Surfaces对象

一种方法就是刚刚说的pygame.image.load,这个surface有着和图像相同的尺寸和颜色;另外一种方法是指定尺寸创建一个空的surface,下面的语句创建一个256×256像素的surface:

bland_surface = pygame.Surface((256, 256))

如果不指定尺寸,那么就创建一个和屏幕一样大小的。

你还有两个参数可选,第一个是flags:

  • HWSURFACE – 类似于前面讲的,更快!不过最好不设定,Pygmae可以自己优化。
  • SRCALPHA – 有Alpha通道的surface,如果你需要透明,就要这个选项。这个选项的使用需要第二个参数为32~

第二个参数是depth,和pygame.display.set_mode中的一样,你可以不设定,Pygame会自动设的和display一致。不过如果你使用了SRCALPHA,还是设为32吧:

bland_alpha_surface = pygame.Surface((256, 256), flags=SRCALPHA, depth=32)

转换Surfaces

通常你不用在意surface里的具体内容,不过也许需要把这些surface转换一下以获得更高的性能,还记得一开始的程序中的两句话吗:

background = pygame.image.load(background_image_filename).convert()
mouse_cursor = pygame.image.load(mouse_image_filename).convert_alpha()

第一句是普通的转换,相同于display;第二句是带alpha通道的转换。如果你给convert或者conver_alpha一个surface对象作为参数,那么这个会被作为目标来转换。

矩形对象(Rectangle Objects)

一般来说在制定一个区域的时候,矩形是必须的,比如在屏幕的一部分画东西。在pygame中矩形对象极为常用,它的指定方法可以用一个四元素的元组,或者两个二元素的元组,前两个数为左上坐标,后两位为右下坐标。

Pygame中有一个Rect类,用来存储和处理矩形对象(包含在pygame.locals中,所以如果你写了from pygame.locals import *就可以直接用这个对象了),比如:

my_rect1 = (100, 100, 200, 150)
my_rect2 = ((100, 100), (200, 150))
#上两种为基础方法,表示的矩形也是一样的
my_rect3 = Rect(100, 100, 200, 150)
my_rect4 = Rect((100, 100), (200, 150))

一旦有了Rect对象,我们就可以对其做很多操作,比如调整位置和大小,判断一个点是否在其中等等。以后会慢慢接触到,求知欲旺盛的可以在http://www.pygame.org/docs/ref/rect.html中找到Rect的详细信息。

剪裁(Clipping)

通常游戏的时候你只需要绘制屏幕的一部分。比如魔兽上面是菜单,下面是操作面板,中间的小兵和英雄打的不可开交时候,上下的部分也是保持相对不动的。为了实现这一点,surface就有了一种叫裁剪区域(clipping area)的东西,也是一个矩形,定义了哪部分会被绘制,也就是说一旦定义了这个区域,那么只有这个区域内的像素会被修改,其他的位置保持不变,默认情况下,这个区域是所有地方。我们可以使用set_clip来设定,使用get_clip来获得这个区域。

下面几句话演示了如何使用这个技术来绘制不同的区域:

screen.set_clip(0, 400, 200, 600)
draw_map()
#在左下角画地图
screen.set_clip(0, 0, 800, 60)
draw_panel()
#在上方画菜单面板

子表面(Subsurfaces)

Subsurface就是在一个Surface中再提取一个Surface,记住当你往Subsurface上画东西的时候,同时也向父表面上操作。这可以用来绘制图形文字,尽管pygame.font可以用来写很不错的字,但只是单色,游戏可能需要更丰富的表现,这时候你可以把每个字母(中文的话有些吃力了)各自做成一个图片,不过更好的方法是在一张图片上画满所有的字母。把整张图读入,然后再用Subsurface把字母一个一个“抠”出来,就像下面这样:

my_font_image = Pygame.load("font.png")
letters = []
letters["a"] = my_font_image.subsurface((0,0), (80,80))
letters["b"] = my_font_image.subsurface((80,0), (80,80))

填充Surface

填充有时候可以作为一种清屏的操作,把整个surface填上一种颜色:

screen.fill((0, 0, 0))

同样可以提供一个矩形来制定填充哪个部分(这也可以作为一种画矩形的方法)。

设置Surface的像素

我们能对Surface做的最基本的操作就是设置一个像素的色彩了,虽然我们基本不会这么做,但还是要了解。set_at方法可以做到这一点,它的参数是坐标和颜色,下面的小脚本会随机的在屏幕上画点:

import pygame
from pygame.locals import *
from sys import exit
from random import randint

pygame.init()
screen = pygame.display.set_mode((640, 480), 0, 32)

while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            exit()

    rand_col = (randint(0, 255), randint(0, 255), randint(0, 255))
    #screen.lock()    #很快你就会知道这两句lock和unlock的意思了
    for _ in xrange(100):
        rand_pos = (randint(0, 639), randint(0, 479))
        screen.set_at(rand_pos, rand_col)
    #screen.unlock()

    pygame.display.update()

获得Surface上的像素

set_at的兄弟get_at可以帮我们做这件事,它接受一个坐标返回指定坐标点上的颜色。不过记住get_at在对hardware surface操作的时候很慢,而全屏的时候总是hardware的,所以慎用这个方法!

锁定Surface

当Pygame往surface上画东西的时候,首先会把surface锁住,以保证不会有其它的进程来干扰,画完之后再解锁。锁和解锁时自动发生的,所以有时候可能不那么有效率,比如上面的例子,每次画100个点,那么就得锁解锁100次,现在我们把两句注释去掉,再执行看看是不是更快了(好吧,其实我没感觉出来,因为现在的机器性能都不错,这么点的差异还不太感觉的出来。不过请相信我~复杂的情况下会影响效率的)?

当你手动加锁的时候,一定不要忘记解锁,否则pygame有可能会失去响应。虽然上面的例子可能没问题,但是隐含的bug是我们一定要避免的事情。

Blitting

blit的的中文翻译给人摸不着头脑的感觉,可以译为位块传送(bit block transfer),其意义是将一个平面的一部分或全部图象整块从这个平面复制到另一个平面,下面还是直接使用英文。

blit是对表面做的最多的操作,我们在前面的程序中已经多次用到,不多说了;blit的还有一种用法,往往用在对动画的表现上,比如下例通过对frame_no的值的改变,我们可以把不同的帧(同一副图的不同位置)画到屏幕上:

screen.blit(ogre, (300, 200), (100 * frame_no, 0, 100, 100))

这次东西真是不少,打完脖子都酸了……

很多以前的程序中已经出现,看完这部分才能算是真正了解。图像是游戏至关重要的一部分,值得多花时间,下一次讲解绘制图形~

>> 用Python和Pygame写游戏-从入门到精通(7)

41 thoughts on “用Python和Pygame写游戏-从入门到精通(6)

  1. abc

    最后这个不懂,要怎样实现
    screen.blit(ogre, (300, 200), (100 * frame_no, 0, 100, 100))

    Reply
    1. lyly加油呀

      (100 * frame_no, 0, 100, 100)表示一个矩形区域,(100 * frame_no, 0)是该矩形左上角的横纵坐标,(100, 100)表示矩形的长和宽。(300,200)是screen上的一个坐标。这行命令表示将ogre上指定的矩形区域(100 * frame_no, 0, 100, 100)提取并显示到screen上指定的坐标位置(300,200)上

      Reply
  2. netwolf103

    “使用get_clip来或者这个区域。” 文字错误
    “使用get_clip来获得这个区域。

    Reply
  3. wei

    创建对象这里是pygame.Surface()为什么上一页的是pygame.surface.Surface()?

    Reply
    1. xishui Post author

      观察入微!其实它们是等价的,正规的Surface类是pygame.surface模块中的,但是pygame在顶层import了整个pygame.surface,所以可以直接使用,估计是出于使用率上的考虑,这样会比较简单。

      Reply
  4. 壮壮

    楼主太好了,以前看SDL看的是C语言的,很郁闷的,一个是官方推荐的指南,一个是中国人写的教程.光搭建环境的就烦死了,看了使用Pygame写的越看越想看.

    Reply
  5. wuxie

    刚刚看到这里,python-pygame 以及博主的教程,真心让我体会到这门语言的使用和开发就像拿中文说事。通俗易懂,使用方便,代码简洁。
    十分敬佩博主这类牺牲自己休息时间,写东西和我等分享的无私贡献者。

    Reply
  6. windsound

    问一下 ” _ ” 是什么意思啊?
    for _ in xrange(100):

    Reply
  7. aha

    求解bilt部分的详解,我有一张人物的多种动作的集合图片,希望能显示图像的一部分,然后流畅切换形成人物在走动的效果。。。 = =

    Reply
  8. xumaomao

    “在pygame中矩形对象极为常用,它的指定方法可以用一个四元素的元组,或者两个二元素的元组,前两个数为左上坐标,后两位为右下坐标。”这句是不是有问题:第二组tuple是矩形的宽和高,而不是右下角坐标?

    Reply
    1. 韩建斌

      你说的没错,第二组的确是宽和高,第一组是矩形左上点坐标

      Reply
  9. xumaomao

    我想用surface.set_clip()来指定某个区域内的透明度:
    bg.set_clip(rect)
    bg.set_alpha(0)
    但是好像还是整个surface同时在改变啊,巨郁闷,博主有没有好办法?
    另外好像没法直接从一个矩形surface上减去一块,要实现挖去一个洞洞就要分别画周围剩下的8个矩形,实在是费劲。

    Reply
    1. xishui Post author

      好像不行诶,也许你可以换一个方法来实现你的效果?比如贴一个半透明的PNG上去……

      Reply
  10. xumaomao

    @xishui:我想要模拟那种黑暗之中只有身边一圈可见度的效果,所以这块半透明的地方需要随人物移动而不能直接贴一整张png图上去,因为还要露出背景上的图像出来。最后还是用最笨的方法,在surface上掏个洞,把剩下周围四个subsurface一个个画出来。。。pygame.transform倒是有个chop()函数是切除一个矩形的,不过那个把剩下的区域又拼成个矩形了。要是想掏个圆出来我就彻底没招了。

    Reply
    1. xishui Post author

      不知是不是我误会了你的意思,我感觉这个好像很简单啊,试一下这个,用第一章的例子,用这张图(http://eyehere.net/wp-content/uploads/2013/01/alpha_mask.png)代替鼠标的PNG图片,看看是不是你需要的效果?当然实际情况不需要用这么大的图片,只是示例:)

      Reply
  11. xumaomao

    @xishui 我又找到一些别的办法来“挖洞”。pygame里有个surfarray模块,可以把一个surface转成一个numpy格式的矩阵,然后可以对这个矩阵做处理来实现改变局部透明度,比如局部设成一种颜色然后用surface.set_colorkey(color)来挖洞,或者用surfarray.pixels_alpha()获得alpha通道来直接修改透明度挖洞。用这种办法不仅可以挖矩形的洞圆形的洞,而且可以挖任何不规则形状的洞洞,只要先用绘图软件把形状画出来,转成矩阵,然后替换掉另一个矩阵的一块地方就可以了。
    关于surface的alpha部分我都想写一篇东西总结一下了,其中还有些细枝末节的东西很容易搞晕人。
    :

    Reply
    1. xishui Post author

      其实,这个方法有点复杂…… 只要定义一个纯黑的Surface,准备好PNG,使用Blit的各种加减异或等参数,可以完全实现任意形状的挖去,numpy多复杂啊:)

      Reply
  12. Pingback: [师傅领进门]Python大作业(二)Pygame简介 | Yarkcy

  13. xumaomao

    我又回来了。:)
    在子表面那部分的代码里有个错误哦:
    letters=[]
    因该是
    letters={}
    吧?
    书中的错误。

    Reply
  14. xumaomao

    @windsound: 好像可以用pygame.trasform.scale(surface,(width,height)) 来拉伸。(width,height) 是新尺寸。

    Reply
  15. Mosaic

    您好,关于矩形对象那里的四个参数,前两个是左上角坐标,后两个是长和宽吧,建议修改一下,今天搞了很久才知道是这两个参数的问题。

    Reply
  16. 龙妈

    博主啊,怎么让pygame支持Tmx地图呢?

    Reply
    1. jasonos

      pygame的地图方面就用Tiled(做地图) + pytmx + pyscroll(加载地图并将其转化为suface)

      Reply
  17. 李彦

    请教如何让窗口背景透明呢 博主大神

    Reply
    1. 李彦

      就是窗口都是透明的 ,我做出来的怎么是黑色的

      Reply
      1. xishui Post author

        Pygame使用的SDL,并不支持透明窗口(至少在Window上不支持),有一个“假冒”的透明方法,就是程序启动前你截个屏,然后算准位置把截屏当作窗口的背景……

        Reply
  18. Pingback: 用Python和Pygame写游戏-从入门到精通(6)-演道网

    1. 114514

      加锁,一般情况下会自动执行,但是按这个代码这样频率的话会花费很多时间。

      Reply
  19. anithgie

    最后一个代码:screen.blit(ogre, (300, 200), (100 * frame_no, 0, 100, 100))
    应该是:screen.blit(ogre, (300, 200), (100 * frame_no, 0, 100 * (frame_no + 1), 100))吧?
    假设frame_no是从0开始的

    Reply
    1. Null

      应该是前两个参数是右上角的点,后两个参数是长和宽吧,我并没有进行实验,如果是你说的那样,那就是博主的笔误了

      Reply
  20. Pingback: Pygame some good tutorials usually collect... - Code Blog Bt

发表评论

您的电子邮箱地址不会被公开。