提高性能

*此内容使用人工智能(Beta)翻译,可能包含错误。若要查看英文页面,请点按 此处

此页面描述了常见的性能问题和最佳实践,以及如何应对它们。

脚本计算

在 Lua 代码中,昂贵的操作需要更长的处理时间,从而可能影响帧率。除非它们并行运行,否则 Lua 代码将同步运行,直到它遇到一个可以产生线程的函数,从而影响主线程直到它遇到这个函数。

常见问题

缓解

  • RunService 事件上使用代码来节省使用,限制高频使用的情况(例如更新相镜头)。你可以在其他事件或更少频繁地执行其他代码。
  • 使用 task.wait() 来分割大型或昂贵的任务,将工作分布到多个框架上。
  • 识别并优化不必要的昂贵操作,并使用 多线程 为不需要访问数据模型的计算性昂贵任务。
  • 有些服务器端脚本可以从原始代码生成中获益,这是一个简单的旗帜,它可以将脚本编译为机器代码而不是 Bytecode。

微型分析器瞄准镜

范围相关计算
运行服务。预览在预览中执行代码
运行服务。预览代码在步骤事件上执行
运行服务。PostSimulation心跳事件上的代码执行
运行服务。心跳心跳事件上的代码执行

有关使用 MicroProfiler 调试脚本的更多信息,请参阅debug 图书,其中包含标记特定代码和进一步提高特定性的函数,例如debug.profilebegindebug.profileend。许多 Roblox API 方法的调用都有其自己的微型调试标签,可以提供有用的信号。

脚本内存使用率

当您写代码时,可能会出现内存泄露,因为垃圾收集器无法在使用它时正确释放内存。泄露通常在服务器上发生,因为它们可以持续多天在线,而客户端会话的时间要短得多。

开发者控制器 中,以下记忆值可以表示需要进一步调查的问题:

  • Lua堆 - 高或增长的使用率表明内存泄露。
  • 实例Count - 致istent地增加实例数量表示有些实例在您的代码中不是垃圾。
  • PlaceScriptMemory - 提供脚本的记忆使用率分析。

常见问题

  • 离开连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连接连

尽管事件在该实例所属的实例被摧毁时会断开,但在 Player 对象上假设这适用于 Player 对象。 在用户离开体验后,引擎不会

  • 桌子) - 在桌子中插入对象,但不会移除它们,如果它们不再需要时,会导致不必要的内存使用,尤其是跟踪用户数据时,桌子会创建一个表。例如,以下代码示例创建了一个表,添加用户信息每次用户加入:

    例子

    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)

物理计算

过度的物理模拟可能是导致服务器和客户端每秒增加计算时间的主要原因之一。

常见问题

  • 过度的物理时间频率 - 默认情况下,步骤行为是在适应模式中,其中物理步骤以 60 Hz、120 Hz 或 240 Hz 的速度进行,这取决于物理机制的复杂性。

    还有一个固定模式,其精度改善了物理,使所有物理组件的步骤为 240 Hz (每个框架四次)。这导致每个框架的计算成本。

  • 过度复杂的模拟对象数量 - 模拟的3D数量越多,每个框架的物理计算时间就越长。经常,体验将有需要不需要或有机制约束和关联的对象。

  • 过于精确的碰撞检测 - 网格零件有一个 CollisionFidelity 属性,用于检测碰撞,这提供了多种模式的不同级别的性能影响。精确碰撞网格零件的碰撞检测模式有最高的性能成本,并且需要更长的时间才能计算。

缓解

  • 锚定不需要模拟的零件 - 锚定所有不需要被物理驱动的零件,例如静电 NPC。

  • 使用适应性物理步骤 - 适应性步骤动态调整物理机制的计算速度,以便在某些情况下减少物理更新的频率。

  • 减少机器复杂性 * 尽可能减少在装配中的物理限制或连接。

    • 在机制中减少自己的碰撞量,例如通过将限制或非碰撞限制应用于 ragdoll 手臂以防止它们之间的碰撞。
  • 减少网格精确度的使用 * 对于小或不互动的对象,用户很少会注意到区域之间的差异。请使用盒子�idelity。

    • 对于小到中等尺寸的对象,请使用箱子或船体模型,取决于形状。

    • 对于大型和非常复杂的对象,尽可能使用隐形部件来建造自定义碰撞。

    • 对于不需要碰撞的对象,启用碰撞关闭和使用箱子或船体�idelity,因为碰撞几何仍然存储在内存中。

    • 您可以在 Studio 中通过切换�������idelity从视觉选项 小组中的 widget 在右上角的 3D 视图中渲染碰撞几何学。

      或者,您可以将 CollisionFidelity = Precise 过滤器应用到 探险器 ,显示所有网格零件的精确�idelity,并允许您轻松选择它们。

    • 了解有关选择碰撞精确度选项,平衡您的精确度和性能要求的深度,请参阅设置物理和渲染参数

