第1~4章介绍了Cocos2d-x引擎的基本架构及新的绘制系统。这部分内容包括:Cocos2d-x的内存管理机制,UI树的遍历及结构,应用程序的生命周期,游戏循环的各个阶段,以及在Cocos2d-x中驱动各个子系统进行逻辑更新的机制和工作原理。这部分也介绍了Cocos2d-x3.x新的数据结构,并详细描述了新的渲染系统。总之,在这一部分,读者可以对Cocos2d-x的基本架构有比较系统的了解。
第一章:
旧的渲染流程:Cocos2d-x有一套很优秀的渲染机制,它给每个元素指定一个逻辑深度,并按照这个深度对UI元素进行排序,游戏中的每一帧只需要对UI树做一个深度优先遍历,就可以按照正确的顺序绘制整个场景。除了渲染,在每一帧上几乎没有额外的计算工作,因比能够比较高效地执行渲染工作。
**缺点:**但是,这套架构设计不易于扩展,以致逐渐显得有些过时。例如,开发者无法修改一个元素在全局的层级,每个元素都按照UI树的结构绘制,不能在父级或者子级之间变更层级。由于每个元素(如Sprite)负责自己的绘制,所以不易针对绘制做更好的优化。例如,相邻的两个元素使用了相同的纹理和相同的OpenGL ES绘制参数,但是它们仍然需要单独绘制两次。
新的渲染流程:Cocos2d-x 3.0重新设计了渲染机制,将渲染从UI树遍历中分离出来,这不仅使其架构更灵活、更易于扩展,还可以针对渲染进行优化(如自动批绘制技术)。新的渲染框架设计在很大程度上提升了引擎的性能与灵活性(详见第5章)。
第二章:
2.2 内存管理
C+11使用3种不同的智能指针,分别是unique ptr、shared_ptr和weak ptr.都属于模板类型,可以通过如下的方式使用它们。
为什么不使用智能指针:
-
性能损失,shared_ptr 保证线程安全
-
使用不方便
removeChild 减少引用计数(对比我们的引擎,node是lua的userdata,由lua负责回收)
游戏循环:
主循环使用sleep暂停(之前的程序都没有这步,可全局搜索dispatchMessage查找之前程序模版)
多种主循环方案
https://www.cnblogs.com/kekec/p/3670389.html
https://blog.csdn.net/zoharwolf/article/details/49467401
while (msg.message != WM_QUIT)
{
//获取消息
if (PeekMessage(&msg, 0, NULL, NULL, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
curTime = GetTickCount();
if (curTime - preTime > 30)
{
preTime = curTime;
GameUpdate(hwnd);
}else
{
sleep(curTime - preTime )
}
}
}
更新流程:
- CCApplication-win32的主循环int Application::run() 中调用director->mainLoop();
- mainLoop中调用drawScene,然后调用 PoolManager::getInstance()->getCurrentPool()->clear() 清理object
- drawScene中流程:
- calculateDeltaTime 计算deltatime
- 发送自定义事件_eventBeforeUpdate
- 调用 _scheduler->update(_deltaTime);
- 发送自定义事件_eventAfterUpdate
- 渲染场景
渲染
批处理:
-
不能有自定义的全局着色器变量(UNIFORM)
-
shaderProgram,纹理,混合模式 一样进行批处理,使用对应的值计算hash
自动裁剪:
Sprite在绘制时会首先计算其可见性,当位置发生变动的时候进行计算,包括扭曲 旋转 缩放 平移以及父节点发生这些改变时
对于大场景,不应该完全依赖自动裁剪,毕竟需要消耗性能,还需要注意对纹理内存的管理,手动移除并删除纹理。
帧缓存
多分辨率支持
-
EXACT_FIT:长宽直接缩放至屏幕分辨率,不考虑等比例,会拉伸
-
NO_BORDER:等比缩放,不拉伸,以宽高中缩放比例较大的比例进行缩放,较短的一边缩放后会超出屏幕
-
SHOW_ALL:等比缩放,以宽高中较小的缩放比例缩放,会有黑边
-
FIXED_HEIGHT:以高的缩放比进行缩放
-
FIXED_WIDTH: 以宽的缩放比进行缩放
getWinSize():上图坐标系范围
getVisibleSize():屏幕可见部分
第5~10章围绕 OpenGL ES图形渲染管线进行介绍,这部分从纹理讲起,详细讲述了纹理的存储格式、传输、缩放、压缩及多重纹理等相关知识;第8章详细讲述了顶点数组的结构、顶点属性的绑定及传输、着色器程序的编译及链接,以及 Cocos2dx新的着色器子系统,并举例在 Cocos2d-x中使用着色器的流程和怎样使用多重纹理。这部分也对顶点着色器和片段着色器两个阶段在图形渲染管线中的作用进行了详细描述。
第9、10章讲述了OpenGLES图形渲染管线的最后两个阶段:帧缓冲和片段操作,并以Cocos2d-x中对这两个阶段的应用Render Texture和ClippingNode为例进行讲解。这样,读者将对整个渲染管线的每一个阶段都能有所了解,并且能够结合Cocos2d-x中的使用去思考每一个阶段的意义和作用,从而对图形渲染管线有更深刻的了解。
第10~15章讲述了Cocos2d-x的一些子系统,包括事件分发、多分辨率支持、动画系统及物理引擎整合。其中物理引擎部分也是我比较喜欢的章节,这部分讲述了一些通用的物理引擎的架构及其使用,以及怎样和游戏引擎进行整合。
12. 事件分发系统 EventDispatch
-
cocos2dx 的事件系统使用订阅者模式,使用 void EventDispatcher::addEventListener(EventListener* listener) 接口添加监听,需要使用EventListenerCustom::create构造EventListener。
-
监听的排序,为了优化性能,cocos尽量避免频繁的排序,其采用了一种策略来进行优化:在优先级变动时进行标记,在分发事件前进行重新排序,且只对当前类型事件进行排序。
-
事件嵌套,在一个事件中触发了另一个事件。cocos使用了一个_inDispatch的变量来保存当前嵌套深度,为0时表示没有事件在分发。在当前嵌套内对监听优先级的修改会在下一个嵌套内生效(排序是在执行之前);对之后要生效的监听的部分修改会立即生效(移除是先标记为不可生效,再等分发结束后移除);添加新的监听并不会立即生效,而是加入列表中,等_inDispatch=0再加入监听列表。
-
停止分发事件,cocos允许先执行的监听让事件停止传播。回调内部通过修改Event的_isStopped,dispatch中通过返回该值来决定是否停止。
对比我们对事件的处理:
-
一样
-
每次在筛选出符合条件的监听时,都进行了排序。需要优化
-
除了优先级,其他任何修改都是立即生效
-
没有该功能
-
动画系统
利用scheduleUpdate 对update进行的封装 -
足够早的执行,在逻辑和物理处理之前
-
使用action类描述动画,提供了并行和串行动画。
-
必须依附与node进行播放
-
将更新间隔转换成百分比0~1
MoveBy 使用CC_ENABLE_STACKABLE_ACTIONS宏控制是否叠加
void MoveBy::update(float t)
{
if (_target)
{
#if CC_ENABLE_STACKABLE_ACTIONS
Vec3 currentPos = _target->getPosition3D();
Vec3 diff = currentPos - _previousPosition;
_startPosition = _startPosition + diff;
Vec3 newPos = _startPosition + (_positionDelta * t);
_target->setPosition3D(newPos);
_previousPosition = newPos;
#else
_target->setPosition3D(_startPosition + _positionDelta * t);
#endif // CC_ENABLE_STACKABLE_ACTIONS
}
}
15. 碰撞及物理引擎
基础的物理引擎知识:
-
碰撞检测系统本质上是几何相交测试器
-
一个刚体由多个形状和变换组成,形状是刚体每个部分的几何表达式,用于进行相交测试,形状通常由一系列点构成。变换用来表示形状中每个点在游戏中的位置及方向,形状中每个点的位置是相对于 Node元素的本地坐标系的,通过一个形状的变换就可以将一个形状描述应用于多个刚体,然后对形状的变换进行调整,这样可以节省内存,同时不需要计算每个点的坐标,计算也更快。通过变换可以调整形状在刚体中的相对位置。例如,在Cocos2d-x中创建一个多边形形状,可以指定offset来调整形状的位置。
刚体可以分为3类:
-
一个动态的体积刚体(Dynamic Volume)使用刚体的体积和质量来模拟刚体受外力的作用及碰撞。通常用动态体积刚体来表示一个需要受外力(如引力)和碰撞影响,或者会围绕其他刚体移动的物体。在Cocos2d-x中通过设置setDynamic(true)来设置刚体为动态体积刚体,这也是默认的刚体类型。
-
一个静态的体积刚体(Static Volume)与动态体积刚体类似,但是它的速度被忽略了,因此不受外力和碰撞的影响。但是由于其具有体积,其他刚体可以与它发生碰撞被弹回,或者受其他影响,如摩擦力。静态体积刚体通常用来描述一些会占据物理空间,但是并不会受物理模拟影响的物体,例如可以用它来表示迷宫墙或者房屋。
-
一个边缘刚体(Edge Volume)是一个静态的非体积刚体,它永远不会受物理模拟的影响,它的质量参数被忽略。边缘刚体通常用来描述一个单一的面,例如一个物体上的一个凹槽,或者一个无法穿越的、不可见的边界。边缘刚体通常用来表示场景边界。
Chipmunk定义了两种类型的刚体之间的交互:接触和碰撞,可以通过控制这两种交互来控制刚体之间的碰撞。
碰撞查询
rayCast用来查询物理世界中是否有形状与指定的一条线段相交,它返回第一个相交的形状。这通常可以用来实现目标命中预算等功能。此外,它还包含线段与形状相交点的法向量,还可以据此信息来实现光线反射,例如可以根据法向量计算一个新的投射线段。
queryRect用来查询一个指定矩形范围内是否包含形状。例如可以用来检测一个炮塔位置是否已经有一个炮塔在工作,或者当炸弹爆炸时指定范围内包含,
queryPoint用来检测某个点是否包含一个形状。例如在《愤怒的小鸟》游戏中,应·用程序首先计算出一系列点组成的一条抛物线,然后使用queryPoint查询这些点上是否包含一些目标。
第16~18章探讨应该怎样去设计和管理游戏世界中的对象。第16章讲述了常见的对象模型、组件模型、属性模型之间的概念、区别及优缺点;第17章则以属性模型为例,讲述了一个游戏对象模型应该怎样设计;第18章探讨了时下最流行的脚本相关的内容,但是与仅仅讨论脚本使用不同的是,我们站在一个游戏引擎的高度去讨论脚本的架构,这样读者甚至能设计自己的脚本模型。这一部分内容具有对前端架构设计的高度总结性与实践性,不管是对经验丰富的读者还是初学者,相信都具有一定的启发性。
16章和17章介绍了下ECS
18章介绍了下LUA