目录

Pygame第3章 Pygame介绍

安装Pygame

Ubuntu下安装Pygame:

1
sudo apt-get install python-pygame

装好后,查看Pygame版本:

1
2
>>> import pygame
>>> print pygame.ver

使用Pygame

Pygame有很多模块。每一个设备都有一个对应的模块。Pygame模块一览:

模块名 功能
pygame.cdrom 访问和控制光驱
pygame.cursors 加载光标图片
pygame.display 访问显示设备
pygame.draw 绘制形状、线和点
pygame.event 管理事件
pygame.font 使用字体
pygame.image 加载和存储图片
pygame.joystick 使用游戏手柄或者类似设备
pygame.key 读取键盘按键
pygame.mixer 加载和播放声音
pygame.mouse 管理鼠标
pygame.movie 播放视频
pygame.music 处理音乐和音频流
pygame.overlay 访问高级视频叠加
pygame 包含高层Pygame函数
pygame.rect 管理矩形区域
pygame.sndarray 操作声音数据
pygame.sprite 操作移动图像
pygame.surface 管理图像和屏幕
pygame.surfarray 管理点阵图像数据
pygame.time 管理时间和帧信息
pygame.transform 缩放和移动图像

有些模块可能在某些平台上不存在,比如游戏运行的硬件驱动没有安装,这种情况下,Pygame将设置这个模块为None,可以使用None来测试。下面这段代码检测pygame.font是否可用:

1
2
3
if pygame.font is None:
    print 'The font module is not available!'
    exit()

重温Hello World

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
background_image_filename = 'sushiplate.jpg'
mouse_image_filename = 'fugu.png'

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

pygame.init()

screen = pygame.display.set_mode((640, 480), 0, 32)
pygame.display.set_caption('Hello, World!')

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

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

    screen.blit(background, (0, 0))

    x, y = pygame.mouse.get_pos()
    x -= mouse_cursor.get_width() / 2
    y -= mouse_cursor.get_height() / 2
    screen.blit(mouse_cursor, (x, y))

    pygame.display.update()

第一行导入pygame包,使我们可以访问它的所有子模块。第二行导入一些常用函数和常量,这个不是必须的,但是更方便。

pygame.init()非常简单,实际上却做了非常多工作。它初始化每一个Pygame的子模块。可以单独初始化某一个模块,比如pygame.sound.init()

pygame.display.set_mode返回一个Surface对象,代表桌面窗口。第一个参数为元祖,代表分辨率(必须)。第二个是一个标志位,具体意思见下表,如果不用什么特性,就指定0。第三个为色深,如果未提供或设置为0,Pygame将使用当前桌面的值。

标志位 用途
FULLSCREEN 创建一个全屏窗口
DOUBLEBUF 创建一个“双缓冲”窗口,建议在HWSURFACE或者OPENGL时使用
HWSURFACE 创建一个硬件加速的窗口,必须和FULLSCREEN同时使用
OPENGL 创建一个OpenGL渲染的窗口
RESIZABLE 创建一个可变大小的窗口
NOFRAME 创建一个没有边框的窗口
色深 颜色个数
8 bits 256种颜色
15 bits 32768种颜色,空闲一位
16 bits 65536种颜色
24 bits 16.7百万种颜色
32 bits 16.7百万种颜色,空闲8位

load函数读取一个文件并返回一个包含图片数据的Surface对象,直到画出来之前是不可见的。 convert函数是将图像数据转化为Surface对象,每次加载完图像以后就应该做这件事件(事实上因为它太常用了,如果你不写Pygame也会帮你做); convert_alpha相比convert,保留了Alpha通道信息(可以简单理解为透明的部分),这样我们的光标才可以是不规则的形状。

游戏的主循环是一个无限循环,直到用户跳出。在这个主循环里做的事情就是不停地画背景和更新光标位置,虽然背景是不动的,我们还是需要每次都画它,否则鼠标覆盖过的位置就不能恢复正常了。 画完以后一定要update更新一下,否则画面一片漆黑。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
init(...)
    pygame.init(): return (numpass, numfail)
    initialize all imported pygame modules

set_mode(...)
    pygame.display.set_mode(resolution=(0,0), flags=0, depth=0): return Surface
    initialize a window or screen for display

set_caption(...)
    pygame.display.set_caption(title, icontitle=None): return None
    set the current window caption

