此頁面描述了常見的性能問題以及減輕這些問題的最佳做法。
腳本計算
在Luau代碼中,代價高昂的操作需要更長的處理時間,從而影響幀速率。除非以並行方式執行,否則Luau代碼將同步運行,並在遇到一個讓線程暫停的函數之前阻塞主線程。
常見問題
復雜的表結構操作 - 複雜的操作,例如序列化、反序列化和深度克隆,在大型表結構中會造成較高的性能成本。特別是當這些操作是遞歸的或涉及大數據結構的迭代時尤其如此。
高頻事件 - 將代價高昂的操作與 RunService 的基於幀的事件關聯,而不限制其頻率,會導致這些操作每幀重複執行,這通常會導致計算時間不必要增加。這些事件包括:
緩解
- 在 RunService 事件上少量調用代碼,僅在高頻調用至關重要的情況(例如,更新相機)中使用。您可以在其他事件或較少頻繁的循環中執行大多數其他代碼。
- 使用 task.wait() 將大型或代價高昂的任務分解,以便在多幀中分散工作。
- 確定並優化不必要的代價高昂操作,並對於不需要訪問數據模型的計算密集型任務使用 多線程。
- 某些伺服器端腳本可以從 本地代碼生成 中受益,這是一個簡單的標誌,將腳本編譯為機器碼而不是字節碼。
MicroProfiler 範圍
| 範圍 | 相關計算 |
| RunService.PreRender | 在 PreRender 事件上執行的代碼 |
| RunService.PreSimulation | 在 Stepped 事件上執行的代碼 |
| RunService.PostSimulation | 在 Heartbeat 事件上執行的代碼 |
| RunService.Heartbeat | 在 Heartbeat 事件上執行的代碼 |
有關使用 MicroProfiler 進行腳本調試的更多信息,請參見 debug 庫,其中包括標記特定代碼以進一步提高特異性的函數,如 debug.profilebegin 和 debug.profileend。許多被腳本調用的 Roblox API 方法也有自己的相關 MicroProfiler 標籤,這可以提供有用的信號。
腳本內存使用
內存泄漏可能會發生,當您編寫的腳本消耗了垃圾回收器無法正確釋放的內存時。泄漏在服務器上特別普遍,因為它們可以持續在線多天,而客戶端會話則短得多。
開發者控制台中的以下內存值可能表明需要進一步調查的問題:
- LuaHeap - 高或增長的消耗量表明內存泄漏。
- InstanceCount - 不斷增長的實例數量表明代碼中的某些實例引用未被垃圾收回。
- PlaceScriptMemory - 提供逐個腳本的內存使用情況分析。
常見問題
保持連接的連接 - 引擎從不垃圾回收連接到實例的事件以及連接回調內部引用的任何值。因此,活動的事件的連接以及所聯繫實例中的代碼和引用的值,對於內存垃圾收集器來說是不在範疇內的,即使事件已被觸發。
雖然當所屬的實例被銷毀時事件會被斷開,但一個常見的錯誤是假設這適用於 Player 對象。在用戶離開遊戲後,引擎不會自動銷毀他們的代表的 Player 對象和角色模型,因此,如果您在腳本中不斷開連接,則對 Player 對象和角色模型下的實例(例如, CharacterAdded)的連接仍然會消耗內存。隨著數百名用戶進入和退出遊戲,這可能會導致伺服器上大量內存泄漏。
表格 - 向表格中插入對象但不在不再需要時將其刪除會導致不必要的內存消耗,尤其是當這些表格跟踪用戶加入時的數據。舉例來說,以下代碼示例在每次用戶加入時創建一個表格,添加用戶資訊:
示例local playerInfo = {}Players.PlayerAdded:Connect(function(player)playerInfo[player] = {} -- 一些信息end)如果您不在不再需要這些條目時刪除它們,則表格將持續增長並隨著更多用戶加入會消耗更多內存。隨著表格增長,任何迭代該表格的代碼也將變得計算上較昂貴。
緩解
要清理所有已使用的值以防止內存泄漏:
斷開所有連接 - 檢查您的代碼庫,以確保每個連接都通過以下任一途徑清理:
- 使用 Disconnect() 函數手動斷開連接。
- 使用 Destroy() 函數銷毀事件所屬的實例。
- 銷毀連接追溯到腳本對象的。
用戶離開後刪除玩家對象和角色 - 啟用 Workspace.PlayerCharacterDestroyBehavior 在用戶離開後自動銷毀玩家對象和角色模型。如果願意,也可以手動清理它們:
示例玩家和角色清理local Players = game:GetService("Players")Players.PlayerAdded:Connect(function(player)player.CharacterRemoving:Connect(function(character)task.defer(character.Destroy, character)end)end)Players.PlayerRemoving:Connect(function(player)task.defer(player.Destroy, player)end)
物理計算
過度的物理模擬可能是伺服器和客戶端每幀計算時間增加的關鍵原因。
常見問題
過度的物理時間步頻率 - 默認情況下,步進行為是 自適應模式,物理步進以 60Hz、120Hz 或 240Hz 進行,具體取決於物理機制的複雜程度。
也可以使用一個固定模式來提高物理的準確性,該模式迫使所有物理組件以 240Hz(每幀四次)進行步進。這會導致每幀需要更多的計算。
模擬對象的複雜性過高 - 模擬的 3D 組件越多,每幀的物理計算所需時間就越長。通常,遊戲中將模擬不需要模擬的對象或會有比實際需要更多約束和關節的機制。
過於精確的碰撞檢測 - 網格零件具有 CollisionFidelity 屬性,用於檢測碰撞,提供不同性能影響級別的多種模式。對於網格零件,精確的碰撞檢測模式的性能成本最高,並且引擎的計算時間較長。
緩解
固定不需要模擬的零件 - 鎖定所有不需要物理驅動的部分,例如靜態 NPC。
使用自適應物理步進 - 自適應步進動態調整物理計算的速率,允許在某些情況下減少物理更新的頻率。
減少機制複雜性
- 在可能的情況下,盡量減少組件中的物理約束或關節數量。
- 減少機制內部的自我碰撞,例如通過對布娃娃肢體應用限制或無碰撞約束,防止它們相互碰撞。
減少對網格的精確碰撞保真度的使用
對於不經常被使用者注意到差異的小型或可互動對象,使用盒子保真度。
對於小中型對象,根據形狀使用盒子或外殼保真度。
對於大型且非常複雜的對象,盡可能使用不可見零件構建自定義碰撞。
對於不需要碰撞的對象,禁用碰撞並使用箱形或外殼保真度,因為碰撞幾何仍然會儲存在內存中。
有關如何選擇平衡精度和性能要求的碰撞保真度選項的詳細演練,請參見 設置物理和渲染參數。
MicroProfiler 範圍
| 範圍 | 相關計算 |
| physicsStepped | 整體物理計算 |
| worldStep | 每幀進行的離散物理步驟 |
物理內存使用
物理運動和碰撞檢測消耗內存。網格零件具有 CollisionFidelity 屬性,決定用於評估網格的碰撞邊界的方式。
常見問題
默認和精確的碰撞檢測模式消耗的內存明顯高於其他兩種低保真度碰撞形狀。
如果您在 PhysicsParts 下看到內存消耗高的情況,則可能需要探索降低遊戲中對象的 碰撞保真度。
如何減少
為了減少用於碰撞保真度的內存:
- 通過使用 CollisionFidelity 設置來降低碰撞的保真度。Box 具有最低的內存開銷,而 Default 和 Precise 通常開銷更大。
- 通常將任何小的固定零件的碰撞保真度設置為 Box 是安全的。
- 對於非常複雜的大型網格,您可能希望使用較小的對象構建自己的碰撞網格,使用方形碰撞保真度。
人形
Humanoid 是一個類,為玩家和非玩家角色(NPC)提供了廣泛的功能。雖然功能強大,但 Humanoid 的運算成本相當高。
常見問題
- 保持所有 HumanoidStateTypes 在 NPC 上啟用 - 保留某些 HumanoidStateTypes 的啟用會產生性能成本。禁用 NPC 不需要的任何狀態。例如,除非您的 NPC 將要爬梯子,否則禁用 Climbing 狀態是安全的。
- 在不需要的情況下使用 Humanoids - 靜態 NPC 通常不需要使用 Humanoid 類。
- 從伺服器播放大量 NPC 的動畫 - 在伺服器上運行的 NPC 動畫需要在伺服器模擬並複製到客戶端,這可能會增加不必要的開銷。
- 執行不必要的大小和縮放更改 - 大小/縮放更改會導致 FastCluster 重新構建。如果在遊玩時發現與 FastCluster 相關的性能問題,請嘗試減少此操作。同樣,其他屬性變更也可能導致 FastCluster 重新構建,因此一般來說,盡量減少這些變更。
緩解
- 在客戶端播放 NPC 動畫 - 在擁有大量 NPC 的遊戲中,考慮在客戶端創建 Animator 並在本地運行動畫。這樣可以減輕伺服器的負擔,並減少不必要的複製需求。它還使額外的優化成為可能(例如,只對接近角色的 NPC 播放動畫)。
- 使用性能友好的 Humanoids 替代品 - NPC 模型不一定需要包含一個人形對象。
- 對於靜態 NPC,使用簡單的 AnimationController,因為它們不需要四處移動,只需播放動畫。
- 對於移動 NPC,根據 NPC 的複雜程度,考慮實現自己的移動控制器並使用 AnimationController 進行動畫。
- 禁用不使用的人形狀態 - 使用 Humanoid:SetStateEnabled() 僅啟用每個人形所需的狀態。
- 池化頻繁重生的 NPC 模型 - 不要完全銷毀 NPC,將其發送到非活動 NPC 的池中。這樣,當需要重生新 NPC 時,您可以簡單地重新啟用池中的一個 NPC。該過程稱為池化,最大限度地減少角色需要實例化的次數。
- 僅在用戶附近生成 NPC - 當用戶不在範圍內時不要生成 NPC,並在用戶離開範圍時裁減它們。
- 避免在人物實例化後對角色層次進行更改 - 對角色層次的某些更改會對性能產生重大影響。可以進行一些優化:
MicroProfiler 範圍
| 範圍 | 相關計算 |
| stepHumanoid | 人形控制和物理 |
| stepAnimation | 人形和動畫師動畫 |
| updateInvalidatedFastClusters | 與實例化或修改角色相關 |
渲染
客戶端在每幀上所花費的時間中很大一部分用於渲染當前幀的場景。伺服器不進行任何渲染,因此本節僅限於客戶端。
繪製調用
繪製調用是一組來自引擎的指令,讓 GPU 渲染某個內容。繪製調用具有顯著的開銷。一般來說,每幀的繪製調用越少,渲染每幀所花的計算時間就越少。
您可以通過 Studio 中的 渲染統計 ⟩ 計時 項目查看當前發生的繪製調用數量。按 ShiftF2 可以在客戶端查看 渲染統計。
在給定幀中,如果需要繪製的對象越多,則對 GPU 的繪製調用次數越多。然而,Roblox 引擎利用了一個稱為 實例化 的過程,將相同紋理特徵的相同網格合併為單個繪製調用。具體來說,當多個網格具有相同的 MeshContent 時,將在單個繪製調用中處理:
- 如果存在,則 SurfaceAppearances 需要相同,否則,TextureContents 需要相同。
- 當 SurfaceAppearance 和 MeshPart.TextureID 都不存在時,材料需要相同。
其他常見問題
過高的物件密度 - 如果大量對象集中在高密度的區域,則渲染該場景區域將需要更多的繪製調用。如果您發現幾個地圖的某一部分時幀速率下降,這可能是該區域對象密度過高的良好信號。
像貼圖、紋理和粒子這樣的對象並不易批處理,會引入額外的繪製調用。在場景中特別注意這些對象類型。特別是,對 ParticleEmitters 的屬性更改可能對性能產生重大影響。
錯過實例化機會 - 通常,一個場景中會重複包含相同的網格,但每個網格的網格或紋理資產 ID 不同。這會阻止實例化,可能導致不必要的繪製調用。
這個問題的常見原因是在一次導入整個場景,而不是將個別資產導入到 Roblox 中,然後在導入後複製它們以組建場景。
甚至像這樣的簡單腳本可以幫助您識別具有相同名稱但使用不同網格 ID 的網格部件:
for _,descendant in workspace:GetDescendants() doif descendant:IsA("MeshPart") thenprint(descendant.Name .. ", " .. descendant.MeshId)endend輸出(啟用 堆疊行)可能看起來像這樣。重複的行表示重用了相同的網格,這是好的。唯一的行未必是壞事,但根據您的命名方案可能表示您遊戲中的重複網格:
LargeRock, rbxassetid://106420009602747 (x144) -- goodLargeRock, rbxassetid://120109824668127LargeRock, rbxassetid://134460273008628LargeRock, rbxassetid://139288987285823LargeRock, rbxassetid://71302144984955LargeRock, rbxassetid://90621205713698LargeRock, rbxassetid://113160939160788LargeRock, rbxassetid://135944592365226 -- all possible duplicates過於複雜的對象 - 雖然對於繪製調用的數量沒那麼重要,但場景中的三角形數量確實會影響幀渲染所需的時間。擁有非常多複雜網格的場景是一個常見問題,並且在過多網格上設置了 MeshPart.RenderFidelity 屬性為 Precise 的場景也是如此。
過度的陰影投射 - 處理陰影是一個昂貴的過程,包含大量陰影光源(或受到陰影影響的小部件)的地圖會有性能問題。
高透明度重繪 - 將部分透明的物件彼此放置在一起會強迫引擎多次渲染重疊的像素,這會影響性能。要獲得有關識別和修復此問題的更多信息,請參見 刪除分層透明度。
不必要的蒙皮 MeshPart 移動 - 沒有 Humanoid 的模型中部組織的蒙皮 MeshParts 使用空間組織的 FastClusters 分組。當這些 MeshParts 移動時,必須不斷將其添加和移除到這些空間簇中,迫使簇重建,影響性能。
- 一個高效的解決方案是在模型中嵌入一個 Humanoid。Humanoid 的存在會覆蓋默認的空間集群行為,要求整個模型使用單一的統一 FastCluster。因此,位置更新不再需要重新構建集群,從而減輕性能瓶頸。這種技術應該僅用於預期移動的 MeshParts,因為這可能會引入內存開銷並抵消空間優化的好處。我們建議在進行這類更改後始終對您的遊戲進行剖析。請參見 Humanoid 性能提示 以獲得更多信息。
在 Model 中的部件過多 - 在模型中有過多部件可能導致由於部件屬性變動而需要經常重建。當使用 FastCluster 時,找到模型中部件的適當平衡。
緩解
實例化相同網格並減少唯一網格的數量 - 如果您確保所有相同的網格具有相同的基本資產 ID,引擎可以識別並在單個繪製調用中渲染它們。確保只在地圖中上傳每個網格一次,然後在 Studio 中重複使用,而不是將大型地圖整體上傳,這可能導致相同的網格擁有不同的內容 ID,被引擎識別為唯一資產。Packages 是物件重用的一種有用機制。
裁剪 - 裁剪是指消除不參與最終渲染幀的繪製調用的過程。默認情況下,引擎跳過對於相機視野外的對象(視野裁剪)和被其他對象遮擋的部件、網格和地形(遮蔽裁剪)的繪製調用。在某些場景(例如室內環境)中,您可能能夠實現房間或門戶系統,並手動裁剪對象,以進一步減少繪製調用或整體計算負載。
減少模型的細節層次 - 啟用 實例流量,並將您的世界模型的 LevelOfDetail 屬性設置為 SLIM,以在遠離相機時為模型呈現 優化的輕量級 SLIM 網格。
減少角色的細節層次 - 啟用實例流量並設置 Workspace.EnableSLIMAvatars 屬性,以在隨距離相機增大時渲染任意數量配件或多層服裝的平臺角色模型,作為 優化的輕量級角色表示,並全面支持動畫。
減少渲染保真度 - 將 MeshPart.RenderFidelity 設置為 Automatic 或 Performance。這使得網格可以退回到不那麼複雜的替代方案,從而減少需要顯示的多邊形數量。
在合適的零件和光源上禁用陰影投射 - Roblox 引擎會隨著客戶端圖形質量水平的降低,自動降低陰影質量,最終在質量水平低於 4 時完全禁用陰影。但是,您可以選擇性地在光源和部件上禁用陰影投射屬性,以改善在啟用陰影時的性能,並提高陰影保持啟用的概率。您可以在編輯時或運行時動態進行的一些優化示例:
使用 BasePart.CastShadow 屬性禁用不太可能可見的陰影的小型零件上的陰影投射。此策略在應用於距離用戶相機較遠的部件時特別有效。
在可能的情況下禁用移動對象上的陰影。
禁用不需要投射陰影的光實例上的 Light.Shadows。
限制光實例的範圍和角度。
使用更少的光實例。
在室內環境中,考慮禁用位於特定範圍之外的光,或逐個房間禁用光。
MicroProfiler 範圍
| 範圍 | 相關計算 |
| Prepare and Perform | 整體渲染 |
| Perform/Scene/computeLightingPerform | 光網格和陰影更新 |
| LightGridCPU | 體素光網格更新 |
| ShadowMapSystem | 陰影映射 |
| Perform/Scene/UpdateView | 渲染和粒子更新的準備 |
| Perform/Scene/RenderView | 渲染和後處理 |
網絡和複製
網絡和複製描述的是伺服器和連接的客戶端之間發送數據的過程。每幀都會在客戶端和伺服器之間發送信息,但大量信息需要更多計算時間。
常見問題
過多的遠程流量 - 通過 RemoteEvent 或 RemoteFunction 對象傳送大量數據,或頻繁調用它們,可能會導致消耗大量 CPU 時間來處理每幀的傳入數據包。常見錯誤包括:
- 在每幀中複製不需要複製的數據。
- 在用戶輸入時複製數據而沒有任何節流機制。
- 發送超過需求的數據。例如,在用戶購買項目時,發送玩家的整個庫存,而不是僅提供已購買項目的詳細信息。
創建或移除複雜實例樹 - 當對伺服器上的數據模型進行更改時,會將其複製到連接的客戶端中。這意味著在運行時創建和銷毀大型實例層次結構(例如地圖)可能會非常耗費網絡資源。
這個問題的常見原因是複雜的動畫數據,由 Animation Editor 插件在模型中保存。如果在遊戲發布之前不將其移除,並且經常克隆帶有動畫的模型,則會不必要地複製大量數據。
伺服器端 TweenService - 如果在伺服器端使用 TweenService 來對對象進行 tween,則每幀都會將 tween 的屬性複製到每個客戶端。這不僅會導致 tween 顯得不穩定,因為客戶端延遲波動,還會導致不少不必要的網絡流量。
緩解
您可以採取以下策略來減少不必要的複製:
- 避免一次性通過遠程事件發送大量數據。相反,僅在較低的頻率下發送必要的數據。例如,對於角色的狀態,當它變更時複製,而不是每幀都發送。
- 將複雜實例樹(如地圖)切塊分割,在幀之間加載它們以分散複製這些數據的工作。
- 清理動畫元數據,特別是模型的動畫目錄,在導入後。
- 限制不必要的實例複製,特別是伺服器不需要知道創建的實例時。這包括:
- 視覺效果,例如爆炸或魔法咒語。而伺服器僅需知道位置以確定結果,而客戶端可以本地創建視覺效果。
- 第一人稱物品視圖模型。
- 在客戶端而非伺服器上進行動畫對象的 tween。
MicroProfiler 範圍
| 範圍 | 相關計算 |
| ProcessPackets | 傳入網絡數據包的處理,例如事件調用和屬性變更 |
| Allocate Bandwidth and Run Senders | 伺服器上的相關傳出事件 |
資產內存使用
改善客戶端內存使用的創作者可用的最高影響機制是啟用 實例流量。
實例流量
實例流量選擇性地加載出不需要的數據模型的部分,這可以大大減少加載時間,並提高客戶端在面對內存壓力時防止崩潰的能力。
如果您遇到內存問題且未啟用實例流量,則考慮更新您的遊戲以支持它,特別是當您的 3D 世界很大時。實例流量基於三維空間中的距離,因此較大的世界自然會受益更多。
如果啟用了實例流量,您可以提高其積極性。例如,考慮:
- 減少使用 Enum.ModelStreamingMode.Persistent,如果可能的話。如果您將其用作兼容性措施,可能需要更新您的腳本。
- 減少 Workspace.StreamingMinRadius 和 Workspace.StreamingTargetRadius。
有關流量選項及其好處的更多信息,請參見 流量屬性。
其他常見問題
資產重複 - 一個常見錯誤是多次上傳相同資產導致不同的資產 ID。這可能導致相同內容多次加載到內存中。
資產數量過多 - 即使資產不同,也會有機會錯過重用相同資產以節省內存。
音頻文件 - 音頻文件可能在內存使用方面是一個驚人的貢獻者,特別是如果您將所有音頻文件一次加載到客戶端,而不是僅加載您需要的部分。具體策略,請參見 加載時間。
高分辨率紋理 - 紋理的圖形內存消耗與其在磁碟上的大小無關;紋理中像素的數量決定了內存使用情況。例如,1024x1024 像素的紋理消耗的圖形內存是 512x512 紋理的四倍。
上傳到 Roblox 的圖像將轉碼為固定格式,因此上傳以更少的每像素字節數相關聯的顏色模型的圖像沒有內存上的好處。同樣,在上傳之前壓縮圖像或移除不需要的圖像的 alpha 通道可以減少圖像在磁盤上的大小,但不會改善內存使用。
隨著遊戲加載,引擎通常從較低質量的紋理開始,然後根據可用設備內存、距離相機、紋理佔據的屏幕空間的數量和其他因素提高質量。即便如此,策略性地調整您的紋理大小可以改善您遊戲中的內存使用。
緩解
僅上傳資產一次 - 在物件中重複使用相同資產 ID,並確保相同資產,尤其是網格和圖像,不要分別上傳多次。
查找並修復重複資產 - 查找多次以不同 ID 上傳的相同網格部件和紋理。
- 雖然沒有 API 可以自動檢測資產的相似性,但您可以收集遊戲中所有的圖像資產 ID(無論是手動還是使用腳本),下載它們,並使用外部比較工具進行比較。
- 對於網格部件,最好的策略是將唯一的網格 ID 按大小組織起來,以便手動識別重複項。
- 當需要不同顏色時,使用單個紋理並使用 SurfaceAppearance.Color 屬性為它應用不同的色調,而不是使用不同顏色的分別紋理。
分別導入地圖資產 - 不要一次導入整個地圖,而是單獨導入和重建地圖中的資產。導入者不會做任何網格去重,因此如果您導入一個包含許多分開地板磚的大型地圖,則每個地板磚都將單獨導入作為單獨資產(即使它們是重複的)。這會導致性能和內存問題,因為每個網格都是單獨處理的,佔用內存和繪製調用。
限制圖像的像素,使其不超過必要的數量。除非圖像在螢幕上占據大量物理空間,否則通常最大需要 512x512 像素。大多數較小的圖像應該小於 256x256 像素。
使用修剪圖 確保在 3D 地圖中最大化紋理重用。有關如何創建修剪圖的步驟和示例,請參見 創建修剪圖。
您也可以考慮使用精靈圖來將許多較小的 UI 圖像加載為單個圖像。然後,您可以使用 ImageLabel.ImageRectOffset 和 ImageLabel.ImageRectSize 顯示圖上的一部分。
加載時間
許多遊戲實現自定義加載屏幕,並使用 ContentProvider:PreloadAsync() 方法請求資產,以便在背景中下載圖像、聲音和網格。
這種方法的好處在於它可以確保遊戲的重要部分被完全加載,而不會出現跳入。但是,一個常見的錯誤是過度使用此方法來預加載實際上不需要的資產。
一個壞習慣的例子是加載整個 Workspace。雖然這可能防止紋理跳入,但這會顯著增加加載時間。
另一個類似的做法是利用 ContentProvider.RequestQueueSize 來確保所有請求的資產都已加載完成。然而,這會導致相同的問題,即顯著增加加載時間,同時由於其波動性,這也是一種不可靠的方法。
因此,僅在必要的情況下使用 ContentProvider:PreloadAsync(),包括:
- 加載屏幕中的圖像。
- 遊戲菜單中的重要圖像,例如按鈕背景和圖標。
- 開始或生成區域中的重要資產。
如果您必須加載大量資產,建議您提供一個 跳過加載 按鈕。