查看原文
其他

Unity3D游戏丨《腾讯桌球》客户端开发经验总结(下)

2016-06-28 tylerzhu Gad-腾讯游戏开发者平台
阅读前文:Unity3D游戏丨《腾讯桌球》客户端开发经验总结(上)
用脚本?还是不用?这是一个问题
由于Unity3D手游更新成本比较大,而且目前腾讯桌球要求不能强制更新,这导致新版本的活动覆盖率提升比较慢、出现问题之后难以修复。
针对这个情况,考虑引入lua进行活动开发,后续发布活动及修复bug只需要发布lua资源,进行资源更新即可,大大降低了发布和修复问题的成本。
可选方案还有使用Html5进行活动开发,目前游戏中已经预埋了Html5活动入口,并且已经用来发过“玩家调查”、“腾讯棋牌宣传”等。
但是与lua对比,不能做到与Unity3D的深度融合,体验不如使用lua,例如不能操作游戏中的ui、不能完成复杂界面的制作、不能复用已有的功能、玩家付费充值跟已有的也会有差异 游戏脚本之王——Lua魔方比较喜欢用lua,火隐忍者(手游)unity+ulua,全民水浒cocos2d-x+lua等等都有使用lua进行开发。 方便更新,减少Crash(特别是使用C++的cocos引擎)
资源管理
资源管理器业务不要直接使用引擎或者系统原生接口,而是封装一个资源管理器负责:资源加载、卸载
兼容Resource.Load与AssetBundle资源互相变更需求,开发期间使用Resource.Load方式而不必打AB包效率更高
加载资源时,不管是同步加载还是异步加载,最好是使用异步编码方式(回调函数或者消息通知机制)。如果哪一天资源由本地加载改为从服务器按需加载,而游戏中的逻辑都是同步方式编码的,改起来将非常痛苦。其实异步编码方式很简单,不比同步方式复杂。 资源类型图片/纹理(对性能、包体影响最大因素)音频背景音乐,腾讯桌球使用.ogg/.mp3音效,腾讯桌球使用.wav数据文本二进制动画/特效
图片-文件格式与纹理格式常用的图像文件格式有BMP,TGA,JPG,GIF,PNG等;常用的纹理格式有R5G6B5,A4R4G4B4,A1R5G5B5,R8G8B8, A8R8G8B8等。 文件格式是图像为了存储信息而使用的对信息的特殊编码方式,它存储在磁盘中,或者内存中,但是并不能被GPU所识别,因为以向量计算见长的GPU对于这些复杂的计算无能为力。
这些文件格式当被游戏读入后,还是需要经过CPU解压成R5G6B5,A4R4G4B4,A1R5G5B5,R8G8B8, A8R8G8B8等像素格式,再传送到GPU端进行使用。
纹理格式是能被GPU所识别的像素格式,能被快速寻址并采样。
举个例子,DDS文件是游戏开发中常用的文件格式,它内部可以包含A4R4G4B4的纹理格式,也可以包含A8R8G8B8的纹理格式,甚至可以包含DXT1的纹理格式。在这里DDS文件有点容器的意味。
OpenGL ES 2.0支持以上提到的R5G6B5,A4R4G4B4,A1R5G5B5,R8G8B8,A8R8G8B8等纹理格式,其中 R5G6B5,A4R4G4B4,A1R5G5B5每个像素占用2个字节(BYTE),R8G8B8每个像素占用3个字节,A8R8G8B8每个像素占用 4个字节。 基于OpenGL ES的压缩纹理有常见的如下几种实现:
1)ETC1(Ericsson texture compression),ETC1格式是OpenGL ES图形标准的一部分,并且被所有的Android设备所支持。
2)PVRTC(PowerVR texture compression),支持的GPU为Imagination Technologies的PowerVR SGX系列。
3)ATITC (ATI texture compression),支持的GPU为Qualcomm的Adreno系列。
4)S3TC (S3 texture compression),也被称为DXTC,在PC上广泛被使用,但是在移动设备上还是属于新鲜事物。支持的GPU为NVIDIA Tegra系列。

资源工具有了规范就可以做工具检查,从源头到打包资源导入检查资源打包检查
性能优化
掉帧主要针对GPU和CPU做分析;内存占用大主要针对美术资源,音效,配置表,缓存等分析;卡顿也需要对GPU和CPU峰值分析,另外IO或者GC也易导致。

