Tuesday, March 3, 2015

Use Cocos2D-x Actions & Sequence for animation and callback lambda functions

cocos-2dx-code-running-screenshot

In this post I will show you how to create an animation using Cocos2D-X Actions and Sequences. I
will also show you how to use lambda functions to repeat an action.
Create a new Cocos2D-X project, as shown here.
Now go to HelloWorld::init method inside HelloWorldScene.cpp file.

Right before the function, define constants for starting point where our label "Hello World!" will be shown later.

const float STARTX = 100;
const float STARTY = 500;

Now find the location in the automatically generated code where main scene is being added to root node. Example is shown below

addChild(rootNode, 1, "mainscene");

We will add a text label on screen, code is

auto lblHello = Label::create
 ("Hello World!", "Times", 48.0, cocos2d::Size::ZERO,
 cocos2d::TextHAlignment::CENTER,
 cocos2d::TextVAlignment::CENTER);

lblHello ->setPosition(STARTX,STARTY);
rootNode ->addChild(lblHello, 1, "lblHello");

We created a new label with text "Hello World!", font is Times, size 48, set the location to STARTX STARTY.

Next we add this label to the root node as a child on top of everything else that was there(see the number 1).
Compile, build, and run the code. It should work fine and display a label on screen.

Cocos2D-X Action MoveBy

An action is an instruction for Cocos2D-X engine to do something over a given span of time. For example, move an object from point x, y to x+10, y in 2 seconds.

First of all let's find the size of the window in which the label will be shown, and the size of the text label itself.

auto winSize = Director::getInstance() ->getWinSize(); 
auto lblSize = lblHello ->getContentSize();
auto locToMove = winSize.width - lblSize.width;

We have used the width of the window and the label to calculate how many pixels will our label need to move in order to fly to the right end of the window.
Director is the grand daddy of everything inside a Cocos2D-x game.

To make it fly, we will create an object of Action as illustrated below

auto actMove = MoveBy::create(DURATION, Vec2(locToMove, 0));

I have defined the constant DURATION earlier, it is a float type and represents the speed of animation in seconds.

const float DURATION = 2.0;

This action can be run by calling appropriate method on lblHello, as shown below

lblHello ->runAction(actMove);

Try to build and run your code, you'll see the label will fly from left to right most corner and stop.

Cocos2D-x Sequence

A sequence as the name implies is a set of actions performed on an object one after another. An example of sequence will be

  1. Move an object by 100 pixels on x axis
  2. Scale the object to double its original size
An example of this behavior with the label which we created earlier is given below:


auto winSize = Director::getInstance() ->getWinSize(); 
auto lblSize = lblHello ->getContentSize();

auto locToMove = winSize.width - lblSize.width;

auto actMove = MoveBy::create(DURATION, Vec2(locToMove, 0));
 
auto actGrow = ScaleBy::create(DURATION, 2, 2, 0);

auto seqMoveAndGrow = Sequence::create(actMove, actGrow, nullptr);

lblHello ->runAction(seqMoveAndGrow);



I'm intentionally repeating some of the code since I believe people tend to zoom straight in to the code, without any idea about the context. I personally discourage copy/paste programmers but still they are brothers in code at some level.

Now build and run this code. You will see the text label hello world fly from left to right edge of screen, it will then grow to double its size. The label will go outside the screen, but don't worry about that yet.

Cocos2D-x Spawn

Spawn is yet another construct to group more than one actions, these actions are performed on an object in parallel. For example if you want the label to move right and grow big at same tie. Try following lines instead of seqMoveAndGrow.

auto spawnMoveAndGrow = Spawn::create(actMove, actGrow, nullptr);
lblHello ->runAction(spawnMoveAndGrow);
// see above snippet for variable definitions

Build and run your code now. You will see spawn in action. I highly recommend you to write code alongside reading this article, that will keep you interest level up and fortify the learning as well.

Using Lambda function to repeat animation

Lambda functions were introduced in C++ 11. These are anonymous methods or call back functions in a decent C++ form. We can register a lambda function as part of a sequence. When the sequence ends, our lambda function will be called back.

Define the lambda function

The lambda function will be defined as a member variable in HelloWorld class.
Open the corresponding header file HelloWorld.h
Right before the ending bracket of class HelloWorld, add following code

public:
 cocos2d::CallFunc* callBackLabelMoveComplete;

CallFunc* is a lambda function type defined by Cocos2D-x game engine.

Assign a value to lambda function

Now let's open the file HelloWorld.cpp and inside init() function go to line after where you added the label to root node, I mean this line

rootNode ->addChild(lblHello, 1, "lblHello");

We will assign a value to the callback function in following manner







// callBackLabelMoveComplete is class level variable
callBackLabelMoveComplete = CallFunc::create([this](){

auto mainScene = getChildByName("mainscene");
auto lblHello = mainScene ->getChildByName("lblHello");
  
int nMoveVal = 100;

auto winSize = Director::getInstance() ->getWinSize(); 
  
auto lblSize = lblHello ->getContentSize().width;
  
auto locToMove = winSize.width - lblSize;

nMoveVal = locToMove;
if(lblHello ->getPositionX() >= locToMove){
 nMoveVal = -locToMove;  
 }

cocos2d::MoveBy * actMove;

actMove = MoveBy::create(DURATION, Vec2(nMoveVal, 0));
auto seqMove = Sequence::create(actMove, callBackLabelMoveComplete, nullptr);
lblHello ->runAction(seqMove);
});


Important: We have the lambda function ready to be fired, but it won't do anything until we pull the trigger somewhere.
In order to do so, we will use a sequence. Where the label will move to right, our callback lambda function will be fired and the label will move back to left. Same behavior will keep repeating again and again. The code for this operation is given below:

auto winSize = Director::getInstance() ->getWinSize(); 
auto lblSize = lblHello ->getContentSize();

auto locToMove = winSize.width - lblSize.width;

auto actMove = MoveBy::create(DURATION, Vec2(locToMove, 0));
 
auto seqMoveForever = Sequence::create(actMove, callBackLabelMoveComplete, nullptr);

lblHello ->runAction(seqMoveForever);
// follow me on Twitter @Na3mAkrm
// for variable definitions see earlier snippets
// code highlighting by tohtml.com

Build and run to see the label running around for you.
There could have been a fighter jet here, but that would make things little complicated.
I will post more simple and easy to use articles so that you can understand how games work, what are sprites, and collisions etc.

Assignment:
Try to move the label in a box formation. Left to right, then bottom right, then back left bottom, and up to the starting point again. Code in lambda function will be changed to do so.

Video of the code in action will be posted soon.

Keep coming back for more.
Feel free to talk back, share ideas and discuss issues. Do follow me on twitter: @Na3mAkrm

3 comments:

  1. Hi.
    Thanks for making these tutorials, they are very helpful.
    Is there any advantage to using lambda functions over using scheduleupdate() and update() functions of cocos?

    ReplyDelete
  2. Hi,
    I am not an expert so I might be wrong. I searched online and found that there is some problem with schedule update, the description was too complicated so I could not understand it.
    Another friend of mine who is an expert game programmer told me that it is a good practice to use lambda expressions. The game becomes much more dynamic and performance is improved.

    ReplyDelete
  3. Thanks for the (insanely) quick reply.
    Please keep adding more tutorials.
    All the best

    ReplyDelete

Feel free to talk back...