cocos2d-x教程:太空游戏

     原文来自:http://www.raywenderlich.com/33752/cocos2d-x-tutorial-for-ios-and-android-space-game

     第一次翻译,本身自己英语也不好,还好有谷歌翻译,加上是初学者,很多地方也不是很明白,不对的地方大家直接指出来。

    我这里只关注了IOS的部分,目前我用的是Cocos2d-x 2.2.1版本。

Getting Started

首先,从这里下载太空游戏资源包 ,然后解压到你的磁盘上。

      然后将文件单个的放到工程的Resources目录下,就像这样:



   加入一个太空船

  1. 让我们尝试看一下这样是否可行,打开工程的Classes\HelloWorldScene.h,添加下面的代码到HelloWorldScene类成员变量声明的地方。(为了偷懒不写命名空间,先添加宏USING_NS_CC;)    

CCSize visibleSize;
CCSpriteBatchNode *_batchNode;
CCSprite *_ship;


这里创建了两个私有的成员变量,一个是精灵批量节点,另一个表示太空船。

接着切换到HelloWorldScene.cpp文件,进入init()函数,从if语句的后面开始删除后面的代码,直到return true(不删除);

   2.     添加一个菜单项到init()最后,然后再添加下面的代码:

    visibleSize = CCDirector::sharedDirector()->getVisibleSize();
    _batchNode = CCSpriteBatchNode::create("Sprites.pvr.ccz");
    this->addChild(_batchNode);
    CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("Sprites.plist");
    
    _ship = CCSprite::createWithSpriteFrameName("SpaceFlier_sm_1.png");
    _ship->setPosition(ccp(visibleSize.width*0.1,visibleSize.height*0.5));
    _batchNode->addChild(_ship,1);


编译->运行,你将会看到一艘太空船出现在模拟器的屏幕上。



添加视图差滚动

接着,用parallax scrolling来让太空背景滚动起来是一个比较吊的方法。

然后,在HelloWorldScene.h中加一些私有的成员变量:

    CCParallaxNode *_backgroundNode;
    CCSprite *_spacedust1;
    CCSprite *_spacedust2;
    CCSprite *_planetsunrise;
    CCSprite *_galaxy;
    CCSprite *_spacialanomaly;
    CCSprite *_spacialanomaly2;


HelloWorldScene.cppinit()方法中添加:(在return true;的前面)

    _spacedust1 = CCSprite::create("bg_front_spacedust.png");
    _spacedust2 = CCSprite::create("bg_front_spacedust.png");
    _planetsunrise = CCSprite::create("bg_planetsunrise.png");
    _galaxy = CCSprite::create("bg_galaxy.png");
    _spacialanomaly = CCSprite::create("bg_spacialanomaly.png");
    _spacialanomaly2 = CCSprite::create("bg_spacialanomaly2.png");
    
    CCPoint dustSpeed = ccp(0.1,0.1);
    CCPoint bgSpeed = ccp(0.05,0.05);
    
    _backgroundNode->addChild(_spacedust1, 0, dustSpeed, ccp(0, visibleSize.height/2));
    _backgroundNode->addChild(_spacedust2, 0, dustSpeed,ccp(_spacedust1->getContentSize().width,visibleSize.height/2));
    _backgroundNode->addChild(_galaxy, -1, bgSpeed, ccp(0, visibleSize.height * 0.7));
    _backgroundNode->addChild(_planetsunrise, -1 , bgSpeed, ccp(600, visibleSize.height * 0));
    _backgroundNode->addChild(_spacialanomaly, -1, bgSpeed, ccp(900, visibleSize.height * 0.3));
    _backgroundNode->addChild(_spacialanomaly2, -1, bgSpeed, ccp(1500, visibleSize.height * 0.9));



编译->运行,你将看到这样的界面:



现在是让背景滚动的时候了,首先在HelloWorldScene.h中声明update方法——你可能会随便添加到private区或者public区,但是update方法是作为内部用,所以更适合加到private区。

 void update(float dt);

然后到HelloWordScene.cpp中去实现它:

