用Python和Pygame写游戏-从入门到精通(22)

By | 2011/08/26

辛苦啦~ 这次是我们系统的pygame理论学习的最后一章了,把这次的音乐播放讲完了,pygame的基础知识就全部OK了。不过作为完整的教程,只有理论讲解太过枯燥了,我随后还会加一个或更多的实践篇系列,看需要可能也会追加真3D等额外的内容。

就像上次所说的,pygame.mixer并不适合播放长时间的音乐播放,我们要使用pygame.mixer.music。

pygame.mixer.music用来播放MP3和OGG音乐文件,不过MP3并不是所有的系统都支持(Linux默认就不支持MP3播放),所以最好还是都用Ogg文件,我们可以很容易把MP3转换为Ogg文件,自己搜一下吧。

我们使用pygame.mixer.music.load()来加载一个文件,然后使用pygame.mixer.music.play()来播放,这里并没有一个类似Music这样的类和对象,因为背景音乐一般般只要有一个在播放就好了不是么~不放的时候就用stop()方法来停止就好了,当然很自然有类似录影机上的pause()和unpause()方法。

音效和音乐方法总结

Sound对象

方法名 作用
fadeout 淡出声音,可接受一个数字(毫秒)作为淡出时间
get_length 获得声音文件长度,以秒计
get_num_channels 声音要播放多少次
get_volume 获取音量(0.0 ~ 1.0)
play 开始播放,返回一个Channel对象,失败则返回None
set_volume 设置音量
stop 立刻停止播放

Channels对象

方法名 作用
fadeout 类似
get_busy 如果正在播放,返回true
get_endevent 获取播放完毕时要做的event,没有则为None
get_queue 获取队列中的声音,没有则为None
get_volume 类似
pause 暂停播放
play 类似
queue 将一个Sound对象加入队列,在当前声音播放完毕后播放
set_endevent 设置播放完毕时要做的event
set_volume 类似
stop 立刻停止播放
unpause 继续播放

Music对象:

方法名 作用
fadeout 类似
get_endevent 类似
get_volume 类似
load 加载一个音乐文件
pause 类似
play 类似
rewind 从头开始重新播放
set_endevent 类似
set_volume 类似
stop 立刻停止播放
unpause 继续播放
get_pos 获得当前播放的位置,毫秒计

虽然很简单,不过还是提供一个例程吧,这里面音乐的播放很简单,就是上面讲过的,不过其中还有一点其他的东西,希望大家学习一下pygame中按钮的实现方法。

界面如上,运行的时候,脚本读取./MUSIC下所有的OGG和MP3文件(如果你不是Windows,可能要去掉MP3的判断),显示的也很简单,几个控制按钮,下面显示当前歌名(显示中文总是不那么方便的,如果你运行失败,请具体参考代码内的注释自己修改):

# -*- coding: utf-8 -*-
# 注意文件编码也必须是utf-8
SCREEN_SIZE = (800, 600)
# 存放音乐文件的位置
MUSIC_PATH = "./MUSIC"

import pygame
from pygame.locals import *
from math import sqrt
import os
import os.path

def get_music(path):

    # 从文件夹来读取所有的音乐文件
    raw_filenames = os.listdir(path)

    music_files = []
    for filename in raw_filenames:
        # 不是Windows的话,还是去掉mp3吧
        if filename.lower().endswith('.ogg') or filename.lower().endswith('.mp3'):
            music_files.append(os.path.join(MUSIC_PATH, filename))

    return sorted(music_files)

class Button(object):
    """这个类是一个按钮,具有自我渲染和判断是否被按上的功能"""
    def __init__(self, image_filename, position):

        self.position = position
        self.image = pygame.image.load(image_filename)

    def render(self, surface):
        # 家常便饭的代码了
        x, y = self.position
        w, h = self.image.get_size()
        x -= w / 2
        y -= h / 2
        surface.blit(self.image, (x, y))

    def is_over(self, point):
        # 如果point在自身范围内,返回True
        point_x, point_y = point
        x, y = self.position
        w, h = self.image.get_size()
        x -= w /2
        y -= h / 2

        in_x = point_x >= x and point_x < x + w
        in_y = point_y >= y and point_y < y + h
        return in_x and in_y

