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

By | 2011/08/23

紧接着上一次,我们继续来看如何在Pygame中使用声音。

Sound对象

在初始化声音系统之后,我们就可以读取一个音乐文件到一个Sound对象中了。pygame.mixer.Sound()接受一个文件名,或者也可以使一个文件对象,不过这个文件必须是WAV或者OGG,切记!

hello_sound = Pygame.mixer.Sound("hello.ogg")

一旦这个Sound对象出来了,你可以使用play方法来播放它。play(loop, maxtime)可以接受两个参数,loop自然就是重复的次数,-1意味着无限循环,1呢?是两次,记住是重复的次数而不是播放的次数;maxtime是指多少毫秒后结束,这个很简单。当你不使用任何参数调用的时候,意味着把这个声音播放一次。一旦play方法调用成功,就会返回一个Channel对象,否则返回一个None。

Channel对象

Channel,也就是声道,可以被声卡混合(共同)播放的数据流。游戏中可以同时播放的声音应该是有限的,pygame中默认是8个,你可以通过pygame.mixer.get_num_channels()来得知当前系统可以同时播放的声道数,而一旦超过,调用sound对象的play方法就会返回一个None,如果你确定自己要同时播放很多声音,可以用set_num_channels()来设定一下,最好一开始就设,因为重新设定会停止当前播放的声音。

那么Channel对象有什么用呢?如果你只是想简单的播放一下声音,那么根本不用管这个东西,不过如果你想创造出一点有意境的声音,比如说一辆火车从左到右呼啸而过,那么应该是一开始左声道比较响,然后相当,最后右声道比较响,直至慢慢消失。这就是Channel能做到的事情。Channel的set_volume(left, right)方法接受两个参数,分别是代表左声道和右声道的音量的小数,从0.0~1.0。

竖起我们的耳朵

OK,来个例子试试吧~

这个例子里我们通过释放金属小球并让它们自由落体和弹跳,听碰撞的声音。这里面不仅仅有这次学习的声音,还有几个一起没有接触到的技术,最重要的一个就是重力的模拟,我们可以设置一个重力因子来影响小球下落的速度,还有一个弹力系数,来决定每次弹跳损失的能量,虽然不难,但是游戏中引入这个东西能让我们的游戏仿真度大大提高。

SCREEN_SIZE = (640, 480)

# 重力因子,实际上是单位 像素/平方秒
GRAVITY = 250.0
# 弹力系数,不要超过1!
BOUNCINESS = 0.7

import pygame
from pygame.locals import *
from random import randint
from gameobjects.vector2 import Vector2

def stero_pan(x_coord, screen_width):
    """这个函数根据位置决定要播放声音左右声道的音量"""
    right_volume = float(x_coord) / screen_width
    left_volume = 1.0 - right_volume
    return (left_volume, right_volume)

class Ball():
    """小球类,实际上我们可以使用Sprite类来简化"""
    def __init__(self, position, speed, image, bounce_sound):
        self.position = Vector2(position)
        self.speed = Vector2(speed)
        self.image = image
        self.bounce_sound = bounce_sound
        self.age = 0.0

    def update(self, time_passed):
        w, h = self.image.get_size()
        screen_width, screen_height = SCREEN_SIZE

        x, y = self.position
        x -= w/2
        y -= h/2
        # 是不是要反弹了
        bounce = False

        # 小球碰壁了么?
        if y + h >= screen_height:
            self.speed.y = -self.speed.y * BOUNCINESS
            self.position.y = screen_height - h / 2.0 - 1.0
            bounce = True
        if x <= 0:
            self.speed.x = -self.speed.x * BOUNCINESS
            self.position.x = w / 2.0 + 1
            bounce = True
        elif x + w >= screen_width:
            self.speed.x = -self.speed.x * BOUNCINESS
            self.position.x = screen_width - w / 2.0 - 1
            bounce = True

        # 根据时间计算现在的位置,物理好的立刻发现这其实不标准,
        # 正规的应该是“s = 1/2*g*t*t”,不过这样省事省时一点,咱只是模拟~
        self.position += self.speed * time_passed
        # 根据重力计算速度
        self.speed.y += time_passed * GRAVITY

        if bounce:
            self.play_bounce_sound()

        self.age += time_passed

    def play_bounce_sound(self):
        """这个就是播放声音的函数"""
        channel = self.bounce_sound.play()

        if channel is not None:
            # 设置左右声道的音量
            left, right = stero_pan(self.position.x, SCREEN_SIZE[0])
            channel.set_volume(left, right)

    def render(self, surface):
        # 真有点麻烦了,有爱的,自己用Sprite改写下吧……
        w, h = self.image.get_size()
        x, y = self.position
        x -= w/2
        y -= h/2
        surface.blit(self.image, (x, y))

