Skip to content

ted10401/Unity-CSharp-Optimize-Guildline

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

31 Commits
 
 

Repository files navigation

Unity-CSharp-Optimize-Guildline

盡可能的讓判斷條件不要在迴圈中

調整前

private void Update()  
{  
    for (int i = 0; i < array.Length; i++)  
    {  
        if (exampleBool)  
        {  
            ExampleFunction(array[i]);  
        }  
    }  
}  

調整後

private void Update()  
{  
    if (exampleBool)  
    {  
        for (int i = 0; i < array.Length; i++)  
        {  
            ExampleFunction(array[i]);  
        }  
    }  
}  

只有在資料改變時再執行方法

案例1

調整前

private int m_score;  
  
public void AddScore(int value)  
{  
    m_score += value;  
}  
  
private void Update()  
{  
    DisplayScore(m_score);  
}  

調整後

private int m_score;  
  
public void AddScore(int value)  
{  
    m_score += value;  
    DisplayScore(m_score);  
}  

案例2

調整前

private void Update()  
{  
    ExampleGarbageGeneratingFunction(transform.position.x);  
}  

調整後

private float m_curPosX;
private float m_lastPosX;  
  
private void Update()  
{  
    m_curPosX = transform.position.x;  
    if (m_lastPosX != m_curPosX)  
    {
        m_lastPosX = m_curPosX;
        ExampleGarbageGeneratingFunction(m_lastPosX);  
    }  
}  

避免逐幀計算

調整前

private void Update()  
{  
    ExampleExpensiveFunction();  
}  

調整後

private int m_interval = 3;  
  
private void Update()  
{  
    if (Time.frameCount % m_interval == 0)  
    {  
        ExampleExpensiveFunction();  
    }  
}  

使用快取

應盡量避免生成參考類型物件,即任何 new XXXXX(),否則會導致記憶體分配

案例 GetComponent

調整前

private void Update()  
{  
    Renderer renderer = GetComponent<Renderer>();  
    ExampleFunction(renderer);  
}  

調整後

private Renderer m_renderer;  
  
private void Awake()  
{  
    m_renderer = GetComponent<Renderer>();  
}  
  
private void Update()  
{  
    ExampleFunction(m_renderer);  
}  

案例 FindObjectsOfType

調整前

private void OnTriggerEnter(Collider other)  
{  
    Renderer[] renderers = FindObjectsOfType<Renderer>();  
    ExampleFunction(renderers);  
}  

調整後

private Renderer[] m_renderers;  
  
private void Awake()  
{  
    m_renderers = FindObjectsOfType<Renderer>();  
}  
  
private void OnTriggerEnter(Collider other)  
{  
    ExampleFunction(m_renderers);  
}  

案例 List

調整前

private void Update()  
{  
    List list = new List();  
    PopulateList(list);  
}  

調整後

private List m_list = new List();  
void Update()  
{  
    m_list.Clear();  
    PopulateList(m_list);  
}  

案例 new WaitForSeconds

調整前

while (!isDone)  
{  
    yield return new WaitForSeconds(1f);  
}  

調整後

WaitForSeconds delay = new WaitForSeconds(1f);  
  
while (!isDone)  
{  
    yield return delay;  
}  

案例 transform

調整前

public void UpdateCharacter()  
{  
    var lastPos = transform.position;  
    transform.position = lastPos  
        + wantedVelocity * (speed * speedFactor  
        * Mathf.Sin(someOtherFactor)  
        * drag * friction * Time.deltaTime);  
}  

調整後

private Transform m_transform;  
  
private void Awake()  
{  
    m_transform = transform;  
}  
  
public void UpdateCharacter()  
{  
    var lastPos = m_transform.position;  
    m_transform.position = lastPos  
        + wantedVelocity * (speed * speedFactor  
        * Mathf.Sin(someOtherFactor)  
        * drag * friction * Time.deltaTime);  
}  

案例 Time.deltaTime

調整前

