如何用cocos2d-x来开发简单的Uphone游戏:(四) 音乐音效 & 最后的润色

到上一篇为止,我们已经基于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 目录下。这里说明两点
  1. Wenderlic文章中提供的这两个音乐文件,是caf格式,这是苹果自己的格式。我们在这里分别转成WAV和MP3,演示一下SimpleAudioEngine对这两种大众格式的支持
  2. 和前面的图片资源一样,我们先简单化地把音乐资源拷贝到uphone模拟器默认的资源目录下,通过直接读文件来使用。在本系列教程的下一篇“打包发布”中将会描述如何把图片和音乐和程序打在一个二进制包里,游戏开发者不用担心资源被人轻易拷走后山寨的问题

 

先在HelloWorldScene.cpp的开头,添加对SimpleaudioEngine.h的包含
//  cpp with cocos2d-x
#include  " SimpleAudioEngine/SimpleAudioEngine.h "
//  objc with cocos2d-iphone
#import  " SimpleAudioEngine.h "  

然后在bool HelloWorld::init()方法中,加入播放背景音乐的代码

//  cpp with cocos2d-x
SimpleAudioEngine::getSharedEngine() -> playBackgroundMusic(

                                       "background-music-aac.mp3");

//  objc with cocos2d-iphone
[[SimpleAudioEngine sharedEngine]  playBackgroundMusic:

                                    @"background-music-aac.caf"]; 

接着在ccTouchesEnded方法中播放扔出飞镖的音效

//  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 :
10    GameOverLayer():_label(NULL) {};
11     virtual   ~ GameOverLayer();
12     bool  init();
13    LAYER_NODE_FUNC(GameOverLayer);
14 
15     void  gameOverDone();
16 
17    CCX_SYNTHESIZE_READONLY(cocos2d::CCLabel * , _label, Label);
18  };
19 
20 
21 
22 
23 
24  class  GameOverScene :  public  cocos2d::CCScene
25  {
26  public :
27    GameOverScene():_layer(NULL) {};
28     ~ GameOverScene();
29     bool  init();
30    SCENE_NODE_FUNC(GameOverScene);
31 
32    CCX_SYNTHESIZE_READONLY(GameOverLayer * , _layer, Layer);
33  };
34 
35
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 
10 
11 
12 
13 
14 
15 
16 
17    CCLabel  * _label;
18  }
19 
20  @property (nonatomic, retain) CCLabel  * label;
21 
22  @end
23 
24  @interface GameOverScene : CCScene 
25  {
26 
27 
28 
29 
30 
31 
32    GameOverLayer  * _layer;
33  }
34 
35  @property (nonatomic, retain) GameOverLayer  * layer;
36 
37  @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;

然后在HelloWorldScene.cpp中,添加对GameOverScene.h的引用

//  cpp with cocos2d-x
#include  " GameOverScene.h "
//  objc with cocos2d-iphone
#import  " GameOverScene.h "

 

在HelloWorld::update方法中的removeChild(target)后面的targetsToDelete循环中增加计数并检查获胜条件,获胜了就显示"You Win!"界面

//  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];
}

 

与之匹配的是失败条件:任何一个反派小兵穿越了屏幕的最左边,你就挂了。于是修改spriteMoveFinished方法,在if (sprite->getTag() == 1)条件里面增加“You Lose”的代码:

//  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];

 

最后编译并运行整个游戏项目,这样你就可以听到带头大哥扔飞镖同时很嚣张地发出的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-x)