Unity經(jīng)驗(yàn)總結(jié)

年后新項(xiàng)目就要轉(zhuǎn)UE4了,本文主要是啟發(fā)式的總結(jié)一下這2年多使用Unity做手游的經(jīng)驗(yàn)和遇到的坑摇天。以下討論的Unity版本為5.6,本文僅代表個(gè)人的一些拙見哈哈。

打包構(gòu)建

為什么是先說打包违孝?因?yàn)槲矣X得出包的時(shí)間是影響團(tuán)隊(duì)工作效率最重要的因素。尤其是大項(xiàng)目泳赋,如果打包時(shí)間在一個(gè)小時(shí)以上雌桑,一般這個(gè)團(tuán)隊(duì)都避免不了996。再加上如果測(cè)試出現(xiàn)了一些卡流程的bug祖今,再重出包的效率真的低的可怕校坑。下面簡(jiǎn)單的說下我們項(xiàng)目是怎么做到打包時(shí)間控制在半小時(shí)內(nèi)的:

  1. AssetBundle與il2cpp代碼并行打包
    不得不說,Unity在多核PC上的打包優(yōu)化做的真的爛千诬,完全不能充分利用多核機(jī)器的并行性能優(yōu)勢(shì)耍目。所以我們的做法很簡(jiǎn)單粗暴:就是弄2個(gè)工程,1個(gè)負(fù)責(zé)打ab包徐绑,另1個(gè)工程打il2cpp包邪驮,這2者并行,且ab包和il2cpp.so都備份傲茄,供下次打包有需要時(shí)使用毅访。
  2. 項(xiàng)目盡可能的早投入使用Lua類語言進(jìn)行開發(fā)
    項(xiàng)目的il2cpp.so達(dá)到90M,C#代碼實(shí)在太多盘榨,每次轉(zhuǎn)il2cpp幾乎都要占用20-30分鐘喻粹,轉(zhuǎn)il2cpp都是全量轉(zhuǎn)(聽說新版Unity做了增量編譯),特別蛋疼草巡。后面接入了xLua后守呜,對(duì)C#代碼做了些裁剪,盡量讓framework層都用C#山憨,業(yè)務(wù)層都改用lua查乒,使得轉(zhuǎn)il2cpp時(shí)間大概控制在5-15mins。
  3. 記錄每次打的ab包的md5萍歉,只在md5發(fā)生變更時(shí)才打包
    這個(gè)應(yīng)該很多項(xiàng)目都會(huì)做侣颂,理由也很簡(jiǎn)單,就是一些美術(shù)資源沒變更枪孩,就不用浪費(fèi)時(shí)間再打一次包了憔晒,直接復(fù)用上次打好的ab包就好藻肄。我們的實(shí)現(xiàn)方式是將ab包的bytes和路徑轉(zhuǎn)成一串md5,然后把所有ab包的md5都記在一個(gè)xml里拒担,通過這個(gè)xml文件嘹屯,每次打包就可以與之前的md5做一下diff,沒變更就直接跳過打ab包的流程从撼。
    同時(shí)州弟,這個(gè)在做資源更新的時(shí)候,如果發(fā)現(xiàn)更新包體過大低零,也方便查找問題源頭婆翔。
  4. ab打包粒度控制
    這個(gè)問題因項(xiàng)目而異,因?yàn)閡nity的ab包怎么打掏婶,在很多層面上啃奴,其實(shí)是個(gè)取舍問題。
    至于我們項(xiàng)目是:
    1. icon資源雄妥,可配置打圖集或者各打在同一bundle
    2. 對(duì)于每一個(gè)ui界面最蕾,它的prefab各自單獨(dú)打bundle,并記錄每一個(gè)界面依賴的資源bundle(如icon資源老厌、動(dòng)畫瘟则、特效的bundle),在加載ui的prefab前枝秤,先加載這個(gè)prefab依賴的bundle醋拧。
    3. 對(duì)于每一個(gè)npc模型,它的prefab淀弹、mesh趁仙、動(dòng)畫、材質(zhì)垦页、特效打在同一bundle
    4. 對(duì)于場(chǎng)景的資源,其所有依賴的資源跟場(chǎng)景打在同一bundle
    5. 如果同一個(gè)資源被2個(gè)或以上的ab包引用干奢,則把它打在common的bundle包里
    6. 配置表打在同一個(gè)bundle
    7. Lua按協(xié)議痊焊、配置、GamePlay這3部分分開打bundle
    8. Shader按模塊(如ui忿峻、character薄啥、effect等)分開打bundle
      這里不討論為啥這么設(shè)計(jì)了,因?yàn)槠鋵?shí)這里也有很多問題逛尚,比如場(chǎng)景引用了一個(gè)npc垄惧,使得場(chǎng)景的ab包和npc的ab包都引用了npc資源,導(dǎo)致打在common包里了绰寞,我們的解決辦法是把npc在場(chǎng)景里的顯示人肉改成代碼動(dòng)態(tài)加載到逊;還有萬一shader在線上有bug铣口,整個(gè)shader和依賴這個(gè)shader的材質(zhì)要重新發(fā)一個(gè)完整的資源包。
  5. 構(gòu)建時(shí)打的log不要太多
    半年前的版本發(fā)現(xiàn)打包時(shí)打了15w行的log觉壶,單是這個(gè)打log的操作就占了15分鐘脑题。所以除非要定位打包問題,建議在平時(shí)的日構(gòu)建盡可能的少打日志铜靶。當(dāng)然叔遂,有些log是引擎里的,有源碼的話直接注釋就好了争剿。

