[Cocos3.0 Tutorial] Game CatchMe! ()

Cocos2d-x v3.0 Tutorial

##CatchMe (C++)

In this tutorial we are going to build a simple game called CatchMe.
The game consist in use clicks to catch a simple animated label moving arround de window.
The code will be written on linux, only for clarification.

The tutorial include:

  • Custom typography
  • Actions
  • Events
  • Animations
  • Use UserDefault (save player scores).

Lets start.

###Chapther 1


#####Setting the proyect
Create a new cocos project:

Write in terminal
cocos new -l cpp -d /path/to/project CatchMe

Replace /path/to/project for the path where you want to save your project.

After that open the project in your IDE, there must be 4 clases:

  • HelloWorld.cpp
  • HelloWorld.h
  • AppDelegate.cpp
  • AppDelegate.h

Add a new empty class called CatchMe.cpp and his respective CatchMe.h. This class take care of the most important element on our game, the label and all his procedures (Listeners,Animations,etc.)

Until now our HelloWorld looks like default cocos HelloWorld:

HelloWorld


#####Building our CatchMe label

First go to: Dafont to download a new font for our label, I downloaded the: NextGames.tff; We must save it in /path/to/CatchMe/Resources/fonts

You can build your own font.ttf or dowloaded in other places too.

In second place we have to declare the next variables and procedures in our CatchMe.h:

///include cocos to the new class

#include "cocos2d.h"


///Extends LabelTTF because CatchMe will have _eventDispatcher for this must be a Node(Father of LabelTTF)

class CatchMe : cocos2d::LabelTTF
{
public:

   ///Constructor and Destructor of CatchMe class
   
    CatchMe();
    ~CatchMe();
    
    ///Gives LabelCatchMe to others
    
    cocos2d::LabelTTF* getLabel();

protected:
    cocos2d::LabelTTF* LabelCatchMe;

};

After that, declare constructor,destructor and getLabel method in Catch Me.cpp

CatchMe::CatchMe()
{

    LabelCatchMe = LabelTTF::create("CatchMe", "fonts/NextGames.ttf", 72);
}

CatchMe::~CatchMe()
{
}

LabelTTF *CatchMe::getLabel()
{
    return LabelCatchMe;
}



Now we replace Hello World label for our new label on HelloWorld.cpp; for that we first have to create a new instance of our class CatchMe on HelloWorld.cpp.

Then declare the CatchMe instance like attribute of HelloWorld class in HelloWorld.h:
protected: CatchMe* Game; cocos2d::LabelTTF* LabelCatchMe;

As you might have noticed also declare another variable, LabelCatchMe, this will be used to maintain backward copy our label in HelloWorld.

And replace:

 // add a label shows "Hello World"
    // create and initialize a label
    
    auto label = LabelTTF::create("Hello World", "Arial", 24);
    
    // position the label on the center of the screen
    label->setPosition(Point(origin.x + visibleSize.width/2,
                            origin.y + visibleSize.height - label->getContentSize().height));

    // add the label as a child to this layer
    this->addChild(label, 1);

to:

 // add a label shows "CatchMe"
    
    Game = new CatchMe();
    
    LabelCatchMe = Game->getLabel();
    
    
    // position the label on the center of the screen
    LabelCatchMe->setPosition(Point(origin.x + visibleSize.width/2,
                            origin.y + visibleSize.height - LabelCatchMe->getContentSize().height));

    // add the label as a child to this layer
    this->addChild(LabelCatchMe, 1);


Don’t forget add #include "CatchMe.h" on HelloWorld.h

If you run the proyect, should look like this:
Second HelloWorld

###Chapther 2


#####Add some color
The label looks good but would look better if the colors change.For this purpose we use GLubyte type variable.

In CatchMe.h declare the next variables:

GLubyte r,g,b;

r,g,b corresponds to RGB color model, The value of this variables can vary between 0 and 255.

Now we go to CatchMe Constructor in CatchMe.cpp and add the next lines