def run():
    # 上一次的内容
    pygame.mixer.pre_init(44100, 16, 2, 1024*4)
    pygame.init()
    pygame.mixer.set_num_channels(8)
    screen = pygame.display.set_mode(SCREEN_SIZE, 0)

    pygame.mouse.set_visible(False)
    clock = pygame.time.Clock()

    ball_image = pygame.image.load("ball.png").convert_alpha()
    mouse_image = pygame.image.load("mousecursor.png").convert_alpha()

    # 加载声音文件
    bounce_sound = pygame.mixer.Sound("bounce.ogg")
    balls = []

    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                return
            if event.type == MOUSEBUTTONDOWN:
                # 刚刚出来的小球,给一个随机的速度
                random_speed = ( randint(-400, 400), randint(-300, 0) )
                new_ball = Ball( event.pos,
                                 random_speed,
                                 ball_image,
                                 bounce_sound )
                balls.append(new_ball)

        time_passed_seconds = clock.tick() / 1000.
        screen.fill((255, 255, 255))
        # 为防止小球太多,把超过寿命的小球加入这个“死亡名单”
        dead_balls = []
        for ball in balls:
            ball.update(time_passed_seconds)
            ball.render(screen)
            # 每个小球的的寿命是10秒
            if ball.age > 10.0:
                dead_balls.append(ball)
        for ball in dead_balls:
            balls.remove(ball)

        mouse_pos = pygame.mouse.get_pos()
        screen.blit(mouse_image, mouse_pos)
        pygame.display.update()

if __name__ == "__main__":
    run()

这么久了,咱们的游戏终于有了声音,太棒了!不过,是不是游戏的背景音乐也是用mixer来播放的呢?这不是一个好主意,因为背景音乐往往很长,比较占资源,pygame中另外提供了一个pygame.mixer.music类来控制背景音乐的播放,这也是我们下一次要讲的内容。

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

这次的例程中使用的几个资源:

小球图像:ball.png
光标图像:mousecursor.png
弹跳声音:bounce.ogg

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

  1. zhang

    pygame.mixer.pre_init(44100, 16, 2, 1024*4)
    在我的机器上运行时,这行代码会导致pygame.init()阻塞
    google了以下,改成-16可以正常运行

    Reply
  2. dahill

    为什么我播出来的音乐声音很奇怪,就像在快进

    Reply
  3. TylerTemp

    在pygame, 1.9.2pre版本中, pygame.mixer.Sound()返回的应该是Sound对象
    >>> sound = pygame.mixer.Sound(file=’./src/bounce.ogg’)
    >>> type(sound)

    >>> pygame.__version__
    ‘1.9.2pre’

    Reply
  4. CY

    我用的Mac OSX 10.10.3 写的
    使用 pygame.mixer.pre_init(xxxx) 报错
    后来改为 pygame.mixer.init(xxxx) 就对了

    Reply
  5. dairenyihao

    想请教一下楼主。为什么程序的41,45,49行后都要加1或者减1的操作?

    Reply
    1. 西红柿

      应该是为了模拟一瞬间小球反弹的位置,你可以把1换成100试试

      Reply
  6. Jetlee

    请问代码中,是靠什么控制球碰壁时才放音乐的?

    Reply
    1. 西红柿

      if y + h >= screen_height:
      self.speed.y = -self.speed.y * BOUNCINESS
      self.position.y = screen_height – h / 2.0 – 1.0
      bounce = True
      if x = screen_width:
      self.speed.x = -self.speed.x * BOUNCINESS
      self.position.x = screen_width – w / 2.0 – 1
      bounce = True

      # 根据时间计算现在的位置,物理好的立刻发现这其实不标准,
      # 正规的应该是“s = 1/2*g*t*t”,不过这样省事省时一点,咱只是模拟~
      self.position += self.speed * time_passed
      # 根据重力计算速度
      self.speed.y += time_passed * GRAVITY

      if bounce:
      self.play_bounce_sound()

      Reply
  7. 白胡子老王

    好奇怪,为什么还要弄个死亡名单,当你判断小球的寿命大于10的时候,直接使用remove不就可以了嘛,这样不是更省事。

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

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

  10. bulabuka

    要是想检测球的碰撞怎么写有没有人指导下,sprite函数里面好像没有检测同组sprite里面碰撞的函数

    Reply
  11. Pingback: 如何用Pygame写游戏(二十一)-识荒者

发表评论

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