工欲善其事,必先利其器Unity ProfilerXCode instrumentsQualcomm Adreno ProfilerNVIDIA PerfHUD ES Tegra
CPU:最佳原则减少计算复用,UIScrollView Item复用,避免频繁创建销毁对象缓存,例如Transform运算裁剪,例如碰撞检测裁剪粗略碰撞检测(划分空间——二分/四叉树/八叉树/网格等,降低碰撞检测的数量)精确碰撞检测(检查候选碰撞结果,进而确定对象是否真实发生碰撞)休眠机制:避免模拟静止的球逻辑帧与渲染帧分离分帧处理异步/多线程处理 GPU:最佳原则减少渲染纹理压缩批处理减少DrawCall(unity-Static Batching和Dynamic Batching,cocos SpriteBatchNode)减少无效/不必要绘制:屏幕外的裁剪,Flash脏矩阵算法,LOD/特效分档NGUI动静分离(UIPanel.LateUpdate的消耗)控制角色骨骼数、模型面数/顶点数降帧,并非所有场景都需要60帧(腾讯桌球游戏场景60帧,其他场景30帧;天天酷跑,在开始游戏前,FPS被限制为30,游戏开始之后FPS才为60。天天飞车的FPS为30,但是当用户一段时间不点击界面后,FPS自动降) 内存:最佳原则减少内存分配/碎片、及时释放纹理压缩-Android ETC1、iOS PVRTC 4bpp、windows DXT5对象池-PoolManager合并空闲图集UI九宫格删除不用的脚本(也会占用内存) IO:最佳原则减少/异步io资源异步/多线程加载预加载文件压缩合理规划资源合并打包,并非texturepacker打包成大图集一定好,会增加文件io时间 网络:其实也是IO的一种使用单线程——共用UI线程,通过事件/UI循环驱动;还是多线程——单独的网络线程?
单线程:由游戏循环(事件)驱动,单线程模式比使用多线程模式开发、维护简单很多,但是性能比多线程要差一些,所以在网络IO的时候,需要注意别阻塞到游戏循环。说明,如果网络IO不复杂的情况下,推荐使用该模式。
在UI线程中,别调用可能阻塞的网络函数,优先考虑非阻塞IO
这是网络开发者经常犯的错误之一。比如:做一个简单如 gethostbyname() 的调用,这个操作在小范围中不会存在任何问题,但是在有些情况中现实世界的玩家却会因此阻塞数分钟之久!
如果你在 GUI 线程中调用这样一个函数,对于用户来说,在函数阻塞时,GUI 一直都处于 frozen 或者 hanged 状态,这从用户体验的角度是绝对不允许的。
多线程:单独的网络线程,使用独立的网络线程有一个非常明显的好处,主线程可以将脏活、累活交给网络线程做使得UI更流畅,例如消息的编解码、加解密工作,这些都是非常耗时的。
但是使用多线程,给开发和维护带来一定成本,并且如果没有一定的经验写出来的网络库不那么稳定,容易出错,甚至导致游戏崩溃。
下面是几点注意事项:千万千万别在网络线程中,回调主线程(UI线程)的回调函数。而是网络线程将数据准备好,让主线程主动去取,亦或者说网络线程将网络数据作为一个事件驱动主线程去取。
当年我在用Cocos2d-x + Lua做魔法花园的手机demo时,就采用的多线程模式,最初在网络线程直接调用主线程回调函数,经常会导致莫名其妙的Crash。
因为网络线程中没有渲染所必须的opengl上下文,会导致渲染出问题而Crash。
包大小

<http://km.oa.com/group/1746/articles/show/248025?kmref=search&from_page=3&no=8&is_from_iso=1> 使用压缩格式的纹理/音频尽量不要使用System.Xml而使用较小的Mono.Xml启用Stripping来减小库的大小Unity strip level(strip by byte code)Unity3D输出APK,取消X86架构iOS Xcode strip开启 耗电
 
异常与Crash
防御式编程非法的输入中保护你的程序检查每一输入参数检查来自外部的数据/资源断言错误处理隔栏
防不胜防,不管如何防御总有失手的时候,这就需要异常捕获和上报。
异常捕获

由于很多错误并不是发生在开发工作者调试阶段,而是在用户或测试工作者使用阶段;这就需要相关代码维护工作者对于程序异常捕获收集现场信息。
异常与Crash的监控和上报,这里不介绍Bugly的使用,按照apollo或者msdk的文档接入即可,没有太多可以说的。这里主要透过Bugly介绍手游的几类异常的捕获和分析:
Unity3D C#层异常捕获:比较简单使用Application.RegisterLogCallback/Application.RegisterLogCallbackThreaded(在一个新的线程中调用委托)注册回调函数。
特别注意:保证项目中只有一个Application.RegisterLogCallback注册回调,否则后面注册的会覆盖前面注册的回调!回调函数中stackTrace参数包异常调用栈。

Android Java层异常捕获Checked Exception:Checked异常一般是不引起游戏Crash的,它又称为编译时异常,即在编译阶段被处理的异常。编译器会强制程序处理所有的Checked异常,也就是用try…catch显式的捕获并处理,因为Java认为这类异常都是可以被处理(修复)的。如果没有try…catch这个异常,则编译出错,错误提示类似于“Unhandled exception type xxxxx”。
UnChecked异常又称为运行时异常,由于没有相应的try…catch处理该异常对象,所以Java运行环境将会终止,程序将退出,也就是我们所说的Crash。
那为什么不会加在try…catch呢?无法将所有的代码都加上try…catchUnChecked异常通常都是较为严重的异常,或者说已经破坏了运行环境的。比如内存地址,即使我们try…catch住了,也不能明确知道如何处理该异常,才能保证程序接下来的运行是正确的。Uncaught异常会导致应用程序崩溃。
那么当崩溃了,我们是否可以做些什么呢,就像Application.RegisterLogCallback注册回调打印日志、上报服务器、弹窗提示用户?Java提供了一个接口给我们,可以完成这些,这就是UncaughtExceptionHandler,该接口含有一个纯虚函数:public abstract void uncaughtException (Thread thread, Throwableex)
Uncaught异常发生时会终止线程,此时,系统便会通知UncaughtExceptionHandler,告诉它被终止的线程以及对应的异常,然后便会调用uncaughtException函数。
如果该handler没有被显式设置,则会调用对应线程组的默认handler。如果我们要捕获该异常,必须实现我们自己的handler,并通过以下函数进行设置:public static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler)
特别注意:多次调用setDefaultUncaughtExceptionHandler设置handler,后面注册的会覆盖前面注册的,以最后一次为准。实现自定义的handler,只需要继承UncaughtExceptionHandler该接口,并实现uncaughtException方法即可。

