0%

Cocos2d-x场景树结构及绘制顺序

场景图

同电影类似,游戏中也需要一个个场景(Scene),显示图片和动画在屏幕上,展示给玩家观看。Cocos2d-x 引擎中也抽象出来一个场景的概念。

场景图(Scene Graph)是一种安排场景内对象的数据结构,它把场景内所有的 节点(Node) 都包含在一个 树(tree) 上。(场景图虽然叫做”图”,但实际使用一个树结构来表示)。

例如《超级马里奥兄弟》中的著名场景 World 1-1:

场景结构可能是:

绘制顺序

中序遍历

场景图是个树形结构,引擎使用中序遍历(inorder)来访问每个节点。根据子节点的 localZOrder 属性,为负的划分为左子树,大于等于 0 的划分为右子树。然后以 左子树 -> 父节点->右子树 的顺序来进行绘制。

实际开发的过程中,可以按照任意顺序添加对象,引擎会按照指定的 localZOrder 来自动排序。

节点属性:

  • localZOrder:顾名思义,表示局部 zOrder,影响此节点在兄弟节点中的绘制顺序。越小越先绘制,越大越后绘制。
  • globalZOrder:全局 zOrder,影响此节点在整个场景中的绘制顺序。越小越先绘制,越大越后绘制。

示意图:

源码分析

节点访问顺序,中序遍历场景树

C++CCNode.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 访问子节点
void Node::visit(Renderer* renderer, const Mat4 &parentTransform, uint32_t parentFlags) {
/*
* ...
*/
int i = 0;

if(!_children.empty())
{
// 根据 zOrder 来排序
sortAllChildren();
// draw children zOrder < 0 绘制 zOrder < 0 的子节点
for(auto size = _children.size(); i < size; ++i)
{
auto node = _children.at(i);

if (node && node->_localZOrder < 0)
node->visit(renderer, _modelViewTransform, flags);
else
break;
}
// self draw 绘制当前节点
if (visibleByCamera)
this->draw(renderer, _modelViewTransform, flags);

// 绘制 zOrder >= 0 的子节点
for(auto it=_children.cbegin()+i, itCend = _children.cend(); it != itCend; ++it)
(*it)->visit(renderer, _modelViewTransform, flags);
}
/*
* ...
*/
}

对子节点进行排序

C++CCNode.cpp
1
2
3
4
5
6
7
8
9
void Node::sortAllChildren()
{
if (_reorderChildDirty) // 是否已排序的 flag
{
sortNodes(_children); // 排序方法
_reorderChildDirty = false;
_eventDispatcher->setDirtyForNode(this);
}
}

节点排序模板方法

C++CCNode.h
1
2
3
4
5
6
7
8
9
10
template<typename _T> inline
static void sortNodes(cocos2d::Vector<_T*>& nodes)
{
// 必须是 Node 的子类
static_assert(std::is_base_of<Node, _T>::value, "Node::sortNodes: Only accept derived of Node!");
std::stable_sort(std::begin(nodes), std::end(nodes), [](_T* n1, _T* n2) {
// 根据 localZOrder 属性排序
return n1->_localZOrder < n2->_localZOrder;
});
}

渲染器渲染方法,globalZOrder 属性影响渲染顺序。

C++CCRenderer.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void Renderer::render()
{
//Uncomment this once everything is rendered by new renderer
//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

//TODO: setup camera or MVP
_isRendering = true;

if (_glViewAssigned)
{
//Process render commands
//1. Sort render commands based on ID
for (auto &renderqueue : _renderGroups)
{
// 渲染队列排序
renderqueue.sort();
}
visitRenderQueue(_renderGroups[0]);
}
clean();
_isRendering = false;
}

渲染队列中的排序方法

C++CCRenderer.cpp
1
2
3
4
5
6
7
8
9
void RenderQueue::sort()
{
// Don't sort _queue0, it already comes sorted
std::stable_sort(std::begin(_commands[QUEUE_GROUP::TRANSPARENT_3D]), std::end(_commands[QUEUE_GROUP::TRANSPARENT_3D]), compare3DCommand);
// globalZOrder 为负
std::stable_sort(std::begin(_commands[QUEUE_GROUP::GLOBALZ_NEG]), std::end(_commands[QUEUE_GROUP::GLOBALZ_NEG]), compareRenderCommand);
// globalZOrder 为正
std::stable_sort(std::begin(_commands[QUEUE_GROUP::GLOBALZ_POS]), std::end(_commands[QUEUE_GROUP::GLOBALZ_POS]), compareRenderCommand);
}

比较 globalZOrder 方法

C++CCRenderer.cpp
1
2
3
4
static bool compareRenderCommand(RenderCommand* a, RenderCommand* b)
{
return a->getGlobalOrder() < b->getGlobalOrder();
}

参考链接