游戏中的背景音乐和声效



0.  前言



不管是大型客户端游戏还是轻量级的网页游戏,游戏中背景音乐和声效是必不可少的。好的背景音乐、声效会给游戏增色,本文不从策划/设计等角度去考虑,只从程序实现上面讲在网页游戏开发中如何去实现背景音乐、声效。背景音乐和声效有以下几个要求:

ü  背景音乐与声效是分开的,可以独立设置开关

ü  背景音乐一般循环播放一直存在

ü  声效点击才触发,这种声音任何时候只播放一个,如果两个瞬间点击多个按钮,只播放最后一个声音

为了使背景音乐和声效分开,可以使用不同的声道来播放,这样可以通过控制声道以控制背景音乐和声效。而任何时刻只播放一个声效,可以有多种方法,如:

①通过一个单例类来控制管理所有的声效,保证每次只播放一个声效;

②将所有的声效放嵌入到一个swf中,并将所有声效放到一个时间轴上,播放的时候跳到相应帧,这样可以保证每次只播放一个声效。

本文主要介绍第二总方法,使用单例模式来管理游戏中的背景音乐和声效,大纲如下:

0.    前言... 1

1.    AS3中声音控制相关类... 1

2.    as3中单例模式如何设计... 2

3.    游戏中声音管理类... 3

 



1.  AS3中声音控制相关类



在 ActionScript 中控制声音之前,需要先将声音信息加载到 Flash Player 中。可以使用四种方法将音频数据加载到 Flash Player 中,以便通过 ActionScript 对其进行使用:

ü  将外部声音文件(如 mp3 文件)加载到 SWF 中(方法一是本文要用的,背景音乐及游戏中的按钮声效都是单独的mp3文件,然后加载到游戏中);

ü  在创建 SWF 文件时将声音信息直接嵌入到其中(前面我们讲到的,创建 SWF 文件时将声音信息直接嵌入到其中,我们可以将所有的声效放嵌入到一个swf中,并将所有声效放到一个时间轴上,播放的时候跳到相应帧,这样可以保证每次只播放一个声效。);

ü  使用连接到用户计算机上的麦克风来获取音频输入;

ü  访问从服务器流式传输的声音数据。

从外部声音文件加载声音数据时,可以在仍加载其余声音数据的同时开始回放声音文件的开头部分。 虽然可以使用各种不同的声音文件格式对数字音频进行编码,但是 ActionScript 3.0 和 Flash Player 支持以 mp3 格式存储的声音文件。它们不能直接加载或播放其它格式的声音文件,如 WAV 或 AIFF。

在 ActionScript 中处理声音时,可能会使用 flash.media 包中的某些类。通过使用Sound 类,您可以加载声音文件并开始回放以获取对音频信息的访问开始播放声音后,Flash Player 可为您提供对 SoundChannel 对象的访问。由于已加载的音频文件只能是在用户计算机上播放的几种声音之一,因此,所播放的每种单独的声音使用其自己的 SoundChannel 对象;混合在一起的所有 SoundChannel 对象的组合输出是实际通过计算机扬声器播放的声音。可以使用此 SoundChannel 实例来控制声音的属性以及将其停止回放。最后,如果要控制组合音频,可以通过SoundMixer 类对混合输出进行控制

ActionScript 3.0 声音体系结构使用 flash.media 包中的以下类。

描述

flash.media.Sound

Sound 类处理声音加载、管理基本声音属性以及启动声音播放。

flash.media.SoundChannel

当应用程序播放 Sound 对象时,将创建一个新的 SoundChannel 对象来控制回放。SoundChannel 对象控制声音的左和右回放声道的音量。播放的每种声音具有其自己的 SoundChannel 对象。

flash.media.SoundLoaderContext

SoundLoaderContext 类指定在加载声音时使用的缓冲秒数,以及 Flash Player 在加载文件时是否从服务器中查找跨域策略文件。SoundLoaderContext 对象用作 Sound.load() 方法的参数。

flash.media.SoundMixer

SoundMixer 类控制与应用程序中的所有声音有关的回放和安全属性。实际上,可通过一个通用 SoundMixer 对象将多个声道混合在一起,因此,该 SoundMixer 对象中的属性值将影响当前播放的所有 SoundChannel 对象。

flash.media.SoundTransform

SoundTransform 类包含控制音量和声相的值。可以将 SoundTransform 对象应用于单个 SoundChannel 对象、全局 SoundMixer 对象或 Microphone 对象等。

flash.media.ID3Info

ID3Info 对象包含一些属性,它们表示通常存储在 mp3 声音文件中的 ID3 元数据信息。

flash.media.Microphone

Microphone 类表示连接到用户计算机上的麦克风或其它声音输入设备。可以将来自麦克风的音频输入传送到本地扬声器或发送到远程服务器。Microphone 对象控制其自己的声音流的增益、采样率以及其它特性。

