Cocos2d-x basics

Some articles about Cocos2d-x framework

Advanced actions

Posted at — Sep 25, 2020

The previous article described how to use Action to move the objects. Now we’ll learn how to do more complicated stuff.

The task

But first let’s create a task to implement.

We have to create a scene with two elements: a green UFO and a blue ship.

  1. The UFO should constantly rotate around it’s center slightly
  2. There should be two buttons: left and right arrows. After pressing the button the UFO should start moving in appropriate direction until it reaches the “finish” position
  3. If the user presses another button during movement, the ship stops and then starts to move to another side.
  4. Blue ship starts on the left side of the screen, facing up (north). There is a button (arrow) on the right side of the screen
  5. After the button is pressed the sheep rotates and then moves towards right.
  6. When the ship reaches destination it turns nose up again. The arrow button changes direction.
  7. If the user presses the arrow button again the ship rotates and moves to another side.

So, the green UFO will work like this:

The green UFO moves from side to side and rotates slightly

And the blue ship:

Blue ship behavior

I should remind you the images here came from free image sets: buttons and spaceships

Action tags

The Action class has a special “tag” parameter. That’s an integer value used to distinguish one action from another. You can set this value with setTag method or sometimes provide as a parameter to constructor.

Also, the Node class has some methods for processing action tags:

And there is a singleton instance of ActionManager with similar methods, but it’s not recommended using it.

In our example we’ll use tags to distinguish UFO rotation and horizontal movement. As for rotation, it’s implemented with the corresponding action and the RepeatForever:

Sequence* rseq = Sequence::create(RotateBy::create(3, 30),
                                  RotateBy::create(3, -30), nullptr);

RepeatForever* reps = RepeatForever::create(rseq);
reps->setTag(AT_UFO_ROTATION);
greenUfo->runAction(reps);

Here AT_UFO_ROTATION is an integer constant used as tag value.

The UFO launch looks like this:

MoveBy* moveTo = MoveTo::create(time, Vec2(newX,240));
moveTo->setTag(AT_UFO_MOVING);
greenUfo->runAction(moveTo);

and stopping like this

greenUfo->stopAllActionsByTag(AT_UFO_MOVING);

Here stopAllActionsByTag stops the action marked by AT_UFO_MOVING tag, but the rotation continues.

In case of blue ship we’ll have to stop the whole sequence. There is not much difference from single action; just set tag to the sequence and not on the action.

// create some actions
///....

Sequence* seq = Sequence::create(rotateAct, moveAct, rotateBackAct, nullptr);
seq->setTag(AT_BS_MOVE);

blueShip->runAction(seq);

Receiving an action

According to the task, it’s not allowed to interrupt the blue ship’s rotation. In order to do this we’ll have to check if the action is running at the moment.

There are two methods we can use for it : getNumberOfRunningActionsByTag() and getActionByTag().

The code will be almost same for both of them:

if (blueShip->getNumberOfRunningActionsByTag(AT_BS_ROTATION_BACK)>0 ) {
  log("%s: rotating back, do not interfere with moving", __func__);
  return;
}

or

if (blueShip->getActionByTag(AT_BS_ROTATION_BACK) != nullptr) {
  log("%s: rotating back, do not interfere", __func__);
  return;
}

Processing action finish

The task says the image on arrow button must be performed right after the ship reaches its destination. We can do it with an instance of CallFunc class. This action calls the function provided to the constructor.

So, we can create a lambda-expression, and provide it to the CallFunc‘s constructor:

CallFunc* finalCf = CallFunc::create([this]() {
  // create and launch action to rotate the ship back
  RotateTo* ra2 = RotateTo::create(3, 0);
  ra2->setTag(AT_BS_ROTATION_BACK);
  this->blueShip->runAction(ra2);

  // change image on the button
  // ......
});

Sequence* seq = Sequence::create(rotateAct, moveAct, finalCf, nullptr);
seq->setTag(AT_BS_MOVE);

blueShip->runAction(seq);

As you already know, the Sequence constructor receives all the actions and as parameters and the nullptr at the end. Here we perform three actions: rotation, movement and callback. Then callback will add one more action to rotate the ship back.