在任何线程中,都可以通过setDefaultUncaughtExceptionHandler来设置handler,但在Android应用程序中,全局的Application和Activity、Service都同属于UI主线程,线程名称默认为“main”。
所以,在Application中应该为UI主线程添加UncaughtExceptionHandler,这样整个程序中的Activity、Service中出现的UncaughtException事件都可以被处理。
捕获Exception之后,我们还需要知道崩溃堆栈的信息,这样有助于我们分析崩溃的原因,查找代码的Bug。异常对象的printStackTrace方法用于打印异常的堆栈信息,根据printStackTrace方法的输出结果,我们可以找到异常的源头,并跟踪到异常一路触发的过程。

iOS 异常捕获:NSSetUncaughtExceptionHandler 用来做异常处理,引起崩溃的大多数原因如:内存访问错误,重复释放等错误就无能为力了,因为这种错误它抛出的是信号,所以必须要专门做信号处理
Android Native Crash:前面我们知道可以编写和使用C/C++原生插件,除非C++使用try...catch捕获异常,否则一般会直接crash。通过捕获信号进行处理
windows crash:同样windows提供SetUnhandledExceptionFilter函数,设置最高一级的异常处理函数,当程序出现任何未处理的异常,都会触发你设置的函数里,然后在异常处理函数中获取程序异常时的调用堆栈、内存信息、线程信息等。
适配与兼容
UI适配锚点(UIAnchor、UIWidgetAnchor属性)NGUI UIRoot统一设置缩放比例UIStretch 兼容shader兼容:例如if语句有的机型支持不好,Google nexus 6在shader中使用了if就会crash字体兼容:android复杂的环境,有的手机厂商和rom会对字体进行优化,去掉android默认字体,如果不打包字体会不现实中文字 
调试及开发工具
日志及跟踪事实证明,打印日志(printf调试法)是非常有效的方法。一个好用的日志调试,必备以下几个功能:日志面板/控制台,格式化输出冗长级别(verbosity level):ERROR、WARN、INFO、DEBUG频道(channel):按功能等进行模块划分,如网络频道只接收/显示网络模块的消息,频道建议使用枚举进行命名。日志同时会输出到日志文件日志上报 调试用绘图工具调试绘图用工具指开发及调试期间为了可视化的绘图用工具,如腾讯桌球开发调试时会使用VectrosityScripts可视化球桌的物理模型(实际碰撞线)帮助调试。这类工具可以节省大量时间及快速定位问题。通常调试用绘图工具包含:支持绘制基本图形,如直线、球体、点、坐标轴、包围盒等支持自定义配置,如颜色、粒度(线的粗细/球体半径/点的大小)等 游戏内置菜单/作弊工具在开发调试期间提供游戏进行中的一些配置选项及作弊工具,以方便调试和提高效率。例如腾讯桌球游戏中提供:游戏内物理引擎参数调整菜单;修改球杆瞄准线长度/反射线数量、修改签到奖励领取天数等作弊工具注意游戏内的所有开发调试用的工具,都需要通过编译宏开关,保证发布版本不会把工具代码包含进去。 Unity扩展Untiy引擎提供了非常强大的编辑器扩展功能,基于Unity Editor可以实现非常多的功能。公司内部、外部都有非常的开源扩展可用公司外部,如GitHub上的:UnityEditor-MiniExtensionUnity-Resource-CheckerUnityEditorHelperMissingReferencesUnityUnity3D-ExtendedEditor…公司内部:TUT、BeautyUnity、UnityDependencyBy
项目运营
自动构建

版本号——主版本号.特性版本号.修正版本号.构建版本号[构建版本号]应用分发平台升级判断基准自动构建AndroidiOS — XUPorter 公司内部接入SODA即可,建议搭建自己的构建机,开发期间每日N Build排队会死人的,另外也可以搭建自己的搭建构建平台
统计上报Tlog上报玩家转化关键步骤统计(重要)Ping统计上报游戏业务的统计上报(例如桌球球局相关的统计上报)灯塔自定义上报 运营模板配置化服务器动态下发CDN拉取图片并缓存

上线前的checklist
腾讯游戏开发者平台长按,识别二维码,加关注
经验分享丨项目实践项目孵化丨渠道发行做有梦想的游戏人-GAME AND DREAM-

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存