def run():

    pygame.mixer.pre_init(44100, 16, 2, 1024*4)
    pygame.init()
    screen = pygame.display.set_mode(SCREEN_SIZE, 0)     

    #font = pygame.font.SysFont("default_font", 50, False)
    # 为了显示中文,我这里使用了这个字体,具体自己机器上的中文字体请自己查询
    # 详见本系列第四部分://eyehere.net/2011/python-pygame-novice-professional-4/
    font = pygame.font.SysFont("simsunnsimsun", 50, False)    

    x = 100
    y = 240
    button_width = 150
    buttons = {}
    buttons["prev"] = Button("prev.png", (x, y))
    buttons["pause"] = Button("pause.png", (x+button_width*1, y))
    buttons["stop"] = Button("stop.png", (x+button_width*2, y))
    buttons["play"] = Button("play.png", (x+button_width*3, y))
    buttons["next"] = Button("next.png", (x+button_width*4, y))

    music_filenames = get_music(MUSIC_PATH)
    if len(music_filenames) == 0:
        print "No music files found in ", MUSIC_PATH
        return

    white = (255, 255, 255)
    label_surfaces = []
    # 一系列的文件名render
    for filename in music_filenames:
        txt = os.path.split(filename)[-1]
        print "Track:", txt
        # 这是简体中文Windows下的文件编码,根据自己系统情况请酌情更改
        txt = txt.split('.')[0].decode('gb2312')
        surface = font.render(txt, True, (100, 0, 100))
        label_surfaces.append(surface)

    current_track = 0
    max_tracks = len(music_filenames)
    pygame.mixer.music.load( music_filenames[current_track] )  

    clock = pygame.time.Clock()
    playing = False
    paused = False

    # USEREVENT是什么?请参考本系列第二部分:
    # //eyehere.net/2011/python-pygame-novice-professional-2/
    TRACK_END = USEREVENT + 1
    pygame.mixer.music.set_endevent(TRACK_END)

    while True:

        button_pressed = None

        for event in pygame.event.get():

            if event.type == QUIT:
                return

            if event.type == MOUSEBUTTONDOWN:

                # 判断哪个按钮被按下
                for button_name, button in buttons.iteritems():
                    if button.is_over(event.pos):
                        print button_name, "pressed"
                        button_pressed = button_name
                        break

            if event.type == TRACK_END:
                # 如果一曲播放结束,就“模拟”按下"next"
                button_pressed = "next"

        if button_pressed is not None:

            if button_pressed == "next":
                current_track = (current_track + 1) % max_tracks
                pygame.mixer.music.load( music_filenames[current_track] )
                if playing:
                    pygame.mixer.music.play()

            elif button_pressed == "prev":

                # prev的处理方法:
                # 已经播放超过3秒,从头开始,否则就播放上一曲
                if pygame.mixer.music.get_pos() > 3000:
                    pygame.mixer.music.stop()
                    pygame.mixer.music.play()
                else:
                    current_track = (current_track - 1) % max_tracks
                    pygame.mixer.music.load( music_filenames[current_track] )
                    if playing:
                        pygame.mixer.music.play()

            elif button_pressed == "pause":
                if paused:
                    pygame.mixer.music.unpause()
                    paused = False
                else:
                    pygame.mixer.music.pause()
                    paused = True

            elif button_pressed == "stop":
                pygame.mixer.music.stop()
                playing = False

            elif button_pressed == "play":
                if paused:
                    pygame.mixer.music.unpause()
                    paused = False
                else:
                    if not playing:
                        pygame.mixer.music.play()
                        playing = True

        screen.fill(white)

        # 写一下当前歌名
        label = label_surfaces[current_track]
        w, h = label.get_size()
        screen_w = SCREEN_SIZE[0]
        screen.blit(label, ((screen_w - w)/2, 450))

        # 画所有按钮
        for button in buttons.values():
            button.render(screen)

        # 因为基本是不动的,这里帧率设的很低
        clock.tick(5)
        pygame.display.update()

if __name__ == "__main__":

    run()

这个程序虽然可以运行,还是很简陋,有兴趣的可以改改,比如显示播放时间/总长度,甚至更厉害一点,鼠标移动到按钮上班,按钮会产生一点变化等等,我们现在已经什么都学过了,唯一欠缺的就是实践而已!

所以下一次,我将开始一个实战篇,用pygame书写一个真正可以玩的游戏,敬请期待~~

>> 用Python和Pygame写游戏-从入门到精通(目录)

本次使用的几个图像文件打包下载(也可以自己找更帅的)。

