Generally speaking, when we move, we will see objects that are closer to us, the faster they will move, and the farther objects, such as mountains in the distance, will move very slowly, and the farthest objects, such as the sun. Almost motionless, this phenomenon is called parallax.
And imitating the parallax in the game can make the player feel that the character in the game is indeed moving. Cocos provides the ParallaxNode parallax node class, which can easily build a parallax layer, and you can control the parallax rate, position and level of each layer.
Cocos2d-x 3.4 version parallax node ParallaxNode
【ParallaxNode】
The use of this class is very simple. Just add the nodes that you want to produce parallax effects as child nodes of ParallaxNode, and set the parallax rate, position and level.
1. Commonly used functions
There are only a few custom functions in the ParallaxNode class, which rewrite related functions of the parent class Node.
Note: For the following overridden functions, do not call the corresponding overloaded functions in the parent class Node. That is, do not call functions like addChild(child, zOrder, tag).
The core functions are as follows:
create()
addChild()
removeChild()
//
/**
* Nodes that simulate parallax scrolling
* The child node moves faster/slower than the parent node according to the parallax ratio (parallaxRatio).
**/
class CC_DLL ParallaxNode : public Node
{
public:
// Create a parallax node
static ParallaxNode* create();
// Add child nodes
// child: child node
// z: zOrder order
// ratio: Vex2(ratioX,ratioY) The ratio of movement relative to ParallaxNode in both directions
// offset: relative offset position of ParallaxNode
void addChild(Node * child, int z, const Vec2& ratio, const Vec2& offset);
// Update the location information of the child nodes
virtual void visit(Renderer *renderer, const Mat4 &parentTransform, uint32_t parentFlags) override;
// Remove child nodes
// child is the deleted child node.
// cleanup true all actions and callbacks on this node will be deleted, false will not delete.
virtual void removeChild(Node* child, bool cleanup) override;
// Remove all child nodes
// cleanup true all actions and callbacks on this node will be deleted, false will not delete.
virtual void removeAllChildrenWithCleanup(bool cleanup) override;
};
//
2. About adding child nodes: addChild()
When adding a child node to ParallaxNode, the way of adding it is different from that of Node::addChild() of the parent class.
The overridden addChild() function must be used instead of overloaded functions such as the parent class addChild(Node* child, int z, int tag).
The implementation of this function is as follows:
//
// Add child nodes
// child: child node
// z: zOrder order
// ratio: Vex2(ratioX,ratioY) The ratio of movement relative to ParallaxNode in both directions
// offset: relative offset position of ParallaxNode
void ParallaxNode::addChild(Node* child, int z, const Vec2& ratio, const Vec2& offset)
{
CCASSERT( child != nullptr, "Argument must be non-nil");
PointObject *obj = PointObject::create(ratio, offset);
obj->setChild(child);
ccArrayAppendObjectWithResize(_parallaxArray, (Ref*)obj);
Vec2 pos = this->absolutePosition();
// The position of the child node is calculated based on the offset position offset and the disparity ratio ratio
pos.x = -pos.x + pos.x * ratio.x + offset.x;
pos.y = -pos.y + pos.y * ratio.y + offset.y;
child->setPosition(pos);
Node::addChild(child, z, child->getName());
}
//
It can be found that the coordinate position (Position) of the child node is determined by the offset position (offset) and the disparity ratio (ratio).
PS: This is why the child nodes added to ParallaxNode cannot use setPosition() to manually set the position. This may be a drawback of using ParallaxNode.
3. About updating the location of child nodes: visit()
When the position of ParallaxNode changes, the position of all child nodes will be relative to the parent node, and the coordinate position of the change will be calculated according to the position, offset, and ratio of the ParallaxNode.
After the location changes, the function called is visit(), which is implemented as follows:
//
// Update the location information of the child nodes
void ParallaxNode::visit(Renderer *renderer, const Mat4 &parentTransform, uint32_t parentFlags)
{
Vec2 pos = this->absolutePosition();
if( ! pos.equals(_lastPosition) )
{
// Calculate the position of all children relative to ParallaxNode
// Note that when we move the position of ParallaxNode, what we show is actually a change in the position of the child. This change is the core design of this class.
for( int i=0; i < _parallaxArray->num; i++ )
{
PointObject *point = (PointObject*)_parallaxArray->arr[i];
// For example, the absolute position of ParallaxNode is 100, which shows that the position of the child is -100. We cannot perceive the movement of ParallaxNode, but the position of the child has changed.
// The simple point is similar to the movement of a camera scene, the camera has not moved, the scenery has changed
// If ratio is (=1), then position == offset
// If the ratio is (0~1), then position <offset, slow moving speed
// If the ratio is (>1), then position> offset, the moving speed is fast
float x = -pos.x + pos.x * point->getRatio().x + point->getOffset().x;
float y = -pos.y + pos.y * point-> getRatio (). y + point-> getOffset (). y;
// The position of the child is calculated from the two lines above
// Therefore manually setting its postion will not have any effect
point->getChild()->setPosition(x,y);
}
_lastPosition = pos;
}
Node::visit(renderer, parentTransform, parentFlags);
}
//
4. Parallax effect of child nodes
After adding the child node to ParallaxNode, and setting the offset position offset and the parallax ratio ratio, all the child nodes of ParallaxNode will move with the movement of ParallaxNode.
The position of the child node after moving is calculated based on the position of ParallaxNode, offset, and ratio.
When the child nodes have different disparity ratios, during the movement process, there will be some fast moving and some slow moving parallax effects.
[Code combat]
0, picture material
1. Create a parallax node class and add child nodes
Create three child nodes and add them to ParallaxNode.
bg: anchor point (0, 0), inspection ratio ratio (0.5, 0.5), offset position offset (0, 0).
ball: anchor point (0.5, 0.5), inspection ratio ratio (1, 1), offset position (the coordinate of the screen center point).
smile: anchor point (0, 0), inspection ratio ratio (4, 4), offset position offset (0, 0).
PS: Test the sprite bg, manually set the position coordinate setPosition( ).
//
//[1] Viewable area size
Size vSize = Director::getInstance()->getVisibleSize();
//[2] Create three child nodes
Sprite* bg = Sprite::create("HelloWorld.png");
bg->setAnchorPoint(Vec2(0, 0)); // anchor point (0, 0)
bg->setName("HelloWorld");
//Bug: The wizard on ParallaxNode is invalid for its setPosition
// See the running results
bg->setPosition(Vec2(100, 100)); // Set the coordinate position of bg
Sprite* ball = Sprite::create("Ball.png");
ball->setAnchorPoint(Vec2(0.5, 0.5)); // anchor point (0.5, 0.5)
ball->setName("Ball");
Sprite* smile = Sprite::create("Smile.png");
smile->setAnchorPoint(Vec2(0, 0)); // anchor point (0, 0)
smile->setName("Smile");
//[3] Create ParallaxNode and add child nodes
// Create a parallax node class
ParallaxNode* parallaxNode = ParallaxNode::create();
this->addChild(parallaxNode, 0, "parallaxNode");
// Add child nodes
// addChild(node, zOrder, ratio, offset);
parallaxNode->addChild(bg, 0, Vec2(0.5, 0.5), Vec2(0, 0));
parallaxNode->addChild(ball, 1, Vec2(1, 1), Vec2(vSize.width/2, vSize.height/2));
parallaxNode->addChild(smile, 1, Vec2(4, 4), Vec2(0, 0));
//[4] Add touch event
EventDispatcher* dispatcher = this->getEventDispatcher();
EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create();
listener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this);
listener->onTouchMoved = CC_CALLBACK_2(HelloWorld::onTouchMoved, this);
listener->onTouchEnded = CC_CALLBACK_2(HelloWorld::onTouchEnded, this);
dispatcher->addEventListenerWithSceneGraphPriority(listener, this);
//
2. Realize touch events
Touch to start: No operation.
Touch to move: ParallaxNode moves with it.
Touch end: output the location information of each node to see the difference.
//
bool HelloWorld::onTouchBegan(Touch* pTouch, Event* pEvent)
{
return true;
}
// Move ParallaxNode node
void HelloWorld::onTouchMoved(Touch* pTouch, Event* pEvent)
{
Vec2 delta = pTouch->getDelta();
// Move ParallaxNode node
// The position of all child nodes will also change accordingly
ParallaxNode* parallaxNode = (ParallaxNode*)this->getChildByName("parallaxNode");
parallaxNode->setPosition(parallaxNode->getPosition() + delta);
}
// After the touch is over, output the position information of each node
void HelloWorld::onTouchEnded(Touch* pTouch, Event* pEvent)
{
ParallaxNode* parallaxNode = (ParallaxNode*)this->getChildByName("parallaxNode");
// Get child nodes by name
Sprite* bg = (Sprite*)parallaxNode->getChildByName("HelloWorld");
Sprite* ball = (Sprite*)parallaxNode->getChildByName("Ball");
Sprite* smile = (Sprite*)parallaxNode->getChildByName("Smile");
// Output coordinate position information
CCLOG("parallax : %f %f", parallaxNode->getPositionX(), parallaxNode->getPositionY());
CCLOG("HelloWorld : %f %f", bg->getPositionX(), bg->getPositionY());
CCLOG("ball : %f %f", ball->getPositionX(), ball->getPositionY());
CCLOG("smile : %f %f", smile->getPositionX(), smile->getPositionY());
CCLOG("---------------------------------------");
}
//
3. Running results
During the movement, the output data result is:
//
parallax : 0.000000 0.000000
HelloWorld : 0.000000 0.000000
ball : 240.000000 160.000000
smile : 0.000000 0.000000
---------------------------------------
parallax : 101.836441 20.723732
HelloWorld : -50.918221 -10.361866
ball : 240.000000 160.000000
smile : 305.509338 62.171196
---------------------------------------
parallax : 19.378311 64.557907
HelloWorld : -9.689156 -32.278954
ball : 240.000000 160.000000
smile : 58.134933 193.673721
---------------------------------------
parallax : -1.778305 -81.284264
HelloWorld : 0.889153 40.642132
ball : 240.000000 160.000000
smile : -5.334915 -243.852783
---------------------------------------
//
4. Data analysis
(0)HelloWorld: setPosition() is set at the beginning, but it is invalid. Because its coordinates are only affected by ratio and offset.
(1) HelloWorld: Why did the coordinates become negative during the movement? This is because the coordinates of the child nodes are offset relative to ParallaxNode. Because the disparity ratio of HelloWorld is 0.5 times, when parallaxNode moves 100 pixels, then HelloWorld only moves 50 pixels. So its offset position relative to parallaxNode is -50.
(2) Ball: During the movement, the coordinate position has not changed. Because its parallax ratio is 1.0 times, how much the parallaxNode moves, and how much the ball moves. So the position of the ball relative to the parallaxNode is still the original offset position.
(3) smile: This moves quickly, with a parallax ratio of 4.0 times. But why its coordinate position is always 3 times that of parallaxNode??? Haha, because parallaxNode has moved 100 pixels, and it has moved 400 pixels, then its position relative to parallaxNode coordinates is not 400-100 = 300 is there.