• 欢迎来到GeekFaner

    每周更新一篇教程,轻松学习cocos2d-x

    联系我们
  • 又是一个美好的一天

    每天的太阳都是新的,今天要比昨天好

    联系我们
  • 出发!

    开始今天的课程学习,让我们的明天更美好

    联系我们

课程概述

“Cocos2d-X的绘制系统”课程概述

Cocos2d-X游戏引擎的核心是渲染系统,渲染系统的核心就是绘制系统,绘制系统就是一套,得到UI元素的绘制命令,将其进行排序之后进行执行的系统,使用本节课将讲述Cocos2d-X的绘制系统。

教学视频

课程笔记

调度器

在(使用Cosos2d-x制作第一个DEMO)一节中,就讲到了调度器,Cocos2d-X的调度器包含三种机制。

Scheduler的scheduleUpdate函数将会每帧调用update函数。实现方式是通过Scheduler的schedulePerFrame方法,将target、taget的update函数、优先级、运行状态传入_updates0List或_updatesNegList或_updatesPosList

Scheduler的schedule函数将生成一个新的Timer(并非线程中的Timer),将target、callback、key(如果有的话,用于检索的标签)、interval(自定义的间隔)、repeat、delay赋值给Timer,并将Timer加入_hashForTimers。

在(使用Cosos2d-x制作第一个DEMO)一节中,我们是使用了Scene的scheduleUpdate函数,Scene没有scheduleUpdate函数,调用的是Node的scheduleUpdate函数,最终调用的是Scheduler的scheduleUpdate函数,传入参数target为Scene,优先级为0(这样不好,优先级都一样,就需要在一个Node中实现大量逻辑),运行状态。

Node还有相应的schedule和scheduleOnce函数,schedule函数调用的是Scheduler的schedule函数,scheduleOnce调用的也是Scheduler的schedule函数,只是传入参数中的interval和repeat均为0。

ActionManager和PhysicsWorld为逻辑子系统,ActionManager的优先级最高,PhysicsWorld为0,但是都为0的时候,PhysicsWorld优先级最高。

Schedule的_timeScale成员变量是用来设定时间线,控制加速或者减速使用。

以上的代码,除了回调函数,其他代码从平台相关代码的角度看,都是在nativeinit函数中被调用,然后在平台相关代码中的真正的绘制函数drawScene中,将会使用到上面定义的函数,在drawScene函数中调用的Schedule的update函数。update函数会依次处理_updatesNegList、_updates0List、_updatesPosList、_hashForTimers中的回调函数,然后再将他们全部清空。

Cocos2d-X如何将绘制图像

在(站在Android App开发者角度分析template project之proj.android(2))一节中,讲到了Cocos2dxRenderer的三个主要函数,在(Cocos2d-X的渲染系统(1))一节中,已经介绍了其中的onSurfaceCreated函数,本节课将介绍另外一个主要函数onDrawFrame,这个函数就是Android开发基于OpenGL ES的App,所调用的绘制函数,Android会不停的调用此函数。

在本系列前面两个教程介绍了FPS的设置和作用,在onDrawFrame函数中,首先先会对绘制加以FPS的限制。由于onDrawFrame函数是会被不停的调用,为了让这个函数的调用受到FPS的限制,在此函数中加入了时间判断,如果两次调用函数时间间隔过短,就会暂停一会,等FPS的时间到了之后再执行函数主体部分。函数的主体部分为nativeRender。

nativeRender为native函数,主要是调用DisplayLinkDirector单例的mainLoop方法。mainLoop中主要包含两个函数,一个是真正的绘制函数drawScene,另外一个是在内存管理介绍过的,在每帧结束的时候将AutoReleasePool中的东西进行release。

glfwPollEvents函数用于交换前后端渲染缓冲,与glfwSwapBuffers对应。

每一帧开始的时候先判断是否有用户输入,因为用户是对上一帧进行的输入,所以要先做判断。然后执行动作更新,ActionManager会对相应元素进行属性更新。之后进行物理判断,物理判断是属于schedule的update函数,在其中优先级为0。下面是程序自定义的一些更新。