CatchMe::CatchMe()
{
    ///Initializes the Label CatchMe
    LabelCatchMe = LabelTTF::create("CatchMe", "fonts/NextGames.ttf", 72);
    
    srand(time(nullptr));
    r = rand()%255;
    g = rand()%255;
    b = rand()%255;
    LabelCatchMe->setColor( Color3B(r,g,b) );
}```

Because of this the label will appear with a random color; but we want to change the color in current time, then we do the next steps.

Go to CatchMe.h and declare the methods:

protected:
///return a small value to change the colors gradually
GLubyte newColor();

public:
///Change color label
void changeColor();


In CatchMe.cpp:

GLubyte CatchMe::newColor()
{
GLubyte nuevoColor = rand() % 5;
if(rand() % 3 == 0)
nuevoColor = -nuevoColor;
return nuevoColor;
}

void CatchMe::changeColor()
{
r += newColor();
g += newColor();
b += newColor();
LabelCatchMe->setColor( Color3B(r,g,b) );
}

The next step is to update the game scene and call the methods we declared above.

In HelloWorld.h:  
`protected: void update(float df);`

In HelloWorld.cpp:

void HelloWorld::update(float df)
{
Game->changeColor();
LabelCatchMe = Game->getLabel();
}

And called in the HelloWordl::init:

schedule (schedule_selector(HelloWorld::update));

note that the update need a schedule.

if you run the project you have something like this:

![](/uploads/default/6067/140524215824_changeColor.png)

With the label color changing Constantly.

###Chapther 3

- - -

#####Events
In this section we want that our label do something if the player touched.

For that we dacleare the next method in CatchMe.h:

protected:
///Manage Events and Actions
void setEventHandlers();

And in the CatchMe.cpp:

void CatchMe::setEventHandlers()
{
//Create a “one by one” touch event listener (processes one touch at a time)
auto listener = EventListenerTouchOneByOne::create();
// When “swallow touches” is true, then returning ‘true’ from the onTouchBegan method will “swallow” the touch event, preventing other listeners from using it.
listener->setSwallowTouches(true);
// Example of using a lambda expression to implement onTouchBegan event callback function
listener->onTouchBegan = [&](Touch* touch, Event* event){
// event->getCurrentTarget() returns the listener’s sceneGraphPriority node.
auto target = static_cast<Sprite*>(event->getCurrentTarget());

        //Get the position of the current point relative to the button
        Point locationInNode = target->convertToNodeSpace(touch->getLocation());
        Size s = target->getContentSize();
        Rect rect = Rect(0, 0, s.width, s.height);
        //Check the click area
        if (rect.containsPoint(locationInNode))
        {
            ///The action that we want to run if the Label get touched
            auto hide = Hide::create();
            target->runAction(hide);
            return true;
        }
        return false;
    };
    //Add listener
        _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, LabelCatchMe);

}

Also need add the call of this method in CatchMe constructor:

CatchMe::CatchMe()
{
///Initializes the Label CatchMe
LabelCatchMe = LabelTTF::create(“CatchMe”, “fonts/NextGames.ttf”, 72);

srand(time(nullptr));
r = rand()%255;
g = rand()%255;
b = rand()%255;
LabelCatchMe->setColor( Color3B(r,g,b) );

setEventHandlers();

}


Now if you run the proyect and click the label you will see the magic!



- - -

#####Movement and Animations
Now we want to do your game hard to play isn't it?

To move the label we need to generate a random point on the screen,taking into account the size of the label, so we will need the following method:

In CatchMe.h:

protected:
///Generate a random point on the scree,taking into account the size of the label
cocos2d::Point generatedRandomPoint(cocos2d::Node* node);


In CatchMe.cpp:

Point CatchMe::generatedRandomPoint(Node* node)
{
srand(time(nullptr));
int randx;
int randy;
int contentSizeHeight = node->getContentSize().height;
int contentSizeWidht = node->getContentSize().width;
int visibleSizeHeight = Director::getInstance()->getVisibleSize().height - (contentSizeHeight * 0.5);
int visibleSizeWidth = Director::getInstance()->getVisibleSize().width - (contentSizeWidht * 0.5);
randx = rand() % visibleSizeWidth;
randy = rand() % visibleSizeHeight;
while((randx > (contentSizeWidht * 0.5) && randx < visibleSizeWidth && randy > (contentSizeHeight *0.5) && randy < visibleSizeHeight) == false ){
randx = rand() % visibleSizeWidth;
randy = rand() % visibleSizeHeight;}
return Point(randx,randy);

}


Now we can add movement and animations, with the next method:

In CatchMe.h:
`  protected:
    void animateGameTitle();`  
In CatchMe.cpp:

void CatchMe::animateGameTitle()
{
///get the label size
int nodeWidth = LabelCatchMe->getContentSize().width;
int nodeHeight = LabelCatchMe->getContentSize().height;
///Animatios to use
auto moveTo = MoveTo::create(0.2f + rand() % 40 / 150.0f, generatedRandomPoint(LabelCatchMe));
auto rotateTo = RotateTo::create(0.5f + rand() % 40 / 150.0f, rand() % 360);
auto scaleTo = ScaleTo::create(0.2f + rand() % 40 / 150.0f,rand() % nodeWidth * 0.5/60,rand()%nodeHeight*0.5 /60);
///The animations will repeated many times,calls himself
auto callFunc = CallFunc::create( this, callfunc_selector(CatchMe::animateGameTitle) );
auto sequence = Sequence::create(moveTo,callFunc, nullptr);
///this Actions will run in parallel to MoveTo sequence.
LabelCatchMe->runAction(sequence);
LabelCatchMe->runAction(rotateTo);
LabelCatchMe->runAction(scaleTo);
}
```
Cocos2d-x has many other animations to use, can be found in the documentation of this.