void HelloWorld::update(float dt)
{
    CCPoint backgroundScrollVert = ccp(-1000,0);
    _backgroundNode->setPosition(ccpAdd(_backgroundNode->getPosition(), ccpMult(backgroundScrollVert, dt)));
}


最后,在init()函数返回前调用scheduleUpdate():

this->scheduleUpdate();

编译->运行,你就会看到背景滚动起来了。

Continuous Scrolling

但是,你会注意到背景滚出了屏幕边界,就没有重复滚了,你应该修改它。

在工程中,我们新建一个C++的类文件,命名为CCParallaxNodeExtras:

这样,我们的工程中就多增加了两个文件:



然后,用下面的代码替换掉CCParallaxNodeExtras.h中的内容:

#ifndef __SpaceGame__CCParallaxNodeExtras__
#define __SpaceGame__CCParallaxNodeExtras__

#include "cocos2d.h"
USING_NS_CC;
class CCParallaxNodeExtras:public CCParallaxNode
{
public:
    CCParallaxNodeExtras();
    static CCParallaxNodeExtras *node();
    void incrementOffset(CCPoint offset,CCNode *node);
};

#endif /* defined(__SpaceGame__CCParallaxNodeExtras__) */


这个类继承了CCParallaxNode,新加了incrementOffset方法用来更新parallax node的子节点的位置。背景从左边移出的部分就会加到背景的右边,形成连续滚动的效果。


接着,用下面的代码替换掉CCParallaxNodeExtras.cpp中的内容:

#include "CCParallaxNodeExtras.h"

class CCPointObject : CCObject
{
    CC_SYNTHESIZE(CCPoint, m_tRatio, Ratio);
    CC_SYNTHESIZE(CCPoint, m_tOffset, Offset);
    CC_SYNTHESIZE(CCNode*, m_pChild, Child);
};

CCParallaxNodeExtras::CCParallaxNodeExtras()
{
    CCParallaxNode();
}

CCParallaxNodeExtras* CCParallaxNodeExtras::node()
{
    return new CCParallaxNodeExtras();
}

void CCParallaxNodeExtras::incrementOffset(cocos2d::CCPoint offset, cocos2d::CCNode *node)
{
    for (int i=0; i<m_pParallaxArray->num; ++i) {
        CCPointObject *point = (CCPointObject*)m_pParallaxArray->arr[i];
        CCNode *curNode = point->getChild();
        if (curNode->isEqual(node)) {
            point->setOffset(ccpAdd(point->getOffset(), offset));
            break;
        }
    }
}



next,在HelloWorldScene.h中添加头文件:

#include "CCParallaxNodeExtras.h"


then,改变_backgroundNode的定义,如下

CCParallaxNodeExtras *_backgroundNode;

.cpp文件中:

_backgroundNode = CCParallaxNodeExtras::node();


finally,把下面代码加到update方法中:

CCArray *spaceDusts = CCArray::createWithCapacity(2);
    spaceDusts->addObject(_spacedust1);
    spaceDusts->addObject(_spacedust2);
    for (int i=0; i<spaceDusts->count(); ++i) {
        CCSprite *spaceDust = (CCSprite*)(spaceDusts->objectAtIndex(i));
        float xPosition = _backgroundNode->convertToWorldSpace(spaceDust->getPosition()).x;
        float size = spaceDust->getContentSize().width;
        if (xPosition < -size/2) {
            _backgroundNode->incrementOffset(ccp(spaceDust->getContentSize().width*2,0), spaceDust);
        }
    }
    
    CCArray *backGrounds = CCArray::createWithCapacity(4);
    backGrounds->addObject(_galaxy);
    backGrounds->addObject(_planetsunrise);
    backGrounds->addObject(_spacialanomaly);
    backGrounds->addObject(_spacialanomaly2);
    for (int i=0; i<backGrounds->count(); ++i) {
        CCSprite *background = (CCSprite*)backGrounds->objectAtIndex(i);
        float xPosition = _backgroundNode->convertToWorldSpace(background->getPosition()).x;
        float size = background->getContentSize().width;
        if (xPosition < -size) {
            _backgroundNode->incrementOffset(ccp(2000, 0), background);
        }
    }



