python游戏引擎ppb介绍(6)声音、文字与自定义事件
ppb播放声音的机制很简明,但相对而言显示文字的操作略显繁琐,可能这个引擎的设计者的本意关注的是更形象化的视觉效果。虽说一图胜千言,但总也有时候,文字是必不可少的。
修改文字,我们使用自定义事件机制。这也是ppb引擎中一个比较有特色的实现。
声音的播放
我们希望在两个地方加入声音效果,一是导弹发射的瞬间,我们加入“嗖”的音效;另一个是靶子爆炸的瞬间,我们加入爆炸音效。音效文件本身无法在网页中提供,请朋友去爱给音效自行下载,或者留言向一声索取。
发射导弹的代码是Blob类中的鼠标事件(on_button_pressed)
def on_button_pressed(self, event, signal):
if event.button== ppb.buttons.Primary:
p1= (event.position- self.position).normalize()
event.scene.add(Projectile(position=self.position, direction= p1))
signal(ppb.events.PlaySound(ppb.Sound('resources/sou.mp3')))
我们看到了第三个参数(signal)的用途之一:播放声音。其实这个参数的功能本质是用来发各种消息,我们很快会看到它另外的用途。
播放爆炸声,则放在靶子碰撞检测的地方。我们一会儿再看。
文字显示
文字在ppb中,也被当作一种精灵来处理,唯一的区别是这个精灵的图片是文字。
游戏标题的文字类,我们把它命名为label1
class label1(ppb.Sprite):
image= ppb.Text('射击游戏',font=ppb.Font("resources/happy1.ttf", size=62), color=(255,255,100))
position= ppb.Vector(0,5)
layer= 1
文字通过ppb.Text()来创建,这几个属性是必须的:文本内容、字体文件、字体大小、颜色。
- 文本内容:文本内容是无法单独更改的,想改变它就得重建图片,详见下。
- 字体文件:TrueType文件,目前只能通过文件的形式来指定字体,而不能直接用系统字体。
- 字体大小:字体本身有大小,同时作为文本精灵还有一个大小,两者都会影响实际文字的显示尺寸。但我们应当尽可能使用字体本身的大小,让精灵大小保持为1,这样字体显示更清晰。
- 颜色:一个三元组,标明了RGB三个颜色成分,取值范围都是0~255。
游戏积分的文字类,我们把它命名为label2
class label2(ppb.Sprite):
image= ppb.Text('score: 0',font=ppb.Font("resources/happy1.ttf", size=62), color=(255,255,100))
position= ppb.Vector(0,1)
score= 0
layer= 1
def on_change_score(self, event, signal):
self.image= ppb.Text('score: '+ str(event.score),font=ppb.Font("resources/happy1.ttf", size=62), color=(255,255,100))
既然文字也是一个精灵,它就可以自己处理事件。我们在这个on_change_score事件中改变文字的内容。这个事件是怎么触发的?
自定义事件
在ppb中,自定义事件非常简单。新建一个类即可。比如上面的on_change_text事件,它的类是什么样的呢?
class ChangeScore:
def __init__(self, score):
self.score= score
在ppb中约定,类名如果使用驼峰命名法(每个单词的头一个字母大写),所对应的事件名就是转换为蛇形命名法(全小写,用下划线分割单词)之后的样子。
一个事件定义好,所有精灵均可触发它,也可以响应它。
比如当靶子被击中的时候,分数增加20分。我们在Target的刷新事件(on_update)中,增加一些代码。
def on_update(self, update_event, signal):
global score
self.rotation-=self.speed* update_event.time_delta
for p in update_event.scene.get(kind=Projectile):
if (p.position - self.position).length <= self.size/ 2:
update_event.scene.add(BombImg(position=self.position))
update_event.scene.remove(self)
update_event.scene.remove(p)
score+= 20
signal(ppb.events.PlaySound(ppb.Sound('resources/bomb.mp3')))
signal(ChangeScore(score=score))
break
score是一个全局变量,严谨地看,仅仅是为了计分就使用一个全局变量是不合理的,但在我们这个小例子中,暂时这样处理也没有什么问题。当一个靶子被击中,分数增加20。随后通过调用signal参数,触发两个事件,一个是播放声音,另一个就是修改计分。
signal参数是一个函数,传递一个自定义事件类的实例,系统会自动产生一个事件。如果某个精灵定义了响应它的函数,就会得到这个事件,并且获得相应的参数。
我们可以想象,其实播放声音也是同样的原理,是系统内某个地方,接收了这个PlaySound事件并播放声音。这种事件传播机制对于系统解耦很有利。
初始化部分的修改
在场景布局时,增加这两个文本精灵即可。
def setup(scene):
scene.add(Blob(pos=(0, -3.5)))
scene.add(label1())
scene.add(label2())
for x in range(-4, 5, 2):
scene.add(Target(position=ppb.Vector(x, 3)))
注意到两个文本的位置,已经在类中固定,所以在布局时无须再次指定。
完整代码
到此,我们已经用ppb开发完成了一个小游戏,在过程中介绍了ppb引擎的若干核心机制。下次课也就是最后一次关于ppb引擎的课,我们将补充介绍一下它的相机机制,并对这个引擎的功能和使用做一个总结。
下面贴完整代码
import ppb
from ppb.features.animation import Animation
score= 0
class Blob(ppb.Sprite):
image = Animation("resources/blob_{0..6}.png", 10)
layer= 3
def on_button_pressed(self, event, signal):
if event.button== ppb.buttons.Primary:
p1= (event.position- self.position).normalize()
event.scene.add(Projectile(position=self.position, direction= p1))
signal(ppb.events.PlaySound(ppb.Sound('resources/sou.mp3')))
def on_mouse_motion(self, event, signal):
p1= (event.position- self.position).normalize()
self.rotation= 270-p1.angle(ppb.Vector(1,0))
class Target(ppb.Sprite):
image = ppb.Image("resources/Target.png")
speed= 50
layer= 2
def on_update(self, update_event, signal):
global score
self.rotation-=self.speed* update_event.time_delta
for p in update_event.scene.get(kind=Projectile):
if (p.position - self.position).length <= self.size/ 2:
update_event.scene.add(BombImg(position=self.position))
update_event.scene.remove(self)
update_event.scene.remove(p)
score+= 20
signal(ppb.events.PlaySound(ppb.Sound('resources/bomb.mp3')))
signal(ChangeText(score=score))
break
class ChangeText:
def __init__(self, score):
self.score= score
class BombImg(ppb.Sprite):
layer= 1
image = Animation("resources/bomb_{0..15}.png", 16)
def on_update(self, update_event, signal):
if self.image.current_frame==15:
update_event.scene.remove(self)
class Projectile(ppb.Sprite):
image = ppb.Image("resources/Projectile.png")
speed = 6
layer= 2
def on_update(self, update_event, signal):
self._rotation= 90- self.direction.angle(ppb.Vector(1,0))
self.position += self.direction * self.speed * update_event.time_delta
class label1(ppb.Sprite):
image= ppb.Text('射击游戏',font=ppb.Font("resources/happy1.ttf", size=62), color=(255,255,100))
position= ppb.Vector(0,5)
layer= 1
class label2(ppb.Sprite):
image= ppb.Text('score: 0',font=ppb.Font("resources/happy1.ttf", size=62), color=(255,255,100))
position= ppb.Vector(0,1)
score= 0
layer= 1
def on_change_text(self, event, signal):
self.image= ppb.Text('score: '+ str(event.score),font=ppb.Font("resources/happy1.ttf", size=62), color=(255,255,100))
def setup(scene):
scene.add(Blob(pos=(0, -3.5)))
scene.add(label1())
scene.add(label2())
for x in range(-4, 5, 2):
scene.add(Target(position=ppb.Vector(x, 3)))
ppb.run(setup=setup,resolution=(800, 600))
上一篇: [每日一题] 114. 二叉树的右视图(二叉树、层序遍历、递归、多方法)
下一篇: 杂