刨根问底---cocos2d源码的理解与分析

主要看的是cocos2d的2D部分C++源码(不包含3D的或者是creator相关的),某些比较特殊的方法和变量会比较详细的展开分析讨论。
cocos2dx_3.1.7版本
CCRef:[已看完]
cocos2d最基础的类,绝大部分cocos2d的类都继承自它。主要作用是用于引用计数(cocos2d的自动内存管理)。CCRef会在构造函数把引用计数_referenceCount设置为1,在创建对象时,尽量调用cocos2d的create方法,他内部初始化(init)完成后会调用autorelease把对象加到自动释放池,而如果用户真的自己去new cocos2d的对象时,由于_referenceCount初始值为1,一定要主动调用release函数以避免内存泄漏。
retain:引用计数加1.
release:引用计数减1.
autorelease:加入自动释放池中,如果计数递减到0,会自动释放(具体原理下面CCAutoreleasePool中详解)
CCNode:[已看完]
同样是cocos2d最基础的类,继承自CCRef,很多cocos2d常用的类也继承自CCNode,如:CCSprite、CCLabel、CCScene等等。在继承了CCRef引用计数的能力后,CCNode的作用是增加了很多基础节点相关的基础属性,如:缩放、位置、旋转、层级、大小等等,让那些继承自CCNode的子类轻松的获得这些基础方法。
_localZOrder:ZOrder的值越少,绘制的优先级越高(ZOrder值小的先绘制,即值大的会盖在显示的最上方),值相等时,先addChild的先绘制。
scale:节点的缩放。
position:节点的坐标(位置)。
skew:节点的倾斜。
anchorPoint:节点的锚点。
contentSize:节点的尺寸(大小)。
visible:节点是否可见。
rotation:节点的旋转。
_realOpacity:设置透明度。
cascadeOpacityEnabled:是否向下传递透明度(即设置了透明度后,子节点是否跟随父节点改变透明度)。
_realColor:设置节点颜色值。
_cascadeColorEnabled:是否向下传递颜色值(即设置了颜色值后,子节点是否跟随父节点改变颜色值)。
tag:节点的标记。
name:节点的名字
_userData:用户的自定义扩展参数,类型是*void,可以保存需要自定义参数的对应指针。
_userObject:扩展对象,类型是Ref,意味着可以保存一些继承自Ref的对象类型。
_running:这个值应该和removeChild时是否clearup有关(在action、schedule等地方会用到),在onEnter时设置为true在onExit时设置为false,也可以用于判断它是否在生命周期内(是否存活)。
_eventDispatcher:用于消息分发的对象,默认使用director类里的dispatcher,但可以自由设置新的dispatcher。
_actionManager:用于控制action相关的行为,默认使用director类里的actionManager,但可以自由设置新的actionManager。
_scheduler:定时器,默认使用director类里的_scheduler,但可以自由设置新的_scheduler。
addChild:把一个子节点添加到这个父节点上。
getChildByTag:通过tag获取加在当前节点上的某个指定子节点。
getChildByName:通过name获取加在当前节点上的某个指定子节点。
enumerateChildren:通过指定选项,正则搜索,枚举所有子节点。
getChildren:获取所有下一级的子节点。
getChildrenCount:获取子节点的数量。
getParent:获取父节点。
removeFromParent:从父节点中移除自己。
removeFromParentAndCleanup:如果为true则立马删除相关action和回调事件。
removeChild、removeChildByTag、removeChildByName:删除指定的子节点。
removeAllChildren:删除所有子节点。
reorderChild:重新设置子节点的ZOrder值。
GLProgram:设置or获取 OpenGL渲染相关代码(如顶点着色器,片段着色器)。
GLProgramState:设置or获取 一些着色过程中用到的uniform。
getScene:获取这个节点添加到的scene(其实就是获取跟节点),通过一直往上遍历父节点,直到找到没父节点的节点,即为根节点也就是当前场景了。
action:runAction、stopAction、stopActionByTag等等action相关的方法没啥特别需要讨论的,就是调用了ActionManager里面封装的方法。
schedule:schedule、scheduleOnce等等相关方法也就直接调用了Scheduler里面的方法,不作讨论了。
resume、pause:主要就是恢复和暂停三个功能(消息分发、action、定时器)。
update:每帧刷新一次的定时器,重写就可以使用。
convertToNodeSpace:把世界坐标转换为相对于当前节点的本地坐标。(convertToNodeSpaceAR转换的是锚点,同理)
convertToWorldSpace:把相对于节点的本地坐标转换为世界坐标。(convertToWorldSpaceAR转换的是锚点,同理)
convertTouchToNodeSpace:把点击屏幕的坐标转换为相对于节点的本地坐标。(convertTouchToNodeSpaceAR转换的是锚点,同理)
visit:每一次mainloop中,都会执行一直绘制,会调用到visit,visit函数会对子节点进行排序(根据ZOrder),然后遍历所有子节点,调用子节点的visit函数进行绘制(ZOrder越少的值越优先绘制)。先绘制ZOrder少于0的子节点,然后绘制自己,最后再绘制ZOrder大于0的子节点。最后就是调用draw函数进行相关的绘制工作。
draw:执行openGL的绘制工作,在node的这个基类里,这个draw方法其实更多的作用就是一种虚函数,让子类去重写并且根据子类的需求来实现具体的draw功能。
CCScene:
_defaultCamera:初始化scene时设置的camera。
_visitingCamera:遍历cameras时最后一个camera(最上层?)。
【刨根问底---cocos2d源码的理解与分析】CCSprite:[已看完]
cocos2d 最常用的类之一。创建一个精灵。
_batchNode:设置图集。
_texture:纹理信息。在setTextrue时会对纹理对象retain,持有这对象不被释放,直到CCSprite的析构函数或者更换texture时才会release释放对象。
_rect:设置texture的矩形区域大小。在创建Sprite或者更换textrue等时候都会重新设置其大小。
_spriteFrame:图集,在设置的时候对其retain,持有对象。并在设置时,根据_spriteFrame里的值重新设置CCSprite部分成员变量,如texutre,rect等。
_rectRotated:显示矩形的旋转度。
create:通过传入一个文件名或者一个多边形信息,调用textureCache里面的addImage方法来创建一个texture出来,然后再执行init初始化。
init:CCSprite类提供了多种init方法,其实归根到底也是统一把传入的内容转换成texture然后再调用initWithTexture进行初始化。
initWithTexture:初始化一些信息,如成员变量的默认值。
setTexture:设置texutre,如果没传入参数,则会设置一张白色图作为texture。
setCenterRectNormalized:设置九宫格中心矩形的位置。传入的矩形参数为“单位”矩形,即值是0~1之间的。只能在QUAD或SLICE9模式下使用。
setCenterRect:设置九宫格中心矩形的位置,传入的是正常的矩形,内部转成单位矩形,再调用setCenterRectNormalized。只能在QUAD或SLICE9模式下使用。
flipX、flipY:X轴和Y轴镜像翻转。
setStretchEnabled:是否拉伸精灵纹理
draw:创建渲染指令,然后把渲染指令加到指令队列中。然后最后会根据优先级排序再进行整个队列的渲染。(详细渲染流程可看下面的【Renderer】段)
SpriteBatchNode:[已看完]
这个类简单来说就是提高渲染效率,减少每帧的绘制时间,如果相同的精灵越多,效果就越明显。
有一个_textureAtlas的成员变量去保存texture图集,然后就可以拿这个texture加上区域去创建需要的sprite,无需每次都创建一个texture这么豪性能,所以需要创建的相同精灵越多,效果越明显。
TextureAtlas:[已看完]
这个类是用于处理图集的,记录图集的texture和一些相关信息,根据需要从图集提取对应的图片。因为以前cocos只支持2的整数幂大小的图集,所以假设一张图片大小为513513,图片就要弄成10241024的大小,大量浪费内存,把所有散图打成图集能减少很多不必要的内存浪费。现在很多工具或者UI编辑器都带有把散图打成图集的功能,如果只是纯粹处理图片,把散图打成图集可以使用texturePackerGUI。
CCLabel:[已看完]
cocos2d 最常用的类之一。用于创建程序中显示到的字体。
setString:设置文本的内容。在内部会尝试把UTF8转换为UTF32保存一份,因为针对外语版(如葡文等),UEF8的区间太少了,UTF32才能比较准确的获取到正确的字符串长度。
getStringNumLines:文本显示的行数。感觉系统字的行数计算有问题,通过换行符“\n”来统计行数,而设置了宽度的自动换行不会计算行数,但TTF的计算行数是没问题的(计算方式和系统字不一样)。
getStringLength:获取文本长度。
setTextColor:设置文本颜色。
enableShadow:开启文本阴影效果。
enableOutline:开启文本描边效果。
enableGlow:开启文本发光效果。(只有TTF才有相关效果)
enableItalics:斜体字,其实就是旋转一下X轴。
enableBold:加粗,其实是直接调用了阴影效果,设置一个X轴的偏移值,就能让字体加粗了。(加粗会默认用字体原有颜色)。
enableUnderline:下划线,其实就是创建了一个DrawNode,然后在【updateContent】时,根据文本行数通过drawLine来绘制下划线。
enableStrikethrough:删除线,其实原理和下划线一模一样,只是把下划线的Y轴位置向上移一点而已。
disableEffect:关闭字体效果。(可以指定关闭某种效果或者关闭全部效果)
setAlignment:设置字体的对齐方式。(左、中、右 )和( 上、中、下)
setLineBreakWithoutSpace:强制超出行宽会自动换行,会截断单词慎重!!!(就是为了那些很长的数字或者单词用的)
setMaxLineWidth:设置一行文字最大长度。但如果设置了_labelWidth这个设置就不生效了。
setWidth:其实就是固定高度,动态设置宽度的setDimensions。
updateContent:更新文本内容。
getLetter:根据传入的index,找到对应字符的纹理,然后通过纹理创建出一个sprite,返回给调用者,调用者可以单独对这个sprite进行操作(位移,变色,单独旋转等)
setLineHeight:设置行高,但这设置对system font无效。
setLineSpacing:设置行间距,置对system font无效,但计算方式和setLineHeight是不同的。(当然最后两个值会相加)。
getLabelType:labelType就是TTF、BMFont、CHARMAP、string_texture四种咯。
setAdditionalKerning:设置字间距。
createWithSystemFont:创建系统字,参数2可以传入你想要的字体名称。(个人建议参数2使用默认值,可能很多游戏美术有需求指定用什么字体会好看点,但【1】你所使用的字体,玩家手机上不一定有;【2】而更坏的结果就是,某些字体可能字库不全,简单举例可能他只有英文而没中文,万一玩家输入到某些字,那字库缺失,也是个麻烦。 如果真的要使用指定的字库,建议使用TTF,自己找个你认为比较全面的字库来使用)
createWithTTF:使用指定的字库。可以在网上找相关的TTF字库,然后使用它。一定要注意,要确保字库包含了你所需要使用的所有文字,如:使用TTF显示一些界面相关的文字(开发者可控制,确保字库齐全,如我在英文地区,只需使用英文字库就好了,字库大小还小点),但如果TTF是用于给玩家输入,这就不可控了,玩家可能可以打出你字库里不存在的内容。
createWithCharMap:使用一张图集,然后自定义一个字的大小(在图中的长宽),再设置ASCII对应的起始位置。然后就可以根据你输入的内容对应ASCII里的位置,去裁剪图集中对应位置的图片出来了。
阴影、描边、发光效果处理过程:
【createShadowSpriteForSystemFont】方法会创建一个_shadowNode对象,我的理解是把这个_shadowNode放在字体下方,然后把size设置大一点就形成描边,把字体位置做点偏移值,就形成阴影。
【onDrawShadow】方法会设置一些片段着色器(ccShader_Label_outline.frag)需要用到的参数,如_uniformEffectType、_uniformEffectColor等。而且TTF如果设置了描边,外发光效果就不会生效了。
CCDirector:
drawScene:在游戏主循环mainloop里面每帧调用, 然后计算deltaTime后调用update,接着清理render和FBO。然后render runningScene(绘制加在scene下的所有子节点内容),接着再visit _notificationNode(绘制加在全局节点下的所有子节点内容)。最后再交换缓冲区。
mainloop:游戏的主循环函数,每帧调用一次,调用drawScene和释放自动对象池。
在CCDirector类中的不同节点,会分发不同的消息通知:
EventDispatcher* _eventDispatcher = nullptr; // 消息通知对象本身
EventCustom* _eventProjectionChanged = nullptr; // 设置openGL project时发送消息
EventCustom* _eventBeforeDraw =nullptr; // drawScene中调用draw绘制之前发送消息
EventCustom* _eventAfterDraw = nullptr; // drawScene中调用draw绘制之后发送消息
EventCustom* _eventAfterVisit = nullptr; // 执行完renderScene后发送消息(是render runingScene)
EventCustom* _eventBeforeUpdate = nullptr; // drawScene中调用定时器update之前发送消息
EventCustom* _eventAfterUpdate = nullptr; // drawScene中调用定时器update之后发送消息
EventCustom* _eventResetDirector = nullptr; // 执行director中reset方式时发送消息
EventCustom* _beforeSetNextScene = nullptr; // 当场景有变化的时候(pushScene、popScene等),在换场景前
EventCustom* _afterSetNextScene = nullptr; // 当场景有变化的时候(pushScene、popScene等),在换场景后
CCCamera:
_defaultViewport:默认显示区域,在游戏初始化时,setOpenGLView的时候会进行设置。
_visitingCamera
CCApplication-win32:
run:几乎相当于游戏的入口函数,在游戏启动时会调用此函数,然后会调用到其子类appDelegate的方法applicationDidFinishLaunching(这个就是游戏代码的接入口啦,可以在此处创建scene,然后就执行相关的游戏代码逻辑啦),接下来会持有一个glview(执行了retain呗),执行一个while循环直到游戏窗口关闭才退出循环,此while循环也就是游戏的主循环了,在while循环内部会一直计算时间差,只要大于等于每帧的时间,就会调用mainloop执行游戏的主循环函数,所以mainloop就是每帧调用一次的。 窗口关闭,while循环结束后也会调用release释放glview
SpriteFrame:[已看完]
其实简单理解就是texture+rect,在一张texture上通过裁剪不同的rect来获取不同的图片,如平时播放的帧动画就可以在一张texture上完成所有的帧,播放时只需修改rect的origin就可以播放动画了(前提是动画的大小都一样不然还要设置size)。
CCAutoreleasePool:[已看完]
自动释放池。加入自动释放池的对象,会在游戏的下一帧(游戏主循环mainLoop),对相关对象进行一次release引用计数减1操作,引用计数到0就会释放对象。如果用户只是创建了对象,而没有执行retain或者addChild等其他让引用计数+1的操作,引擎会视为这对象只是“创建”了而没有“被使用”,即垃圾资源而释放掉。
CCScheduler:[已看完]
就是做定时器的。在Director类里面有一个_scheduler对象,在mainloop函数会调用到Director类里面的drawScene函数,然后在此函数中会调用到 _scheduler->update从而完成了每帧执行一次的定时器功能。
_hashForTimers:用一个结构指针以哈希表的形式来保存了相关定时器的信息。结构的部分内容如下:
timers:一个数组,用于保存TimerTargetCallback类型的变量
target:设置定时器的对象
hh:就是用UT_hash_handle来使用cocos2d自定义的一种hash表,可以简单的使用一句宏来实现表的增、删、查等操作,如HASH_ADD_PTR(_hashForTimers, target, element);
TimerTargetCallback:继承了timer,总的来说就是用来保存用户创建定时器时传入的参数。
update:遍历_hashForTimers,然后再逐一调用其中的timers的update(就是Timer类的update),然后在此update内判断是否达到用户指定的定时器时间,最终再调用创建定时器时传入的回调。
CCEventDispatcher:[已看完]
cocos2d的消息分发类(包括触屏,鼠标点击,键盘输入等消息)。
addEventListenerWithSceneGraphPriority方法:
在场景中添加一个监听,会自动根据场景中监听者的zorder值来进行优先级排序,显示在游戏最上层的监听优先级最高。在消息分发的时候,会检查值dirtyFlag是否为“脏”,如果是,则进行排序,获取当前runningScene的所有node,只要有这种类型的监听都根据其Zorder的值进行排序。 这里要注意!是当前runningScene,所以如果有加到全局节点(director类里的_notificationNode),因为这节点上的node不属于runnningScene里面,所以不会进行排序优先级会属于最低(当然,我觉得_notificationNode也不应该这样加监听)。
getFixedPriorityListeners方法:
比较简单,就是直接根据你设置的优先级进行排序,数值越少,优先级越高,数值相等,先注册的优先级高。
EventListener的相关事件:
TOUCH_ONE_BY_ONE, // 单点触屏监听
TOUCH_ALL_AT_ONCE, // 多点触屏监听
KEYBOARD, // 键盘输入监听
MOUSE, // 鼠标监听
ACCELERATION, // 加速计监听
FOCUS, // 焦点监听
GAME_CONTROLLER, // 游戏手柄监听
CUSTOM // 用户自定义监听
误解:之前看到CCNode持有一个_eventDispatcher的成员变量,以为每个node都各自持有自己的_eventDispatcher,但在eventDispatcher里面又是进行统一排序的,就在这上面卡里很久(没仔细看代码的恶果)。最后发现原来在node里面初始化_eventDispatcher变量时是用获取_director里面的dispatcher来赋值的。 所以我的理解,cocos2d里面所有的消息分发应该都是统一使用director里面的dispatcher,然后就基于这个dispatcher进行消息分发优先级排序。 至于为什么没把CCEventDispatcher做成单例的形式,估计是允许自己组建自己的消息分发系统吧。
RenderQueue:
这个其实就是一个渲染队列类,根据不同的分组类型,通过数组保存下需要渲染的命令,再根据不同的分类用不同的方式进行排序(如深度、Order等)。
QUEUE_GROUP:枚举不同的渲染类型,最终渲染时,根据不同的渲染类型进行分组,同组的一起渲染。
_isCullEnabled:是否进行裁剪。
_isDepthEnabled:是否进行深度测试。
_isDepthWrite:深度缓存是否可写入。
size:command命令的条数。
sort:对数组内不同的分组类型,使用不同的方式进行排序。
clear:清理未渲染的command命令。
Renderer:
VBO_SIZE:单个VBO缓存顶点最大上限为65536.
INDEX_VBO_SIZE:因为2D基本都是四边形,而四边形由2个三角形组成,所以一个四边形会有4个顶点,6个索引,所以此处索引缓存最大值就是VBO_SIZE*6/4
_commandGroupStack:根据顺序来保存渲染组别的类型。
_renderGroups:渲染组,保存不同类型的渲染队列。
_clearColor:用于清除颜色缓冲的颜色值。
TriBatchToDraw:一个Batches的内部结构。
initGLView:初始化GLView,例如创建VBO等。
addCommand(RenderCommand* command):添加渲染指令到渲染组最上层的渲染队列中。
addCommand(RenderCommand* command, int renderQueueID):添加渲染指令到指定组别的渲染队列中。
pushGroup:把一个新类型的渲染队列,加到渲染组中。
popGroup:移除一个最上层的渲染队列。
createRenderQueue:创建一个渲染队列,返回队列ID。
render:渲染函数,对渲染组进行排序(为什么不只排最上层需要渲染的队列,要排整个渲染组所有队列?),然后获取最上层的队列,执行渲染队列中的所有指令。
clean:清理渲染组中所有渲染队列。
clear:清楚GL的深度缓冲和颜色缓冲。
setDepthTest:开启关闭深度测试。
checkVisibility:测试是否在视觉内,不在视觉内不绘制。
setupBuffer、setupVBO、setupVBOAndVAO:创建VBO或者VAO缓冲(gen)
渲染相关流程:
在【render】函数里面,会对整个渲染组的各渲染队列进行排序,然后获取最顶层的渲染队列进行渲染【visitRenderQueue】,然后根据不同的渲染队列组别执行不同的处理逻辑(设置深度测试、颜色混合等),再调用【processRenderCommand】获取对应队列的command指令,接着调用【flush】,最后调用到【drawBatchedTriangles】调用opengl相关接口处理command命令,进行渲染。
Command:command指令内部,包含了一些如排序用到的globalOrder等相关信息,和一些渲染时用到的textureID,mv,glProgramState等相关信息。

    推荐阅读