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; } }
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:
- Creating and Deleting objects are expensive and can disrupt the game if there are a few of them happening at the same time.
- 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!