preface

This article is based on Cocos Creator 2.4.5.

๐ŸŽ‰ Celebrate

Come to come, “source code interpretation” series of articles finally come again!

๐Ÿ‘พ Warm reminder

This article contains a large section of engine source code, the use of large screen device reading experience is better!

Hi There!

As the basic unit of the Cocos Creator engine, all components need to be attached to the Node (cc.node).

Nodes are also the things we touch most frequently in daily development.

We often need to “change the order of nodes” to achieve some effect (such as image occlusion).

A Question?

๐Ÿ˜• Have you ever thought about:

How is node ordering implemented?

Oops!

๐Ÿคฏ I found after analyzing the source code:

Sorting nodes is not as easy as you might think!

๐Ÿ˜น slag skin quotations

Listen to pipi advice, zIndex water is too deep, you can not hold!


The body of the

Node Order

๐Ÿค” How do I change the sequence of nodes?

First, in the “Hierarchy Manager” in the Cocos Creator editor, you can change the order of the nodes by dragging them around.

๐Ÿคจ But what do we do in code?

The first thing that comes to mind is the setSiblingIndex function of the node, followed by the zIndex property of the node.

My guess is that most people don’t know the difference between the two plans.

So let’s go deep into the source code to find out!

siblingIndex

“SiblingIndex” is “siblingIndex”, meaning “the position between siblings under the same parent node”.

The smaller the siblingIndex is, the minimum index value is 0, that is, the index value of the first node.

Note that the node does not actually have siblingIndex, only getSiblingIndex and setSiblingIndex.

Note: siblingIndex is used in this papergetSiblingIndex ๅ’Œ setSiblingIndexFunction.

In addition, the getSiblingIndex and setSiblingIndex functions are implemented by cc._basenode.

๐Ÿ’ก cc. _BaseNode

Cc._BaseNode is the base class of cc.Node.

This class “defines the basic properties and functions of a node”, including but not limited to common functions such as setParent, addChild, and getComponent…

๐Ÿ“ source code:

Function:cc._BaseNode.prototype.getSiblingIndex

getSiblingIndex() {
  if (this._parent) {
    return this._parent._children.indexOf(this);
  } else {
    return 0; }},Copy the code

Function:cc._BaseNode.prototype.setSiblingIndex

setSiblingIndex(index) {
  if (!this._parent) {
    return;
  }
  if (this._parent._objFlags & Deactivating) {
    return;
  }
  var siblings = this._parent._children; index = index ! = = -1 ? index : siblings.length - 1;
  var oldIndex = siblings.indexOf(this);
  if(index ! == oldIndex) { siblings.splice(oldIndex,1);
    if (index < siblings.length) {
      siblings.splice(index, 0.this);
    } else {
      siblings.push(this);
    }
    this._onSiblingIndexChanged && this._onSiblingIndexChanged(index); }},Copy the code

[source] base-node. Js# L514:Github.com/cocos-creat…

What did ๐Ÿ•ต๏ธ do?

Sifting through the source code found that the essence of siblingIndex is very simple.

That is the subscript of the current node in the _children property of the parent node.

getSiblingIndexThe function returns “the current node is on the parent node_childrenSubscript (position) in property “.

setSiblingIndexThe function sets the current node to the parent node_childrenSubscript (position) in property “.

๐Ÿ’ก cc. _BaseNode. Prototype. _children

The _children property of a node is the children property of the node.

And the children property is a getter that returns its own _children property.

Also, the children property doesn’t implement the setter, so it doesn’t work if you assign to the children property directly.

zIndex

“ZIndex” is “the key attribute used to sort nodes” and determines the position of a node between its siblings.

The value of zIndex is between cc.macro.min_zindex and cc.macro.max_zindex.

In addition, the zIndex property is implemented in cc.node using the Cocos custom getters and setters.

๐Ÿ“ source code:

Properties:cc.Node.prototype.zIndex

// To reduce space, some irrelevant code has been omitted
zIndex: {
  get() {
    return this._localZOrder >> 16;
  },
  set(value) {
    if (value > macro.MAX_ZINDEX) {
      value = macro.MAX_ZINDEX;
    } else if (value < macro.MIN_ZINDEX) {
      value = macro.MIN_ZINDEX;
    }
    if (this.zIndex ! == value) {this._localZOrder = (this._localZOrder & 0x0000ffff) | (value << 16);
      this.emit(EventType.SIBLING_ORDER_CHANGED);
      this._onSiblingIndexChanged(); }}},Copy the code

