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

By | 2011/04/14

上次我们试着写了一个最简单的Pygame程序并且解释了一个大概的框架,这次就Pygame中也是游戏中最关键(……好吧,也许并不是关键,但绝对是至关重要的一项)的事件来展开。

此图为一个用Pygame开发的游戏,或许有些简陋,但是只要你有爱,什么都能出来!

理解事件

事件是什么,其实从名称来看我们就能想到些什么,而且你所想到的基本就是事件的真正意思了。我们上一个程序,会一直运行下去,直到你关闭窗口而产生了一个QUIT事件,Pygame会接受用户的各种操作(比如按键盘,移动鼠标等)产生事件。事件随时可能发生,而且量也可能会很大,Pygame的做法是把一系列的事件存放一个队列里,逐个的处理。

事件检索

上个程序中,使用了pygame.event.get()来处理所有的事件,这好像打开大门让所有的人进入。如果我们使用pygame.event.wait(),Pygame就会等到发生一个事件才继续下去,就好像你在门的猫眼上盯着外面一样,来一个放一个……一般游戏中不太实用,因为游戏往往是需要动态运作的;而另外一个方法pygame.event.poll()就好一些,一旦调用,它会根据现在的情形返回一个真实的事件,或者一个“什么都没有”。下表是一个常用事件集:

事件 产生途径 参数
QUIT 用户按下关闭按钮 none
ATIVEEVENT Pygame被激活或者隐藏 gain, state
KEYDOWN 键盘被按下 unicode, key, mod
KEYUP 键盘被放开 key, mod
MOUSEMOTION 鼠标移动 pos, rel, buttons
MOUSEBUTTONDOWN 鼠标按下 pos, button
MOUSEBUTTONUP 鼠标放开 pos, button
JOYAXISMOTION 游戏手柄(Joystick or pad)移动 joy, axis, value
JOYBALLMOTION 游戏球(Joy ball)?移动 joy, axis, value
JOYHATMOTION 游戏手柄(Joystick)?移动 joy, axis, value
JOYBUTTONDOWN 游戏手柄按下 joy, button
JOYBUTTONUP 游戏手柄放开 joy, button
VIDEORESIZE Pygame窗口缩放 size, w, h
VIDEOEXPOSE Pygame窗口部分公开(expose)? none
USEREVENT 触发了一个用户事件 code

如果你想把这个表现在就背下来,当然我不会阻止你,但实在不是个好主意,在实际的使用中,自然而然的就会记住。我们先来写一个可以把所有方法输出的程序,它的结果是这样的。

我们这里使用了wait(),因为这个程序在有事件发生的时候动弹就可以了。还用了font模块来显示文字(后面会讲的),下面是源代码:

小贴士:
书上说,如果你把填充色的(0, 0, 0)改为(0, 255, 0),效果会想黑客帝国的字幕雨一样,我得说,实际试一下并不太像……不过以后你完全可以写一个以假乱真甚至更酷的!

这个程序在你移动鼠标的时候产生了海量的信息,让我们知道了Pygame是多么的繁忙……我们第一个程序那样是调用pygame.mouse.get_pos()来得到当前鼠标的位置,而现在利用事件可以直接获得!

处理鼠标事件

MOUSEMOTION事件会在鼠标动作的时候发生,它有三个参数:

  • buttons – 一个含有三个数字的元组,三个值分别代表左键、中键和右键,1就是按下了。
  • pos – 就是位置了……
  • rel – 代表了现在距离上次产生鼠标事件时的距离

和MOUSEMOTION类似的,我们还有MOUSEBUTTONDOWNMOUSEBUTTONUP两个事件,看名字就明白是什么意思了。很多时候,你只需要知道鼠标点下就可以了,那就可以不用上面那个比较强大(也比较复杂)的事件了。它们的参数为:

  • button – 看清楚少了个s,这个值代表了哪个按键被操作
  • pos – 和上面一样

处理键盘事件

键盘和游戏手柄的事件比较类似,为KEYDOWNKEYUP,下面有一个例子来演示使用方向键移动一些东西。

