查看原文
其他

Cocos Engine-节点与组件

搜狐视频客户端 搜狐技术产品 2021-07-27

 

本文字数:3460

预计阅读时间:13分钟


Cocos Creator的工作流程是以组合方式进行实体构建,及组件式架构(组件-实体系统Entity-Component System)。节点(Node)是承载组件的实体,通过将具有各种功能的组件挂载到节点上去实现各式的表现和功能。在日常的开发过程中,我们更多关注的是功能的使用,对组件内部具体实现逻辑未有更多探究,而对实现结构的了解其实更能帮助我们理解开发逻辑及脉络。本文将对Cocos Engine的节点和组件进行深入讨论,具体基础内容请读者结合Cocos官方文档手册补充理解。

创建节点:
通过层级管理器-创建节点。可在场景编辑器和层级管理器中看到节点信息。 

此处创建一个新的sprite节点,表示这是一个由sprite组件负责提供功能的节点。

那么到底什么是节点?有什么作用?
节点是Cocos基本类,用于定义抽象的界面元素。具有以下作用:

  1. 描述UI窗口层级,通过节点树可以直观描述游戏界面中每个界面元素之间的父子关系,构建清晰的层级关系(层级关系影响最后的渲染顺序)
  2. 描述空间信息
  3. 作为事件监听器,监听用户的具体操作,并广播传递给节点监听者(组件)
  4. 作为逻辑组件的容器,承载逻辑组件

一、节点-窗口层级&visit 

每个视觉元素都是一个节点,在开发过程中需要理清各个节点间关系和访问顺序

  1. 节点的窗口层级以树形结构构成,顶层节点为父节点,下面的为子节点。
  2. 遍历时,从根节点按照深度优先策略进行遍历,越靠近根位置的节点越先访问到。
  3. 访问到执行,世界空间计算->顶点位置计算->mask设置等,构建出GL能访问的模型(model),最后形成一个model数组,然后顺序渲染Model
  4. 兄弟节点在特定时机会根据zIndex执行排序,否则按照Creator的从上到下顺序

二、节点-空间信息 

节点都是根据位置信息在画布上进行绘制渲染,需了解空间信息,进而明确节点间的位置关系。

  • Cocos和OpenGL坐标系均使用笛卡尔右手系,原点左下角,x向右,y向上。
  • 空间信息,由positiion,anchor,size,scale,rotation,skew,共同决定。
  • 世界坐标系(绝对坐标系)是场景内的统一坐标系,左下点为原点。
  • 本地坐标系,是每个节点关联的坐标系,锚点为原点。

GL使用空间信息步骤:
图形学中,一般用4*4矩阵来描述空间变换信息, position,scale,rotation,skew这些信息共同来组成下面的矩阵。
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
在visit流程中,先通过_localTransform将坐标信息转换为本地空间矩阵,再通过_worldTransform转换为世界坐标空间矩阵(父节点的世界空间矩阵*本地空间矩阵)。最后根据根据世界矩阵 + size + anchor计算获得矩形4个顶点的世界坐标。 

如图,由于两个主要节点和背景节点没有任何关系,因此它们的位置都是在世界坐标系下的数值,基本上没有规律,当我们需要让节点在背景范围内移动时,要计算出节点新的位置也需要动一番脑筋。Cocos节点父子关系的重要作用之一就是能够在本地坐标系下使用子节点的变换属性(所以在本地坐标系下,不要用xxx节点在(50,50)这样来描述position,应该用xxx节点的锚点距离父节点的锚点为(50,50)来描述position)。
使用本地坐标系的位置信息能够直观的反应两个子节点的摆放逻辑。这样的工作方式能够更直接的体现设计师在搭建场景时的想法,在后续让节点在背景范围内运动的过程中,也更容易获得边界范围。另外当需要将一组节点作为一个整体进行移动、缩放、旋转时,节点的父子关系也可以让我们只关心父节点的变换操作,而不需要再去对子节点进行一一的遍历和计算.

三、节点-touch事件监听流程

事件处理是在节点(cc.Node)中完成的。对于组件,可以通过访问节点 this.node 来注册和监听事件。监听事件可以通过 this.node.on() 函数来注册,方法如下:

   
cc.Class({   
  extends: cc.Component,   
  properties: {   
  },   
  onLoad: function () {   
    this.node.on('mousedown'function ( event ) {   
      console.log('Hello,Node!');   
    });   
  },   
});  
    

