1. 程式人生 > >Untiy3d-unity遊戲效能優化-詳細介紹

Untiy3d-unity遊戲效能優化-詳細介紹

本文固定連線:http://blog.csdn.net/u013108312/article/details/52723347
我們玩過的遊戲中,神優化和渣優化的案例都不勝列舉,它們直接影響著玩家們的遊戲體驗。這篇文章將從最基礎的概念開始,配合例項講解各種優化的技巧,最終建立起一套屬於自己的優化方法,完成對遊戲專案的評估和優化工作。

1. 簡介

1.什麼是效能優化

1.常見的優化型別

1.效能優化

2.流出優化

3.體驗優化

2.效能優化目標

1.遊戲流暢執行

1.多種幀數標準 30-60幀
2.避免卡頓

2.遊戲符合市場需要

1.硬體相容性
2.安裝包/資料包大小

2.優化常見的誤區

誤區1:我的遊戲很簡單不需要優化 (效能黑點)
誤區2:優化工作儘早進行
誤區3:效能優化=Debug

3.優化的兩大原則

1.不過早做優化
2.使用者不察覺
玩家不一定能發現欠優化的地方
玩家不一定能發現優化欠佳的地方

4.優化的組成部分

  • 指令碼
    – 常見的效能黑點(正確的程式碼放到了錯誤的地方)底層!!!
    – 如何找到需要優化的程式碼

    1.常規迴圈
    2.變數的隱性呼叫
    3.Gmaeobject.Find 儲存到變數中 gameobject go; 查詢一次就可以了。
    4.多執行緒

    IEnumerator Work()
    {
        //執行緒不安全
//StartCoroutine(MyIoWork1()); //StartCoroutine(MyIoWork2()); yield return StartCoroutine(MyIoWork1()); yield return StartCoroutine(MyIoWork2()); } IEnumerator MyIoWork1() { for (int i = 0; i < 1000; i++) { System.IO.File.Delete("c:\a.zip"
); yield return null; } } IEnumerator MyIoWork2() { for (int i = 0; i < 1000; i++) { System.IO.File.Delete("c:\a.zip"); yield return null; } }

5.數學
計算距離:Mathf.Sqrt
計算方向:Vector3.Angle
Minimize use of complex mathematical operations such as pow, sin and cos in pixel shaders.

using UnityEngine;
using System.Collections;

public class TestMagnitude : MonoBehaviour {

    public Transform player1;
    public Transform player2;

    void Start()
    {
        Calc();
    }

    void Calc()
    {
        float distance1 = (player1.position - transform.position).magnitude;
        float distance2 = (player2.position - transform.position).magnitude;
        Debug.Log("Player1 is closer than Player2:" + (distance1 < distance2).ToString());

        float distance11 = (player1.position - transform.position).sqrMagnitude;
        float distance22 = (player2.position - transform.position).sqrMagnitude;
        Debug.Log("Player1 is closer than Player2:" + (distance1 < distance2).ToString());
    }
}
using UnityEngine;
using System.Collections;

public class Compare : MonoBehaviour {

    public Transform player1;
    public Transform player2;

    // Use this for initialization
    void Start () {

        float angle = Vector3.Angle(player2.forward, player1.forward);
        float dot = Vector3.Dot(player2.forward,player1.forward);

        Debug.Log("Dot=" + dot +" Angle=" + angle);
    }

    // Update is called once per frame
    void Update () {

    }
}

6.Object Pool 適用於頻繁操作的物件,需要快取

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class TestPool : MonoBehaviour
{
    public Transform root;
    public GameObject prefab;

    public float loopCount;
    public float prefabCount;
    List<GameObject> objects;

    // Use this for initialization
    void Start()
    {

        objects = new List<GameObject>();
        System.DateTime startTime = System.DateTime.Now;
        TestCaseWaitOutPool();
        System.DateTime endTime = System.DateTime.Now;
        string totalMs = (endTime - startTime).TotalMilliseconds.ToString();

        Debug.Log("Test case Without Pool take " + totalMs + "ms.");
    }

    // Update is called once per frame
    void Update()
    {

    }

    void TestCaseWaitOutPool()
    {
        for (int j = 0; j < loopCount; j++)
        {
            for (int i = 0; i < prefabCount; i++)
            {
                //create prefab
                GameObject go = Instantiate(prefab);
                //set parent
                go.transform.parent = root;
                //add to list
                objects.Add(go);
            }
            for (int i = 0; i < prefabCount; i++)
            {
                GameObject.Destroy(objects[i]);
            }

            //destory prefab
            objects.Clear();
        }

    }
}

