提高效能

*此內容是使用 AI(Beta 測試版)翻譯,可能含有錯誤。若要以英文檢視此頁面,請按一下這裡

此頁面描述了常見的性能問題和減少它們的最佳做法。

腳本計算

在 Luau 代碼中的昂貴操作需要更長的時間來處理,因此可能會影響幀率。除非並行執行,否則 Luau 代碼會同步運行,並阻塞主線程直到遇到產生線程的函數。

常見問題

緩解

  • RunService 事件上輕量呼叫代碼,限制使用情況到需要高頻率呼叫的情況(例如,更新相機)。您可以在其他事件中執行大多數其他代碼,或在迴圈中較少執行。
  • 使用 task.wait() 分解大或貴重的任務,將工作分散到多個框架上。
  • 識別並最佳化不必要昂貴的操作,並使用 多線程 來計算昂貴的任務,不需要存取資料模型。
  • 某些服務器側腳本可以從 原生代碼生成 中受益,這是一個簡單的標籤,可以將一個腳本轉換為機器代碼而不是 bytecode。

微型調查員範圍

範圍相關計算
RunService.PreRender在預渲染事件上執行代碼
運行服務。PreSimulation在步驟事件上執行代碼
RunService.PostSimulation在心跳事件上執行代碼
運行服務。心跳在心跳事件上執行代碼

有關使用微型調試器檢查腳本的更多信息,請參閱 debug 圖書館,包括標記特定代碼和進一步提高特定性的功能,例如 debug.profilebegindebug.profileend 。許多由腳本呼叫的 Roblox API 方法也有自己相關的微型調試器標籤,可以提供有用的信號。

腳本記憶使用率

當您寫出會消耗記憶的腳本時,記憶洩漏可能會發生,當垃圾收集器不再使用時無法正確釋放時。洩露在伺服器上特別普遍,因為它們可以連續多天在線,而客戶端會話則短得多。

開發者控制台 中的下列記憶值可能會指示需要進一步調查的問題:

  • LuaHeap - 高或增長的消耗建議發生記憶泄漏。
  • 實例數量 - 一致地增長的實例數量表明您的代碼中的某些實例沒有被垃圾收集。
  • 放置脚本記憶體 - 提供一個通過分析記憶使用量的腳本來提供一個腳本。

常見問題

  • 保留連線連接 - 引擎永遠不會收集到連接到實例的事件和內部參考的任何值,連接到的回應呼叫內的任何值。因此,連接到連線的實例、連線的功能和參考值內的事件和代碼的主動連線,即使在事件發射後,在記憶碎片收集器的範圍之外。

    雖然當屬於的實例被摧毀時,事件會斷開,但一個常見的錯誤是假設這適用於 Player 對象。使用者離開體驗後,引擎不會自動刪除他們的代表 Player 對象和角色模型,因此連接到角色模型下的 Player 對象和實例,例如 Player.CharacterAdded ,仍然會消耗記憶,如果你在腳本中未能夠將它們切斷,否則會導致資源浪費。這可能會導致伺服器上的數百個使用者加入和離開體驗時,隨著時間推移發生非常大的記憶泄漏。

  • 桌子 - 將對象插入桌子,但當它們不再需要時未移除時,會導致不必要的記憶消耗,特別是當它們加入時跟蹤用戶數據的桌子。例如,下面的代碼示例每次用戶加入時創建一個表,添加用戶信息:

    範例

    local playerInfo = {}
    Players.PlayerAdded:Connect(function(player)
    playerInfo[player] = {} -- 一些資訊
    end)

    如果您不在必要時移除這些條目,表將繼續增長規模並消耗更多記憶,因為更多用戶加入會話。對此表進行循環的任何代碼也會隨著表的增長而變得更加計算成本高昂。

緩解