[source] ccNode.js #L1549:Github.com/cocos-creat…

What did ๐Ÿ•ต๏ธ do?

After pulling the source code found that the essence of zIndex is actually very simple.

That is, “Return or set the _localZOrder attribute of the node.”

๐Ÿง is not that simple!

Interestingly, instead of returning the _localZOrder property directly in the getter, the value of the _localZOrder property is returned 16 bits to the right (>>).

Setting the _localZOrder property in the setter is also not a simple assignment, but a bit operation:

Here we break down the bitwise operations within the function from the perspective of binary numbers.

  1. through& 0x0000ffffTake out the original_localZOrderThe “lower 16 bits” of;
  2. The target valuevalue“Move 16 bits to the left”;
  3. I’m going to move it to the leftvalueAs “high 16 bit” with original_localZOrderThe “lower 16 bit” merge of;
  4. Finally, a “32-bit binary number” is obtained and given_localZOrder.

๐Ÿ˜ฒ huh?

Slow! What does _localZOrder do? What a detour!

Don’t worry, the answer is in the back

Sorting (Sorting)

Careful friends should be found, siblingIndex and zIndex source code does not contain the actual sorting logic.

But they all have one thing in common: “They all end up calling their own _onSiblingIndexChanged function.”

_onSiblingIndexChanged

๐Ÿ“ source code:

Function:cc.Node.prototype._onSiblingIndexChanged

_onSiblingIndexChanged() {
  if (this._parent) {
    this._parent._delaySort(); }},Copy the code

What did ๐Ÿ•ต๏ธ do?

The _onSiblingIndexChanged function calls the _delaySort function of the parent node.

_delaySort

๐Ÿ“ source code:

Function:cc.Node.prototype._delaySort

_delaySort() {
  if (!this._reorderChildDirty) {
    this._reorderChildDirty = true;
    cc.director.__fastOn(cc.Director.EVENT_AFTER_UPDATE, this.sortAllChildren, this); }},Copy the code

What did ๐Ÿ•ต๏ธ do?

After a bit of digging around, it turns out that the real place to sort is the sortAllChildren function of the parent node.

๐Ÿ’ก blind, you found hua point!

It is worth noting that the sortAllChildren function call in _delaySort is not triggered immediately, but after the next update (life cycle).

The purpose of delayed firing should be to avoid repeated calls within the same frame, thereby reducing unnecessary performance costs.

sortAllChildren

๐Ÿ“ source code:

Function:cc.Node.prototype.sortAllChildren

// To reduce space, some irrelevant code has been omitted
sortAllChildren() {
  if (this._reorderChildDirty) {
    this._reorderChildDirty = false;
    // Part 1
    var _children = this._children, child;
    this._childArrivalOrder = 1;
    for (let i = 0, len = _children.length; i < len; i++) {
      child = _children[i];
      child._updateOrderOfArrival();
    }
    eventManager._setDirtyForNode(this);
    // Part 2
    if (_children.length > 1) {
      let child, child2;
      for (let i = 1, count = _children.length; i < count; i++) {
        child = _children[i];
        let j = i;
        for (;
          j > 0 && (child2 = _children[j - 1])._localZOrder > child._localZOrder;
          j--
        ) {
          _children[j] = child2;
        }
        _children[j] = child;
      }
      this.emit(EventType.CHILD_REORDER, this);
    }
    cc.director.__fastOff(cc.Director.EVENT_AFTER_UPDATE, this.sortAllChildren, this); }},Copy the code

[source] ccNode.js #L3680:Github.com/cocos-creat…

First Part (Part 1)

As we go along, we finally get to the key part.

Now let’s think about the sortAllChildren function.

By the first half of the function, I would have set (reset) the _childarbottom ORDER attribute to 1.

This is followed by a for loop that iterates through all the “children” of the current node and executes the _updateOrderOfArrival function of the “children” one by one.

๐Ÿคจ huh? What is the _updateOrderOfArrival function?

_updateOrderOfArrival

๐Ÿ“ source code:

Function:cc.Node.prototype._updateOrderOfArrival

_updateOrderOfArrival() {
  var arrivalOrder = this._parent ? ++this._parent._childArrivalOrder : 0;
  this._localZOrder = (this._localZOrder & 0xffff0000) | arrivalOrder;
  this.emit(EventType.SIBLING_ORDER_CHANGED);
},
Copy the code

What did ๐Ÿ•ต๏ธ do?

Obviously, the purpose of the _updateOrderOfArrival function is to “update the _localZOrder property of the node.”

๐Ÿฅฑ this function also uses bit operations:

So again, let’s break it down in terms of binary numbers and the bit operations here.

  1. The parent node_childArrivalOrder(ๅ‰็ฝฎ) increment1And givearrivalOrder(If there is no parent node0);
  2. through& 0xffff0000Of the current node_localZOrderThe “high 16 bits” of;
  3. willarrivalOrderAs the “lower 16 bits” with the current node_localZOrderThe “high 16 bit” merge of;
  4. Finally a new “32-bit binary number” is obtained and assigned to the current node_localZOrderProperties.

๐Ÿค” see here are you already confused?

Don’t worry, we’ll find out soon!

Second Half (Part 2)

The lower part of the sortAllChildren function is easier to understand.

It basically sorts the _children property of the current node through Insertion Sort.

The order is mainly based on the value of the _localZOrder attribute of the child nodes. The child nodes with a small value of _localZOrder attribute are ranked first and vice versa.

The Key to sorting

๐Ÿค” after analyzing the source code, it is found that the sorting of nodes is not as simple as imagined.

We can draw a few conclusions:

  1. SiblingIndex is the parent nodechildrenSubscripts in attributes;
  2. zIndexIs a separate property that is not directly related to the siblingIndex;
  3. SiblingIndex andzIndexChanges to trigger sorting;
  4. SiblingIndex andzIndexWhich together form the node_localZOrder;
  5. zIndexIs heavier than siblingIndex;
  6. The node’s_localZOrderIt directly determines the final order of nodes.

How siblingIndex affects sorting

As we mentioned earlier:

  • getSiblingIndexThe function “returns the value of the current node on the parent node_childrenSubscript (position) in property “.
  • setSiblingIndexThe function “sets the value of the current node on the parent node_childrenProperty, and tell the parent node to sort “.

This subscript is then used as the lower 16 bits of node _localZOrder in the upper part of the sortAllChildren function of the parent node.

๐Ÿง so we can understand as follows:

SiblingIndex is the index of the element, which determines during sorting_localZOrderIs “16 bits lower”.

How zIndex affects sorting

As we mentioned earlier:

  • zIndex ็š„ getter“Returned to the_localZOrderThe highest 16 bits “.
  • zIndex ็š„ setter“Set up_localZOrder16 bits high, and notify the parent node to sort “.

๐Ÿง so we can understand as follows:

zIndexIt’s actually just a body, and it’s essentially_localZOrderThe “high 16 bits” of.

_localZOrder How to determine order (How _localZOrder works)

The sortAllChildren function of the parent node performs the final sort according to the _localZOrder size of the child node.

We can think of _localZOrder as a “32-bit binary number” consisting of both siblingIndex and zIndex.

But why say”zIndexIs weighted more than siblingIndex?

Because zIndex determines the “high 16 bits” of _localZOrder, and siblingIndex determines the “low 16 bits” of _localZOrder.

So, only inzIndexIn the case of equality, the size of the siblingIndex is decisive.

And in thezIndexIf it’s not equal, the size of the siblingIndex doesn’t matter.

๐ŸŒฐ for example

There are two 32-bit binary numbers (pseudocode) :

  • A: 0000 0000 0000 0001 xxxx xxxx xxxx xxxx
  • B: 0000 0000 0000 0010 xxxx xxxx xxxx xxxx

Since B’s “higher 16 bits” (0000 0000 0000 0010) is larger than A’s “higher 16 bits” (0000 0000 0000 0001), B will always be greater than A, no matter what x is in their “lower 16 bits”.

Experiment

We can write a widget to test the effect of siblingIndex and zIndex on _localZOrder.

๐Ÿ“ a coding:

const { ccclass, property, executeInEditMode } = cc._decorator;

@ccclass
@executeInEditMode
export default class Test_NodeOrder extends cc.Component {

  @property({ displayName: 'siblingIndex' })
  get siblingIndex() {
    return this.node.getSiblingIndex();
  }
  set siblingIndex(value) {
    this.node.setSiblingIndex(value);
  }

  @property({ displayName: 'zIndex' })
  get zIndex() {
    return this.node.zIndex;
  }
  set zIndex(value) {
    this.node.zIndex = value;
  }

  @property({ displayName: '_localZOrder' })
  get localZOrder() {
    return this.node._localZOrder;
  }

  @property({ displayName: '_localZOrder (binary)' })
  get localZOrderBinary() {
    return this.node._localZOrder.toString(2).padStart(32.0); }}Copy the code

Scene 1

A child node is placed under a node.

๐Ÿ–ผ Sort information about child nodes:

