[catlike coding]Frames Per Second

原教程地址:Frames Per Second, a Unity C# Tutorial

這個是我的筆記,不是翻譯。所以懶得放圖,oh yeah

主要內容:

- 使用物理引擎生成一個不斷增大的原子核

- 使用Profiler分析性能

- 測量並顯示FPS

- 避免生成臨時字元串

- 對FPS多楨取樣,計算平均值

- 為FPS顯示字元上色

這一章會生成一個簡單的場景,然後測試其性能。性能的測試首先會使用profiler,然後我們會自己建一個fps計數器。

建立一個原子核

要建立一個能代表低性能與高性能情況下的場景,我們選用的是原子核匯聚這樣一個過程。隨著原子核變得越來越大,性能也會逐漸變差。

原子由多個簡單球體構成,其中心位於場景原點。當然這不是準確的表達方式,但這不是重點。

建立一個球體,為球體添加Nucleon這個class:

using UnityEngine;[RequireComponent(typeof(Rigidbody))]public class Nucleon : MonoBehaviour { public float attractionForce; Rigidbody body; void Awake(){ body = GetComponent<Rigidbody>(); } void FixedUpdate() { body.AddForce(transform.localPosition * -attractionForce) } void Update () { }}

這段代碼檢查object是否具有Rigidbody,同時給物體十佳一個指向原點的力,力由用戶決定的一個參數以及物體距離原點的位置決定。

接下來生成兩個Prefab,一個叫做protons一個叫neutron,分別讓顏色設置為紅色和藍色。只用一個Prefab也行,就是單調了一點。

要不斷生成新的質子和中子,需要另外一個class:NucleonSpawner:

using UnityEngine;public class NucleonSpawner : MonoBehaviour { public float timeBetweenSpawns; public float spawnDistance; public Nucleon[] nucleonPrefabs;}

在場景中新建一個game object,加入NucleonSpawner組件,給Nucleon Spawner添加兩個prefab:Proton和Neutron。接下來要記錄自從上次生成物體以後過了多少時間,通過FixedUpdate方法,使用以下的代碼記錄:

float timeSinceLastSpawn; void FixedUpdate () { timeSinceLastSpawn += Time.deltaTime; if (timeSinceLastSpawn >= timeBetweenSpawns) { timeSinceLastSpawn -= timeBetweenSpawns; SpawnNucleon(); } }

SpawnNucleon指向的是實際進行小球生成的代碼。這個過程包括三步:1. 從供選擇的Prefab裡面隨便選一個,2.初始化這個Prefab,3.在指定距離上給這個Prefab一個位置

void SpawnNucleon () { Nucleon prefab = nucleonPrefabs[Random.Range(0, nucleonPrefabs.Length)]; Nucleon spawn = Instantiate<Nucleon>(prefab); spawn.transform.localPosition = Random.onUnitSphere * spawnDistance; }

現在運行代碼的話可以看到很多小球向中心匯聚,中央的球體會越來越大。如果你是看到一群球體像雨一樣落下的話估計你在prefab裡面使用了重力,把Use gravity勾掉,同時注意設置attraction force。

這種球體之間相互碰撞的物理計算會越來越複雜,到了一定的時候你會發現FPS出現下降。

使用Profiler

現在我們有了一個性能逐漸變差的場景,你可以開始測量場景的表現了。最簡單的辦法是打開statistic這個面板。

不過stat面板給出的FPS其實很不準確,Unity的profiler可以提供更為準確的計算。

測量FPS

說到底還是自己寫一個東西計算FPS吧。新建一個類FPSCounter

using UnityEngine;public class FPSCounter : MonoBehaviour { public int FPS{get;private set;}}

注意這裡屬性的寫法。接下來計算FPS:

void Update(){ FPS = (int)(1f/Time.deltaTime);}

由於deltaTime會受到time scale的影響,除非time scale設置為1,否則計算的FPS會出錯,所以應該使用unscaledDeltaTime.

void Update(){ FPS = (int)(1f/Time.unscaledDeltaTime); }

接下來我們要來生成一個UI去顯示這個FPS值。我們可以用Unity的UI來做這個事情。新建一個canvas對象,在裡面放一個panel,panel下面放一然後在FpsDisplay輸入這些代碼:

FPSCounter fpsCounter; void Awake(){ fpsCounter = GetComponent<FPSCounter>(); } void Update(){ fpsLabel.text = fpsCounter.FPS.ToString(); }

這一段呢,自己去看原教程吧。

下面要給panel添加一個組件讓它能夠更新FPS:

using UnityEngine;using UnityEngine.UI;[RequireComponent(typeof(FPSCounter))]public class FPSDisplay : MonoBehaviour { public Text fpsLabel;}

把text拖到編輯器上Fps Label的區域建立連接。然後在FpsDisplay輸入這些代碼:

FPSCounter fpsCounter; void Awake(){ fpsCounter = GetComponent<FPSCounter>(); } void Update(){ fpsLabel.text = fpsCounter.FPS.ToString(); }

