~~~~我的生活,我的点点滴滴!!
玩游戏的时候,背景音乐和音效能够使一款游戏增添不少色彩。
背景音乐:时间相对比较长,同一时间内只能播放一首背景音乐。
音效:时间相对比较短,同一时间内可以播放多个音效。
不管是音乐还是音效,cocos2d-x中都采用SimpleAudioEngine类实现跨平台的声音引擎。
今天我们分析的例子就是cpp-tests中关于声音的引擎使用例子部分。
1. 游戏背景音乐
1.1 背景音乐在不同平台下所支持的声音
关于背景音乐,cocos2d-x 在不同平台下所支持的格式大致如下:
主要平台 | 支持类型 |
Android | android.media.MediaPlayer类支持的格式,包括MP3、WAV和3GP |
iOS | cocos2d-iPhone中cocosDenshion支持的类型,推荐MP3和CAF |
Win32 | MID和WAV |
1.2 背景音乐SimpleAudioEngine类常用的函数
函数名 | 返回类型 | 描述 |
preloadBackgroundMusic | 空 | 预加载背景音乐 |
playBackgroundMusic | 空 | 播放背景音乐 |
stopBackgroundMusic | 空 | 停止背景音乐 |
pauseBackgroundMusic | 空 | 暂停背景音乐 |
resumeBackgroundMusic | 空 | 重新开始背景音乐 |
rewindBackgroundMusic | 空 | 回放背景音乐 |
willPlayBackgroundMusic | 布尔型 | 是否会播放背景音乐 |
isBackgroundMusicPlaying | 布尔型 | 是否正播放背景音乐 |
getBackgroundMusicVolume | 布尔型 | 获得背景音乐音量 |
setBackgroundMusicVolume | 空 | 设置背景音乐音量 |
2. 游戏音效
2.1 音效在不同平台下所支持的音效格式:
平台名称 | 支持类型 |
Android | 对应OGG格式支持最好,同时支持WAV格式 |
iOS | cocos2d-iPhone中cocosDenshion支持的类型,推荐CAF |
Win32 | MID和WAV |
2.2 音效SimepleAudioEngine类常用的函数
函数名 | 返回类型 | 描述 |
getEffectsVolume | 浮点型 | 获得音效音量 |
setEffectsVolume | 空 | 设置音效音量 |
playEffect | 整型 | 播放音效,参数为文件路径和是否循环 |
pauseEffect | 空 | 暂停音效,参数为播放时获得的ID号 |
pauseAllEffects | 空 | 暂停所有音效 |
resumeEffect | 空 | 开始音效,参数为播放时获得的ID号 |
resumeAllEffects | 空 | 开始所有音效 |
stopEffect | 空 | 停止音效,参数为播放时获得的ID号 |
stopAllEffects | 空 | 停止所有音效 |
preloadEffect | 空 | 预加载音效 |
unloadEffect | 空 | 将预加载的音效从缓存中删除 |
注意:预加载音乐和音效能够提高程序的执行效率,但是这样也会增加内存的占用。所以,在实际使用时,要根据项目特点来平衡效率
和内存压力的问题。
例子图:
3. 源码分析
下面是简单的代码分析,代码里面都写上了相应的注释,我懒得在扯一些重复的解释话了,大家直接看代码里面的注释:
//注意要带上此头文件,他是主要的声音引擎类,不同平台有不同的接口,但是类名还是一样的
#include "SimpleAudioEngine.h"
#include "extensions/GUI/CCControlExtension/CCControlSlider.h"
//=================================================================
//对于不同平台支持不同的音乐与音效格式,这点很重要,上面的表格已经有说到
//也许不全,但是肯定是主要的,下面的官方例子也有讲解
//=================================================================
// android effect only support ogg
#if (CC_TARGET_PLATFORM == CC_PLATFOR_ANDROID)
#define EFFECT_FILE "effect2.ogg"
#elif( CC_TARGET_PLATFORM == CC_PLATFOR_MARMALADE)
#define EFFECT_FILE "effect1.raw"
#else
#define EFFECT_FILE "effect1.wav"
#endif // CC_PLATFOR_ANDROID
#if (CC_TARGET_PLATFORM == CC_PLATFOR_WIN32)
#define MUSIC_FILE "music.mid"
#elif (CC_TARGET_PLATFORM == CC_PLATFOR_BLACKBERRY || CC_TARGET_PLATFORM == CC_PLATFOR_LINUX )
#define MUSIC_FILE "background.ogg"
#else
#define MUSIC_FILE "background.mp3"
#endif // CC_PLATFOR_WIN32
USING_NS_CC;
using namespace CocosDenshion;
#define LINE_SPACE 40
class Button : public Node//, public TargetedTouchDelegate
{
public:
//此处省略N行代码
......
~Button()
{
// Director::getInstance()->getTouchDispatcher()->removeDelegate(this);
}
//后面lambda表达式给std::function<void()> &onTriggered赋值,这是一个回调功能函数
//但是我没有发现什么触发的这个回调,所谓回调就是提前写好在哪里调用,仔细看效果
//是在鼠标点击结束后才会相应的动作产生,这样我们就去看onTouchEnded()这个函数,
//他在里面有个调用_onTriggered()这个动作,他就是我们绑定的回调函数。
//至于onTouchEnded()为什么能触发,那是cocos2dx的消息
//机制推动的,这个大家要深究可以去看源码了,我这里只是解释为什么这样设置了回调
//函数后就能触发我们想要的效果,而不是cocos2dx在促使回调进行
void onTriggered(const std::function<void()> &onTriggered)
{
_onTriggered = onTriggered;
}
private:
Button()
: _child(NULL)
{
// 注册触摸事件
auto listener = EventListenerTouchOneByOne::create();
listener->setSwallowTouches(true);
//不懂的可以去看链接http://blog.csdn.net/ac_huang/article/details/37839413
listener->onTouchBegan = CC_CALLBACK_2(Button::onTouchBegan, this);
listener->onTouchEnded = CC_CALLBACK_2(Button::onTouchEnded, this);
listener->onTouchCancelled = CC_CALLBACK_2(Button::onTouchCancelled, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
}
//此处省略N行代码
......
//返回触摸区域
bool touchHits(Touch *touch)
{
const Rect area(0, 0, _child->getContentSize().width, _child->getContentSize().height);
return area.containsPoint(_child->convertToNodeSpace(touch->getLocation()));
}
bool onTouchBegan(Touch *touch, Event *event)
{
CC_UNUSED_PARAM(event);
const bool hits = touchHits(touch);
if (hits)
//产生缩小,看上去点击的感觉
scaleButtonTo(0.9f);
return hits;
}
void onTouchEnded(Touch *touch, Event *event)
{
CC_UNUSED_PARAM(event);
const bool hits = touchHits(touch);
if (hits && _onTriggered)
//在这里进度回调的
_onTriggered();
scaleButtonTo(1);
}
void onTouchCancelled(Touch *touch, Event *event)
{
CC_UNUSED_PARAM(event);
scaleButtonTo(1);
}
void scaleButtonTo(float scale)
{
auto action = ScaleTo::create(0.1f, scale);
action->setTag(900);
//个人觉得stopActionByTag()函数能把所有相同的tag的Action动作都停止掉
//后来看完代码,确实也是如此
stopActionByTag(900);
runAction(action);
}
Node *_child;
std::function<void()> _onTriggered;
};
class AudioSlider : public Node
{
public:
//此处省略N行代码
......
private:
AudioSlider(Direction direction)
: _direction(direction)
, _slider(NULL)
, _lblMinValue(NULL)
, _lblMaxValue(NULL)
{
}
bool init()
{
//建议去看ControlSlider的源码,了解此类的一些基本接口,此类用来设置滑动条
_slider = extension::ControlSlider::create("extensions/sliderTrack.png","extensions/sliderProgress.png" ,"extensions/sliderThumb.png");
//设置缩放为原来的一半
_slider->setScale(0.5);
if (_direction == Vertical)
_slider->setRotation(-90.0);
addChild(_slider);
return true;
}
Direction _direction;
extension::ControlSlider *_slider;
LabelTTF *_lblMinValue;
LabelTTF *_lblMaxValue;
};
CocosDenshionTest::CocosDenshionTest()
: _soundId(0),
_musicVolume(1),
_effectsVolume(1),
_sliderPitch(NULL),
_sliderPan(NULL),
_sliderGain(NULL),
_sliderEffectsVolume(NULL),
_sliderMusicVolume(NULL)
{
addButtons();
addSliders();
schedule(schedule_selector(CocosDenshionTest::updateVolumes));
// 提前加载背景音乐与音效,如果内存吃紧的话,就不要提前加载了,如果在win32上开发,进去看源码,会发现此函数为空
// 其他平台下不是这样的,因为引擎主要针对移动平台,在win32上只是一个开发与调试,所以有可能会有不理想的效果,这都
// 无关紧要的。
SimpleAudioEngine::getInstance()->preloadBackgroundMusic( MUSIC_FILE );
SimpleAudioEngine::getInstance()->preloadEffect( EFFECT_FILE );
// 设置默认音量
SimpleAudioEngine::getInstance()->setEffectsVolume(0.5);
SimpleAudioEngine::getInstance()->setBackgroundMusicVolume(0.5);
}
CocosDenshionTest::~CocosDenshionTest()
{
}
void CocosDenshionTest::onExit()
{
Layer::onExit();
//一定要记得释放掉声音引擎
SimpleAudioEngine::end();
}
void CocosDenshionTest::addButtons()
{
auto lblMusic = LabelTTF::create("Control Music", "Arial", 24);
addChildAt(lblMusic, 0.25f, 0.9f);
Button *btnPlay = Button::createWithText("play");
//lambda表达式注册回调函数
btnPlay->onTriggered([]() {
SimpleAudioEngine::getInstance()->playBackgroundMusic(MUSIC_FILE, true);
});
addChildAt(btnPlay, 0.1f, 0.75f);
//此处省略N多行代码
......
Button *btnPlayEffect = Button::createWithText("play");
//由于要用到类成员变量,所以lambda表达式里面带上了"[this]"
//详细请看链接http://blog.csdn.net/ac_huang/article/details/27574889
btnPlayEffect->onTriggered([this]() {
const float pitch = _sliderPitch->getValue();
const float pan = _sliderPan->getValue();
const float gain = _sliderGain->getValue();
_soundId = SimpleAudioEngine::getInstance()->playEffect(EFFECT_FILE, false, pitch, pan, gain);
});
addChildAt(btnPlayEffect, 0.6f, 0.8f);
//此处省略N行代码
......
}
//根据百分比来添加控件的位置
void CocosDenshionTest::addChildAt(Node *node, float percentageX, float percentageY)
{
const Size size = VisibleRect::getVisibleRect().size;
node->setPosition(Point(percentageX * size.width, percentageY * size.height));
addChild(node);
}
//SimpleAudioEngine是我们用来控制音乐、音效的类,他是单个例子
void CocosDenshionTest::updateVolumes(float)
{
//通过判断当前他们是否有改变来区别更新哪一个
const float musicVolume = _sliderMusicVolume->getValue();
if (fabs(musicVolume - _musicVolume) > 0.001) {
_musicVolume = musicVolume;
//用来改变背景音乐的声音大小
SimpleAudioEngine::getInstance()->setBackgroundMusicVolume(_musicVolume);
}
const float effectsVolume = _sliderEffectsVolume->getValue();
if (fabs(effectsVolume - _effectsVolume) > 0.001) {
_effectsVolume = effectsVolume;
//用来改变背景音效的声音大小
SimpleAudioEngine::getInstance()->setEffectsVolume(_effectsVolume);
}
}
个人觉得重要的知识点来和自己有疑问的点都在上面有注释,大家就直接看代码和注释,这样理解起来更快,有哪些错误的地方,欢迎大家
指出,共同学习,共同进步。