当我们运行这个程序的时候,按下方向键就可以把背景图移动,但是等等!为什么我只能按一下动一下啊……太不好试了吧?!用脚掌考虑下就应该按着就一直动下去才是啊!?Pygame这么垃圾么……

哦,真是抱歉上面的代码有点小bug,但是真的很小,你都不需要更改代码本身,只要改一下缩进就可以了,你可以发现么?Python本身是缩进编排来表现层次,有些时候可能会出现一点小麻烦,要我们自己注意才可以。

KEYDOWN和KEYUP的参数描述如下:

  • key – 按下或者放开的键值,是一个数字,估计地球上很少有人可以记住,所以Pygame中你可以使用K_xxx来表示,比如字母a就是K_a,还有K_SPACEK_RETURN等。
  • mod – 包含了组合键信息,如果mod & KMOD_CTRL是真的话,表示用户同时按下了Ctrl键。类似的还有KMOD_SHIFTKMOD_ALT
  • unicode – 代表了按下键的Unicode值,这个有点不好理解,真正说清楚又太麻烦,游戏中也不太常用,说明暂时省略,什么时候需要再讲吧。

事件过滤

并不是所有的事件都需要处理的,就好像不是所有登门造访的人都是我们欢迎的一样。比如,俄罗斯方块就无视你的鼠标,而在游戏场景切换的时候,你按什么都是徒劳的。我们应该有一个方法来过滤掉一些我们不感兴趣的事件(当然我们可以不处理这些没兴趣的事件,但最好的方法还是让它们根本不进入我们的事件队列,就好像在门上贴着“XXX免进”一样),我们使用pygame.event.set_blocked(事件名)来完成。如果有好多事件需要过滤,可以传递一个列表,比如pygame.event.set_blocked([KEYDOWN, KEYUP]),如果你设置参数None,那么所有的事件有被打开了。与之相对的,我们使用pygame.event.set_allowed()来设定允许的事件。

产生事件

通常玩家做什么,Pygame就产生对应的事件就可以了,不过有的时候我们需要模拟出一些事件来,比如录像回放的时候,我们就要把用户的操作再现一遍。

为了产生事件,必须先造一个出来,然后再传递它:

你甚至可以产生一个完全自定义的全新事件,有些高级的话题,暂时不详细说,仅用代码演示一下:

这次的内容很多,又很重要,一遍看下来云里雾里或者看的时候明白看完了全忘了什么的估计很多,慢慢学习吧~~多看看动手写写,其实都很简单。

