这节课我们来谈谈 Pygame 中的 播放声音和音效,因为几乎没有任何游戏是一声不吭的,多重的感官体验更能刺激玩家的神经,没有声音的游戏就好比 不蘸番茄的薯条,尽管如此,Pygame 对于声音的处理并不是太理想,我说的是如果你想用 Pygame 做一个炫酷的音乐播放器那可能会让你失望,因为Pygame对于声音格式的支持十分有限,不过对于游戏开发来说,这完全是足够的,我们需要的我们自己转换就可以了。
一般游戏来说,声音主要分为两种,一种是背景音乐,一种是音效。
背景音乐就是时刻伴随着游戏存在的,往往就是重复播放的一首曲子或者歌曲;音效就是在某种条件下被触发产生的,比如两个小球碰撞在一起就会发出 “啪啪啪” 的声音。
刚才我们也说了,Pygame 支持的声音格式十分有限,一般情况下我们使用 .ogg 的格式来做背景音乐,用无压缩的 .wvb 来作为音效。那么你拿到一个 .mp3 格式该怎么办呢?
你可以使用 格式工厂 这类的软件把它转为 .ogg 或者 .wvb 格式。
注意:music 模块虽然写了支持 .mp3 格式,但是它对 .mp3 格式的支持十分有限,经常你会在网上找到一段很好的 .mp3 的曲子,但是载入之后压根没有声音。你把它转为 .ogg 格式就可以很好的支持了。
播放音效我们使用 mixer 模块,在使用之前,需要先生成一个 Sound 对象,对这个 Sound 对象进行控制,Sound 对象 的 play() 方法就是播放音效,稍候我们来讲这些方法。
播放背景音乐我们使用 music 模块,music 模块和 mixer 模块是紧密关联的。
方法 |
含义 |
play() |
播放音效 |
stop() |
停止播放 |
fadeout() |
淡出 |
set_volume() |
设置音量 |
get_volume() |
获取音量 |
get_num_channels() |
计算该音效播放了多少次 |
get_length() |
获得该音效的长度 |
get_raw() |
将该音效以二进制格式的字符串返回 |
方法 |
含义 |
load() |
载入音乐 |
play() |
播放音乐 |
rewind() |
重新播放 |
stop() |
停止播放 |
pause() |
暂停播放 |
unpause() |
恢复播放 |
fadeout() |
淡出 |
set_volume() |
设置音量 |
get_volume() |
获取音量 |
get_busy() |
检测音乐流是否正在播放 |
set_pos() |
设置开始播放的位置 |
get_pos() |
获取已经播放的时间 |
queue() |
将音乐文件放入待播放列表中 |
set_endevent() |
在音乐播放完毕时发送事件 |
get_endevent() |
获取音乐播放完毕时发送的事件类型 |
我们来举个例子:
要求:写一个程序,打开程序就会自动播放背景音乐(bg_music.ogg),当你在窗口中点击鼠标左键时,就会播放 winner.wav 音效;点击鼠标右键,就会播放 loser.wav 音效;点击空格键就暂停背景音乐,再次点击就继续播放。
from pygame.locals import *
from random import *
import pygame
import math
import sys
def main() :
pygame.init()
pygame.mixer.init()
pygame.mixer.init()#初始化混音器模块(可以不写,上面初始化已经包含了,最好写上)
bg_music = r"D:\Code\Python\Pygame\pygame8:播放声音和音效\bg_music.ogg"
hole_music = r"D:\Code\Python\Pygame\pygame8:播放声音和音效\hole.wav"
laugh_music = r"D:\Code\Python\Pygame\pygame8:播放声音和音效\laugh.wav"
pygame.mixer.music.load(bg_music) #加载背景音乐
pygame.mixer.music.set_volume(0.2) #设置背景音乐音量
pygame.mixer.music.play()
pygame.mixer.Sound(hole_music) #加载点击鼠标左键发生的音乐
pygame.mixer.Sound(hole_music).set_volume(0.2) #设置音量
pygame.mixer.Sound(laugh_music) #加载点击鼠标右键发生的音乐
pygame.mixer.Sound(laugh_music).set_volume(0.2) #设置音量
bg_size = width , height = 500 , 450 #设置窗口
screen = pygame.display.set_mode(bg_size)
pygame.display.set_caption("music-demo")
bg = pygame.image.load(r"D:\Code\Python\Pygame\pygame8:播放声音和音效\jieshao.png").convert_alpha() #加载背景图片
pause_image = pygame.image.load(r"D:\Code\Python\Pygame\pygame8:播放声音和音效\pause.png").convert_alpha() #加载暂停播放图片
unpause_image = pygame.image.load(r"D:\Code\Python\Pygame\pygame8:播放声音和音效\unpause.png").convert_alpha() #加载继续播放图片
pause_rect = pause_image.get_rect() #得到图片的尺寸
clock = pygame.time.Clock()
position = ((width - pause_rect.width) // 2 , (height - pause_rect.height) // 2 + 150 ) #图片记载的位置
flag = 0 #设置一个flag变量使其能够判断现在是暂停还是继续
while True :
for event in pygame.event.get():
if event.type == QUIT:
sys.exit()
elif event.type == MOUSEBUTTONDOWN: #如果鼠标按下
if event.button == 1: #如果按下的是鼠标左键
pygame.mixer.Sound(hole_music).play() #播放hole_music
elif event.button == 3: #如果按下的是鼠标右键
pygame.mixer.Sound(laugh_music).play() #播放laugh_music
elif event.type == pygame.KEYDOWN: #如果键盘按下:
if event.key == pygame.K_SPACE and flag == 0: #如果按下的是空格且 flag == 0
flag += 1
elif event.key == pygame.K_SPACE and flag == 1: #如果按下的是空格且 flag == 1
flag -= 1
#窗口背景白色
screen.fill((255, 255, 255))
# 更新图像
screen.blit(bg, (0,0))
if flag :
screen.blit(pause_image,position) #记载暂停播放图片
pygame.mixer.music.pause() #暂停播放背景音乐
else :
screen.blit(unpause_image,position) #记载继续播放图片
pygame.mixer.music.unpause() #继续播放背景音乐
# 更新界面
pygame.display.flip()
clock.tick(30)
if __name__ == "__main__":
main()
现在我们就来把我们学到的新东西加到 Play TheBall 小游戏中。我们继续完善我们的代码:
有一点需要讲解的:
背景音乐会贯穿游戏的始终,背景音乐完整播放一次我们视为游戏的时间,因此我们需要想办法让游戏在背景音乐停止时结束,我们应该有留意到:
music 模块有一个 set_endevent() 方法,该方法的作用就是在音乐播放完时发送一条事件消息,发送什么消息是我们自定义的,USEREVENT 就是自定义消息,Pygame 给我们预定了很多事件,像我们熟悉的 键盘事件、鼠标事件等。这些预定义的事件都有一个标记符,
例如:MOUSEBUTTONDOWN 、KEYDOWN等。这些都是一些数字的等值定义,其实在内部,2 就表示鼠标按下,但是人类难以记住,所以定义为 MOUSEBUTTONDOWN。
USEREVENT 就是数字24,24以上就是我们可以自定义的事件,我们可以像这样自定义事件:
MY_EVENT = USEREVENT。
MY_EVENT_1 = USEREVENT + 1........
代码如下:
from pygame.locals import *
from random import *
import pygame
import time
import math
import sys
class Ball(pygame.sprite.Sprite) : #继承动画精灵基类
def __init__ (self,imgae,position,speed,bg_size) :
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(imgae).convert_alpha()
self.rect = self.image.get_rect() #获得球的尺寸
self.rect.left , self.rect.top = position #将出现的位置赋给球
self.speed = speed #设置速度
self.width , self.height = bg_size[0] , bg_size[1] #获得活动边界,就是背景的边界
def move(self):
self.rect = self.rect.move(self.speed) #根据自身的速度进行移动
if self.rect.right < 0: #图片的右边已经超出边界的左边,即整个球已经出界
self.rect.left = self.width #让他从右边界回来
if self.rect.bottom < 0: #图片的底已经超出边界的上面
self.rect.top = self.height #让他从底部回来
if self.rect.left > self.width: #图片的左边已经超出边界的右边
self.rect.right = 0 #让他从左边回来
if self.rect.top > self.height: #如果图片的顶部已经超出边界的底部
self.rect.bottom = 0 #让他从顶部回来
#判断碰撞检测函数
def collide_check(item,target):
col_balls = [] #添加碰撞小球
for each in target: #对 target 中所有的目标小球进行检测
#两个球心之间的距离
distance = math.sqrt( math.pow( (item.rect.center[0] - each.rect.center[0]) , 2 ) + \
math.pow( (item.rect.center[1] - each.rect.center[1]) , 2) )
if distance <= ( item.rect.width + each.rect.width ) / 2: #如果距离小于等于两者间的半径之和也就是两个直径之和的一半
col_balls.append(each) #将这个发生碰撞的小球添加到列表中
return col_balls
def main() :
pygame.init()
bg_image = r"D:\Code\Python\Pygame\pygame6:动画精灵\background.png"
ball_image = r"D:\Code\Python\Pygame\pygame6:动画精灵\gray_ball.png"
running = True #为了以后而已有多种方法退出程序
#加载背景音乐
pygame.mixer.music.load(r"D:\Code\Python\Pygame\pygame8:播放声音和音效\bg_music.ogg")
pygame.mixer.music.set_volume(0.2) #设置音量
pygame.mixer.music.play() #播放背景音乐
#设置一个背景音乐完毕之后的结束时间
GAMEOVER = USEREVENT
pygame.mixer.music.set_endevent(GAMEOVER) #当音乐播完后,发送一个GAMEOVER事件
#加载音效
hole_music = pygame.mixer.Sound(r"D:\Code\Python\Pygame\pygame8:播放声音和音效\loser.wav")
laugh_music = pygame.mixer.Sound(r"D:\Code\Python\Pygame\pygame8:播放声音和音效\laugh.wav")
winner_music = pygame.mixer.Sound(r"D:\Code\Python\Pygame\pygame8:播放声音和音效\winner.wav")
loser_music = pygame.mixer.Sound(r"D:\Code\Python\Pygame\pygame8:播放声音和音效\loser.wav")
bg_size = width , height = 1024 , 681 #背景大小
screen = pygame.display.set_mode(bg_size) # 设置背景大小
background = pygame.image.load(bg_image).convert_alpha() #画背景
balls = []
# 创建五个小球
BALL_NUM = 5
for i in range (BALL_NUM) : #生成5个球
position = randint (0,width-100) , randint(0,height-100) #要减去100是因为球图片尺寸的大小为100,随机生成位置
speed = [ randint (-10,10) , randint(-10,10) ]
ball = Ball(ball_image,position,speed,bg_size) #生成球的对象
while collide_check(ball,balls): #如果生成的小球和之前的球发生碰撞,那么重新生成小球
ball.rect.left , ball.rect.top = randint (0,width-100) , randint(0,height-100)
balls.append(ball) #将所有的球对象添加到类表中,方便管理
clock = pygame.time.Clock() #生成刷新帧率控制器
while running :
for event in pygame.event.get():
if event.type == QUIT:
sys.exit()
elif event.type == GAMEOVER: #如果游戏结束
loser_music.play() #播放失败的音乐
time.sleep(2) #延时2s
laugh_music.play() #播放大笑音效
running = False
screen.blit(background, (0, 0)) #将背景画到screen上
for each in balls: #每个球进行移动并重新绘制
each.move()
screen.blit(each.image, each.rect)
for i in range (BALL_NUM) : #循环5个小球,分别判断这个小球有没有和另外四个小球发生碰撞
item = balls.pop(i) #因为是判断和其他四个小球,所以需要先将这个小球取出
if collide_check( item , balls ): #调用碰撞检测的函数,如果结果为真,也就是有发生碰撞的小球
item.speed[0] = - item.speed[0] #碰撞后向反方向运动
item.speed[1] = - item.speed[1]
balls.insert(i , item) #最后不要忘记把这个小球放回原位
pygame.display.flip()
clock.tick(30)
if __name__ == "__main__":
main()