這篇文章主要講解了“Unity游戲開發的性能衡量方法”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Unity游戲開發的性能衡量方法”吧!
建立原子核
我們需要一個測試場景。理想地涵蓋高性能和低性能情況的一種。我建議我們通過將越來越多的核子融合在一起來創建原子核。隨著細胞核變大,性能會變差。
核子將是簡單的球體,將被吸引到場景的中心,在那里它們會聚成一個球。這當然不是原子的正確表示,但這不是重點。
我們可以使用默認球體和自定義Nucleon
組件對核子建模。該組件可確保將剛體附著到其對象,然后將其簡單地拉向原點。拉力的強度取決于可配置的吸引力和距中心的距離。
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);
}
}
是的,我現在省略了字段和方法聲明中的private修飾符,因為默認情況下它們是私有的。讓我們看看它如何進行,無論它是否令人困惑。
使用球體創建兩個核子預制體,一個用于質子,另一個用于中子。為每種材料提供不同的材料,以使它們看起來不同。我們只滿足一種核子類型就足夠了,但是這很無聊。
預制件是場景中不存在且尚未激活的Unity對象(或對象層次)。您將其用作模板,創建它的克隆并將其添加到場景中。要創建一個對象,請照常在場景中構造一個對象,然后將其拖到項目窗口中。場景對象將成為預制實例,如果不再需要它可以將其刪除。
要生成這些核子,我們需要創建另一個組件NucleonSpawner
。它需要知道生成之間的時間間隔,離生成中心有多遠以及生成什么。
using UnityEngine;
public class NucleonSpawner : MonoBehaviour {
public float timeBetweenSpawns;
public float spawnDistance;
public Nucleon[] nucleonPrefabs;
}
創建一個空的游戲對象,附加一個NucleonSpawner
組件,然后根據需要對其進行配置。
要定期生成,我們需要跟蹤自上次生成以來的時間。我們可以用一種簡單的FixedUpdate
方法來做到這一點。
float timeSinceLastSpawn;
void FixedUpdate () {
timeSinceLastSpawn += Time.deltaTime;
if (timeSinceLastSpawn >= timeBetweenSpawns) {
timeSinceLastSpawn -= timeBetweenSpawns;
SpawnNucleon();
}
}
使用FixedUpdate使產生的幀與幀速率無關。如果配置的生成之間的時間短于幀時間,則使用Update會導致生成延遲。并且由于此場景的重點是降低我們的幀速率,因此將發生這種情況。
您可以使用while循環而不是if檢查來追趕錯過的生成,但是當timeSinceLastSpawn意外將其設置為零時,這將導致無限的產卵循環。將產卵限制為每個固定時間步一次是明智的限制。
實際的生成包括三個步驟。挑選一個隨機的預制件,實例化它,并在所需的距離上給它一個隨機的位置。
void SpawnNucleon () { Nucleon prefab = nucleonPrefabs[Random.Range(0, nucleonPrefabs.Length)]; Nucleon spawn = Instantiate<Nucleon>(prefab); spawn.transform.localPosition = Random.onUnitSphere * spawnDistance; }
播放此場景應導致球體朝中心射擊。它們會過一會兒,直到彼此碰撞到形成一個球為止。這個球將繼續增長,物理計算將變得更加復雜,并且在某些時候您會注意到幀速率下降。
如果花費太長時間才能看到性能下降,則可以提高生成速度。通過增加時間比例來加快時間也可以。您可以通過“ 編輯” /“項目設置” /“時間”找到它。您還可以減少固定時間步長,這將導致每秒更多的物理計算。
當時間刻度設置為較低的值(例如0.1)時,時間將非常緩慢地移動。由于固定時間步長是恒定的,因此這意味著物理系統的更新頻率將降低。因此,物理對象將保持不動,直到發生固定的更新(每隔幾幀僅更新一次)。
您可以通過增加時間刻度來減小固定時間步長來解決此問題?;蛘?,您可以更改剛體部件的插值模式,以便它們在物理步驟之間插值,從而隱藏了較低的更新頻率。
使用探查器
現在我們有了一個最終會降低任何機器的幀速率的場景,是時候測量實際性能了。您最快可以做的就是啟用游戲視圖的統計信息疊加。
但是,那里顯示的幀速率根本不準確,更像是一個粗略的猜測。通過Window / Profiler打開Unity的探查器,我們可以做得更好。探查器為我們提供了許多有用的信息,尤其是CPU使用率和內存數據。
如果啟用了vsync,則一開始它可能會主導CPU圖形。為了更好地了解場景需要多少CPU資源,請關閉vsync。您可以通過“ 編輯” /“項目設置” /“質量”執行此操作。它位于“ 其他”標題下的底部。
我現在的幀率非常高!
如果沒有vsync,則對于簡單的場景,可能會獲得很高的幀速率,甚至超過100。這會給硬件造成不必要的壓力。您可以通過設置Application.targetFrameRate屬性通過代碼強制使用最大幀速率來防止這種情況。請注意,即使退出播放模式,此設置仍會保留在編輯器中。將其設置為-1將消除限制。
現在,您可以更好地了解CPU使用情況。在我的情況下,物理需要最多的時間,接著是渲染,然后是我的腳本。即使一切隨著球體數量的增加而變慢,這種情況也將在很長一段時間內保持不變。
我們還有兩個意想不到的發現。首先,偶爾會有CPU使用率飆升。其次,內存圖顯示了頻繁的GC分配峰值,這表明存在正在分配并隨后釋放的內存。由于我們只是在創建新對象而從不丟棄任何東西,所以這很奇怪。
這兩種現象都是由Unity編輯器引起的。每當在編輯器中選擇某些內容時,就會發生CPU峰值。內存分配是由編輯器調用GameView.GetMainGameViewRenderRect引起的。還有額外的開銷,特別是如果同時顯示游戲視圖和場景視圖。簡而言之,編輯器本身會干擾我們的測量。
您仍然可以從編輯器內分析中獲得大量有用的信息,但是如果您想從測量中消除編輯器本身,則必須進行獨立構建。如果您進行開發,甚至在運行應用程序時自動連接到探查器,您仍然可以使用探查器。您可以通過“ 文件/構建設置”進行配置...
對獨立構建進行概要分析時,數據看起來完全不同?,F在,內存分配僅由產生的核子引起,并且不再發生垃圾回收。就我而言,渲染需要花費更多時間,因為我是在全屏模式下運行應用程序,而不是在小游戲視圖中運行。此外,腳本是如此微不足道,以至于它們甚至在圖形中都不可見。
每秒測量幀
探查器為我們提供了有用的信息,但仍然不能很好地衡量幀速率。顯示的FPS數僅是1除以CPU時間,這不是我們得到的實際幀速率。因此,讓我們自己衡量一下。
我們需要一個簡單的組件來告訴我們應用程序正在運行的當前每秒幀數。一個公共財產就足夠了。我們將其設為整數,因為我們實際上不需要小數精度。
using UnityEngine;
public class FPSCounter : MonoBehaviour {
public int FPS { get; private set; }
}
請記住,屬性是偽裝成字段的方法。我們提供FPS作為公共信息,但只有組件本身需要更新值。使用的語法是自動生成的屬性的簡寫形式,看起來像這樣。
int fps; public int FPS { get { return fps; } private set { fps = value; } }
這個簡寫不適用于Unity的序列化,但這很好,因為我們仍然不需要保存FPS值。
我們通過將1除以當前幀的時間增量來測量每次更新的每秒幀數。我們將結果轉換為整數,有效地四舍五入。
void Update () { FPS = (int)(1f / Time.deltaTime); }
但是,這種方法存在問題。時間增量不是處理最后一幀所花費的實際時間,它受當前時間比例的影響。這意味著除非將時間標度設置為1,否則我們的FPS將是錯誤的。幸運的是,我們還可以向Unity請求未標度的時間增量。
void Update () { FPS = (int)(1f / Time.unscaledDeltaTime); }
需要某種UI來顯示FPS。讓我們使用Unity的UI。創建一個內部帶有面板的畫布,該面板又包含一個文本對象。這些可以通過GameObject / UI子菜單添加。添加畫布時,您還將獲得一個EventSystem對象來處理用戶輸入,但是我們不需要它,因此可以將其刪除。
我使用了默認的畫布設置,除了我將其設置為像素完美。
該面板用于為FPS標簽創建半透明的黑色背景。這樣,它將始終可讀。我把它放在窗口的左上角。將其錨點設置為左上角,以便無論窗口的大小如何都將其保留在適當的位置。將其樞軸設置為(0,1),以方便放置。
用類似的方法將標簽放置在面板內。將其設為水平和垂直居中的白色粗體文本。設計整個內容,使其恰好適合兩位數。
現在我們需要將FPS值綁定到標簽。為此,我們創建一個組件。它需要一個FPSCounter
組件來從中檢索值,并需要引用UnityEngine.UI命名空間中的Text
標簽以將值分配給它。
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(FPSCounter))]
public class FPSDisplay : MonoBehaviour {
public Text fpsLabel;
}
將此組件添加到面板中并進行連接。我們將其附加到面板上,因為這是整個FPS顯示屏,而不是標簽。稍后我們將包含更多標簽。
顯示組件只需在每一幀更新標簽的文本。讓我們緩存對計數器的引用,這樣就不必每次都調用GetComponent。
FPSCounter fpsCounter;
void Awake () {
fpsCounter = GetComponent<FPSCounter>();
}
void Update () {
fpsLabel.text = fpsCounter.FPS.ToString();
}
FPS標簽現在正在更新!但是,由于我們將其設計為兩位數,因此只要我們的幀速率超過99每秒,它就會顯示無用的值。因此,讓我們限制顯示的值。無論如何,99以上的表現都足夠好。
void Update () { fpsLabel.text =Mathf.Clamp(fpsCounter.FPS, 0, 99).ToString(); }
現在一切似乎都可以正常工作,但是存在一個細微的問題?,F在,我們在每次更新時都創建一個新的字符串對象,在下次更新時將其丟棄。這會污染托管內存,這將觸發垃圾回收器。盡管這對于臺式機應用程序來說并不是什么大問題,但對于幾乎沒有可用內存的設備而言,這更為麻煩。它還會污染我們的探查器數據,這在您尋找分配時很煩人。
我們可以擺脫這些臨時字符串嗎?我們顯示的值可以是0到99之間的任何整數。這是100個不同的字符串。為什么不一次創建所有這些字符串并重用它們,而不是始終重新創建相同的內容?
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值會有不利的副作用。當幀頻不穩定時,標簽將不斷波動,從而難以獲得有用的讀數。我們只能偶爾更新一次標簽,但是這樣一來,我們就不會再對幀頻的表現有任何印象。
一種可能的解決方案是平均幀速率,以平滑突然變化的影響,產生較小的抖動值。讓我們進行調整FPSCounter
,使其在可配置的幀范圍內執行此操作。將此值設置為1等于根本不求平均值,因此實際上是可選的。
public int frameRange = 60;
讓我們將屬性名稱從更改FPS
為AverageFPS
,因為這是對其現在表示的值的更好描述。您可以使用IDE重構名稱,也可以手動更新顯示組件以使用新名稱。
public intAverageFPS{ get; private set; }
現在,我們需要一個緩沖區來存儲多個幀的FPS值,以及一個索引,以便我們知道將下一幀的數據放在何處。
int[] fpsBuffer; int fpsBufferIndex;
初始化此緩沖區時,請確保該frameRange
值至少為1,并將索引設置為0。
void InitializeBuffer () { if (frameRange <= 0) { frameRange = 1; } fpsBuffer = new int[frameRange]; fpsBufferIndex = 0; }
該Update
方法變得有點復雜。它是從初始化緩沖區(如果需要)開始的,這可能是因為我們剛剛啟動,還是因為frameRange
已更改。然后必須更新緩沖區,然后才能計算平均FPS。
void Update () { if (fpsBuffer == null || fpsBuffer.Length != frameRange) { InitializeBuffer(); } UpdateBuffer(); CalculateFPS(); }
通過將當前FPS存儲在當前索引處來完成對緩沖區的更新,然后將其遞增。
void UpdateBuffer () { fpsBuffer[fpsBufferIndex++] = (int)(1f / Time.unscaledDeltaTime); }
但是我們很快就會填滿整個緩沖區,然后呢?在添加新值之前,我們必須丟棄最舊的值。我們可以將所有值移動一個位置,但是平均值并不關心值的順序。因此,我們可以將索引回繞到數組的開頭。這樣,一旦緩沖區被填滿,我們總是用最新的值覆蓋最舊的值。
void UpdateBuffer () { fpsBuffer[fpsBufferIndex++] = (int)(1f / Time.unscaledDeltaTime); if (fpsBufferIndex >= frameRange) { fpsBufferIndex = 0; } }
計算平均值是將緩沖區中的所有值相加并除以值量的簡單問題。
void CalculateFPS () { int sum = 0; for (int i = 0; i < frameRange; i++) { sum += fpsBuffer[i]; } AverageFPS = sum / frameRange; }
現在,我們的平均幀速率有效,并且在合理的幀范圍內,輕松獲得良好的閱讀效果非常容易。但是我們可以做得更好。由于我們現在具有來自多個幀的數據,因此我們也可以公開此范圍內的最高和最低FPS。這給了我們比平均值更多的信息。
public int HighestFPS { get; private set; } public int LowestFPS { get; private set; }
我們可以在計算總和的同時找到這些值。
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; }
現在,我們的FPSDisplay組件可以綁定兩個附加標簽。
public Text highestFPSLabel, averageFPSLabel, lowestFPSLabel;
void Update () {
highestFPSLabel.text =
stringsFrom00To99[Mathf.Clamp(fpsCounter.HighestFPS, 0, 99)];
averageFPSLabel.text =
stringsFrom00To99[Mathf.Clamp(fpsCounter.AverageFPS, 0, 99)];
lowestFPSLabel.text =
stringsFrom00To99[Mathf.Clamp(fpsCounter.LowestFPS, 0, 99)];
}
在用戶界面中再添加兩個標簽,然后將它們全部連接起來。我將最高FPS放在頂部,將最低FPS放在底部,將平均FPS放在中間。
給標簽上色
作為FPS標簽的最后修飾,我們可以為它們著色。這可以通過將顏色與FPS值關聯來完成。這樣的關聯可以用自定義結構表示。
[System.Serializable] private struct FPSColor { public Color color; public int minimumFPS; }
作為FPSDisplay
唯一將使用此結構的東西,我們將struct定義直接放在該類內,并將其私有化,這樣它就不會顯示在全局名稱空間中。使它可序列化,以便可以由Unity編輯器公開。
現在添加這些結構的數組,以便我們可以配置FPS標簽的顏色。我們通常會為此添加一個公共字段,但是由于結構本身是私有的,因此我們不能這樣做。因此,也將數組設為私有并為其賦予SerializeField
屬性,以便Unity在編輯器中公開并保存它。
[SerializeField] private FPSColor[] coloring;
繼續添加一些顏色!確保至少有一個條目,從最高FPS到最低FPS進行排序,最后一個條目為0 FPS。
在將顏色應用于標簽之前,Update
通過引入一個單獨的Display
方法來調整方法,以調整單個標簽。
void Update () {
Display(highestFPSLabel,fpsCounter.HighestFPS);
Display(averageFPSLabel,fpsCounter.AverageFPS);
Display(lowestFPSLabel,fpsCounter.LowestFPS);
}
void Display (Text label, int fps) {
label.text = stringsFrom00To99[Mathf.Clamp(fps, 0, 99)];
}
可以通過遍歷陣列直到找到某種顏色的最低FPS來找到正確的顏色。然后設置顏色并跳出循環。
void Display (Text label, int fps) { label.text = stringsFrom00To99[Mathf.Clamp(fps, 0, 99)]; for (int i = 0; i < coloring.Length; i++) { if (fps >= coloring[i].minimumFPS) { label.color = coloring[i].color; break; } } }
默認顏色的所有四個通道都設置為零。這包括控制不透明度的Alpha通道。如果您尚未更改Alpha通道,則將獲得完全透明的標簽。
感謝各位的閱讀,以上就是“Unity游戲開發的性能衡量方法”的內容了,經過本文的學習后,相信大家對Unity游戲開發的性能衡量方法這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。