load(...)
    pygame.image.load(filename): return Surface
    pygame.image.load(fileobj, namehint=): return Surface
    load new image from a file

convert(...)
    Surface.convert(Surface): return Surface
    Surface.convert(depth, flags=0): return Surface
    Surface.convert(masks, flags=0): return Surface
    Surface.convert(): return Surface
    change the pixel format of an image

convert_alpha(...)
    Surface.convert_alpha(Surface): return Surface
    Surface.convert_alpha(): return Surface
    change the pixel format of an image including per pixel alphas

get(...)
    pygame.event.get(): return Eventlist
    pygame.event.get(type): return Eventlist
    pygame.event.get(typelist): return Eventlist
    get events from the queue

blit(...)
    Surface.blit(source, dest, area=None, special_flags = 0): return Rect
    draw one image onto another

get_pos(...)
    pygame.mouse.get_pos(): return (x, y)
    get the mouse cursor position

get_width(...)
    Surface.get_width(): return width
    get the width of the Surface

get_height(...)
    Surface.get_height(): return height
    get the height of the Surface

update(...)
    pygame.display.update(rectangle=None): return None
    pygame.display.update(rectangle_list): return None
    update portions of the screen for software displays

理解事件

事件可以在任何时候产生,不管程序当前在做什么。因为你不能对发生的事件立刻做出反应,Pygame将事件存入一个队列,逐个处理。

事件检索

上个程序中,使用pygame.event.get()来处理所有的事件,这就像打开大门让所有的人进入。如果我们使用pygame.event.wait(),Pygame就会等到一个事件发生才继续下去,就好像等在门口,直到有人来。这个函数不太常用,因为它会挂起程序直到有事情发生;而另外一个方法**pygame.event.poll()**就好一些,一旦调用,它会根据现在的情形返回一个真实的事件,否则返回一个类型为NOEVENT的假事件。

每隔一段固定的时间调用事件处理函数很有必要,这样Pygame才能在内部处理事件。如果不使用任何事件处理函数,也可以调用**pygame.event.pump()**替代事件循环。

事件对象包含一些描述事件发生的成员变量。所有事件对象都有一个事件类型。

事件 产生途径 参数
QUIT 用户点击关闭按钮 none
ACTIVEEVENT 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,ball,rel
JOYHATMOTION 游戏手柄(Joystick)移动 joy,hat,value
JOYBUTTONDOWN 游戏手柄按下 joy,button
JOYBUTTONUP 游戏手柄放开 joy,button
VIDEORESIZE Pygame窗口缩放 size,w,h
VIDEOEXPOSE 部分或所有Pygame窗口暴露 none
USEREVENT 触发一个用户事件 code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import pygame
from pygame.locals import *
from sys import exit

pygame.init()
SCREEN_SIZE = (800, 600)
screen = pygame.display.set_mode(SCREEN_SIZE, 0, 32)

font = pygame.font.SysFont('arial', 16)
font_height = font.get_linesize()
event_text = []

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()

pygame.font.SysFont返回一个pygame.font.Font对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
SysFont(name, size, bold=False, italic=False)
    pygame.font.SysFont(name, size, bold=False, italic=False) -> Font
    create a pygame Font from system font resources
    
    This will search the system fonts for the given font
    name. You can also enable bold or italic styles, and
    the appropriate system font will be selected if available.
    
    This will always return a valid Font object, and will
    fallback on the builtin pygame font if the given font
    is not found.
    
    Name can also be a comma separated list of names, in
    which case set of names will be searched in order. Pygame
    uses a small set of common font aliases, if the specific
    font you ask for is not available, a reasonable alternative
    may be used.

get_linesize(...)
    Font.get_linesize(): return int
    get the line space of the font text

wait(...)
    pygame.event.wait(): return Event
    wait for a single event from the queue

render(...)
    Font.render(text, antialias, color, background=None): return Surface
    draw text on a new Surface

fill(...)
    Surface.fill(color, rect=None, special_flags=0): return Rect
    fill Surface with a solid color

pump(...)
    pygame.event.pump(): return None
    internally process pygame event handlers

处理鼠标移动事件