当我们不再关心某个事件时,我们可以使用 off 方法关闭对应的监听事件。需要注意的是,off 方法的参数必须和 on 方法的参数一一对应,才能完成关闭。

 
cc.Class({
  extends: cc.Component,

  _sayHello: function () {
    console.log('Hello Node');
  },

  onEnable: function () {
    this.node.on('
foobar', this._sayHello, this);
  },

  onDisable: function () {
    this.node.off('
foobar', this._sayHello, this);
  },
});

监听起始通过CCGame将触摸事件监听设置到Canvas。在处理多个关联节点的监听时间时,需遵循一定流程,比如:

A包含B,B包含C,开发者对A,B,C,都监听了事件。

  1. 用户触摸时产生touchStart事件传递给Canvas的监听者。
  2. 按照渲染的反方向顺序(子节点靠前)查找target目标节点。
  3. 查找到先验证是否在区域内,否继续查找,是,则确定target后,开始捕获流 + target + 冒泡流流程。

获取及派发流程图:

  • 事件的dispatch过程分为捕获流和冒泡流。
  • Cocos中两种都支持,事件会从根节点流动到达target节点后返回根节点。
  • 捕获流和冒泡流仅仅表示的是事件的流动方向(顺序),实际上事件产生时,就已经可以确认target(经常很多人误以为事件是通过根节点一直捕获查找,到达target后才知道是某个节点)。
  • 不管事件流动到那个节点,均可以通过event的stopPropagation停止流动。

参考文档:https://www.w3.org/TR/DOM-Level-3-Events/#event-flow

使用捕获流监听部分问题剖析:

  1. Node.on (type, callback, target, useCapture) ,其中useCapture干什么?
  • useCapture 设置为true,会启用捕获流监听,意思是如果target不是目标的时候事件会先给父节点处理,后才会捕获给目标事件,(ScrollView用到捕获流监听,为了防止ScrollView包含Button的时候,事件被Button给拦截导致无法滚动)。
  • 默认使用冒泡的时候,事件先给子节点处理,完毕后才冒泡给目标事件(大部分都采用冒泡监听,因为理论上子节点应该更早接收到用户交互事件) 。
  1. 通常在onLoad监听广播,onDestroy里需要取消监听,Node.on (type, callback, target, useCapture) ,如果target是this,为何在onDestroy里不需要手动off掉(或者Node.targetOff(this))?
  • 监听节点时,会将节点this,存到Component的__eventTargets变量中。
  • 组件_onPreDestroy会调用Node. targetOff(Compontent)。相当于当组件死亡之前会自动移除自己在任意节点上的监听
  1. active, activeInHierarchy, enable如何理解?
  • active表示当前节点自身的激活态,仅仅是一个标志位。
  • activeInHierarchy表示该节点真正激活,真正激活时会触发关联Component的preLoad,onLoad,onEnable事件,且只有真正激活才会参与Visit流程,否则直接略过当前节点(包含子节点)。
  • active & parent. activeInHierarchy共同决定当前节点的activeInHierarchy。
  • enable,可用态,组件的两种形态。(比如button不可用时是灰色的)。


组件(Component)



在属性检查器中,可看到刚构建的sprite节点的属性信息。

如图,Node内容下为节点属性,具体包括节点位置、旋转、缩放、尺寸等变换信息和锚点、颜色、不透明度等其他信息。

Sprite下为Sprite组件的属性,在2D 游戏中,Sprite 组件负责游戏中绝大部分图像的渲染。


节点-承载组件

节点自身不具备渲染和逻辑能力,此时节点仅仅是一个数据结构的概念,节点通过增加组件脚本,作为组件的承载,比如增加精灵组件,就成了精灵节点,增加Label组件,就成了Label节点,以此组件式结构进行功能扩展。

具体有CCLabel、CCSprite、CCGraphics、CCMask、CCButton、CCScrollView等。

注意:

_components作为Node的属性存在。


获取组件所在的节点方法,只要在组件方法里访问this.node变量:

start: function () {
    var node = this.node;
    node.x = 100;
}

获取同一个节点上的其他组件,则使用getComponent这个API:

start: function () {
    var label = this.getComponent(cc.Label);
    var text = this.name + 'started';

    // Change the text in Label Component
    label.string = text;
}

组件结构图:

CCComponent组件为所有组件的基类,提供组件的基本操作。

CCRenderComponent为渲染组件基类,提供顶点、纹理图片等各项渲染数据。

Cocos中使用的基本组件均由此类派生而来,承接不同的功能属性,并且支持自定义组件,用于进行业务扩展。


组件-Visit流程图

引擎渲染组件 - Visit流程:
构建inputAssember+ material
  1. LOCAL_TRANSFORM,将position,scale,skew,rotation转换为本地坐标系下的local matrix(4*4);
  2. WORLD_TRANSFORM,将local matrix(4*4)乘以parent world matrix得到当前的world matrix;
  3. UPDATE_RENDER_DATA,根据world matrix更新顶点数据(4个角的坐标),索引数据(6个索引数据,两个三角形),设置到渲染组件基类的_renderData中。
  4. OPACITY,设置节点不透明度,不透明度会影响最终颜色值。(父节点的OPACITY 会影响子节点的OPACITY )
  5. COLOR,颜色值,RGB值设置,更新到material的color中。
  6. RENDER,将inputAssember+ material组合构建出渲染模型( renderEngine.Model)。
  7. CUSTOM_IA_RENDER,自定义inputAssember
  8. CHILDREN,顺序递归执行子节点的Visit流程。
  9. POST_UPDATE_RENDER_DATA& POST_RENDER,目前用于关闭遮罩。

总结:
Visit 节点的作用,根据节点的颜色值,空间属性,节点类型等设置,构建出inputAssember(顶点数据)和material(存放纹理,颜色值,mask设置),作为下一步render的源数据(模型)。Visit全部完成后,会生成一个模型数组, render时按照顺序,一个一个将模型渲染到页面上。


引擎渲染组件-Render流程

  1. 根据照相机设置,更新渲染视图设置,照相机的ZoomRatio, _fov, _near, _far等设置会影响矩阵变化。
  2. 根据_cullingMask设置过滤。
  3. _commitBlendStates,提交混合设置。
  4. _commitDepthStates,提交深度测试信息。
  5. _commitStencilStates,提交模板测试信息。
  6. _commitCullMode,是否背面剔除。
  7. _commitVertexBuffers,提交顶点数据。
  8. _commitTextures,提交纹理信息。

以上使用webgl( canvas.getContext('webgl', opts) )接口去绘制页面。

以上是对节点功能、组件的具体产生和渲染过程进行的简单讨论,了解基础开发组件的构成和生成过程,望能对游戏渲染效果、性能优劣等问题的解决有一定的帮助,知其然更知其所以然。有不足和错误的地方欢迎大家批评指正,一起讨论~


<THE END>



本期赠书

《SQL必知必会(第5版)》(本福达 著 钟鸣 刘晓霞 译)是麻省理工学院、伊利诺伊大学等众多大学的参考教材,中文版累计销量超13万册。本书由浅入深地讲解了SQL的基本概念和语法,涉及数据的排序、过滤和分组,以及表、视图、联结、子查询、游标、存储过程和触发器等内容,实例丰富,便于查阅。与其他同类图书不同,本书没有过多阐述数据库基础理论,而是专门针对一线软件开发人员,直接从SQL SELECT开始,讲述实际工作环境中最常用和最必需的SQL知识,实用性极强。新版对书中的案例进行了全面的更新,并增加了章后挑战题,便于读者巩固所学知识。


参与方式

文末留言板留言,点赞前5名各获得赠书一本~


获奖公布

公布时间及位置:10月22日头条推送文末
特别提醒:兑奖截止至10月29日,请参与读者及时兑奖

上期获奖名单公布

恭喜“Howie”、“smile...”、“Null”、“欣然起...”、“哈哈”!以上读者请添加小编微信:sohu-tech20兑奖~


加入搜狐技术作者天团

千元稿费等你来!

戳这里!☛




也许你还想看

(▼点击文章标题或封面查看)

  全面详细的java线程池解密,看我就够了!
  【周年福利Round3】Coroutines(协程)我是这样理解的!
  RecyclerView的曝光统计
  安卓自定义view中绘画几何图形和文字及圆角ImageView图片等api使用及举例
  Android死锁初探
  DialogFragment引起的内存泄露



    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存