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