为游戏添加音效,可以增加更多的趣味性
前面的文章《python小欢喜(八)俄罗斯方块 (11) 判断游戏是否结束》实现了俄罗斯方块游戏的基本功能。
接下来给游戏添加上音效。
先准备好音效素材:
其中
background.mp3 是背景音乐
down.wav 是方块落下来碰到其它方块的声音
remove.wav是一行方块被消去的声音
gameover.wav是游戏结束的声音
需要注意的是:
1 对于背景音乐,这一类播放时间比较长的声音资源
使用 pygame.mixer.music 进行处理
2 对于时间比较短的音效资源
使用 pygame.mixer.Sound 进行处理。并且 pygame.mixer.Sound 一般用于加载wav文件,不能加载mp3文件。
使用声音功能前需要先做初始化
pygame.mixer.pre_init(44100, -16, 2, 512)
pygame.mixer.init() # 初始化混音器
下面用这个游戏中的例子分别说一下 music与sound的用法。
1 播放背景音乐
#加载音乐文件
mp3_backGround = pygame.mixer.music.load("audio/background.mp3")
#设置背景音乐的音量
pygame.mixer.music.set_volume(Config.bgMusicVolume)
#播放背景音乐,参数-1表示循环播放
pygame.mixer.music.play(-1)
2 播放音效
#加载wave文件
wave_down = pygame.mixer.Sound("audio/down.wav")
#播放音效
wave_down.play()
以上只是例子,在实际播放时要在合适的时机调用play方法。
完整的python代码如下
config.py
# config.py
# 配置数据,全局变量的定义
#颜色常量的定义
BLACK = (0,0,0) # 用RGB值定义黑色
WHITE = (255,255,255) # 用RGB值定义白色
#配置参数类
class Config():
def __init__(self):
pass
screenWidth = 640
screenHeight= 800
blockWidth = 40
blockColor = 'blue'
downSpeed = 2
bgMusicVolume = 0.2
#方块组合形状的二维矩阵图示,1表示该处有方块,0表示没有
shapeGraph=[
(
[1,1,1,1],
[0,0,0,0],
[0,0,0,0],
[0,0,0,0],
),
(
[0,1,0,0],
[1,1,1,0],
[0,0,0,0],
[0,0,0,0],
),
(
[1,0,0,0],
[1,0,0,0],
[1,0,0,0],
[1,0,0,0],
),
(
[1,1,1,0],
[1,0,0,0],
[0,0,0,0],
[0,0,0,0],
),
(
[1,0,0,0],
[1,0,0,0],
[1,1,0,0],
[0,0,0,0],
),
(
[1,1,0,0],
[1,0,0,0],
[1,0,0,0],
[0,0,0,0],
),
(
[1,0,0,0],
[1,1,0,0],
[1,0,0,0],
[0,0,0,0],
),
(
[1,1,0,0],
[0,1,1,0],
[0,0,0,0],
[0,0,0,0],
),
(
[1,1,0,0],
[1,1,0,0],
[0,0,0,0],
[0,0,0,0],
),
]
#将形状图转换为形状坐标列表
def shpaeGraph2List(shapeGraph):
shapeList =[]
for g in shapeGraph:
shape=[]
for y in range(4):
for x in range(4):
if g[y][x] == 1 :
shape.append([x*Config.blockWidth,y*Config.blockWidth])
shapeList.append(shape)
return shapeList
#存放形状初始坐标列表的全局变量 shapeList
shapeList = shpaeGraph2List(shapeGraph)
block.py
# block.py
# 方块及方块组合类的定义
import pygame
from PIL import Image,ImageDraw
from config import *
pygame.mixer.pre_init(44100, -16, 2, 512)
pygame.mixer.init() # 初始化混音器
mp3_backGround = pygame.mixer.music.load("audio/background.mp3")
wave_down = pygame.mixer.Sound("audio/down.wav")
wave_remove = pygame.mixer.Sound("audio/remove.wav")
wave_gameover = pygame.mixer.Sound("audio/gameover.wav")
# 方块类
class Block(pygame.sprite.Sprite):
def __init__(self,x,y):
self.inix = x
self.iniy = y
pygame.sprite.Sprite.__init__(self)
self.image = self.createBlockImg()
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
#下移的次数,在清除底部填满的行时会用到
self.downTimes = 0
#生成方块图片
def createBlockImg(self):
#生成一个指定大小与颜色的矩形图像
im = Image.new('RGB',(Config.blockWidth,Config.blockWidth),Config.blockColor)
draw = ImageDraw.Draw(im)
#画一个黑色的边框
draw.line([(0,0),(0,Config.blockWidth)],'black',1)
draw.line([(0,Config.blockWidth),(Config.blockWidth,Config.blockWidth)],'black',1)
draw.line([(Config.blockWidth,Config.blockWidth),(Config.blockWidth,0)],'black',1)
draw.line([(Config.blockWidth,0),(0,0)],'black',1)
data = im.tobytes() #转换成字节
size = im.size #图形分辨率
mode = im.mode
return pygame.image.fromstring(data,size,mode)
#重置初始位置
def reset(self):
self.rect.x = self.inix
self.rect.y = self.iniy
#方块向下移动
def down(self,distance):
# 向下移动
self.rect.centery += distance
#方块左右移动
def move(self,distance):
# 左右移动
self.rect.centerx += distance
# 表示下落中的多个方块的组合,typeIdx指明组合形状在shapeList中的索引
class FallingGroup(pygame.sprite.Group):
def __init__(self, typeIdx,bottomGroup):
self.bottomGroup = bottomGroup
pygame.sprite.Group.__init__(self)
iniX = int((Config.screenWidth/2)/Config.blockWidth)*Config.blockWidth
shape = shapeList[typeIdx]
for xyPair in shape:
x = iniX+xyPair[0]
y = xyPair[1]
self.add(Block(x,y))
#得到组合对象的包络矩形
self.rect = self.boundingRect()
#记录初始位置,此处要使用copy方法
self.iniRect = self.rect.copy()
#下落速度
self.downSpeed = Config.downSpeed
#重置初始位置
def reset(self):
#恢复初始位置,此处要使用copy方法
self.rect = self.iniRect.copy()
for block in self.sprites():
block.reset()
#方块组合向下移动
def down(self, distance):
if not self.bottomGroup.collided(self):
self.rect.y += distance
for block in self.sprites():
block.down(distance)
#检查一下,经过下移后是否即将发生碰撞,这时在视觉上已经挨在一起了,应该播放音效
if self.bottomGroup.collided(self):
#播放音效
wave_down.play()
else:
#self.reset()
#被底部方块组合吃掉
self.bottomGroup.eat(self)
#方块组合左右移动
def move(self, distance):
#print([self.rect.x,self.rect.y,self.rect.width,self.rect.height])
if (distance > 0 and self.rect.x < Config.screenWidth-self.rect.width) or (distance < 0 and self.rect.x > 0):
self.rect.x += distance
for block in self.sprites():
block.move(distance)
#求出包围组合对象的矩形
def boundingRect(self):
minX = Config.screenWidth+100
minY = Config.screenHeight+100
maxX = -100
maxY = -100
for block in self.sprites():
if block.rect.x < minX:
minX = block.rect.x
if block.rect.y < minY:
minY = block.rect.y
if block.rect.x > maxX:
maxX = block.rect.x
if block.rect.y > maxY:
maxY = block.rect.y
return pygame.Rect(minX,minY,maxX-minX+Config.blockWidth,maxY-minY+Config.blockWidth)
#旋转
def rotate(self):
#取组合对象的中心点作为旋转中心,旋转中心应位于网格点上
cx=int((self.rect.x+self.rect.width/2)/Config.blockWidth)*Config.blockWidth
cy=int((self.rect.y+self.rect.height/2)/Config.blockWidth)*Config.blockWidth
for block in self.sprites():
#求出当前方块的中心与旋转中心的距离差
dx = block.rect.centerx -cx
dy = block.rect.centery -cy
#距离差组成的复数 乘上 复数 i ,得到的复数是 原复数逆时针旋转90度的结果
r = complex(dx,dy)*complex(0,1)
#得到旋转之后的结果
block.rect.centerx = cx + r.real + Config.blockWidth
block.rect.centery = cy + r.imag
#取得包络矩形的原始水平位置
lastRectX = self.rect.x
#更新组合对象的包络矩形
self.rect = self.boundingRect()
dx = lastRectX - self.rect.x
#使得旋转后的组合对象的水平位置保持不变
self.rect.x += dx
for block in self.sprites():
block.rect.x+=dx
# 表示底部方块的组合
class BottomGroup(pygame.sprite.Group):
def __init__(self):
pygame.sprite.Group.__init__(self)
#标记游戏是否结束
self.gameOver = False
#记录得分
self.score = 0
#检查下落的方块是否与底部方块发生了碰撞
def collided(self,fallingGroup):
#检查是否碰到了窗口边界
for d in fallingGroup.sprites():
if Config.screenHeight - d.rect.y <=Config.blockWidth:
return True
#检查是否碰到了停在底部的方块
for d in fallingGroup.sprites():
for b in self.sprites():
if b.rect.y - d.rect.y <=Config.blockWidth and b.rect.x == d.rect.x:
return True
return False
#吃掉下落的方块
def eat(self,fallingGroup):
for d in fallingGroup.sprites():
self.add(d)
#清除下落方块组合中的所有元素
fallingGroup.empty()
#获取当前的行集信息
lineSet = self.getLineSet()
#检查停留的方块是否达到了最上面一行
if 0 in lineSet.keys():
wave_gameover.play()
self.gameOver = True
print('game over')
return 'over'
#清除掉已经填满的行
self.removeFullLine(lineSet)
return 'ok'
#检查底部方块的每一行,如果一行被方块填满后,移除此行
def removeFullLine(self,lineSet):
#被移除的行的y坐标组成的列表,初始值为空列表
removeLines =[]
self.printLineSet(lineSet)
for y in lineSet:
#如果该行的方块数目已经到达一行中最大的方块数目
if len(lineSet[y]) >= int(Config.screenWidth/Config.blockWidth):
#逐一清除方块,注意调用了sprite对象的kill方法
for b in lineSet[y]:
b.kill()
lineSet[y]=[]
removeLines.append(y)
#添加音效
wave_remove.play()
self.printLineSet(lineSet)
#增加游戏得分值
self.score += 10*len(removeLines)
#剩下的方块如果比被移除的行的位置要高,则向下移动
for b in self.sprites():
b.downTimes = 0
for height in removeLines:
if height > b.rect.y:
#记录该方块应该下移的次数
b.downTimes+=1
#调用block的移动方法
for b in self.sprites():
b.down(b.downTimes*Config.blockWidth)
#获取行集信息
def getLineSet(self):
lineSet ={}
#逐一检查底部方块组合中的每一个方块
for b in self.sprites():
if b.rect.y in lineSet.keys():
lineSet[b.rect.y].append(b)
else:
lineSet[b.rect.y]=[b]
return lineSet
#输出行集中的内容,输出这一行的高度,行中方块的数目,行中每一个方标的x坐标
def printLineSet(self,lineSet):
print('-------LineSet------------')
for y in lineSet:
xPos=[]
for b in lineSet[y]:
xPos.append(b.rect.x)
print(y,len(lineSet[y]),xPos)
main.py
# main.py
# 俄罗斯方块,主程序所在文件
# 请使用python3,并安装好pygame与pillow模块
import pygame
from block import *
import random
#随机生成下落方块组合
def randomFallingGroup():
#return FallingGroup(typeIdx=0,bottomGroup=bottomGroup)
return FallingGroup(typeIdx=random.randint(0,8),bottomGroup=bottomGroup)
# 绘制场景
def showScene():
global fallingGroup,bottomGroup,lastCheckPoint
#设置屏幕为黑色
screen.fill(BLACK)
if bottomGroup.gameOver :
showText("游戏结束!!! 得分: " + str(bottomGroup.score))
else:
showText("当前得分: " + str(bottomGroup.score))
#根据下落速度,计算出两个检查点的间隔时间(单位毫秒),如果到达了下一个检查点,则执行下落方法
if pygame.time.get_ticks() - lastCheckPoint > 1000/fallingGroup.downSpeed:
#下落方块组合执行下落方法
fallingGroup.down(Config.blockWidth)
lastCheckPoint = pygame.time.get_ticks()
#如果下落的方块组合已经被底部方块组合“吃”掉了,则生成新的下落方块组合
if len(fallingGroup.sprites()) <= 0:
fallingGroup = randomFallingGroup()
#下落方块组合执行绘制方法
fallingGroup.draw(screen)
#底部方块组合执行绘制方法
bottomGroup.draw(screen)
#刷新屏幕
pygame.display.flip()
#显示文本
def showText(tip):
# 输出提示信息
text = font.render(tip, True, WHITE)
text_rect = text.get_rect()
text_rect.x = 10
text_rect.y = 10
screen.blit(text, text_rect)
# ------------------------main---------------------------------------------------------------------
# 初始化pygame
pygame.init()
#游戏窗口的屏幕
screen = pygame.display.set_mode([Config.screenWidth,Config.screenHeight])
#用黑色填充背景
screen.fill(BLACK)
#设置图形窗口标题
pygame.display.set_caption("俄罗斯方块")
#游戏时钟
clock = pygame.time.Clock()
#生成底部方块组合对象
bottomGroup =BottomGroup()
#随机生成一个下落方块组合对象
fallingGroup = randomFallingGroup()
#下落时刻检查点
lastCheckPoint = pygame.time.get_ticks()
#设置输出文本所用的字体
font = pygame.font.Font("C:\Windows\Fonts\STSONG.TTF", 24)
#设置背景音乐的音量
pygame.mixer.music.set_volume(Config.bgMusicVolume)
#播放背景音乐,参数-1表示循环播放
pygame.mixer.music.play(-1)
# 事件处理循环
running = True
while running:
#设定每秒帧数
clock.tick(30)
#获取当前发生的事件
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
#如果游戏已经结束,则跳过对按键事件的处理
if bottomGroup.gameOver :
continue
if event.type == pygame.KEYDOWN: # 如果按下了键盘上的键
if event.key == pygame.K_LEFT: # 如果按下了向左的方向键
#向左平移一格
fallingGroup.move(-1*Config.blockWidth)
elif event.key == pygame.K_RIGHT: #如果按下了向右的方向键
#向右平移一格
fallingGroup.move(Config.blockWidth)
elif event.key == pygame.K_UP: #如果按下了向上的方向键
#调用旋转方法
fallingGroup.rotate()
elif event.key == pygame.K_DOWN: #如果按下了向下的方向键
#下落速度乘以100
fallingGroup.downSpeed*=100
#绘制场景
showScene()
pygame.quit() #退出pygame
该游戏中用到的音效文件在此下载
提取码:34zg