Generally speaking, the _childarbottom order of a node starts from 1, and will increase by 1 in calculation.

So the child’s _localZOrder is always 16 bits lower than its siblingIndex by 2 numbers.

Scene 2

A child node is placed under a node, and the child node’szIndexSet to1.

๐Ÿ–ผ Sort information about child nodes:

As can be seen, just setting the zIndex attribute of the node to 1, its _localZOrder is up to 65538.

๐Ÿ”  The approximate calculation process is as follows (extremely abstract pseudocode) :

1. zIndex = 1 = 0b0000000000000001
2. siblingIndex = 0
3. arrivalOrder = 1 + (siblingIndex + 1)
4. arrivalOrder = 0b0000000000000010
5. _localZOrder = (zIndex << 16) | arrivalOrder
6. _localZOrder = 0b00000000000000010000000000000000 | 0b0000000000000010
7. _localZOrder = 0b00000000000000010000000000000010 = 65538
Copy the code

๐Ÿ“ continues with simplified pseudocode:

_localZOrder = (zIndex << 16) | (siblingIndex + 2)
Copy the code

๐Ÿ’ก By the way

When a node has no parent, its arrivalOrder is always 0.

It doesn’t really matter what it is, because a node without a parent can’t be sorted anyway.

Scene 3

Six child nodes are placed under the same nodezIndexAll set to0.

๐ŸŽฅ Sort information of each child node:

Scene 4

Six child nodes are placed under the same nodezIndexSet to0 ๅˆฐ 5.

๐ŸŽฅ Sort information of each child node:

As you can see, the value of zIndex is directly represented in the “high 16 bits” of _localZOrder; Every time zIndex increases by 1, _localZOrder increases by 65537.

So how can siblingIndex be beatenzIndex!

Scene 5

Six child nodes are placed under the same nodezIndexSet to0 ๅˆฐ 5.

๐ŸŽฅ change the siblingIndex of the sixth child node from 0 to 4, its sorting information:

As you can see, no matter how much we change the siblingIndex of the sixth child node, it automatically changes back to 5 (the maximum value in the sibling node).

Because the zIndex of this child has an absolute advantage over its peers.

Something is wrong.

๐Ÿ˜ฒ Here’s a phenomenon that doesn’t look right!

For example, when we change siblingIndex from 5 to 0, _localZOrder changes from 327687 to 327682; But when siblingIndex is automatically changed back to 5, _localZOrder is still 327682, not 327687.

๐Ÿค” Why is this?

The reason is simple:

When we modify the siblingIndex of the node, the sorting will be triggered. During the sorting process, “a new _localZOrder will be generated according to the siblingIndex and zIndex of the node at the current time”.

Finally, in the sortAllChildren function of the parent node, the _children array will be sorted according to the _localZOrder of the child node. At this time, “the siblingIndex of the child node will also be updated passively”. But _localZOrder is not regenerated.

However, due to the “absolute dominance” of zIndex, this “strange phenomenon” will not affect the normal ordering of nodes ~

Summary (Summary)

After analyzing the source code, let’s summarize.

There are two main ways to change the order of nodes in code:

  1. Modify nodezIndexattribute
  2. throughsetSiblingIndexThe function setting

Either of these methods will eventually “sort by a combination of zIndex and siblingIndex”.

In most cases, “modifying the zIndex property of a node invalidates its setSiblingIndex function.”

This virtually increases the mental burden of coding, but also increases the difficulty of troubleshooting problems.

Usage in Engine

Out of curiosity, I searched the engine source code to see if the zIndex attribute was used internally.

The result: the zIndex property of a node is used only in a few “debug” related places.

For example, in preview mode, the Profiler node in the lower left corner.

And debug boxes for collision components, etc., which I won’t go into here.

(the Suggestion)

So, to avoid some unnecessary bugs and logical conflicts.

My advice:

“Use less or no zIndex in favor of siblingIndex related functions.”

๐Ÿฅด listen to pipi advice, zIndex water is too deep, you can not hold!


portal

Wechat twitter version

Personal blog: Rookie small stack

Open source home page: Chen PI PI


More share

Cocos Creator Performance Optimization: DrawCall

“Draw a cool radar in Cocos Creator.”

Write a Perfect Wave with Shader

Gracefully and efficiently managing popovers in Cocos Creator

Detailed JavaScript Memory Analysis Guide

Cocos Creator Editor Extension: Quick Finder

JavaScript Raw Values and Wrapped Objects

Cocos Creator source Code interpretation: Engine startup and main Loop