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)
{}
 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() & onExit()

在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 }
void addnumber(int var)

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);

表示击中后分数增加的效果(其实之前我就已经加上了这个部分,但是只是注释化了而已,这里可以去除注释)

编译并运行游戏,可以看到我们能够打到敌人而获得分数了

到此为止,我们的游戏主场景就完成了,在下一节里面我会介绍如何为我们的游戏添加主菜单,即打开游戏后需要玩家选择的部分,敬请继续关注

posted @ 2013-11-12 22:28  fuutou  阅读(723)  评论(0编辑  收藏  举报