性能

這個(gè)話題太廣已艰,不同類型的游戲都有各種特定的優(yōu)化,這里只是簡(jiǎn)單提一些我覺得比較冷門的(需要引擎源碼)蚕苇。

  1. 引擎Background線程數(shù)量改成2哩掺,Unity默認(rèn)是16,引擎啟動(dòng)的時(shí)候這些線程就會(huì)創(chuàng)建捆蜀,相信大部分做手游都用不上這么多線程疮丛。
  2. 引擎啟動(dòng)的時(shí)候會(huì)創(chuàng)建Enlighten和Substance線程,如果不用的話辆它,可以把引擎編譯宏ENABLE_SUBSTANCE和ENABLE_RUNTIME_GI去掉誊薄。
  3. 在做一些類似皮膚換裝的需求時(shí),盡量把蒙皮和貼圖合并的邏輯做在引擎的C++層锰茉,可以避免頂點(diǎn)和貼圖拷貝操作呢蔫。
  4. il2cpp的反射信息是存儲(chǔ)在dense_hash_map上的,這個(gè)dense_hash_map在我們項(xiàng)目運(yùn)行時(shí)會(huì)占用30M左右的內(nèi)存飒筑,這個(gè)大小與項(xiàng)目運(yùn)行時(shí)加載的C#代碼量(反射信息的讀取是lazy load)成正比片吊,所以在低端機(jī)上,我們把這個(gè)存儲(chǔ)方式改成更省內(nèi)存的sparse_hash_map协屡,但其實(shí)cpu會(huì)更耗一點(diǎn)俏脊,最終大概省到10M左右,本質(zhì)是時(shí)間換空間的方案肤晓。
  5. 引擎資源的Remapper也是用dense_hash_map的爷贫,內(nèi)存很吃緊的情況下,也是可以改成sparse_hash_map的形式补憾。
  6. Strip Engine Code可以嘗試開下漫萄,能省一些編譯的引擎包量。但是會(huì)有很多坑盈匾,官方的解釋是如果開了會(huì)有問題腾务,就關(guān)掉,哈哈削饵,服不服岩瘦。
  7. 引擎里返回給C#層的數(shù)組類接口未巫,如GetVertices(),GetMaterials()等,都是有g(shù)c風(fēng)險(xiǎn)的担钮,因?yàn)樗鼤?huì)重新alloc一個(gè)C#的數(shù)組橱赠,然后把C++對(duì)象再一個(gè)個(gè)塞到這個(gè)數(shù)組里。例子如下:
void Update()
{
    var mats = renderer.GetMaterials(); // gc
    for(int i = 0; i < mats.Length;++i)
    {
        var mat = mats[i];
        // mat.SetXXX()
    }
}

簡(jiǎn)單的解決辦法當(dāng)然是緩存箫津,不用每次都調(diào)狭姨。其實(shí)也可以在引擎層加類似GetXXX(int index)和GetXXXCount()的接口,避免遍歷數(shù)組時(shí)造成的gc苏遥。所以上面的例子可以改寫成下面的形式饼拍。

