Reza Ghobadinic

Node Parenting

One of the features of the Node class is that you can parent them together and create a hierarchy of nodes. This allows us to change the transformation of a parent node and cause its children nodes to follow suit and do the same. Unlike in real life, where children usually don’t listen to their parents!

This is useful when you have a game character or vehicle and you want to parent independent moving parts to it, like weapons or turret, etc…

Until now, we had to use the simple parenting method addChild() that attached our nodes to the main scene that runs in the app. Inside the init() method of our HelloWorld.cpp we had:

this->addChild(myNode);

This is not all! There are other options that you can set when parenting to a node. There is tag, name and z order to set. The tag and name will help us refer to the children nodes easier. The z order will define which of the children will be drawn before the other ones. The lower the z order is the more priority they have for drawing on the screen.

Lets have a look at the different methods we can use here:

Methods to call from a Parent Node

There are all sort of methods related to parenting. You can add and remove children. You can get a pointer to them to access them. You can get their counts. You can assign a name or tag to them for easier access. Delete them all in one go, iterate on the list of children, etc…

To add a child to the parent node:

// Adds a child to the container with z-order as 0.
virtual void 	addChild (Node *child)

// Adds a child to the container with a local z-order.
virtual void 	addChild (Node *child, int localZOrder)

// Adds a child to the container with z order and tag.
virtual void 	addChild (Node *child, int localZOrder, int tag)

// Adds a child to the container with z order and tag.
virtual void 	addChild (Node *child, int localZOrder, const std::string &name)

To remove a child from the parent node:

// Removes a child from the container.
virtual void 	removeChild (Node *child, bool cleanup = true)

// Removes a child from the container by tag value.
virtual void 	removeChildByTag (int tag, bool cleanup = true)

// Removes a child from the container by tag value.
virtual void 	removeChildByName (const std::string &name, bool cleanup = true)

// Removes all children from the container with a cleanup.
virtual void 	removeAllChildren ()

// Removes all children from the container, and do a cleanup to all running actions depending on the cleanup parameter.
virtual void 	removeAllChildrenWithCleanup (bool cleanup)

To get a child from the parent node:

// Gets a child from the container with its tag.
virtual Node * 	getChildByTag (int tag) const

// Gets a child from the container with its name.
virtual Node * 	getChildByName (const std::string &name) const

// Gets a child from the container with its tag that can be cast to Type T.
template<typename T> T getChildByTag (int tag) const

// Gets a child from the container with its name that can be cast to Type T.
template<typename T> T getChildByName (const std::string &name) const

Get all children and their count from a parent node:

// Returns the array of the node's children.
virtual Vector< Node * > & 	 getChildren ()

// Return the children count
virtual ssize_t 	         getChildrenCount () const

Enumerate on the children:

// Search the children of the receiving node to perform processing for nodes which share a name.
virtual void 	enumerateChildren (const std::string &name, std::function< bool(Node *node)> callback) const

Methods to call from a Child Node

There are some virtual methods to call from the child node too!

Being virtual means you have to define these inside your child node class and keep a pointer to the parent inside your node, so that you can call _parent->removeChild() inside the child node class!

Get and Set the parent node:

// Sets the parent node.
virtual void 	        setParent (Node *parent)

// Returns a pointer to the parent node.
virtual Node * 	        getParent ()
virtual const Node * 	getParent () const

Remove itself from the parent node:

// Removes this node itself from its parent node.
virtual void 	removeFromParent ()

// Removes this node itself from its parent node with a cleanup.
virtual void 	removeFromParentAndCleanup (bool cleanup)

Parenting Example

In this example I will make a cube that is parented to the main scene. Inside the cube class I will create 4 triangles as its children. These objects will be animated and each triangle will be deleted in each cycle of the animation. Something like this:

We will have two Node classes that will be called NodeBox and NodeTriangle.

Lets get to it then.

HelloWorldScene

Inside the HelloWorldScene.cpp add the header at the top:

#include "NodeBox.h"

Then in the init() class create an instance of the box class:

// on "init" you need to initialize your instance
bool HelloWorld::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !Scene::init() )
    {
        return false;
    }

    auto visibleSize = Director::getInstance()->getVisibleSize();
    Vec2 origin = Director::getInstance()->getVisibleOrigin();
    
    float cx = origin.x + visibleSize.width * 0.5;
    float cy = origin.y + visibleSize.height * 0.5;
    
    // Creating a box
    auto box = NodeBox::create();
    box->setPosition(cx, cy);
    this->addChild(box);

    return true;
}