CCArray是基于STL的,用createWithCapacity构造方法将会自动帮你释放对象。

编译->运行,现在你的背景就能无限的滚动了!


Adding Stars

添加星星相当简单,只需在init()方法中加入:

 HelloWorld::addChild(CCParticleSystemQuad::create("Stars1.plist"));
 HelloWorld::addChild(CCParticleSystemQuad::create("Stars2.plist"));
 HelloWorld::addChild(CCParticleSystemQuad::create("Stars3.plist"));


Moving the Ship with Accelerometer

cocos2d-x 提供了围绕加速器输入的一个抽象层。来看看它是怎么工作的?

First,在HelloWordScene.h中加入一个新的private变量:

float _shipPointPerSecY;


then,添加一个public方法:

virtual void didAccelerate(CCAcceleration *pAccelerationValue);

then, 在init()中让加速计可用:

this->setAccelerometerEnabled(true);

next,在HelloWordScene.cpp中实现上面的方法:

 void HelloWorld::didAccelerate(cocos2d::CCAcceleration *pAccelerationValue)
{
#define KFILTERINGFACTOR 0.1
#define KRESTACCELX -0.6
#define KSHIPMAXPOINTSPERSEC (visibleSize.height*0.5)
#define KMAXDIFFX 0.2
    double rollingX;
    pAccelerationValue->x = pAccelerationValue->y;
    rollingX = (pAccelerationValue->x * KFILTERINGFACTOR) + (rollingX * (1.0 - KFILTERINGFACTOR));
    float accelX = pAccelerationValue->x - rollingX;
    float accelDiff = accelX - KRESTACCELX;
    float accelFraction = accelDiff / KMAXDIFFX;
    _shipPointPerSecY = KSHIPMAXPOINTSPERSEC * accelFraction;
}


finally,在update方法后面添加下面代码:

    float maxY = visibleSize.height - _ship->getContentSize().height/2;
    float minY = _ship->getContentSize().height/2;
    float diff = _shipPointPerSecY * dt;
    float newY = _ship->getPosition().y + diff;
    newY = MIN(MAX(newY, minY), maxY);
    _ship->setPosition(ccp(_ship->getPosition().x,newY));


didAccelerate方法的参数提供了一个包含x,y,z加速计数据信息CCAcceleration类型的变量,在这篇cocos2d-x教程中,你只要关心加速计上的x,就能让你的太空船在跟着手机旋转。

编译->运行,来看看效果。(加速计在模拟器上没有效果的,所以要用真机才能看到效果)


Adding Asteroids(小行星)

添加一些小行星到游戏中,同样先在HelloWordScene.h中加入private成员变量:

    CCArray *_asteroids;
    int _nextAsteroid;
    float _nextAsteroidSpawn;


then,添加一些public方法:

    float randomValueBetween(float low,float high);
    void setInvisible(CCNode *node);
    float getTimeTick();


next,去实现这些方法:

float HelloWorld::randomValueBetween(float low, float high)
{
    return (((float)arc4random() / 0xFFFFFFFFu) * (high - low)) + low;
}

float HelloWorld::getTimeTick()
{
    timeval time;
    gettimeofday(&time, NULL);
    unsigned long millisecs = (time.tv_sec * 1000) + (time.tv_usec / 1000);
    return (float)millisecs;
}


randomValueBetween是一个辅助方法,用来获取在一个浮点数范围内的随机数。

getTimeTick是一个可移植的方法,用来获取以毫秒为单位的时间。(作为一个跨平台方法,替代了CACurrentMediaTime,但产生的是毫秒而不是秒)

next,为了创建一个asteroids数组,添加下面的代码到init()中。

#define KNUMASTEROIDS 15
    _asteroids = new CCArray();
    for (int i=0; i<KNUMASTEROIDS; ++i) {
        CCSprite *asteroid = CCSprite::createWithSpriteFrameName("asteroid.png");
        asteroid->setVisible(false);
        _batchNode->addChild(asteroid);
        _asteroids->addObject(asteroid);
    }


这里,用CCArray类来存储CCSprite类型的对象,注意你是手动new出来的(而不是用arrayWithCapacity)是为了避免autorelease机制。

