2017年7月23日 星期日

[效能調教] 十個提升遊戲執行效率的 Script 撰寫建議 by Playdead (Part 2)

Part 1 連結 、演講影片簡報檔

6、Actually… don’t do Vector math at all! (不要對 Vector 做運算 )

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    Transform _transform;

    // Use this for initialization
    void Start()
    {
        _transform = transform;
    }

    public void before()
    {
        cachedLocalPosition += wantedVelocity * (speed * speedfactor
                             * Mathf.Sin(someOtherFactor)
                             * drag * friction * Time.deltaTime);
        _transform.localPosition = cachedLocalPosition;
    }

    public void after()
    {
        float factor = speed * speedfactor
                     * Mathf.Sin(someOtherFactor)
                     * drag * friction * Time.deltaTime;

        cachedLocalPosition.x += wantedVelocity.x * factor;
        cachedLocalPosition.y += wantedVelocity.y * factor;
        cachedLocalPosition.z += wantedVelocity.z * factor;
        _transform.localPosition = cachedLocalPosition;
    }
承接第一點 (減少 Vector 的運算) 的建議 ,
這裡Playdead工作室建議的不只是減少對 Vector 做運算了,
直接建議大家完全不要對 Vector 做運算!

所以在這裡的優化範例中,他們將下面的計算 :
        cachedLocalPosition += wantedVelocity * (speed * speedfactor
                             * Mathf.Sin(someOtherFactor)
                             * drag * friction * Time.deltaTime);
猜分為先計算變化量係數 factor  :
        float factor = speed * speedfactor
                     * Mathf.Sin(someOtherFactor)
                     * drag * friction * Time.deltaTime;
然後在針對 Vector 內的 XYZ 做個別計算:
        cachedLocalPosition.x += wantedVelocity.x * factor;
        cachedLocalPosition.y += wantedVelocity.y * factor;
        cachedLocalPosition.z += wantedVelocity.z * factor;

7、Time.deltaTime could be cached ( Time.deltaTime 也用快取吧! )

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    public void before()
    {
        float factor = speed * speedfactor
                     * Mathf.Sin(someOtherFactor)
                     * drag * friction * Time.deltaTime;

        cachedLocalPosition.x += wantedVelocity.x * factor;
        cachedLocalPosition.y += wantedVelocity.y * factor;
        cachedLocalPosition.z += wantedVelocity.z * factor;
        _transform.localPosition = cachedLocalPosition;
    }

    public void after()
    {
        float factor = speed * speedfactor
                     * Mathf.Sin(someOtherFactor)
                     * drag * friction * main.globalDeltaTime;

        cachedLocalPosition.x += wantedVelocity.x * factor;
        cachedLocalPosition.y += wantedVelocity.y * factor;
        cachedLocalPosition.z += wantedVelocity.z * factor;
        _transform.localPosition = cachedLocalPosition;

    }
這其概念即承接前面的優化建議第4點【減少向 Unity 查找資料的頻率】,
向 Unity 引擎查找資料其實是會消耗一些效能的。
因此 Playdead 建議也將 Time.deltaTime 統一儲存在一個全域變數供需要的程序存取。

8、Don’t use “foreach” - ever ( 永遠不要使用 foreach )

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    void Update()
    {
        // do a lot of iterations:
        for (int a = 0; a < 1000; ++a)
        {
            ScriptPerformanceTest.globalDeltaTime = Time.deltaTime;
            for (int i = 0, cnt = scriptList.Count; i < cnt; ++i)
                scriptList[i].UpdateCharacter();
        }
    }

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    void Update ()
    {
        // do a lot of iterations:
        for (int a = 0; a<1000; ++a)
        {
            ScriptPerformanceTest.globalDeltaTime=Time.deltaTime;
            foreach (var script in scriptList)
                script.UpdateCharacter();
        }
    }
foreach 雖然寫起來非常簡短,
但是 for...Loop 在Unity中執行起來還是比較節省效能。
雖然 比起 foreach 寫起來會比較長一些些,
但為了在有限的硬體資源中讓遊戲跑得更順,這點麻煩算不了什麼吧?

9、Arrays! Not Lists. (用Arrays 而非Lists! )

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    public List<ScriptPerformanceTest> scriptList;

    void Update()
    {
        for (int a = 0; a < 1000; ++a)
        {
            ScriptPerformanceTest.globalDeltaTime = Time.deltaTime;
            for (int i = 0, cnt = scriptList.Count; i < cnt; ++i)
                scriptList[i].UpdateCharacter();
        }
    }

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    // this array will be filled with 100 elements in “Start()”
    public ScriptPerformanceTest[] scriptArray;

    void Update()
    {
        for (int a = 0; a < 1000; ++a)
        {
            ScriptPerformanceTest.globalDeltaTime = Time.deltaTime;
            for (int i = 0, cnt = scriptArray.Length; i < cnt; ++i)
                scriptArray[i].UpdateCharacter();
        }
    }
... 這點真的是讓我蠻訝異的 ! 因為我一直以為 Array 比較慢 @@
我超級喜歡使用List類型的陣列,
不用特別宣告長度這一點真的很方便。
P.S : 不過簡報中也提到實際節省的效率差異不大,請大家自行斟酌囉

10、Avoid using Update() and FixedUpdate() . (自製更新管理系統 )


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
using UnityEngine;
using System.Collections.Generic;

public class UpdateManager : MonoBehaviour 
{

 private ManagedUpdateBehavior[] list;

 private void Start () 
 {
  list = GetComponents<ManagedUpdateBehavior>();
 }
 
 private void Update () {
  var count = list.Length;
  for (var i = 0; i < count; i++)
  {
   list[i].UpdateMe();
  }
 }
}
在 Unity 中使用 Update() 或是 FixedUpdate() 時,
會產生一些不會記錄在側寫器 (Profiler)的運算資源消耗。
因此 Playdead 建議 Unity 使用者自行撰寫一個 Update-manager。
將有所有的Script記錄在一個Array之中,並由Update-manger統一執行。
Playdade在簡報中表這麼做使得 Inside 在 XBox 及 Ps4 版上獲得 1、2ms的效能提升。

P.S 1 老實說這段我沒有很確定 @@ 大家可以自己看影片 ... 連結
P.S 2 上面的程式碼並沒有出現在Playdead的簡報中 ... 來源
P.S 3  其他延伸閱讀 : 10000 Update ... 英文中文

===============================================================
Playdead 在這個演講中其實提到非常多他們在實務開發時所採用的作法,
非常值得大家花點時間去參考學習一下。之後我有稍微看懂一些後也會再發文分享。

文章若有什麼我理解錯誤的,歡迎大家提出來討論唷!

沒有留言:

張貼留言