加载和播放的每种声音需要其自己的 Sound 类和 SoundChannel 类的实例。然后,全局 SoundMixer 类在回放期间将来自多个 SoundChannel 实例的输出混合在一起。另外,Sound、SoundChannel 和 SoundMixer 类不能用于从麦克风或流媒体服务器(如 Flash Media Server)中获取的声音数据。



2.  as3中单例模式如何设计



单例模式(Singleton)单例模式(也叫单件模式)的作用就是保证在整个应用程序的生命周期中,任何一个时刻,单例类的实例都只存在一个。一般编程语言中,单例模式有以下两个特点:

ü  单例模式使类在程序生命周期的任何时刻都只有一个实例

ü  单例的构造函数是私有的,这样可以阻止调用构造函数生成实例,必须通过getInstance()接口得到这个单例类的实例

然而由于AS3中的构造函数必须是public,所以不可以像其它编程语言一样,将构造函数置为private来阻止调用构造函数生成实例。这样是不是说as3天生就不支持单例模式了呢?很多人这样来实现单例模式:

protected static var instance : Singleton;
        
        public function Singleton()
        {
            if (instance != null)
            {
                //throw new Error("只能用getInstance()来获取实例");
            }
        }
    
        public static function getInstance() : Singleton
        {
            if (instance == null)
            {
                instance = new Singleton();
            }
            return instance;
        }

想阻止构造函数在类的外部被调用还有另外一种方法,使用as3中的包外类。包外类:只能被包中的类访问,它属于当前类的私有类。此包外类对外不可见。定义位置为package{ }外。可以定义一个或者多个class类。但类名不能与文件同名。具体如何使用包外类实现单例模式,见下面小节。

注意:flash是单线程模式的,不用考虑线程安全问题。



3.  游戏中声音管理类



有了上面的基础,下面可以游戏中的声音管理类了,声音管理类设计成一个单例类。通过两个声道SoundChannel分别播放背景音乐、声效;SoundLoaderContext类设置MP3文件加载上下文;Sound 类处理声音加载、管理基本声音属性以及启动声音播放。完整的代码如下:

package utils
{
    import flash.events.EventDispatcher;
    import flash.media.Sound;
    import flash.media.SoundChannel;
    import flash.media.SoundLoaderContext;
    import utils.QAssetManager;
    
    /**
     * 游戏中背景音乐、声效管理类:
     * 声音的播放,即将Sound.play()方法赋值给SoundChannel实例就可以开始播放歌曲了。
     * 如果使用Sound和SoundChannel装载和播放另一个mp3时,这个声音也会开始播放,因为没有对实例做任何限制,因此两个实例都可以正常播放。
     * 因此必须检测是否对SoundChannel实例赋值了,如果它是null,脚本继续执行并播放选择的文件;如果它不是null,先停止当前正在播放的文件
     * 后再装载和播放另一个mp3文件。这样就可以保证某一时刻只播放一个文件了。
     * 
     * 游戏中声音有2两种:
     *         1. 背景音乐:循环播放一直存在
     *         2. 按钮音效等:点击才触发,这种声音任何时候只播放一个,如果两个瞬间点击多个按钮,只播放最后一个声音
     * @author tyler
     */
    public class GameSound extends EventDispatcher 
    {
        //public static const SOUND_BACKGROUND:int = 0;
        
        protected var m_bkmusicChannel:SoundChannel;
        protected var m_soundChannel:SoundChannel;
        
        //单态实例
        protected static var m_instance:GameSound;
        
        //单态构造函数
        public function GameSound(pvt:PrivateClass) 
        {
            
        }
        
        //单态构造方法
        public static function getInstance():GameSound
        {
            if (m_instance == null)
            {
                m_instance = new GameSound(new PrivateClass());
            }
            return m_instance;
        }
        
        //开始播放背景音乐
        public function bkmusicPlay(music:String):void
        {
            bkmusicStop();
            QAssetManager.getInstance().getAssets(
                music,
                {context: new SoundLoaderContext() },
                function(content:Object):void {
                    m_bkmusicChannel = (content as Sound).play();
                });
        }
        
        //停止播放背景音乐
        public function bkmusicStop():void
        {
            if (m_bkmusicChannel != null)
            {
                m_bkmusicChannel.stop();
            }
        }
        
        //播放音效
        public function soundPlay(sound:String):void
        {
            soundStop();
            QAssetManager.getInstance().getAssets(
                sound,
                {context: new SoundLoaderContext() },
                function(context:Object):void {
                    m_soundChannel = (context as Sound).play();
                });
        }
        
        //停止音效
        public function soundStop():void
        {
            if (m_soundChannel != null)
            {
                m_soundChannel.stop();
            }
        }
    }
}

class PrivateClass
{
    public function PrivateClass()
    {
        trace("包外类,用于实现单例");
    }
}

播放背景音乐的时候只需要这样调用:GameSound.getInstance().bkmusicPlay(url);播放其它声效时:GameSound.getInstance().soundPlay(url)。停止的话调用相应接口即可。

注意:utils.QAssetManager只是一个加载的通用类,大家可以使用自己的加载类,我推荐开源的BulkLoader。