這裡在Awake里把FPSCounter存進了fpsCounter這個FPSCounter類型中……哎,我的C#是非常渣的,不清楚為什麼要這麼做呢。據說是緩存,然後不用每次update的時候重新GetComponent一次,說回來為什麼一開始需要GetComponent我也一直沒弄明白。

另外如果FPS大於99以後會顯示不全,對於這種情況顯示99就好:

fpsLabel.text=Mathf.Clamp(fpsCounter.FPS,0,99).ToString();

現在一切運行順利,但是又產生了一個新的問題。每次我們更新text的時候都會生成一個臨時的string,這會耗費內存。有沒有辦法去優化呢?考慮到00到99總共只有100種組合,實際上可以用一個列表把所有數字寫進去,這樣就不用生成臨時string了。

static string[] stringsFrom00To99 = { "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", "91", "92", "93", "94", "95", "96", "97", "98", "99" }; void Update () { fpsLabel.text = stringsFrom00To99[Mathf.Clamp(fpsCounter.FPS, 0, 99)]; }

對FPS值取均值

在FPS不穩定的時候,顯示讀數波動大,不利於讀取。如果能對多個時間點取平均值會更方便閱讀。為FPSCounter類添加一個public變數frameRange。另外把FPS改成AverageFPS更能表達新的意思。注意修改其他引用的地方。

接下來添加一個數組記錄一系列時間點的fps:

int[] fpsBuffer; int fpsBufferIndex; void InitializeBuffer(){ if(frameRange<=0){ frameRange=1; } fpsBuffer = new int[frameRange]; fpsBufferIndex = 0; }

接下來update變得稍微複雜一些:

void Update(){ if(fpsBuffer == null || fpsBuffer.Length != frameRange){ InitializeBuffer(); } UpdateBuffer(); CalculateFPS(); }

UpdateBuffer的基本功能就是每次計算當前的FPS並存儲起來,同時把數組的指針往後走一格:

void UpdateBuffer(){ fpsBuffer[fpsBufferIndex++] = (int)(1f/Time.unscaledDeltaTime); if (fpsBufferIndex>=frameRange){ fpsBufferIndex=0; } }

隨著記錄的增加,總會出現預訂的記錄點(比如說60個)都用完的情況,這個時侯fpsBufferIndex>=frameRange後將index設置為0,於是從頭開始記錄,覆蓋原先的數據。

對於FPS取平均值的代碼如下:

void CalculateFPS(){ int sum=0; for(int i=0;i<frameRange;i++){ sum+=fpsBuffer[i]; } AverageFPS=sum/frameRange; }

現在FPS已經是平均以後的結果了。實際上還可以做得更好,顯示最高和最低FPS,聲明兩個public int:HighestFPS,LowestFPS。

void CalculateFPS(){ int sum=0; int highest=0; int lowest=int.MaxValue; for(int i=0;i<frameRange;i++){ int fps=fpsBuffer[i]; sum+=fps; if(fps>highest){ highest=fps; } if(fps<lowest){ lowest=fps; } } AverageFPS=sum/frameRange; HighestFPS=highest; LowestFPS=lowest; }

注意最高最低值的初始設定以及循環中的修正方法。接下來給panel添加2個額外的text,然後將FPSDisplay也做一下修改。

public Text highestFPSLabel,averageFPSLabel,lowestFPSLabel; void Update(){ highestFPSLabel.text=stringFrom00To99[Mathf.Clamp(fpsCounter.HighestFPS,0,99)]; averageFPSLabel.text=stringFrom00To99[Mathf.Clamp(fpsCounter.AverageFPS,0,99)]; lowestFPSLabel.text = stringFrom00To99[Mathf.Clamp(fpsCounter.LowestFPS,0,99)]; }

給文字上色

啊,已經基本完成了,接下來還可以給ui再做點點美化。先寫一個struct將FPS值與顏色相聯繫。

[System.Serializable] private struct FPSColor { public Color color; public int minimumFPS; } [SerializeField] private FPSColor[] coloring;

上面的代碼我看的雲里霧裡。不過private的struct要想在編輯器裡面設置就需要聲明serializeField。

在進一步編碼以前,我們先把update這一塊字元設置的代碼修改一下:

void Update(){ Display(highestFPSLabel,fpsCounter.HighestFPS); Display(averageFPSLabel,fpsCounter.AverageFPS); Display(highestFPSLabel,fpsCounter.LowestFPS); } void Display(Text label,int fps){ label.text = stringFrom00To99[Mathf.Clamp(fps,0,99)]; }

接下來對字元顏色的修改也在Display函數中完成:

void Display(Text label,int fps){ label.text = stringFrom00To99[Mathf.Clamp(fps,0,99)]; for(int i=0;i<coloring.Length;i++){ if(fps>=coloring[i].minimumFPS){ label.color=coloring[i].color; break; } } }

推薦閱讀:

C++中是否應避免使用C語言函數?
設計C++函數傳參時如何決定使用指針還是引用?
如何看待微軟自己的產品不用自己的庫寫而用Qt寫?
c++primer中文版第五版中關於unique_ptr 的 release 函數的描述是不是有矛盾?

TAG:Unity遊戲引擎 | C | 筆記 |