当鼠标移动时,MOUSEMOTION事件发生。包含下面三个值:

  • buttons-一个对应鼠标按钮的元组。buttons[0]是鼠标左按钮,buttons[1]是鼠标中间按钮,buttons[2]是鼠标右按钮。如果按钮被按下,则值为1,反之为0。多个按钮可以同时按下。
  • pos-一个元组,包含事件发生时鼠标所在位置。
  • rel-一个元祖,包含现在距离上次产生鼠标事件时的距离。

处理鼠标按钮事件

除了鼠标移动事件,鼠标还能产生MOUSEBUTTONDOWNMOUSEBUTTONUP事件。包含下面2个值:

  • button-被按下的按钮的数字。1为鼠标左按钮,2为鼠标中间按钮,3为鼠标右按钮。
  • pos-一个元组,包含事件发生时鼠标所在位置。

处理键盘事件

键盘和游戏手柄的事件类似。当一个键被按下KEYDOWN事件发生。当一个键松开KEYUP事件发生。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
bg_file = 'sushiplate.jpg'

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

pygame.init()
screen = pygame.display.set_mode((640, 480), FULLSCREEN, 32)
background = pygame.image.load(bg_file).convert()

x, y = 0, 0
move_x, move_y = 0, 0

while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            exit()
        if event.type == KEYDOWN:
            if event.key == K_LEFT:
                move_x = -1
            elif event.key == K_RIGHT:
                move_x = 1
            elif event.key == K_UP:
                move_y = -1
            elif event.key == K_DOWN:
                move_y = 1
        elif event.type == KEYUP:
            if event.key == K_LEFT or event.key == K_RIGHT:
                move_x = 0
            elif event.key == K_UP or event.key == K_DOWN:
                move_y = 0
    x += move_x
    y += move_y

    screen.fill((0, 0, 0))
    screen.blit(background, (x, y))

    pygame.display.update()

KEYDOWNKEYUP事件包含下面三个值:

  • key-这是一个代表按下或松开的键值的数字。每一个键盘上的物理按钮都有一个以K_开头的常量。字母键为K_a到K_z,其它的如K_SPACE和K_RETURN。
  • mod-这个值代表和key组合使用的其它键,比如Shift,Alt和Ctrl。每一个组合键以KMOD_开头,比如KMOD_SHIFT,KMOD_ALT和KMOD_CTRL。如果mod & KMOD_CTRL为真,则表示按下了Ctrl键。
  • unicode-这个是被按下的键的Unicode值。每一个符号都有一个Unicode值与它对应。

过滤事件

一个游戏不是所有的事件都需要处理,而且通常存在其它方式获取某个事件可能提供的信息。比如,使用pygame.mouse.get_pos()就不需要响应MOUSEMOTION事件了。

使用pygame.event.set_blocked函数可以屏蔽事件,阻止事件进入事件队列。比如:

1
2
3
4
5
6
7
8
9
pygame.event.set_blocked(MOUSEMOTION)
pygame.event.set_blocked([KEYDOWN, KEYUP])
pygame.event.set_blocked(None)

set_blocked(...)
    pygame.event.set_blocked(type): return None
    pygame.event.set_blocked(typelist): return None
    pygame.event.set_blocked(None): return None
    control which events are allowed on the queue

与之相对的,pygame.event.set_allowed设定允许的事件。

1
2
3
4
5
set_allowed(...)
    pygame.event.set_allowed(type): return None
    pygame.event.set_allowed(typelist): return None
    pygame.event.set_allowed(None): return None
    control which events are allowed on the queue

pygame.event.get_blocked可以查询一个事件是否被屏蔽。

1
2
3
get_blocked(...)
    pygame.event.get_blocked(type): return bool
    test if a type of event is blocked from the queue

产生事件

通常Pygame为你产生相应的事件,但是你也可以产生自己的事件。为了产生一个事件,必须首先使用pygame.event.Event创建一个事件对象,然后使用pygame.event.post发送到事件队列尾端。

1
2
my_event = pygame.event.Event(KEYDOWN, key=K_SPACE, mod=0, unicode=u' ')
pgame.event.post(my_event)

事件构造函数接收事件类型和事件值参数。

1
2
3
4
5
6
7
8
Event(...)
    pygame.event.Event(type, dict): return Event
    pygame.event.Event(type, **attributes): return Event
    create a new event object

post(...)
    pygame.event.post(Event): return None
    place a new event on the queue

除了模拟Pygame产生的事件,也可以创建新的事件。你只需使用一个大于USEREVENT的值作为事件的值。

