Make it easier to prevent wasted script work on off-screen and distant objects

As a Roblox developer, it is currently too difficult to increase performance by skipping script work for objects the player cannot see. My code updates properties on thousands of BaseParts every frame: setting CFrame to move them, and Transparency, Color, or Size to drive effects and state changes. This is not the built-in animation system (Animator); that system is designed for character rigs and is too expensive to run on thousands of separate parts, so the standard approach at this scale is to set each part’s properties directly from a script every frame. Much of this work is spent on parts that are currently off-screen or occluded, but a script has no way to know that a part is culled, so it keeps updating parts that are not being rendered. This is not only a framerate cost: if enough updates land in a single frame, the client can hitch, freeze, or crash outright, particularly on low-end devices. If a script could read whether a part is culled, it could stop updating parts that are not being drawn and spread the remaining work across frames, which would both recover performance and prevent these crashes.

Today I approximate culling myself using two methods, and both are insufficient. First, I throttle updates by distance, so parts farther from the camera update less often; but distance is a poor proxy for visibility, because a nearby part can be fully occluded and a distant part can be in plain view. Second, I run a custom frustum check to test whether a part is within the camera’s view; but this only accounts for the frustum, has no knowledge of occlusion, and so keeps updating parts hidden behind walls. Camera:WorldToViewportPoint has the same frustum-only limitation and is expensive to call on thousands of parts per frame. Performing an actual occlusion test in Luau, such as raycasting toward the camera, costs more than the update work it would save. Most importantly, none of these can match the client’s real culling result, because culling depends on the player’s graphics quality setting and other engine state that is not exposed to scripts. Two clients viewing the same scene can cull different parts, so no Luau calculation can reproduce what the engine actually does.

If this issue is addressed, it would improve my development experience because I could skip per-frame work for anything the client has actually culled instead of relying on a frustum estimate that can’t see the real inputs. Where this would help:

  • Skipping the property updates my own code performs every frame, like moving a part or changing its transparency or color. Roblox’s built-in animation system already slows down when things are off-screen, but it can’t handle thousands of objects, so I set properties directly instead, and that manual work has no way to know when it’s safe to stop.
  • Throttling how often I update something based on how far or visible it is, which I currently have to do by computing magnitude myself every frame.
  • Skipping the small idle motions I add to collectibles, pickups, and props, like a coin slowly spinning, which code produces by repositioning the part each frame.
  • Stopping faked animated materials done through property updates, like scrolling texture offsets, pulsing neon, or emissive flicker.
7 Likes

So TLDR: Add streaming behaviour to entity culling?

Not quite. Streaming (StreamingEnabled) unloads instances by distance from the character, and it has the same blind spot my distance throttling does: it can’t tell a nearby part is occluded or a distant one is in plain view. I’m not asking for anything to be unloaded, though. The parts exist and my scripts are actively updating them.

I just want a read-only way to ask “is the renderer currently drawing this part?”, accounting for occlusion and graphics settings, so my per-frame code can skip parts that aren’t being drawn. And to be clear on scope, it’s any BasePart I update, not just NPCs or “entities.”

2 Likes