使用 Pool 之後。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class TestPool : MonoBehaviour
{
    public SimplePool pool;
    public Transform root;
    public GameObject prefab;

    public float loopCount;
    public float prefabCount;
    List<GameObject> objects;

    // Use this for initialization
    void Start()
    {

        objects = new List<GameObject>();
        // with out pool
        System.DateTime startTime = System.DateTime.Now;
        TestCaseWaitOutPool();
        System.DateTime endTime = System.DateTime.Now;
        string totalMs = (endTime - startTime).TotalMilliseconds.ToString();

        Debug.Log("Test case Without Pool take " + totalMs + "ms.");

        // with pool
        System.DateTime poolstartTime = System.DateTime.Now;
        TestCaseWithPool();
        System.DateTime poolendTime = System.DateTime.Now;
        string pooltotalMs = (poolendTime - poolstartTime).TotalMilliseconds.ToString();

        Debug.Log("Test case With Pool take " + pooltotalMs + "ms.");
    }

    void TestCaseWaitOutPool()
    {
        for (int j = 0; j < loopCount; j++)
        {
            for (int i = 0; i < prefabCount; i++)
            {
                //create prefab
                GameObject go = Instantiate(prefab);
                //set parent
                go.transform.parent = root;
                //add to list
                objects.Add(go);
            }
            for (int i = 0; i < prefabCount; i++)
            {
                GameObject.Destroy(objects[i]);
            }

            //destory prefab
            objects.Clear();
        }

    }

    void TestCaseWithPool()
    {
        for (int i = 0; i < loopCount; i++)
        {
            List<GameObject> objectsList = pool.GetObjects((int)prefabCount);
            pool.DestroyObjects(objectsList);
        }

    }
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class SimplePool : MonoBehaviour
{
    public Transform root;
    public GameObject prefab;
    public int size;

    List<GameObject> pooled;

    void Start()
    {
        pooled = new List<GameObject>();
        Prewarm();
    }
    void Prewarm()
    {
        PoolObjects(size);
    }

    List<GameObject> PoolObjects(int _amount)
    {
        List<GameObject> newPooled = new List<GameObject>();
        for (int i = 0; i < _amount; i++)
        {
            GameObject go = Instantiate(prefab);
            go.transform.parent = root;
            go.SetActive(false);
            newPooled.Add(go);
        }
        pooled.AddRange(newPooled);
        return newPooled;
    }

    public List<GameObject> GetObjects(int _amount)
    {
        List<GameObject> pooledObjects = pooled.FindAll(_go => !_go.activeSelf);
        if (pooledObjects.Count < _amount)
        {
            List<GameObject> newObjects = PoolObjects(_amount - pooledObjects.Count);
            pooledObjects.AddRange(newObjects);
            foreach (var go in pooledObjects)
            {
                go.SetActive(true);
            }
            return pooledObjects;
        }
        else
        {
            foreach (var go in pooledObjects)
            {
                go.SetActive(true);
            }
            return pooledObjects;
        }
    }

    public void DestroyObjects(List<GameObject> objects)
    {
        for (int i = 0; i < objects.Count; i++)
        {
            objects[i].SetActive(false);
        }
    }
}
Test case Without Pool take 226.1487ms.
UnityEngine.Debug:Log(Object)
TestPool:Start() (at Assets/TestPool.cs:26)
Test case With Pool take 58.0407ms.
UnityEngine.Debug:Log(Object)
TestPool:Start() (at Assets/TestPool.cs:34)

時間從 226.1487ms 縮短到58.0407ms。看看吧,這就是效率

7.Total 與 Self
在 Unity-Window-Profiler
Overview 裡面的 Total,Self 觀察 百分百

using UnityEngine;
using System.Collections;

public class TestTime : MonoBehaviour {

    public GameObject prefab;

    void Start()
    {
        //self
        System.Threading.Thread.Sleep(2000);

        Create(); // others
    }

    void Create()
    {
        for (int i = 0; i < 10000; i++)
        {
            GameObject go = GameObject.Instantiate(prefab);
            GameObject.Destroy(go);
        }
    }
}
  • 圖形與物理
    –美術資源
    對Mesh的優化

開啟Game場景Stats 觀察 Tris(三角形),Verts(頂點數)
模型面數
在這裡可以使用外掛 Simple LOD可以生成更低的模型tris減少,但是模型會變得不清晰

using UnityEngine;
using System.Collections;

public class TestLod : MonoBehaviour {

    public float[] camDistance;
    public Mesh[] lod;
    SkinnedMeshRenderer skin;

    // Use this for initialization
    void Start () {

        skin = GetComponent<SkinnedMeshRenderer>();
    }

    // Update is called once per frame
    void Update () {

        int level = GetLevel();
        skin.sharedMesh = lod[level];
    }

    int GetLevel()
    {
        float distance = Vector3.Distance(transform.position,Camera.main.transform.position);
        for (int i = 0; i < camDistance.Length; i++)
        {
            if (distance < camDistance[i])
            {
                return i;
            }
        }
        return camDistance.Length;
    }
}

Material
SetPass Calls 在移動平臺上最好不要超過60或者80,PC 300
2D UI 打包成圖集 減少setpass calls
3D 合併材質
本質 共享材質,壓縮材質

Shader
Main Maps 越少越好
這裡在推薦一個外掛: shader Fore
使用 Mobile 使用 圖形化工具

粒子
數量 數量要限制
透明
材質 /shader 移動端:SM2.0

–物理效果
1.鏡頭
Clipping Planes
Occlusion Culling 預設勾上了。但是沒有任何效果
開啟Window-Occlusion Culling 需要bake 一下
需要bake的東西,必須是Static
Smallers Occluder 擋住後面的東西,優化做不好,就可能是負優化
在Scene場景,選中攝像機,可以設定Occlusion Culling是Edit或者Visualize。這個時候隨著鏡頭的移動,鏡頭中的物體就會動態的顯示了。
從螢幕上看到的點,都不會剔除掉的。
Unity 3專業版內建了一個強大的 Occlusion Culling 外掛 Umbra免費的
2.光照
Bake and Probes
我們的目標就是降低:SetPass Calls
Light 選則Bake光,使用靜態光。出現色差
設定:Scenes In Build Player Setting
找到Color Space 有 Linear(不支援移動端) Gamma
缺點:移動的物體不會受到光照的影響。這個時候就需要建立光照探針了。Light-Light Probe Group.記錄靜態光照的效果。

3.碰撞
Collider儘可能簡單
控制rigidbody數量
Rigidbody檢查方式,檢測間隔,Collision Detection 持續的。離散型的。

4.CheckList
Simple checklist to make your game faster
對於PC建築(取決於目標GPU)時,請記住下面的200K和3M頂點數每幀。
如果你使用內建著色器,從挑選的那些移動或熄滅類別。他們在非移動平臺以及工作,但更復雜的著色器的簡化和近似版本。
保持每個場景低的不同材料的數量,並共享不同的物件儘可能之間儘可能多的材料。
將Static非運動物體的屬性,以允許像內部優化靜態批次。
只有一個(最好是定向)pixel light影響幾何體,而不是整數倍。
烘烤照明,而不是使用動態照明。
儘可能使用壓縮紋理格式,以及超過32位紋理使用16位紋理。
避免使用霧在可能的情況。
使用遮擋剔除,以減少可見的幾何圖形的量和抽取呼叫中的有很多閉塞複雜靜態場景的情況。閉塞記撲殺設計你的水平。
使用包廂到“假”遙遠的幾何體。
使用畫素著色器或紋理組合搭配,而不是多遍方法有幾個紋理。
使用half精度變數在可能的情況。
儘量減少使用複雜的數學運算,如的pow,sin並cos在畫素著色器。
使用每個片段較少紋理。

  • 檔案
    1.AssetBundle 建立 讀取
    設定Prefab。
    AssetBundle New:env/go
using UnityEngine;
using System.Collections;
using System;

public class TestAssetBundle : MonoBehaviour {

    public string path;
    public string file;

    // Use this for initialization
    void Start () {

        StartCoroutine( Load());
    }

    IEnumerator Load()
    {
        string _path = "file:///" + Application.dataPath + path;
        WWW www = WWW.LoadFromCacheOrDownload(_path,1);
        yield return www;

        AssetBundle bundle = www.assetBundle;
        AssetBundleRequest request = bundle.LoadAssetAsync(file);
        yield return request;

        GameObject prefab = request.asset as GameObject;
        Instantiate(prefab);

        //Clean
        bundle.Unload(false);
        www.Dispose();
    }

}

2.移動端打包優化
縮減包體積
設定 Andriod player setting Optimlzation .NET2.0(完整版) .NET2.0 Subset(簡化版)
Stripping Leve Disabled .Strip Assembies. Strip Byte Code(一般用這個就可以了) .Use Micro mscorlib

1.momo version
full
subset
2.stripping level
disabled
strip bute code
3.媒體檔案
圖片 psd/png/jpg
音訊 ogg/mp3/wav
fbx 公用animationclip

3.跨平臺開發效率優化
節省時間 utomate外掛
Debug SRDebugger外掛

–安裝包的優化
–資源包的優化
–工作流程的優化

5.所有遊戲都需要優化嗎?

效能完美是我們追求的目標
不同型別的遊戲對優化的側重點不一樣
優化工作會佔生命週期非常大的一部分

2. Profiler

這裡要多檢視 官網的API 在Manual 裡面搜尋 Profiler
Unity3d-Window-Profiler
Profiler window
CPU Usage Area
Rendering Area
Memory Area
Audio Area
Physics Profiler
GPU Area
觀察Profiler 視窗 測試程式碼:

using UnityEngine;
using System.Collections;

public class BadExampleProfiler : MonoBehaviour {

    public GameObject cube;

    // Update is called once per frame
    void Update () {

        Loop ();
    }

    void Loop()
    {
        for (int i = 0; i < 100; i++) {
            float x = Random.Range (0,360f);
            float y = Random.Range (0,360f);
            float z = Random.Range (0,360f);
            Vector3 randomVector = new Vector3 (x,y,z);
            Object.Instantiate (cube, randomVector, Quaternion.Euler(randomVector));
        }
    }
}

3. 圖形優化

4. 檔案優化

5. 結束

建立一套屬於自己的優化方法
1.確定優化目標 (幀數)(卡頓)
2.選擇合適的工具(Profiler)(SRDebugger)
3.找到效能瓶頸(指令碼)(圖形)(繞開)
4.無法解決(找手冊)(問google)(繞開)
5.經常與團隊溝通