有没有办法unity3d官网的托管堆

1.c#的托管代码和非托管代码c#有自己的内存回收机制,所以在c#中我们可以只new,不用关心怎样delete,c#使用gc来清理内存,这部分内存就是managed memory,大部分时候我们工作于c#环境中,都是在使用托管内存,然而c#毕竟运行在c++之上,有的时候,(比如可能我们需要引入一些第三方的c++或native代码的库,在Unity3d开发中很常见)我们需要直接在c#中操纵非托管的代码,这些non-managed memory我们就需要自己去处理他们的申请和释放了, c# 中提供了一些接口,完成托管和非托管之间的转换,以及对这部分内存的操作。基本上有以下几种:2.managed mem-& un-managed mem比如在c#中调用第三方的某个c++库,库中有个函数是void func(float * data, int length).我们需要传入给data的就应该是一个非托管的代码(why?首先传入托管的内存,c#层很可能会把它gc掉,而c++还在使用,而且托管的mem它的指针地址可能会发生改变,因此直接传给c++可能拿到的地址是错误的)代码如下:using System.Runtime.InteropSfloat[] _managed_data& =... // this is the c# managed dataGCHandle unmanaged_data_handle = GCHandle.Alloc(_managed_data, GCHandleType.Pinned); //这里将标记_managed_data暂时不能被gc回收,并且固定对象的地址func(unmanaged_data_handle.AddrOfPinnedObject(),_managed_data.Length);//这里将拿到非托管内存的固定地址,传给c++unmanaged_data_handle.Free();//使用完毕后,将其handle free,这样c#可以正常gc这块内存3.un-managed mem-&managed mem,&在c++中返回一个un-managed mem给c#使用有时需要在c++中分配一块处理好的内存,然后返回给c#来使用,如c++中某个接口 int func(int** data) (注意这里要使用指针的指针,因为data是得到的结果)IntPtr unmanaged_ptr=IntPtr.Z //定义这个c#中用来接收c++返回数据的指针类型int length = func(out unmanaged_ptr&);//调用c++的函数,使unmanaged_ptr指向c++里分配的内存,注意这里用out ,才能与c++里面的**匹配。byte[] managed_data = new byte[length];Marshal.Copy(unmanaged_ptr, managed_data, 0, length);//将非托管内存拷贝成托管内存,才能在c#里面使用Marshal.FreeHGlobal(unmanaged_ptr);//释放非托管的内存4.在c#直接申请一个un-managed mem传给c++有时需要直接在c#开辟一块非托管的内存,传给c++用,这块内存同样可以在c#中用后销毁。代码如下IntPtr unmanaged_data_prt = Marshal.&AllocHGlobal(100);// 直接分配100 byte的内存func(unmanaged_data_prt);//传给c++使用Marshal.FreeHGlobal(unmanaged_data_prt);使用后销毁非托管内存此外 Marshal类里面还有很多处理非托管内存的方法。托管内存和非托管内存在c#里面可以互相自由的转化,主要通过Marshal类和GCHandle类,编程时只要注意非托管的内存一定要负责好释放就可以了。腾讯游戏分享汇:天天飞车六大研发经验_unity3d吧_百度贴吧
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&签到排名:今日本吧第个签到,本吧因你更精彩,明天继续来努力!
本吧签到人数:0成为超级会员,使用一键签到本月漏签0次!成为超级会员,赠送8张补签卡连续签到:天&&累计签到:天超级会员单次开通12个月以上,赠送连续签到卡3张
关注:34,653贴子:
腾讯游戏分享汇:天天飞车六大研发经验收藏
发布者: sea_bug | 发布时间:
00:58| 评论数: 0  序言:转型手游,问题比想象要复杂。一些问题是研发阶段就能预见的,但是有些问题上线后才发现远超出我们的想象。从端游转型做手游变化远没有想象简单  可能和公司内很多手游研发团队一样,我们也是从传统PC端游转型做手游的。 我们一度认为手游研发会比较轻松,技术上跟端游比起来相对容易, 人力上也不需要太多投入。但真正开始做之后才发现无论技术还是产品各方面远没有我们想象的简单,好在我们的核心成员都有多年的端游开发经历,不光有相应的技术和经验积累,还养成了一些好的工作习惯及方法,在遇到问题时能够及时调整和灵活应对,我想这也是我们项目研发从总体上看还比较顺利的重要原因之一吧。  最开始遇到的问题主要还是技术上的,我们没有Unity3D引擎的实际项目经验,心里面没底,不知道研发过程中会遇到哪些坑,对于iOS/Android平台编程更是一窍不通,项目进度又紧,不可能有充裕的时间自己去学习和摸索,万幸得到兄弟工作室团队的无私支持让团队少走了很多弯路,我也从他们身上学到很多移动端游戏开发的一线实战经验。  我们的研发团队规模一直都比较小, 直到项目上线也只有十来个人,不少美术同学还是兼职的。 正式运营后才深刻认识到我们的人力规模完全没法适应玩家对于游戏内容的消耗速度。在工作室和产品中心的支持下,队伍得到不小的扩充,目前是30人出头的规模,也算是一个中等规模的团队了吧,然而团队规模扩大了又会凸显出很多新的问题,比方说工作任务间的偶合,多线开发的版本管理等。  对于小团队来说,解决问题可以很直接,只要有可行的方案,可能一句话就搞定了,尤其是我们有几个人做天天飞车之前就已经磨合两三年了,配合起来非常默契。但团队规模大了之后,可能就会涉及到一些团队管理问题了,如何让大家拧成一股绳去做事情,并不是简简单单把问题指出来就可以了, 这时候制度和流程就显得尤为重要了。  早作规范早验证,避免返工。寻找表现与性能的平衡点很重要  我们的一个习惯就是一开始一定要把游戏最核心的东西、最可能遇到的技术难点,不管是玩法方面的还是表现方面的,彻底捋顺想清楚了做出Demo来验证,然后才是制定标准和量产,这能提高开发效率降低走弯路的机率。  天天飞车的Demo做的非常快大概两三周就基本完成了,包括了无限赛道的动态拼接, 碰撞检测,车辆的碰撞响应,车辆和道具动态生成等等这些最基础的功能, 而且也已经具备了最基本的玩法逻辑比如说惊险超车。虽然以现在的眼光看起来它很原始也有很多问题,但如果撇开画面表现和细节手感之类的不谈,天天飞车最最核心的东西其实就是这些。  对于客户端来说可能最首要的问题就是设备适配问题,Unity3D有很好的封装性,系统和设备的特殊性大多被隐藏起来了,所以兼容适配基本不用操心,就算后期测试时发现一些兼容问题针对性的处理下也就好了。分辨率适配也没有太大问题,业内广泛采用的NGUI界面中间件提供了一套基于锚点和等比缩放的很成熟的方案,我们只需要按照它的规范去做就行了。所以,最需要投入精力的还是性能适配问题。  按照以往做端游的经验,如果有成熟的引擎和技术方案作为基础,性能适配问题的核心其实就是订立合适的资源规范并严格执行。资源规范主要分为两类,一是数量上的,包括单帧渲染的面数及顶点数,DrawCall数,同时载入的贴图和音频容量等等。另一类是技术规格上的,如光照模式,贴图格式,音频格式等。在制定规范前,我们首先依据微信和手 Q的用户设备统计数据确定了游戏的最低配置要求,因为我们的用户就是他们中的一部份,而且很显然,游戏的配置要求越低,将来的用户量也就可能越大。另外因为没有经验不能一上来就埋头自己干,所以我们还参考了怪物大作战的一些规范,他们可以说是公司Unity3D手游的先驱,向他们致敬。  当然了,光有资源规范是不够的,程序上的基础性能优化工作还是必须得做好做足的,对于Unity3D手游来说主要有托管堆内存的合理使用,对象池,资源加载卸载管理等。  作为一款3D游戏,我们也一直在寻找游戏表现和性能的最佳平衡点。一个基本的取舍原则可能就是性价比,看性能的损耗带来的效果提升到底大不大,是不是用户比较关注的。另外在这点上程序和美术之间也经常会出现一些意见分歧,这很正常。对于程序同学来说希望客户端特别省,轻巧而快速。对于美术同学来说希望画面足够精细,能够最大程度的保留设计初衷可能是非常重要的。这个问题目前看来我们处理的还是比较好的,站在客户端程序的角度,一方面有些原则我们必须要坚持,比方说不能使用复杂的实时动态光影,后处理效果等确实与当前大多数移动设备硬件性能不相匹配的技术,另一方面我们也应该和美术同学一起为游戏的表现品质努力,比方说我们实现了一个效果自适应模块,能够根据用户的设备性能选择合适的效果等级,既能让配置较差的设备以比较流畅的帧率运行游戏,高端机型也能够表现出更精细的纹理,更真实的光照,更多的场景细节,更华丽的特效等等,避免了画面品质一刀切的情况,给了美术同学多一些的发挥空间。当然总的来讲,我们的资源规范还是比较严苛的,不得不赞叹我们的美术oa,在如此多的限制下,依然让我们的游戏有很不错的画面品质。车库高配画面低配画面端游经验用于反外挂,安全问题早作预案  相比端游而言手游面临的网络环境更严峻,我们不可能做到实时通过服务器校验玩家的每一步操作,从延迟,稳定性,流量上讲都不允许。单局的玩法逻辑大都是在客户端完成在结算时才统一上报服务器的,玩家就有可能修改一些关键数据,比方说分数,金币,Buf持续时间等等,从而获得非法收益,这就必然牵扯到令人头疼的反外挂问题。  基于以前端游的积累我们初期就预想到了这个情况,比较早地开始了对抗准备。我们的外挂对抗体系大体分为三层,第一层是客户端的防御,除了必不可少的协议加密之外,客户端对内存中的关键数据也都是密文存储的并且加上了校验码,这样通过烧饼之类的通用内存修改工具就比较难定位数据的具体位置了,而且就算真的找到了内存地址,修改之后也会导致校验失败,这样客户端就能侦测到内存数据的非法篡改。第二层是我们自己服务器的即时对抗,客户端在单局结算时上报的数据中不光有最终的结果还有一些和结果有关联的中间数据,比方说实际生成了多少金币,是否有出现某种特殊车等等,如果外挂只是修改了部份数据,服务器就可以根据校验公式检测出数据被非法篡改过了,服务端kenny以前在QQ飞车就做过类似的对抗,经验非常丰富。最后一层是互娱安全组的后校验,他们采用的检测方法和我们服务器的类似,只是不会对作弊行为进行实时处罚。  还有一个要特别注意的是,在Android平台上,Unity的C#脚本是以JIT方式运行的,apk包里的程序集dll文件很容易被Reflector等工具反编译,一旦被别有用心的人知道了客户端逻辑到底如何运作的,就可能做出一些比较逆天的外挂来。当时我们想了很多办法,一开始是做混淆,但发现执行起来不是很方便,对开发存在一定的限制。后来我们想到一个方法,我们可以对程序集dll文件进行加密,这样通用的反编译工具就打不开了,但苦于我们没有 Unity及其修改过的mono组件的源代码, 也不擅长逆向工程,我们把这个需求提给Unity的开发商,可能出于某些原因,他们也没有太积极的反馈,后来还是安全组给了我们支持,他们通过逆向工程实现了对程序集文件的加解密,之后公司开发/代理的Unity手游应该都有采用这个加密方案。崩溃上报灵活处理是解决之道  针对移动应用,MIG研发了一套叫做RQD的崩溃上报系统,MSDK对它作了统一接入,所以凡是接入了MSDK组件的移动端游戏在客户端发生崩溃时都会自动捕捉上报异常的现场信息,对分析崩溃来说最有用的可能就莫过于调用栈了。  RQD 虽然很强大,不光支持C/C++,ObjC这些原生语言的调用栈的还原,也支持Java的调用栈还原,但可惜的是Unity开发的游戏大多的异常其实发生在C#层,iOS平台还好,C#是以AOT方式预先编译成了原生代码,调用栈会被当作C语言的对待,从函数名上也能基本定位到C#中具体发生崩溃的地方。但Android平台就没这么幸运了,C#是被预先编译为IL中间代码,以JIT模式在mono虚拟机中运行的,所以一旦C#层出现异常,RQD报上来的调用栈反映的都是虚拟机自身发生了异常,无法定位异常发生的具体位置,然而Android崩溃率大大高于iOS,恰恰是需要更多关注的。  后来通过猜测和试验,我们发现其实在C#层产生异常时,Unity可以通过回调的方式把异常类型和调用栈告知应用,这正是我们需要的,但如何把这些信息上报又成了问题,因为一旦RQD捕捉到异常就会立即kill掉应用,可能应用都没有机会处理这些信息,我们把情况反馈给了RQD的研发组,但短时间内他们也很难有比较完善而系统的解决方案。后来通过和RQD开发同事不断的探讨终于找到了一种看起来简陋但却行之有效的方案:由他们提供一个发生异常时延迟kill 掉进程的定制版本,在异常发生时我们把需要额外上报的异常信息以追加的方式写入会上报的tomb文件。采用这种方法,我们所需的C#异常信息在 Android平台也能有效地通过RQD上报了,而且能在后台页面比较方便的查看。安装包容量缩减方案分享,多种方法协力显功效  天天飞车属于最早那一两批上线的微信/手Q游戏,为了尽量降低玩家进入的门槛,当时对安装包体积的要求还是蛮严格的,至少要控制到40M以下,而当时我们的安装包体积已经突破50M了。  第一步,我们还是检查梳理了一遍资源,看看有没有资源没按照规范来制作,有没有资源是冗余的,这应该是最基本的,这项工作的一部份可以通过自动化的工具来完成,利用Unity提供的编辑器扩展接口来实现还是很方便的。  我们分析过安装包的具体构成,其实还是贴图资源占比最大,所以很自然地应用了TinyPNG之类的减色工具来减少贴图的信息量,实际结果表明这个优化对决大多数贴图表现的影响都是可以接受的,仅有少量的UI贴图需要区别对待一下。  渲染中文字符所使用的矢量字体文件ttf的体积对于手游安装包来说实在是有点大,一个最基本的优化手段就是使用精简字库,但精简过渡又会导致很多字符无法正常渲染,所以最后依然会占用5M左右的空间,当时的一个想法就是可否直接把移动设备操作系统的字体文件拿来用,感谢iOS这个封闭的系统,字库很统一,这个方案确实是可行的,游戏首次运行时通过系统API直接从系统字库提取出字形信息组装成了ttf文件,但Android系统就不行了,各种系统版本字库五花八门,只能还是老实地把精简字体文件打进安装包。  还有一个大头是在音频上,在PC 端上游戏音频一般直接采用mp3/ogg等流行的压缩格式就可以了,但终端设备尤其是安卓设备很多不具备音频的硬件解码能力,CPU运算能力又相对较弱,在做性能分析的时候发现,一些中低端设备,甚至某些比较高端的设备,在音频解压这一块都有相当大的性能消耗。为了流畅的帧率我们最初不得不采用非压缩的 wav格式。但问题很明显,非压缩格式的音频文件体积太大了,因为信息量特别大,打进安装包里也缩减不了太多。后来我们采取了一个两全其美的方案,安装包里依然存放压缩格式的音频,游戏第一运行时,把音频解压成非压缩格式来播放,这块当时节省了8M多空间。  经过这些努力,我们的首发安装包体积成功的控制在了30M的水平,但需要注意的是苹果会对可执行文件加密,这会导致AppStore中的版本会比提交审核的原始包大一些.手游与省电似乎天生存在矛盾,小技巧暗藏客观效果  与普通的App相比,手游耗电确实要厉害得多,大家也都比较关注这一块,影响手机耗电的因素很多,CPU的占用率,GPU的运算量,屏幕的亮度,网络模式和通讯量等等,都会影响到电量消耗的快慢,然而手机网游恰恰是这些硬件资源的消耗大户,而且消耗的更多往往就能给用户带来更绚丽的表现,更流畅的操作体验。  一方面,我们要做好游戏的性能优化,这是最基础的,因为这样就能以更少的硬件资源消耗带来基本相同的游戏体验,另一方面,我们也可以采取一些小策略,小技巧来达到相对省电的效果,比方说我们将游戏刷新率最高限制在了30帧,因为这对于天天飞车这种类型的3D游戏的流畅体验来说基本足够了,又比如在大厅里面挂机或者暂停游戏的时候, 其实不需要保持比较高的刷新率,这时候就完全可以把帧率降下来,自然而然就获得了省电的效果。当然这只是两个很小的例子,具体游戏可以根据自身情况仔细挖掘,灵活利用,运用的好的话效果应该是相当可观的。引擎选择必须要适合自身项目,能否驾驭和掌控至关重要  对于新的手游项目来说,从一开始立项就需要考虑好,选引擎就是要适合自己的,看你的游戏类型,团队规模,还有很重要的一点是看周围环境是怎么样的,如果你选择了一款很少人用的引擎,当你需要交流、需要别人帮助的时候,你可能都找不到人。国人做事讲究天时地利人和,我觉得有一个好的交流分享环境应该算是地利之一吧。  当时我们选择Unity来开发也是有多方面考虑的。首先我们要开发的是一款中小型的,3D的,移动端游戏,需要同时发布到iOS和Android两个平台,我们的团队规模很小,Unity的应用非常广泛,有不少和我们同类型同规模的游戏的成功案例,比如一起车车车,神庙逃亡等,公司也已经有了Unity开发的且已上线的移动端游戏项目,其次Unity本身也是一款开发效率很高的引擎,它的集成开发环境非常好用,资源整合很方便,游戏逻辑主要用C#等托管语言编写,和引擎本身的实现是完全隔离开的,开发,编译效率都非常的高,改完代码,几秒钟就能看到实际跑起来的效果,手游这种特别需要快速迭代的项目再适合不过了。再次,对于3D游戏开发开说,Unity提供的功能也是相当完备的,扩展起来也很方便,可以用C/C++,ObjC,Java编写运算密集型的或者需要直接访问系统底层接口的组件以插件的形式供游戏逻辑层访问,编辑器功能也很容易扩展,此外Unity的AssetStore提供了一个很好的组件交流平台,需要什么额外功能的时候去看看,也许能避免重复发明轮子。  2D 游戏我们目前不推荐Unity来开发,毕竟这个引擎原本是面向3D游戏的,功能也主要是围绕这一块来做的,如果你只是做一个2D游戏,它不可能把你不需要的功能全部都干净的拿掉,肯定会有很多额外的开销。虽然Unity最近的新版本也逐渐在增强对2D游戏的支持,但可能还不太完善,项目案例应该也少,2D 的话我们还是推荐使用公司的自研引擎或者是应用广泛的cocos2D。  对于已上线的产品来说,在游戏玩法不断扩展,新资源不断增加的运营过程中,是否能够把消耗的增加也控制在可接受范围之内,这是很关键的。比方说,Unity引擎比较大的一个问题就是内存控制不是那么的好,不太注意的话很可能让你的内存占用超标。  一方面你自己的程序框架要合理,另一方面就取决于你对引擎的掌握程度了,如果你都不太清楚究竟是什么地方吃掉了CPU时间,什么地方占掉了内存,那可能遇到问题时你就束手无策了,这其实是一个失控的状态,对于一个已上线产品来说是很可怕的。我们不怕问题有多大,最怕的是虽然知道遇到了问题,却搞不清楚问题的具体缘由,更谈不上彻底去解决它了。Unity是一个比较封闭的引擎,少有机会去研究和修改它的源代码,所以对它的掌控,我们目前做的也并不算特别好,现在我们也是在不断的加强,看怎么去彻底的驾驭它。  作者:胡波,人物介绍:2003年开始接触游戏编程,2006年进入游戏行业,2010年加入腾讯,先后参与多款UE3次世代PC端网络游戏的开发和预研,2013年初开始移动端3D网游的研发,见证了天天飞车从萌芽到诞生及成长的整个过程,现为客户端研发负责人。
交出逆向工程做出的加密dll   --来自助手版贴吧客户端------------------打开 fuckyou360.tk 有亮点
建议排版,或者给个原文链接吧。。。。。
看了半个钟感觉靠小团队太艰巨了。
交出逆向工程做出的加密dll
登录百度帐号推荐应用
为兴趣而生,贴吧更懂你。或5006人阅读
由于最近开发游戏寻路A*使用非常频繁.所以是逻辑上的瓶颈. c#又比c++慢一倍.所以决定上c++.这样算一种优化吧.哈哈.
关于vs上的vc++.分托管和非托管模式. &托管模式就是 C++/CLI 也就说可以c++使用.net库. 和 CLR是一起的. &还有就是 非托管模式就是传统的c++直接调用win32 API.
两种方式都可以创建dll. 但是如果是和c# 连用肯定是 托管模式的更方便一些.下面就讲解下如何 unity3d 使用 CLR的C++/CLI.网上充斥着 使用非托管模式c++和unity连用的方式,这里本尊就不在阐述了..
1. vs2012 或者 vs2013.
2. unity4.3.0
首先说一下CLR项目的创建.
打开vs.然后新建. 这时候选择. CLR库项目.
之后就进入了项目.然后加入如下代码 用来测试 是否dll 被使用. a 和 b 相加返回结果.很简单的程序.
之后就是需要改一些项目的属性. 因为unity使用mono的原因.&
1.Framework3.5 是 unity的版本. 这里本人的vs是 使用fw 4.5了. 所以高了这里需要降下来.由于IDE里面没有选项所以需要打开 proj文件来修改版本.
把v4.5 改成 v3.5就ok.
2.clr也是很多版本的. unity支持 safe的clr. 所以这个也需要改.
项目上面 右键属性.
选成 safe的clr.就可以了. 然后编译出来 dll.
最后就是.把dll copy到unity里面去使用.unity需要建一个plugin的文件夹来存放dll.
放到这个文件夹里 之后.unity就可以使用了.
然后我们创建一个unity的c#脚本.来调用TSEngineCLI.
之后把这个c#脚本拖到随便一个GameObject上面. 运行一下. 会看到Console框 里面输出了3. 说明大功告成.哇嘎嘎.
好了.祝大家看的开心.编的愉快. 拜拜~...
版权声明:本文为博主原创文章,未经博主允许不得转载。
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:178163次
积分:2808
积分:2808
排名:第6743名
原创:96篇
评论:84条
(1)(2)(1)(1)(1)(1)(1)(2)(3)(5)(2)(2)(3)(6)(1)(6)(2)(2)(4)(1)(1)(1)(1)(1)(3)(2)(3)(1)(1)(1)(3)(1)(1)(8)(6)(1)(2)(21)深入浅出聊Unity3D项目优化:从Draw Calls到GC - 博客 - 伯乐在线
& 深入浅出聊Unity3D项目优化:从Draw Calls到GC
刚开始写这篇文章的时候选了一个很土的题目。。。《Unity3D优化全解析》。因为这是一篇临时起意才写的文章,而且陈述的都是既有的事实,因而给自己“文(dou)学(bi)”加工留下的余地就少了很多。但又觉得这块是不得不提的一个地方,平时见到很多人对此处也给予了忽略了事,需要时才去网上扒一些只言片语的资料。也恰逢年前,寻思着周末认真写点东西遇到节假日没准也没什么人读,所以索性就写了这篇临时的文章。题目很土,因为用了指向性很明确的“Unity3D”,让人少了遐(瞎)想的空间,同时用了“高大全”这样的构词法,也让匹夫有成为众矢之的的可能。。。所以最后还是改成了现在各位看到的题目。话不多说,下面就开始正文~正所谓“草蛇灰线,伏脉千里”。那咱们首先~~~~~~
看看优化需要从哪里着手?
匹夫印象里遇到的童靴,提Unity3D项目优化则必提DrawCall,这自然没错,但也有很不好影响。因为这会给人一个错误的认识:所谓的优化就是把DrawCall弄的比较低就对了。
对优化有这种第一印象的人不在少数,drawcall的确是一个很重要的指标,但绝非全部。为了让各位和匹夫能达成尽可能多的共识,匹夫首先介绍一下本文可能会涉及到的几个概念,之后会提出优化所涉及的三大方面:
drawcall是啥?其实就是对底层图形程序(比如:OpenGL ES)接口的调用,以在屏幕上画出东西。所以,是谁去调用这些接口呢?CPU。
fragment是啥?经常有人说vf啥的,vertex我们都知道是顶点,那fragment是啥呢?说它之前需要先说一下像素,像素各位应该都知道吧?像素是构成数码影像的基本单元呀。那fragment呢?是有可能成为像素的东西。啥叫有可能?就是最终会不会被画出来不一定,是潜在的像素。这会涉及到谁呢?GPU。
batching是啥?都知道批处理是干嘛的吧?没错,将批处理之前需要很多次调用(drawcall)的物体合并,之后只需要调用一次底层图形程序的接口就行。听上去这简直就是优化的终极方案啊!但是,理想是美好的,世界是残酷的,一些不足之后我们再细聊。
内存的分配:记住,除了Unity3D自己的内存损耗。我们可是还带着Mono呢啊,还有托管的那一套东西呢。更别说你一激动,又引入了自己的几个dll。这些都是内存开销上需要考虑到的。
好啦,文中的几个概念提前讲清楚了,其实各位也能看的出来匹夫接下来要说的匹夫关注的优化时需要注意的方面:
所以,这篇文章也会按照CPU—-&GPU—-&内存的顺序进行。
CPU的方面的优化:
上文中说了,drawcall影响的是CPU的效率,而且也是最知名的一个优化点。但是除了drawcall之外,还有哪些因素也会影响到CPU的效率呢?让我们一一列出暂时能想得到的:
物理组件(Physics)
GC(什么?GC不是处理内存问题的嘛?匹夫你不要骗我啊!不过,匹夫也要提醒一句,GC是用来处理内存的,但是是谁使用GC去处理内存的呢?)
当然,还有代码质量
DrawCalls:
前面说过了,DrawCall是CPU调用底层图形接口。比如有上千个物体,每一个的渲染都需要去调用一次底层接口,而每一次的调用CPU都需要做很多工作,那么CPU必然不堪重负。但是对于GPU来说,图形处理的工作量是一样的。所以对DrawCall的优化,主要就是为了尽量解放CPU在调用图形接口上的开销。所以针对drawcall我们主要的思路就是每个物体尽量减少渲染次数,多个物体最好一起渲染。所以,按照这个思路就有了以下几个方案:
使用Draw Call Batching,也就是描绘调用批处理。Unity在运行时可以将一些物体进行合并,从而用一个描绘调用来渲染他们。具体下面会介绍。
通过把纹理打包成图集来尽量减少材质的使用。
尽量少的使用反光啦,阴影啦之类的,因为那会使物体多次渲染。
Draw Call Batching
首先我们要先理解为何2个没有使用相同材质的物体即使使用批处理,也无法实现Draw Call数量的下降和性能上的提升。
因为被“批处理”的2个物体的网格模型需要使用相同材质的目的,在于其纹理是相同的,这样才可以实现同时渲染的目的。因而保证材质相同,是为了保证被渲染的纹理相同。
因此,为了将2个纹理不同的材质合二为一,我们就需要进行上面列出的第二步,将纹理打包成图集。具体到合二为一这种情况,就是将2个纹理合成一个纹理。这样我们就可以只用一个材质来代替之前的2个材质了。
而Draw Call Batching本身,也还会细分为2种。
Static Batching 静态批处理
看名字,猜使用的情景。
静态?那就是不动的咯。还有呢?额,听上去状态也不会改变,没有“生命”,比如山山石石,楼房校舍啥的。那和什么比较类似呢?嗯,聪明的各位一定觉得和场景的属性很像吧!所以我们的场景似乎就可以采用这种方式来减少draw call了。
那么写个定义:只要这些物体不移动,并且拥有相同的材质,静态批处理就允许引擎对任意大小的几何物体进行批处理操作来降低描绘调用。
那要如何使用静态批来减少Draw Call呢?你只需要明确指出哪些物体是静止的,并且在游戏中永远不会移动、旋转和缩放。想完成这一步,你只需要在检测器(Inspector)中将Static复选框打勾即可,如下图所示:
至于效果如何呢?
举个例子:新建4个物体,分别是Cube,Sphere, Capsule, Cylinder,它们有不同的网格模型,但是也有相同的材质(Default-Diffuse)。
首先,我们不指定它们是static的。Draw Call的次数是4次,如图:
我们现在将它们4个物体都设为static,在来运行一下:
如图,Draw Call的次数变成了1,而Saved by batching的次数变成了3。
静态批处理的好处很多,其中之一就是与下面要说的动态批处理相比,约束要少很多。所以一般推荐的是draw call的静态批处理来减少draw call的次数。那么接下来,我们就继续聊聊draw call的动态批处理。
Dynamic Batching 动态批处理
有阴就有阳,有静就有动,所以聊完了静态批处理,肯定跟着就要说说动态批处理了。首先要明确一点,Unity3D的draw call动态批处理机制是引擎自动进行的,无需像静态批处理那样手动设置static。我们举一个动态实例化prefab的例子,如果动态物体共享相同的材质,则引擎会自动对draw call优化,也就是使用批处理。首先,我们将一个cube做成prefab,然后再实例化500次,看看draw call的数量。
for(int i = 0; i & 500; i++)
cube = GameObject.Instantiate(prefab) as GameO
draw call的数量:
可以看到draw call的数量为1,而 saved by batching的数量是499。而这个过程中,我们除了实例化创建物体之外什么都没做。不错,unity3d引擎为我们自动处理了这种情况。
但是有很多童靴也遇到这种情况,就是我也是从prefab实例化创建的物体,为何我的draw call依然很高呢?这就是匹夫上文说的,draw call的动态批处理存在着很多约束。下面匹夫就演示一下,针对cube这样一个简单的物体的创建,如果稍有不慎就会造成draw call飞涨的情况吧。
我们同样是创建500个物体,不同的是其中的100个物体,每个物体的大小都不同,也就是Scale不同。
for(int i = 0; i & 500; i++)
cube = GameObject.Instantiate(prefab) as GameO
if(i / 100 == 0)
cube.transform.localScale = new Vector3(2 + i, 2 + i, 2 + i);
draw call的数量:
我们看到draw call的数量上升到了101次,而saved by batching的数量也下降到了399。各位看官可以看到,仅仅是一个简单的cube的创建,如果scale不同,竟然也不会去做批处理优化。这仅仅是动态批处理机制的一种约束,那我们总结一下动态批处理的约束,各位也许也能从中找到为何动态批处理在自己的项目中不起作用的原因:
批处理动态物体需要在每个顶点上进行一定的开销,所以动态批处理仅支持小于900顶点的网格物体。
如果你的着色器使用顶点位置,法线和UV值三种属性,那么你只能批处理300顶点以下的物体;如果你的着色器需要使用顶点位置,法线,UV0,UV1和切向量,那你只能批处理180顶点以下的物体。
不要使用缩放。分别拥有缩放大小(1,1,1) 和(2,2,2)的两个物体将不会进行批处理。
统一缩放的物体不会与非统一缩放的物体进行批处理。
使用缩放尺度(1,1,1) 和 (1,2,1)的两个物体将不会进行批处理,但是使用缩放尺度(1,2,1) 和(1,3,1)的两个物体将可以进行批处理。
使用不同材质的实例化物体(instance)将会导致批处理失败。
拥有lightmap的物体含有额外(隐藏)的材质属性,比如:lightmap的偏移和缩放系数等。所以,拥有lightmap的物体将不会进行批处理(除非他们指向lightmap的同一部分)。
多通道的shader会妨碍批处理操作。比如,几乎unity中所有的着色器在前向渲染中都支持多个光源,并为它们有效地开辟多个通道。
预设体的实例会自动地使用相同的网格模型和材质。
所以,尽量使用静态的批处理。
曾几何时,匹夫在做一个策略类游戏的时候需要在单元格上排兵布阵,而要侦测到哪个兵站在哪个格子匹夫选择使用了射线,由于士兵单位很多,而且为了精确每一帧都会执行检测,那时候CPU的负担叫一个惨不忍睹。后来匹夫果断放弃了这种做法,并且对物理组件产生了心理的阴影。
这里匹夫只提2点匹夫感觉比较重要的优化措施:
1.设置一个合适的Fixed Timestep。设置的位置如图:
那何谓“合适”呢?首先我们要搞明白Fixed Timestep和物理组件的关系。物理组件,或者说游戏中模拟各种物理效果的组件,最重要的是什么呢?计算啊。对,需要通过计算才能将真实的物理效果展现在虚拟的游戏中。那么Fixed Timestep这货就是和物理计算有关的啦。所以,若计算的频率太高,自然会影响到CPU的开销。同时,若计算频率达不到游戏设计时的要求,有会影响到功能的实现,所以如何抉择需要各位具体分析,选择一个合适的值。
2.就是不要使用网格碰撞器(mesh collider):为啥?因为实在是太复杂了。网格碰撞器利用一个网格资源并在其上构建碰撞器。对于复杂网状模型上的碰撞检测,它要比应用原型碰撞器精确的多。标记为凸起的(Convex )的网格碰撞器才能够和其他网格碰撞器发生碰撞。各位上网搜一下mesh collider的图片,自然就会明白了。我们的手机游戏自然无需这种性价比不高的东西。
当然,从性能优化的角度考虑,物理组件能少用还是少用为好。
处理内存,却让CPU受伤的GC
在CPU的部分聊GC,感觉是不是怪怪的?其实小匹夫不这么觉得,虽然GC是用来处理内存的,但的确增加的是CPU的开销。因此它的确能达到释放内存的效果,但代价更加沉重,会加重CPU的负担,因此对于GC的优化目标就是尽量少的触发GC。
首先我们要明确所谓的GC是Mono运行时的机制,而非Unity3D游戏引擎的机制,所以GC也主要是针对Mono的对象来说的,而它管理的也是Mono的托管堆。 搞清楚这一点,你也就明白了GC不是用来处理引擎的assets(纹理啦,音效啦等等)的内存释放的,因为U3D引擎也有自己的内存堆而不是和Mono一起使用所谓的托管堆。
其次我们要搞清楚什么东西会被分配到托管堆上?不错咯,就是引用类型咯。比如类的实例,字符串,数组等等。而作为int,float,包括结构体struct其实都是值类型,它们会被分配在堆栈上而非堆上。所以我们关注的对象无外乎就是类实例,字符串,数组这些了。
那么GC什么时候会触发呢?两种情况:
首先当然是我们的堆的内存不足时,会自动调用GC。
其次呢,作为编程人员,我们自己也可以手动的调用GC。
所以为了达到优化CPU的目的,我们就不能频繁的触发GC。而上文也说了GC处理的是托管堆,而不是Unity3D引擎的那些资源,所以GC的优化说白了也就是代码的优化。那么匹夫觉得有以下几点是需要注意的:
字符串连接的处理。因为将两个字符串连接的过程,其实是生成一个新的字符串的过程。而之前的旧的字符串自然而然就成为了垃圾。而作为引用类型的字符串,其空间是在堆上分配的,被弃置的旧的字符串的空间会被GC当做垃圾回收。
尽量不要使用foreach,而是使用for。foreach其实会涉及到迭代器的使用,而据传说每一次循环所产生的迭代器会带来24 Bytes的垃圾。那么循环10次就是240Bytes。
不要直接访问gameobject的tag属性。比如if (go.tag == “human”)最好换成if (go.CompareTag (“human”))。因为访问物体的tag属性会在堆上额外的分配空间。如果在循环中这么处理,留下的垃圾就可想而知了。
使用“池”,以实现空间的重复利用。
最好不用LINQ的命令,因为它们会分配临时的空间,同样也是GC收集的目标。而且我很讨厌LINQ的一点就是它有可能在某些情况下无法很好的进行AOT编译。比如“OrderBy”会生成内部的泛型类“OrderedEnumerable”。这在AOT编译时是无法进行的,因为它只是在OrderBy的方法中才使用。所以如果你使用了OrderBy,那么在IOS平台上也许会报错。
代码?脚本?
聊到代码这个话题,也许有人会觉得匹夫多此一举。因为代码质量因人而异,很难像上面提到的几点,有一个明确的评判标准。也是,公写公有理,婆写婆有理。但是匹夫这里要提到的所谓代码质量是基于一个前提的:Unity3D是用C++写的,而我们的代码是用C#作为脚本来写的,那么问题就来了~脚本和底层的交互开销是否需要考虑呢?也就是说,我们用Unity3D写游戏的“游戏脚本语言”,也就是C#是由mono运行时托管的。而功能是底层引擎的C++实现的,“游戏脚本”中的功能实现都离不开对底层代码的调用。那么这部分的开销,我们应该如何优化呢?
1.以物体的Transform组件为例,我们应该只访问一次,之后就将它的引用保留,而非每次使用都去访问。这里有人做过一个小实验,就是对比通过方法GetComponent&Transform&()获取Transform组件, 通过MonoBehavor的transform属性去取,以及保留引用之后再去访问所需要的时间:
GetComponent = 619ms
Monobehaviour = 60ms
CachedMB = 8ms
Manual Cache = 3ms
2.如上所述,最好不要频繁使用GetComponent,尤其是在循环中。
3.善于使用OnBecameVisible()和OnBecameVisible(),来控制物体的update()函数的执行以减少开销。
4.使用内建的数组,比如用Vector3.zero而不是new Vector(0, 0, 0);
5.对于方法的参数的优化:善于使用ref关键字。值类型的参数,是通过将实参的值复制到形参,来实现按值传递到方法,也就是我们通常说的按值传递。复制嘛,总会让人感觉很笨重。比如Matrix4x4这样比较复杂的值类型,如果直接复制一份新的,反而不如将值类型的引用传递给方法作为参数。
好啦,CPU的部分匹夫觉得到此就介绍的差不多了。下面就简单聊聊其实匹夫并不是十分熟悉的部分,GPU的优化。
GPU与CPU不同,所以侧重点自然也不一样。GPU的瓶颈主要存在在如下的方面:
填充率,可以简单的理解为图形处理单元每秒渲染的像素数量。
像素的复杂度,比如动态阴影,光照,复杂的shader等等
几何体的复杂度(顶点数量)
当然还有GPU的显存带宽
那么针对以上4点,其实仔细分析我们就可以发现,影响的GPU性能的无非就是2大方面,一方面是顶点数量过多,像素计算过于复杂。另一方面就是GPU的显存带宽。那么针锋相对的两方面举措也就十分明显了。
减少顶点数量,简化计算复杂度。
压缩图片,以适应显存带宽。
减少绘制的数目
那么第一个方面的优化也就是减少顶点数量,简化复杂度,具体的举措就总结如下了:
保持材质的数目尽可能少。这使得Unity更容易进行批处理。
使用纹理图集(一张大贴图里包含了很多子贴图)来代替一系列单独的小贴图。它们可以更快地被加载,具有很少的状态转换,而且批处理更友好。
如果使用了纹理图集和共享材质,使用Renderer.sharedMaterial 来代替Renderer.material 。
使用光照纹理(lightmap)而非实时灯光。
使用LOD,好处就是对那些离得远,看不清的物体的细节可以忽略。
遮挡剔除(Occlusion culling)
使用mobile版的shader。因为简单。
优化显存带宽
第二个方向呢?压缩图片,减小显存带宽的压力。
OpenGL ES 2.0使用ETC1格式压缩等等,在打包设置那里都有。
使用mipmap。
这里匹夫要着重介绍一下MipMap到底是啥。因为有人说过MipMap会占用内存呀,但为何又会优化显存带宽呢?那就不得不从MipMap是什么开始聊起。一张图其实就能解决这个疑问。
上面是一个mipmap 如何储存的例子,左边的主图伴有一系列逐层缩小的备份小图
是不是很一目了然呢?Mipmap中每一个层级的小图都是主图的一个特定比例的缩小细节的复制品。因为存了主图和它的那些缩小的复制品,所以内存占用会比之前大。但是为何又优化了显存带宽呢?因为可以根据实际情况,选择适合的小图来渲染。所以,虽然会消耗一些内存,但是为了图片渲染的质量(比压缩要好),这种方式也是推荐的。
内存的优化
既然要聊Unity3D运行时候的内存优化,那我们自然首先要知道Unity3D游戏引擎是如何分配内存的。大概可以分成三大部分:
Unity3D内部的内存
Mono的托管内存
若干我们自己引入的DLL或者第三方DLL所需要的内存。
第3类不是我们关注的重点,所以接下来我们会分别来看一下Unity3D内部内存和Mono托管内存,最后还将分析一个官网上Assetbundle的案例来说明内存的管理。
Unity3D内部内存
Unity3D的内部内存都会存放一些什么呢?各位想一想,除了用代码来驱动逻辑,一个游戏还需要什么呢?对,各种资源。所以简单总结一下Unity3D内部内存存放的东西吧:
资源:纹理、网格、音频等等
GameObject和各种组件。
引擎内部逻辑需要的内存:渲染器,物理系统,粒子系统等等
Mono托管内存
因为我们的游戏脚本是用C#写的,同时还要跨平台,所以带着一个Mono的托管环境显然必须的。那么Mono的托管内存自然就不得不放到内存的优化范畴中进行考虑。那么我们所说的Mono托管内存中存放的东西和Unity3D内部内存中存放的东西究竟有何不同呢?其实Mono的内存分配就是很传统的运行时内存的分配了:
值类型:int型啦,float型啦,结构体struct啦,bool啦之类的。它们都存放在堆栈上(注意额,不是堆所以不涉及GC)。
引用类型:其实可以狭义的理解为各种类的实例。比如游戏脚本中对游戏引擎各种控件的封装。其实很好理解,C#中肯定要有对应的类去对应游戏引擎中的控件。那么这部分就是C#中的封装。由于是在堆上分配,所以会涉及到GC。
而Mono托管堆中的那些封装的对象,除了在在Mono托管堆上分配封装类实例化之后所需要的内存之外,还会牵扯到其背后对应的游戏引擎内部控件在Unity3D内部内存上的分配。
举一个例子:
一个在.cs脚本中声明的WWW类型的对象www,Mono会在Mono托管堆上为www分配它所需要的内存。同时,这个实例对象背后的所代表的引擎资源所需要的内存也需要被分配。
一个WWW实例背后的资源:
压缩的文件
解压缩所需的缓存
解压缩之后的文件
那么下面就举一个AssetBundle的例子:
Assetbundle的内存处理
以下载Assetbundle为例子,聊一下内存的分配。匹夫从官网的手册上找到了一个使用Assetbundle的情景如下:
IEnumerator DownloadAndCache (){
// Wait for the Caching system to be ready
while (!Caching.ready)
// Load the AssetBundle file from Cache if it exists with the same version or download and store it in the cache
using(WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)){
//WWW是第1部分
if (www.error != null)
throw new Exception(&WWW download had an error:& + www.error);
AssetBundle bundle = www.assetB//AssetBundle是第2部分
if (AssetName == &&)
Instantiate(bundle.mainAsset);//实例化是第3部分
Instantiate(bundle.Load(AssetName));
// Unload the AssetBundles compressed contents to conserve memory
bundle.Unload(false);
} // memory is freed from the web stream (www.Dispose() gets called implicitly)
内存分配的三个部分匹夫已经在代码中标识了出来:
Web Stream:包括了压缩的文件,解压所需的缓存,以及解压后的文件。
AssetBundle:Web Stream中的文件的映射,或者说引用。
实例化之后的对象:就是引擎的各种资源文件了,会在内存中创建出来。
那就分别解析一下:
WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)
将压缩的文件读入内存中
创建解压所需的缓存
将文件解压,解压后的文件进入内存
关闭掉为解压创建的缓存
AssetBundle bundle = www.assetB
AssetBundle此时相当于一个桥梁,从Web Stream解压后的文件到最后实例化创建的对象之间的桥梁。
所以AssetBundle实质上是Web Stream解压后的文件中各个对象的映射。而非真实的对象。
实际的资源还存在Web Stream中,所以此时要保留Web Stream。
Instantiate(bundle.mainAsset);
通过AssetBundle获取资源,实例化对象
最后各位可能看到了官网中的这个例子使用了:
using(WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)){
这种using的用法。这种用法其实就是为了在使用完Web Stream之后,将内存释放掉的。因为WWW也继承了idispose的接口,所以可以使用using的这种用法。其实相当于最后执行了:
//删除Web Stream
www.Dispose();
OK,Web Stream被删除掉了。那还有谁呢?对Assetbundle。那么使用
//删除AssetBundle
bundle.Unload(false);
ok,写到这里就先打住啦。写的有点超了。有点赶也有点临时,日后在补充编辑。
可能感兴趣的话题
请问类似于单元格上排兵布阵的问题您是怎么解决的?
最新评论(期待您也参与评论)
关于伯乐在线博客
在这个信息爆炸的时代,人们已然被大量、快速并且简短的信息所包围。然而,我们相信:过多“快餐”式的阅读只会令人“虚胖”,缺乏实质的内涵。伯乐在线博客团队正试图以我们微薄的力量,把优秀的原创/译文分享给读者,做一个小而精的精选博客,为“快餐”添加一些“营养”元素。
新浪微博:
微信号:Jobbole
(加好友请注明来意)
– 好的话题、有启发的回复、值得信赖的圈子
– 分享和发现有价值的内容与观点
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 翻译传播优秀的外文文章
– 国内外的精选博客文章
– 专注iOS技术分享
– 专注Android技术分享
– JavaScript, HTML5, CSS
– 专注Java技术分享
– 专注Python技术分享
& 2015 伯乐在线
赞助云主机

我要回帖

更多关于 unity3d 的文章

 

随机推荐