1
2
3
4
5
6
7
CATONKEYBOARD = USEREVENT + 1
my_event = pygame.event.Event(CATONKEYBOARD, message="Bad cat!")
pgame.event.post(my_event)

for event in pygame.event.get():
    if event.type == CATONKEYBOARD:
        print event.message

打开一个显示

全屏显示

pygame.display.set_mode第二个参数设置为FULLSCREEN,就能得到一个全屏窗口了。

1
screen = pygame.display.set_mode((640, 480), FULLSCREEN, 32)

注意 如果在全屏模式下出问题,有时候非常难回到桌面。因此进入全屏模式前,需要先在窗口模式下测试。同时提供一个退出程序方法,因为全屏模式下关闭按钮看不到。

当进入全屏时,你的显卡可能会切换到不同的显示模式,这将改变显示的宽度,高度和一次显示颜色的数量。显卡只支持几种大小和颜色数量的组合。如果显示的大小不支持,Pygame将选择下一个大小,并居中显示。pygame.display.list_modes可以查看显卡支持的分辨率。

1
2
3
4
5
6
7
>>> import pygame
>>> pygame.init()
>>> pygame.display.list_modes()

list_modes(...)
    pygame.display.list_modes(depth=0, flags=pygame.FULLSCREEN): return list
    get list of available fullscreen modes

如果显卡不支持你想要的颜色数量,Pygame将自动转换以适应当前显示设备。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
background_image_filename = 'sushiplate.jpg'

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

pygame.init()
screen = pygame.display.set_mode((640, 480), 0, 32)
background = pygame.image.load(background_image_filename).convert()

Fullscreen = False

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

    if event.type == KEYDOWN:
        if event.key == K_f:
            Fullscreen = not Fullscreen
            if Fullscreen:
                screen = pygame.display.set_mode((640, 480), FULLSCREEN, 32)
            else:
                screen = pygame.display.set_mode((640, 480), 0, 32)

    screen.blit(background, (0, 0))
    pygame.display.update()

可变尺寸的Pygame窗口

有时候用户想要能够改变窗口大小,调用pygame.display.set_mode时使用RESIZABLE标志位,可以达到这个目的。Pygame通过发送包含新宽高的VIDEORESIZE事件告诉用户窗口大小改变了。当收到这个事件时,我们应该再次调用pygame.display.set_mode

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
background_image_filename = 'sushiplate.jpg'

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

SCREEN_SIZE = (640, 480)

pygame.init()
screen = pygame.display.set_mode(SCREEN_SIZE, RESIZABLE, 32)

background = pygame.image.load(background_image_filename).convert()

while True:
    event = pygame.event.wait()
    if event.type == QUIT:
        exit()
    if event.type == VIDEORESIZE:
        SCREEN_SIZE = event.size
        screen = pygame.display.set_mode(SCREEN_SIZE, RESIZABLE, 32)
        pygame.display.set_caption('Window resized to ' + str(event.size))

    screen_width, screen_height = SCREEN_SIZE
    for y in range(0, screen_height, background.get_height()):
        for x in range(0, screen_width, background.get_width()):
            screen.blit(background, (x, y))

    pygame.display.update()

VIDEORESIZE事件包含下面的值:

  • size-一个元组,包含更改后窗口的尺寸。size[0]代表宽度,size[1]代表高度。
  • w-宽度,和size[0]一样,但是更方便
  • h-高度,和size[1]一样,但是更方便

无边框窗口

当调用set_mode时使用NOFRAME标志可以设置一个无边框窗口。

其它显示标志

通常最好是使用0显示窗口而使用FULLSCREEN全屏以保证程序在所有平台都能运行。其它高级标志也许会有兼容问题。 如果设置了HWSURFACE标志,将创建一个硬件显示,存储在显存里面,只能和FULLSCREEN一起使用。

1
screen = pygame.display.set_mode(SCREEN_SIZE, HWSURFACE | FULLSCREEN, 32)

硬件显示比常规显示更快,因为它能够利用显卡的特性加速显示,缺点是兼容性不够好。硬件显示也能从DOUBLEBUF标志受益。这个有效地创建2个硬件显示,但是一次只能看见一个。

1
screen = pygame.display.set_mode(SCREEN_SIZE, DOUBLEBUF | HWSURFACE | FULLSCREEN, 32)

