cocos2dx - 制作纵版射击游戏:喵星战争 (六)
注:本教程主要来自《Cocos2D-x权威指南》满硕泉著 机械工业出版社,如需要更详细的内容,请支持并购买正版实体书籍
2013/11/13 更新
完成了打飞机游戏的核心部分后,我们就可以根据各种另外需要的功能进行加工了
滚动背景的实现
打飞机游戏一般都有一个背景,能给玩家一种前进的感觉,其实很简单,通过连接两个图在屏幕中循环滚动就可以实现这种效果了,接下来看看具体实现。
在GameScene.h的GameMain类中加入两个私有精灵变量来表示背景
class GameMain : public CCLayer { private: ...... CCSprite * bg1; CCSprite * bg2; ...... };
在GameScene.cpp中的bool init()中初始化两个背景变量
bool GameMain::init() { ...... // build the background bg1 = CCSprite::create("bg.png"); bg1 -> setScale(0.5); bg2 = CCSprite::create("bg.png"); bg2 -> setScale(0.5); bg1 -> setAnchorPoint(ccp(0, 0)); bg2 -> setAnchorPoint(ccp(0, 0)); bg1 -> setPosition(ccp(0, 0)); bg2 -> setPosition(ccp(0, size.height)); // zOrder = 0, background is in the bottom this -> addChild(bg1, 0); this -> addChild(bg2, 0); ....... }
背景主要是由一个bg.png的图片组成,在游戏加载的时候,一个背景占据整个游戏屏幕(0, 0),另一个背景则紧接着第一个背景(0, size.height),为了不阻挡游戏里的其他元素,我们将背景的zOrder设置为0, this -> addChild(bg1, 0), 完成基本设置之后,需要在update函数中加入让两个背景变量移动的功能,具体如下
void GameMain::update(float time) { // logic of background moving bg1 -> setPosition(ccp(bg1 -> getPosition().x, bg1 -> getPosition().y -2)); bg2 -> setPosition(ccp(bg2 -> getPosition().x, bg2 -> getPosition().y -2)); if (bg2 -> getPosition().y < 0) { //CCLog("bg1 > 0, %f", bg1 -> getPosition().y); //CCLog("bg2 > 0, %f", bg2 -> getPosition().y); float temp = bg2 -> getPosition().y + 480; bg1 -> setPosition(ccp(bg2 -> getPosition().x, temp)); } if (bg1 -> getPosition().y < 0) { //CCLog("bg1 < 0, %f", bg1 -> getPosition().y); //CCLog("bg2 > 0, %f", bg2 -> getPosition().y); float temp = bg1 -> getPosition().y + 480; bg2 -> setPosition(ccp(bg1 -> getPosition().x, temp)); } ...... }
两个背景变量都以每帧2像素单位移动,当背景一不能覆盖上整个背景的时候,就用背景二来补上空缺的部分,以此来达到背景循环滚动的效果, 编译并运行游戏,这个打飞机游戏就有了一个滚动的背景了
主角小猫的残机数以及GameOver判定
一个游戏有开始肯定有结束,在打飞机游戏里一般的结束就是体现为玩家残机为0 - GameOver,接下来看看如何实现。
在GameMain类中加入表示主角残机数整数int blood以及其UI的精灵变量blood1, blood2, blood3, 游戏结束场景的gameover, 表示游戏结束的公共函数void setover(), 重新开始游戏的按钮返回函数void menuBackCallback()
class GameMain : public CCLayer { private: ..... // blood UI related int blood; // hero's life CCSprite * blood1; CCSprite * blood2; CCSprite * blood3;
// gameover scene
CCSprite * gameover; ......
public:
......
// a selector callback
void menuBackCallback();
// gameover method
void setover();
};
在GameScene.cpp中的bool init()加入残机数以及UI的初始化, gameover场景的初始化,gameover按钮的初始化
bool GameMain::init() { ...... // init blood UI blood = 3; CCSpriteBatchNode * ui = CCSpriteBatchNode::create("cat.png"); blood1 = CCSprite::createWithTexture(ui -> getTexture()); blood1 -> setPosition(ccp(20, size.height - 20)); blood1 -> setScale(0.2); ui -> addChild(blood1); blood2 = CCSprite::createWithTexture(ui -> getTexture()); blood2 -> setPosition(ccp(50, size.height - 20)); blood2 -> setScale(0.2); ui -> addChild(blood2); blood3 = CCSprite::createWithTexture(ui -> getTexture()); blood3 -> setPosition(ccp(80, size.height - 20)); blood3 -> setScale(0.2); ui -> addChild(blood3); addChild(ui, 4); // init gameover scene gameover = CCSprite::create("gameover.png"); gameover -> setAnchorPoint(ccp(0.5, 0.5)); gameover -> setPosition(ccp(0, 0)); gameover -> setPosition(ccp(size.width/2, size.height/2 + 70)); gameover -> setVisible(false); gameover -> setScale(0.5); addChild(gameover, 5); // init gameover menu CCMenuItemImage * pCloseItem = CCMenuItemImage::create("back.png", "back.png", this, menu_selector(GameMain::menuBackCallback)); pCloseItem -> setPosition(ccp(size.width/2, size.height/2 - 50)); pCloseItem -> setScale(0.5); CCMenu * pMenu = CCMenu::create(pCloseItem, NULL); pMenu -> setPosition(CCPointZero); this -> addChild(pMenu, 5, 25); pMenu -> setVisible(false); pMenu -> setEnabled(false); ...... }
初始主角残机数为3,用cat.png表示主角的残机数,建立一个CCSpriteBatchNode变量ui作为UI的装载体,初始化每一个血量UI并加入到ui中去,这样当游戏一开始我们就能看到主角的残机数出现在左上角, gameover的场景主要由gameover.png组成,游戏一开始装载的时候设置为不可视,gameover按钮由back.png组成,游戏装载的时候设定为不可视,且不能使用。
接下来是当主角受到伤害的时候,残机数就会减少,并通过UI传达给玩家,在void setherohurt()函数中加入以下血量减少判断
void GameMain::setherohurt() { // Heor is hurt, life reduce ...... switch (blood) { case 3: blood1 -> setVisible(false); blood--; break; case 2: blood2 -> setVisible(false); blood --; break; case 1: blood3 -> setVisible(false); blood--; break; case 0: if (!isOver) { isOver = true; setover(); } break; } ...... }
当然,残机数为零的时候,游戏就会结束 isOver = true; setover();,以下是表示结束的函数
void GameMain::setover() { // set gameover CCMenu * pMenu = (CCMenu *) this -> getChildByTag(25); pMenu -> setVisible(true); pMenu -> setEnabled(true); gameover -> setVisible(true); gameover -> setScale(0); pMenu -> setScale(0); pMenu -> runAction(CCScaleTo::create(0.5, 1)); gameover -> runAction(CCScaleTo::create(0.5, 0.5)); hero -> setVisible(false); }
gameover场景以及按钮变为可视,按钮变为可以使用,并加入了一些进入游戏画面的动画,最后将主角设为不可视。
不要忘记了按钮的返回函数也需要作设定,这个函数会在之后加入游戏主菜单之后作一些更改,现在暂时先返回游戏主场景。
void GameMain::menuBackCallback() { CCDirector::sharedDirector() -> replaceScene(GameMain::scene()); }
编译并运行游戏,可以看到主角小猫的血量,以及游戏结束后弹出的gameover场景
游戏分数UI的实现
作为一个游戏,通过分数给予玩家成就感是一个必不可少的部分,于是我们这里需要编写一个简单的分数UI
首先建立一个分数UI类GameMark, 新建GameMark.h/cpp文件,在GameMark.h中如下定义GameMark类
#include "cocos2d.h" using namespace cocos2d; class GameMark : public CCNode { public: CCArray * bits; CCTexture2D * ui; int mark; // memory control GameMark(void); virtual ~GameMark(void); // redefine onEnter and onExit method virtual void onEnter(); virtual void onExit(); // socore display void addnumber(int var); };
GameMark类包含以下要素
装载分数每一位数的序列bits,装载UI图片的ui,记录游戏分数的mark
四个基本构成函数
更新分数显示的void addnumber(int var)函数
四个基本构成函数如下
GameMark::GameMark(void) {} GameMark::~GameMark(void) {}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 void GameMark::onEnter() 2 { 3 CCNode::onEnter(); 4 CCSize size = CCDirector::sharedDirector() -> getWinSize(); 5 this -> setContentSize(size); 6 bits = new CCArray(); 7 bits -> initWithCapacity(5); 8 9 // score title 10 CCSprite * title = CCSprite::create("score.png"); 11 title -> setPosition(ccp(size.width/2 + 40, size.height - 15)); 12 title -> setScale(0.5); 13 addChild(title); 14 15 // set number 16 for (int i = 0 ; i < 5; i++) { 17 CCSprite * number = CCSprite::create("shu.png"); 18 ui = number -> getTexture(); 19 number -> setScale(0.5); 20 number -> setTextureRect(CCRectMake(234, 0, 26, 31)); 21 number -> setPosition(ccp(size.width - 15 - i*15, size.height - 15)); 22 bits -> addObject(number); 23 addChild(number); 24 } 25 bits -> retain(); 26 mark = 0; 27 } 28 29 void GameMark::onExit() 30 { 31 CCNode::onExit(); 32 }
在onEnter对分数UI初始化中,需要注意的是以下几部分
1. 分数UI是由五位数组成,用initWithCapacity()来初始化bits的容量
2. 分数的标题由score.png表示
3. shu.png是这样的一张图,包含了所有的数字
ui = number -> getTexture(),初始化ui为这幅图片的texture,为以后的addnumber()函数作准备,
number -> setTextureRect(CCRectMake(234, 0, 26, 31)) 截取这个图为0部分的图片,用来初始化分数UI的bits,再用setPosition()和for循环进行一个一个的排位
4. 最后初始化分数mark为0。
接下来是更新分数UI的函数addnumber(int var)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 void GameMark::addnumber(int var) 2 { 3 // score, set number by position 4 mark += var; 5 int temp = mark % 10; 6 7 // set singe 8 if (temp > 0) { 9 ((CCSprite *)bits -> objectAtIndex(0)) -> setTexture(ui); 10 ((CCSprite *)bits -> objectAtIndex(0)) -> setTextureRect(CCRectMake((temp - 1) * 26, 0, 26, 31)); 11 } 12 else 13 { 14 ((CCSprite *)bits -> objectAtIndex(0)) -> setTexture(ui); 15 ((CCSprite *)bits -> objectAtIndex(0)) -> setTextureRect(CCRectMake(234, 0, 26, 31)); 16 } 17 18 // set tens 19 temp = (mark % 100) / 10; 20 if (temp > 0) { 21 ((CCSprite *)bits -> objectAtIndex(0)) -> setTexture(ui); 22 ((CCSprite *)bits -> objectAtIndex(1)) -> setTextureRect(CCRectMake((temp - 1) * 26, 0, 26, 31)); 23 } 24 else 25 { 26 ((CCSprite *)bits -> objectAtIndex(0)) -> setTexture(ui); 27 ((CCSprite *)bits -> objectAtIndex(1)) -> setTextureRect(CCRectMake(234, 0, 26, 31)); 28 } 29 30 // set hundreds 31 temp = (mark % 1000) / 100; 32 if (temp > 0) { 33 ((CCSprite *)bits -> objectAtIndex(0)) -> setTexture(ui); 34 ((CCSprite *)bits -> objectAtIndex(2)) -> setTextureRect(CCRectMake((temp - 1) * 26, 0, 26, 31)); 35 } 36 else 37 { 38 ((CCSprite *)bits -> objectAtIndex(0)) -> setTexture(ui); 39 ((CCSprite *)bits -> objectAtIndex(2)) -> setTextureRect(CCRectMake(234, 0, 26, 31)); 40 } 41 42 // set thousand 43 temp = (mark % 10000) / 1000; 44 if (temp > 0) { 45 ((CCSprite *)bits -> objectAtIndex(0)) -> setTexture(ui); 46 ((CCSprite *)bits -> objectAtIndex(3)) -> setTextureRect(CCRectMake((temp - 1) * 26, 0, 26, 31)); 47 } 48 else 49 { 50 ((CCSprite *)bits -> objectAtIndex(0)) -> setTexture(ui); 51 ((CCSprite *)bits -> objectAtIndex(3)) -> setTextureRect(CCRectMake(234, 0, 26, 31)); 52 } 53 54 // set ten thousand 55 temp = mark / 10000; 56 if (temp > 0) { 57 ((CCSprite *)bits -> objectAtIndex(0)) -> setTexture(ui); 58 ((CCSprite *)bits -> objectAtIndex(4)) -> setTextureRect(CCRectMake((temp - 1) * 26, 0, 26, 31)); 59 } 60 else 61 { 62 ((CCSprite *)bits -> objectAtIndex(0)) -> setTexture(ui); 63 ((CCSprite *)bits -> objectAtIndex(4)) -> setTextureRect(CCRectMake(234, 0, 26, 31)); 64 } 65 }
var表示每次打败敌人的得分,将其加入到游戏总分数mark中,然后用求得分数(五位数)每一位数字加上setTextureRect()函数来更新UI上的每一位数字。
回到GameScene.h的GameMain类定义中,加入GameMark.h头文件声明,添加一个GameMark类的私有变量gamemark
#include "GameMark.h" using namespace cocos2d; class GameMain : public CCLayer { private: ...... // score UI GameMark * gamemark; ...... };
然后在GameScene.cpp的bool init()中加入分数UI初始化部分
bool GameMain::init() { ...... // init score UI gamemark = new GameMark(); addChild(gamemark, 4); ...... }
最后来到更新每一帧的update函数,判断敌人被主角子弹击中后,加入
gamemark -> addnumber(200);
表示击中后分数增加的效果(其实之前我就已经加上了这个部分,但是只是注释化了而已,这里可以去除注释)
编译并运行游戏,可以看到我们能够打到敌人而获得分数了
到此为止,我们的游戏主场景就完成了,在下一节里面我会介绍如何为我们的游戏添加主菜单,即打开游戏后需要玩家选择的部分,敬请继续关注