微型分析器瞄准镜

范围相关计算
物理Stepped物理总计算
世界步骤每个框架的迁移物理步骤

物理学记忆使用

物理移动和碰撞检测会消耗内存。 网格零件具有一个 CollisionFidelity 属性,该属性决定用于评估网格边界的碰撞边界的接近方法。

常见问题

默认和精确的碰撞检测模式的内存消耗比其他两个低精度的碰撞模式要多。

如果您在 物理部件 下看到高水平的内存使用率,您可能需要探索减少体验中物体的 �������idelity

如何减缓

要减少碰撞忠实度的内存使用:

  • 对于不需要碰撞的零件,请设置 BasePart.CanCollideBasePart.CanTouchBasePart.CanQuery 为 2>2> 。
  • 使用 CollisionFidelity 设置来减少碰撞的忠实度。 Box 有最低的内存覆盖, Default 和 1> num.CollisionFidelity.Prec
    • 通常情况下,设置任何小型锚定部件的碰撞精度为 Box
    • 对于非常复杂的大型网格,您可能需要使用较小的对象(箱子�������idelity)来建建您自己的碰撞网格。

人形怪物

Humanoid 是一个提供多种功能于玩家和非玩家角色(NPC)的类型。虽然 Humanoid 很强大,但它的计算成本很高。

常见问题

  • 在 NPC 上启用所有的人形状状态类型 - 在某些 HumanoidStateTypes 启用后,会花费性能成本。禁用不需要的 Climbing 状态。例如,除非您的 NPC 要攀爬梯子,否则禁用所有 0>Ennum.HumanoidStateType|
  • 常常使用人形来启动、修改和重生模型。 * 这可能会对引擎的处理造成高度忙碌,尤其是如果这些模型使用 分层服装 。这也可能是在体验中经常重生的虚拟形象的情况下的特殊问题。
    • MicroProfiler 中,长度为 updateInvalidatedFastClusters 标签(超过 4ms)通常是一个信号,表明虚拟形象的初始化/修改正在触发过度的无效化。
  • 在不需要的情况下使用人形表情符号 - 静电NPC,通常不需要Humanoid 类。
  • 在服务器上播放大量 NPC 动画 - 服务器上运行的 NPC 动画需要在服务器上模拟并复制到客户端。这可能是不必要的额外负载。

缓解

  • 在客户端上播放 NPC 动画 - 在有大量 NPC 的体验中,请考虑在客户端上创建 Animator 并运行本地动画。这将减少服务器的负载,并且避免不必要的重复。它还可以启用额外的优化(例如仅为角色播放动画)。
  • 使用友好于人形的替代方案来使用人形。 - 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 中当前发生的绘制调用是多少,该 渲染统计 > 计时 项目。您可以在客户端上使用 渲染统计 来查看在 1>Shift1> 按下 3>F23> 。

在指定的框架中需要绘制的对象越多,绘制调用就越多,调用时对GPU。 但是,Roblox 引擎使用一个过程称为 实例化 来在一个绘制调用中使用相同的材质特性集合在一个绘制调用中折叠相同的网格。 具体地,多个网格具有相同的 MeshId 属性时,多个网格将在一个绘制调用