要清理所有使用的值以防止記憶泄漏:

  • 斷開所有連線 - 通過您的代碼基地確保每個連線都通過以下路徑進行清理:

    • 使用 Disconnect() 功能手動斷開。
    • 使用 Destroy() 函數摧毀屬於事件的實例。
    • 摧毀連線追溯到的腳本對象。
  • 在離開後移除玩家對象和角色 - 實裝代碼以確保用戶離開後不會保留連接,如下一個例子:

    範例

    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,取決於物理機制的複雜程度。

    還有一種固定模式,其準確度提高的物理學也可用,強制所有物理組件以 240 Hz(每秒四次)步行。這會導致每個框架的計算量大幅增加。

  • 模擬對象複雜度過多 - 模擬的 3D 裝配越多,物理計算每個框架所需的時間越長。經常會發生體驗會有對象被模擬,這些對象並不需要或會有比需要更多限制和關節的機制。

  • 過度精確的碰撞偵測 - 網格零件有一個 CollisionFidelity 偵測碰撞的屬性,可提供各種不同績效影響等級的模式。網格零件的精確碰撞偵測模式具有最高成本效益,需要花較長時間才能計算引擎。

緩解

  • 不需要模擬的錨定零件 - 錨定所有不需要由物理學驅動的零件,例如靜態NPC。

  • 使用適應物理步驟 - 適應步驟動態調整物理機制的物理計算速率,在某些情況下讓物理更新更少頻繁。

  • 減少機制複雜度 * 在可能的情況下,盡量減少裝配中的物理限制或節點數量。

    • 通過對機制內的自我碰撞數量進行減少,例如應用限制或無碰撞限制來限制布偶肢體之間的碰撞,以防止它們相互碰撞。
  • 減少對精確碰撞穩定性的使用對網格的影響 * 對於用戶很少注意到差異的小或非互動對象,使用盒子穩定性。

    • 對於小型到中型的物件,可以使用盒子或船體的忠實度,取決於形狀。

    • 對於大型且非常複雜的物件,在可能的情況下,使用隱形零件來構建自訂衝突。

    • 對於不需要碰撞的對象,禁用碰撞並使用箱子或船體穩定性,因為碰撞幾何仍然存儲在記憶中。

    • 您可以在 Studio 中以調整碰撞精度從視覺化選項控件在右上角的 3D 視窗中切換,來渲染碰撞幾何圖形以進行調試。

      或者,您可以將 CollisionFidelity=PreciseConvexDecomposition 過濾器應用到 尋找器 ,該尋找器會顯示所有精確匹配的網格零件的數量,並允許您輕鬆選擇它們。

    • 要了解如何選擇一個平衡精度和性能要求的碰撞忠實度選項的詳細教學,請參閱設置物理和渲染參數

微型調查員範圍

範圍相關計算
物理步驟整體物理計算
世界步驟每個框架執行的分散物理步驟

物理內存使用率

物理移動和碰撞偵測會消耗記憶。網格零件有一個 CollisionFidelity 屬性,決定用於評估網格碰撞邊界的方法。

常見問題

預設和精確的碰撞偵測模式會消耗比其他兩種模式(精度較低的碰撞形狀)消耗更多記憶。

如果在 物理零件 下看到高記憶使用量,您可能需要探索減少體驗中對象的 碰撞精度

如何緩解

要減少用於碰撞穩定性的記憶使用:

  • 對於不需要碰撞的零件,通過設置 BasePart.CanCollideBasePart.CanTouchBasePart.CanQuery 將其碰撞關閉到 false
  • 使用 CollisionFidelity 設置減少碰撞的忠實度。Box 具有最低的記憶擴展,而 DefaultPrecise 一般來說較貴。
    • 一般來說,設定任何小型錨定零件的碰撞精度為 Box 是安全的。
    • 對於非常複雜的大型網格,您可能想使用盒子碰撞精度的較小物體來建立自己的碰撞網格。

人形怪物

Humanoid 是一個提供給玩家和非玩家角色(NPC)廣泛功能的類。雖然強大,但 Humanoid 帶來了相當大的計算成本。

