ieee xplore怎么下载游戏备份

XploreFullyFeaturedRangerX手机游戏大全
XploreFullyFeaturedRangerX手机游戏大全
XploreFullyFeaturedRangerX手机游戏大全
九游这里为大家精选了好玩的XploreFullyFeaturedRangerX游戏大全,涵盖了XploreFullyFeaturedRangerX手机游戏、XploreFullyFeaturedRangerX单机、XploreFullyFeaturedRangerX手机网游等下载,同时还为您推荐了最新热门的XploreFullyFeaturedRangerX同类型游戏,让你在这里能够找到自己喜欢的XploreFullyFeaturedRangerX手游戏,
喜欢的小伙伴记得下载体验哦!
XploreFullyFeaturedRangerX游戏排行榜
《仙神之怒》是2017首款飞行空战修仙手游。全图无限制飞行,珍稀物品pk全掉落,千人同屏,激战仙魔界!精美的画...
《英雄文明》是一款魔幻题材的真实战争策略手游,玩家将身临浩瀚的魔幻世界,建立城市,招募军队,召唤英雄,结交盟友...
《冰火魔界》是一款欧美卡通风格的策略类游戏。玩家通过建造升级防御建筑、建造进化陷阱神像,调整主基地建筑摆放来防...
《王者军团》,强势的FPS+MOBA手游,第一人称视角畅快的枪战体验结合着激发战斗热情与竞技精神的多人在线平台...
火力全开,三界乱入,通过研发方与发行方两年的细心雕琢,一度成为年度黑马。
通过游戏细节,西游情怀,三界乱入,...
艾格拉斯历时5年,耗资过亿,《英雄战魂2》于11月1日上午10:00开启公测!
全新的英灵系统,让收集与搭配...
《王者一刀》是一款以经典战道法为主角,在古风异域大世界里纵横历险的实时交互PK手游。深度还原传奇精髓,全面解锁...
全新正版敢达手游《敢达争锋对决》来袭!
本作是一款3D真实比例敢达手游,且拥有剧情模式,实时对战和挑战等多种...
超福利公平策略PK竞技手游,五大福利,双项公平!
【福利一】新区福利,注册送元宝
全民福利,只为给您更佳的...
迪丽热巴代言正版授权次世代3D飞行手游《九州天空城3D》9月26日倾城公测!祖龙娱乐2017匠心巨作,在这东方...
招兵买马,攻城掠地。《攻城三国》是一款战斗策略游戏,在游戏中,您将扮演三国乱世中的一名诸侯,率领千军万马迎战诸...
漂浮在天空之上的魔法世界,拉普达多,最高裁决与和平的守护者,被称为“天之宰”。上一届天之宰去世300年后,拉普...
游戏类型:
游戏题材:
玩法类型:
其他类型:
XploreFullyFeaturedRangerX游戏资讯
《仙神之怒》12月14日10点新服“满面春风”火爆开启! 【暴力仙侠美学 神魔之战】《仙神之怒》是2017首款飞行空战修仙手游。全图无限制飞行,珍稀物品pk全掉落,千人同屏,激战仙魔界!精美的画面,丰富的剧情、独特的...
活动时间: 12:03:44 至
月底啦~ 小莫又来收割你们手里的花花了~ 请把你们手里的小花花送给小莫 还是老规矩, 送的花花越多, 奖池就越多哦~
参与活动...
活动时间: 10:40:00 至
在活动期间,各位友友们只需要在九游的《冰火魔界》回帖玩拼图,赢九游金币的特定帖子上,按照图片顺序拼回原图,如果答对的话,就有机会赢...
亲爱的王者们:
为了给大家带来更好的游戏体验,我们将于12月12日进行例行维护,预计维护时间09:00~11:00,预计2个小时。服务器开启时间会根据实际的修复情况提前或顺延,给您带来不便还请见谅,感谢您的理解与支持,祝您游戏愉快! ...
活动时间: 15:00:00 至
活动期间首次登录游戏的玩家可以获得5元代金券。
和阴阳西游-热血版相关的活动
登陆送12元代金券!& 参与&gt...
植物大战僵尸1
安卓平台下载
苹果平台下载天涯明月刀
狼人杀专区
球球大作战
赠送鱼丸可增加主播体重!
数据加载中...
做任务可获得丰厚奖励&p&年初的一次优化笔记,第一篇。每个版本一周,每个版本一篇已经6,7篇了。过早的优化可能并不是最好的,但是每个版本每个月都要上线测试。始终觉得对于实时多人竞技游戏来说,流畅的重要性超过画面。&/p&&p&&b&使用Unity的Profile先简单分析&/b&&/p&&ul&&li&角色更新过高,后面更详细的分析。&/li&&/ul&&figure&&img src=&/v2-c9a2afdc2588_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1708& data-rawheight=&966& class=&origin_image zh-lightbox-thumb& width=&1708& data-original=&/v2-c9a2afdc2588_r.jpg&&&/figure&&ul&&li&屏幕边缘图标和Wifi过高。优化方案:频繁设置UI文字和贴了可以优化。&/li&&/ul&&figure&&img src=&/v2-6e044d6fb8e20df32fe4_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1168& data-rawheight=&188& class=&origin_image zh-lightbox-thumb& width=&1168& data-original=&/v2-6e044d6fb8e20df32fe4_r.jpg&&&/figure&&ul&&li&放技能开销非常高,每个子弹有独立弹道逻辑,技能释放频率比价高。优化方案:类对象使用内存池,delegate优化减少gc,更完善的预加载机制,逻辑优化等等。CMT的timer使用内存池。&/li&&/ul&&figure&&img src=&/v2-b92cd7b35f7bd8dcdef99fb_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&964& data-rawheight=&340& class=&origin_image zh-lightbox-thumb& width=&964& data-original=&/v2-b92cd7b35f7bd8dcdef99fb_r.jpg&&&/figure&&figure&&img src=&/v2-ce3b112b2a5d9ec3ca4284_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1154& data-rawheight=&404& class=&origin_image zh-lightbox-thumb& width=&1154& data-original=&/v2-ce3b112b2a5d9ec3ca4284_r.jpg&&&/figure&&ul&&li&部分非技能的CMT没有预加载。&/li&&/ul&&figure&&img src=&/v2-fd6efd2d5744d0bcc1e35e_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&874& data-rawheight=&136& class=&origin_image zh-lightbox-thumb& width=&874& data-original=&/v2-fd6efd2d5744d0bcc1e35e_r.jpg&&&/figure&&ul&&li&召唤物没有预加载。&/li&&/ul&&figure&&img src=&/v2-6e78c0fa222b94454acd334b3e6fa3d4_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&872& data-rawheight=&114& class=&origin_image zh-lightbox-thumb& width=&872& data-original=&/v2-6e78c0fa222b94454acd334b3e6fa3d4_r.jpg&&&/figure&&ul&&li&部分道具有些没有预加载。&/li&&/ul&&figure&&img src=&/v2-98e1f5fae0f82d303f271_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1100& data-rawheight=&202& class=&origin_image zh-lightbox-thumb& width=&1100& data-original=&/v2-98e1f5fae0f82d303f271_r.jpg&&&/figure&&ul&&li&消息处理非常卡。解决方案:使用类对象内存池,优化序列化方式不使用protobuff的。&/li&&/ul&&figure&&img src=&/v2-535a722fa8e03c5e599c2_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1188& data-rawheight=&184& class=&origin_image zh-lightbox-thumb& width=&1188& data-original=&/v2-535a722fa8e03c5e599c2_r.jpg&&&/figure&&ul&&li&帧操作增减人会卡。解决方案:推测玩家行为预加载。&/li&&/ul&&figure&&img src=&/v2-0effc5b565cf_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1192& data-rawheight=&428& class=&origin_image zh-lightbox-thumb& width=&1192& data-original=&/v2-0effc5b565cf_r.jpg&&&/figure&&ul&&li&飞行道具开销很高,特别是Deactive上。解决方案:可见性使用layer,使用内存池&/li&&/ul&&figure&&img src=&/v2-0effc5b565cf_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1192& data-rawheight=&428& class=&origin_image zh-lightbox-thumb& width=&1192& data-original=&/v2-0effc5b565cf_r.jpg&&&/figure&&ul&&li&地图脚本开销较高。&/li&&/ul&&figure&&img src=&/v2-535ceb012edf93b98de5fc8eccd7e66a_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1374& data-rawheight=&520& class=&origin_image zh-lightbox-thumb& width=&1374& data-original=&/v2-535ceb012edf93b98de5fc8eccd7e66a_r.jpg&&&/figure&&ul&&li&Camera.main直接调用开销较高。解决方案:因为Unity会遍历所有的Camera,是一个线性时间。在Update中调用的话,缓存一下main&br&
camera。&/li&&/ul&&p&&br&&/p&&p&&b&使用Unity的FrameDebugger简单分析&/b&&/p&&ul&&li&俯视角开启了天空球渲染,其实没必要&/li&&/ul&&figure&&img src=&/v2-0e0f64abae42f_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&566& data-rawheight=&352& class=&origin_image zh-lightbox-thumb& width=&566& data-original=&/v2-0e0f64abae42f_r.jpg&&&/figure&&p&&br&&/p&&p&&b&使用Unity MemoryProfiler分析内存占用和内存泄漏&/b&&/p&&p&通过memoryprofile抓帧分析,内存占用不高,但是RenderTexture占用非常高63.5M&/p&&ul&&li&大厅场景RenderTexture 63.5m。解决方案:因为使用了景深,扰动,屏幕校色,实时阴影,等后期处理效果所致,我们根据玩家手机配置做了自适应和限定,并且再图形配置选项允许玩家做一些调整。景深开销非常高因此关闭了实时景深效果使用其他方法代替。&/li&&li&战斗场景Texture 33.7m Mesh 14.9m animationclip 3.7m Audiomanager 4.1M。解决方案:贴图压缩,使用shader减少换色贴图,修正一些贴图引用不释放导致的内存泄漏。&/li&&li&LockstepMnanager.FixedUpdate函数 每帧分配10kb以上内存。解决方案:各种GC相关的优化。&/li&&li&SceneLoader.update函数(315 bytes) MainLoop.Update函数(202bytes), NetManager.update函数(186bs) CharacterActor.LateUpdate函数(392 bytes)皆为每帧都分配。GC的分配每帧超过200b就算大了。解决方案:各种内存优化,消息使用内存池等等。&/li&&li&MainLoop.OnGUI函数每帧分配内存 300b。解决方案:删掉空函数。&/li&&li&大厅场景Camera.Render cpu等待gnu渲染,造成时间帧率抖动 25ms-& 43ms 体现为Camera.Render self时间大幅变动 (优化方法 降低场景面数,拆分Gm_Cabin模型)&/li&&li&Loading贴图存在伪内存泄漏,因为图集和按钮放在一起了被按钮引用。&/li&&li&Unity技术支持提供了脚本插件可以分析代码中所有静态的引用。发现是lua的LuaScriptMgr中会对一些贴图有引用,需要注意使用Lua导致的内存问题。&/li&&/ul&&p&&br&&/p&&p&&b&使用XCode做详尽的性能优化&/b& &/p&&p&XCode也可以做GPU的性能分析。编译xcode工程需要Development版,启动,GUP的优化点需要在Run的面板里设置成metal否则有些堆栈看不到。点FPS,然后点相机Capture一帧,可以看到帧率,CPUGPU的占比,渲染时每个函数的开销。点Shader里面的看更详细堆栈,可以看到每个shader的每行代码的开销占比。&/p&&ul&&li&这个版本大厅很简单但是非常非常卡,看到UI和景深开销占比比较高,UI占了25,景深占了30。看到UI费是采样一张贴图占了50但是这张贴图并不大,景深费是因为shader里lerp了很多次导致开销很高。在FrameDebugger里可以看到UI最后渲染了一个全屏的面,可以看到每次渲染了占用的时间比,这个面实际没用。关闭了这个UI的全屏面,关了景深和后处理,基本就没开销了,连UI的渲染也只有2ms了因为景深把总线带宽给占满了,其中最费的还是景深效果。&/li&&/ul&&figure&&img src=&/v2-2c3a023cecb20d34bd17c_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&960& data-rawheight=&310& class=&origin_image zh-lightbox-thumb& width=&960& data-original=&/v2-2c3a023cecb20d34bd17c_r.jpg&&&/figure&&p&渲染线程开销也较高&/p&&figure&&img src=&/v2-fa235fc553ea1d667aaa6_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1046& data-rawheight=&370& class=&origin_image zh-lightbox-thumb& width=&1046& data-original=&/v2-fa235fc553ea1d667aaa6_r.jpg&&&/figure&&ul&&li&版本机出的版本在XCode中会报错APPLE MACH-O Linker Error,无法做性能分析。解决方法:改成il2cpp了。&/li&&li&修复字符串拼接的量级,使用StringBuilder等。&/li&&li&OnGUI,FixedUpdate,Update等空函数也会有gc开销,因为会产生从C++到C#层调用的开销。最好都去掉。&/li&&li&FMOD音效模块开销过高。我们没使用官方的FMOD使用了FMODStudio,测试版资源为了效果和开发方便使用了一些事件内的效果器。优化后音效占用10-15%。&/li&&/ul&&figure&&img src=&/v2-b9e453c924ca085f6c5aad8ad8107883_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&686& data-rawheight=&324& class=&origin_image zh-lightbox-thumb& width=&686& data-original=&/v2-b9e453c924ca085f6c5aad8ad8107883_r.jpg&&&/figure&&ul&&li&动画开销较高,使用了动作融合,动画状态机较复杂。解决方案:不可见的角色不更新动画,一些顶点受骨骼影响数可以设置很小,小怪等简单角色使用Animation,动画的一些优化等。&/li&&/ul&&figure&&img src=&/v2-e021d2b5e82b7af8cced22ae_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1282& data-rawheight=&298& class=&origin_image zh-lightbox-thumb& width=&1282& data-original=&/v2-e021d2b5e82b7af8cced22ae_r.jpg&&&/figure&&ul&&li&UI,粒子,动作开销较高,并且多线程开销也高。&/li&&/ul&&figure&&img src=&/v2-cd6adf1b65cf2e5623a2_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1008& data-rawheight=&446& class=&origin_image zh-lightbox-thumb& width=&1008& data-original=&/v2-cd6adf1b65cf2e5623a2_r.jpg&&&/figure&&ul&&li&可以看到每个模块开销比较平均,已经做了很多优化了没有明显瓶颈,只能一个模块一个模块的抠了&/li&&/ul&&figure&&img src=&/v2-b3c98ad7e776c6323aca985d422d4f42_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1046& data-rawheight=&602& class=&origin_image zh-lightbox-thumb& width=&1046& data-original=&/v2-b3c98ad7e776c6323aca985d422d4f42_r.jpg&&&/figure&&p&&br&&/p&&p&&b&其他一些测试以及和Unity官方支持的问答:&/b&&/p&&ul&&li&Unity的物理比射线省,因为会优化做空间划分,只计算较小范围。Navmesh最优化。&/li&&li&迷雾和可以性检测可见性预缓存,预缓存分块加载,迷雾进行分块划分优化,分担计算量到多帧执行等等&/li&&li&Resource目录资源多会导致Unity进游戏时间特别长,因为会把Resource下所有资源做一次检索,导致启动慢。建议使用Assertbundle,也支持压缩。另外提升进入游戏速度的就是,第一个场景足够简单足够小。解决方案:开发了热更的资源管理,每个资源有一个唯一ID,可以存在Resource下也可以存在Assertbundle中。&/li&&li&Assetbundle的新算法是按Chunk加载,不会把整个Assertbundle都加载进来但是压缩比小,压缩的时候要选择ChunkBase。如果bundle加载好之后资源的引用时放在bundle上,如果a界面打开bundle包读取贴图,a界面关闭则贴图会释放,如果a没关闭,b界面读取贴图b界面会对应到同一张贴图上。&/li&&li&建议bundle放在StreamingAssets目录下。&/li&&li&Profile分析建议:Overhead,是Unity没统计到的时间,用总计时间减去剩余的时间,一般包括C++到C#层的调用开销,场景复杂度,垂直同步等。大厅因为Overhead卡是因为CPU在等GPU,建议用XCode看。&/li&&li&通过Profile.BeginSample自己插代码对怀疑的代码进行内存和性能调试。&/li&&li&Unity5.3.5p8修改了Foreach装箱拆箱导致的内存开销。但是没有修改mono的gc。实际测试可以用Foreach了。&/li&&li&部分Android设备总线太烂导致GC会特别卡&/li&&li&OnGUI,FixedUpdate,Update等空函数也会有gc开销,因为会产生从C++到C#层调用的开销。最好都去掉&/li&&li&部分3G的Android设备,实际可用内存可能只有500M。Unity启动时最高能占到80M,一般在40-50M&/li&&li&unload切场景自动调,gc也会自动调&/li&&li&编辑器下看到的资源引用计数不准确,得看真机。实际测试的时候老发现很多资源释放不掉,但是到真机测试发现实际已经释放掉了,因为编辑器模式会缓存一些资源,所有的测试都应该以真机为准。&/li&&li&lua导致gc太多,没有比较好的解决办法,把lua的代码放到C# 层&/li&&li&IOS上如果限45帧对IOS来说就是限30帧,低于30帧才是真实帧率&/li&&li&CPU优化:ulua的计时器开销很大。其中PreloadManager是Unity的各个模块的预加载。&/li&&li&GPU优化:Mask会很费。&/li&&/ul&&p&&/p&&p&&/p&&p&&/p&
年初的一次优化笔记,第一篇。每个版本一周,每个版本一篇已经6,7篇了。过早的优化可能并不是最好的,但是每个版本每个月都要上线测试。始终觉得对于实时多人竞技游戏来说,流畅的重要性超过画面。使用Unity的Profile先简单分析角色更新过高,后面更详细的…
&figure&&img src=&/v2-57fdb044e98a956f19ff07a3b6ec9e54_b.jpg& data-rawwidth=&1200& data-rawheight=&676& class=&origin_image zh-lightbox-thumb& width=&1200& data-original=&/v2-57fdb044e98a956f19ff07a3b6ec9e54_r.jpg&&&/figure&&p&上次和大家分享了&a href=&/?target=http%3A///p/b1& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Unity项目中的资源管理&i class=&icon-external&&&/i&&/a&主要讲资源配置以及资源配置工具,Unity资源配置在资源管理中处于基础地位,影响资源的增长速率以及量级。通过合理的资源配置,可以承载更多的资源,丰富游戏的内容。今天主要分享运行时的资源管理,探讨如何妥善的管理资源以达到内存与性能兼顾。从资源介绍开始,分析加载接口与对象池设计,然后讨论资源内容分级,最后分享一款轻量级内存Profile工具。&/p&&h2&&b&内存与程序稳定性&/b&&/h2&&p&iPhone 6& iPhone 6P只有1G的内存,而这两个机型在iOS平台上的市场份额超过40%。如果使用超量的内存游戏将闪退,这会带来极差的游戏体验。想象在进行激烈的战斗的时候,由于加载了更多的特效和模型,游戏突然闪退了。或许游戏有一套不错的断线重连机制,你还能回到战场。但基本上来说你很难获得这场战斗的胜利,这一个非常差的游戏体验。&/p&&p&同时在iPhone 6S以上的机型又有2G的内存可以使用,只要性能没有问题,完全可以承载更多的内容(资源)。在制作了过量了资源的情况下,如何妥善的管理资源是一个较大的挑战。一个项目一百多号人参与制作,如何协调工作,规整制作内容是一个头疼的问题。&/p&&p&&b&合理的资源管理方案兼顾性能与内存,提供一个稳定流畅的游戏环境。&/b&&/p&&h2&&b&Unity资源介绍&/b&&/h2&&p&在做资源管理之前,首先我们要对资源有足够的了解,这样可以方便展开之后的工作。Unity官方已经有一篇非常精彩的文章来介绍Unity资源&a href=&/?target=https%3A///cn/learn/tutorials/temas/best-practices/assets-objects-and-serialization%3Fplaylist%3D30089& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《Assets, Objects and serialization》&i class=&icon-external&&&/i&&/a&。&/p&&p&&br&&/p&&blockquote&An Asset is a file on disk, stored in the Assets folder of a Unity project. For example, texture files, material files and FBX files are all Assets. Some Assets contain data in formats native to Unity, such as materials. Other Assets need to be processed into native formats, such as FBX files.&br&&br&A UnityEngine.Object, or Object with a capitalized 'O', is a set of serialized data collectively describing a specific instance of a resource. This can be any type of resource which the Unity Engine uses, such as a mesh, a sprite, an AudioClip or an AnimationClip. All Objects are subclasses of the UnityEngine.Object base class.&/blockquote&&p&Asset是指在Assets目录下的所有文件,在工程里面每个Asset会有一个对应的Meta文件,Meta文件用于描述Asset在工程里面的格式,之前分享的贴图配置也是通过修改Meta文件来达成。一个Asset包含一个或多个Object,这里的Obejct可以直接包含数据,也可以表示引用了其他Asset文件下的Object。&/p&&p&GameObject是一个特殊类型的Obejct,通常我们通过把一系列的Assets组装成Prefab(GameObject)来制作资源,Unity通过依赖关系加载所有资源。在加载一个GameObject之后,我们通常需要实例化GameObject。大部分Asset资源是共用的,实例化过程中Unity并不会复制这些共用资源,而是复制那些可修改的不可复用的数据,比如MonoBehaviour上的数据。当然我们也可以直接加载Asset资源来使用,比如直接加载一张贴图,放在一个UI面板上展示。通过依赖加载的贴图和直接加载的贴图是同一份贴图,Unity内部帮我们解决了资源重复的问题,可以放心使用。&/p&&h2&&b&Resources&/b&&/h2&&blockquote&The Assets and Objects in all folders named &Resources& are combined into a single serialized file when a project is built.&/blockquote&&p&Resources目录下所有的资源,都会被打包且可以通过Resources接口加载,加载路径为Resources目录的相对路径。支持同步与异步两个加载接口,支持单对象的UnloadAsset,还有一个清理未被引用的资源的接口。这里UnloadAsset不能卸载GameObject和Component,而且是强制卸载,即使外部仍然在使用这个资源。UnloadUnusedAssets则是一个安全的接口,只清理那些不再被引用的资源,不过这个接口开销较大会引起卡顿。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public static T Load&T&(string path) where T : O
public static ResourceRequest LoadAsync&T&(string path) where T : O
public static void UnloadAsset(Object assetToUnload);
public static AsyncOperation UnloadUnusedAssets();
&/code&&/pre&&/div&&h2&&b&AssetBundles&/b&&/h2&&blockquote&An AssetBundle is an archive file containing platform specific Assets (Models, Textures, Prefabs, Audio clips, and even entire Scenes) that can be loaded at runtime.&/blockquote&&p&通常推荐使用AssetBundle来加载资源,使用AssetBundle可以按更小的包来管理资源、更新资源,同时还可以加快游戏启动速度。更深入的内容可以看看Unity官方的文章&a href=&/?target=https%3A///learn/tutorials/temas/best-practices/resources-folder& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《The Resources folder》&i class=&icon-external&&&/i&&/a&。&/p&&p&加载AssetBundle需要我们自己去维护依赖关系,对比起Resources来说更加麻烦。通常在开发的时候使用Resources加载,而在发布版本使用AssetBundle。这里需要实现自己的加载器来满足两套资源的切换。&/p&&h2&&b&资源管理器&/b&&/h2&&ul&&li&统一Resources和AssetBundles加载&/li&&li&类似的加载接口设计,包括同步与异步&/li&&li&强引用计数管理,Load与Unload匹配&/li&&li&支持按优先级加载资源&/li&&li&支持配置系统开销,异步加载开销&/li&&/ul&&p&对外实现为静态接口,正常情况下支持Editor运行时与非运行时,运行时不管在PC还是手机都支持Resources与AssetBundles无缝切换。所有的加载路径参数统一为Resources目录相对路径且不包含扩展名,这里要求在同一目录不要有同名文件(仅扩展名不一样)。按类型匹配资源是较烦琐的工作,而且对于Object基类加载,无法匹配到正确的资源。&/p&&p&异步接口定义一个自己Request类返回,除了原有的ResourceRequest数据,这里新增一个打断属性。当不再持有这个对象的时候设置打断属性来中断加载。同时这里还支持配置回调接口,这样不需要每次更新去查询状态,资源管理器在异步加载完成后执行回调接口。&/p&&p&异步加载接口增加优先级参数,优先加载高优先级的对象。自己维护一个优先级列表,并发起一定数量的异步加载请求,对于在队列中被打断的资源则可以节省一次资源价值请求。&/p&&p&然后还要关注异步加载的开销,避免异步加载占用太多的主线程时间。Unity可以通过配置&a href=&/?target=https%3A///ScriptReference/Application-backgroundLoadingPriority.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Application.backgroundLoadingPriority&i class=&icon-external&&&/i&&/a&来约束开销。如果要求游戏跑30帧的话,建议配置为Normal即可,在过场景的情况下,则配置成High来提高加载速度。&/p&&ul&&li&ThreadPriority.Low - 2&/li&&li&ThreadPriority.BelowNormal - 4&/li&&li&ThreadPriority.Normal - 10&/li&&li&ThreadPriority.High - 50ms.&/li&&/ul&&p&由于实现了自己Request,所以这里也要实现自己的时间片管理器。实例化对象与回调接口的开销都是不可预期的,我们配置一个每帧最大执行时间做平滑。&/p&&p&最后讨论下资源卸载策略,实时卸载资源导致资源反复加载,引起游戏卡顿。通常会缓存一定数量的资源来改善体验,由于只有调用了UnloadUnusedAsset才会真正清理资源,所以一般情况下会一直持有资源,然后根据未使用的资源数量情况触发统一的UnloadUnusedAsset,这时候资源才会被真正释放。由于我们使用了强引用计数管理,所以在清理的时候通过对引用计数的判断就可以正确的清理资源。特别对于使用AssetBundle加载资源的情况,错误的管理可能会导致资源重复加载,浪费内存。&/p&&h2&&b&资源对象池&/b&&/h2&&p&资源加载器负责加载、卸载资源,同时缓存资源,这里的资源对象池特指&b&GameObject资源池&/b&。GameObject资源通常带有自己的数据,在加载的时候需要实例化一份以便使用。实例化GameObject是一个开销较大的操作,同时也会带来较高的GC Alloc(内存分配)。资源对象池就是一个GameObject对象池用于缓存实例化的GameObject对象。&/p&&p&资源对象池在使用上要注意GameObject对象的可复用性,开始的时候加载一个预制体(Prefab)是一个干净的数据。外部逻辑会修改GameObject上的数据、添加新的组件,之后这个对象会入池。设计上如果一个对象需要使用对象池的复用功能,逻辑需要保证这个GameObject是可复用的,这并不是一件容易的事情。把状态还原重置本身就有一定的开销,如果实例化一个对象的成本低于重置数据的开销,那就不需要对象池,每次重新实例化即可。&/p&&p&同时在对象入池的时候还需要做一项工作是让对象不可见,与销毁一个对象(对象入池)在这里保持行为一致。有两个常见的做法,一个是SetActive(false),还有一个做法是把对象移出摄像机。对于对象数特别多的对象修改坐标的开销较大,对于组件较多的对象修改激活状态的开销可能会更大。这里提供了三种入池行为,InActive、InVisible、Destroy用于处理上面讨论的情况。&/p&&p&资源对象池封装实现自己的一个Spawn接口,表示生成一个对象,然后对应的一个Despawn接口用于销毁对象。这里还提供了异步的SpawnAsync接口用于异步加载以及错帧实例化,使游戏体验更加平滑。对于Spawn接口提供带初始坐标的实例化接口与Instantiate保持一致,提供初始坐标减少坐标次数,一般来说可以得到5%-10%的性能提升。对于一些拖尾特效,正确的坐标也可以避免特效拉一条从原点到当前位置的长线。&/p&&p&最后讨论下资源池的缓存策略,通常资源池里面存在两种情况的资源。一种外部还存在相同的对象在使用,另一种则是所有的对象都在资源池。对于所有对象都在资源池的对象,可以认为是不使用资源根据时间淘汰。对于外部存在引用的情况,增加其权重值但还是会按时间来淘汰。存在部分类型资源会有较多的实例而部分资源只有一两个实例,这里做资源池总上限的约束而不做单类型数量约束。在激烈的战斗场景下对象数量会远远高于平时,过小的资源池上限会导致卡顿,过大的资源池上限会导致内存过高。这里增加一个资源池下限,当资源池对象数高于这个数目的时候执行按时间清理操作,然后配置一个较高的资源池上限而不用当心资源池一直占用过高的内存,得到一个性能与内存兼顾的结果。&/p&&h2&&b&资源内容分级&/b&&/h2&&p&当资源的使用上超标时,也可以通过简单的调整一些参数来开关这些对内存有较大影响的对象。&br&观察iPhone机型内存可以发现内存有较大的跨越,在2G机型可以承载游戏内容的情况下,1G机型承载不了这么多的内容。通过对资源内容进行分级,来稳定1G机型的内存使用,避免闪退。&/p&&p&&br&&/p&&figure&&img src=&/v2-27aa33ef45ade1d14fb2ce_b.png& data-rawwidth=&488& data-rawheight=&223& class=&origin_image zh-lightbox-thumb& width=&488& data-original=&/v2-27aa33ef45ade1d14fb2ce_r.png&&&/figure&&p&&br&&/p&&h2&&b&常见的分级内容&/b&&/h2&&ul&&li&屏幕后期效果&/li&&li&高低材质&/li&&li&贴图大小减半&/li&&/ul&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&void OnRenderImage(RenderTexture source, RenderTexture destination)
&/code&&/pre&&/div&&p&如果通过上面的OnRenderImage实现屏幕后期效果,这里的source和destination贴图都是Unity申请的与分辨率直接挂钩。在1080P的分辨率情况下,会消耗掉50M左右的内存。所以一个比较好的做法是在低内存机型上关掉这个效果。&/p&&p&高级的材质使用更多的顶点数据与贴图,比如法线贴图、通道贴图。低级材质使用更少的贴图,通过高低材质的切换可以减少贴图到达节省内存的效果。Mesh这里也是同理,如果不需要法线则不需要有法线的顶点数据。然后缩减贴图大小也是一个不错的方法,不过保存两份贴图会使包文件变大。&/p&&h2&&b&配置资源管理参数&/b&&/h2&&p&前面我们为了得到一个较好的性能做了较多的资源缓存工作,针对不同的内存配置不同的参数达到优化内存的目的。内存不够带来的体验是游戏直接闪退,所以这里认为游戏稳定性的优先级高于游戏卡顿。&/p&&p&这里主要配置Assets资源缓存数量,资源池的上限与下限,还有一些资源清理时间间隔的参数配置。这些数据可以方便的修改,通常经过一系列的压力测试可以得到一个安全配置参数。后期如果增长导致内存不够,则可以通过修改配置参数来达到稳定游戏的目的。&/p&&h2&&b&简洁的内存管理机制&/b&&/h2&&p&想要解决内存闪退,了解闪退时的内存使用情况是很有必要的。在游戏运行过程中,我们可以记录内存使用情况。同时可以对资源类型进行分类,了解细节。Unity的Profile工具虽然非常方便,功能也足够强悍。但是没有数据落地,而且采样占用额外的内存。这里自己实现了一个简易的内存Profile工具,支持数据落地方便对比,同时不占用过多的额外内存。&/p&&p&在了解到闪退时的内存情况后,我们可以很容易就了解是在什么样的情况下内存会不够用。有哪些地方的内存使用超标,是否有可以优化的余地。极限情况下最低内存使用量。&/p&&h2&&b&内存Profile工具&/b&&/h2&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&Resources.FindObjectsOfTypeAll(Type type);
Profiler.GetRuntimeMemorySize(UnityEngine.Object o);
Profiler.GetMonoHeapSize();
Profiler.GetTotalAllocatedMemory();
&/code&&/pre&&/div&&p&通过Resources.FindObjectsOfTypeAll获取当前所有的对象,通过Profiler.GetRuntimeMemorySize计算每个Object的内存大小,通过Object.name可以获取对象的名字。了解了这些信息可以实现一个简洁的内存Profile工具,对比起Unity提供的Profile工具,自己实现工具可以比较方便的做一些数据落地以及自动采样的过程。&/p&&p&同时这个Profile工具还和自己实现的资源管理器进行了整合,可以记录当前的Assets数量,GameObject数量、缓存数量,以及引用计数为零的对象数量。这些额外的数据有较大的参考价值也可以直接记录,方便做后续的对比以及观察数据趋势。同时Profile工具还支持导出资源列表,之前提到我们的加载接口是通过路径加载做强引用计数管理。这里可以输出每个资源的路径与引用计数,可以定位资源泄露,排查资源残留的情况。&/p&&p&数据以文本的形式记录,支持自动采集上报,之后可以对这些数据做图形化显示、分析。每次跑游戏都可以得到一份数据报告,对比数据报告可以对客户端内存使用趋势有一定的认识。避免出现内存不够导致游戏闪退的情况。&/p&&figure&&img src=&/v2-ef9c0e41a3ed5ae9c4dd2a_b.png& data-rawwidth=&1153& data-rawheight=&496& class=&origin_image zh-lightbox-thumb& width=&1153& data-original=&/v2-ef9c0e41a3ed5ae9c4dd2a_r.png&&&/figure&&p&上图是内存Profile工具在PC上采样的结果,这里对数据进行了分类,按类型和使用场景分类。通过这些数据可以得出一些指标,比如贴图不能超过50M,Mesh不能超过20M。不同场景下的资源使用情况是不同的,这里做的工作就是把50M分配给各个模块。这样做的一个好处是能找到一个人负责,同时这个人又是对这个模块最熟悉的。&/p&&h2&&b&兼顾内存与性能&/b&&/h2&&ul&&li&Android 高内存,低CPU,低I/O&/li&&li&IOS 低内存,高CPU,高I/O&/li&&/ul&&p&资源加载是一项非常慢的操作,如果所有的资源都实时释放,那下次加载资源带来的卡顿也会带来较差的游戏体验。由于iPhone机型内存少,加载快,可以做实时释放策略。对于Android机型内存多,加载慢,可以做预加载策略。同时还可以做带权重的资源缓存策略,资源缓存是由资源的最后使用时间和加载时间得到一个权重,优先释放加载快不经常使用的资源。这样可以在内存和性能上得到一个较好的照顾。同时在IOS上会有内存的Warning警告,当触发警告的时可以做强制性清理,避免游戏闪退。&/p&&p&[完 Carber ]&/p&&ul&&li&本文首发于&a href=&/?target=http%3A//mp./s/VdKgKxLTl4yW2awCfLHyUQ& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&西山居技术中心公众号&i class=&icon-external&&&/i&&/a&&/li&&/ul&
上次和大家分享了主要讲资源配置以及资源配置工具,Unity资源配置在资源管理中处于基础地位,影响资源的增长速率以及量级。通过合理的资源配置,可以承载更多的资源,丰富游戏的内容。今天主要分享运行时的资源管理,探讨如何妥善的管…
&h2&0. 写在之前&/h2&&p&本来创业之后,博客都计划整理成分量比较足、沉淀比较够的内容来做,但在游戏开发过程中,也不断踩了Unity的很多小坑。它们不足以整理成条理清晰、内容丰富的博客文章,但是填坑过程又花费了半天到一天的时间,如果记录下来给踩到同样坑的同学,也许就可以省去那么一些时间。于是决定新开一个小的笔记系列——Unity填坑笔记,我简单记录,您快速查看。&/p&&h2&1. 起因&/h2&&p&目前在做大世界的动态加载,加载的时候有明显的顿卡,于是通过Profile来查顿卡的原因,发现有Shader.Parse的调用,占用时间非常高,中低配机器上大约200ms+。&br&于是做了Shader的预编译,提前WarmUp用到的Shader,再次打包测试,依然有Shader.Parse的调用,通过Sample内存的差异来看编译了哪些Shader,其中有一大部分是Standard。&/p&&figure&&img src=&/v2-8bdb5e7f06fd9dc1cb2a1e4401eab9ca_b.png& data-rawwidth=&1279& data-rawheight=&434& class=&origin_image zh-lightbox-thumb& width=&1279& data-original=&/v2-8bdb5e7f06fd9dc1cb2a1e4401eab9ca_r.png&&&/figure&&br&&br&内存中有Standard的Shader&br&&h2&2. 原因&/h2&&p&通过Profile右侧的Referenced By,可以查看当前的Asset被哪个东西引用,看了一下发现,都是被FBX引用了,导入的FBX文件中,都使用了Default-Material,而这个Default-Material是使用的Standard Shader。&/p&&figure&&img src=&/v2-809f3de59fc5be2e858efaef65cff231_b.png& data-rawwidth=&1440& data-rawheight=&803& class=&origin_image zh-lightbox-thumb& width=&1440& data-original=&/v2-809f3de59fc5be2e858efaef65cff231_r.png&&&/figure&&br&&br&FBX使用的Default-Material&br&&h2&3. 解决方法&/h2&&p&这个Default-Material并不能被修改,于是我搜索了半天,找到一个插件号称可以支持替换Default-Material,够买来看了下,用的是在AssetPostprocessor中OnAssignMaterialModel来替换材质的方法,实例代码供参考:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
protected virtual Material OnAssignMaterialModel(Material previousMaterial, Renderer renderer)
var materialPath = &Assets/Default_Materiall.mat&;
if (AssetDatabase.LoadAssetAtPath(materialPath, typeof(Material)))
return AssetDatabase.LoadAssetAtPath(materialPath, typeof(Material)) as M
Debug.Log(previousMaterial.name);
return previousM
&/code&&/pre&&/div&&p&这样只需要提供一个自定义的Default_Material.mat文件,重新导入所有的FBX文件,就可以进行替换了。&br&另外一个种方法是UWA群里的朋友提供了,就是删除掉Defualt-Material,沿着这个思路搜索了一下,发现网上已经有解决方案:&a href=&/?target=http%3A///p/48& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&删除FBX的默认材质&i class=&icon-external&&&/i&&/a&&br&示例代码如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
/// &summary&
/// 对于模型,删除共享材质,保证不会将包括Standard这样的材质打包到游戏中。
/// &/summary&
/// &param name=&model&&&/param&
public void OnPostprocessModel(GameObject model)
Renderer[] renders = model.GetComponentsInChildren&Renderer&();
if (null != renders)
foreach (Renderer render in renders)
render.sharedMaterials = new Material[render.sharedMaterials.Length];
&/code&&/pre&&/div&&p&注意这里不能将sharedMaterials设置为null,而是要new一个空的列表给它。&br&给一个遍历代码来处理所有的FBX文件:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
// 添加重新导入fbx的功能。
[MenuItem(&Assets/Reimport all FBX&)]
public static void ReimportAllFBX()
var files = AssetDatabase.GetAllAssetPaths();
foreach (var vv in files)
var vvLower = vv.ToLower();
if (vvLower.EndsWith(&fbx&))
AssetDatabase.ImportAsset(vv, ImportAssetOptions.ImportRecursive | ImportAssetOptions.ForceUpdate);
&/code&&/pre&&/div&&h2&4. 坑&/h2&&p&除了前面提到的Default-Material会导致按照AB打包可能重复加载Standard Shader,Unity 5.5.1f版本还有一个坑就是如果用了Remote的Cache Server,会导致AssetDatabase.ImportAsset不会更新内容,因为这个问题坑了我很久。。。因为右键直接ReImport是可以更新的,但是如果想Reimport All,会重新导入贴图,时间占用非常多,因此一直想只处理fbx文件,但是在Unity 5.5.1f版本中一直不太对,后来查到&a href=&/?target=https%3A///issues/assetdatabase-dot-importasset-does-not-trigger-onpreprocessmodel& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&这个bug Report&i class=&icon-external&&&/i&&/a&。&br&可恨的是,我本地貌似调用前面写的ReimportAllFBX方法会把Cache Server设置为Remote!虽然不能理解,但是现象貌似就是如此。。。&br&最后,升级了Unity版本到5.5.3f,依然关闭Cache Server,重新导入所有的,可以了。&br&tips:可以通过Project中只显示model来过滤查看所有的模型是否都是粉色的来保证所有的Default-Material都被干掉了。&/p&&figure&&img src=&/v2-351ae71bdf52f7e5ac5ff3e521a1f5e9_b.png& data-rawwidth=&1000& data-rawheight=&1054& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&/v2-351ae71bdf52f7e5ac5ff3e521a1f5e9_r.png&&&/figure&&br&&br&验证所有model都变粉了~
0. 写在之前本来创业之后,博客都计划整理成分量比较足、沉淀比较够的内容来做,但在游戏开发过程中,也不断踩了Unity的很多小坑。它们不足以整理成条理清晰、内容丰富的博客文章,但是填坑过程又花费了半天到一天的时间,如果记录下来给踩到同样坑的同学,…
&figure&&img src=&/v2-b193dfa5_b.jpg& data-rawwidth=&1024& data-rawheight=&640& class=&origin_image zh-lightbox-thumb& width=&1024& data-original=&/v2-b193dfa5_r.jpg&&&/figure&&p&看到一篇文章,&a href=&/?target=https%3A///news/tech-design-riot& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Tech Design at Riot&i class=&icon-external&&&/i&&/a&,其中讲到了Riot是如何使用RFC管理技术规范的。在一个大团队里,如何统一思想,建立技术规范是非常重要的。Riot介绍了他们的经验。实践证明,RFC不仅是一个技术规范的历史演变记录,更是一个集合群体智慧解决复杂技术问题的有效手段。下面是翻译内容。&/p&&p&我是Cam Dunn,是League的技术总监。一直以来,我觉得我们对人类历史的认识太少了。而这是因为根本没有人做记录。到底谁发明了锤子?带手柄的锤子已经发明了数千年了,但是没人确切知道是谁第一个做出了锤子。有时候,历史就像我们喝醉了酒,一觉醒来拼命想昨晚我们到底干了什么?&/p&&p&Riot成立以来,有一个问题始终困扰我们。在公司最初的几年里,公司的技术演变太快了,有时候我们会忘记把过程仔细记录下来。我记得我第一次去某个Riot数据中心,我看到只有几台机器开着,其他都关掉了。我问为啥只有这几台开着,他们说他们不知道这几台机器在运行什么,所以不能关掉。&/p&&p&最后我们终于搞清楚这些机器在跑什么服务,并且关掉了。但是这启发我思考:我们必须有查询服务的手段。我们必须有识别服务的技术标准,但是如何建立标准呢?&/p&&p&在Riot我们把自治看得很重要,实际上我们期望团队是“高度统一,高度自治”的。这种程度的自治意味着,依靠批准流程,靠有个人在技术设计和技术标准上盖章,是不靠谱的。但是这并不意味着一切都是“自由”的。如你所料,我们有技术标准和最佳工程实践。我们使用Request For Comment流程来贯彻标准和最佳实践。&/p&&p&RFC并不是我们的发明。Internet标准都是RFC。Python用的PEP也是类似的东西。我们也用了这个名字,不过为了适应我们的需要,稍微改了下流程。&/p&&p&大体上,我们的RFC很简单,假如你要写一个新系统或者对系统做一些较大的改动。Riot有很多很棒的工程师,其中肯定有些在你的工作领域也是专家。所以你把你的想法写一个proposal,然后发给整个工程师团队问问他们的意见。比如,为了避免&the case of the mystery servers&,我写了一个RFC,建议每个服务都按某种统一的机制把服务ID显示出来,这样即使你根本不知道这是什么服务,但是你至少知道这是一个有效的服务。&/p&&p&然后,我给我的RFC指定一个唯一ID,并添加一些描述信息,以方便其他人检索。描述信息可以包括团队或组织名称,RFC问题领域等。这些额外信息可以减少维护负担。每个人对每个RFC都有访问权限,这种方式可以帮助人们找到自己感兴趣的那些RFC。我的Mystery Servers RFC最终被命名为RFC100,并打上了SOA, API, Standard的标签。这样一来,关心相关领域的人就可以看到这篇RFC,并添加他们的意见。&/p&&p&当你得到你的proposal的反馈后,怎么处理就取决于你。这不是一个批准的流程,这是获得宝贵建议的途径。通常情况下,有些建议没有采纳,有些建议则帮助改进了设计。有些时候,并没有得到什么有价值的反馈。还有些时候,通过反馈,作者最终发现原先的想法太烂了所以彻底放弃了。但不管是哪种,最终的决定是由提出proposal的工程师做出的。&/p&&p&对于RFC100来说,我得到了好多反馈。在API设计方面比较强的工程师指出我的proposal有些地方没说清楚,有些地方则跟游戏过于联系紧密了,所以建议修改。举个例子,因为我是搞游戏开发的,最初我的RFC提议每个server都把frame rate都显示出来。frame rate是一个游戏概念,但在很多web服务上并没有这个概念。所以我把它做了修改,改成一个更可执行,更通用的测量量。&/p&&p&就反馈来讲,RFC100并不是个例。每次写RFC,总有人能指出该RFC带来的改进或者我的方法的缺陷。其中可能有些熟手有20年的经验,相似的系统写了五次之多。有些则可能是一个年轻工程师,头一次听说这个问题,但也从中获得了一些启发。如果我直接开些代码,那么可能就不会有这些好处。&/p&&p&反馈是很有用的,但是什么情况下可以变成标准呢?如果每个人都可以随心所欲,那么怎么一起共事呢?RFC流程让大家在技术标准上达成共识。这是靠&adoption“流程实现。每个团队,或者整个公司都可以选择采纳某个RFC。&/p&&p&采纳某个RFC意味着,每个在相关领域工作的人都应该按照该RFC中提供的标准行事。当然RFC中不适用的方面就没必要做了。比如该RFC如果只适用于JAVA,你在写C++就没必要用了。或者该RFC在说怎么从game server搜集性能数据,而你在写一个内部工具。没关系的,我们并不要求RFC的每一条都要做到。但是,通常如果一个标准在&riot.lol&范围内被采纳,就意味着每个LoL的工程师都要用这个标准。通常在我们决定一个产品开发时要采用哪个标准时,我们的团队已经选好了要用的技术领域,所以一般不会发生这种意外情况。&/p&&p&你可能想到了,所有类似的流程都可能面临质疑,而需要不断完善。在过去的三年里,我们不断迭代,简化和改进了原先的流程。比如,最开始的时候,每个RFC都列出一个&stakeholders&列表,由作者指定哪些相关人员会对此RFC感兴趣。但是,由作者指定哪些人会感兴趣是有问题的。现在我们倾向于一种自服务的模型,每个利益相关人员自己识别哪些RFC和自己相关。RFC100是一个应用广泛的RFC,所以很多写服务的工程师都来添加意见。这仅是一个我们如何修改流程以满足需要的实例。&/p&&p&到2015年10月,我们已经有超过425个RFC,涉及到我们技术栈的各个方面。多数RFC都是“你怎么想?”这种风格的,作者希望通过这种方式得到别人的反馈。其中一部分是“标准”RFC,定义了communication protocols或coding style指南。有些RFC有数百条建议,有些则少一些。有些我们抛弃了,有些我们则在riot公司范围内广泛使用。&/p&&p&最终,我的RFC100成为了一个RIOT范围内的&query and control rfc pattern&。其定义了终端的服务标识问题。你可以由此查询RIOT服务的ID,这样就可以避免有一个服务我们不知道是什么的问题。数据中心的后来的访问者就不会因为“神秘的活动服务”问题而挠头了。&/p&&p&正是由于RFC,我们有了一种记录行之有效的经验的方法,RIOT工程师需要关注的事情也变少了。RIOT没有人希望成为那个盖橡皮章的人,但是每个人都认为共享技术设计和技术标准非常重要,也很有价值。RFC流程则提供了一种去中心化、自服务的共享方式。&/p&
看到一篇文章,,其中讲到了Riot是如何使用RFC管理技术规范的。在一个大团队里,如何统一思想,建立技术规范是非常重要的。Riot介绍了他们的经验。实践证明,RFC不仅是一个技术规范的历史演变记录,更是一个集合群体智慧解决复杂技术问…
&figure&&img src=&/v2-bc95f0ffe2d0af54d7dc_b.jpg& data-rawwidth=&400& data-rawheight=&256& class=&content_image& width=&400&&&/figure&&blockquote&此文为 &a href=&/taiyouxi& class=&internal&&钛核互动·喵小逗&/a& 原创文章,转载请注明出处。&/blockquote&&p&在Unity移动平台的游戏开发过程中,贴图资源是往往是占资源量最大的资源。如何在保证视觉效果的同时,尽可能的减少贴图资源,是开发团队会经常遇到的问题。通常来说,对于3D物体的纹理,是可以采用ETC/PVRTC等压缩比很大的算法处理的,但是对于细节要求很高的UI纹理,这样处理造成的失真往往达不到质量要求。对于这类的贴图,我们可以考虑使用失真较小的16位的贴图格式存储。&/p&&p&但是对于颜色数较高的纹理,Unity提供的默认转换方法会呈现明显的色带。针对该问题,keijiro实现了一种&a href=&/?target=https%3A///keijiro/unity-dither4444& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&dither4444的改进算法&i class=&icon-external&&&/i&&/a&。从图1上可以看到,对于画面细节比较平滑的图片,该算法虽然消除了色带现象,同时带来了肉眼可见的噪点。&/p&&figure&&img src=&/v2-56f3beaf025c1dce9af7af37c1850665_b.png& data-rawwidth=&513& data-rawheight=&258& class=&origin_image zh-lightbox-thumb& width=&513& data-original=&/v2-56f3beaf025c1dce9af7af37c1850665_r.png&&&/figure&&p&&图1&
右:keijiro的dither4444示意图 &/p&&p&笔者在keijiro的算法基础上进行了改进,提供了一个将RGB24bit图dither之后转RGB565的方法,基本消除了肉眼可见的失真,实际效果见图2。 &/p&&figure&&img src=&/v2-95a3afd38501baad5c6d_b.png& data-rawwidth=&1666& data-rawheight=&564& class=&origin_image zh-lightbox-thumb& width=&1666& data-original=&/v2-95a3afd38501baad5c6d_r.png&&&/figure&&p&&图2&
右:笔者的dither565示意图 &/p&&p&实际在我的项目的应用中,对于不适合ETC/PVRTC压缩的图片,都采用了该文章中的RGB565或者RGB565+A8的方式。在肉眼基本无失真的基础上,节省了部分资源。&/p&&p&最后附上OnPostprocessTexture代码&/p&&div class=&highlight&&&pre&&code class=&language-csharp&&&span&&/span&&span class=&k&&void&/span& &span class=&nf&&OnPostprocessTexture&/span& &span class=&p&&(&/span&&span class=&n&&Texture2D&/span& &span class=&n&&texture&/span&&span class=&p&&)&/span&
&span class=&p&&{&/span&
&span class=&k&&if&/span&&span class=&p&&(&/span&&span class=&n&&assetPath&/span&&span class=&p&&.&/span&&span class=&n&&Contains&/span& &span class=&p&&(&/span&&span class=&s&&&_dither565&&/span&&span class=&p&&))&/span&
&span class=&p&&{&/span&
&span class=&kt&&var&/span& &span class=&n&&texw&/span& &span class=&p&&=&/span& &span class=&n&&texture&/span&&span class=&p&&.&/span&&span class=&n&&width&/span&&span class=&p&&;&/span&
&span class=&kt&&var&/span& &span class=&n&&texh&/span& &span class=&p&&=&/span& &span class=&n&&texture&/span&&span class=&p&&.&/span&&span class=&n&&height&/span&&span class=&p&&;&/span&
&span class=&kt&&var&/span& &span class=&n&&pixels&/span& &span class=&p&&=&/span& &span class=&n&&texture&/span&&span class=&p&&.&/span&&span class=&n&&GetPixels&/span& &span class=&p&&();&/span&
&span class=&kt&&var&/span& &span class=&n&&offs&/span& &span class=&p&&=&/span& &span class=&m&&0&/span&&span class=&p&&;&/span&
&span class=&kt&&var&/span& &span class=&n&&k1Per31&/span& &span class=&p&&=&/span& &span class=&m&&1.0f&/span& &span class=&p&&/&/span& &span class=&m&&31.0f&/span&&span class=&p&&;&/span&
&span class=&kt&&var&/span& &span class=&n&&k1Per32&/span& &span class=&p&&=&/span& &span class=&m&&1.0f&/span& &span class=&p&&/&/span& &span class=&m&&32.0f&/span&&span class=&p&&;&/span&
&span class=&kt&&var&/span& &span class=&n&&k5Per32&/span& &span class=&p&&=&/span& &span class=&m&&5.0f&/span& &span class=&p&&/&/span& &span class=&m&&32.0f&/span&&span class=&p&&;&/span&
&span class=&kt&&var&/span& &span class=&n&&k11Per32&/span& &span class=&p&&=&/span& &span class=&m&&11.0f&/span& &span class=&p&&/&/span& &span class=&m&&32.0f&/span&&span class=&p&&;&/span&
&span class=&kt&&var&/span& &span class=&n&&k15Per32&/span& &span class=&p&&=&/span& &span class=&m&&15.0f&/span& &span class=&p&&/&/span& &span class=&m&&32.0f&/span&&span class=&p&&;&/span&
&span class=&kt&&var&/span& &span class=&n&&k1Per63&/span& &span class=&p&&=&/span& &span class=&m&&1.0f&/span& &span class=&p&&/&/span& &span class=&m&&63.0f&/span&&span class=&p&&;&/span&
&span class=&kt&&var&/span& &span class=&n&&k3Per64&/span& &span class=&p&&=&/span& &span class=&m&&3.0f&/span& &span class=&p&&/&/span& &span class=&m&&64.0f&/span&&span class=&p&&;&/span&
&span class=&kt&&var&/span& &span class=&n&&k11Per64&/span& &span class=&p&&=&/span& &span class=&m&&11.0f&/span& &span class=&p&&/&/span& &span class=&m&&64.0f&/span&&span class=&p&&;&/span&
&span class=&kt&&var&/span& &span class=&n&&k21Per64&/span& &span class=&p&&=&/span& &span class=&m&&21.0f&/span& &span class=&p&&/&/span& &span class=&m&&64.0f&/span&&span class=&p&&;&/span&
&span class=&kt&&var&/span& &span class=&n&&k29Per64&/span& &span class=&p&&=&/span& &span class=&m&&29.0f&/span& &span class=&p&&/&/span& &span class=&m&&64.0f&/span&&span class=&p&&;&/span&
&span class=&kt&&var&/span& &span class=&n&&k_r&/span& &span class=&p&&=&/span& &span class=&m&&32&/span&&span class=&p&&;&/span& &span class=&c1&&//R&B压缩到5位,所以取2的5次方&/span&
&span class=&kt&&var&/span& &span class=&n&&k_g&/span& &span class=&p&&=&/span& &span class=&m&&64&/span&&span class=&p&&;&/span& &span class=&c1&&//G压缩到6位,所以取2的6次方&/span&
&span class=&k&&for&/span&&span class=&p&&(&/span&&span class=&kt&&var&/span& &span class=&n&&y&/span& &span class=&p&&=&/span& &span class=&m&&0&/span&&span class=&p&&;&/span& &span class=&n&&y&/span& &span class=&p&&&&/span& &span class=&n&&texh&/span&&span class=&p&&;&/span& &span class=&n&&y&/span&&span class=&p&&++){&/span&
&span class=&k&&for&/span&&span class=&p&&(&/span&&span class=&kt&&var&/span& &span class=&n&&x&/span& &span class=&p&&=&/span& &span class=&m&&0&/span&&span class=&p&&;&/span& &span class=&n&&x&/span& &span class=&p&&&&/span& &span class=&n&&texw&/span&&span class=&p&&;&/span& &span class=&n&&x&/span&&span class=&p&&++){&/span&
&span class=&kt&&float&/span& &span class=&n&&r&/span& &span class=&p&&=&/span& &span class=&n&&pixels&/span& &span class=&p&&[&/span&&span class=&n&&offs&/span&&span class=&p&&].&/span&&span class=&n&&r&/span&&span class=&p&&;&/span&
&span class=&kt&&float&/span& &span class=&n&&g&/span& &span class=&p&&=&/span& &span class=&n&&pixels&/span& &span class=&p&&[&/span&&span class=&n&&offs&/span&&span class=&p&&].&/span&&span class=&n&&g&/span&&span class=&p&&;&/span&
&span class=&kt&&float&/span& &span class=&n&&b&/span& &span class=&p&&=&/span& &span class=&n&&pixels&/span& &span class=&p&&[&/span&&span class=&n&&offs&/span&&span class=&p&&].&/span&&span class=&n&&b&/span&&span class=&p&&;&/span&
&span class=&kt&&var&/span& &span class=&n&&r2&/span& &span class=&p&&=&/span& &span class=&n&&Mathf&/span&&span class=&p&&.&/span&&span class=&n&&Clamp01&/span& &span class=&p&&(&/span&&span class=&n&&Mathf&/span&&span class=&p&&.&/span&&span class=&n&&Floor&/span& &span class=&p&&(&/span&&span class=&n&&r&/span& &span class=&p&&*&/span& &span class=&n&&k_r&/span&&span class=&p&&)&/span& &span class=&p&&*&/span& &span class=&n&&k1Per31&/span&&span class=&p&&);&/span&
&span class=&kt&&var&/span& &span class=&n&&g2&/span& &span class=&p&&=&/span& &span class=&n&&Mathf&/span&&span class=&p&&.&/span&&span class=&n&&Clamp01&/span& &span class=&p&&(&/span&&span class=&n&&Mathf&/span&&span class=&p&&.&/span&&span class=&n&&Floor&/span& &span class=&p&&(&/span&&span class=&n&&g&/span& &span class=&p&&*&/span& &span class=&n&&k_g&/span&&span class=&p&&)&/span& &span class=&p&&*&/span& &span class=&n&&k1Per63&/span&&span class=&p&&);&/span&
&span class=&kt&&var&/span& &span class=&n&&b2&/span& &span class=&p&&=&/span& &span class=&n&&Mathf&/span&&span class=&p&&.&/span&&span class=&n&&Clamp01&/span& &span class=&p&&(&/span&&span class=&n&&Mathf&/span&&span class=&p&&.&/span&&span class=&n&&Floor&/span& &span class=&p&&(&/span&&span class=&n&&b&/span& &span class=&p&&*&/span& &span class=&n&&k_r&/span&&span class=&p&&)&/span& &span class=&p&&*&/span& &span class=&n&&k1Per31&/span&&span class=&p&&);&/span&
&span class=&kt&&var&/span& &span class=&n&&re&/span& &span class=&p&&=&/span& &span class=&n&&r&/span& &span class=&p&&-&/span& &span class=&n&&r2&/span&&span class=&p&&;&/span&
&span class=&kt&&var&/span& &span class=&n&&ge&/span& &span class=&p&&=&/span& &span class=&n&&g&/span& &span class=&p&&-&/span& &span class=&n&&g2&/span&&span class=&p&&;&/span&
&span class=&kt&&var&/span& &span class=&n&&be&/span& &span class=&p&&=&/span& &span class=&n&&b&/span& &span class=&p&&-&/span& &span class=&n&&b2&/span&&span class=&p&&;&/span&
&span class=&kt&&var&/span& &span class=&n&&n1&/span& &span class=&p&&=&/span& &span class=&n&&offs&/span& &span class=&p&&+&/span& &span class=&m&&1&/span&&span class=&p&&;&/span&
&span class=&kt&&var&/span& &span class=&n&&n2&/span& &span class=&p&&=&/span& &span class=&n&&offs&/span& &span class=&p&&+&/span& &span class=&n&&texw&/span& &span class=&p&&-&/span& &span class=&m&&1&/span&&span class=&p&&;&/span&
&span class=&kt&&var&/span& &span class=&n&&n3&/span& &span class=&p&&=&/span& &span class=&n&&offs&/span& &span class=&p&&+&/span& &span class=&n&&texw&/span&&span class=&p&&;&/span&
&span class=&kt&&var&/span& &span class=&n&&n4&/span& &span class=&p&&=&/span& &span class=&n&&offs&/span& &span class=&p&&+&/span& &span class=&n&&texw&/span& &span class=&p&&+&/span& &span class=&m&&1&/span&&span class=&p&&;&/span&
&span class=&k&&if&/span&&span class=&p&&(&/span&&span class=&n&&x&/span& &span class=&p&&&&/span& &span class=&n&&texw&/span& &span class=&p&&-&/span& &span class=&m&&1&/span&&span class=&p&&){&/span&
&span class=&n&&pixels&/span& &span class=&p&&[&/span&&span class=&n&&n1&/span&&span class=&p&&].&/span&&span class=&n&&r&/span& &span class=&p&&+=&/span& &span class=&n&&re&/span& &span class=&p&&*&/span& &span class=&n&&k15Per32&/span&&span class=&p&&;&/span&
&span class=&n&&pixels&/span& &span class=&p&&[&/span&&span class=&n&&n1&/span&&span class=&p&&].&/span&&span class=&n&&g&/span& &span class=&p&&+=&/span& &span class=&n&&ge&/span& &span class=&p&&*&/span& &span class=&n&&k29Per64&/span&&span class=&p&&;&/span&
&span class=&n&&pixels&/span& &span class=&p&&[&/span&&span class=&n&&n1&/span&&span class=&p&&].&/span&&span class=&n&&b&/span& &span class=&p&&+=&/span& &span class=&n&&be&/span& &span class=&p&&*&/span& &span class=&n&&k15Per32&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&k&&if&/span&&span class=&p&&(&/span&&span class=&n&&y&/span& &span class=&p&&&&/span& &span class=&n&&texh&/span& &span class=&p&&-&/span& &span class=&m&&1&/span&&span class=&p&&){&/span&
&span class=&n&&pixels&/span& &span class=&p&&[&/span&&span class=&n&&n3&/span&&span class=&p&&].&/span&&span class=&n&&r&/span& &span class=&p&&+=&/span& &span class=&n&&re&/span& &span class=&p&&*&/span& &span class=&n&&k11Per32&/span&&span class=&p&&;&/span&
&span class=&n&&pixels&/span& &span class=&p&&[&/span&&span class=&n&&n3&/span&&span class=&p&&].&/span&&span class=&n&&g&/span& &span class=&p&&+=&/span& &span class=&n&&ge&/span& &span class=&p&&*&/span& &span class=&n&&k21Per64&/span&&span class=&p&&;&/span&
&span class=&n&&pixels&/span& &span class=&p&&[&/span&&span class=&n&&n3&/span&&span class=&p&&].&/span&&span class=&n&&b&/span& &span class=&p&&+=&/span& &span class=&n&&be&/span& &span class=&p&&*&/span& &span class=&n&&k11Per32&/span&&span class=&p&&;&/span&
&span class=&k&&if&/span&&span class=&p&&(&/span&&span class=&n&&x&/span& &span class=&p&&&&/span& &span class=&m&&0&/span&&span class=&p&&){&/span&
&span class=&n&&pixels&/span& &span class=&p&&[&/span&&span class=&n&&n2&/span&&span class=&p&&].&/span&&span class=&n&&r&/span& &span class=&p&&+=&/span& &span class=&n&&re&/span& &span class=&p&&*&/span& &span class=&n&&k5Per32&/span&&span class=&p&&;&/span&
&span class=&n&&pixels&/span& &span class=&p&&[&/span&&span class=&n&&n2&/span&&span class=&p&&].&/span&&span class=&n&&g&/span& &span class=&p&&+=&/span& &span class=&n&&ge&/span& &span class=&p&&*&/span& &span class=&n&&k11Per64&/span&&span class=&p&&;&/span&
&span class=&n&&pixels&/span& &span class=&p&&[&/span&&span class=&n&&n2&/span&&span class=&p&&].&/span&&span class=&n&&b&/span& &span class=&p&&+=&/span& &span class=&n&&be&/span& &span class=&p&&*&/span& &span class=&n&&k5Per32&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&k&&if&/span&&span class=&p&&(&/span&&span class=&n&&x&/span& &span class=&p&&&&/span& &span class=&n&&texw&/span& &span class=&p&&-&/span& &span class=&m&&1&/span&&span class=&p&&){&/span&
&span class=&n&&pixels&/span& &span class=&p&&[&/span&&span class=&n&&n4&/span&&span class=&p&&].&/span&&span class=&n&&r&/span& &span class=&p&&+=&/span& &span class=&n&&re&/span& &span class=&p&&*&/span& &span class=&n&&k1Per32&/span&&span class=&p&&;&/span&
&span class=&n&&pixels&/span& &span class=&p&&[&/span&&span class=&n&&n4&/span&&span class=&p&&].&/span&&span class=&n&&g&/span& &span class=&p&&+=&/span& &span class=&n&&ge&/span& &span class=&p&&*&/span& &span class=&n&&k3Per64&/span&&span class=&p&&;&/span&
&span class=&n&&pixels&/span& &span class=&p&&[&/span&&span class=&n&&n4&/span&&span class=&p&&].&/span&&span class=&n&&b&/span& &span class=&p&&+=&/span& &span class=&n&&be&/span& &span class=&p&&*&/span& &span class=&n&&k1Per32&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&span class=&n&&pixels&/span& &span class=&p&&[&/span&&span class=&n&&offs&/span&&span class=&p&&].&/span&&span class=&n&&r&/span& &span class=&p&&=&/span& &span class=&n&&r2&/span&&span class=&p&&;&/span&
&span class=&n&&pixels&/span& &span class=&p&&[&/span&&span class=&n&&offs&/span&&span class=&p&&].&/span&&span class=&n&&g&/span& &span class=&p&&=&/span& &span class=&n&&g2&/span&&span class=&p&&;&/span&
&span class=&n&&pixels&/span& &span class=&p&&[&/span&&span class=&n&&offs&/span&&span class=&p&&].&/span&&span class=&n&&b&/span& &span class=&p&&=&/span& &span class=&n&&b2&/span&&span class=&p&&;&/span&
&span class=&n&&offs&/span&&span class=&p&&++;&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&span class=&n&&texture&/span&&span class=&p&&.&/span&&span class=&n&&SetPixels&/span& &span class=&p&&(&/span&&span class=&n&&pixels&/span&&span class=&p&&);&/span&
&span class=&n&&EditorUtility&/span&&span class=&p&&.&/span&&span class=&n&&CompressTexture&/span& &span class=&p&&(&/span&&span class=&n&&texture&/span&&span class=&p&&,&/span& &span class=&n&&TextureFormat&/span&&span class=&p&&.&/span&&span class=&n&&RGB565&/span&&span class=&p&&,&/span& &span class=&n&&TextureCompressionQuality&/span&&span class=&p&&.&/span&&span class=&n&&Best&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&&/p&
原创文章,转载请注明出处。在Unity移动平台的游戏开发过程中,贴图资源是往往是占资源量最大的资源。如何在保证视觉效果的同时,尽可能的减少贴图资源,是开发团队会经常遇到的问题。通常来说,对于3D物体的纹理,是可以采用ETC/P…
&figure&&img src=&/v2-3941eccfa098e495f7277_b.jpg& data-rawwidth=&1291& data-rawheight=&1029& class=&origin_image zh-lightbox-thumb& width=&1291& data-original=&/v2-3941eccfa098e495f7277_r.jpg&&&/figure&&p&前两周分享了资源配置与资源管理,今天分享一种特殊的资源脚本数据。在Unity项目中,我们通常使用C#编写脚本,C#是一门非常方便的语言可以帮助我们快速开发。不过也有一些要点需要关注,影响内存与性能。&/p&&h2&&b&字符串String&/b&&/h2&&p&首先要关注String,String没有看起来那么简单,什么是String呢?&/p&&ul&&li&String是一个UTF-16编码的文本&/li&&li&String是一个引用类型&/li&&li&String是不可变的&/li&&/ul&&p&在C#里面,字符串是一个引用类型而不是一个值类型,即使看起来像是持有一个值类型对象并可以方便的修改,这里修改字符串是创建一个新的字符串。通常建议使用StringBuilder来拼接字符串,下面看看不同行为的拼接字符串带来的性能差异吧。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public class MonoTest : MonoBehaviour {
const int SIZE = 1024;
void Update () {
_UpdateStringAppend();
_UpdateStringFormat();
_UpdateStringBuild();
string _UpdateStringAppend() {
string str = string.E
for (int i = 0; i & SIZE; ++i) {
string _UpdateStringFormat() {
string str = string.E
for (int i = 0; i & SIZE; ++i) {
str += string.Format(&{0}&, i);
string _UpdateStringBuild() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i & SIZE; ++i) {
sb.Append(i);
return sb.ToString();
&/code&&/pre&&/div&&p&&br&&/p&&figure&&img src=&/v2-5ed19eb7fda80ee48e765_b.jpg& data-rawwidth=&386& data-rawheight=&227& class=&content_image& width=&386&&&/figure&&p&&br&&/p&&p&&br&&/p&&p&观察数据可以发现StringBuilder在性能上和GC上都有极大的提升,每次创建一个新的字符串,字符串长度从0增长到n,这是一个O(n^2)的操作。而StringBuilder则只会在长度不够的时候重新申请并赋值,如果内部的长度是按2递增的话,这里的复杂度是O(nlogn)。如果设定一个足够大的预初始值,那这里的复杂度则可以降低到O(n)。&/p&&p&这里的内存申请的量级和运算复杂度也是一样的,关注大小与分配次数。过多的分配次数会导致堆碎片变多,过多的内存分配则会导致触发内存清理GC,这是额外的无必要的开销。所以推荐尽可能的使用StringBuilder来优化这个操作,同时StringBuilder本身也会在内部申请内存,复用StringBuilder能进一步优化内存。&/p&&p&再来看看之前讨论里面被忽略的Format拼接,Format表现最差有点出人意外又在情理之中。我们在平时被建议使用Format来拼接字符串,但在有些情况下Format的表现非常差。这里就是一个不恰当的Format使用案例,这是一个冗余的Format操作,同时还多了一次int转object的GC。如果还难以理解,则可以看看下面的Format操作得到了一个更差的结果。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&string _UpdateStringFormatEx() {
string str = string.E
for (int i = 0; i & SIZE; ++i) {
str = string.Format(&{0}{1}&, str, i);
&/code&&/pre&&/div&&p&&br&&/p&&figure&&img src=&/v2-ae79aa3e673f0b0f96cddca_b.jpg& data-rawwidth=&417& data-rawheight=&288& class=&content_image& width=&417&&&/figure&&p&&br&&/p&&p&其实一个正确的Format用法是下面这样的&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&string str = string.Format(&{0}{1}....{n}&, 0, 1, ..., n);
&/code&&/pre&&/div&&p&通过把多次拼接操作合并成一次来达到减少GC提高效率,实际Format的内部使用了StringBuilder来拼接字符串。N次使用StringBuilder来拼接字符串的性能与1次的操作性能有较大差异,这是平时使用中也需要注意的。&/p&&h2&&b&优化字符串数量&/b&&/h2&&p&字符串是不可变,每次修改字符串都会生成一个新的字符串,那创建的字符串呢?尽管实验得到,每次创建字符串都会得到一个新串,即使已经存在一个相同的字符串。这里有一篇顾露分享的&a href=&/p/& class=&internal&&《Unity 游戏的 string interning 优化》&/a&已经做了这块内容详细描述。这里通过string.Intern来减少字符串数量达到优化内存的效果,同时让我发现了项目中存在着大量的字符串使用。如何更进一步的减少字符串数量是个有趣的问题。&/p&&p&通过顾露的自制工具&a href=&/?target=https%3A///PerfAssist/PA_ResourceTracker& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&PA_ResourceTracker&i class=&icon-external&&&/i&&/a&采集的数据,分析数据发现字符串数据里面存在较多的资源加载路径。这些路径数据非常的长,而且数量也非常的多。字符串路径的作用是标识资源,考虑使用Hash来标识资源也可以做到相同的事情。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&Resources.Load(string path, Type type);
Resources.Load(ulong pathHash, Type type);
Resources.PathToHash(string path);
&/code&&/pre&&/div&&p&在资源管理上实现两个新增的接口,支持按Hash加载资源,然后提供一个字符串路径转Hash的接口,来实现这一目标。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public class Template
public int id = -1;
public class Template
public int id = -1;
public ulong pathH
&/code&&/pre&&/div&&p&然后替换结构体里面的变量为Hash,在第一次得到这个字符串后立刻调用Resources.PathToHash计算Hash值并存储。&/p&&p&计算路径的Hash还需要考虑路径的大小写、斜杠与放斜杠。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public ulong PathToHash(string str) {
ulong hashCode = 0;
for (int i = 0; i & str.L ++i) {
char ch = Char.ToUpperInvariant(str[i]);
if (str[i] == '\\')
{ ch = '/'; }
hashCode = (hashCode && 7) + (hashCode && 1) + hashCode +
return hashC
&/code&&/pre&&/div&&p&使用ulong降低Hash的冲突,由于存在冲突的可能,这里在日常构建的时候对所有的资源路径计算Hash判断是否有冲突。这里路径Hash不仅减少了对象数量,也减少了一些字符串修改操作导致的GC。下面举个降低的例子,得到唯一字符串路径。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public string GetUniString(string str) {
return str.Replace('\\', '/').ToUpperInvariant();
&/code&&/pre&&/div&&p&这实在是一个低效的行为,所以即使你不需要缩减字符串个数,还是强烈推荐使用Hash来做唯一标识符。&/p&&p&由于Unity提供的Resources接口需要使用路径字符串来加载资源,所以之前说了那么多还没有解释为什么可以减少字符串对象这个问题。这里我们项目能使用主要是由于使用了AssetBundle。只需要先存Hash对应的AssetBundle ID然后加载这个AssetBundle的时候加载Hash对应Name即可。AssetBundle支持直接使用Name加载,也可以使用Asset Path加载。这里的AssetPath是相对于Assets目录的路径与Resources的相对于Resources目录还是有差异的,所以使用Name来加载。AssetBundle本身就有一个接口AssetBundle.GetAllAssetNames()获取所有资源路径。不过这里会包含被依赖的所有资源路径,所以一般自己存这个数据。&/p&&p&细心的人也注意到了上面提到的AssetBundle ID,由于AssetBundle打包是可以完全控制的。所以给AssetBundle命名一个数字ID,也是有效的减少字符串数量的方法。这对使用AssetBundle打包加载资源的项目是一个不错的参考。我们实现自己的AssetBundleManifest维护AssetBundle之间的依赖关系。&/p&&p&Unity的Animator类提供了StringToHash接口来帮助消除字符串,同时配套提供两套接口可以调用,和这里消除字符串路径的思路是一致的。相信还有其他地方也可以通过这个思路来消除字符串优化性能。&/p&&p&最后这里做字符串转路径这个实现是由于游戏在开始的时候就加载了大部分配置表,表现里面有着大量路径字符串。在工具里面发现路径字符串的比重大概在20%,所以来做这项工作优化对象数量。带来了不错的性能收益,不过如果出错只能看到hash而不能看到实际字符串。不过可以通过区分DEBUG与RELEASE版本来决定是否保留这些字符串。&/p&&h2&&b&优化字符串比较&/b&&/h2&&p&默认的字符串比较操作是非常低效的,&a href=&/?target=https%3A///en-us/dotnet/standard/base-types/best-practices-strings& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《Best Practices for Using Strings in .NET》&i class=&icon-external&&&/i&&/a&这篇文章讲了这方面的大部分细节。这里主要展示一些实践测试数据,让我们对性能有一个认识。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&StringBuilder sBuilder = new StringBuilder();
System.Random random = new System.Random();
for (int i = 0; i & 100; ++i)
sBuilder.Append((char)(random.Next() % 256));
string str = sBuilder.ToString();
string preStr = str.Substring(0, 16);
string lastStr = str.Substring(str.Length - 16, 16);
int cnt = 0;
for (int i = 0; i & 100 * 1024; ++i)
if (str.StartsWith(preStr)) ++
if (str.EndsWith(lastStr)) ++
&/code&&/pre&&/div&&p&测试结果&/p&&p&&br&&/p&&figure&&img src=&/v2-74cf2ddf2bdaaeb5326a_b.jpg& data-rawwidth=&594& data-rawheight=&389& class=&origin_image zh-lightbox-thumb& width=&594& data-original=&/v2-74cf2ddf2bdaaeb5326a_r.jpg&&&/figure&&p&&br&&/p&&p&字符串比较接口默认行为&/p&&p&&br&&/p&&figure&&img src=&/v2-47b68fcc3ce64e_b.jpg& data-rawwidth=&531& data-rawheight=&451& class=&origin_image zh-lightbox-thumb& width=&531& data-original=&/v2-47b68fcc3ce64e_r.jpg&&&/figure&&p&&br&&/p&&p&正常情况下使用Ordinal比较即可,自己实现Ordinal行为的比较还可以提高10倍的性能。&/p&&h2&&b&从容器谈Boxing&/b&&/h2&&p&泛型容器内部实现会调用一些System.Object接口,如果我们不实现对应的泛型接口,在调用接口的时候就会找到基类Object的接口。而由于Struct是一个值类型,value type转class type会触发内存分配,定义这种行为为Boxing。&a href=&/?target=http%3A///blog/2015/08/c-performance-tips-for-unity-part-2-structs-and-enums/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《c-performance-tips-for-unity-part-2-structs-and-enums》&i class=&icon-external&&&/i&&/a&这篇文章已经对这块做了详细描述与举例。我自己也做了一些数据测试,分享给大家做参考。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public struct SmallStruct
// 2 int fields. Total size: 2 * 4B + 16B = 24B
public int a,
public struct LargeStruct
// 20 int fields. Total size: 20 * 4B + 16B = 96B
public int a, b,
// Dictionary&Struct, bool& dict
// 1024 calls dict. ContainsKey
&/code&&/pre&&/div&&p&&br&&/p&&figure&&img src=&/v2-cc6ed5d711a5ff7a5beab0_b.jpg& data-rawwidth=&356& data-rawheight=&177& class=&content_image& width=&356&&&/figure&&p&&br&&/p&&p&实现了不同接口之后&/p&&p&&br&&/p&&figure&&img src=&/v2-d0423ebbed_b.jpg& data-rawwidth=&530& data-rawheight=&282& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&/v2-d0423ebbed_r.jpg&&&/figure&&p&&br&&/p&&p&观察发现Dictionary内部使用 EqualityComparer&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public abstract class EqualityComparer&T&
protected EqualityComparer();
public static EqualityComparer&T& Default { }
public abstract bool Equals(T x, T y);
public abstract int GetHashCode(T obj);
&/code&&/pre&&/div&&p&如果没有实现还GetHashCode触发一次boxing,而Equals则触发两次。实现IEquatable泛型接口,以及override int GetHashCode则可避免触发GC。非泛型的HashTable实现和泛型Dictionary基本一致,推荐使用Dictionary泛型版本,提高性能。&/p&&h2&&b&其他Tips&/b&&/h2&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&void DispatchEvent(string str, params object[] data);
static object[] _default = new object[] {};
void DispatchEvent(string str)
_DispatchEvent(str, _default);
void _DispatchEvent(string str, object[] data);
&/code&&/pre&&/div&&p&params object每次调用会申请一个object数组,对于无参数的行为,实现一个默认接口减少GC。&/p&&p&一般情况下使用Profile Windows排查不必要的GC Alloc。&/p&&figure&&img src=&/v2-56ce3db9fd32ecf380c4b6e_b.jpg& data-rawwidth=&855& data-rawheight=&304& class=&origin_image zh-lightbox-thumb& width=&855& data-original=&/v2-56ce3db9fd32ecf380c4b6e_r.jpg&&&/figure&&p&&br&&/p&&p&这个工具能帮助我们定位发生GC Alloc行为的代码,通常第一步优化那些每帧都存在的GC,之后优化那些峰值很高的GC。优化GC能带来什么好处呢,假设当前使用了30M内存,申请了50M内存。这里有20M的空间可以用于日常的GC Alloc。假设我们每帧的GC Alloc=100K,则20 * 1024 / 100 = 204帧。如果每帧的执行时间为33ms(30帧),则6.76S触发一次GC.Collect()。这个函数开销在100ms以上,当前帧的开销从33ms变成133ms,这会有明显的卡顿感。更多的GC优化可以参考&a href=&/?target=https%3A///cn/learn/tutorials/temas/performance-optimization/optimizing-garbage-collection-unity-games%3Fplaylist%3D44069& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《Structing out code to minimize the impact of garbage collection》&i class=&icon-external&&&/i&&/a&。&/p&&h2&&b&从Struct再谈优化对象数量&/b&&/h2&&p&从Rich Geldreich的《Lessons Learned While Fixing Memory Leaks in our First Unity Title》了解到对象数量过大造成额外的内存使用。这里再次谈对象数量优化,优化内存使用。&/p&&blockquote&The Boehm collector grows its OS memory allocation so it has enough internal heap headroom to avoid collecting too frequently. You must factor this headroom into account when budgeting your C# memory, i.e. if your budget calls for 25MB of C# memory then the actual amount of memory consumed at the OS level will be significantly larger (approximately 40-50MB in our experience).&/blockquote&&p&这里主要讨论配置表,配置表一般是一种Key-Value结构,同时在运行时我们不需要修改内存,最后配置表的总量和数量会非常多。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public class Dicitonary&TKey, TValue& {
private int[] m_
private int[] m_entryN
private int[] m_entryH
private TKey[] m_entryK
private TValue[] m_entryV
public class PlayerTemplate {
public ulong pathH
/* ... more data */
} // assume size = 128B
Dictionary&int, PlayerTempl

我要回帖

更多关于 ieeexplore.ieee.org 的文章

 

随机推荐