注:本教程主要来自《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)
{}
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)
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);
表示击中后分数增加的效果(其实之前我就已经加上了这个部分,但是只是注释化了而已,这里可以去除注释)
编译并运行游戏,可以看到我们能够打到敌人而获得分数了
到此为止,我们的游戏主场景就完成了,在下一节里面我会介绍如何为我们的游戏添加主菜单,即打开游戏后需要玩家选择的部分,敬请继续关注