常見問題

  • 將所有人形狀態類型在 NPC 上啟用 - 啟用某些 HumanoidStateTypes 會有性能成本。停用任何不需要 NPC 的東西。例如,除非你的 NPC 要爬梯子,否則禁用 Climbing 狀態是安全的。
  • 經常使用機器人模型來啟動、修改和重生模型 * 這可能需要引擎處理,特別是如果這些模型使用 分層服裝 。這也可能在經驗中特別困難,在那裡人物經常重生。
    • 微型調試器 中,長 更新無效快堆 標籤(超過 4 毫秒)經常是表示虛擬人偶即時化/修改引發過多無效化的信號。
  • 在不需要的情況下使用人形 - 不會移動的靜態 NPC 一般來說沒有需要 Humanoid 類。
  • 從伺服器播放動畫到大量 NPC - 在伺服器上運行的 NPC 動畫需要在伺服器上模擬並複製到客戶端。這可能是不必要的開銷。

緩解

  • 在客戶端播放 NPC 動畫 - 在擁有大量 NPC 的體驗中,考慮在客戶端創建 Animator 並運行動畫,以便在本地執行。這減少了服務器的負載和不必要的複製需求。它也讓額外的最佳化變得可能(例如只為靠近角色的 NPC 播放動畫)。
  • 使用性能友好的替代方案來取代人形怪物 - NPC 模型不一定需要包含人形物體。
    • 對於靜態 NPC,請使用簡單的 AnimationController,因為它們不需要移動,只需要播放動畫。
    • 對於移動 NPC,請考慮實裝自己的移動控制器並使用 AnimationController 來執行動畫,取決於您的 NPC 的複雜程度。
  • 停用未使用的人形狀態 - 使用 Humanoid:SetStateEnabled() 僅啟用每個人形狀態所需的狀態。
  • 經常重生的游泳池 NPC 模型 - 而不是完全摧毀 NPC,將 NPC 傳送到一個不活躍的 NPC 池。這樣,當新的 NPC 需要重生時,您可以從池中重新啟用其中一個 NPC。這個過程稱為泳池化,會減少字元需要被實例化的次數。
  • 只有在使用者靠近時才生成 NPC - 不要在使用者不在範圍內時生成 NPC,並在使用者離開範圍時刪除它們
  • 避免在它被實例化後對虛擬人偶層級進行變更 - 對虛擬人偶層級進行某些修改會有重大性能影響。有一些最佳化可用:
    • 對於自訂程序動畫,不要更新 JointInstance.C0JointInstance.C1 屬性。相反,更新 Motor6D.Transform 屬性。
    • 如果您需要將任何 BasePart 物件附加到虛擬人偶上,請在虛擬人偶的階層之外進行 Model

微型調查員範圍

範圍相關計算
步驟人形態人形控制和物理學
步驟動畫人形和動畫師動畫
更新無效的快速集群與創建或修改虛擬人偶相關

渲染

客戶每次花費的時間的重要部分是在當前框架中渲染場景。伺服器不進行任何渲染,因此此部分僅供客戶端使用。

呼叫绘制

繪製呼叫是由引擎向 GPU 渲染某些內容的一組指令。呼叫有明顯的延遲。一般來說,每個框架的呼叫次數越少,渲染一個框架所需的計算時間越少。

您可以在 Studio 中查看多少绘制调用正在发生,以及与 渲染统计 > 计时 项目有多少关联。您可以透過按下 渲染統計資料 在客戶端查看 ShiftF2

在給定的框架中需要在場景中繪製的物件越多,GPU 就會收到越多的繪製呼叫。然而,Roblox 引擎使用稱為 實例化 的過程將相同的紋理特性的相同網格瓦解為單一呼叫。特別是,具有相同 MeshId 的多個網格在單次繪製呼叫中處理時:

其他常見問題

  • 過度物件密度 - 如果大量物件集中在高密度,那麼渲染此場景區域的繪圖需要更多的呼叫。如果您在查看地圖的某部分時發現幀率下降,這可能是地圖中某個區域物件密度過高的好跡象。

    像裝飾、紋理和粒子等對象不會很好地批量,並且會導入額外的繪製呼叫。在場景中給予這些物件類型額外關注。特別是,屬性變更到 ParticleEmitters 可能會對性能產生巨大影響。

  • 錯過了實例化機會 - 經常會發生場景包含同一網格複製多次的情況,但每個網格複製的網格或紋理資產ID各不相同。這會防止實例化並可能導致不必要的呼叫。

    這個問題的常見原因是當一次匯入整個場景,而不是個別資產被匯入 Roblox 然後在匯入後複製以組裝場景時。

    即使是像這樣簡單的腳本也可以幫助您識別使用不同網格ID的網格零件:


    local Workspace = game:GetService("Workspace")
    for _, descendant in Workspace:GetDescendants() do
    if descendant:IsA("MeshPart") then
    print(descendant.Name .. ", " .. descendant.MeshId)
    end
    end

    輸出(啟用了 堆疊線 )可能會看起來像這樣。重複的線指示使用相同的網格,這很好。獨特的線並不一定就是壞事,但取決於你的命名方案,可能會在你的體驗中顯示重複的網格:


    LargeRock, rbxassetid://106420009602747 (x144) -- good
    LargeRock, rbxassetid://120109824668127
    LargeRock, rbxassetid://134460273008628
    LargeRock, rbxassetid://139288987285823
    LargeRock, rbxassetid://71302144984955
    LargeRock, rbxassetid://90621205713698
    LargeRock, rbxassetid://113160939160788
    LargeRock, rbxassetid://135944592365226 -- all possible duplicates
  • 過度複雜的物件複雜度 - 雖然不像是呼叫數量那麼重要,但場景中的三角形數量會影響框架渲染所需的時間。擁有非常大數量的非常複雜的網格的場景是一個常見問題,場景擁有 MeshPart.RenderFidelity 屬性設置為 Enum.RenderFidelity.Precise 在太多網格上也是如此。

  • 過度投射陰影 - 處理陰影是一個昂貴的過程,包含大量投射陰影的地圖(或大量受陰影影響的小部件)可能會出現性能問題。

  • 高透明度擊過 - 將部分透明度的對象放置在一起,強迫引擎多次渲染重疊像素,這可能會傷害性能。要了解有關識別和修復此問題的更多信息,請參閱刪除分層透明度

緩解

  • 重複複製相同網格並減少獨特網格數量 - 如果您確保所有相同網格都擁有相同的基礎資產ID,引擎可以認識並在單次呼叫中渲染它們。請確保只上傳每個網格在地圖上一次,然後在工作室複製它們,而不是匯入大型地圖的整體,這可能會導致相同的網格擁有不同的內容ID並被引擎認為是獨特資產。包裝是一個有用的對象重複機制。
  • 篩選 - 篩選描述對不屬於最終渲染框架的對象的呼叫刪除過程。預設情況下,引擎會跳過攝影機領域外的物件的呼叫(碎片篩選),但不會跳過其他物件遮蔽視線的物件呼叫(遮蔽篩選)。如果你的場景有大量的繪圖呼叫,考慮在運行時動態實現自己的額外篩選,例如應用以下常見策略:
    • 隱藏遠離相機或設置的 MeshPartBasePart
    • 對於室內環境,實裝一個房間或傳送門系統,隱藏目前任何使用者都未使用的物件。
  • 降低渲染精度 - 將渲染精度設為 自動性能 。這允許網格回落到較少複雜的替代方案,可以減少需要繪製的多邊形數量。
  • 停用適當的零件和光物體上的陰影投射功能 - 在光物體和零件上選擇性地停用陰影投射功能,可以減少場景中陰影的複雜度。這可以在編輯時間或在運行時動態執行。一些例子是:
    • 使用 BasePart.CastShadow 屬性來禁用在小部件上投射陰影,陰影不太可能被看到。這可能特別適用於只對離使用者攝影機很遠的零件進行應用。

    • 如果可能,關閉移動物體上的陰影。

    • 在不需要投射陰影的輕型實例上停用 Light.Shadows,當對象不需要投射陰影時。

    • 限制光源範圍和角度的光實例。

    • 使用更少的光源實例。

    • 考慮停用特定範圍之外或以房間為基礎的室內燈光。

