Cocos2D-X Tutorial: Making a Universal App: Part 1

Learn how to make a universal app that works on the iPhone, iPad, and Android in this Cocos2D-X tutorial. By .

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Placing the Moles

For this game, you’re going to add three moles to the scene – one for each hole. The moles will usually be “underground” beneath the lower part of the grass – but occasionally they will “pop up” so you can try to whack them.

First, let’s add the moles to the level underneath each of the holes. You’ll temporarily make them appear above all the other art so you can make sure they’re in the right spot, then you’ll put them underground once you’re happy with their position.

Open up HelloWorldScene.h and add an array as private variable of HelloWorldScene to keep track of the moles in the level, as shown below:

CCArray *_moles;

By storing the moles in this array, it will make it easy to loop through each of the moles later on.

Before you go to the next step, you have to create our own Sprite Class, press Command+N choose OS X/C and C++/C++ Class and name it GameSprite. Add the below codes to GameSprite.h as below

#include <iostream>
#include "cocos2d.h"

using namespace cocos2d;

class GameSprite : public CCSprite {
    CCSize _screenSize;
    bool userData;

public:
    GameSprite();
    ~GameSprite();
    
    static GameSprite* gameSpriteWithFile(const char *pszFileName);
    void setUserData(GameSprite *mole, bool flag);
    bool getUserData(GameSprite *mole);
};

Now add codes to the GameSprite.cpp as below

#include "GameSprite.h"

GameSprite::GameSprite(){
    
}

GameSprite::~GameSprite(){
    
}

GameSprite* GameSprite::gameSpriteWithFile(const char *pszFileName){
    GameSprite *sprite = new GameSprite();
    if (sprite && sprite->initWithSpriteFrameName(pszFileName)) {
        sprite->autorelease();
        return sprite;
    }
    CC_SAFE_DELETE(sprite);
    return NULL;
}

void GameSprite::setUserData(GameSprite *mole, bool flag){
    mole->userData = flag;
}

bool GameSprite::getUserData(GameSprite *mole){
 return mole->userData;
}

Next, in HelloWorldScene.h include GameSprite.h

#include "GameSprite.h"

and in HelloWorldScene.cpp add the code to place the moles at the end of your init function before return, as shown below:

// load sprites
CCSpriteBatchNode *spriteNode = CCSpriteBatchNode::create("sprites.pvr.ccz");
this->addChild(spriteNode, 999);
CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("sprites.plist");
     
float offset = 155;
float startPoint = 85 + offset;
CCSize frameSize = CCEGLView::sharedOpenGLView()->getFrameSize();
if ( fabs(frameSize.width - 1024) < FLT_EPSILON) {
    offset = offset * (1024 / 480.0);
    startPoint = startPoint * (1024 / 480.0) ;
}
    
// Add mole2
GameSprite *mole2 = GameSprite::gameSpriteWithFile("mole_1.png");
mole2->setPosition(HelloWorld::convertPoint(ccp(startPoint, 85)));
spriteNode->addChild(mole2);
    
// Add mole1
GameSprite *mole1 = GameSprite::gameSpriteWithFile("mole_1.png");
mole1->setPosition(HelloWorld::convertPoint(ccpSub(mole2->getPosition(), ccp(offset, mole2->getPositionY() - 85))));
spriteNode->addChild(mole1);
       
// Add mole3
GameSprite *mole3 = GameSprite::gameSpriteWithFile("mole_1.png");
mole3->setPosition(HelloWorld::convertPoint(ccpAdd(mole2->getPosition(), ccp(offset, 85 - mole2->getPositionY()))));
spriteNode->addChild(mole3);
_moles = CCArray::create(mole1, mole2, mole3, NULL);
_moles->retain();

This first creates a CCSpriteBatchNode for the sprites, so that drawing the three moles is done more efficiently, and adds it as a child of the layer. Note it’s setting the zOrder value to 999 temporarily, so that the moles appear on top so you can make sure they’re set up OK.

It then loads all of the sprite frames from the property list to the cache, so they can be pulled out later.

Then it goes through and creates a sprite for each mole, places them in the scene, and adds them to the list of moles. Note the coordinate for each mole is within the 480×320 “playable area” of the game (the size of the iPhone). For the iPad, these points will need to be converted, so you don’t use hard-coding. And following function is how you deal with multi-resolution.

Add the following method right above the init function:

CCPoint HelloWorld::convertPoint(CCPoint point){
    CCSize frameSize = CCEGLView::sharedOpenGLView()->getFrameSize();
    if ( fabs(frameSize.width - 1024) < FLT_EPSILON) {
        return ccp(0.9 * point.x + 47, point.y + 100);
    }
    if (fabs(frameSize.width - 1136) < FLT_EPSILON) {
        return ccp(point.x, point.y - 18);
    }
    return point;
}

and declare it under the public section of HelloWorldScene.h

CCPoint convertPoint(CCPoint point);

This function converts a point in the "playable area" to the appropriate screen position on the iPad. Remember that:

  • You're using the HD graphics on the iPad, so all points are doubled.
  • You're centering that 960x640 area in the 1024x968 iPad screen, so that leaves 32 pixel margins on the left and right, and 64 pixel margins on the top and bottom.

So this method simply does that math to give the right position on the iPad.

One more thing - before you forget, add the following lines to clean up the memory you allocated for the moles array in the destructor:

HelloWorld::~HelloWorld()
{
    CC_SAFE_RELEASE_NULL(_moles);
}

and don't forget to add the destructor declaration in HelloWorldScene.h

~HelloWorld();

Compile and Run your code, and you should see the three moles happily in the scene at the correct spots! You should try the code on the iPhone, iPhone Retina, and iPad to make sure that they're in the right spot on each device.

figure9

Popping the Moles

Now that you're sure the moles are in the right place, let's add the code to make them pop out of their holes.

First things first - switch the zOrder value of 999 for the mole sprite sheet back to 0 so the moles are underground.

Once that's done, add the following line of code to the bottom of your init function:

this->schedule(schedule_selector(HelloWorld::tryPopMoles), 0.5);

If you haven't seen this before, you can run the schedule function on a node to tell Cocos2D-X to call another function every so many seconds. In this case, you want to try popping some moles out of their holes every 1/2 second.

Next, add the implementation of tryPopMoles:

void HelloWorld::tryPopMoles(CCTime dt){
    GameSprite *mole;
    for (int i = 0; i < 3; i++) {
        mole = (GameSprite *)_moles->objectAtIndex(i);
        if (arc4random() % 3 == 0) {
            if (mole->numberOfRunningActions() == 0) {
                this->popMole(mole);
            }
        }
    }
}

This function will be called every 1/2 second, and each time it will loop through each mole and give it a 1 in 3 chance of popping out of its hole. But it will only pop out if it isn't moving already - and one easy way to check for this is to see if the number running actions is 0.

Finally, add the implementation of popMole:

void HelloWorld::popMole(GameSprite *mole){
    CCMoveBy *moveUp = CCMoveBy::create(0.2, ccp(0, _winSize.height * 0.25));
    CCEaseInOut *easeMoveUp = CCEaseInOut::create(moveUp, 3.0);
    CCDelayTime *delay = CCDelayTime::create(0.5);
    CCAction *easeMoveDown = easeMoveUp->reverse();
    mole->runAction(CCSequence::create(easeMoveUp, delay, easeMoveDown, NULL));
}

And don’t forget to add the interfaces in the head file:

class HelloWorld : public cocos2d::CCLayer
{
private:
    CCSize          _winSize;
    CCArray*        _moles;

public:
    ~HelloWorld();
    
    // Method 'init' in cocos2d-x returns bool, instead of 'id' in cocos2d-iphone (an object pointer)
    virtual bool init();

    // there's no 'id' in cpp, so we recommend to return the class instance pointer
    static cocos2d::CCScene* scene();
    
    // a selector callback
    void menuCloseCallback(CCObject* pSender);

    CCPoint convertPoint(CCPoint point);
    
    void tryPopMoles(CCTime dt);
    void popMole(GameSprite *mole);
        
    // preprocessor macro for "static create()" constructor ( node() deprecated )
    CREATE_FUNC(HelloWorld);
};

This code uses some Cocos2D-X actions to make the mole pop out of its hole, pause for half a second, then pop back down. Let’s go through this line-by-line to make sure you’re on the same page:

  1. Creates an action to move the mole move up along the Y axis as much as the mole is tall. Since you placed the mole right below the hole, it will look right.
  2. To make the movement look more natural, it wraps the move action with a CCEaseInOut action. This causes the action to go slower at the beginning and end, as if the mole is accelerating/decelerating, as it naturally would.
  3. To create an action to move the mole move back down again, an easy way is to call the reverse function on an action, which will give its opposite.
  4. Creates an action to pause for one second after the mole pops out.
  5. Now that the actions are ready to go, it runs them on the mole in a sequence: move up, delay, and finally move down. Note it has to terminate the sequence with a nil to show it’s done.

That's it! Compile and Run the code, and you'll see the moles happily popping out of their holes!

figure10