遊戲測試中常見細節優化實踐

遊戲開發中一些細節優化

1.能整不浮,能乘不除

看一下代碼

		float f_a = 66666888f;
        float f_b = f_a + 0.01f;
        Debug.Log(f_a);//1.677722E+07
        Debug.Log(f_b);//1.677722E+07
        Debug.Log("_______________****_______________");
        double d_a = 9007199254740992f;
        double d_b = d_a + 0.01f;
        Debug.Log(d_a);//9.00719925474099E+15
        Debug.Log(d_b);//9.00719925474099E+15

輸出結果;
在這裏插入圖片描述
實際上
一是二者存儲結構不同;
二就是就是浮點數精度的問題;

float把 32位分成了3部分,1位(符號位)8位(指數位)23位(有效數字)那麼 1+ 8 + 23 等於32吧,所以float的32位是這麼來的。23位有效數字就表示float真正能存的精度,23位小數部分是反被儲存的部分,所以它是有24位存儲的,2^24(2的24次方)=16777216 。
如果程序中有一個float的數值運算後的小數部分,如果超過16777216.xxx後運算的結果就會不準確;
double則是1位符號位+11位指數+52位有效數字 = 64位
有效數2^53(2的53次方)=9007199254740992,超過9007199254740992.xxx後運算的結果就會不準確;

浮點數計算結果不同的CPU計算出來可能是不一致的,像幀同步等就基本告別浮點數了,儘量用整型代替浮點型,實在需要可以用定點數,但也要注意是否存在定點數轉回浮點數的現象;
其實還可以巧用位操作符,
位操作符比傳統乘除效率要高,適合大量計算時使用
例如:
int a = 100 >> 1 相當於除2取整 結果爲 50
int a = 100 << 2 相當於乘4取整 結果爲 400
不過不必要過分追求位運算,在許多比較老的微處理器上, 位運算比加減運算略快, 通常位運算比乘除法運算要快很多,但現代架構中, 情況並非如此:位運算的運算速度通常與加法運算相同(仍然快於乘法運算)。在現代處理機架構中編譯器一般會自動優化爲移位運算的。還有很多芯片已經內置了硬件乘法器(乘法器的模型就是基於“移位和相加”的算法);

2.MonoBehaviour

1.MonoBehaviour中,如果沒有相應的事件要處理,要刪除默認的空函數;
Update、FixedUpdate、LateUpdate中,如果沒必要每幀的邏輯,可以降低頻率

Void Update(){
    if(Time.frameCount%6==0{
        //要執行的功能函數
    }
}

2.如果間隔更長,沒必要每幀的邏輯,使用週期性的協程更妥當,例如使用InvokeRepeating函數:

InvokeRepeating();

3.Gameobject不可見時,設置 enabled = false 時,update 就會停止調用。
4. FindGameObjectWithTag或者GetComponent<>等查找操作放在Star()先引用,不要放在Update或者FixedUpdate裏;
5. 協程使用 yield return new WaitForSeconds() 將會每幀導致 大概21Byte GC,而yield return null 會產生 大概9 Byte GC;
我們可以簡單地通過複用一個全局的 WaitForEndOfFrame 對象來優化掉這個開銷:

static WaitForEndOfFrame EndOfTest = new WaitForEndOfFrame();
    // Start is called before the first frame update
    void Start()
    {
        StartCoroutine(Test());
    }
    IEnumerator Test()
    {
        yield return null;

        while (true)
        {
           //原本是yield return new WaitForEndOfFrame();
            yield return EndOfTest;
            
        }
    }

實際上實際上,所有繼承自YieldInstruction 的用於掛起協程的指令類型,都可以使用全局緩存來避免不必要的 GC 負擔。常見的有:
WaitForSeconds
WaitForFixedUpdate
WaitForEndOfFrame
可以自己新建文件xxx.cs這個文件裏,集中地創建了上面這些類型的靜態對象,使用時可以直接這樣:
yield return xxx.GetWaitForSeconds(1.0f);
6.使用內置數據代替新建數據
例如: 使用 Vector3.up 而不是 new Vector(0, 0, 0);
在這裏插入圖片描述
7.緩存組件,緩存Gameobject,調用 GetComponent 函數會有查找開銷,用變量掛載到腳本使用,降低開銷(GetComponent時如果獲取到空的組件也會產生GC)。在腳本掛載對象引用,可減少查找。
8.查找對象標籤用if (go.CompareTag (“xxx”)來代替if (go.tag == “xxx”)。GameObject.tag會在內部循環調用對象分配的標籤屬性,並分配額外的內存,並且效率也更低。
9.使用委託機制代替SendMessage,BroadcastMessage,SendMessageUpwards這三個函數。
因爲它們的實現是一種僞監聽者模式,利用的是反射機制,性能非常低。具體的性能大概是委託的十分之一。

3.Instantiate實例化操作技巧

需要頻繁實例化的gameobject比如子彈,需要放入對象池,可以大量減少實例化,銷燬的開銷。將部分耗時資源進行預載。

4.複雜的UI界面和帶有動畫組件的GameObject不要頻繁切換Active/Deactive

設置Active/Deactive複雜對象所用的耗時會比較大,這裏可以使用一個小技巧,可以將需要Deactive的操作變成將GameObject移動到比較遠的,攝像機之外。然後將GameObject上面的全部Component的enabled屬性設置成false。Active時再重新設置回來

5.使用for或者while代替foreach

foreach每次調用會產生有40Byte左右的GC數據,產生 GC 的根本原因是使用了 using 語句。(GetEnumerator()返回值類型,在
using 中裝箱了)

6.合理使用數組,ArrayList,List

數組:內存中是連續存儲的,索引速度非常快,賦值與修改元素也很簡單。但不利於動態擴展以及移動。因爲數組的缺點,就產生了 ArrayList。
ArrayList:使用該類時必須進行引用,同時繼承了 IList 接口,提供了數據存儲和檢索,ArrayList對象的大小動態伸縮,支持不同類型的結點。
ArrayList雖然很完美,但結點類型是 Object,故不是類型安全的,也可能發生裝箱和拆箱操作,帶來很大的性能耗損。
List是泛型接口,規避了 ArrayList的兩個問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章