微型調查員範圍

範圍相關計算
準備和執行整體渲染
執行/場景/計算光源執行燈光網格和陰影更新
光網路CPU音箱燈網格更新
暗影地圖系統陰影映射
執行/場景/更新視圖準備渲染和粒子更新
執行/場景/渲染視圖渲染和後期處理

網路和複製

網路和複製描述數據之間的伺服器和連線客戶端之間的傳送過程。資訊在每一個框架間傳送從客戶端到伺服器,但需要更多運算時間的資訊量較大。

常見問題

  • 過度的遠端流量 - 通過 RemoteEventRemoteFunction 對象發送大量數據或過度頻繁地呼叫它們可能會導致每個框架花費大量的CPU時間來處理收到的包。常見錯誤包括:

    • 重複不需要重複的每一個框架的資料。
    • 沒有任何機制來限制它的用戶輸入上的數據複製。
    • 派發比需要的數據多。例如,當玩家購買一件物品時,發送整個庫存,而不是只有購買的物品細節。
  • 創建或移除複雜的實例樹 - 當在伺服器上對資料模型進行變更時,它將被複製到連接的客戶端。這意味著創建和摧毀在運行時創建和摧毀像地圖一樣的大規模實例層次可能非常消耗網絡。

    常見的罪魁是動畫編輯器插件在模型中保存的複雜動畫資料。如果在遊戲發布和動畫模型定期複製之前這些內容未被移除,大量不需要的資料將被複製。

  • 服務器端暫停服務 - 如果 用於暫停對象服務端,暫停的屬性將在每一個框架複製到每個客戶端。這不僅會導致青少年因客戶端延遲波動而感到緊張,還會導致大量不必要的網絡流量。

緩解

您可以使用以下策略來減少不必要的複製:

  • 避免一次通過遠端事件傳送大量資料 。相反,只在較低頻率傳送必要數據。例如,對於角色的狀態,在每一個框架更改之前複製它,而不是每一次更改。
  • 塊化複雜的實例樹 像地圖並將它們分塊載入以分配這些在多個框架上複製的工作。
  • 清理動畫元數據 ,特別是在進行匯入後,動畫目錄的骨架。
  • 限制不必要的實例複製 ,特別是在服務器不需要知道創建的實例時。這包括:
    • 視覺效果,例如爆炸或魔法法術爆炸。伺服器只需知道位置來確定結果,客戶端可以在本地創建視覺效果。
    • 第一人稱物品檢視模型。
    • 在客戶端而不是服務器上檢查子對象。

微型調查員範圍

範圍相關計算
過程包裹處理進入網絡包的過程,例如事件呼叫和屬性變更
分配帶寬並執行發送者在服務器上相關的輸出事件

資產記憶體使用率

創作者可以使用的最高影響機制來改善客戶端記憶使用是啟用實例傳輸

實例傳輸

實例傳輸會選擇性地載入不需要的數據模型部分,這可能會導致載入時間大幅縮短,並提高客戶端在記憶壓力下防止發生故障的能力。

如果您遇到記憶體問題且已停用實例傳輸,請考慮升級您的體驗以支持它,特別是如果您的 3D 世界很大。實例傳輸基於 3D 空間的距離,因此較大的世界自然會受益更多。

如果實例傳輸已啟用,您可以增加它的攻擊性。例如,考慮:

  • 減少使用持久的 傳輸完整性
  • 減少 傳輸範圍

要了解更多關於傳輸選項和其優點的信息,請參閱 傳輸屬性

