技术面¶
-
HTTP/WebSocket/Socket
-
单工、半双工、全双工
- 单工:只能单向通信,如:电视、广播。
- 半双工:可以双向通信,但不能同时,如:对讲机。
- 全双工:可以同时双向通信,如:电话。
HTTP1.1
以下是单工,HTTP1.1
是半双工,HTTP2.0
是全双工。
-
TCP/UDP
TCP/UDP
是传输层协议,解决数据在网络中的传输和组织方式。UDP
不需要建立连接,数据是不可靠的,有丢包情况,因此无法保证数据的完整性,但是结构简单,资源占用少,传输速度快。TCP
是面向连接的,建立连接需要三次握手,数据传输是可靠的,有序的,但结构较为复杂,且传输速度较慢,对系统资源要求多。- 短连接的优点是管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段。
- 长连接可以省去较多的
TCP
建立和关闭的操作,减少浪费,节约时间,对于频繁请求资源的客户端适合使用长连接,但需要维护连接,且连接多了占用资源。
-
三次握手
- 第一次:客户端向服务器发生
Sync
包,进入等待状态;(客户端说明都不能确认,服务器确认了对方发送正常,自己接收正常) - 第二次:服务器收到客户端的
Sync
包,确认之后向客户端发送Sync+Ack
包,进入收到状态;(客户端确认了自己和对方发送和接收正常,服务器确认了对方发送正常,自己接收正常) - 第三次:客户端收到服务器的
Sync+Ack
包,向服务器发送Ack
确认包,客户端和服务器即进入连接建立状态。(客户端和服务器分别确认了自己和对方发送和接收正常)
- 第一次:客户端向服务器发生
-
四次挥手
- 第一次:A 向 B 发送
Fin
终止包,同时停止发送数据,并进入终止等待状态; - 第二次:B 收到 A 的
Fin
包,发送ACK
包予以确认,进入关闭等待状态;A 收到 B 的 Ack 包后进入终止等待状态 2; - 第三次:B 发送完有数据后,再给 A 发送
Fin
包予以确认表示可以断开连接了; - 第四次:A 收到 B 的
Fin
包,再发送Ack
包给 B,此时 A 关闭到 B 的连接,B 收到包后也关闭到 A 的连接,连接彻底关闭。
- 第一次:A 向 B 发送
-
Socket
Socket
不属于协议层,它是一组对TCP/IP
协议包装的接口。
-
HTTP
HTTP
是应用层协议,主要解决如何包装数据。HTTP
是短连接或伪长连接(keep-alive
,它可以在一段时间内使用同一个TCP
连接,但每个请求仍需单独发送 Header,且只能由客户端发起请求、服务器响应)。- 致命缺点:只能由客户端发起,做不到服务器主动向客户端推送消息。
-
WebSocket
WebSocket
是基于HTTP
的应用层协议,属于HTML5
标准的一部分。WebSocket
借用了Socket
的概念,并模拟了Socket
的接口,实现了双向通信,但与Socket
完全是两个东西。
-
-
Protobuf/JSon/XML/Buffer
- JSon
- 可读性强,有类型,比
XML
小巧 - 传输效率不高,比
XML
快,比PB
慢
- 可读性强,有类型,比
- XML
- 格式统一,解析方便
- 数据量大,传输效率低
- Buffer
- 数据量小,传输效率高
- 可读性差,需要一套成熟的解析器
- PB
- 数据量小,有类型,解析快,效率高
- 学习成本高,需要手写
PB
文件
- JSon
-
Cocos2d-x 内存优化
- 使用静态合图
- 按照从大到小的顺序加载合图
- 使用对象池
- 减少音频文件大小
- 使用极少的内容使用完毕后立即释放
- 切换场景时释放无用资源
- 使用
Spine
骨骼动画替换序列帧动画 - 使用预制体,且单个预制体不宜过大
- 使用
BMFont
- 使用异步/分帧加载等多种方式,避免内存激增,导致性能下降
- 减少 Mask 组件的使用
-
Cocos2d-x Drawcall 优化
- 使用静态合图
- UI 层级拆分和调整,分离图像节点和文本节点
- 文本使用
BMFont
或开启char
缓存模式 - 需要使用一个
Spine
文件创建多个节点时,开启 Spine 的合批 - 尽量减少使用
shader
和更换材质,因为它们都会打断合批
-
Cocos2d-x 渲染过程
- 导演类的
mainLoop
中会调用drawScene
- 在
drawScene
中会调用场景类的render
render
中会递归执行节点类的visit
visit
中会调用精灵类的draw
draw
中会执行渲染类的addCommand
将渲染指令加入渲染队列- 场景树遍历完成后,会对渲染队列进行排序,最后才调用
OpenGL API
进行渲染 - 特别地,在生成材质的时候,如果
ProgramState
的Uniform
数量大于 0,也就是设置了Uniform
,那么材质 ID 就会被赋值为MATERIAL_ID_DO_NOT_BATCH
,这是一个特殊的材质 ID,意为不可批次渲染。因为Uniform
属于一个Shader
的变量,每次使用同一个Shader
进行渲染时,Uniform
都可能不一样,这里进行合并的话,后面设置的变量就会失效,也就意味着,需要增加一个drawcall
。
- 导演类的
-
Lua
- 轻量
- 动态语言,语法简单,高效简洁
- 支持热更新
- 代码写起来约束少,较为灵活,但随着开发的深入,如果没有严谨的代码质量把关,会制造大量垃圾代码
- 支持脚本加密
- 扩展较少,需要疯狂造轮子
-
A 星寻路
- 公式:
F = G + H
- F:起点移动到当前点的总代价
- G: 起点移动到当前点的移动代价;
- H: 当前点移动到终点的预估代价,通常使用曼哈顿距离计算;
- A 星算法伪代码:
- 创建开放列表和封闭列表;
- 首先将起点放入开放列表,将终点放入封闭列表;
- 开始循环,从开放列表中找
F
值【总代价】最小的节点,设置为current;
- 如果找到目标,则结束搜寻并回传结果;否则将它所有尚未检验过的所有相邻节点加入队列中;
- 假如某邻近点既没有在开放列表或封闭列表里面,则计算出该邻近点的
G
值和H
值,并设其父节点为P
,然后将其放入开放列表; - 继续循环,直至没有节点可以访问。
- 多拐点优化:改公式为
F = G + H + E(拐点值)
- 公式:
-
Cocos2d-x 与 Creator
Cocos2d-x
是游戏引擎Creator
是游戏开发工作流一体化的方案,其内部引擎是Cocos2d-x
-
JS 与 TS
- JS
- 动态语言
- 运行时进行类型检查,类型约束差,代码较为自由
- 开发环境无法提供丰富的帮助
- 无成员访问控制权限
- 无高级特性
- TS
- 静态语言
- 编译期进行类型检查,类型约束强,代码较为严谨
- 开发环境能提供丰富的信息
- 拥有完整的成员访问控制权限
- 拥有泛型、装饰器等高级特性
TS
是JS
的超集,最终运行时,TS
会转换为JS
- JS
-
EventDispatcher
整个流程包含 3 个类,第一个是EventListener
,负责监听并实现事件触发后的回调,第二个是Event
,这是一个结构体,用于记录事件内容,作为参数传递到回调中,最后是EventDispatcher,用来管理事件和触发事件。
EventDispatcher
除了实现最基本的监听者管理和消息分发外,还实现了消息的优先级,以及监听者与节点的关联EventDispatcher
将事件按照fixedPriority
从小到大的顺序通知该监听者队列的所有监听者,所有绑定到节点的监听者的fixedPriority
都为 0,EventDispatcher
会根据所关联节点的ZOrder
从大到小的顺序进行回调- 实现一个消息派发类,说难不难,但也不简单,这种类一般都是底层类,所以特别注重两点,一是稳定性,二是效率。主要体现在注册了大量监听者之后,在事件回调中注册、注销,以及连环触发其他的事件等各种复杂的情况下能够不崩溃,并且保证程序运行效率。
-
音频优化
- wav 文件大,占用空间,且只支持同时播放一个文件
- ogg 与 mp3 之争
- ogg 可以支持多声道,而 mp3 最多支持双声道
- 低码率下 ogg 音质较 mp3 好,且文件略小于 mp3
- mp3 会砍掉音频高频部分,导致频谱丢失,直接结果就是音质变差
- Android 支持 mp3 和 ogg,但是 iOS 不支持 ogg
- 综合来看,建议 Android 使用 ogg,iOS 使用 mp3
- 减少声音文件大小
- mp3 可能采用立体声,可以把它转换为单声道,这样可以减少一半大小
- 在 iOS 设备上,任何比特率大于 192kbps 的声音都是浪费,所以尽量采用低比特率来获得最好的音质效果,一般来说 96~128kbps 是比较合适的
- 采样率越低,声音文件越小,但是音质也会越差,常用的采样率有 11、22、44、48kHz,一般来说采用 22/44kHz 是比较合适的
- SimpleAudioEngine / AudioEngine / FMOD
- SimpleAudioEngine 只封装了各平台的简单 API,支持有限,特别明显的是在播放音效时无法获取状态,也无法在播放完成后回调
- AudioEngine 支持更多高级特性
- SimpleAudioEngine/AudioEngine 都存在一些问题,比如:
- 同时快速播放多个文件可能造成崩溃,
- 存在播放卡顿问题
- 播放长音效可能丢失
- FMOD 跨平台,专业级的音频处理库,支持丰富的高级特性
- 音频优化
- 建议采用单声道,这样可以把文件大小和内存使用都减少一半
- 尽量采用低比特率来获得最好的音质效果,一般来说,96~128kbps 对于 mp3 文件来说足够了
- 降低文件的比特率可以减小声音文件的大小
-
图片优化
-
存储格式 (文件存储)
图片的文件格式决定了以何种方式来压缩图片 图片内容越简单,压缩率越高,而越复杂的图片,压缩率越低 单纯的文件压缩并不能减少其在内存中的占用,唯一的目的就是减小包体积。
- jpg
- 压缩率高,有损压缩,文件占用小
- 不支持透明通道
- 占用内存与 png 相当,画质却差很多
- 解压所需时间和内存都比较高
- png
- 压缩率较高,无损压缩,支持透明通道
- tiff
- 比较复杂,高级特性比较多
- 常用于印刷和扫描领域
- tga
- 无损压缩,高质量,支持透明通道,接哦古简单,支持不规则图形
- 常用于工业、影视领域
- webp
- 有损压缩,在相同的质量下体积比 jpg 小 40%,但编码压缩时间比 jpg 场 8 倍
- 目前在移动设备上加载效率比较低
- jpg
-
纹理压缩格式
正常纹理在加载的时候需要开辟内存进行解析,将文件解压成可渲染的像素格式 而压缩纹理只需要进行简单的解析,即可载入 GPU 进行渲染,其存储大小即内存占用大小,效率自然快了很多 占用内存更低,也可以起到一定压缩文件大小的作用
- pvrtc
pvrtc
iOS 直接支持,解压快,效率高,只支持方形贴图,非方形会被处理成方形,且必须为 POTpvrtc2
iOS 直接支持,解压快,效率高,相比pvrtc2
质量高一些,支持NPOT
pvr.ccz
是压缩后的pvr
格式,将大量的文件压缩为pvr.ccz
能够有效的减小文件大小
- etc
etc1
仅支持安卓,压缩率高,不支持透明通道,部分显卡不支持NPOT
etc2
仅支持安卓,压缩率高,支持透明通道,支持NPOT
,需要OpenGL ES3
- pvrtc
-
显示 (像素) 格式 (内存存储)
像素格式决定了纹理所占用的内存大小,以及图片显示的质量 计算公式:
占用内存 = 图片宽度 x 图片高度 x 纹理位数 / 8
BGRA8888
32 位,效果好,兼容性高RGBA8888
32 位,效果好,兼容性较差,需要转换为 BGRARGB888
24 位,效果好,不支持透明通道RGB565
16 位,效果较好,不支持透明通道RGB5A1
16 位,色彩较好,半透明效果良好,不支持透明通道RGBA4444
16 位,色彩比 RGB5A1 略差,但半透明效果良好RGBA5551
16 位,色彩较好,但半透明效果很差RGBA5555
20 位,色彩较好,半透明效果也不错
-
总结
- 逐帧载入游戏资源,避免内存高峰
- 载入纹理是按照内存占用从大到小的顺序
- 可以使用 Loading 来预加载,优化体验
- 打包大图,减少
drawcall
- 避免载入超大纹理
- 闲时或收到内存警告时释放空闲资源
- 使用
NPOT
纹理,而非POT
,因为POT
会占用额外内存 - 在不影响画质的前提下,尽量使用低颜色深度的纹理
- 优先考虑压缩纹理格式
-
-
Cocos2d-x 内存管理
- Cocos2d-x 的内存管理机制
- 首先,要想让对象参与
Cocos2d-x
的内存管理机制,必须继承CCRef
类 - 然后,调用对象的
autorelease
函数,对象就会被 内存管理机制盯上,在游戏的每一帧,内存管理机制都会扫描一遍被盯上的对象,一旦发现对象无人认领,就会将对象杀死! - 如果不想让对象被杀死,那么就要调用对象的
retain
函数,这样对象就被认领了,一旦对象被认领,就永远不会被内存管理机制杀掉
- 首先,要想让对象参与
- 引用计数
- 构造函数中,引用计数成员变量被设置为 1
- 当调用
retain
的时候,引用计数自增 1 - 当调用
release
的时候,引用计数自减 1,并判断引用计数是否为 0,如是则执行delete this
操作 - 当调用
autorelease
时,引用技术自增 1,并在切换至下一帧时自动减 1 retain
和release
需要成对地出现,一旦调用了该对象的retain
方法,在不需要使用该对象时,必须调用该对象的release
方法。
- Cocos2d-x 的内存管理机制
-
Cocos2d-x 定时器 Schedule
Schedule
的整体结构由Schedule
和Timer
组成Timer
负责将一个回调封装为一个对象,管理回调的计时、触发、保存回调的状态。一共有 3 种Timer
:TimerTargetSelector
封装了对象回调TimerTargetCallback
封装了函数回调TimerScriptHandler
封装了脚本回调
Schedule
管理着大量的Timer
,负责注册、注销,以及驱动执行回调等工作,是Timer
的总调度室
- 在
Cocos2d-x
中共用一个Schedule
对象,该Schedule
对象由Director
创建并维护。在Director
启动的时候,会创建一个Schedule
对象,通过Director::setScheduler
可以获取到。可以自己创建一个Schedule
对象来管理局部的某些计时回调,但一般直接使用全局的Schedule
。 Director
在每一帧的主循环中,都会调用全局Schedule
的update
,来驱动Schedule
内部的计时回调。- 当
Director
被销毁时(退出游戏的时候),全局的Schedule
也会被销毁,同时清理Schedule
自身的所有回调对象。 - 在遍历的过程中动态地注册和注销都是安全的。对象回调、函数回调和脚本回调会在遍历的过程中被安全地移除,而
update
回调会在遍历完成之后,额外遍历一次,将标记为移除的回调移除。 Schedule
提供了一个比较实用的功能performFunctionInCocosThread
。该功能是Cocos2d-x 3.0
之后新增的,用于线程安全。在其他线程中使用该方法可以安全地由主线程执行一些逻辑,因为当我们在其他线程中操作Cocos2d-x
相关内容时,有可能会导致程序崩溃或出现一些渲染错误的问题。- 当节点被添加到场景里的时候,就会进入激活状态,而当节点从场景中删除的时候,就会进入非激活状态,甚至直接被销毁。
Node
中有一个成员变量_running
来标识这个状态,该变量的改变主要在onEnter
和onExit
中,在游戏设计的时候经常会继承,然后重写这两个方法,并且忘记调用父类的onEnter
和onExit
,那么这个时候状态变量就没有被重置,而runAction
、Schedule
等一系列的函数,在注册Schedule
时会根据该变量来判断回调是否暂停,默认是非激活状态,所以Schedule
的回调不会被执行。