public void UpdateCharacter()  
{  
    float factor = speed * speedFactor  
        * Mathf.Sin(someOtherFactor)  
        * drag * friction * Time.deltaTime;  
  
    m_lastPos.x += wantedVelocity.x * factor;  
    m_lastPos.y += wantedVelocity.y * factor;  
    m_lastPos.z += wantedVelocity.z * factor;  
    m_transform.localPosition = m_lastPos;  
}  

調整後

private float m_deltaTime;  
  
public void UpdateCharacter()  
{  
    float factor = speed * speedFactor  
        * Mathf.Sin(someOtherFactor)  
        * drag * friction * m_deltaTime;  
  
    m_lastPos.x += wantedVelocity.x * factor;  
    m_lastPos.y += wantedVelocity.y * factor;  
    m_lastPos.z += wantedVelocity.z * factor;  
    m_transform.localPosition = m_lastPos;  
}  

避免使用 LINQ

雖然 LINQ 簡潔易讀寫,但通常需要更多計算及記憶體配置

避免使用下列 Unity API

使用 GameObject.CompareTag 取代 GameObject.tag

調整前

private const string TAG_PLAYER = "Player";  
  
void OnTriggerEnter(Collider other)  
{  
    bool isPlayer = other.gameObject.tag == TAG_PLAYER;  
}  

調整後

private const string TAG_PLAYER = "Player";  
  
void OnTriggerEnter(Collider other)  
{  
    bool isPlayer = other.gameObject.CompareTag(TAG_PLAYER);  
}  

使用 yield return null 取代 yield return 0

調整前

yield return 0;  

調整後

yield return null; 

減少 Vector 計算

案例1

調整前

public void UpdateCharacter()  
{  
    var lastPos = transform.position;  
    transform.position = lastPos  
        + wantedVelocity * speed * speedFactor  
        * Mathf.Sin(someOtherFactor)  
        * drag * friction * Time.deltaTime;  
}  

調整後

public void UpdateCharacter()  
{  
    var lastPos = transform.position;  
    transform.position = lastPos  
        + wantedVelocity * (speed * speedFactor  
        * Mathf.Sin(someOtherFactor)  
        * drag * friction * Time.deltaTime);  
}  

案例2

調整前

public void UpdateCharacter()  
{  
    m_lastPos += wantedVelocity * (speed * speedFactor  
        * Mathf.Sin(someOtherFactor)  
        * drag * friction * Time.deltaTime);  
    m_transform.localPosition = m_lastPos;  
}  

調整後

public void UpdateCharacter()  
{  
    float factor = speed * speedFactor  
        * Mathf.Sin(someOtherFactor)  
        * drag * friction * Time.deltaTime;  
  
    m_lastPos.x += wantedVelocity.x * factor;  
    m_lastPos.y += wantedVelocity.y * factor;  
    m_lastPos.z += wantedVelocity.z * factor;  
    m_transform.localPosition = m_lastPos;  
}  

盡可能使用 Transform.localPosition

調整前

public void UpdateCharacter()  
{  
    var lastPos = m_transform.position;  
    m_transform.position = lastPos  
        + wantedVelocity * (speed * speedFactor  
        * Mathf.Sin(someOtherFactor)  
        * drag * friction * Time.deltaTime);  
}  

調整後

public void UpdateCharacter()  
{  
    var lastPos = m_transform.localPosition;  
    m_transform.localPosition= lastPos  
        + wantedVelocity * (speed * speedFactor  
        * Mathf.Sin(someOtherFactor)  
        * drag * friction * Time.deltaTime);  
}  

減少取得 Transform.position、Transform.localPosition

調整前

public void UpdateCharacter()  
{  
    var lastPos = m_transform.localPosition;  
    m_transform.localPosition = lastPos  
        + wantedVelocity * (speed * speedFactor  
        * Mathf.Sin(someOtherFactor)  
        * drag * friction * Time.deltaTime);  
}  

調整後

private Vector3 m_lastPos = Vector3.zero;  
  
public void UpdateCharacter()  
{  
    m_lastPos += wantedVelocity * (speed * speedFactor  
        * Mathf.Sin(someOtherFactor)  
        * drag * friction * Time.deltaTime);  
    m_transform.localPosition = m_lastPos;  
}  