其他常見問題

  • 資產複製 - 一個常見的錯誤是多次上傳相同的資產,導致不同的資產ID。這可能導致相同內容多次載入記憶。
  • 過度的資產量 - 即使資產不相同,也有情況會錯過重複使用相同資產和節省記憶的機會。
  • 音頻文件 - 音頻文件可能是記憶使用率的驚人貢獻者,特別是如果你一次載入所有音頻,而不是只載入體驗的一部分所需的內容。對於策略,請參閱載入時間
  • 高解析度紋理 - 圖形記憶對紋理的尺寸沒有影響,而是與紋理上的像素數量相關。
    • 例如,1024x1024 像素紋理消耗 4 倍 512x512 紋理的圖形記憶。
    • 上傳到 Roblox 的圖像會被轉換為固定格式,因此沒有將圖像上傳到具有少於每個像素的顏色模型相關的記憶優惠。相同地,在上傳或從不需要它的圖像中移除Alpha通道之前壓縮圖像可以減少圖像在磁碟上的大小,但是或不會改善或只會稍微改善記憶使用率。雖然引擎會自動在某些裝置上降低紋理解析度,但降低的程度取決於裝置特性,過多的紋理解析度仍然可能導致問題。
    • 您可以在 開發者控制台 中擴展 圖形紋理 類別來識別給定紋理的圖形記憶消耗。

緩解

  • 只上傳資產一次 - 在對象間重複使用相同的資產ID,確保相同的資產,特別是網格和圖像,不會獨立多次上傳。

  • 尋找並修復重複資產 - 尋找具有相同網格部分和紋理的資產,並上傳多次使用不同ID的資產。

    • 雖然沒有 API 自動偵測資產相似性,但您可以在地方收集所有圖像資產 ID(手動或使用腳本)、下載它們,並使用外部比較工具進行比較。
    • 對於網格零件,最好的策略是使用獨特的網格 ID 並依照尺寸將它們組織,手動標示重複項目。
    • 取代使用不同顏色的獨立紋理,上傳單一紋理,並使用 SurfaceAppearance.Color 屬性將各種染色應用到它上。
  • 獨立地在地圖上匯入資產 - 而不是一次匯入整個地圖,獨立地在地圖上匯入和重建資產,並重建它們。3D 导入器不會對網格進行重複,因此如果您要匯入大型地圖並包含大量獨立地板網格,每個網格都會被匯入為獨立資產(即使它們是重複的)。這可能會導致線上的性能和記憶問題,因為每個網格都被視為獨立並消耗記憶和呼叫。

  • 限制圖像的像素 不超過必要數量。除非圖像佔用了大量物理空間在畫面上,通常最多只需要 512x512 像素。大多數小畫像應小於 256x256 像素。

  • 使用修邊表 以確保在3D地圖中最大限度地重複紋理。有關如何創建修邊單的步驟和例子,請參閱創建修邊單

    您也可以考慮使用碎片表來載入許多較小的用戶介面圖像作為單個圖像。您可以使用 ImageLabel.ImageRectOffsetImageLabel.ImageRectSize 來顯示表單的部分。

載入時間

許多體驗實現了自定義載入屏幕,並使用 ContentProvider:PreloadAsync() 方法要求資產,以便圖像、聲音和網格在背景下下載。

這種方法的優點是,它可以讓你確保體驗的重要部分無需彈出即可完全載入。然而,一個常見的錯誤是過度使用此方法來預加載超過實際需要的資產。

一個惡劣習慣的例子是載入整個 Workspace 。雖然這可能會阻止紋理冒出,但它會大大增加載時間。

相反,只在必要的情況下使用 ContentProvider:PreloadAsync(),這些情況包括:

  • 載入畫面中的圖像。
  • 體驗選單中的重要圖像,例如按鈕背景和圖示。
  • 啟動或生成區域中的重要資產。

如果您必須載入大量資產,我們建議您提供 跳過載入 按鈕。