在上一篇博文中我们实现了About场景和Option场景,在这篇博文里我们将实现游戏场景,先上一张截图:
先把gamelayer.h头文件的源码列一下:
gamelayer.h
1 class GameLayer : public BaserLayer 2 { 3 public: 4 GameLayer(); 5 ~GameLayer(); 6 7 virtual bool init(); 8 9 static CCScene * scene(); 10 11 void setViews(); 12 CREATE_FUNC(GameLayer); 13 14 void background_scroll(float t); 15 void pause_game(CCObject *obj); 16 void warrior_add_new_bullet(float t); 17 18 WarriorLayer *mWarrior; 19 BulletLayer *bulletManager ; 20 EnemyManager *enemyManager; 21 };
这个就是我们游戏的主场景了,在这里我们看到有三个我们自定义的图层:WarriorLayer,BulletLayer,EnemyManager其分别代表了飞机类,子弹类,敌机类。接下来我们看看其实现文件,在这里我们只列出初始化界面元素的函数:
1 void GameLayer::setViews() 2 { 3 do 4 { 5 CCSprite *bg1 = CCSprite::create("bg01.jpg"); 6 CCSprite *bg2 = CCSprite::create("bg01.jpg"); 7 8 bg1->setAnchorPoint(CCPointZero); 9 bg2->setAnchorPoint(CCPointZero); 10 11 bg1->setPosition(CCPointZero); 12 bg2->setPosition(ccp(0,576)); 13 14 this->addChild(bg1,0,11); 15 this->addChild(bg2,0,12); 16 17 //调用滚动背景 18 this->schedule(schedule_selector(GameLayer::background_scroll),0.1f); 19 20 //加入分数 21 ScoreLayer *scoreLayer = ScoreLayer::create(); 22 scoreLayer->setAnchorPoint(ccp(0,1)); 23 scoreLayer->setPosition(ccp(10,getWinSize().height-40)); 24 25 this->addChild(scoreLayer); 26 27 //加入时间 28 TimeLayer *timeLayer = TimeLayer::create(); 29 timeLayer->setAnchorPoint(ccp(0,1)); 30 timeLayer->setPosition(ccp(getWinSize().width-120,getWinSize().height-40)); 31 32 this->addChild(timeLayer); 33 34 //创建暂停/恢复按钮 35 CCMenuItemImage *menuImage = CCMenuItemImage::create("pause.png","pause.png",this,menu_selector(GameLayer::pause_game)); 36 menuImage->setPosition(ccp(getWinSize().width -menuImage->getContentSize().width-160 , 37 menuImage->getContentSize().height-230)); 38 // menuImage->setPosition(ccp(0,0)); 39 menuImage->setAnchorPoint(ccp(0,1)); 40 41 CCMenu *menu = CCMenu::create(menuImage,NULL); 42 43 this->addChild(menu,1,156); 44 45 //加入pauselayer 46 PauseLayer *pauseLayer = PauseLayer::create(); 47 pauseLayer->setPosition(CCPointZero); 48 pauseLayer->setVisible(false); 49 this->addChild(pauseLayer,10,10); 50 51 mWarrior = WarriorLayer::create(); 52 this->addChild(mWarrior,1); 53 54 //加入子弹管理类 55 56 bulletManager = BulletLayer::create(); 57 this->addChild(bulletManager,1); 58 59 this->schedule(schedule_selector(GameLayer::warrior_add_new_bullet),0.2f); 60 61 enemyManager = EnemyManager::create(); 62 this->addChild(enemyManager,1); 63 64 }while(0); 65 66 }
在这个函数里我们实现了一个滚动背景的实现,这样就会有一个飞机在飞的视觉效果。在这里我们用了两张背景图实现。第二张图接在第一张图的末尾,然后我们调用一个定时器每隔固定的时间重新设置场景的背景 ,这样看上去背景一直在变化。在这里我们了解一下cocos2d-x里一个很重要的东西:schedule
我们看一下其调用方式:
1 this->schedule(schedule_selector(GameLayer::background_scroll),0.1f);
这个函数就是用来实现一定超时函数的功能,类似与glib里的g_timeout_seconds ,第一个参数为我们传递的函数指针,第二个函数为设置的间隔时间。我们看看更换背景的回调函数:
1 void GameLayer::background_scroll(float t) 2 { 3 CCSprite *bg1 =(CCSprite *) getChildByTag(11); 4 CCSprite *bg2 =(CCSprite *) getChildByTag(12); 5 6 bg1->setPositionY(bg1->getPositionY() -10); 7 bg2->setPositionY(bg1->getPositionY()+576); 8 9 if(bg2->getPositionY() <=0) 10 { 11 bg1->setPositionY(0); 12 } 13 }
这个函数估计大家都看得明白就不细说了,在这里我们看一下getChildByTag这个函数。在我们向图层中加入元素是可以指定该tag,相应的我们就可以在其他的地方通过该tag获得相应的元素指针。
接下来实现的是分数榜和时间,这个比较简单就不说了。接着我们看看暂停按钮的实现,在这里我们又看到了菜单这个东东,不过在这里看到的是菜单的另外一个分类:CCMenuItemImage。具体的就不细说了,看看它的回调函数吧:
1 void GameLayer::pause_game(CCObject *obj) 2 { 3 getChildByTag(10)->setVisible(true); 4 5 CCDirector::sharedDirector()->pause(); 6 7 CCMenu *menu = (CCMenu *)getChildByTag(156); 8 9 menu->setEnabled(true); 10 11 if(SimpleAudioEngine::sharedEngine()->isBackgroundMusicPlaying()) 12 { 13 SimpleAudioEngine::sharedEngine()->pauseBackgroundMusic(); 14 } 15 16 }
在这个函数里我们获得了tag为10的元素,让其可见,暂停了当前的游戏场景,接着让我们创建的菜单按钮可见。上面是暂停了游戏,那我们如何恢复游戏呢,接下来我们就要看看PauseLayer的实现,首先贴出它的头文件:
1 class PauseLayer : public CCLayerColor 2 { 3 public: 4 PauseLayer(); 5 ~PauseLayer(); 6 7 virtual bool init(); 8 9 void setViews(); 10 11 CREATE_FUNC(PauseLayer); 12 void play_stats(CCObject *obj); 13 virtual void registerWithTouchDispatcher(void); 14 };
在这里需要注意最后一个函数:registerWithTouchDispatcher,这个函数是用来分发触摸事件的,会根据我们指定的顺序来决定时间分发。接这个我们看看它的界面初始化函数:
1 void PauseLayer::setViews() 2 { 3 do 4 { 5 CCSize size = CCDirector::sharedDirector()->getWinSize(); 6 CCMenuItemImage *menuImage = CCMenuItemImage::create("play.png","pause.png",this,menu_selector(PauseLayer::play_stats)); 7 menuImage->setPosition(ccp(size.width/2,size.height/2)); 8 9 CCMenu *menu = CCMenu::create(menuImage,NULL); 10 menu->setPosition(CCPointZero); 11 this->addChild(menu); 12 13 14 }while(0); 15 16 }
这个函数很简单,初始化了一个菜单,就不看了,接着我们看看其点下按钮时的回调函数:
1 void PauseLayer::play_stats(CCObject *obj) 2 { 3 4 CCLayer *parentLayer = (GameLayer *)getParent(); 5 6 7 PauseLayer *pauseLayer = (PauseLayer *) parentLayer->getChildByTag(10); 8 pauseLayer->setVisible(false); 9 10 CCMenu *menu = (CCMenu *)parentLayer->getChildByTag(156); 11 menu->setEnabled(true); 12 13 CCDirector::sharedDirector()->resume(); 14 SimpleAudioEngine::sharedEngine()->resumeBackgroundMusic(); 15 16 }
在这个函数里我们执行了与暂停相反的动作,首先将我们的pauseLayer设置为不可见,隐藏菜单,接着恢复我们之前暂停的场景。
接着我们实现飞机类,飞机类的头文件实现如下:
warriorlayer.h
1 class WarriorLayer : public BaserLayer 2 { 3 public: 4 WarriorLayer(); 5 ~WarriorLayer(); 6 7 virtual bool init(); 8 9 CREATE_FUNC(WarriorLayer); 10 11 void setViews(); 12 13 virtual bool ccTouchBegan(CCTouch *pTouch,CCEvent *event); 14 virtual void registerWithTouchDispatcher(void); 15 16 /* 17 *如果是android平台处理back键和menu键 18 */ 19 #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) 20 21 virtual void keyBackClicked(); 22 virtual void keyMenuClicked(); 23 #endif 24 25 26 CCPoint getWarriorPostion(); 27 CCSize getFlySize(); 28 29 private: 30 CCSprite *warrior; 31 };
在这里我们还是只看其初始化界面元素的实现:
1 void WarriorLayer::setViews() 2 { 3 4 /** 5 *创建飞机 6 */ 7 warrior = CCSprite::create("ship01.png",CCRectMake(60,0,60,43)); 8 9 warrior->setPosition(ccp(getWinSize().width/2,getWinSize().height/2)); 10 this->addChild(warrior); 11 12 13 /** 14 *创建动画 15 */ 16 CCAnimation *animation = CCAnimation::create(); 17 animation->setDelayPerUnit(0.1f); 18 19 animation->addSpriteFrame(CCSpriteFrame::create("ship01.png", CCRectMake(60,0,60,43))); 20 animation->addSpriteFrame(CCSpriteFrame::create("ship01.png", CCRectMake(0,0,60,43))); 21 22 CCAnimate *animate = CCAnimate::create(animation); 23 24 warrior->runAction(CCRepeatForever::create(animate)); 25 26 this->setTouchEnabled(true); 27 }
在这个函数里我们第一次看到了动画的实现。在此处我们使用的Frame动画,设置的时间为0.1秒。一般我们会调用CCSprite对象的runAction对象来执行动画,这个动画是立即执行的。最后一个函数则设置了我们可以接受触摸事件。这个时候的界面看上去应该是这个样子的:
接着给飞机加上子弹,该头文件实现如下:
1 class BulletLayer : public BaserLayer 2 { 3 public: 4 BulletLayer(); 5 ~BulletLayer(); 6 7 virtual bool init(); 8 9 CREATE_FUNC(BulletLayer); 10 11 void setViews(); 12 13 void addBullet(const CCPoint startPoint,CCSize flySize); 14 void moveAllBullet(float t); 15 16 private: 17 CCSpriteBatchNode *mBulletNode; 18 CCArray *bulletArray; 19 };
在这个函数中我们定义了一个CCSpriteBatchNode对象和一个CCArray对象。其中第一个对象据官方文档说是为了提高渲染效率,具体的后面会有详细的研究。第二个是定义了一个数组。接着我们看看其初始化界面元素的函数实现:
1 void BulletLayer::setViews() 2 { 3 CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("bullet.plist","bullet.png"); 4 5 mBulletNode = CCSpriteBatchNode::create("bullet.png"); 6 7 ccBlendFunc cb = {GL_SRC_ALPHA,GL_NONE}; 8 9 mBulletNode->setBlendFunc(cb); 10 11 this->addChild(mBulletNode); 12 13 bulletArray = CCArray::create(); 14 bulletArray->retain(); 15 16 this->schedule(schedule_selector(BulletLayer::moveAllBullet),0.05f); 17 18 }
在这个函数中第一函数是将我们要重复加载的图片加载到缓存池中,在这里要看看这个plist文件,这个文件是用一个工具生成具体怎么用我也不是很清楚,不过我们可以看看这个文件的描述。不过我们可以使用文本编辑器打开这个文件,实际上这个文件就是一个xml文件。接着的是为了实现了一个颜色值的计算函数具体的介绍可以看看下面的这篇文章:
http://blog.sina.com.cn/s/blog_7a2ffd5c0100xtid.html
接着我们看到其初始化了我们定义的数组对象,需要注意的是此处调用的retain方法,这个是干什么呢,在cocos2d中使用了引用技术来管理内存,此处的函数就是通知该对象引用计数加1,否则会把该对象释放掉。接着我们调用schedule函数让子弹动起来,我们看看这个函数的实现
1 void BulletLayer::moveAllBullet(float t) 2 { 3 CCSize size = getWinSize(); 4 5 CCObject *obj = NULL; 6 7 CCARRAY_FOREACH(bulletArray,obj) 8 { 9 CCSprite *sp = (CCSprite *)obj; 10 sp->setPositionY(sp->getPositionY() +10); 11 12 if(sp->getPositionY()> size.height ) 13 { 14 mBulletNode->removeChild(sp,true); 15 bulletArray->removeObject(sp); 16 17 } 18 else 19 { 20 GameLayer *father = (GameLayer *)this->getParent(); 21 CCArray *array = father->enemyManager->getEnemyArray(); 22 23 CCObject *oo = NULL; 24 CCARRAY_FOREACH(array,oo) 25 { 26 EnemyModel *model = (EnemyModel *)oo; 27 28 if(sp->boundingBox().containsPoint(model->getPosition())) 29 { 30 CCLog("**********"); 31 Effect::getSharedEffect()->boom(this->getParent(),model->getPosition()); 32 } 33 } 34 } 35 } 36 }
在这个函数中我们遍历了我们定义的数组,挨个移动子弹对象。如果子弹已经超出了屏幕则将其释放掉。如果没有超出则加入碰撞检测。碰撞检测这个是后面的内容了,在这里先不说了,在后面会讲到。这个时候界面看上去应该是这个样子的:
现在飞机已经可以发射子弹了,那如何让飞机动起来呢,在这里我们就要处理屏幕的触摸事件了,我们看看WarriorLayer类的ccTouchBegan函数,这个函数是从父类继承的,我们看看其函数的实现:
1 bool WarriorLayer::ccTouchBegan(CCTouch *pTouch,CCEvent *event) 2 { 3 CCMoveTo *moveTo = CCMoveTo::create(0.35f,pTouch->getLocation()); 4 5 warrior->runAction(moveTo); 6 7 return true; 8 }
在这里我们创建了一个简单动画来移动飞机。接下来我们加入敌机,这个方法和加入我们自己的飞机类差不多,这里就不多讲了,大家自己实现一下吧。
最后上一张gif图片吧