避免使用 foreach

調整前

foreach(int value in m_list)  
{  
    DoSomething(value);  
}  

調整後

int length = m_list.Count;  
for(int i = 0; i < length; i++)  
{  
    DoSomething(m_list[i]);  
}  

數據比較 (執行次數 100000)

Time ms GC Alloc
foreach 28.04 48 B
for 17.47 0 B

盡量使用 Array 取代 List

調整前

int length = m_list.Count;  
for(int i = 0; i < length; i++)  
{  
    DoSomething(m_list[i]);  
}  

調整後

int length = m_array.Length;  
for (int i = 0; i < length; i++)  
{  
    DoSomething(m_array[i]);  
}  

數據比較 (執行次數 100000)

Time ms GC Alloc
List 17.44 0 B
Array 8.70 0 B

避免使用 Enum 函式

調整前

Enum.GetName(typeof(State), stringValue);  
Enum.GetName(typeof(State), intValue);  
Enum.GetNames(typeof(State));  
Enum.GetValues(typeof(State));  
Enum.Parse(typeof(State), stringValue);  
 
State result;  
Enum.TryParse<State>(stringValue, out result);  
 
result.ToString();

調整後

FastEnum.GetName<State>(stringValue);  
FastEnum.GetName<State>(intValue);  
FastEnum.GetNames<State>();  
FastEnum.GetValues<State>();  
FastEnum.Parse<State>(stringValue); 
 
State result;  
FastEnum.TryParse<State>(stringValue, out result);  
 
FastEnum.ToString<State>((int)result); 

數據比較 (執行次數 100000)
Enum > FastEnum

Time ms GC Alloc
GetName(string) 339.60 > 18.30 1.9 MB > 0 B
GetName(int) 662.15 > 18.56 3.8 MB > 0 B
GetNames 227.57 > 12.99 3.8 MB > 0 B
GetValues 821.96 > 15.63 8.8 MB > 1.0 KB
Parse 765.91 > 184.60 12.2 MB > 0 B
TryParse 761.73 > 185.73 12.2 MB > 0 B
ToString 579.02 > 18.10 3.8 MB > 0 B

避免使用屬性 Property

屬性 Property 底層依然是透過方法實現,使用 Property 會導致執行效能較差,但使用上及維護上會較為方便,能夠針對 get 或 set 設置不同的訪問層級和檢查機制,且支援任何方法的語言特性,如 virtual、abstract。

調整前

public int intValue { get; set; }  

調整後

public int intValue;  

數據比較 (執行次數 1000000)

Time ms GC Alloc
Property 41.00 0 B
Field 0 0 B

使用 is 或 as 而不是強制類型轉換

is: 能夠檢查一個對象是否兼容於其他指定類型,回傳一個 Bool 值且不會跳出異常
as: 作用跟強制類型轉換一樣,但不會跳出異常,如果轉換失敗,會回傳 null

避免大量使用 MonoBehaviour.Update、FixedUpdate、LateUpdate

由於 Unity MonoBehaviour 使用的是 Messaging System,能夠讓開發者在 MonoBehaviour 中自行定義特殊方法,如: Awake、Start、Update、FixedUpdate、LateUpdate 等。
當有使用大量 MonoBehaviour.Update、FixedUpdate、LateUpdate 需求時,應自定義功能取代。

調整前

using UnityEngine;  
public class TestMonoBehaviour : MonoBehaviour  
{  
    private int m_count;  
    private void Update()  
    {  
        m_count++;  
    }  
}  

調整後

public class TestOptimizedMonoBehaviour : CoreComponent 
{  
    private int m_count;  
    public override void Tick(float deltaTime, float unscaledDeltaTime)  
    {  
        m_count++;  
    }  
}  

數據比較 (執行次數 10000)

Time ms GC Alloc
MonoBehaviour 4.01 0 B
CoreComponent 1.82 0 B

大量字元串接時使用 String.Concat

調整前

