本文转载自:http://eyehere.net/2011/python-pygame-novice-professional-sprite/
这又是Pygame教程系列的一个——OVA篇,类似于py2exe篇一样,额外写的,也许不是pygame游戏开发必须的东西,但是知道了绝对大有裨益。因此友情大放送~
看pygame模块介绍的时候,细心的人会发现有一个pygame.sprite模块,而在讲动画的时候,虽然引入了精灵这个概念,却没有使用这个模块。在官方文档上也说了,这个模块是轻量级的,在游戏开发中也未必要使用。讲解动画的时候为了避免太多新东西,直接把一个surface画来画去,难道没有人觉得不和谐么:)我们这次试着使用Sprite把动画变的更简单一些(不过这里没有使用GameObjects,两者结合更健康~)。
“sprite”,中文翻译“精灵”,在游戏动画一般是指一个独立运动的画面元素,在pygame中,就可以是一个带有图像(Surface)和大小位置(Rect)的对象。 精灵特别适合用在OO语言中,比如Python。
pygame.sprite.Sprite是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
35
36
37
38
39
40
|
import
cStringIO
,
base64
import
pygame
from
pygame
.
locals
import
*
class
Ball
(
pygame
.
sprite
.
Sprite
)
:
def
__init__
(
self
,
color
,
initial_position
)
:
pygame
.
sprite
.
Sprite
.
__init__
(
self
)
ball_file
=
cStringIO
.
StringIO
(
base64
.
decodestring
(
"""iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ
bWFnZVJlYWR5ccllPAAABBJJREFUeNqsVj2PG1UUvfPp8XictXfHa+9mlyJCNEQRWiToqACJAgGC
LqJNlQZR0IFEj8RPSJkGGooUpEWJkGhR0tAAElI2tsfjjxnPjIdz7oyDF2wSUK72yN43793z7rkf
Y8N2HFmbbVliGIYiyzIpy1Isy3oHeMswzLOyXJ2tVit9VhTFAxz5Cfge+A7IZIcZmySObQudwIE0
veanraB1w/O8l5x6D9eXy6UkSaJYLBa6BvsNuAV8uY3sCQlvX4LANM0Xw/Dgdhj2Xm02m+K6LqPR
PXmeS5qmMp/PZTabyXQ6lclkosS1/QJcB+5vkthrAkoAuc4uHx//0B8MvCAIxG/5jEg0kpIkmcwX
icTxBIhlHWEURXoedgW4B3wIfHuBJM9yMQ3j5PTk5N7g6MjtdrrS3e9Ku90GUUvc2hkdMYJx5Ivn
NRC19UReRlRLR/sGeB34UUkMJBcJlcHg6K4SdDvS7/el1+tJp7MnQdCWRqMhDGWZLmWCCFog9rBm
GBYc50rOKON4uqkSC+IQSC3moeX7N09PX/i4AwLkAoQDxeFhHziU8CCUzt6e+EFLc2QaJi4mFQHy
kQLZMpME+WJF1sabdYA7Nq4jQbv9OZPs+75cgkSMYH9/X6PhJ9dpTLjruFLkBRyjACBd1BoLzzY8
T3O0IRntJvCZDXsTTnq262CzrzmgRHu4+QEIQhAxNzRWU1mTxfjOwvBIAOlIYNnWtja5bqM33mN/
sBEdx9bNPOQ1PWlqZJdAFKoMrEI6R+9gj6t7cUl1zjKnjFvsfaybr1Uqlv94ypXSKCud+aefpezs
7O3LL9s4c5U65gCrhGDDpUkqyWIuU1STweNlJRe7nAlmA+ZaVbnmiD4KFNEWC+3VqjB5YImDdMA+
YKONx2OVgxefojRL8CzmCxkOhxLhWYy+mGIvz6RKmv096X91PErP4Byazapbs3vZB45bVQqTzBzQ
kjQBQSTnjx7JcDTCRSLkKNY9SbKACsttHKZdrIqHILnGCNhoDU0qG83U5mNUVTOKShRPYo3m8fAc
nT/S/3mWFy2KrXKNOFbuI+Rr1FvLsB731Ho2m2pU7I1Sx8pSHTLaESIZjob6nfso2w77mSR3IMsN
zh4mmLOIBAkO6fjAgESdV1MYiV4kiUZHRDjD3E0Qza580D+rjsUdAQEj4fRl8wUkqBttPeo5RlJI
uB71jIASc8D+i4W8IoX8CviC5cuI+JlgpLsgcF1ng6RQyaoX1oWX1i67DTxe9w+9/EHW9VOrngCW
ZfNFpmvVWOfUzZ/mfG0HwHBz4ZV1kz8nvLuL+YPnRPDJ00J8A/j9fzrnW+sjeUbjbP8amDyj86z+
tXL5PwzOC4njj4K3gavA8cazczYacLd+p/+6y8mfAgwAsRuLfp/zVLMAAAAASUVORK5CYII="""
)
)
self
.
image
=
pygame
.
image
.
load
(
ball_file
,
'file'
)
.
convert_alpha
(
)
self
.
rect
=
self
.
image
.
fill
(
color
,
None
,
BLEND_ADD
)
self
.
rect
.
topleft
=
initial_position
pygame
.
init
(
)
screen
=
pygame
.
display
.
set_mode
(
[
350
,
350
]
)
ball
=
Ball
(
(
255
,
0
,
0
)
,
(
100
,
100
)
)
screen
.
blit
(
ball
.
image
,
ball
.
rect
)
pygame
.
display
.
update
(
)
while
pygame
.
event
.
poll
(
)
.
type
!=
KEYDOWN
:
pygame
.
time
.
delay
(
10
)
|
那一大堆的字符串,相信懂Python的人会明白的,不明白的请去查阅一下base64编码和Python对应的StringIO、base64库。我这里使用这种方法而不是直接读取文件,只是想告诉大家pygame.image.load方法不仅仅可以读取文件,也可以读取文件对象。是不是感觉一下子思路开阔了?Python那么多方便的文件对象,以后游戏的资源文件就可以不用一一独立放出来了,使用zipfile,我们很容易就可以把资源文件打包起来,这样看起来咱的游戏可就专业多了~这是后话,以后有机会再讲。
而本例没有直接画一个圆,而是使用用了颜色混合的方法,这样可以画出有立体感的球体,效果如左图。而上面一大堆的字符串,其实就是那个球体的图像文件编码以后的东西。这个和本教程没啥大联系,请自行学习光与色的知识……
但是但是,看了上面的代码大家一定会有意见了,这样感觉比直接用Surface写的代码还多啊!一点好处都没有的样子。确实会有这样的错觉,但是一个球看不出好处来,多个球呢?我们就可以这么写了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
balls
=
[
]
for
color
,
location
in
[
(
[
255
,
0
,
0
]
,
[
50
,
50
]
)
,
(
[
0
,
255
,
0
]
,
[
100
,
100
]
)
,
(
[
0
,
0
,
255
]
,
[
150
,
150
]
)
]
:
boxes
.
append
(
Box
(
color
,
location
)
)
.
.
.
for
b
in
balls
:
screen
.
blit
(
b
.
image
,
b
.
rect
)
pygame
.
display
.
update
(
)
# 我们还能用一种更牛的重绘方式
# rectlist = [screen.blit(b.image, b.rect) for b in balls]
# pygame.display.update(rectlist)
# 这样的好处是,pygame只会重绘有更改的部分
|
我就不给出完整代码和效果图了,请大家自己试验。
不过光这样还不足以体现sprite的好处,sprite最大的优势在于动画,这里就需要用一下update方法,举一个例子,把第一个程序,从33行开始换成下面的代码:
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
|
class
MoveBall
(
Ball
)
:
def
__init__
(
self
,
color
,
initial_position
,
speed
,
border
)
:
super
(
MoveBall
,
self
)
.
__init__
(
color
,
initial_position
)
self
.
speed
=
speed
self
.
border
=
border
self
.
update_time
=
0
def
update
(
self
,
current_time
)
:
if
self
.
update_time
<
current_time
:
if
self
.
rect
.
left
<
0
or
self
.
rect
.
left
>
self
.
border
[
0
]
-
self
.
rect
.
w
:
self
.
speed
[
0
]
*=
-
1
if
self
.
rect
.
top
<
0
or
self
.
rect
.
top
>
self
.
border
[
1
]
-
self
.
rect
.
h
:
self
.
speed
[
1
]
*=
-
1
self
.
rect
.
x
,
self
.
rect
.
y
=
self
.
rect
.
x
+
self
.
speed
[
0
]
,
self
.
rect
.
y
+
self
.
speed
[
1
]
self
.
update_time
=
current_time
+
10
pygame
.
init
(
)
screen
=
pygame
.
display
.
set_mode
(
[
350
,
350
]
)
balls
=
[
]
for
color
,
location
,
speed
in
[
(
[
255
,
0
,
0
]
,
[
50
,
50
]
,
[
2
,
3
]
)
,
(
[
0
,
255
,
0
]
,
[
100
,
100
]
,
[
3
,
2
]
)
,
(
[
0
,
0
,
255
]
,
[
150
,
150
]
,
[
4
,
3
]
)
]
:
balls
.
append
(
MoveBall
(
color
,
location
,
speed
,
(
350
,
350
)
)
)
while
True
:
if
pygame
.
event
.
poll
(
)
.
type
==
QUIT
:
break
screen
.
fill
(
(
0
,
0
,
0
,
)
)
current_time
=
pygame
.
time
.
get_ticks
(
)
for
b
in
balls
:
b
.
update
(
current_time
)
screen
.
blit
(
b
.
image
,
b
.
rect
)
pygame
.
display
.
update
(
)
|
我们可以看到小球欢快的运动起来,碰到边界就会弹回来,这才是sprite类的真正用处。每一个Sprite类都会有各自的速度属性,每次调用update都会各自更新自己的位置,主循环只需要update+blit就可以了,至于各个小球到底在一个怎样的状态,完全可以不在意。不过精灵的魅力还是不仅在此,上面的代码中我们把每个精灵加入一个列表,然后分别调用每个精灵的update方法,太麻烦了!使用pygame.sprite.Group类吧,建立它的一个实例balls,然后用add方法把精灵加入,然后只需要调用balls.update(args..)就可以了,连循环的不用写。同样的使用balls.draw()方法,你可以让pygame只重绘有变化的部分。请尝试使用(记住还有一个balls.clear()方法,实际写一下就知道这个方法用来干嘛了)。
尽管我们已经说了很多,也着实领略到了精灵的好处,但故事还没有结束,pygame.sprite有着层与碰撞的概念。层的引入是因为Group默认是没有次序的,所以哪个精灵覆盖哪个精灵完全就不知道了,解决方法嘛,使用多个Group、使用OrderedUpdates,或者使用LayeredUpdates,至于具体使用方法,相信如果您需要用到的时候,已经到相当的高度了,随便看看文档就明白了,我就不多说了;而碰撞,又是一个无底洞啊,下次有机会再讲吧~