之后通过glClear对缓冲进行clear,然后判断是否进入下一个scene,如果进入下一个scene就调用这个scene退出的回调函数,以及下一个scene进入的回调函数。

之后通过scene的visit函数(其实是Node的Visit函数)遍历整个UI树,得到一定顺序的绘制命令。交给Renderer的render函数进行绘制。

查看是否有通知,是否显示fps状态栏。之后交换缓冲区。

原理

Cocos2d-X3.0之后就将绘制命令从每个UI元素中抽取出来,每个UI元素在进行UI树遍历的时候,生成绘制命令,然后将绘制命令加入Render实例化对象的绘制命令队列中,然后Render实例化对象对绘制命令进行排序、执行。

这样将绘制命令放在一起,可以对绘制命令进行优化,也可以对绘制顺序进行定制,对绘制命令进行升级优化的时候,也不需要针对一个个的UI元素进行重复工作。

生成绘制命令

进行UI树遍历的时候,如果UI元素为sprite,那么先对UI元素进行判断,判断其是否可见,以及是否存在于视锥体内部,如果UI元素以及UI元素的先辈UI元素的位置没有发生变化,那么保持之前的判断。如果不是sprite,那么就不进行判断,原因是sprite对应于一个图元或者一次绘制,非sprite的UI元素有可能具有很多子UI元素,判断其是否在视锥体之内比较麻烦,会影响性能。

虽然有了上述的判断,可以提升性能,但是所使用的内存还在,所以如果确认不在显示状态,可以考虑删除。不显示的UI元素有可能依然和事件绑定。

每一个UI元素,都可以对Node的draw函数进行重写(Node的draw函数为空函数),在draw函数中生成一个绘制函数,绘制函数一般为RenderCommand的子类实例化(sprite的绘制命令为QuadCommand的实例化,QuadCommand包含了一个纹理和4个顶点的信息、shader program、blend)。或者生成一个绘制命令组(clippingNode、RenderTexture的绘制命令为GroupCommand)。Lab、TileMap的绘制命令为Batch_Command。遍历UI树的时候,将这些绘制命令通过addCommand存储在Render对象的RenderQueue中。RenderQueue属于_renderGroups。

对绘制命令进行排序

Render对象包含一个_renderGroups,_renderGroups包含若干个RenderQueue,Render对象会使用globalZorder对RenderQueue进行排序(不同RenderQueue的globalZorder没可比性),排序后按照顺序执行绘制命令。Cocos2d-X针对sprite提供自动批绘制,意思是当连续的sprite使用相同的texture、blend、program,且没有使用uniform,那么就将连续的sprite在一条绘制命令中进行执行。如果存在GroupCommand,那么就先执行GroupCommand。在每个RenderCommand执行之前都会先对状态进行恢复默认设置。

执行绘制命令

排序后执行绘制命令,但是如果想要知道绘制命令全部被执行了。可以有两个办法,一是通过Schedule,另外一个方法是创建一个CustomCommand,在CustomCommand的func函数中可以包含一个回调函数。

Cocos2d-X如何做到跨平台

在(如何使用Cocos2d-x进行跨平台编程)一节中,讲述了Cocos2d-X跨平台的原理。在(站在Android App开发者角度分析template project之proj.android(2))一节中,从Android App开发者角度,讲述了Cocos2d-X所封装的与Android平台相关的代码,以及Android平台相关的代码如何调用平台不相关的代码。其他平台类似,Cocos2d-X就是通过这种方式,将平台相关的代码全部封装起来,制定了一些平台不相关的库,让开发者可以基于这个平台不相关的库进行代码编写。最终,编写的代码都会被各个平台所调用,以达到开发者跨平台开发的效果。

每个游戏对应于一个Application对象。Application的作用是管理游戏的生命周期、设置默认窗口、获取平台和本地信息等。每个平台的Application类都不一样,所以Cocos2d-X抽象出来一个Application的子类AppDelegate,在AppDelegate中定义了一个Director对整个游戏进行管理。如果是多屏的设备,可以创建多个Director。

