2010年11月27日至28日,在虛幻的引擎技術(shù)開放日非現(xiàn)實開放會議上,Yumi互助娛樂公司高級技術(shù)總監(jiān)韓天陽就ue4移動游戲的性能優(yōu)化進行了專題分享。從該公司生產(chǎn)的兩款游戲開始,他告訴你生產(chǎn)過程中面臨的優(yōu)化問題,以及一些優(yōu)化的想法。
以下是劉薇的講話全文:
大家好,大家好,我是Yumi互助娛樂公司的高級技術(shù)總監(jiān)韓天陽,很榮幸能有這樣的機會與大家分享和交流。我們公司成立于2016年,是一家初創(chuàng)企業(yè),主要的創(chuàng)業(yè)人員來自金山和長游,擁有十多年的游戲開發(fā)經(jīng)驗,我們公司從成立之初就把精品游戲的開發(fā)作為我們的發(fā)展方向,然后我們認為--移動游戲的后續(xù)發(fā)展將成為第二代手機游戲的發(fā)展趨勢,所以我們選擇了ue4引擎。
最近幾年,我們開發(fā)了兩款手機游戲,一款是天空之門,另一款是救贖之地。其中之一的天空之門是一個開放的MMORPG,救贖之地是一個全方位的PBR工藝俯瞰競爭的游戲,游戲預(yù)計將在不久的將來推出。天空之門已經(jīng)上線了,在這兩款游戲的開發(fā)中,其實我們也使用了一些常用的移動游戲不敢使用的一些功能和效果,同時也給我們的優(yōu)化帶來了很大的壓力,今天是與大家分享這個主題也關(guān)系到我們的ue4手機游戲的性能優(yōu)化。因為我們開發(fā)的兩個游戲也是移動游戲的實用版本,不同版本的ue4,這些東西的總結(jié)在以后的實際應(yīng)用中可能會有一些不同,如果有一些錯誤,也歡迎您改正。
為什么我們要特別關(guān)注移動游戲的性能優(yōu)化,因為首先,它在我們的國內(nèi)硬件設(shè)備。在國內(nèi)Android市場上,高、低端設(shè)備長期并存,Android平臺上低端設(shè)備所占比例很大。以今年的模型為例,就像你在今年20年中所做的那樣。它已經(jīng)發(fā)布了很長時間,但是你仍然可以在市場上看到Snap巨龍435,而且手機的處理器跨度比較大,屬于16年。我們?nèi)匀豢梢钥吹?,他們的性能從GPU可能是幾十倍的不同。為了給玩家一個好的體驗,我們還必須優(yōu)化這些低端機器.因此,如果你像iOS一樣,它的設(shè)備類型相對簡單,硬件性能更強。我們今天交流的主要內(nèi)容就是針對這個Android平臺。
我們剛才提到了我們面臨的游戲類型,比如MMORPG和開銷競爭游戲,所以MMORPG本身,它需要開放的視角,需要看到很多這樣的場景,也可以看到大量的角色,每個角色被分成多個部分,包括武器,競爭性游戲需要高幀率,非Carton幀速率也很穩(wěn)定,在后期階段,可能會有10到20個人在一個區(qū)域內(nèi)戰(zhàn)斗。其特點是人口多、UI多、特效多。我們在優(yōu)化中所面臨的問題可以分為以下幾個維度,一個是內(nèi)存,一個是幀速率,一個是Carton,還有一個是博弈效應(yīng)。隨后的分享也將從這些維度逐一討論。
關(guān)于優(yōu)化的思路首先就是我們要對我們面對的目標機型進行分檔,我們面對的目標機型是什么樣的,每一檔我們關(guān)注點是什么,要達到什么樣的指標,這個就涉及到一個量化,每一檔我們的性能指標是什么,確定了這些以后我們就開始對項目進行優(yōu)化,并且進行迭代。
舉個例子,那么比如說我們對那個《救贖之地》它的這個高中低機型的分檔是這樣的,高端機是從驍龍845開始,驍龍845及以上的,中端機從驍龍660開始及以上的,低端機是驍龍435 為一個我們的樣板機。你會發(fā)現(xiàn)就是整個的這幾個機型,它的上市年份從16年到18年的都有,16年的機器和18年的機器它顯然不是一個檔次的,上市的價格也是從800塊錢到2000多塊錢都有,這也說明了本身我們的這個國內(nèi)安卓機分配市場的復(fù)雜性。
優(yōu)化的思路就是首先就要進行匹配分檔,在UE4引擎里面提供一個比較方便的就是Base Device Profiles.ini,在里面可以配置Matching Rules,那么我們在Matching Rules里面就可以把各種機型根據(jù)GPU或者根據(jù)手機的型號,進行一些分檔,把這些具體的每一檔次的信息在Default Device Profiles.ini里面進行控制。比如說我們可以去根據(jù)不同的這個檔次去控制它的陰影,控制它的Bloom Quality,比如說Mobile Content Scale Factor之類的這些東西。
首先作為優(yōu)化,我們需要首先就是要定位這個整個的性能瓶頸,性能瓶頸到底出現(xiàn)在哪,比如說它的幀率比較低,它到底是卡在CPU上還是卡在GPU上,我們最簡單的方式就是可以通過這個stat fps和stat unit 獲得這些東西的信息,那么在運行時我們就可以看到,跑的這個幀率和各個主要的這些線程的這么一個關(guān)系.如果這個我們從那個stat unit上可以看出來,像我們一個Frame他花了多少時間,我們再看Game、Draw和GPU大概花了多長時間,大概就可以去定位它到底是卡在什么地方,因為這個Frame它代表了這一幀的時間,這一幀的時間 主要就是和Game、Draw以及GPU 它里面哪一個時間花的最長,和它的時間是相匹配的。
我們在使用的這個優(yōu)化時候用到的比較多的工具,在CPU方面用到的是UE4的前端工具,GPU方面我們用的比較多的就是高通的Pro?filer,那么CPU的瓶頸 常見就在Game線程上,通??梢苑殖蛇@么幾個點,第一就是Tick,第二就是藍圖中的大量的廣播事件,然還有就是藍圖中的大量的使用循環(huán)、數(shù)學運算這些東西,還有就是引擎的Character Movement,另外還有UI Slate Prepass音效并發(fā),Skeleton?Mesh的更新,以上這幾個都是我們在項目中實際遇到的問題,后續(xù)會給大家逐一講解。
1.Tick。這個本身它是必不可少的一個東西,我們能做其實就是減少Tick的頻率并且合理設(shè)置各個Actor和各個組件的Tick interval,Interval就是每一個Tick的間隔。舉個例子我們默認每一個角色其實都是按照一定的固定的Tick在跑,我們面對的角色比如說主角還有一些其他的怪,這些東西有一些可能離我們非常遠的,這些離我們比較遠的這一些怪物或者人物,我們其實就是可以調(diào)節(jié)它的Tick的頻率,讓它以一個比較低的Tick頻率去運行從而減少一部分的CPU開銷。對不敏感的邏輯可以進行這些分幀的計算,舉個例子比如說我們游戲里面UI上有一些紅點提示,這些東西它對于這個整個的要求 不要求那么實時,但它的一個計算量很大,本來我們?nèi)绻阉旁谝粠锩嫒プ?,那可能這一幀的開銷就會比較大,如果我們把它分散到10幀里面去做,主觀感受上不會有太大的差異,但是CPU會比較省。
我們還有一些功能開銷比較大的,比如說我們的游戲里面使用到的那個一些債務(wù)的和可見性不可見的計算,這些東西我們都使用多線程去分擔Game的壓力,我們經(jīng)常會將藍圖中特別耗時的計算的移到C++函數(shù)里面,我們發(fā)現(xiàn)就是藍圖里面處理一些純邏輯方面的東西,包括一些數(shù)學運算什么的,它本身比起C++來講的話,它還是稍微要慢一些的,把這些東西尤其是大量的數(shù)學計算從藍圖里面移到C++里面去會節(jié)省出可觀的CPU時間。
2.藍圖中大量的廣播事件響應(yīng)。那么對于廣播事件也是在大規(guī)模的應(yīng)用的時候 需要非常小心的一個事情,我們要控制廣播的影響,廣播消息的影響范圍。舉個例子,我們游戲里面每一個角色其實它都有一個名字板,上面顯示了一些血條一些其他的信息,如果在少的時候看不出來,但如果在非常多的時候,比如說我同時有幾十只怪和這個人在那里戰(zhàn)斗的時候,你就會發(fā)現(xiàn)這些廣播消息它響應(yīng)的比較頻繁。我們的血量變化這個東西可能會同步給所有的人,但每一個人其實他只關(guān)心的又是自己的,于是它的名字板藍圖的事件響應(yīng)里面判斷了一堆東西之后,最后看這不是我的,只有一個人響應(yīng)了這個消息,但是我們可能有四五十個人,但是只有一個人響應(yīng)這個消息,這個時候其實是比較虧的。
我們其實完全可以在C++里面進行判斷,在藍圖里面每一個名字板 ,只想要自己的這個消息就可以了,不要去擴大消息的廣播,還有就是控制廣播消息的頻度,還是拿剛才的例子說,那如果我接受到一個HP變動這個消息,那血量變動其實我有時候一幀里面會接收到很多次,我在和一些怪物去戰(zhàn)斗的時候,那可能同時和幾十只怪,在戰(zhàn)斗的時候它的血量變化是非常頻繁的,但對于玩家的感官上來看,他其實注意不到就是一幀中的多次變化,所以我們可以把它合并一下每一幀處理一次,感受上差不多。還有一個就是控制這些就是響應(yīng)消息的處理的復(fù)雜度,如果有太復(fù)雜的東西盡量就是別在藍圖里面處理。
3.藍圖中大量的使用循環(huán)。數(shù)學運算、查找等這些密集計算的操作這些也都比較費,盡量的這些東西不要在Tick中進行循環(huán)。復(fù)雜的邏輯還是要把它從我們的藍圖里面挪出去,藍圖里面盡量地只控制處理的流程。
4.Character Movement。我們在大量的玩家移動的時候,發(fā)現(xiàn)這些玩家移動的Movement的組件這個Tick的耗時很高,后來查了一下代碼,本質(zhì)上除了Movement它自己的Tick消耗以外,它還要處理很多其它的東西,比如說要處理一個地方是不是可行走,是不是可以站在前面,是不是有坡這些東西,這一些東西其實是非常消耗CPU時間的。除了主角以外,我們其他的角色盡量使用從服務(wù)器發(fā)來的位置進行插值計算模擬一下玩家的位置和他的狀態(tài),這樣也能減少一部分消耗。
5.UI Slate Prepass。在我們的UI比較復(fù)雜的情況下會發(fā)現(xiàn)UI Slate Prepass這個消耗比較大,UI Slate Prepass主要是用來處理,各個UI的位置以及這些位置刷新的這些東西,當UI的這些組件比較多的時候它是非常消耗的,比較有效的方式其實是可以通過Invalidation Box來緩存Prepass的數(shù)據(jù),這樣盡量減少位置更新的計算,這個實際測試了一下,還是能帶來比較好的效果。還可以嘗試用控制臺的這個指令Slate.Enable?Layout?Caching或者Slate.Enable?Global?Invalidation,這兩個也都是比較有效的方式,也能夠大量地減少我們的Prepass的開銷。
6.音效并發(fā)。我們的MMO游戲遇到過大量的音效并發(fā)的情況。我們同時和幾十只怪進行戰(zhàn)斗可能放了一個群攻特效,可能每一個音效可能會被播放了幾十次甚至上百次幾百次的量級,我們可以通過控制聲音的并發(fā)來減少它的同時并發(fā)的數(shù)量,這樣也能減少一些爆破音的出現(xiàn)。
7.我們也遇到過一些Skeleton Mesh更新的問題。在一些低端機上,本來它的CPU其實就不是很強,UE4里面如果我們在把骨骼控制在75根之內(nèi),并且每一個頂點受到了骨骼這個影響的數(shù)量少于4根情況下,引擎默認會使用GPU的蒙皮,它不會使用CPU的 效率會比較高,如果超過這個限制這個時候,它就會使用CPU的蒙皮。對于CPU的蒙皮來講,第一本身 它對CPU的消耗比較大,第二對于低端機來講,它肯定會加加重CPU的負擔,在我們之前的一個項目里面也遇到過情況,到最后我們是把資源進行了修改,保證骨骼的頂點都在這個限制之內(nèi),這樣的話GPU的蒙皮比CPU的這個蒙皮的話本身要快非常多。
除了剛才提到的CPU的這些消耗以外其實還有很多GPU的消耗,GPU這個瓶頸主要來源于渲染壓力太大,我們也進行了一些分類,比如說Draw?Call、材質(zhì)的復(fù)雜度,Shadow,屏幕的分辨率,還有一些后處理效果以及大面積的Alpha疊加。我們針對這些情況會進行一些LOD的處理。
1.Draw?Call。對于Static?Mesh場景中大量的這些Mesh都是Static?Mesh,對于這些Static?Mesh我們可以通過Merge?Actors 引擎提供的這個功能來合并,盡量把同樣的這個Material的Actor合并成同一個Actor。這樣的話它在繪制的時候一次就繪制出來了,這個能有效地減少咱們的Draw?Call。在移動設(shè)備上它每一個這個合并完的Mesh的頂點數(shù)應(yīng)該要控制在65535之內(nèi),否則可能會出現(xiàn)問題,可能它會畫不出來,還有一個就是UI的的消耗 其實對Draw?Call也是消耗的比較大的,我們也進行了一些Sprite的進行合,也進行了一個用的UI Sprite進行,同樣的這個Sprite的UI會進行合批,也能減少很多Draw?Call。需要注意的就是通過最新的機型發(fā)現(xiàn),隨著機型機器的效果,機器的性能越來越好,UI的簡單的Draw?Call性能的影響其實是越來越小的,但是相反,機器越差的話可能這個影響會越大。
2.材質(zhì)復(fù)雜度。它分為很多的方面,一個就是材質(zhì)本身它的運算節(jié)點,它的采樣其他的一些東西,還有就是它本身的加減乘除運算,這些都會影響材質(zhì)的復(fù)雜度,材質(zhì)節(jié)點提供了一個比較好的優(yōu)化的方法叫Quality?Switch,我們就可以使用這個Quality?Switch進行高端機和低端機的區(qū)分。通過在控制臺里面我們就去設(shè)置這個Quality Label,它就會讓整個這個材質(zhì)走不同的Quality?Switch的分支。我們可以在低端機的分支上處理這些運算的時候,例如我們在處理一些紋理混合的時候是不是可以少混合幾張,是不是我們在進行其他的法線運算的時候也可以進行一些其他的優(yōu)化,通過這個Quality?Switch很方便地實現(xiàn)。
材質(zhì)屬性里面的這個Fully Rough其實也會起到很好的效果,同樣的渲染起來它其實會 減少很多的Shader的運算,實際效果非常好。不過它可能會對效果有一定的影響,所以使用這個的時候我們可能要一邊測試一邊使用。另外一個可以通過設(shè)置紋理的MipBias,通過這個方式對打開了MipMap的紋理進行紋理的偏移,偏移之后因為本身它是采樣的不同層,會對我們的采樣的消耗有一定的影響,會有好的效果,本身它也會對填充率有一定的影響。
3.Shadow。移動設(shè)備上其實是推薦使用Custom?UV的,對低端機上就是影子的開銷,尤其是實時陰影它的開銷其實是特別大的,我們可以通過這個Shadow?Quality控制實時陰影的質(zhì)量,低端機甚至可以直接把它關(guān)掉,只給主角開一個實時陰影,其他的玩家為了我們表現(xiàn)效果可以加一些假的那種面片的陰影上去。
4.Mobile?Content?Scale?Factor和Screen?Percentage。這兩個對GPU的性能影響非常顯著,可以根據(jù)不同的檔次進行調(diào)節(jié),也可以通過這個控制臺實時地去切換,需要注意的是因為它本身設(shè)置完了以后是會改變渲染的這個Back?Buffer的大小從而提高性能的,當我們把它拉伸到這個手機屏幕上去就會發(fā)現(xiàn),這個可能會使畫面有一些模糊,它是影響畫質(zhì)的一個選項。使用的時候我們需要注意,當模糊比較厲害的時候可能還需要開AA去解決問題。
5.后處理效果。對于低端機消耗極大的是后處理效果,尤其是大量的后處理,好幾個后處理疊加在一起的時候,那么GPU弱的機器,我們其實也可以根據(jù)項目的實際需求確定了是不是要把這個后處理關(guān)掉,因為這個東西對這個低端機的GPU壓力太大。
6.面積的Alpha的疊加。在我們比如說像 多人戰(zhàn)斗的時候,會出現(xiàn)多個粒子系統(tǒng)的疊加,這個時候就會導致大面積的Alpha的疊加,這個大面積的Alpha面片的疊加本身導致的結(jié)果就是性能會急劇下降,這個肯定是卡在GPU上的,同時它也會導致發(fā)熱相當嚴重。這個時候我們可以考慮設(shè)置Particle的LOD來減緩一部分問題,那么我們可以在做粒子系統(tǒng)的時候把粒子系統(tǒng)的 LOD 也做了,這個在多人戰(zhàn)斗的時候是比較有效的。
7.HLOD、Proxy Level、LOD、Detail?Mode、Max?DrallDistance。還有剛才提到的Draw?Call和Mesh的合并,除了這個Mesh的合并,其實我們還有很多LOD可以結(jié)合著使用,引擎提供了這么一種LOD ,一個本身就是單個Actor的LOD這個單個Actor的LOD 它不會減少Draw?Call,但是它可以為Actor設(shè)置不同的貼圖,你給它設(shè)置不同的紋理也可以達到效果,達到減少渲染的壓力的作用,我們離得比較遠的Actor是不是可以給它考慮設(shè)一個不受光照影響的貼圖,不受光照影響的Material它其實比默認的Default lit 性能要高很多,離遠了我們是不是也發(fā)現(xiàn)不了區(qū)別,那就可以嘗試。
HLOD和距離遠的Actor它可以有效地減少Draw?Call,因為我們可以把多個小的Actor合并成一個HLOD的Actor,那么這個HLOD的Actor它用一張貼圖,用一個材質(zhì)就可以解決問題,這個時候我們會發(fā)現(xiàn)Draw?Call少了,渲染效率會有大幅的提升。Proxy Level這個代理場景它適合用于特別遠的場景,比如說離我們非常遠 有這么一個地塊上面都有很多的東西,它可以極大地減少Draw?Call,只是它的顯示效果比較粗糙,同樣也意味著它的效率會比較高。
有些時候可能我們覺得有了HLOD了是不是還有必要開這個Proxy Level,它的主要的用途就是說當我們的HLOD 其實它是隨著我們要加載的場景再加載出來的,但是如果本身這個場景沒有加載出來,其實這個HLOD它也是加載不出來的。舉個例子,我們的節(jié)能表默認的可能是加載兩三百米的場景,有些時候因為我們有飛行坐騎的系統(tǒng),可能飛行的比較高,一下子可能看出幾千米去,你會發(fā)現(xiàn)這個時候遠處的東西其實并沒有用這個 Level Streaming 加載出來,因為太遠了。遠處就出現(xiàn)了很多比較空曠的情況,這個時候Proxy Level就能比較好地解決問題,它會在在沒有加載出真實場景的時候,它先把代理場景加載出來,因為比較遠的所以效果我們也是可以接受的,如果覺得不行,這個Proxy Level其實也可以調(diào)節(jié)多級,根據(jù)不同的遠近調(diào)整效果以達到目的。
在低端機上我們可以設(shè)置一些Detail?Mode,比如說有一些裝飾的一些草和地表的一些細節(jié)什么的,沒有什么邏輯影響的我們就可以把它減掉緩解一部分GPU的壓力,尤其是針對那種帶半透明的物體,其實也是比較有效的一種方法。Max?DrallDistance它可以控制最遠可見的這個Actor的距離,當設(shè)置完了這個以后一些比較遠的這個Actor它就直接被剔除掉了,它不會顯示在我們屏幕上,也是一種比較有效的優(yōu)化效果。在我們做這個兩個項目的優(yōu)化的時候這幾個這些方案其實都是摻雜在一起用的,單獨用某一個方案可能都沒有辦法解決所有的問題,而這個其實也需要一個非常仔細的調(diào)節(jié)才能做到效率和性能的平衡。
說完了這個效率,再提到一個卡頓,有些時候?qū)τ谧鰞?yōu)化不是非常熟悉的這些,可能會覺得這個卡頓和那個幀率低這兩個是一個事情,但這個就是卡頓和那個幀率低它不是一個事情,幀率低可能是因為我們的運算量太大,它的運算量一直大,它可能持續(xù)的地比如說十幀、二十幀這樣的,那它是幀率低。但是對于卡頓,它是說在突然之間幀率有20幀,或者說突然之間由30幀掉到10幀馬上它可能又恢復(fù)了二三十幀的這個樣子,那么感受是非常差的。
通常有些時候比較有意思,我們在面對外部玩家的時候,可能外部玩家其實很難分得清楚到底哪一個是卡頓,到底哪一個是幀率低,這個時候我們?nèi)y試了,可能玩家就反饋 卡的厲害他說的這個卡到底是因為幀率低還是因為出現(xiàn)忽然掉幀的情況,我們其實是要詳細地進行去分析的,這個時候我們就用到了Unreal里面的前端工具,這個工具其實它對分析卡頓其實是非常有用的,因為我們可以直接看到每一幀 它的Game線程的波峰波谷,當有出現(xiàn)一個特別特別高的這個卡頓的時候,它這一幀的時間就會花的特別長,我們可以很方便地就把這一幀就是抓下來從這個數(shù)據(jù)里面去分析,并且定位到到底是因為是什么,這個工具它可以很方便提供咱們的堆棧信息。
在我們優(yōu)化的過程中其實也對這些優(yōu)化的點進行了一些分析,大概卡頓主要出現(xiàn)在這么如下的幾個方面,一個是GC,一個是資源加載,一個是Actor的這個動態(tài)創(chuàng)建。
1.GC。本身GC其實它是一個非常耗時的工作,因為它要遍歷這些Object并且發(fā)現(xiàn)這些Object 哪一個是有引用的哪一個是沒有引用的,哪一個它可以釋放的,當我們的Object相當多的時候,本身它這個GC的時間也會跟著增長,在引擎里面提供了一個Max?Object?Not?Considered?By?GC這么一個選項,默認設(shè)置成1的話可以有效地提高整個GC的效率,因為設(shè)置成1以后引擎默認創(chuàng)建的一些對象將不再會GC的時候進行計算,從而可以少遍歷一些有這個的,這樣的話會有一定的效率提升。
當我們的本身的內(nèi)存不是那么緊張的時候,也是可以考慮GC,稍微時間長一些的話,這樣的話它卡頓的現(xiàn)象也能減少,它每一次的GC的時間不會像之前的那么長,那么的這些就會導致頻繁的卡頓。GC的卡頓有可能是在我們的那個性能分析工具里面去看到的最高的那么一個。
2.資源加載。尤其需要注意的是我們加載資源的時候,有時候會調(diào)用那個Static?Load?Object,當我們調(diào)用Static?Load?Object的時候,由于引擎沒有辦法判斷這些資源的相關(guān)的依賴性,它首先會做的一件事情,它會Flush 異步加載的資源,這就有一個問題,如果我們正在手動或者自動地或者引擎 調(diào)用了一些異步加載的函數(shù)比如說我們發(fā)現(xiàn)現(xiàn)在加載了一個人,那么我遠處看見一個人,我需要把它資源加載上來,如果用同步就很慢,同步這個東西它加載的時候就會很卡,我們就會把它設(shè)置成異步,異步加載它的這些衣服上的貼圖,異步地去加載一些它其它的一些資源之類的。這個時候如果剛好我們有一個其他的功能打開了,比如說一個UI這個Static?Load?Object,這個時候它就會先把異步加載的資源要求它先加載完成,所有的異步加載就都必須在這一幀馬上完成,這個時候就會帶來一個極大的卡頓,所以有些時候受這個影響比較大的。
在Level Streaming我們的場景地圖在進行切換加載的時候,其實它有一些東西也是異步的,這個時候有些時候我們可能會非常奇怪,我加載了就這么一個Static Load Object的,加載了這么一個小小的這么一個UI為什么會造成這么大的一個卡頓,其實這個時候可能它會有一些其他的這些Level Streaming加載,或者說咱們一個異步加載的一些東西在里面,那么這些東西它才是真正造成卡頓的原因,所以對于Static Load Object的方式,我們在使用中一定要非常非常地小心。
另外打開Texture?Streaming可以提高一些紋理的加載速度,一旦打開了Texture?Streaming它加載的時候其實會先顯示一些比較粗糙的精度的模型,它先從小的加載再把大的加載完了以后才會顯示出一些比較精細的就是上面的貼圖效果。資源加載這一塊,異步加載其實相對來講較慢,但它能緩解卡頓。所以Static?Load?Object它同步加載的這個它會加載的比較快,但是卡頓也是比較明顯,所以我們其實會傾向于比如說一些需要Static?Load?Object,盡量我們在加載場景讀條的時候把這些東西都加載完,場景里面如果有一些比較大的問題盡量使用異步的加載,就是少使用Static?Load?Object方式,因為它會有太多的不可控的東西。
3.Actor的動態(tài)創(chuàng)建。我們的這個場景里面殺怪可能有一波怪我們放了幾個AOE 技能,AOE的技能直接就把他們都殺死了,這個時候它又會重新創(chuàng)建,有些時候因為我們的Actor帶著很多的Component,怪身上帶了很多的那個組件有不同的功能,還有不同的藍圖組件,這個時候我們?nèi)討B(tài)創(chuàng)建一個Actor的時候就會造成一定的卡頓,當你的這個Component越多,你卡頓就會越明顯,你Component少的時候還好,但是我們會面臨著因為剛才提到我們因為涉及到游戲類型,我們可能會進行一些AOE的操作,比如說十幾個怪 一下子都殺死了,同時一下子又都加載出來了,那這個時候比較有效的方法第一就是要減少它Component的數(shù)量,同時它本身自己的貼圖精度也好,它的動作也好,要盡量簡單,我們加載它的這個時候相對來講就會好很多,但是這個不能解決根本問題。
我們也可以減少這個Actor的創(chuàng)建次數(shù),不要頻繁地生成和銷毀這些Actor,剛才提到的也可以進行分幀加載,我每一幀只加載一個,但是這樣又造成一個問題,比如說我十幾只怪都死了,那可能我加載出來它這個速度會非常的慢,分成了好幾幀可能有一些怪 它就跑了或者其他的感受又不好,所以我們后來把所有的就是重復(fù)的比較多的,比如說怪 同樣的怪特別多,那么我們就會為它創(chuàng)建一些Actor池,這樣的話我們當一個怪死了以后我們不會直接把它Destroy,我們減少動態(tài)創(chuàng)建,每次從池里面把它拿出來,當它Destroy的時候我們再放回這個池里,這是可以有效的減少方式,創(chuàng)建這個的時間幾乎就是沒有消耗的。
要解決卡頓,我認為其實就是創(chuàng)建Actor池可能是一個終結(jié)的解決辦法,尤其對Actor的比較復(fù)雜的一種情況,當然大家如果有一些其他比較好的方法也可以一起來討論。
剛才提到的就是整個的那個我們的造成幀率低和卡頓的原因,后續(xù)還有一個就是對于我們比較重要的其實是一個內(nèi)存,一個好的消息是我們現(xiàn)在機器 隨著機器越來越好,我們現(xiàn)在已經(jīng)能見到有配置12g的內(nèi)存的機器了,有沒有16g的我還沒見著,但這個基本上已經(jīng)快趕上我們的PC機了,但是同時我們又不得不面對很多低端機,比如說像我們的剛才提到的那個比較老的機器,它可能只有兩g或者三g的內(nèi)存,這些的時候如果我們的內(nèi)存占用比較大,那么它崩潰的幾率就會比較大,很可能我們的東西還沒有加載完它崩潰了。
這里面又有幾個比較值得注意的東西,就是關(guān)于安卓機Armv7和Arm64的區(qū)別。ARM64它可用的內(nèi)存會多一些,如果我們只有一個ARM V7的包,那ARM V7它能夠利用的內(nèi)存可能只到了個2g以內(nèi)它肯定會崩潰,可能在一個1.45g的時候1.6g的時候可能它就已經(jīng)非常接近崩潰的邊緣了。我們其實就是應(yīng)該盡量的去使用ARM64的包,ARM64的包其實它的可用內(nèi)存會多一些,在我們使用內(nèi)存不太過分的情況下它崩的幾率就比ARM V7的要小要小很多了,例如我們使用到的1.7、1.8g的內(nèi)存可能它也不會崩潰。
我們?nèi)ゲ檫@個內(nèi)存的工具除了用一些ADB的指令以外,如果我們想去分析到底它占了多少內(nèi)存,我們用的Memory Report這個log比較多,以4.25這個引擎的log為例,有這么幾個東西是我們著重可以去觀察的,一個是Process,就是整個進程占用的實際內(nèi)存是多少,另外的一個是通過引擎這分配的系統(tǒng)內(nèi)存是多少,還有一個是那個顯示所用的內(nèi)存是多少。當然顯示所用的這個內(nèi)存其實由于它是驅(qū)動層面去分配的,有可能也是分配在顯存里面或者其他方面的,RHI這部分內(nèi)存引擎log進行的是一些估算,會有一定的偏差,但是可以給我們一定的作為我們?nèi)?yōu)化了這么一個參考的依據(jù)。
通過除了這幾個內(nèi)存以外,我們可能還會發(fā)現(xiàn)它和我們統(tǒng)計出來的系統(tǒng)內(nèi)存和顯示內(nèi)存加起來,比這個進程實際占用的內(nèi)存要低一些,那是因為第一有一些第三方庫其實也會分配一些內(nèi)存,這些東西可能不在我們的這個系統(tǒng)內(nèi)存統(tǒng)計范圍內(nèi),還有就是我們本身自己運行安卓的SO以及一些其他的這些東西現(xiàn)在也會占用一部分內(nèi)存,這些都是沒有接入引擎統(tǒng)計范疇內(nèi)的,但是實際又確確實實地占用了它的進程實際的內(nèi)存之中。
我們常見的一些內(nèi)存的優(yōu)化的方式,剛才也提到了一些像我們的這個Level?Streaming,用Level?Streaming 本身就是可以讓我們的地塊進行這個分塊加載,還有一個就是剛才也提到了就是Texture?Streaming。另外的一個就是說通過對于RHI內(nèi)存,我們可以多去考慮一下紋理的精度到底需要到一個什么樣的層面。
1.Level?Streaming 。它需要注意的是本身它是根據(jù)我們的這個包圍盒來進行加載的,那么合理設(shè)置包圍盒和加載距離是非常重要的,它可以防止在某一個交界處人物來回移動頻繁造成場景加載卸載的情況,你會覺得我就在這里走來走去的,會發(fā)現(xiàn)它的卡頓就非常的明顯,往往都是因為我們的這個Level?Streaming。
Actor的序列化以及這些東西造成的一些問題。我們在子關(guān)卡的大小和加載之間需要做好權(quán)衡,太大了內(nèi)存太高,太小了這個加載又會頻繁那種卡頓,感受也都也都不是非常好。包括在做地形的時候,其實我們也有一些方式可以去 考慮,比如說我們做這個每一個這個子關(guān)卡的時候,它如果是一個比較接近于方形的,那在我們使用的時候會比較好用,如果非常不巧你把這個東西做成了一個梯形的,或者做成了一個L型的,這樣的話那你會發(fā)現(xiàn),它其實只有窄窄的一溜,但是在整個的這么一個大的Bounding?Box里面的話都需要去加載,這一塊就是也是需要我們?nèi)ブ谱髦行枰プ⒁獾囊粋€東西。
2.Texture?Streaming。由于這個UI它本身Texture一般不設(shè)置Mipmap,它是無法從這個 Texture?Streaming中獲得優(yōu)化的,除了通過Pool?Size去設(shè)置這個內(nèi)存以外,它還可以加快這個Texture的紋理的加載速度,這個剛才已經(jīng)提到過了。我們設(shè)置完了這個Pool?Size以后,它可以比較好地去控制整個這個使用的紋理的總量,當有一些超出池的這個Pool?Size的設(shè)置之外的,它可能會優(yōu)先的把一些遠處的一些東西把它 替換成那個低的Mipmap。
3.紋理的精度。我們可以很清楚地發(fā)現(xiàn)每一個就是說紋理的精度是什么,我們可以通過剛才提到的那個 log可以查到每一張紋理它大概占了多少的內(nèi)存,以及它是多大的,首先要考慮這些紋理有沒有減小,比如說我用了2048的貼圖是不是要用這么高的,還有我們可以通過設(shè)置這個在低端機上設(shè)置一些Mobile?Reduce?Loaded?Mips,這個可以把它最頂層的那一層忽略掉,這樣的話在它加載的時候也會減少很多的內(nèi)存。
因為紋理差一半的話它的大小要差了4倍,所以這塊的話其實是可以很好地控制它的大小的。這些東西我們都可以在剛才提到的我們的Profiling里面去設(shè)置,根據(jù)高端機和低端機不同再切換,低端機既然內(nèi)存小的話,那我們有一些紋理的精度可能降了一些也是可以理解的。
那么剛才也大致講了一下就是我之前優(yōu)化過的一些東西,還有一些我關(guān)于這個優(yōu)化的幾點思考。
首先就是說優(yōu)化,我認為應(yīng)該是盡量的不損失游戲的效果以及功能,那么在不損失這個游戲效果和功能的情況下進行優(yōu)化了其實才是這個最優(yōu)的選擇。另外還有就是以產(chǎn)品的一個角度思考問題,它需要這個設(shè)計資源和技術(shù)的共同努力,有些時候如果只靠技術(shù)方向解決可能有一些東西不見得解決的非常完美,如果很多能從設(shè)計角度去規(guī)避的東西才是最優(yōu)的選擇。從設(shè)計完了以后,如果從資源的角度再能控制好,那么我們在技術(shù)上就可以少用一些剛才提到的技術(shù)手段。
優(yōu)化其實它是貫穿游戲開發(fā)始終的這么一個過程,它其實本身就是說是不斷地做,不斷地優(yōu)化,往往有些時候可能大家都想是不是我們可以開始的時候不做,我一開始的就是把所有的這些效果都做出來,等到了差不多快上線的時候我再去優(yōu)化,其實這個時候往往你會發(fā)現(xiàn)來不及,因為各種東西涉及到的問題太多。我認為持續(xù)的優(yōu)化它是對產(chǎn)品品質(zhì)的這么一個不懈的追求,那么我們 為什么要做優(yōu)化,其實總結(jié)成一句話,就是要將更好的效果帶給更多的用戶,使更多的用戶可以體驗到我們真正的高品質(zhì)的游戲,這就是關(guān)于優(yōu)化的一些思考。
謝謝大家!
405959