如何用cocos2d-x来开发简单的Uphone游戏:(四) 音乐音效 & 最后的润色
2010-10-12 18:03
447 查看
到上一篇为止,我们已经基于cocos2d-x开发出一个很简单的Uphone游戏原型了。下面再添加一些音效、背景音乐和简单的游戏逻辑就可以完工了。
六、音乐音效的实现
cocos2d-iphone中包含一个cocosDenshion库,里面从底到高提供三层接口,CDSoundEngine->CDAudioManager->SimpleAudioEngine。 cocosDenshion整个是依赖于OpenAL实现的。但OpenAL并不像OpenGL是Khronos Group的标准,而是Creative(创新)公司的一个开源库,可以软实现或硬件实现,只是名字起的比较山寨容易让人联想到OpenGL而已。目前硬件实现了OpenAL的好像就苹果一家,对于没有OpenAL的Uphone而言,我们就无法提供cocosDenshion库里底层那些复杂的音效支持了。而最顶一层,SimpleAudioEngine是最简单实用的、也是开发者最常用到的,cocos2d-x提供了这层接口的封装。我们来看游戏代码中如何“只用一行”就实现了音效播放
首先把background-music-aac.mp3和pew-pew-lei.wav两个文件拷贝到 D:\Work7\NEWPLUS\TDA_DATA\UserData 目录下。这里说明两点
Wenderlic文章中提供的这两个音乐文件,是caf格式,这是苹果自己的格式。我们在这里分别转成WAV和MP3,演示一下SimpleAudioEngine对这两种大众格式的支持
和前面的图片资源一样,我们先简单化地把音乐资源拷贝到uphone模拟器默认的资源目录下,通过直接读文件来使用。在本系列教程的下一篇“打包发布”中将会描述如何把图片和音乐和程序打在一个二进制包里,游戏开发者不用担心资源被人轻易拷走后山寨的问题
先在HelloWorldScene.cpp的开头,添加对SimpleaudioEngine.h的包含
然后在bool HelloWorld::init()方法中,加入播放背景音乐的代码
接着在ccTouchesEnded方法中播放扔出飞镖的音效
音效和背景音乐就这样简单搞定了。
七、最后的润色
现在让我们创建一个新场景,在打掉若干小怪、或者被小怪穿到屏幕左边时显示“You Win”或“You Lose”的界面。在Visual Studio中新建两个文件, GameOverScene.cpp和GameOverScene.h
GameOverScene.h的内容
转换要点
1. 在objc的头文件中,可以不声明类成员函数,而直接在.m文件里实现。cpp不允许这样做。所以我们会多个bool init();
2. 由于cpp里没有self这种强大的关键字,所以CCLayer::node()和CCScene::node()方法的都需要派生类自己实现一份,不能像objc那样直接从父类继承下来靠self关键字变成指向自己的对象。node()方法很方便,集合了new,init,autorelease等方法,可以减少调用者的代码量。但由于每份node方法的代码都类似,我们就做了两个宏来方便大家 LAYER_NODE_FUNC和SCENE_NODE_FUNC. 如果想使用这两个宏,就必须在派生类里实现bool init()方法。
2. 关于构造函数和init方法。cocos2d-x在从objc改写为cpp时,并不是直接把init的内容翻到C++构造函数里面,主要出于这样的考虑:C++构造函数有个天生缺陷——没有返回值。这就导致C++构造函数依赖try-catch来捕捉逻辑异常。而一般try-catch用的人不多,开启try-catch支持会使编译后的二进制程序增加不少体积,而且android NDK上也是彻底不支持try-catch。所以我们采取现在比较流行的“二阶段构造”的方法,即使用时先调构造函数,再调用init处理初始化逻辑。这种思路不论是在苹果iOS的接口设计(比如[[NSString alloc] init],即二阶段构造)、还是在samsung bada操作系统使用C++类时都是如此。
3. objc中的@synthesize实现了_label和_layer两个属性的具体setter和getter。我们在cocos2dx\include\Cocos2dDefine.h中实现了一系列的宏定义,来模仿实现@property和@synthesize的功能。在上面代码中,我们用CCX_SYNTHESIZE_READONLY宏来实现了只读的类成员变量,只有getter没有setter。由于VC++的规则是inline函数只能在头文件里实现,所以@synthesize就从objc的.m文件里移动到cpp的.h文件里,和成员变量声明一并实现了
4. 严谨起见,我们需要在CPP的类构造函数里,对所有成员变量初始化,上文中增加了构造函数把_layer和_label指针初始化为NULL
接着就实现GameOverScene.cpp的内容
转换要点
1. 再次注意GameOverLayer._label和GameOverScene._layer两个属性。这两个属性在objc的头文件里被声明为@property (nonatomic, retain),也就是被retain了一次,所以在dealloc里才要调用release方法。同样地,我们在~GameOverLayer()和~GameOverScene()析构函数里分别release()了这两个属性,但这个release需要和一个retain对应,所以在两个init方法里都分别添加了_label->retain()和_layer->retain();
2. 关于NSAutoReleasePool, cocos2d-x里也有个模仿实现,这个简单的垃圾回收机制对C++编程来说是个福音;它使用起来和iOS上的NSAutoReleasePool原则一样,参考苹果的文档 http://developer.apple.com/library/ios/#documentation/cocoa/reference/foundation/Classes/NSAutoreleasePool_Class/Reference/Reference.html
简而言之就是,在使用cocos2d-x中继承自NSObject类的对象指针时,以下两种情况是需要用户多调一个release
类对象是用户自己new出来的。比如CCSprite *sprite = new CCSprite();
类对象是通过某个静态函数建立并返回的,比如CCSprite *sprite = CCSprite::spriteWithFile(...),这种情况不需要用户release;但如果你接着调用了sprite->retain(), 那么就需要一个sprite->release()对应
这个山寨版的NSAutoReleasePool使用和调试细节,多释放了或未释放了应如何处理,我会另外写篇文章描述
注意,上面GameOverScene.cpp里有两个对象,一个场景(scene)和一个图层(layer),场景可以包含多个图层,而这个图层只在屏幕正中间放了一个文字标签(label),显示3秒种后返回到HelloWorldScene中。
最后,为了调用起这个GameOverScene,我们需要在HelloWorldScene中添加一些游戏逻辑代码。先得添加一个变量,判断我们的带头大哥一共用飞镖干掉了多少杂兵。在class HelloWorld中添加一个成员变量:
然后在HelloWorldScene.cpp中,添加对GameOverScene.h的引用
在HelloWorld::update方法中的removeChild(target)后面的targetsToDelete循环中增加计数并检查获胜条件,获胜了就显示"You Win!"界面
与之匹配的是失败条件:任何一个反派小兵穿越了屏幕的最左边,你就挂了。于是修改spriteMoveFinished方法,在if (sprite->getTag() == 1)条件里面增加“You Lose”的代码:
最后编译并运行整个游戏项目,这样你就可以听到带头大哥扔飞镖同时很嚣张地发出的DIU~DIU~的声音,还有很HIGH的背景音乐循环播放;另一方面,游戏赢或输了会有一个界面提示。
这个游戏至此,已经在模拟器上100%完成了。下一篇我们讲如何做交叉编译成linux版本(也就是Uphone真机运行的版本), 如何打包图片和音乐资源到程序二进制中防止被拷贝、以及如何用制作成Uphone安装包发布。
系列教程
如何用cocos2d-x来开发简单的Uphone游戏:(一) 下载安装和HelloWorld
如何用cocos2d-x来开发简单的Uphone游戏:(二) 移动的精灵
如何用cocos2d-x来开发简单的Uphone游戏:(三) 射击子弹 & 碰撞检测
如何用cocos2d-x来开发简单的Uphone游戏:(四) 音乐音效 & 最后的润色
如何用cocos2d-x来开发简单的Uphone游戏:(五) 打包和发布
著作权声明:本文由http://www.walzer.cn/原创,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢!
六、音乐音效的实现
cocos2d-iphone中包含一个cocosDenshion库,里面从底到高提供三层接口,CDSoundEngine->CDAudioManager->SimpleAudioEngine。 cocosDenshion整个是依赖于OpenAL实现的。但OpenAL并不像OpenGL是Khronos Group的标准,而是Creative(创新)公司的一个开源库,可以软实现或硬件实现,只是名字起的比较山寨容易让人联想到OpenGL而已。目前硬件实现了OpenAL的好像就苹果一家,对于没有OpenAL的Uphone而言,我们就无法提供cocosDenshion库里底层那些复杂的音效支持了。而最顶一层,SimpleAudioEngine是最简单实用的、也是开发者最常用到的,cocos2d-x提供了这层接口的封装。我们来看游戏代码中如何“只用一行”就实现了音效播放
首先把background-music-aac.mp3和pew-pew-lei.wav两个文件拷贝到 D:\Work7\NEWPLUS\TDA_DATA\UserData 目录下。这里说明两点
Wenderlic文章中提供的这两个音乐文件,是caf格式,这是苹果自己的格式。我们在这里分别转成WAV和MP3,演示一下SimpleAudioEngine对这两种大众格式的支持
和前面的图片资源一样,我们先简单化地把音乐资源拷贝到uphone模拟器默认的资源目录下,通过直接读文件来使用。在本系列教程的下一篇“打包发布”中将会描述如何把图片和音乐和程序打在一个二进制包里,游戏开发者不用担心资源被人轻易拷走后山寨的问题
先在HelloWorldScene.cpp的开头,添加对SimpleaudioEngine.h的包含
// cpp with cocos2d-x #include "SimpleAudioEngine/SimpleAudioEngine.h" | // objc with cocos2d-iphone #import "SimpleAudioEngine.h" |
// cpp with cocos2d-x SimpleAudioEngine::getSharedEngine()->playBackgroundMusic( "background-music-aac.mp3"); | // objc with cocos2d-iphone [[SimpleAudioEngine sharedEngine] playBackgroundMusic: @"background-music-aac.caf"]; |
// cpp with cocos2d-x SimpleAudioEngine::getSharedEngine()->playEffect("pew-pew-lei.wav"); | // objc with cocos2d-iphone [[SimpleAudioEngine sharedEngine] playEffect:@"pew-pew-lei.caf"]; |
七、最后的润色
现在让我们创建一个新场景,在打掉若干小怪、或者被小怪穿到屏幕左边时显示“You Win”或“You Lose”的界面。在Visual Studio中新建两个文件, GameOverScene.cpp和GameOverScene.h
GameOverScene.h的内容
1 // cpp with cocos2d-x 2 #ifndef _GAME_OVER_SCENE_H_ 3 #define _GAME_OVER_SCENE_H_ 4 5 #include "cocos2d.h" 6 7 class GameOverLayer : public cocos2d::CCColorLayer 8 { 9 public: GameOverLayer():_label(NULL) {}; virtual ~GameOverLayer(); bool init(); LAYER_NODE_FUNC(GameOverLayer); void gameOverDone(); CCX_SYNTHESIZE_READONLY(cocos2d::CCLabel*, _label, Label); }; class GameOverScene : public cocos2d::CCScene { public: GameOverScene():_layer(NULL) {}; ~GameOverScene(); bool init(); SCENE_NODE_FUNC(GameOverScene); CCX_SYNTHESIZE_READONLY(GameOverLayer*, _layer, Layer); }; 36 37 #endif // _GAME_OVER_SCENE_H_ | 1 // objc with cocos2d-iphone 2 3 4 5 #import "cocos2d.h" 6 7 @interface GameOverLayer : CCColorLayer 8 { 9 CCLabel *_label; } @property (nonatomic, retain) CCLabel *label; @end @interface GameOverScene : CCScene { GameOverLayer *_layer; } @property (nonatomic, retain) GameOverLayer *layer; @end |
1. 在objc的头文件中,可以不声明类成员函数,而直接在.m文件里实现。cpp不允许这样做。所以我们会多个bool init();
2. 由于cpp里没有self这种强大的关键字,所以CCLayer::node()和CCScene::node()方法的都需要派生类自己实现一份,不能像objc那样直接从父类继承下来靠self关键字变成指向自己的对象。node()方法很方便,集合了new,init,autorelease等方法,可以减少调用者的代码量。但由于每份node方法的代码都类似,我们就做了两个宏来方便大家 LAYER_NODE_FUNC和SCENE_NODE_FUNC. 如果想使用这两个宏,就必须在派生类里实现bool init()方法。
2. 关于构造函数和init方法。cocos2d-x在从objc改写为cpp时,并不是直接把init的内容翻到C++构造函数里面,主要出于这样的考虑:C++构造函数有个天生缺陷——没有返回值。这就导致C++构造函数依赖try-catch来捕捉逻辑异常。而一般try-catch用的人不多,开启try-catch支持会使编译后的二进制程序增加不少体积,而且android NDK上也是彻底不支持try-catch。所以我们采取现在比较流行的“二阶段构造”的方法,即使用时先调构造函数,再调用init处理初始化逻辑。这种思路不论是在苹果iOS的接口设计(比如[[NSString alloc] init],即二阶段构造)、还是在samsung bada操作系统使用C++类时都是如此。
3. objc中的@synthesize实现了_label和_layer两个属性的具体setter和getter。我们在cocos2dx\include\Cocos2dDefine.h中实现了一系列的宏定义,来模仿实现@property和@synthesize的功能。在上面代码中,我们用CCX_SYNTHESIZE_READONLY宏来实现了只读的类成员变量,只有getter没有setter。由于VC++的规则是inline函数只能在头文件里实现,所以@synthesize就从objc的.m文件里移动到cpp的.h文件里,和成员变量声明一并实现了
4. 严谨起见,我们需要在CPP的类构造函数里,对所有成员变量初始化,上文中增加了构造函数把_layer和_label指针初始化为NULL
接着就实现GameOverScene.cpp的内容
1 // cpp with cocos2d-x 2 #include "GameOverScene.h" 3 #include "HelloWorldScene.h" 4 5 using namespace cocos2d; 6 7 8 bool GameOverScene::init() 9 { 10 if( CCScene::init() ) 11 { 12 this->_layer = GameOverLayer::node(); 13 _layer->retain(); 14 this->addChild(_layer); 15 16 return true; 17 } 18 else 19 { 20 return false; 21 } 22 } 23 24 GameOverScene::~GameOverScene() 25 { 26 if (_layer) 27 { 28 _layer->release(); 29 _layer = NULL; 30 } 31 } 32 33 34 35 36 37 38 bool GameOverLayer::init() 39 { 40 if ( CCColorLayer::initWithColor( ccc4(255,255,255,255) ) ) 41 { 42 CGSize winSize = CCDirector::getSharedDirector()->getWinSize(); 43 this->_label = CCLabel::labelWithString("","Artial", 32); 44 _label->retain(); 45 _label->setColor( ccc3(0, 0, 0) ); 46 _label->setPosition( ccp(winSize.width/2, winSize.height/2) ); 47 this->addChild(_label); 48 49 this->runAction( CCSequence::actions( 50 CCDelayTime::actionWithDuration(3), 51 CCCallFunc::actionWithTarget(this, 52 callfunc_selector(GameOverLayer::gameOverDone)), 53 NULL)); 54 55 return true; 56 } 57 else 58 { 59 return false; 60 } 61 } 62 63 void GameOverLayer::gameOverDone() 64 { 65 CCDirector::getSharedDirector()->replaceScene( HelloWorld::scene() ); 66 } 67 68 GameOverLayer::~GameOverLayer() 69 { 70 if (_label) 71 { 72 _label->release(); 73 _label = NULL; 74 } 75 } 76 77 | 1 // objc with cocos2d-iphone 2 #import "GameOverScene.h" 3 #import "HelloWorldScene.h" 4 5 @implementation GameOverScene 6 @synthesize layer = _layer; 7 8 - (id)init 9 { 10 if ((self = [super init])) 11 { 12 self.layer = [GameOverLayer node]; 13 14 [self addChild:_layer]; 15 } 16 return self; 17 } 18 19 20 21 22 23 24 - (void)dealloc 25 { 26 27 28 [_layer release]; 29 _layer = nil; 30 [super dealloc]; 31 } 32 33 @end 34 35 @implementation GameOverLayer 36 @synthesize label = _label; 37 38 -(id) init 39 { 40 if( (self=[super initWithColor:ccc4(255,255,255,255)] )) 41 { 42 CGSize winSize = [[CCDirector sharedDirector] winSize]; 43 self.label = [CCLabel labelWithString:@"" fontName:@"Arial" fontSize:32]; 44 45 _label.color = ccc3(0,0,0); 46 _label.position = ccp(winSize.width/2, winSize.height/2); 47 [self addChild:_label]; 48 49 [self runAction:[CCSequence actions: 50 [CCDelayTime actionWithDuration:3], 51 [CCCallFunc actionWithTarget:self 52 selector:@selector(gameOverDone)], 53 nil]]; 54 } 55 return self; 56 } 57 58 59 60 61 62 63 - (void)gameOverDone 64 { 65 [[CCDirector sharedDirector] replaceScene:[HelloWorld scene]]; 66 } 67 68 - (void)dealloc 69 { 70 71 72 [_label release]; 73 _label = nil; 74 [super dealloc]; 75 } 76 77 @end |
1. 再次注意GameOverLayer._label和GameOverScene._layer两个属性。这两个属性在objc的头文件里被声明为@property (nonatomic, retain),也就是被retain了一次,所以在dealloc里才要调用release方法。同样地,我们在~GameOverLayer()和~GameOverScene()析构函数里分别release()了这两个属性,但这个release需要和一个retain对应,所以在两个init方法里都分别添加了_label->retain()和_layer->retain();
2. 关于NSAutoReleasePool, cocos2d-x里也有个模仿实现,这个简单的垃圾回收机制对C++编程来说是个福音;它使用起来和iOS上的NSAutoReleasePool原则一样,参考苹果的文档 http://developer.apple.com/library/ios/#documentation/cocoa/reference/foundation/Classes/NSAutoreleasePool_Class/Reference/Reference.html
简而言之就是,在使用cocos2d-x中继承自NSObject类的对象指针时,以下两种情况是需要用户多调一个release
类对象是用户自己new出来的。比如CCSprite *sprite = new CCSprite();
类对象是通过某个静态函数建立并返回的,比如CCSprite *sprite = CCSprite::spriteWithFile(...),这种情况不需要用户release;但如果你接着调用了sprite->retain(), 那么就需要一个sprite->release()对应
这个山寨版的NSAutoReleasePool使用和调试细节,多释放了或未释放了应如何处理,我会另外写篇文章描述
注意,上面GameOverScene.cpp里有两个对象,一个场景(scene)和一个图层(layer),场景可以包含多个图层,而这个图层只在屏幕正中间放了一个文字标签(label),显示3秒种后返回到HelloWorldScene中。
最后,为了调用起这个GameOverScene,我们需要在HelloWorldScene中添加一些游戏逻辑代码。先得添加一个变量,判断我们的带头大哥一共用飞镖干掉了多少杂兵。在class HelloWorld中添加一个成员变量:
1 // cpp with cocos2d-x 2 protected: 3 int _projectilesDestroyed; | 1 // objc with cocos2d-iphone 2 3 int _projectilesDestroyed; |
// cpp with cocos2d-x #include "GameOverScene.h" | // objc with cocos2d-iphone #import "GameOverScene.h" |
// cpp with cocos2d-x _projectilesDestroyed++; if (_projectilesDestroyed > 30) { GameOverScene *gameOverScene = GameOverScene::node(); gameOverScene->getLayer()->getLabel()->setString("You Win!"); CCDirector::getSharedDirector()->replaceScene(gameOverScene); } | // objc with cocos2d-iphone _projectilesDestroyed++; if (_projectilesDestroyed > 30) { GameOverScene *gameOverScene = [GameOverScene node]; [gameOverScene.layer.label setString:@"You Win!"]; [[CCDirector sharedDirector] replaceScene:gameOverScene]; } |
// cpp with cocos2d-x GameOverScene *gameOverScene = GameOverScene::node(); gameOverScene->getLayer()->getLabel()->setString("You Lose :["); CCDirector::getSharedDirector()->replaceScene(gameOverScene); | // objc with cocos2d-iphone GameOverScene *gameOverScene = [GameOverScene node]; [gameOverScene.layer.label setString:@"You Lose :["]; [[CCDirector sharedDirector] replaceScene:gameOverScene]; |
这个游戏至此,已经在模拟器上100%完成了。下一篇我们讲如何做交叉编译成linux版本(也就是Uphone真机运行的版本), 如何打包图片和音乐资源到程序二进制中防止被拷贝、以及如何用制作成Uphone安装包发布。
系列教程
如何用cocos2d-x来开发简单的Uphone游戏:(一) 下载安装和HelloWorld
如何用cocos2d-x来开发简单的Uphone游戏:(二) 移动的精灵
如何用cocos2d-x来开发简单的Uphone游戏:(三) 射击子弹 & 碰撞检测
如何用cocos2d-x来开发简单的Uphone游戏:(四) 音乐音效 & 最后的润色
如何用cocos2d-x来开发简单的Uphone游戏:(五) 打包和发布
著作权声明:本文由http://www.walzer.cn/原创,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢!
相关文章推荐
- 如何用cocos2d-x来开发简单的Uphone游戏:(四) 音乐音效 & 最后的润色
- 如何用cocos2d-x来开发简单的Uphone游戏:(三) 射击子弹 & 碰撞检测
- 如何用cocos2d-x来开发简单的Uphone游戏:(三) 射击子弹 & 碰撞检测
- 如何用cocos2d-x来开发简单的Uphone游戏:(五) 打包和发布
- 如何用cocos2d-x来开发简单的Uphone游戏:(五) 打包和发布
- 如何用cocos2d-x来开发简单的Uphone游戏:(二) 移动的精灵
- 如何用cocos2d-x来开发简单的Uphone游戏:(二) 移动的精灵
- 如何用cocos2d-x来开发简单的Uphone游戏:(一) 下载安装和HelloWorld
- 【iOS-cocos2d-X 游戏开发之十四】Xcode中c++&Object-C混编,详细介绍如何在cocos2dx中访问object函数以及Apple Api
- 【iOS-cocos2d-X 游戏开发之十四】Xcode中c++&Object-C混编,详细介绍如何在cocos2dx中访问object函数以及Apple Api
- Cocos2d-x游戏开发之SimpleAudioEngine 音乐音效
- 如何用cocos2d-x来开发简单的沃phone游戏:(一) 下载安装和HelloWorld
- 【cocos2d-x IOS游戏开发-捕鱼达人19】音乐与音效的实现
- 【Cocos2D-X游戏开发笔记1】播放音乐&CocosDenshion
- 【Cocos2d-X开发学习笔记】第28期:游戏中音乐和音效的使用
- 【Cocos2d-X开发学习笔记】第28期:游戏中音乐和音效的使用
- 如何在WP8上搭建cocos2d-x的游戏开发环境
- cocos2d-x学习(2)---游戏实例开发:一个简单的射击游戏一
- 【iOS-cocos2d-X 游戏开发之七】整合Cocos2dX的Android项目到Xcode项目中,Xcode编写&编译代码,Android导入打包运行即可!
- 如何利用Cocos2d-x开发一个游戏?