Director的函数详解

Director的setOpenGLView函数用于设置Director当前的OpenGL ES窗口。OpenGL ES是与GPU打交道,所以先要获得GPU的信息。然后创建一个状态显示栏用于显示FPS等信息。设置OpenGL ES的初始值(blend、depthtest、clearcolor)。设置VBO和VAO,如果有的话。开启事件分发器。

Director的runWithScene函数用于将输入参数的scene push到场景栈的栈顶。然后调用Application的startAnimation函数,引入Cocos2dxRenderer lib。通过pushScene、popScene、popToRootScene、popToSceneStackLevel、replaceScene对Scene进行管理。

Director介绍

Director是Ref的子类,具备管理Scene、管理fps、管理OpenGL ES View的handle GLView、管理OpenGL ES过程中用到的TextureCache/viewport/blend/depthtest、管理坐标系变换矩阵、管理游戏运行状态、管理事件/通知、管理窗口尺寸、数据管理等功能。

Director的getInstance函数得到一个DisplayLinkDirector的单例,DisplayLinkDirector是Director的子类,如果将Director看作一堆图片的集合,那么DisplayLinkDirector具备的函数是在Director的基础上,重载了让图片按照一定的fps播放或者停止的函数。

GLView是Ref的子类,具备检查OpenGL ES状态、将图片显示在屏幕上、设置OpenGL ES属性、设置尺寸、设置Viewport/Scissor等功能。

上节课介绍到的nativeInit函数将一个GLViewImpl的实例化对象设置为了Director的管理OpenGL ES View的handle。GLViewImpl是GLView的子类。GLViewImpl跟平台相关,重载了检查OpenGL ES状态、将图片显示在屏幕上等函数。

性能问题

不要经常查找,如果需要查找,使用std::unordered_map查找最快。

使用缓存,比如UI树中的矩阵,如果UI元素或者其父UI元素没有发生坐标变换,就不重新计算矩阵。

update函数尽量少放东西。

多线程

在游戏开发的过程中,开发者基本不会使用多线程开发。虽然这种方法理论上也可以实现,但是从易用性出发,Cocos2d-X并没有暴漏给开发者这样的接口,也并不建议开发者去这样制作游戏。

但是在Cocos2d-X底层是使用了部分并行化。虽然Cocos2d-X游戏引擎并没有向其他游戏引擎将绘制从游戏引擎中分离,但是cocos2d-X在网络请求、异步加载数据上也使用到了多线程。但是GL方法、内存处理,一般应该是在主线程完成。

Scheduler提供了performFunctionInCocosThread函数,这个函数的目的:当其他元素调用这个函数的时候,可以将一个回调函数,存入Scheduler的一个函数vector中,这个vector中存储的函数,将会在主线程中被调用。

在(Cocos2d-X的渲染系统(4))一节中,我们讲到了很多Scheduler的函数,其中有一个函数是在drawScene的时候会被调用,Scheduler的update的函数,用于在每一帧遍历UI树之前对UI元素进行更新,而在这个函数中,会遍历刚才存储的包含很多回调函数的vector,然后依次执行这些函数。

上面这种机制是在每一帧将存储在Scheduler回调函数vector中的函数进行处理,但是假如这些回调函数有很多,且需要耗费很多时间,比如上传很多texture,那么使用这种方法会导致很差的用户体验。那么还有一种方式,就是创建一个自定义的Schedule,每帧处理一个纹理,直到纹理全部上传结束。

联系我们

笔者制作网站的目的,主要是借用自己之前的知识背景(Android App开发和图形学知识),将自己学习笔记拿出来,和大家一起进行交流,毕竟每个人的知识体系不同,有交流才会有提高,所以欢迎大家通过各种方式和我联系。
网址:www.geekfaner.com
"极客学院"教学视频(高清版_推荐):http://www.jikexueyuan.com/course/cocos/1-5-0

wangshuo@geekfaner.com