其他常见问题

  • 过度的物品密度 - 如果大量物体密度高,那么该场景区域的渲染需要更多的画画调用。如果您正在查看特定部分地图时发现您的帧率下降,这可能是对物品密度在该区域的一个好信号,表示该区域的物品密度太高。

    像贴纸、纹理和粒子对象不能很好地批量处理,并且会导致额外的绘制调用。 在场景中注意这些对象类型的特殊注意。 特别是对 ParticleEmitters 的属性更改可以有显著的性能影响。

  • 错过机会 - 常常,场景中会包含重复的网格,但每个网格的复制都有不同的网格或纹理资产 ID。这会导致无法实例化并可能导致额外的绘制调用。

    这个问题的常见原因是,当一个整个场景导入到 Roblox 时,而不是单个资产导入到 Roblox 然后重复导入到以便组合场景。

  • 过度的对象复杂性 - 尽管不是数量的 Draw 调用数量,但场景中的三角形数量会影响画面的渲染时间。 场景中有很大数量的很复杂的网格的情况下,很常见,因为有 MeshPart.RenderFidelity 属性设置在 Enum.RenderFidelity.Precise

  • 过度阴影投射 - 处理阴影是一个昂贵的过程,并且包含大量和密度高的光对投射阴影(或受到暗影影响的小部件密度高)的地图可能会导致性能问题。

  • 高透明度过低 - 放置部分透明度的对象会强制引擎多次渲染交叉像素,这可能会导致性能问题。 要了解此问题的更多信息,请参阅删除多层透明度

