您好,登錄后才能下訂單哦!
此總結(jié)由自己經(jīng)驗及網(wǎng)上收集整理優(yōu)化內(nèi)容 包括:
1.代碼方面;
2.函數(shù)使用方面;
3.ngui注意方面;
4.數(shù)學(xué)運算方面;
5.內(nèi)存方面;
6.垃圾回收方面 等等...
總結(jié)如下:
function Update()
{
DoSomeThing();
}
可改為每5幀處理一次:
function Update()
{
if(Time.frameCount % 5 == 0)
{
DoSomeThing();
}
}
function Start()
{
InvokeRepeating("DoSomeThing", 0.5, 1.0);
}
CancelInvoke("你調(diào)用的方法"); 停止InvokeRepeating
比如:
function Update()
{
var pos: Vector3 = transform.position;
}
可改為
private var pos: Vector3;
function Update()
{
pos = transform.position;
}
function Update()
{
if(Time.frameCount % 50 == 0)
{
System.GC.Collect();
}
}
運行時盡量減少 Tris 和 Draw Calls
預(yù)覽的時候,可點開 Stats,查看圖形渲染的開銷情況。特別注意 Tris 和 Draw Calls 這兩個參數(shù)。一般來說,要做到:
Tris 保持在 7.5k 以下
Draw Calls 保持在 35 以下
壓縮 Mesh
導(dǎo)入 3D 模型之后,在不影響顯示效果的前提下,最好打開 Mesh Compression。
Off, Low, Medium, High 這幾個選項,可酌情選取。對于單個Mesh最好使用一個材質(zhì)。
避免大量使用 Unity 自帶的 Sphere 等內(nèi)建 Mesh
Unity 內(nèi)建的 Mesh,多邊形的數(shù)量比較大,如果物體不要求特別圓滑,可導(dǎo)入其他的簡單3D模型代替。
9.如果你做了一個圖集是1024X1024的。此時你的界面上只用了圖集中的一張很小的圖,那么很抱歉1024X1024這張大圖都需要載入你的內(nèi)存里面,1024就是4M的內(nèi)存,如果你做了10個1024的圖集,你的界面上剛好都只用了每個圖集里面的一張小圖,那么再次抱歉你的內(nèi)存直接飆40M。意思是任何一個4096的圖片,不管是圖集還是texture,他都占用4*4=16M?
=====================分割線=====================================================
1、在使用數(shù)組或ArrayList對象時應(yīng)當(dāng)注意
length=myArray.Length;
for(int i=0;i<length;i++)
{
}
for(int i=0;i<myArray.Length;i++)
{
}
2、少使用臨時變量,特別是在Update OnGUI等實時調(diào)用的函數(shù)中。
void Update()
{
Vector3 pos;
pos=transform.position;
}
可以改為:
private Vector3 pos;
void Update()
{
pos=transform.position;
}
3、如果可能,將GameObject上不必要的腳本disable掉。
如果你有一個大的場景在你的游戲中,并且敵方的位置在數(shù)千米意外,
這時你可以disable你的敵方AI腳本直到它們接近攝像機為止。
一個好的途徑來開啟或關(guān)閉GameObject是使用SetActiveRecursively(false),并且球形或盒型碰撞器設(shè)為trigger。
4、刪除空的Update方法。
當(dāng)通過Assets目錄創(chuàng)建新的腳本時,腳本里會包括一個Update方法,當(dāng)你不使用時刪除它。
5、引用一個游戲?qū)ο蟮淖詈虾踹壿嫷慕M件。
有人可能會這樣寫someGameObject.transform,gameObject.rigidbody.transform.gameObject.rigidbody.transform,但是這樣做了一些不必要的工作,你可以在最開始的地方引用它,像這樣:
private Transform myTrans;
void Start()
{
myTrans=transform;
}
6、協(xié)同是一個好方法。
可以使用協(xié)同程序來代替不必每幀都執(zhí)行的方法。(還有InvokeRepeating方法也是一個好的取代Update的方法)。
7、盡可能不要再Update或FixedUpdate中使用搜索方法(例如GameObject.Find()),你可以像前面那樣在Start方法里獲得它。
8、不要使用SendMessage之類的方法,他比直接調(diào)用方法慢了100倍,你可以直接調(diào)用或通過C#的委托來實現(xiàn)。
9、使用javascript或Boo語言時,你最好確定變量的類型,不要使用動態(tài)類型,這樣會降低效率,
你可以在腳本開頭使用#pragmastrict 來檢查,這樣當(dāng)你編譯你的游戲時就不會出現(xiàn)莫名其妙的錯誤了。
==================================================分割線=====================================================
1、頂點性能
一般來說,如果您想在iPhone 3GS或更新的設(shè)備上每幀渲染不超過40,000可見點,那么對于一些配備 MBX GPU的舊設(shè)備(比如,原始的 iPhone,如 iPhone 3g和 iPod Touch第1和第2代)來說,你應(yīng)該保證每幀的渲染頂點在10000以下。
2、光照性能
像素的動態(tài)光照將對每個受影響的像素增加顯著的計算開銷,并可能導(dǎo)致物體會被渲染多次。為了避免這種情況的發(fā)生,您應(yīng)該避免對于任何單個物體都使用多個像素光照,并盡可能地使用方向光。
需要注意的是像素光源是一個渲染模式(Render Mode)設(shè)置為重要(Important)的光源。
像素的動態(tài)光照將對頂點變換增加顯著的開銷。所以,應(yīng)該盡量避免任何給定的物體被多個光源同時照亮的情況。
對于靜態(tài)物體,采用烘焙光照方法則是更為有效的方法。
3、角色
每個角色盡量使用一個Skinned Mesh Renderer,這是因為當(dāng)角色僅有一個 Skinned Mesh Renderer 時,
Unity 會使用可見性裁剪和包圍體更新的方法來優(yōu)化角色的運動,而這種優(yōu)化只有在角色僅含有一個 Skinned Mesh Renderer時才會啟動。
角色的面數(shù)一般不要超過1500,骨骼數(shù)量少于30就好,角色Material數(shù)量一般1~2個為最佳。
4、靜態(tài)物體
對于靜態(tài)物體定點數(shù)要求少于500,UV的取值范圍不要超過(0,1)區(qū)間,這對于紋理的拼合優(yōu)化很有幫助。不要在靜態(tài)物體上附加Animation組件,雖然加了對結(jié)果沒什么影響,但是會增加CPU開銷。
5、攝像機
將遠(yuǎn)平面設(shè)置成合適的距離,遠(yuǎn)平面過大會將一些不必要的物體加入渲染,降低效率。另外我們可以根據(jù)不同的物體來設(shè)置攝像機的遠(yuǎn)裁剪平面。Unity 提供了可以根據(jù)不同的 layer 來設(shè)置不同的 view distance ,
所以我們可以實現(xiàn)將物體進行分層,大物體層設(shè)置的可視距離大些,而小物體層可以設(shè)置地小些,
另外,一些開銷比較大的實體(如粒子系統(tǒng))可以設(shè)置得更小些等等。
6、DrawCall
盡可能地減少 Drawcall 的數(shù)量。 IOS 設(shè)備上建議不超過 100 。
減少的方法主要有如下幾種: Frustum Culling ,Occlusion Culling , Texture Packing 。 Frustum Culling 是 Unity 內(nèi)建的,我們需要做的就是尋求一個合適的遠(yuǎn)裁剪平面;
Occlusion Culling ,遮擋剔除, Unity 內(nèi)嵌了 Umbra ,一個非常好 OC 庫。
但 Occlusion Culling 也并不是放之四海而皆準(zhǔn)的,有時候進行 OC 反而比不進行還要慢,
建議在 OC 之前先確定自己的場景是否適合利用 OC 來優(yōu)化; Texture Packing ,或者叫 Texture Atlasing ,
是將同種 shader 的紋理進行拼合,根據(jù) Unity 的 static batching 的特性來減少 draw call 。
建議使用,但也有弊端,那就是一定要將場景中距離相近的實體紋理進行拼合,否則,拼合后很可能會增加每幀渲染所需的紋理大小,
加大內(nèi)存帶寬的負(fù)擔(dān)。這也就是為什么會出現(xiàn)“ DrawCall 降了,渲染速度也變慢了”的原因。
7.粒子系統(tǒng)運行在iPhone上時很慢,怎么辦?
答:iPhone擁有相對較低的fillrate 。
如果您的粒子效果覆蓋大部分的屏幕,而且是multiple layers的,這樣即使最簡單的shader,也能讓iPhone傻眼。
我們建議把您的粒子效果baking成紋理序列圖。
然后在運行時可以使用1-2個粒子,通過動畫紋理來顯示它們。這種方式可以取得很好的效果,以最小的代價。
===========================================分割線==============================
1.操作transform.localPosition的時候請小心
移動GameObject是非常平常的一件事情,以下代碼看起來很簡單:
transform.localPosition += new Vector3 ( 10.0f * Time.deltaTime, 0.0f, 0.0f );
但是小心了,假設(shè)上面這個GameObject有一個parent, 并且這個parent GameObject的localScale是(2.0f,2.0f,2.0f)。你的GameObject將會移動20.0個單位/秒。
因為該 GameObject的world position等于:
Vector3 offset = new Vector3( my.localPosition.x parent.lossyScale.x, my.localPosition.y parent.lossyScale.y, my.localPosition.z * parent.lossyScale.z );
Vector3 worldPosition = parent.position + parent.rotation * offset;
換句話說,上面這種直接操作localPosition的方式是在沒有考慮scale計算的時候進行的,為了解決這個問題,Unity3D提供了Translate函數(shù),
所以正確的做法應(yīng)該是:
transform.Translate ( 10.0f * Time.deltaTime, 0.0f, 0.0f );
曝出在Inspector的變量同樣的也能被Animation View Editor所使用
有時候我們會想用Unity3D自帶的Animation View Editor來做一些簡單的動畫操作。而Animation Editor不僅可以操作Unity3D自身的component,
還可以操作我們自定義的MonoBehavior中的各個Property。所以加入 你有個float值需要用曲線操作,你可以簡單的將它曝出到成可以serialize的類型,如:
public float foobar = 1.0f;
這樣,這個變量不僅會在Inspector中出現(xiàn),還可以在animation view中進行操作,生成AnimationClip供我們通過AnimationComponent調(diào)用。
范例:
public class TestCurve : MonoBehaviour
{
public float foobar = 0.0f;
IEnumerator Start ()
{
yield return new WaitForSeconds (2.0f);
animation.Play("foobar_op");
InvokeRepeating ( "LogFoobar", 0.0f, 0.2f );
yield return new WaitForSeconds (animation["foobar_op"].length);
CancelInvoke ("LogFoobar");
}
void LogFoobar ()
{
Debug.Log("foobar = " + foobar);
}
}
2.GetComopnent<T> 可以取父類類型
Unity3D 允許我們對MonoBehavior做派生,所以你可能有以下代碼:
public class foo : MonoBehaviour { ...}
public class bar : foo { ...}
假設(shè)我們現(xiàn)在有A,B兩個GameObject, A包含foo, B包含bar, 當(dāng)我們寫
foo comp1 = A.GetComponent<foo>();
bar comp2 = B.GetComponent<bar>();
可以看到comp1, comp2都得到了應(yīng)得的Component。那如果我們對B的操作改成:
foo comp2 = B.GetComponent<foo>();
答案是comp2還是會返回bar Component并且轉(zhuǎn)換為foo類型。你同樣可以用向下轉(zhuǎn)換得到有效變量:
bar comp2_bar = comp2 as bar;
合理利用GetComponent<base_type>()可以讓我們設(shè)計Component的時候耦合性更低。
3.Invoke, yield 等函數(shù)會受 Time.timeScale 影響
Unity3D提供了一個十分方便的調(diào)節(jié)時間的函數(shù)Time.timeScale。對于初次使用Unity3D的使用者,會誤導(dǎo)性的認(rèn)為Time.timeScale同樣可以適用于游戲中的暫停(Pause)和開始(Resume)。
所以很多人有習(xí)慣寫:
Time.timeScale = 0.0f;
對于游戲的暫停/開始,是游戲系統(tǒng)設(shè)計的一部分,而Time.timeScale不不是用于這個部分的操作。
正確的做法應(yīng)該是搜集需要暫停的腳本或 GameObject,
通過設(shè)置他們的enabled = false 來停止他們的腳本活動或者通過特定函數(shù)來設(shè)置這些物件暫停時需要關(guān)閉那些操作。
Time.timeScale 更多的是用于游戲中慢鏡頭的播放等操作,在服務(wù)器端主導(dǎo)的游戲中更應(yīng)該避免此類操作。
值得一提的是,Unity3D的許多時間相關(guān)的函數(shù)都和 timeScale掛鉤,而timeScale = 0.0f將使這些函數(shù)或動畫處于完全停止的狀態(tài),這也是為什么它不適合做暫停操作的主要原因。
這里列出受timeScale影響的一些主要函數(shù)和Component:
MonoBehaviour.Invoke(…)
MonoBehaviour.InvokeRepeating(…)
yield WaitForSeconds(…)
GameObject.Destroy(…)
Animation Component
Time.time, Time.deltaTime
…
4.Coroutine 和 IEnumerator的關(guān)系
初寫Unity3D C#腳本的時候,我們經(jīng)常會犯的錯誤是調(diào)用Coroutine函數(shù)忘記使用StartCoroutine的方式。如:
IEnumerator CoLog ()
{
yield return new WaitForSeconds (2.0f);
Debug.Log("hello foobar");
}
當(dāng)我們用以下代碼去調(diào)用上述函數(shù):
TestCoroutine testCo = GetComponent<TestCoroutine>();
testCo.CoLog ();
testCo.StartCoroutine ( "CoLog" );
那么testCo.CoLog()的調(diào)用將不會起任何作用。
5.StartCoroutine, InvokeRepeating 和其調(diào)用者關(guān)聯(lián)
通常我們只在一份GameObject中去調(diào)用StartCoroutine或者InvokeRepeating,我們寫:
StartCoroutine ( Foobar() );
InvokeRepeating ( "Foobar", 0.0f, 0.1f );
所以如果這個GameObject被disable或者destroy了,這些coroutine和invokes將會被取消。就好比我們手動調(diào)用:
StopAllCoroutines ();
CancelInvoke ();
這看上去很美妙,對于AI來說,這就像告訴一個NPC你已經(jīng)死了,你自己的那些小動作就都聽下來吧。
但是注意了,假如這樣的代碼用在了一個Manager類型的控制AI上,他有可能去控制其他的AI, 也有可能通過Invoke, Coroutine去做一些微線程的操作,這個時候就要明確StartCoroutine或者InvokeRepeating的調(diào)用者的設(shè)計。討論之前我 們先要理解,StartCoroutine或InvokeRepeating的調(diào)用會在該MonoBehavior中開啟一份Thread State, 并將需要操作的函數(shù),變量以及計時器放入這份Stack中通過并在引擎每幀Update的最后,Renderer渲染之前統(tǒng)一做處理。所以如果這個 MonoBehavior被Destroy了,那么這份Thread State也就隨之消失,那么所有他存儲的調(diào)用也就失效了。
如果有兩份GameObject A和B, 他們互相知道對方,假如A中通過StartCoroutine或InvokeRepeating去調(diào)用B的函數(shù)從而控制B,這個時候Thread State是存放在A里,當(dāng)A被disable或者destroy了,這些可能需要一段時間的控制函數(shù)也就失效了,這個時候B明明還沒死,也不會動了。更 好的做法是讓在A的函數(shù)中通過B.StartCoroutine ( … ) 讓這份Thread State存放于B中。
// class TestCortouine
public class TestCoroutine : MonoBehaviour
{
public IEnumerator CoLog ( string _name )
{
Debug.Log(_name + " hello foobar 01");
yield return new WaitForSeconds (2.0f);
Debug.Log(_name + " hello foobar 02"); }}
// component attached on GameObject A
public class A: MonoBehaviour
{
public GameObject B;
void Start ()
{
TestCoroutine compB = B.GetComponent<TestCoroutine>();
// GOOD, thread state in B
// same as: comp
B.StartCoroutine ( "CoLog", "B" );
compB.StartCoroutine ( compB.CoLog("B") );
// BAD, thread state in A
StartCoroutine ( compB.CoLog("A") );
Debug.Log("Bye bye A, we'll miss you");
Destroy(gameObject);
/ / T_T I don't want to die...
}
}
以上代碼,得到的結(jié)果將會是:
B hello foobar 01
A hello foobar 01
Bye bye A, we'll miss you
B hello foobar 02
6.如不需要Start, Update, LateUpdate函數(shù),請去掉他們
當(dāng)你的腳本里沒有任何Start, Update, LateUpdate函數(shù)的時候,Unity3D將不會將它們加入到他的Update List中,有利于腳本整體效率的提升。
們可以從這兩個腳本中看到區(qū)別:
Update_01.cs
public class Update_01 : MonoBehaviour
{
void Start () {}
void Update () {}
}
Update_02.cs
public class Update_02 : MonoBehaviour
{
}
===========================================分割線==============
1.減少固定增量時間
將固定增量時間值設(shè)定在0.04-0.067區(qū)間(即,每秒15-25幀)。您可以通過Edit->Project Settings->Time來改變這個值。這樣做降低了FixedUpdate函數(shù)被調(diào)用的頻率以及物理引擎執(zhí)行碰撞檢測與剛體更新的頻率。如果您使用了較低的固定增量時間,并且在主角身上使用了剛體部件,那么您可以啟用插值辦法來平滑剛體組件。
2.減少GetComponent的調(diào)用使用 GetComponent或內(nèi)置組件訪問器會產(chǎn)生明顯的開銷。您可以通過一次獲取組件的引用來避免開銷,并將該引用分配給一個變量(有時稱為"緩存"的引用)。
例如,如果您使用如下的代碼:
void Update ()
{
transform.Translate(0, 1, 0);
}
通過下面的更改您將獲得更好的性能:
Transform myTransform = null;
void Awake ()
{
myTransform = transform;
}
void Update ()
{
myTransform.Translate(0, 1, 0);
}
3.避免分配內(nèi)存
您應(yīng)該避免分配新對象,除非你真的需要,因為他們不再在使用時,會增加垃圾回收系統(tǒng)的開銷。
您可以經(jīng)常重復(fù)使用數(shù)組和其他對象,而不是分配新的數(shù)組或?qū)ο?。這樣做好處則是盡量減少垃圾的回收工作。
同時,在某些可能的情況下,您也可以使用結(jié)構(gòu)(struct)來代替類(class)。
這是因為,結(jié)構(gòu)變量主要存放在棧區(qū)而非堆區(qū)。因為棧的分配較快,并且不調(diào)用垃圾回收操作,所以當(dāng)結(jié)構(gòu)變量比較小時可以提升程序的運行性能。
但是當(dāng)結(jié)構(gòu)體較大時,雖然它仍可避免分配/回收的開銷,而它由于"傳值"操作也會導(dǎo)致單獨的開銷,實際上它可能比等效對象類的效率還要低。
4.最小化GUI
使用GUILayout 函數(shù)可以很方便地將GUI元素進行自動布局。然而,這種自動化自然也附帶著一定的處理開銷。
您可以通過手動的GUI功能布局來避免這種開銷。
此外,您也可以設(shè)置一個腳本的useGUILayout變量為 false來完全禁用GUI布局:
void Awake ()
{
useGUILayout = false;
}
5.使用iOS腳本調(diào)用優(yōu)化功能
UnityEngine 命名空間中的函數(shù)的大多數(shù)是在 C/c + +中實現(xiàn)的。
從Mono的腳本調(diào)用 C/C++函數(shù)也存在著一定的性能開銷。
您可以使用iOS腳本調(diào)用優(yōu)化功能(菜單:Edit->Project Settings->Player)讓每幀節(jié)省1-4毫秒。
此設(shè)置的選項有:
Slow and Safe – Mono內(nèi)部默認(rèn)的處理異常的調(diào)用
Fast and Exceptions Unsupported –一個快速執(zhí)行的Mono內(nèi)部調(diào)用。
不過,它并不支持異常,因此應(yīng)謹(jǐn)慎使用。
它對于不需要顯式地處理異常(也不需要對異常進行處理)的應(yīng)用程序來說,是一個理想的候選項。
6.優(yōu)化垃圾回收
如上文所述,您應(yīng)該盡量避免分配操作。
但是,考慮到它們是不能完全杜絕的,所以我們提供兩種方法來讓您盡量減少它們在游戲運行時的使用:
如果堆比較小,則進行快速而頻繁的垃圾回收
這一策略比較適合運行時間較長的游戲,其中幀率是否平滑過渡是主要的考慮因素。像這樣的游戲通常會頻繁地分配小塊內(nèi)存,但這些小塊內(nèi)存只是暫時地被使用。如果在iOS系統(tǒng)上使用該策略,那么一個典型的堆大小是大約 200 KB,這樣在iPhone 3G設(shè)備上,垃圾回收操作將耗時大約 5毫秒。如果堆大小增加到1 MB時,該回收操作將耗時大約 7ms。
因此,在普通幀的間隔期進行垃圾回收有時候是一個不錯的選擇。
通常,這種做法會讓回收操作執(zhí)行的更加頻繁(有些回收操作并不是嚴(yán)格必須進行的),但它們可以快速處理并且對游戲的影響很小:
if (Time.frameCount % 30 == 0)
{
System.GC.Collect();
}
但是,您應(yīng)該小心地使用這種技術(shù),并且通過檢查Profiler來確保這種操作確實可以降低您游戲的垃圾回收時間
如果堆比較大,則進行緩慢且不頻繁的垃圾回收
這一策略適合于那些內(nèi)存分配 (和回收)相對不頻繁,并且可以在游戲停頓期間進行處理的游戲。
如果堆足夠大,但還沒有大到被系統(tǒng)關(guān)掉的話,這種方法是比較適用的。
但是,Mono運行時會盡可能地避免堆的自動擴大。
因此,您需要通過在啟動過程中預(yù)分配一些空間來手動擴展堆(ie,你實例化一個純粹影響內(nèi)存管理器分配的"無用"對象):
void function Start()
{
var tmp = new System.Object[1024];
// make allocations in smaller blocks to avoid them to be treated in a special way, which is designed for large blocks
for (int i = 0; i < 1024; i++)
{
tmp = new byte[1024];
// release reference
tmp = null;
}
游戲中的暫停是用來對堆內(nèi)存進行回收,而一個足夠大的堆應(yīng)該不會在游戲的暫停與暫停之間被完全占滿。所以,當(dāng)這種游戲暫停發(fā)生時,您可以顯式請求一次垃圾回收:
System.GC.Collect();
另外,您應(yīng)該謹(jǐn)慎地使用這一策略并時刻關(guān)注Profiler的統(tǒng)計結(jié)果,而不是假定它已經(jīng)達(dá)到了您想要的效果。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。