NodeBox

Now we create the box class that we have placed one instance of it in the middle of main scene.

The NodeBox class draws a rectangle that rotates 0.5 degrees and scales up 0.001 units for 3 seconds and then reverses the rotation and scale direction every 3 seconds and keeps repeating forever. It has 4 triangle children and every time it reaches the 3 seconds limit, it will delete one of its children too, but not in the first 4 3 seconds. That is why I have a delay value of 3 in the killNextChild() method.

NodeBox.h

#ifndef NodeBox_hpp
#define NodeBox_hpp

#include "cocos2d.h"

using namespace cocos2d;

class NodeBox : public Node
{
    bool  m_isDrawn;            // Make sure to draw once
    float m_rotateAngle;        // Rotation angle value
    float m_rotateDirection;    // Rotation direction
    float m_scaleDelta;         // Scale value
    float m_scaleDirection;     // Scale direction
    int   m_childKillerCounter; // Tag ID for killing the child
    
public:
    
    static Node* createNode();
    
    virtual bool init();
    virtual void update(float delta);
    virtual void draw(Renderer* renderer, const Mat4& transform, uint32_t flags);
    
    void reverseDirection(float delta);
    void killNextChild(float delta);
    
    CREATE_FUNC(NodeBox);
};

#endif /* NodeBox_hpp */

NodeBox.cpp

#include "NodeBox.h"
#include "NodeTriangle.h"

Node* NodeBox::createNode()
{
    return NodeBox::create();
}

bool NodeBox::init()
{
    if(!Node::init())
        return false;
    
    m_isDrawn = false;          // Make sure to draw once
    m_rotateAngle = 0.5;        // Rotation angle value
    m_rotateDirection = 1.0;    // Rotation direction
    m_scaleDelta = 0.001;       // Scale value
    m_scaleDirection = 1.0;     // Scale direction
    m_childKillerCounter = 0;   // Tag ID for killing the child
    
    // Creating four triangles
    auto triangle1 = NodeTriangle::create();
    auto triangle2 = NodeTriangle::create();
    auto triangle3 = NodeTriangle::create();
    auto triangle4 = NodeTriangle::create();
    
    // Position each triangle on the box's corners
    triangle1->setPosition(100, 100);
    triangle2->setPosition(100, -100);
    triangle3->setPosition(-100, 100);
    triangle4->setPosition(-100, -100);
    
    // Assigning the triangles as children with a tag
    addChild(triangle1, 0, 1); // child tagged 1
    addChild(triangle2, 0, 2); // child tagged 2
    addChild(triangle3, 0, 3); // child tagged 3
    addChild(triangle4, 0, 4); // child tagged 4
    
    // Repeats every frame
    scheduleUpdate();
    // Reverse direction every 3 seconds
    schedule(SEL_SCHEDULE(&NodeBox::reverseDirection), 3.0);
    // Kill a child every 3 seconds, repeat 4 times and start with 3 seconds delay
    schedule(SEL_SCHEDULE(&NodeBox::killNextChild), 3.0, 4, 3);
    
    return true;
}

void NodeBox::update(float delta)
{
    // Increase or Decrease the rotation angle
    auto rotation = getRotation();
    rotation += (m_rotateAngle * m_rotateDirection);
    setRotation(rotation);
    
    // Increase or Decrease the scale value
    auto scale = getScale();
    scale += (m_scaleDelta * m_scaleDirection);
    setScale(scale);
}

void NodeBox::reverseDirection(float delta)
{
    // Change the scale and rotaton directions
    m_rotateDirection *= -1.0;
    m_scaleDirection  *= -1.0;
}

void NodeBox::killNextChild(float delta)
{
    // Keeps track of the tag number to remove
    m_childKillerCounter++;
    removeChildByTag(m_childKillerCounter);
}