Finally call animateGameTitle in the constructor:

CatchMe::CatchMe()
{
    ///Initializes the Label CatchMe
    LabelCatchMe = LabelTTF::create("CatchMe", "fonts/NextGames.ttf", 72);

    srand(time(nullptr));
    r = rand()%255;
    g = rand()%255;
    b = rand()%255;
    LabelCatchMe->setColor( Color3B(r,g,b) );
    setEventHandlers();
    animateGameTitle();

}

If you run your proyect you will have something like this:

With the label moving, rotating and resizing, all over the screen.

But we have a problem, if we click the label, this not appears again.
For fix it we go to the setEventHandlers where we had this:

///The action that we want to run if the Label get touched
                auto hide = Hide::create();
                target->runAction(hide);
                return true;

we raplace for:

    ///The action that we want to run if the Label get touched
                auto hide = Hide::create();
                auto show = Show::create();
                auto delay = DelayTime::create(0.75f);
   ///make a sequence that hide and show a little delay to the label
   ///keep moving to another part of the screen without the player sees
                 auto sequenceTouch = Sequence::create(hide,delay,show, nullptr);
                target->runAction(sequenceTouch);
                return true;

Now we have a funnier game to play!

###Chapther 4


#####Scores!
A game without scores? What sense does it play if you can not exceed the maximum score or even know your score!?

We need to declare 3 new variables, due to time I’ll add in the class HelloWorld is that it is not very elegant but worked for now.

HelloWorld.h:

public
 cocos2d::LabelTTF* currentScore;
 cocos2d::LabelTTF* bestScore;
    ```
And in the CatchMe.h:
`public:
int currentPoints;`  

HelloWorld.cpp:
In HelloWorld::init;

currentScore = LabelTTF::create(“0000”,“fonts/NextGames.ttf”,25);
currentScore->setPosition(Point(origin.x + visibleSize.width-currentScore->getContentSize().width,
origin.y + visibleSize.height-currentScore->getContentSize().height));
addChild(currentScore,-1);
```
And in CatchMe.cpp:
In:

///The action that we want to run if the Label get touched
                auto hide = Hide::create();
                auto show = Show::create();
                auto delay = DelayTime::create(0.75f);
                ///make a sequence that hide and show a little delay to the label
                ///keep moving to another part of the screen without the player sees
                 auto sequenceTouch = Sequence::create(hide,delay,show, nullptr);
                target->runAction(sequenceTouch);
               

add:
currentPoints +=5;
This will increment currentPoints value if the label get touched.

get back to HelloWorld.cpp
In HelloWorld::update
add:

 char buffer[10];
 sprintf(buffer, "%04lli", Game->currentPoints);
 currentScore->setString(std::string(buffer));

This will take the current value og ‘currentPoints’ and transform it to string.

Now if you run the proyect you will play with points!!

But what about best score?

For that we declare the method bestScoreUpdated in HelloWorld.h:
void bestScoreUpdated();

and in the HelloWorld.cpp:

void HelloWorld::bestScoreUpdated()
{
    int best = UserDefault::getInstance()->getIntegerForKey("Best_Score");
    
    if(best < Game->currentPoints){
        best = Game->currentPoints;
        UserDefault::getInstance()->setIntegerForKey("Best_Score",best);
        char buffer[10];
        sprintf(buffer, "%04lli", best);
        bestScore->setString("Best: " + std::string(buffer));
        UserDefault::getInstance()->flush();
    }
    else{
        char buffer[10];
        sprintf(buffer, "%04lli", best);
        bestScore->setString("Best: " + std::string(buffer));
}
}