下次讲解显示的部分。

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

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

  1. 77

    同意上面,后5行往左缩进两格。
    要传递最终结果,而不是每次结果

    Reply
  2. 77

    你好,在处理键盘事件的例子中,for event in pygame.event.get():语句何种情况下退出此循环?
    后5行缩进了后,应该是执行完for再执行这后5句,为何当我按下右键感觉for的语句和后五句是同时执行的呢?难道是event机制?不懂啊

    Reply
  3. 77

    @77: 再补充一下,按住右键不放,是一个事件还是多个啊。。。

    Reply
  4. 77

    @77: 在for循环中,print event 发现按住右键,是多个事件。。。

    Reply
  5. 77

    抱歉打扰了,一个事件的话那我就懂了。while是为了让一个事件产生的结果,不断地影响后面的语句。当松开按钮就给值为0,并不断地继续影响后面的结果。。。

    Reply
  6. xishui Post author

    @77: 自问自答的可爱77,congratulation!很多时候,把问题前后调查一下,答案也就呼之欲出了。

    Reply
    1. 初学者

      能教我这个愚笨的人怎样在windows7上安装livewires模块吗?我点了里面的setup.py一点用也没,大师请教教

      Reply
      1. water

        setup.py 不是点击的啊。。。。。。。你百度怎么安装setup.py。。。

        Reply
  7. wazedix

    试了下这个上下左右移动,如果按键交换太快,就会导致错乱失效,而且不支持同时按下几个方向键,于是我改进了下:

    x, y = 320, 240
    move = {K_LEFT:0, K_RIGHT:0, K_UP:0, K_DOWN:0}
    
    
    while True:    
        for event in pygame.event.get():
            if event.type == QUIT:
               exit()
            if event.type == KEYDOWN:
                if event.key in move:
                    move[event.key] = 1
            elif event.type == KEYUP:
                if event.key in move:
                    move[event.key] = 0
        x -= move[K_LEFT]
        x += move[K_RIGHT]
        y -= move[K_UP]
        y += move[K_DOWN]
        screen.fill((0,0,0))
        screen.blit(picture, (x,y))
        pygame.display.update()
    Reply
    1. wayhome

      赞!不过有个小瑕疵:同时松开两个键时图片会往其中一个方向移动一像素。始终不知道为什么!

      Reply
      1. GYLheihei

        不可能是同时的,总有一个先放,这时后放的移动

        Reply
    2. 珏七

      赞 不过我好像遇到了一个问题 ,按下键移动以后,图像动的太快了 ,解决这个是需要控制帧数吗?

      Reply
    3. 1

      你的理解高啊,就是先统计好上下左右都动多少,然后统一的一动一次就到最后,所以可以识别多个按钮一起按

      Reply
    4. 橙みこ

      完全没有遇到这个问题,而且是支持同时按下几个方向键的。

      Reply
  8. xishui Post author

    @wazedix: Great! 我帮您把空格加回去了,HTML总会把多个空格合并为一个,除非把代码放在<pre>…</pre>里面。

    Reply
  9. wei

    为什么x+= move_x
    y+= move_y放在for循环里是移动一次啊?不是一直循环吗?

    Reply
  10. wei

    这个我也理解了,原来是本来没理解透event.get()…

    Reply
  11. xishui Post author

    @wei: 每当想帅气的回答疑问的时候,却突然发现你们自己搞明白了,就会在欣慰感叹之余又有了一些莫名的落寞感……

    Reply
  12. rabbitfan

    大神老师啊,我在定义一个新的函数
    def gameover(root,screen):
    root.close()
    screen.exit()

    这样的话 ,执行这个函数时,pygame的窗口“screen”会关闭么?如果不行,该怎么办呢?

    Reply
  13. Yo

    为何啊 还是不能理解 为什么只能移动一下 get不是提取多个event么

    Reply
  14. Yo

    @Yo:
    哈哈哈理解了 原来一直按着键算一个事件么
    之前以为一直按着键算一直都是那个事件

    Reply
  15. KidSc

    @wazedix:
    同时按住左右键或者上下键,你的方法似乎更好一些~~

    Reply
  16. fatfatface

    我有个小问题
    第二个程式
    按着上下左右其中一颗键后
    移动滑鼠
    图也会跟着动,为什么会这样?

    Reply
    1. wayhome

      虽然不知道你能不能看到,但我还是想说一说:
      我也遇到了这个情况,也解决了,原来我把最外if 语句的elif部分忽略了,不知你是否是这种情况。这个疏忽会导致另一个问题,不知你有没有发现,当key up的时候背景也会同方向移动一个像素。我猜想光标移动一个像素点同key_up 都是一种最。。。基本的事件,不知道说的准确不,但它们确实导致同一种现象,真正原理我却不知道。

      Reply
      1. wayhome

        我知道了:如果if语句不以elif结束,pygame.event.get()接收到key_up(我们松键),或者任何事件(如光标移动一个像素),x和y都会再运算一次。

        Reply
  17. MaximusZhou

    @wazedix:,@xishui: 这样做,还是有一点问题,同时按下左右键,然后再按向上方向的键,图并不会向上移,但若同时按下左右键,然后再按向下方向的键,则图会向下移,请问这是怎么回事

    Reply
  18. 1111

    Traceback (most recent call last):
    File “C:Documents and Settings小电脑桌面用Python和Pygame写游戏-从入门到精通2moveImg.py”, line 17, in
    exit()
    SystemExit

    在按下关闭按钮的时候总是报这个错误
    并且卡死,最后窗口是强制关闭的,我想问问是不是在python2.7.2里退出函数有修改?

    Reply
  19. 月符

    @1111: 调用exit()先调用pygame.exit()就不会卡死了。SystemExit是正常现象,exit()就是通过SystemExit异常终止解释器进程的,文档上有说

    Reply
    1. Geegle

      同意,第一个例子也是这样,关闭按钮时就死了,不知道为什么,然后看到别人有用 pygame.quit() 时就试了一下,

      Reply
  20. Mr Li

    我复制你写的源码来执行
    >>> while True:
    event=pygame.event.wait()
    event_text.append(str(event))
    event_text=event_text[-SCREEN_SIZE[1]/font_height:]
    if event.type==QUIT:
    exit()
    screen.fill((255,255,255))
    y=SCREEN_SIZE[1]-font_height
    for text in reversed(event_text):
    screen.blit(font.render(text,True,(0,0,0)),(0,y))
    y-=font_height
    pygame.display.update()

    我执行后IDLE报出:
    Traceback (most recent call last):
    File “”, line 4, in
    event_text=event_text[-SCREEN_SIZE[1]/font_height:]
    TypeError: slice indices must be integers or None or have an __index__ method
    >>>

    我用windows命令提示符也报这个错,我用的是python 3.2.2,请帮忙查一下错在哪里。谢谢!

    Reply
    1. xishui Post author

      请使用2.X的Python来执行。。。

      Reply
      1. 幽浮

        如果已经是3.3了,那应该怎样改呢?

        Reply
    2. heloo

      event_text是个LIST,LIST后的【】里要是整型,所以,在【】里加个int()进行转化就行了

      Reply
        1. cncser

          把切片event_text[-SCREEN_SIZE[1]/font_height:]中的“/”用“//”替换即可。

          Reply
          1. Chris.Y

            我也遇到了同样的问题,感谢你的回答,但是为什么会出现这样的情况呢?是python版本的问题吗?我是python新手,刚入门就要学习pygame。。

  21. cfy

    我现在才开始学习,不知道编者是否还在,请多多指教呀

    Reply
  22. hcl

    学渣的路过,有木有具体的关于这些method的用法,里面有哪些可以调用,怎么调用,有什么作用,我目前都不清楚。理解初学者,求具体教程。

    Reply
  23. xumaomao

    我感觉把最后5行缩进之后,可以做按下方向键进行多次移动的操作,但这种操作貌似与运行程序的电脑速度有关:如果机子速度快,while循环进行的很密集,在按下按键的那段时间里得到的key_down events很多,移动的也就多,反之亦然。

    Reply
  24. 康慨

    我觉得那个if event.type == QUIT那里,在sys.exit()之前还要添加pygame.quit(),否则在我的电脑上每次运行都报错…

    Reply
  25. fromjupiter

    第二段例程琢磨了许久,大概了解了,有些问题:
    1,似乎event.get()中只读取事件,不会消除已读取的事件?那有什么method可以实现消除已读取事件吗?
    2,同时按(左/右)和(上/下)可以实现斜向移动,但是为什么同时按上下或者同时按左右 不会像想象中的一样固定不动?
    我的想法是: 事件中同时存在UP和DOWN,那for循环结束后move_y应该为0啊?
    请指教。
    3,如果想实现那种“长按方向键时,移动速度逐渐加快”,应该怎么做?我的想法是还要设个计时器来记录时间距离,不知道event对象里有发生时间这个属性么?

    Reply
  26. scx0237

    不知道哪缩进,好像怎么按都是event
    出发一次就执行一次呀~~~
    自己好笨 求解答

    Reply
  27. scx0237

    pygame.key.set_repeat(200, 2)
    加到第十行

    Reply
  28. 叶子

    @Mr Li 把-SCREEN_SIZE[1]/font_height:改成-SCREEN_SIZE[1]//font_height:就可以了
    请问,为什么改了缩进后就可以一直移动了呢,按下方向键不是依旧是一个事件吗,难道和for有关系吗?

    Reply
  29. lxr1010@gmail.com

    @Mr Li: 在2.X里/得到的是int,在3.X里边是除法,需要来个int()

    Reply
  30. wanghuixd

    我运行出现错误: python2.7 pygame1.9 win32

    Traceback (most recent call last):
    File “E:/Python27/pygame_example/game3.py”, line 20, in
    print event.event_name(KEYDOWN)
    AttributeError: event member not defined

    Reply
  31. Pingback: pygame系列_游戏中的事件 - Python教程 - 开发者

  32. cobolMao

    评论的貌似有些晚。呵呵,11年接触的PYTHON,至今一直QQ未改名字,目前在银行工作,用的COBOL。
    但依然没有减退我对PYTHON的热爱,特地最近学习这个语言。谢谢您热心的分享,对于后来之人,这些是您成功的宝贵经验与成果。再次谢谢!

    Reply
  33. Pingback: pygame系列_游戏中的事件 - Python教程 - 开发者

  34. GYLheihei

    我觉得按下移动 有时候太快了,可以加个延时函数 不知道行不
    while (True):
    time+=1
    if not time%10000 == 0:
    continue

    Reply
  35. GYLheihei

    我试了一下 的确是wazedix的程序号 = =

    Reply
  36. shawn

    event_text = event_text[-SCREEN_SIZE[1]/font_height:]
    这句话要改成
    event_text = event_text[int(-SCREEN_SIZE[1]/font_height):] 才行。。不然类型不对。无法运行。。

    Reply
  37. happy球

    video system not initialized是什么情况啊?

    Reply
  38. Mr.chen

    为什么一直按着键图片会加快速度?

    Reply
  39. NPC

    关于缩进:最后五行语句都向前缩进一个TAB的距离

    Reply
  40. newraina

    试了一下,不用form sys import exit 也能正常退出

    Reply
  41. kroos

    太悲惨了,游戏玩多的后遗症,键盘移动的时候图片一直移动不了,纠结了半天,最后发现我一直按的是wasd,难怪不能移动,oh my god!

    Reply
  42. databatman

    总算搞懂为什么后五行缩进后会持续动了。。。就是当按下一个键不放后,event.get里面就只有一个事件,这时候执行完for之后会开始画图,画图完因为while的原因本来还要再执行for循环的,但是这时候的for里面的event.get里是没有事件的,所以for被跳过了,直接又开始执行后面的后五行,所以就产生了不断前进的效果。注:同时按下两个键的时候因为for里将这两个键的移动设置为1了,所以后五行会不断的按照这两个值来移动,直到key被放下,重新进入for,更新。

    Reply
  43. jackerb

    闲来无聊,发现有pygame这么个东西,拿来看看,发现博主的地盘,就跟着博主学学,看到这句话:如果你把填充色的(0, 0, 0)改为(0, 255, 0),效果会想黑客帝国的字幕雨一样。
    其实是真的,很像。这是我修改了一下的代码

    import pygame
    from pygame.locals import *
    from sys import exit
    import random
    
    pygame.init()
    SCREEN_SIZE = (640, 480)
    
    screen = pygame.display.set_mode(SCREEN_SIZE, 0, 32)
    font = pygame.font.SysFont("arial", 18)
    font_height = font.get_linesize()
    event_text = []
    
    while True:
    	event = pygame.event.wait()
    	event_text = str(event)
    	
    	#event_text = event_text[-30:]
    		
    	if event.type == QUIT:
    		exit()
    		
    	screen.fill((0, 0, 0))
    	
    	y = SCREEN_SIZE[1] - font_height
    	
    	for text in event_text[::-1]:
    		if text.isalpha():
    		  x = random.randint(0, SCREEN_SIZE[0])
    		  screen.blit(font.render(text, True, (0, 255, 0)), (x, y))
    		  y -= font_height
    		
    	pygame.display.update()
    
    Reply
    1. 张旭帆

      我很喜欢你的这段程序,希望可以一起交流,我还是在校学生,可以的话加我qq注明在这个网站上的朋友就行了516259684,谢谢啦,渴望学习

      Reply
  44. wzzzx

    您好,我想请问下关于那个图片不断的动的问题.,按照我的理解,按下是一个事件,所以一直在for循环里,当松开的时候,又是一个事件,这时候就没有事件了,,所以跳出for循环,执行后五行.但是,图片要动起来,得松开之后才能动啊,为什么会一边按一边动??

    Reply
    1. xishui Post author

      这个动并不是靠事件驱动,而是在现有位置上不断加上一个偏移量,这个偏移量是由事件除非设置的,只要没有新的有效事件,这个偏移量就会不停作用在现有位置上。

      Reply
  45. John

    有个问题想请教一下,我改了后五行的缩进,
    pygame.event.get()是得到所有的事件,举个例子,假如我就是按一下向上键(up),再松开,应该是两个时间,也就是说pygame.event.get()应该是[‘keyup’,’keydown’]这两个值,然后for循环开始生效,依次去判断event.类型,没问题,但是我们在for循环之上不是使用了一个while true:这个无限循环吗,我的意识也就是说执行完第一遍for循环,不是还应该执行第二遍for循环吗,直到执行到无数遍,一直循环下去,结果也就是我按一下然后再松开,图片也应该一直动

    Reply
  46. John

    查了python有关pygame的官网才懂,http://www.pygame.org/docs/ref/event.html#pygame.event.get,
    其实get.event()方法最后是要将序列中的message清空的

    Reply
  47. Y

    加了一句,以免移动到窗口显示以外去移动不回来了。
    if event.key == K_SPACE:
    x,y,move_x,move_y = 0,0,0,0

    Reply
  48. x=x+1

    好吧,我看得不够细心,没有看到这个缩进错误,毕竟不是一行一行看着打的,自己想当然就打了(按照第一课的逻辑,执行代码不会放在事件检测的for循环内)……另外,if…elif…群是不是可以用一个dict字典解决?我是用这两行:
    to_do = {K_LEFT:(-1,0),K_RIGHT:(1,0),K_UP:(0,-1),K_DOWN:(0,1)}
    #…
    moveX,moveY = to_do[event.key]

    Reply
    1. x=x+1

      按照我以前VB的逻辑,我一直在盼望着有一个KEYPRESS……这样就不用moveX,moveY了

      Reply
  49. Sam100

    将21、22行移到33行来让程序能捕捉退出事件。

    Reply
  50. 杨水月

    我有一个问题,在我的图片还没有移动出屏幕外时,我移动鼠标不会对桌面造成影响,当我的图片移动出屏幕窗口外之后,我移动鼠标,便会不停的画出鼠标的轨迹(鼠标我用带alpha的图片替代中),这是为什么?

    Reply
  51. 北方的绵羊

    C:\Users\Administrator\Desktop>Python pygame.py
    Traceback (most recent call last):
    File “pygame.py”, line 1, in
    import pygame
    File “C:\Users\Administrator\Desktop\pygame.py”, line 2, in
    from pygame.locals import *
    ImportError: No module named locals

    为什么会提示 No module named locals? 在第一讲中,代码里也有用到 from pygame.locals import *,在那里就没有问题,在这个第二讲中,就会提示这个。是什么问题呢?是我按楼主的代码,缩进有误?还想请教一个问题,写Python代码,用什么编辑器编辑适合初学者?谢谢!

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

  53. candy

    博主我想问下为什么没改缩进前的那段代码运行的时候,按住某个方向键是只能移动一下的,但是我按住之后然后移动鼠标,图片就一直会往那个方向移动,这是为什么啊?另外我想请博主解释一下缩进之前不能一直移动的原因。。。。

    Reply
    1. 一颗赛艇

      pygame.event.get()是一直执行的,返回当前的所有事件,如果当前什么都没做就返回空list
      按照博主的代码,如果什么都没做,那么画面就不会更新(pygame.display.update()不会调用)
      当你按下方向键后只是设置了移动方向和移动距离,真正”移动”了几次看`for event in pygame.event.get():`循环次数
      如果你只按下了方向键让后什么都不做,那么循环只执行一次。但如果你有做了其他事(移动鼠标),此时·pygame.event.get()·返回不为空,鼠标移动事件触发了几次就循环了几次,也就“移动”了几次

      Reply
  54. Simona

    博主可以转载吗?会标明出处,因为自己又敲了一遍,加了些自己的注释等,想发布到自己的博客中去,望回复

    Reply

发表评论

电子邮件地址不会被公开。 必填项已用*标注