void Update()
{
   var matCount = renderer.GetMaterialCount();
    for(int i = 0; i < matCount;++i)
    {
        var mat = renderer.GetMaterial(i);
        // mat.SetXXX()
    }
}
  1. Time.realtimeSinceStartup在iOS上會(huì)走系統(tǒng)調(diào)用,非常耗田炭,盡可能的少調(diào)用师抄,如下圖是引擎的Lod系統(tǒng)時(shí)每個(gè)幾幀會(huì)調(diào)用這個(gè)接口,導(dǎo)致了一些無用的開銷教硫。改法是把他改成用Time.frameCount叨吮,不必用啟動(dòng)時(shí)間來做diff。


    Time.realtimeSinceStartup的系統(tǒng)調(diào)用
  2. 在大多數(shù)的簡(jiǎn)單渲染場(chǎng)景下瞬矩,如ui茶鉴,Camera的多線程culling在反而比同步culling要耗。引擎內(nèi)部默認(rèn)是用JobSystem來實(shí)現(xiàn)多線程culling的景用,建議加個(gè)Camera的屬性涵叮,讓這個(gè)culling可以動(dòng)態(tài)改成同步執(zhí)行。
  3. il2cpp的內(nèi)存alloc是通過內(nèi)存池(也可以說分塊申請(qǐng))的形式分配的伞插,用instrument是截取不了準(zhǔn)確內(nèi)存申請(qǐng)的堆棧割粮,所以如果某個(gè)時(shí)刻分配了很多內(nèi)存,這個(gè)時(shí)候想找到代碼源頭就比較蛋疼了媚污。一個(gè)粗暴的解決辦法是舀瓢,在il2cpp向內(nèi)存池申請(qǐng)內(nèi)存時(shí),再alloc一個(gè)相同大小的內(nèi)存耗美,這個(gè)時(shí)候instrument就能截到堆棧了氢伟。當(dāng)然這個(gè)方法只在dev包的時(shí)候才可以做,否則代碼內(nèi)存會(huì)翻一翻幽歼。

  1. 打外網(wǎng)包時(shí)要備份library,否則在打資源更新包時(shí)谬盐,如果library沒有備份甸私,而是重新import生成的,其中很多序列化id都是隨機(jī)生成的飞傀,會(huì)跟之前的不一致皇型,而結(jié)果就是在打資源更新包的時(shí)候會(huì)發(fā)現(xiàn)有一堆變更诬烹。
  2. 資源更新和一些cache不要存在Application.temporaryCachePath,在手機(jī)上有可能會(huì)被系統(tǒng)刪掉弃鸦。簡(jiǎn)單的改法是用Application.persistentDataPath绞吁,但對(duì)于iOS,這個(gè)目錄文件過大的話唬格,可能會(huì)不過審家破,最好的做法是放在Library/Application Support/{packageName}里,具體見蘋果的文檔File System Basics
  3. 盡量少直接引用fbx里的子asset购岗。舉個(gè)例子:如果一個(gè)prefab里引用了如下fbx的_cao這個(gè)mesh汰聋,那在打包的時(shí)候調(diào)用就會(huì)把fbx的_cao2、_shui2喊积、_shan這些mesh也打在包里烹困,因?yàn)檫@個(gè)prefab你調(diào)用GetDepdency的時(shí)候會(huì)發(fā)現(xiàn),這個(gè)prefab依賴的是_shui.fbx這個(gè)整體乾吻,所以打包的時(shí)候會(huì)把整個(gè)整體都打進(jìn)去髓梅。解決辦法是把fbx里的子asset單獨(dú)拆出來,并制作規(guī)范禁止直接引用fbx里的資源绎签。


    多asset的fbx在被引用時(shí)要留意打包問題
  4. ip5和ip5c不能開啟Metal和OpenGL ES3枯饿,所以是不支持Half float;而Android則沒有這個(gè)問題辜御。
    所以在處理mesh頂點(diǎn)格式時(shí)要注意鸭你,否則ip5和ip5c會(huì)崩潰。具體原因可參考引擎PrepareMeshDataForBuildTarget::VertexCompression PrepareMeshDataForBuildTarget::GetMaxVertexCompressionForPlatform(BuildTargetSelection targetPlatform)的實(shí)現(xiàn)擒权。
  5. ab包的加載袱巨,在引擎里的實(shí)現(xiàn)會(huì)有各種lock/unlock,這也是為啥unity加載資源卡頓的問題不好解決的原因碳抄。我們后面把a(bǔ)b包做了多線程的異步加載(非協(xié)程的異步加載)愉老,途中改造了不少引擎代碼,到最后幀率才逐漸穩(wěn)定剖效。不過現(xiàn)在看來其實(shí)治標(biāo)不治本嫉入,因?yàn)殒i的消耗還是很高,Unity的官方駐場(chǎng)表示也沒法解決璧尸,畢竟AssetBundle這個(gè)系統(tǒng)設(shè)計(jì)的太臃腫咒林。。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末爷光,一起剝皮案震驚了整個(gè)濱河市垫竞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖欢瞪,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件活烙,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡遣鼓,警方通過查閱死者的電腦和手機(jī)啸盏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來骑祟,“玉大人回懦,你說我怎么就攤上這事≡遥” “怎么了粉怕?”我有些...
    開封第一講書人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)抒巢。 經(jīng)常有香客問我贫贝,道長(zhǎng),這世上最難降的妖魔是什么蛉谜? 我笑而不...
    開封第一講書人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任稚晚,我火速辦了婚禮,結(jié)果婚禮上型诚,老公的妹妹穿的比我還像新娘客燕。我一直安慰自己,他們只是感情好狰贯,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開白布也搓。 她就那樣靜靜地躺著,像睡著了一般涵紊。 火紅的嫁衣襯著肌膚如雪傍妒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評(píng)論 1 302
  • 那天摸柄,我揣著相機(jī)與錄音颤练,去河邊找鬼。 笑死驱负,一個(gè)胖子當(dāng)著我的面吹牛嗦玖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播跃脊,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼宇挫,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了酪术?” 一聲冷哼從身側(cè)響起器瘪,我...
    開封第一講書人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后娱局,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咧七,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年衰齐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片继阻。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡耻涛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瘟檩,到底是詐尸還是另有隱情抹缕,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布墨辛,位于F島的核電站卓研,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏睹簇。R本人自食惡果不足惜奏赘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望太惠。 院中可真熱鬧磨淌,春花似錦、人聲如沸凿渊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽埃脏。三九已至搪锣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間剂癌,已是汗流浹背淤翔。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留佩谷,地道東北人旁壮。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像谐檀,于是被迫代替她去往敵國(guó)和親抡谐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354