缓解

  • 实现相同的网格并减少相同的网格数量 - 如果您确保所有相同的网格都具有相同的基础资产 ID,引擎可以在单个绘制调用中识别并渲染它们。请确保您在地图上每个网格上只包含一个,然后在 Studio 中复制它们,以便引擎将独特
  • 清除 - 清除描述了移除对象不参与最终渲染框架的对象的图镜头调用的过程。 默认情况下,引擎会跳过对象外的视野(即,通过其他对象的视图(包括摘果和摘果的视图)的图画调用,但不会跳过
    • 隐藏 MeshPartBasePart 那些远离相机或设置的地方。
    • 对于室内环境,实现一个房间或传送门系统,该中心不是当前未使用的任何用户。
  • 减少渲染�idelity - 将渲染�idelity设置为 自动性能 。这允许网格回落到更复杂的替代方案,从而减少需要绘制的几何形象。
  • 关闭暗影投射在适当的零件和光对象上 - 场景中暗影的复杂性可以通过选择性禁用暗影投射属性在光对象和零件上关闭。这可以在执行时间时或动态时执行。一些示例是:
    • 使用 BasePart.CastShadow 属性禁用阴影投射在小零件上,阴影不可能在那里可见。这可以是特别有效,尤其是在远离用户相镜头的零件上。

    • 尽可能禁用移动对象上的阴影。

    • 在不需要投射阴影的情况下,禁用 Light.Shadows 在灯光实例上。

    • 限制光照实例的范围和角度。

    • 使用更少的灯光实例。

微型分析器瞄准镜

范围相关计算
准备和执行总体渲染
演出/场景/计算灯光/演出光网和暗影更新
LightGridCPU虚拟光网格更新
暗影地图系统暗影映射
演出/场景/更新视图准备对待渲染和粒子更新
演出/场景/渲染视图渲染和后处理

网络和复制

网络和复制描述数据在服务器和连接的客户端之间发送的过程。 信息在客户端和服务器之间每个框架中发送,但大量信息需要更多的计算时间。

常见问题

  • 过度的远程交通 - 发送大量数据通过 RemoteEventRemoteFunction 对象或使用它们非常频繁地调用它们可能会导致大量的 CPU 时间被花在处理每个框架中进入的数据上。常见的错误包括:

    • 复制不需要复制的每个数据框。
    • 复制用户输入的数据,无机制限制它。
    • 发送多余的数据。例如,发送玩家整个物品背包,而不是发送购买物品的详细信息。
  • 创建或移除复杂实例树木 - 当服务器上的数据模型发生变更时,它会复制到连接的客户端。这意味着在运行时创建和摧毁大型实例树木,如地图,可能会很网络忙碌。

    这里的常见罪魁是由动画编辑器插件在装备上保存的复杂动画数据。 如果在游戏发布之前将这些数据移除,并且动画模型经常复制,大量数据将无法必要地重复。

  • 服务器端TweenService - 如果 TweenService 用于 tween 一个对象服务器端,渐变属性将在每个客户端每个框架中复制。不仅如此,这会导致 tween 在客户端的延迟变化,还会导致大量的网络流量。

缓解

您可以使用以下策略来减少不必要的重复:

  • 避免发送大量数据通过远程事件发送 。 相反,只有在下列频率发送必要数据。 例如,对于角色的状态,在更改每个框架而不是每个字节时重复它。
  • 像地图一样把复杂实例树称为 “块”,并将其在多个框架中分段传递工作。 * 清除动画数据,尤其是装备的动画目录,在导入后。 * 限制不必要的实例复制 ,例如在服务器不需要知道创建的实例的情况下。这包括:
    • 视觉效果,例如爆炸或魔法法术爆炸。服务器只需知道位置,而客户端可以在本地创建视觉。
    • 第一人称物品视图模型。
    • 在客户端上调整对象,而不是在服务器上。

微型分析器瞄准镜

范围相关计算
处理包处理进入的网络包裹,例如事件邀请和属性更改
分配带宽并运行发射器服务器上有关的出发事件

资产内存使用率

对于创建者来说,提高客户端内存使用率的最高影响机制是启用实例串流

实例流媒

实例 streaming 选择性加载数据模型中不需要的部分,这可能会导致数据量大幅减少的时间,并且提高客户端在内存压力下来时防止崩溃的能力。

如果您遇到了内存问题,并且实例流媒已禁用,请考虑更新您的体验以支持它,特别是如果您的3D世界是大的。实例流媒基于3D空间的距离,因此更大的世界自然会受益更多。

如果实例流媒体已启用,您可以增加它的侵略性。例如,考虑:

  • 减少使用持久的 StreamingIntegrity
  • 减少 串流 радиius

了解有关流媒体选项的更多信息,请参阅流媒体属性

其他常见问题

  • 资产重复 - 上传相同的资产多次会导致不同的资产ID。这可能会导致同一内容多次加载到内存。
  • 过度资产音量 - 即使资产不相同,但在重用相同资产和节省内存的机会也会错过。
  • 音频文件 - 音频文件可以是使用内存的惊人贡献者,尤其是如果您将它们所有加载到客户端一次而不是仅加载您需要的部分体验。有关策略,请参阅加载时间
  • 高分辨率文本材质 - 图形记忆消耗对于材质的大小不相关,而是对纹理的像素数。
    • 例如,1024x1024像素的材质消耗了512x512的纹理的四倍的图形内存。
    • Roblox 上传的图像以固定格式转码为,因此无需上传颜色模型中的图像在每个像素上传下一个内存优势。同样,在上传颜色模型之前或从不需要它的图像移除 Alpha 通道可以减少图像大小在硬盘上,但不会提高或只是最小限度地提高内存使用率。 尽管引擎会自动将
    • 您可以通过在 开发者控制 中扩展 图形 类别来确定图形消耗。

缓解

  • 仅同步资源一次 - 重用同一个资源ID在对象上,确保同一个资源,例如网格和图像,不会在多个对象上同步多次。
  • 查找并修复重复资产 - 查找类似的网格部分和纹理,上传多次使用不同的 ID。
    • 尽管没有 API 可以检测到资源的相似性,但您可以在您的位置(例如手动或使用脚本)收集所有图像资产 ID,下载它们,并使用外部比较工具进行比较。
    • 对于网格零件,最佳策略是取用独特的网格ID,并按大小排序来手动识别重复项。
    • 而不是使用单独的 текстуры为不同的颜色,上传一个单独的 текстуры并使用 SurfaceAppearance.Color 属性为它添加各种色调。
  • 在地图上导入资产在地图上导入资产相互独立 - 而不是导入整个地图一次,导入和重建地图上的资产个别地图上,并重建它们。 3D 导入器不会进行任何网格重复,因此如果您导入了大型地图,每个网格都将导入为单独的资产
  • 限制图像的像素数量 为不超过所需数量。除非图像占用屏幕上的大量物理空间,否则通常需要至多 512x512 像素。大多数小型图像通常应小于 256x256 像素。
  • 使用 Trim Sheets 以确保在 3D 地图上最大限度地重用材质。有关创建 Trim Sheets 的步骤和示例,请参阅创建 Trim Sheets

加载次数

许多体验使用自定义加载屏幕,并使用 ContentProvider:PreloadAsync() 方法请求资源,以便在后台下载图像、声音和网格。

这种方法的优势是,它让您确保您的体验重要部分已经完全加载,而不会弹出。 但是,一个常见的错误是使用此方法来预加载更多资产,这些资产实际上不是需要的。

一个错误的做法是加载 整个 Workspace 。虽然这可能会防止弹出 тексту,但它会大大增加加载时间。

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

  • 加载屏幕上的图像。
  • 导入您的体验菜单中的重要图像,例如按钮背景和图标。
  • 在开始或生成区域中导入重要资产。

如果您必须加载大量资产,我们建议您提供一个 跳过加载 按钮。