cocos2d 回顾

最近两三年年都没怎么搞游戏客户端了,最近在重新找工作,有cocos的面试,于是就再扫一遍引擎源码了。

关于cocos技术, 我认为比较重要几个点:

1. 引擎工作流?

一个游戏循环,每帧渲染节点树。。

2. 渲染流?

3.  事件流?  鼠标点击, 键盘输入, 手指触摸, 重力感应

4.  不同语言的交互。  c++ 掉用 mm文件方法, c++调用 java.     lua调用c++.   js调用c++

5.  drawcall 优化:  Node节点关系入手, BatchNode入手

6.  内存优化:能否改用有对象池? 场景按需加载, 切换场景释放不必要声音资源,图片资源?

7.   电量优化:  从CPU计算,网络消息, drawcall优化 着手

8.  数据和界面之间咋衔接的?  游戏设计思想, 采用哪些设计模式?

经常碰到的场景, 一个界面上很多元素的显示依赖服务器数据。   

大部分做法是调用服务端接口,等消息到了再整体显示。 这种情况一般需要菊花转圈。

也有一个部分是先显示界面, 等消息来了,在刷新页面元素。 两种方式没有好坏,这个看功能需求。

我设计一般某个页面层, 初始化时采用Net.on("login", this.onLogin)  这种方式,  销毁时采用Net.off('login', this.onLogin)

net.send('login', {usr: 'dzq', pwd:'123'}).   收到服务器回复后把数据先放到消息队列,用一个帧循环处理这个消息队列。 

有时需要对网络数据进行保存,一般设计很多数据类管理器, 比如UserMangaer,   GameDataManager, 之后用就从管理器中去取。 

 

 9.  包体优化, 一般就是把图片进行无损压缩, tinypng网站不错。 声音资源压缩。  然后就是热更新了。 如果还是很大, 把资源进行压缩打包,如使用ZIP, 把资源都搞成bin文件

 

其他的点, 无关紧要的点, 面试官喜欢问的点:

1.  Node节点引用技术机制?  估计是面试想知道你是否了解cocos?

Ref基本上是所有cocos类的超类。 只要new 一个cocos 对象,那么它的应用计数是1, 

retain() +1   

release() -1

autorelease().  引用计数不变, 不过把对象添加到了_releasePoolStack最后一个元素。 这个元素是一个AutoreleasePool* 队列

也就是说实际放到了 AutoreleasePool::_managedObjectArray

 

 每帧都会调用clear.

clear函数功能对_managedObjectArray每个对象调用release.   如果引用计数为0, 则释放内存。

也就是说创建了, 不addChild 或者retain , 那么对象就会被销毁。

如果addChild了,  那么此时引用计数是2,  clear后引用计数是1。  那么问题来了,  第二帧后, 还会调用clear,  此时又变成0了, 岂不是会释放掉?

其实并不会, 因为clear函数用了一个 verctor.swap函数, 相当于把_managedObjectArray清空了。 在第二帧的时候没有这个对象了, 所以不会再改变引用计数了。 一直是1. 

之后如果调用removeChild 那么 引用计数就会为0, 那么直接释放内存。

 

2.  spine动画咋使用?  这个问题说真的, 没啥价值, 即使没用过, 看下API也知道咋使用了。

3.  消息协议用的啥,数据协议用的啥, 粘包残包咋处理的?  

消息传输无非就是HTTP, socket, websocket.  百分之九十九的游戏都是这三种的传输方式

数据协议无非就是json, protobuf,  MessagePack,  百分之八十的游戏都是这三种的传输方式。 不过很多游戏有自己的加密算法。

粘包残包只在socket编程的出现, websocket协议不需要自己处理。 只要把数据包格式设计好就没啥好说的。

一般来说,  4字节包长加上包体。 在包体部分有的会加上4字节crc32校验, 防止数据出现修改。   这个还是比较好处理的,网上也有很多开源的算法。

服务器和客户端都需要处理分包粘包的问题,额外的一点, 业务层需要加心跳协议。 因为用户一段时间(这个时间大概1分钟吧, 不同系统时间也不一样)不发消息,TCP连接会被系统断开。

于是就需要加一个心跳协议,  发了一个心跳包,服务器肯定会回复。如果收不到回复,就基本认为与服务器断开连接。 这个等待回复时间一般是心跳包间隔的2倍。

比如间隔5秒发一个心跳包,从调用发送心跳包开始计算,第10秒还没有收到回复, 就可以弹出一个小框告诉玩家掉线了。不建议时间太短。 时间设置太短和太长都不好。 

 

 

引擎是怎么工作的?

主要方法: Director:mainloop  游戏循环

渲染工作:

Node::visit(renderer, parentTransform, parentFlags)

drawScene()-> 或递归遍历每一个节点, 然后使用rencder对象渲染, render是对GLES的封装。

 

怎么把图片显示到屏幕上的?

首先说一点:

每个平台opengl都不一样:

#if CC_TARGET_PLATFORM == CC_PLATFORM_MAC
#include "platform/mac/CCGL-mac.h"
#elif CC_TARGET_PLATFORM == CC_PLATFORM_IOS
#include "platform/ios/CCGL-ios.h"
#elif CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
#include "platform/android/CCGL-android.h"
#elif CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
#include "platform/win32/CCGL-win32.h"
#elif CC_TARGET_PLATFORM == CC_PLATFORM_WINRT
#include "platform/winrt/CCGL.h"
#elif CC_TARGET_PLATFORM == CC_PLATFORM_LINUX
#include "platform/linux/CCGL-linux.h"
#elif CC_TARGET_PLATFORM == CC_PLATFORM_TIZEN
#include "platform/tizen/CCGL-tizen.h"
#endif
 
1. PNG文件
2. sprite->initWithFile
3. Texture2D *texture = _director->getTextureCache()->addImage(filename);   得到texture对象
4.  setTexture 主要做了两个事情,更新对象属性_texture 和 updateBlendFunc
5.  游戏循环到visit, 在到draw方法
_trianglesCommand.init(_globalZOrder,
_texture,
getGLProgramState(),
_blendFunc,
_polyInfo.triangles,
transform,
flags);
renderer->addCommand(&_trianglesCommand);
这个步骤添加了一个渲染命令, 其实添加到 _renderGroups[_commandGroupStack.top()] 队列了。
 
6.  Renderer::render() 方法里会调用: renderer->visitRenderQueue..   --->   renderer->processRenderCommand

 

 7.  最终调用: TrianglesCommand::useMaterial()

 

最终调用gl函数 activeTexure和 glbindTexture

 

 

render方法在drawScene中调用:

 

 

至此一个图片就显示了。  至于gl是怎么操作texture的就不用管了,最终肯定也是调用显卡驱动的API了。

 

 

总结起来其实就四步: 

1, 把图片转成texture对象

2.  把tuexture设置给Node节点

3.  当帧循环执行到vistit时 发送_trianglesCommand

4.  执行_trianglesCommand命令, 调用GL::bindTuexture2D显示图片

 

 

 

 

 

 

 

posted @ 2021-05-23 03:05  Please Call me 小强  阅读(116)  评论(0编辑  收藏  举报