char c = 'X';  
string output = string.Empty;  
for(int i = 0; i < stringLength; i++)  
{  
    output += c;  
}  

調整後

char c = 'X';  
char[] chars = new char[stringLength];  
for (int i = 0; i < stringLength; i++)  
{  
    chars[i] = c;  
}  
  
string output = string.Concat(chars);  

數據比較 (執行次數 100000)

Time ms GC Alloc
String += 11723.20 1.32 GB
String.Concat 27.50 3.3 MB

大量字串串接時使用 StringBuilder.Append

調整前 - String +=

string output = string.Empty;
for(int i = 0; i < count; i++)
{
    output += value;
}

調整前 - String.Format

private const string FORMAT = "{0}{1}";
string output = string.Empty;
for (int i = 0; i < count; i++)
{
    output = string.Format(FORMAT, output, value);
}

調整後

m_stringBuilder.Clear();
for (int i = 0; i < count; i++)
{
    m_stringBuilder.Append(value);
}

string output = m_stringBuilder.ToString();

數據比較 (執行次數 10000)

Time ms GC Alloc
String += 563.65 477.1 MB
String.Format 1331.72 1.07 GB
StringBuilder.Append 0.48 97.7 KB

生成大量相同物件使用 Object Pool

調整前

GameObject instance = GameObject.Instantiate(m_prefab);  
GameObject.Destroy(instance);  

調整後

GameObject instance = PoolManager.Instance.Get(m_prefab);  
PoolManager.Instance.Recycle(instance);  

使用 struct 取代 class

調整前

class VectorClass  
{  
    public int X { get; set; }  
    public int Y { get; set; }  
}  
  
private void Execute()  
{  
    VectorClass[] vectors = new VectorClass[10000];  
    for (int i = 0; i < vectors.Length; i++)  
    {  
        vectors[i] = new VectorClass();  
        vectors[i].X = 5;  
        vectors[i].Y = 10;  
    }  
}  

調整後

struct VectorStruct  
{  
    public int X { get; set; }  
    public int Y { get; set; }  
}  
  
private void Execute()  
{  
    VectorStruct[] vectors = new VectorStruct[10000];  
    for (int i = 0; i < vectors.Length; i++)  
    {  
        vectors[i].X = 5;  
        vectors[i].Y = 10;  
    }  
}  

數據比較 (執行次數 10000)

Time ms GC Alloc
class 5.68 312.5 KB
struct 2.00 78.2 KB

避免使用解構子

調整前

class SimpleWithFinalizer  
{  
    public int x { get; set; }  
  
    ~SimpleWithFinalizer()  
    {  
  
    }  
}  
  
private void Execute()  
{  
    for (int i = 0; i < 10000; i++)  
    {  
        new SimpleWithFinalizer();  
    }  
}  

調整後

class Simple  
{  
    public int x { get; set; }  
}  
  
private void Execute()  
{  
    for (int i = 0; i < 10000; i++)  
    {  
        new Simple();  
    }  
}  

數據比較 (執行次數 10000)

Time ms GC Alloc
有解構子 4.48 195.3 KB
沒有解構子 2.14 195.3 KB

盡可能預先快取組件

調整前

[SerializeField] protected Transform m_transform = null;
[SerializeField] protected RectTransform m_rectTransform = null;
[SerializeField] protected Canvas m_canvas = null;
[SerializeField] protected CanvasScaler m_canvasScaler = null;
[SerializeField] protected GraphicRaycaster m_graphicRaycaster = null;
[SerializeField] protected CanvasGroup m_canvasGroup = null;
[SerializeField] protected ScrollRect[] m_scrollRects = null;
[SerializeField] protected InputField[] m_inputFields = null;

private void Awake()
{
    m_transform = transform;
    m_rectTransform = GetComponent<RectTransform>();
    m_canvas = GetComponent<Canvas>();
    m_canvasScaler = GetComponent<CanvasScaler>();
    m_graphicRaycaster = GetComponent<GraphicRaycaster>();
    m_canvasGroup = GetComponent<CanvasGroup>();
    m_scrollRects = GetComponentsInChildren<ScrollRect>(true);
    m_inputFields = GetComponentsInChildren<InputField>(true);
}