通常当你调用**pygame.display.update()**时,整个屏幕从内存拷贝到显示设备,这会花费一些时间。而双缓冲允许你立刻切换到新的屏幕,使你的程序运行更快。

注意 如果使用双缓冲显示,应该调用pygame.display.flip()而不是pygame.display.update()。这个做立即显示切换而不是拷贝屏幕数据。

1
2
3
flip(...)
    pygame.display.flip(): return None
    update the full display Surface to the screen

最后一个显示标志是OPENGL,它能够使用3D加速显示。

使用字体模块

字体模块使用TrueType字体(TTFs)。必须先创建一个Font对象才能使用字体。最简单的方式就是使用pygame.font.SysFont,它会使用系统自带的一个字体。

1
my_font = pygame.font.SysFont("arial", 16)

第一个参数是你想要创建字体的名字,第二个参数指定了字体的大小。Pygame会在系统字体里面查找,如果没找到则返回一个默认字体。pygame.font.get_fonts()可以获得当前系统所有可用字体。也可以使用pygame.font.Font直接从.ttf文件创建字体。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
my_font = pygame.font.Font("my_font.ttf", 16)

class Font(__builtin__.object)
 |  pygame.font.Font(filename, size): return Font
 |  pygame.font.Font(object, size): return Font
 |  create a new Font object from a file

get_fonts()
    pygame.font.get_fonts() -> list
    get a list of system font names
    
    Returns the list of all found system fonts. Note that
    the names of the fonts will be all lowercase with spaces
    removed. This is how pygame internally stores the font
    names for matching.

一旦创建了Font对象,就可以使用Font对象的render函数来渲染文字。它创建一个新的包含文字的Surface,可以输出到显示设备。

1
2
3
4
5
text_surface = my_font.render("Pygame is cool!", True, (0,0,0), (255, 255, 255))

render(...)
    Font.render(text, antialias, color, background=None): return Surface
    draw text on a new Surface

render的第一个参数是你想渲染的文字,它必须是一行。如果有多行,必须使用多个render调用。第二个参数是一个布尔值,用来开启抗锯齿。如果设置为True,则文字看起来会比较平滑。后面两个参数是文字的颜色和背景颜色。背景色是可选的,默认为透明的。

1
2
3
4
5
6
7
import pygame
pygame.init()

my_name = 'Smith'
my_font = pygame.font.SysFont('arial', 64)
name_surface = my_font.render(my_name, True, (0, 0, 0), (255, 255, 255))
pygame.image.save(name_surface, 'name.png')

font模块详尽参考见http://www.pygame.org/docs/ref/font.html

注意 安装的字体因机各异,不能保证某个字体一定存在。解决方法是.ttf文件同游戏一起发布,但必须得到字体作者的许可。

当Pygame出错

pygame.image.load不能读取图片时,Pygame会抛出pygame.error异常。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
>>> import pygame
>>> screen = pygame.display.set_mode((640, 0))
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
pygame.error: Cannot set 0 sized display mode

class error(exceptions.RuntimeError)
 |  Method resolution order:
 |      error
 |      exceptions.RuntimeError
 |      exceptions.StandardError
 |      exceptions.Exception
 |      exceptions.BaseException
 |      __builtin__.object

一般而言,当你碰到pygame.error异常时,你也做不了什么事,因为这说明一个比较大的故障发生了。通常你能做的就是指导用户怎么做。在一个大的项目里面,检查错误很重要。

1
2
3
4
5
6
try:
    screen = pygame.display.set_mode(SCREEN_SIZE)
except pygame.error, e:
    print "Can't create the display :-("
    print e
    exit()

Pygame动起来

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
background_image_filename = 'sushiplate.jpg'
SCREEN_SIZE = (640, 480)
message = '    This is a demonstration of the scrolly message script.    '

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

pygame.init()
screen = pygame.display.set_mode(SCREEN_SIZE)

font = pygame.font.SysFont('arial', 80)
text_surface = font.render(message, True, (0, 0, 255))

x = 0
y = (SCREEN_SIZE[1] - text_surface.get_height()) / 2

background = pygame.image.load(background_image_filename).convert()

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

    screen.blit(background, (0, 0))

    x -= 2
    if x < -text_surface.get_width():
        x = 0

    screen.blit(text_surface, (x, y))
    screen.blit(text_surface, (x + text_surface.get_width(), y))

    pygame.display.update()