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