Now we call the method in HelloWorld::update:

void HelloWorld::update(float df)
{
 Game->changeColor();
 LabelCatchMe = Game->getLabel();
 char buffer[10];
 sprintf(buffer, "%04lli", Game->currentPoints);
 currentScore->setString(std::string(buffer));
 bestScoreUpdated();
}

And finally we add the label to the scene un HelloWorld::init:

 bestScore = LabelTTF::create("Best: ","fonts/NextGames.ttf",25);
    bestScore->setPosition(Point(origin.x + currentScore->getContentSize().width * 2.0f,
                                    origin.y + visibleSize.height-currentScore->getContentSize().height));
    addChild(bestScore,-1);

Now you have a useful game!


I hope you enjoy the tutorial!

Some extras to improve the game:
Add a timer for play,add funny music. Limit is your imagination with cocos2d-x!


changeColor.png (172.5 KB)


HelloWorldScene.h.zip (0.6 KB)


HelloWorldScene.cpp.zip (1.5 KB)


CatchMe.cpp.zip (1.7 KB)


HelloWorld0.png (28.5 KB)


Points.png (170.6 KB)


best.png (176.5 KB)


HelloWorld1.png (75.5 KB)


CatchMe.h.zip (0.6 KB)


event.png (172.3 KB)

2 Likes

Great tutorial for noobs like me.

Thanks:)

Run-Time Check Failure #2 - Stack around the variable ‘buffer’ was corrupted.

Is that something wrong with the “buffer”? or maybe it is just too small?

I am using VS2012 on win7.

Same IDE, same error like @Suigintou
currentScore->setString(std::string(buffer)); line97

Ok, I will check the error in visual soon, thank’s! :;ok

Hi @Torelli95, @Suigintou and @panor,

I think that the buffer stack value corruption/memory access violation issue is caused by this line:

I changed it to:

sprintf(buffer, “%04i”, Game->currentPoints);

as Game->currentPoints was declared as int so I dropped the “ll” from the format string.

Using Eclipse:
After Chapther 1 I get the error:
On line:
class CatchMe : cocos2d::LabelTTF
Error:
Multiple markers at this line
- previous definition of ‘class
CatchMe’
- redefinition of ‘class CatchMe’

Code:
.h

///include cocos to the new class

#include "cocos2d.h"


///Extends LabelTTF because CatchMe will have _eventDispatcher for this must be a Node(Father of LabelTTF)

class CatchMe : cocos2d::LabelTTF
{
public:

   ///Constructor and Destructor of CatchMe class

    CatchMe();
    ~CatchMe();

    ///Gives LabelCatchMe to others

    cocos2d::LabelTTF* getLabel();

protected:
    cocos2d::LabelTTF* LabelCatchMe;

};

.cpp

#include "CatchMe.h"
#include "HelloWorldScene.h"
USING_NS_CC;

CatchMe::CatchMe()
{

    LabelCatchMe = LabelTTF::create("CatchMe", "fonts/NextGames.ttf", 72);
}

CatchMe::~CatchMe()
{
}

LabelTTF *CatchMe::getLabel()
{
    return LabelCatchMe;
}

EDIT:
I downloaded the CatchMe.cpp and h file and removed everything else and now it works.

Thanks for a great tutorial. In the next, make more comments for each syntax please.

hi, i don’t fully understand the event propagation model of Cocos 2dx. to me the following snippet seems redundant since it is only attached to

LabelCatchMe

with

_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, LabelCatchMe);

   listener->onTouchBegan = [&](Touch* touch, Event* event){
        // event->getCurrentTarget() returns the *listener's* sceneGraphPriority node.
        auto target = static_cast<Sprite*>(event->getCurrentTarget());
        //Get the position of the current point relative to the button
        Point locationInNode = target->convertToNodeSpace(touch->getLocation());
        Size s = target->getContentSize();
        Rect rect = Rect(0, 0, s.width, s.height);
        //Check the click area
        if (rect.containsPoint(locationInNode))

aren’t rect and locationInNode both derived from target?

thanks and sorry about any typos, back on my phone again:)