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:
#####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:
###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)