到上一章,一个简单的游戏基本已经完成了,我们还可以加一些小的装饰,让这个游戏看起来更专业一点。
这一章里我们添加一个新的场景。当大师兄消灭一定数量的妖怪后,在屏幕上显示"You Win",如果有一只妖怪跑出屏幕了,就显示"You Lose"。
现在我们要在工程中创建两个新文件:"GameOverScene.cpp" 和 "GameOverScene.h",并且把它们放到Cocos2dxSimpleGame\Classes文件夹里。
GameOcerScene.h的C++代码
#ifndef _GAME_OVER_SCENE_H_ #define _GAME_OVER_SCENE_H_ #include "cocos2d.h" class GameOverLayer : public cocos2d::CCLayerColor { public: GameOverLayer():_label(NULL) {}; virtual ~GameOverLayer(); bool init(); CREATE_FUNC(GameOverLayer); void gameOverDone(); CC_SYNTHESIZE_READONLY(cocos2d::CCLabelTTF*, _label, Label); }; class GameOverScene : public cocos2d::CCScene { public: GameOverScene():_layer(NULL) {}; ~GameOverScene(); bool init(); CREATE_FUNC(GameOverScene); CC_SYNTHESIZE_READONLY(GameOverLayer*, _layer, Layer); }; #endif // _GAME_OVER_SCENE_H_
相比之下objc的代码似乎简洁的多……
#import "cocos2d.h" @interface GameOverLayer : CCLayerColor { CCLabel *_label; } @property (nonatomic, retain) CCLabel *label; @end @interface GameOverScene : CCScene { GameOverLayer *_layer; } @property (nonatomic, retain) GameOverLayer *layer; @end
TIPS
1.为什么objc的头文件里这么简洁?因为objc能直接在.m文件(源代码文件,相当于C++的.cpp)里直接实现而不需要用声明,真的不需要……
2.译者:不好意思才疏学浅我也研究的不是很懂这一段。基本就是说objc(或者是objc的cocos2d-iphone)里有一个node()方法直接整合了new、init、autorelease。而在C++里则需要自己去定义。当然cocos2d-x为了方便广大C++程序猿,提供了一个CREATE_FUNC(在旧版本是很多不同的静态构造器……就不提及了)宏,在声明里使用CREATE_FUNC宏能够快速建立一个默认的create方法,不带参数,返回的是一个该类的对象,并且自动调用了init和autorelease方法(这就是为什么说使用create创建的对象都不需要手动去release的原因)。一行宏代码:
CREATE_FUNC(GameOverScene);
等价于一下面一段代码
static GameOverScene* create() { GameOverScene *pRet = new GameOverScene(); if (pRet && pRet->init()) { pRet->autorelease(); return pRet; } else { delete pRet; pRet = NULL; return NULL; } }
3.关于构造器和init方法。我们知道C++里的构造器是不能写返回值的(但是objc里的init能写),所以需要写try-catch来处理异常。但是try-catch这个东西android SDK不支持,所以cocos2d-x对于新建对象的处理分两步,先呼叫构造器分配空间,然后立马调用init()方法初始化。事实上iOS也采用了这个方法设计接口,比如[[NSString alloc] init](先是分配空间,然后初始化)。三星的bada也是这样用C++的类的。
4.objc里有个@synthesize,表示这是类里的一个组成,能够自动生成setter/getter方法。在C++里他们要保持objc的习惯,也在cocos2dx里定义了CC_SYNTHESIZE系列宏去模仿它们,以自动定义内联的setter/getter方法,CC_SYNTHESIZE_READONLY表示只生成getter不生成setter使它的指针不能被更改。(对应的在objc里还有@property,cocos2d-x里也有CC_PROPERTY系列的宏)
GameOverScene.cpp 的C++代码
// cpp with cocos2d-x #include "GameOverScene.h" #include "HelloWorldScene.h" USING_NS_CC; bool GameOverScene::init() { if( CCScene::init() ) { this->_layer = GameOverLayer::create(); this->_layer->retain(); this->addChild(_layer); return true; } else { return false; } } GameOverScene::~GameOverScene() { if (_layer) { _layer->release(); _layer = NULL; } } bool GameOverLayer::init() { if ( CCLayerColor::initWithColor( ccc4(255,255,255,255) ) ) { CCSize winSize = CCDirector::sharedDirector()->getWinSize(); this->_label = CCLabelTTF::create("","Artial", 32); _label->retain(); _label->setColor( ccc3(0, 0, 0) ); _label->setPosition(ccp(winSize.width/2, winSize.height/2)); this->addChild(_label); this->runAction( CCSequence::create( CCDelayTime::create(3), CCCallFunc::create(this, callfunc_selector(GameOverLayer::gameOverDone)), NULL)); return true; } else { return false; } } void GameOverLayer::gameOverDone() { CCDirector::sharedDirector()->replaceScene(HelloWorld::scene()); } GameOverLayer::~GameOverLayer() { if (_label) { _label->release(); _label = NULL; } }
objc代码
// objc with cocos2d-iphone #import "GameOverScene.h" #import "HelloWorldScene.h" @implementation GameOverScene @synthesize layer = _layer; - (id)init { if ((self = [super init])) { self.layer = [GameOverLayer node]; [self addChild:_layer]; } return self; } - (void)dealloc { [_layer release]; _layer = nil; [super dealloc]; } @end @implementation GameOverLayer @synthesize label = _label; -(id) init { if( (self=[super initWithColor:ccc4(255,255,255,255)] )) { CGSize winSize = [[CCDirector sharedDirector] winSize]; self.label = [CCLabel labelWithString:@"" fontName:@"Arial" fontSize:32]; _label.color = ccc3(0,0,0); _label.position = ccp(winSize.width/2, winSize.height/2); [self addChild:_label]; [self runAction:[CCSequence actions: [CCDelayTime actionWithDuration:3], [CCCallFunc actionWithTarget:self selector:@selector(gameOverDone)], nil]]; } return self; } - (void)gameOverDone { [[CCDirector sharedDirector] replaceScene:[HelloWorld scene]]; } - (void)dealloc { [_label release]; _label = nil; [super dealloc]; } @end
GameOverScene.cpp里有两个类,一个是scene,一个是layer。Scene,场景,可以含有多个layer,在这个场景里只有一个layer。在这个layer中心有个label要显示"You Win"或者是"You Lose",三秒之后场景结束。
TIPs:
1.注意到GameOverLayer._label、GameOverScene._layer。它们在objc里被声明为@property(nonatomic, retain)。这意味着当没有指针指向它们的时候它们是不会被自动释放的,而会在dealloc时里被释放。在C++里,对于create的对象,我们要在create之后手动调用retain方法,然后在析构函数里手动调用release方法。就像上面的代码里显式地做的。当然你也可以用一行CC_SAFE_RELEASE_NULL来代替,这很方便。
2.很炫的是NSAutoReleasePool被引入到cocos2d-x中了,这是一个很不错的垃圾回收器,对于C++编程来说都不错,尤其对于刚从java转到C++的程序员来说简直如鱼得水(C++程序猿自满的手动控制回收呢!节操呢!)。这个用法和iOS是一样的,请参考:http://developer.apple.com/library/ios/#documentation/cocoa/reference/foundation/Classes/NSAutoreleasePool_Class/Reference/Reference.html.
在cocos2d-x中,仅有下面两种情况需要调用release方法
• 你使用构造器和new创建的对象,比如:CCSprite* sprite = new CCSprite(); 那么你需要调用release()方法释放它。
• 你使用类的静态方法create创建了对象,比如CCSprite* sprite = CCSprite::create(...),这样它是自动释放的,你不需要调用release方法,但是!如果你在create之后调用了sprite->retain(),那么你就也要调用sprite->release(),它才会被释放。
回到项目中来,我们要在符合条件的情况调用GameOverScene出来,一个是杀掉比如30个妖怪获得胜利;另一个是有任意一只妖怪抛出屏幕了。首先搞定第一个情况吧。
首先在HelloWorldScene.h中声明一个成员变量来计数比如
protected: int _projectilesDestroyed;
然后来到HelloWorldScene.cpp,先包含GameOverScene的头文件
#include "GameOverScene.h"
C++不允许我们直接在声明的时候初始化其默认值,所以我们在构造器里初始化计数变量
HelloWorld::HelloWorld():_projectilesDestroyed(0) ,_targets(NULL) ,_projectiles(NULL) { _targets = new CCArray(); _projectiles = new CCArray(); }
然后在碰撞检测成功的那段代码里添加上如下代码
// 计数干掉的妖怪数量,如果超过30就跳转到游戏结束场景 _projectilesDestroyed++; if (_projectilesDestroyed > 30) { GameOverScene *gameOverScene = GameOverScene::create(); gameOverScene->getLayer()->getLabel()->setString("You Win!"); CCDirector::sharedDirector()->replaceScene(gameOverScene); }
对于另外一种情况,很简单当妖怪走到屏幕左侧时会调用spriteMoveFinished方法,我们直接在该方法里加入失败的场景转换。在" if(sprite->getTag() == 1) "的分支里添加如下代码:
GameOverScene *gameOverScene = GameOverScene::create(); gameOverScene->getLayer()->getLabel()->setString("You Lose :["); CCDirector::sharedDirector()->replaceScene(gameOverScene);
现在全部完工了,生成跑一遍吧。该有的效果都有了,妖怪满天飞子弹满天飞,可爱的音乐音效还有完整的游戏结束画面,一个完整的游戏已经完成了,欢呼吧~(我赌这是你们的第一个)