void NodeBox::draw(Renderer* renderer, const Mat4& transform, uint32_t flags)
{
    if(!m_isDrawn)
    {
        auto draw = DrawNode::create();
        draw->drawLine(Vec2(-100, -100), Vec2(-100, +100), Color4F(1.0, 1.0, 1.0, 1.0));
        draw->drawLine(Vec2(+100, -100), Vec2(+100, +100), Color4F(1.0, 1.0, 1.0, 1.0));
        draw->drawLine(Vec2(-100, -100), Vec2(+100, -100), Color4F(1.0, 1.0, 1.0, 1.0));
        draw->drawLine(Vec2(-100, +100), Vec2(+100, +100), Color4F(1.0, 1.0, 1.0, 1.0));
        addChild(draw);
        m_isDrawn = true;
    }
}

NodeTriangle

NodeTriangle class draws a triangle, doh! The triangle rotates the same way as the rectangle we have. It scales too! I have made it to reverse its direction every three seconds, to be in harmony with our rectangle.

NodeTriangle.h

#ifndef NodeTriangle_hpp
#define NodeTriangle_hpp

#include "cocos2d.h"

using namespace cocos2d;

class NodeTriangle : public Node
{
    bool  m_isDrawn;        // Make sure to draw once
    float m_scaleDelta;     // Scale value
    float m_rotateDelta;    // Rotation angle value
    
public:
    
    static Node* createNode();
    
    virtual bool init();
    virtual void update(float delta);
    virtual void draw(Renderer* renderer, const Mat4& transform, uint32_t flags);
    
    void reverseDirection(float delta);
        
    CREATE_FUNC(NodeTriangle);
};

#endif /* NodeTriangle_hpp */

NodeTriangle.cpp

#include "NodeTriangle.h"

Node* NodeTriangle::createNode()
{
    return NodeTriangle::create();
}

bool NodeTriangle::init()
{
    if(!Node::init())
        return false;
    
    m_isDrawn = false;      // Make sure to draw once
    m_scaleDelta  = 0.01;   // Scale value
    m_rotateDelta = 0.5;    // Rotation angle value
    
    // Repeats every frame
    scheduleUpdate();
    // Reverse direction every 3 seconds
    schedule(SEL_SCHEDULE(&NodeTriangle::reverseDirection), 3.0);
    
    return true;
}

void NodeTriangle::update(float delta)
{
    // Increase or Decrease the rotation angle
    auto scale = getScale();
    scale += m_scaleDelta;
    setScale(scale);
    
    // Increase or Decrease the scale value
    auto rotation = getRotation();
    rotation -= m_rotateDelta;
    setRotation(rotation);
}

void NodeTriangle::reverseDirection(float delta)
{
    // Change the scale and rotaton directions
    m_scaleDelta  *= -1.0;
    m_rotateDelta *= -1.0;
}

void NodeTriangle::draw(Renderer* renderer, const Mat4& transform, uint32_t flags)
{
    if(!m_isDrawn)
    {
        auto draw = DrawNode::create();
        // This is how the points values are calculated, if you are wondering:
        // float radius = 20.0;
        // Vec2 point1 = Vec2(radius * cos(90  * 3.1415 / 180), radius * sin(90  * 3.1415 / 180));
        // Vec2 point2 = Vec2(radius * cos(210 * 3.1415 / 180), radius * sin(210 * 3.1415 / 180));
        // Vec2 point3 = Vec2(radius * cos(330 * 3.1415 / 180), radius * sin(330 * 3.1415 / 180));
        Vec2 point1 = Vec2(0, 20);
        Vec2 point2 = Vec2(-17.3, -10);
        Vec2 point3 = Vec2(17.3, -10);
        draw->drawLine(point1, point2, Color4F(1.0, 1.0, 1.0, 1.0));
        draw->drawLine(point2, point3, Color4F(1.0, 1.0, 1.0, 1.0));
        draw->drawLine(point3, point1, Color4F(1.0, 1.0, 1.0, 1.0));
        addChild(draw);
        m_isDrawn = true;
    }
}

Download Source

Warnings

It is important to mention that as you can see, I have created and deleted all the children in the same class. This is important because it may save you some headache and a few crashes or memory leaks too.

Also, bear in mind that unlike what I have done here, you SHOULD NOT create or remove your nodes in the middle of the game. Why?

Because:

  1. Creating and Deleting objects are expensive and can disrupt the game if there are a few of them happening at the same time.
  2. All the children of a node are added to a Vector class and removing objects from middle or the worst from beginning of a Vector is super expensive. That is because after removing an element of the Vector, all the other elements have to be moved to fill the gap and yes that is not good!
Exit mobile version