finally,添加下面代码到update方法中:

    float curTimeMillis = getTimeTick();
    if (curTimeMillis > _nextAsteroidSpawn)
    {
        float randMillisecs = randomValueBetween(0.2, 1.0) * 1000;
        _nextAsteroidSpawn = randMillisecs + curTimeMillis;
        float randY = randomValueBetween(0.0, visibleSize.height);
        float randDuration = randomValueBetween(2.0, 10.0);
        
        CCSprite *asteroid = (CCSprite*)_asteroids->objectAtIndex(_nextAsteroid);
        _nextAsteroid++;
        
        if (_nextAsteroid >= _asteroids->count()) {
            _nextAsteroid = 0;
        }
        
        asteroid->stopAllActions();
        asteroid->setPosition(ccp(visibleSize.width + asteroid->getContentSize().width/2,randY));
        asteroid->setVisible(true);
        asteroid->runAction(CCSequence::create(CCMoveBy::create(randDuration, ccp(-visibleSize.width - asteroid->getContentSize().width, 0)),CCCallFuncN::create(this,callfuncN_selector(HelloWorld::setInvisible)),NULL));


注意:因为CCSequence::actions方法是被定义为具有可变数目的参数,你可能想忽略最后的NULL参数,因为它在C++中是可以这样的。

           但是为了兼容性,cocos2d-x开发者决定保持与Cocos2d兼容,这就需要用NULL来结尾,如果你没有加NULL的话,程序可能会崩溃。

最后一步,添加setInvisible回调方法:

void HelloWorld::setInvisible(cocos2d::CCNode *node)
{
    node->setVisible(false);
}


编译->运行,你就会看到下面的结果:出现了很多小行星



Shooting Lasers(激光射击)

到了让太空船射击的时候了,像以前一样添加private变量:

    CCArray *_shipLasers;
    int _nextShipLaser;


再添加public方法:

virtual void ccTouchesBegan(CCSet *touches,CCEvent *evnet);


then,还是在init()中添加下面代码:

#define KNUMLASERS 5
    _nextShipLaser = 0;
    _shipLasers = new CCArray();
    for (int i=0; i< KNUMLASERS; ++i) {
        CCSprite* shipLaser = CCSprite::createWithSpriteFrameName("laserbeam_blue.png");
        shipLaser->setVisible(false);
        _batchNode->addChild(shipLaser);
        _shipLasers->addObject(shipLaser);
    }
    this->setTouchEnabled(true);


finally,实现出处TouchesBegan 方法:

void HelloWorld::ccTouchesBegan(cocos2d::CCSet *touches, cocos2d::CCEvent *evnet)
{
    CCSprite *shipLaser = (CCSprite*)_shipLasers->objectAtIndex(_nextShipLaser++);
    if (_nextShipLaser >= _shipLasers->count())
    {
        _nextShipLaser = 0;
    }
    shipLaser->setPosition(ccpAdd(_ship->getPosition(), ccp(shipLaser->getContentSize().width/2,0)));
    shipLaser->setVisible(true);
    shipLaser->stopAllActions();
    shipLaser->runAction(CCSequence::create(CCMoveBy::create(0.5, ccp(visibleSize.width, 0)),CCCallFuncN::create(this, callfuncN_selector(HelloWorld::setInvisible)),NULL));
    
}


编译->运行,点击屏幕就能看到我们发射的激光了




Basic Collision Detection(基本碰撞检测)

next,你将添加一些代码来检测激光和小行星的碰撞,当激光打到小行星时,发生爆炸。


first,添加private变量:

int _lives;


then,在update方法中添加:

    CCObject *asteroid1;
    CCObject *shipLaser1;
    CCARRAY_FOREACH(_asteroids, asteroid1)
    {
        CCSprite* asteroid = (CCSprite*)asteroid1;
        if (!asteroid->isVisible())
        {
            continue;
        }
        CCARRAY_FOREACH(_shipLasers, shipLaser1)
        {
            CCSprite* shipLaser = (CCSprite*)shipLaser1;
            if (!shipLaser->isVisible())
            {
                continue;
            }
            if (shipLaser->boundingBox().intersectsRect(asteroid->boundingBox()))
            {
                shipLaser->setVisible(false);
                asteroid->setVisible(false);
                continue;
            }
        }
        if (_ship->boundingBox().intersectsRect(asteroid->boundingBox()))
        {
            asteroid->setVisible(false);
            _ship->runAction(CCBlink::create(1.0, 9));
            _lives-- ;
        }
    }



编译->运行,你就可以炸毁小行星了!

当然,你会注意到当激光打到小行星,并没有爆炸效果,因为我们没有给它添加爆炸粒子效果。前面在添加星星的时候,我们用到了粒子系统,这个问题留给读者。

Win/Lose Detection(输赢检测)

HelloWorldScene.h中类定义前面加入一个枚举:

typedef enum{
    KENDREASONWIN,
    KENDREASONLOSE
}EndReason;


然后是两个private成员变量:

    double _gameOverTime;
    bool _gameOver;


接着是两个private成员方法:

    void endScene(EndReason endReason);
    void restartTapped();


then,进入init()方法,加入:

    _lives = 3;
    double curTime = getTimeTick();
    _gameOverTime = curTime + 30000;


update的中加入:

    if (_lives <=0) {
        _ship->stopAllActions();
        _ship->setVisible(false);
        this->endScene(KENDREASONLOSE);
    }
    else if(curTimeMillis>= _gameOverTime){
         this->endScene(KENDREASONWIN);
    }


finally,实现刚才的那两个方法:

void HelloWorld::restartTapped()
{
    CCDirector::sharedDirector()->replaceScene(CCTransitionZoomFlipX::create(0.5, this->scene()));
    this->scheduleUpdate();
}
void HelloWorld::endScene(EndReason endReason)
{
    if (_gameOver) {
        return;
    }
    _gameOver = true;
    
    char message[10] = "You Win";
    if (endReason == KENDREASONLOSE) {
        strcpy(message, "You Lose");
    }
    
    CCLabelBMFont *label = CCLabelBMFont::create(message, "Arial.fnt");
    label->setScale(0.1);
    label->setPosition(ccp(visibleSize.width/2,visibleSize.height/2));
    this->addChild(label);
    
    strcpy(message, "Restart");
    CCLabelBMFont *restartLabel = CCLabelBMFont::create(message, "Arial.fnt");
    CCMenuItemLabel *restartItem = CCMenuItemLabel::create(restartLabel, this, menu_selector(HelloWorld::restartTapped));
    restartItem->setScale(0.1);
    restartItem->setPosition(ccp(visibleSize.width/2, visibleSize.height*0.4));
    
    CCMenu *menu = CCMenu::create(restartItem,NULL);
    menu->setPosition(CCPointZero);
    this->addChild(menu);
    
    restartItem->runAction(CCScaleTo::create(0.5, 1.0));
    label->runAction(CCScaleTo::create(0.5, 1.0));
    this->unscheduleUpdate();
    
}


编译->运行,如果你受伤多次,你就会死!


音效

HelloWordScene.cpp的顶部加入所需的头文件和命名空间

#include "SimpleAudioEngine.h"
using namespace CocosDenshion;


init()返回前加入:

    SimpleAudioEngine::sharedEngine()->playBackgroundMusic("SpaceGame.wav", true);
    SimpleAudioEngine::sharedEngine()->preloadEffect("explosion_large.wav");
    SimpleAudioEngine::sharedEngine()->preloadEffect("laser_ship.wav");


当激光打到小行星时,加入爆炸声,也就是update()方法中:(绿色部分)

if (shipLaser->boundingBox().intersectsRect(asteroid->boundingBox()))
            {
               <span style="color:#006600;"> SimpleAudioEngine::sharedEngine()->playEffect("explosion_large.wav");</span>
                shipLaser->setVisible(false);
                asteroid->setVisible(false);
                continue;
            }


最后就是发射激光声,ccTouchesBegan()的开头加入:

 SimpleAudioEngine::sharedEngine()->playEffect("laser_ship.wav");

编译->运行,还不错吧,算是完成了!




最后,完整的太空游戏代码点击这里下载

 



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值