用Python和Pygame写游戏-从入门到精通(实战二:恶搞俄罗斯方块2)
我们接着来做这个整死人不偿命的俄罗斯方块。
代码组织和名词约定
上一次我们稍微整理了一下游戏运行的框架,这里需要整理一下python代码的框架,一个典型的pygame脚本结构如下:
其中,lib为pygame的脚本,游戏中声音、图像、控制模块等都放在这里;而data就是游戏的资源文件,图像、声音等文件放在这里。当然这东西并不是硬性规定的,你可以用你自己喜欢的结构来组织自己的pygame游戏,事实上,除了付你工钱的那家伙以外,没有人可以强迫你这样做或那样做~ 这次我还是用这种典型的方法来存放各个文件,便于大家理解。
因为我是抽空在Linux和Windows上交叉编写的,代码中没有中文注释,游戏里也没有中文的输出,所以希望看到清楚解释的话,还是应该好好的看这几篇文章。当然最后我会放出所有的代码,也会用py2exe编译一份exe出来,方便传给不会用python的人娱乐。
因为主要是讲解pygame,很多python相关的知识点就一代而过了,只稍微解释一下可能有些疑问的,如果有看不懂的,请留言,我会酌情追加到文章中。
我们约定,那个掉落方块的区域叫board,落下的方块叫shape,组成方块的最小单位叫tile。
我们的lib中可能会有这几个文件(未必是全部):
1
2
3
4
5
6
|
game
.
py
--
--
主循环,该文件会调用
main,
menu来展示不同界面
main
.
py
--
--
游戏界面,但为了实现不同模式,这里需再次调用
tetris
menu
.
py
--
--
菜单界面,开始的选择等
shape
.
py
--
--
方块的类
tetris
.
py
--
--
游戏核心代码,各种不同种类的俄罗斯方块实现
util
.
py
--
--
各种工具函数,如读取图片等
|
引导文件
run_game.py很简单,而且基本所有的pygame工程里都长得一样:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#!/usr/bin/env python
import
sys
,
os
try
:
libdir
=
os.path
.
join
(
os.path
.
dirname
(
os.path
.
abspath
(
__file__
)
)
,
'lib'
)
sys
.
path
.
insert
(
0
,
libdir
)
except
:
# in py2exe, __file__ is gone...
pass
import
game
game
.
run
(
)
|
将lib目录加入搜索路径就完事了,没有什么值得特别说明的。
主循环文件
到现在为止,我们所有的pygame都只有一个界面,打开是什么,到关闭也就那个样子。但实际上的游戏,一般进去就会有一个开始界面,那里我们可以选“开始”,“继续”,“选项”……等等内容。pygame中如何实现这个呢?
因为pygame一开始运行,就是一根筋的等事件并响应,所以我们就需要在事件的循环中加入界面的判断,然后针对的显示界面。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
def
loop
(
self
)
:
clock
=
pygame
.
time
.
Clock
(
)
while
self
.
stat
!=
'quit'
:
elapse
=
clock
.
tick
(
25
)
if
self
.
stat
==
'menu'
:
self
.
stat
=
self
.
menu
.
run
(
elapse
)
elif
self
.
stat
==
'game'
:
self
.
stat
=
self
.
main
.
run
(
elapse
)
if
self
.
stat
.
startswith
(
'level'
)
:
level
=
int
(
self
.
stat
.
split
(
)
[
1
]
)
print
"Start game at level"
,
level
self
.
main
.
start
(
level
)
self
.
stat
=
"game"
pygame
.
display
.
update
(
)
pygame
.
quit
(
)
|
因为有很多朋友说之前使用的while True的方法不能正常退出,这里我就听取大家的意见干脆把退出也作为一种状态,兼容性可能会好一些。不过在我的机器上(Windows 7 64bit + Python 2.6 32bit + Pygame 1.9.1 32bit)是正常的,怀疑是不是无法正常退出的朋友使用了64位的Python和Pygame(尽管64位Pygame也有,但并不是官方推出的,不保证效果)。
这里定义了几个游戏状态,最主要的就是’menu‘和’game‘,意义也是一目了然的。我们有游戏对象和菜单对象,当游戏处于某种状态的时候,就调用对应对象的run方法,这个run接受一个时间参数,具体意义相信大家也明白了,基于时间的控制。
同时,我们还有一个’level X‘的状态,这个主要是控制菜单到游戏之间的转换,不过虽然写的level,实际的意义是模式,因为我们希望有几种不同的游戏模式,所以在从菜单到游戏过渡的时候,需要这个信息。
这个程序中,所有的状态都是通过字符串来实现的,说实话未必很好。虽然容易理解但是效率等可能不高,也许使用标志变量会更好一些。不过既然是例子,首先自然是希望大家能够看的容易一些。所以最终还是决定使用这个方法。
Menu类
菜单显示了一些选项,并且在用户调节的时候可以显示当前的选项(一般来说就是高亮出来),最后确定时,改变状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class
Menu
:
OPTS
=
[
'LEVEL 1'
,
'LEVEL 2'
,
'LEVEL 3'
,
'QUIT'
]
def
__init__
(
self
,
screen
)
:
self
.
screen
=
screen
self
.
current
=
0
def
run
(
self
,
elapse
)
:
self
.
draw
(
)
for
e
in
pygame
.
event
.
get
(
)
:
if
e
.
type
==
QUIT
:
return
'quit'
elif
e
.
type
==
KEYDOWN
:
if
e
.
key
==
K_UP
:
self
.
current
=
(
self
.
current
-
1
)
%
len
(
self
.
OPTS
)
elif
e
.
key
==
K_DOWN
:
self
.
current
=
(
self
.
current
+
1
)
%
len
(
self
.
OPTS
)
elif
e
.
key
==
K_RETURN
:
return
self
.
OPTS
[
self
.
current
]
.
lower
(
)
return
'menu'
|
菜单的话,大概就是长这个样子,都是我们已经熟练掌握的东西,按上下键的时候会修改当前的选项,然后draw的时候也就判断一下颜色有些不同的标识一下就OK了。这里的draw就是把几个项目写出来的函数。绘图部分和控制部分尽量分开,比较清晰,也容易修改。
这里的run其实并没有用到elapse参数,不过我们还是把它准备好了,首先可以与main一致,其次如果我们想在开始菜单里加一些小动画什么的,也比较便于扩展。
工具函数
工具库util.py里其实没有什么特别的,都是一些便于使用的小东西,比如说在加载资源文件是,我们希望只给出一个文件名就能正确加载,那就需要一个返回路径的函数,就像这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
_ME_PATH
=
os.path
.
abspath
(
os.path
.
dirname
(
__file__
)
)
DATA_PATH
=
os.path
.
normpath
(
os.path
.
join
(
_ME_PATH
,
'..'
,
'data'
)
)
def
file_path
(
filename
=
None
)
:
""" give a file(img, sound, font...) name, return full path name. """
if
filename
is
None
:
raise
ValueError
,
'must supply a filename'
fileext
=
os.path
.
splitext
(
filename
)
[
1
]
if
fileext
in
(
'.png'
,
'.bmp'
,
'.tga'
,
'.jpg'
)
:
sub
=
'image'
elif
fileext
in
(
'.ogg'
,
'.mp3'
,
'.wav'
)
:
sub
=
'sound'
elif
fileext
in
(
'.ttf'
,
)
:
sub
=
'font'
file_path
=
os.path
.
join
(
DATA_PATH
,
sub
,
filename
)
print
'Will read'
,
file_path
if
os.path
.
abspath
(
file_path
)
:
return
file_path
else
:
raise
ValueError
,
"Cant open file `%s'."
%
file_path
|
这个函数可以根据给定的文件名,自己搜索相应的路径,最后返回全路径以供加载。
这次把一些周边的代码说明了一下,当然仅有这些无法构成一个可以用的俄罗斯方块,下一次我们就要开始搭建俄罗斯方块的游戏代码了