29 thoughts on “用Python和Pygame写游戏-从入门到精通(22)

  1. Clover

    – 列表里面有两个Sound Object,第二个应该是Music Object

    Reply
  2. 1111

    那些表格:
    方法名 左右

    是”作用“,错别字

    Reply
  3. abcd1234

    如果是中文名字的音乐文件会出现这个
    pygame.mixer.music.load( music_filenames[current_track] )
    error: MPEG file does not have any audio stream.
    不明白什么意思,用的windows

    Reply
    1. xishui Post author

      这个应该和pygame没关系,请确认您读取的音乐文件有没有什么特别的,换一些试试?

      Reply
  4. 浮游

    不能打开中文名字的歌曲

    pygame.mixer.music.load( music_filenames[current_track] )
    pygame.error: Couldn’t open ‘D:MUSIC怎么了.mp3’

    Reply
  5. criss

    请问博主,有什么方法可以获取music对象的length?
    貌似把MP3文件加载为sound对象,然后用get_length获取的长度不太对。
    真心求教

    Reply
    1. xishui Post author

      能提供的答案可能无法让您满意,据我所知,pygame没有这个能力,您也许应该使用其他的库来做,例如pymad等。。个人意见,我想以后您一定能发现更好的方法!

      Reply
  6. JBSang

    亲爱的博主,为什么我的“飞鼠溪”的歌名字叫“BGM.mp3”,导入后播放,就发出了“嘟嘟嘟嘟嘟”的声音,不能正常播放。BTW,永久循环播放代码是啥?

    Reply
    1. xishui Post author

      亲爱的JBSang,上面显示的名字是文件名,再加上它的声音是“嘟嘟嘟”,我相当怀疑您播放的文件和您想要播放的文件恐怕不是同一个……至于永久循环播放,很明显,您应该在“TRACK_END”那里做一下处理就OK了~~

      Reply
    2. 白胡子老王

      你确定不是用的紫霞的宝剑发出的声音?

      Reply
  7. JBSang

    博主有qq么?可以让我们这些小白加好友么?

    Reply
  8. RichardRui

    博主你好,请问:pygame.mixer.music.load(music_filenames[current_track])
    error: MPEG file does not have any audio stream.
    是什么情况

    Reply
  9. yellow

    这个例子是不是会经常内存溢出?我运行几次 都很容易崩溃 请博主说明一下是不是的呢?

    Reply
  10. 孟雨

    我的是win8系统,最后运行不成功,
    Traceback (most recent call last):
    File “D:\0831\musicplay.py”, line 183, in
    run()
    File “D:\0831\musicplay.py”, line 92, in run
    pygame.mixer.music.load( music_filenames[current_track] )
    error: Couldn’t open ‘D:\KuGou\李宗盛 – 鬼迷心窍.mp3’
    求助

    Reply
    1. xishui Post author

      先试试纯英文名,再试试ogg文件而不是mp3,还不行的话?

      Reply
      1. 孟雨

        英文名mp3文件可以,但是中文名还是不可以
        Track: guimixinqiao.mp3
        Track: meilibennvren.mp3
        Track: zanmenjiehunba.mp3
        Track: 李宗盛 – 鬼迷心窍.mp3
        Track: 李玟 – 美丽笨女人.mp3
        Track: 云菲菲、冷漠 – 这条街.mp3
        Track: 咱们结婚吧.mp3
        Track: 侃侃 – 滴答.mp3
        play pressed
        next pressed
        next pressed
        next pressed

        Traceback (most recent call last):
        File “D:\0831\musicplay.py”, line 180, in
        run()
        File “D:\0831\musicplay.py”, line 124, in run
        pygame.mixer.music.load( music_filenames[current_track])
        error: Couldn’t open ‘D:\KuGou\李宗盛 – 鬼迷心窍.mp3’

        Reply
  11. 孟雨

    看了许多编码方面的文档还没有解决

    Reply
    1. Sam100

      在内部把它的名字改成英文再播放不就行了嘛。

      Reply
  12. Pingback: 用Python和Pygame写游戏-从入门到精通(22) | 演道网

  13. Grothen

    为什么stop()之后就直接切到下一首歌了

    Reply
  14. 谈政

    有没有遇到 在播放器界面 中文是乱码的

    Reply
  15. circle orbit

    有个小bug,“prev”按钮里面如果播放超过3秒重头播放条件里面的pygame.mixer.music.stop()貌似会触发pygame.mixer.music.set_endevent(TRACK_END) 这个事件,导致本代码里的“next”按钮被按下

    Reply

Sam100进行回复 取消回复

您的电子邮箱地址不会被公开。