調整後

[SerializeField] protected Transform m_transform = null;
[SerializeField] protected RectTransform m_rectTransform = null;
[SerializeField] protected Canvas m_canvas = null;
[SerializeField] protected CanvasScaler m_canvasScaler = null;
[SerializeField] protected GraphicRaycaster m_graphicRaycaster = null;
[SerializeField] protected CanvasGroup m_canvasGroup = null;
[SerializeField] protected ScrollRect[] m_scrollRects = null;
[SerializeField] protected InputField[] m_inputFields = null;

protected override void CacheComponents()
{
    CacheComponent(ref m_transform);
    CacheComponent(ref m_rectTransform);
    CacheComponent(ref m_canvas);
    CacheComponent(ref m_canvasScaler);
    CacheComponent(ref m_graphicRaycaster);
    CacheComponent(ref m_canvasGroup);
    CacheComponentsInChildren(ref m_scrollRects, true);
    CacheComponentsInChildren(ref m_inputFields, true);
}

看不到物件時,關閉不需要執行的組件

當物件存在於場景中,即使物件在可視範圍之外其身上的組件也會持續運作,當這類組件的數量變多時,就會開始影響遊戲性能。
建議當物件在視錐體之外或沒有顯示在畫面上時,關閉將下列組件。

  • CanvasScaler
  • ScrollRect
  • InputField
  • DynamicBone
  • 其他任何昂貴的 MonoBehaviour

設定 Shader 參數時,使用 PropertyToID

Unity 執行後會給予 Shader 參數名稱一個唯一的識別符。
比起使用參數名稱傳遞資料,使用 Shader.PropertyToID 更為高效。

調整前

private const string _Color = "_Color";
private void SetColor(Color value)
{
    m_material.SetColor(_Color, value);
}

調整後

private readonly int _Color = Shader.PropertyToID("_Color");
private void SetColor(Color value)
{
    m_material.SetColor(_Color, value);
}

數據比較 (執行次數 100000)

Time ms GC Alloc
Material.SetColor(string name, Color value) 44.38 0 B
Material.SetColor(int nameID, Color value) 29.83 0 B

設定 Animator 參數時,使用 StringToHash

同理 Shader.PropertyToID,使用 Animator.StringToHash 更為高效。

調整前

private const string _BoolParameterName = "_BoolParameterName";
private void SetBoolParameter(bool value)
{
    m_animator.SetBool(_BoolParameterName, value);
}

調整後

private readonly int _BoolParameterName = Animator.StringToHash("_BoolParameterName");
private void SetBoolParameter(bool value)
{
    m_animator.SetBool(_BoolParameterName, value);
}

數據比較 (執行次數 100000)

Time ms GC Alloc
Animator.SetBool(string name, bool value) 19.86 0 B
Animator.SetBool(int nameID, bool value) 16.78 0 B

預先定義 List 或 Dictionary 的初始化大小

調整前

private void InitializeList(int count)
{
    m_list = new List<int>();
    for(int i = 0; i < count; i++)
    {
        m_list.Add(i);
    }
}

private void InitializeDictionary(int count)
{
    m_dictionary = new Dictionary<int, int>();
    for(int i = 0; i < count; i++)
    {
        m_dictionary.Add(i, i);
    }
}

調整後

private void InitializeList(int count)
{
    m_list = new List<int>(count);
    for(int i = 0; i < count; i++)
    {
        m_list.Add(i);
    }
}

private void InitializeDictionary(int count)
{
    m_dictionary = new Dictionary<int, int>(count);
    for(int i = 0; i < count; i++)
    {
        m_dictionary.Add(i, i);
    }
}

數據比較 (執行次數 100000)

Time ms GC Alloc
List 無初始化大小 16.77 1.0 MB
List 有初始化大小 8.72 390.7 KB
Dictionary 無初始化大小 40.42 5.8 MB
Dictionary 有初始化大小 36.50 2.1 MB

資料來源

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors