I created a checklist/guide for optimizing your builds:
Let me know if I missed something, I’m a scripter so I don’t know much about building .
OPTIMIZATION GUIDE FOR BUILDERS
You may already be using some or all of these methods but just in case:
Meshes
-
Meshes should be reused as much as possible. Each new imported mesh takes up memory, so every mesh counts. *Be creative! Resize, recolor, rotate, scale
-
If a mesh has a lot of triangles (use wireframe rendering to check), make sure that it is decimated (in blender). Try to reduce the use of complex geometry as much as possible
-
Remove useless faces
-
You can also use a Limited Dissolve operation which will merge faces that have too small of an angle between them (In basic terms, trying to keep curves but removing excess geometry in flat or near-flat areas).
-
Avoid using n-gons
-
Adjust render fidelity appropriately: RenderFidelity | Documentation - Roblox Creator Hub
-
Do NOT use special meshes or limit their use as much as possible
-
Avoid unionOperations AS MUCH AS POSSIBLE
Read this for more detailed insight: MeshPart Usage, Performance & Optimizations
Check your budget
Recommended Budgets
Here’s what I currently recommend* (mid 2024) for some good budgeting numbers, which will still let you have great looking scenes at 60 FPS on most (90%+) of phones and tablets out there, without causing the quality settings to automatically degrade off of “Quality 10.”
500,000 triangle budget for triangles “in scene”
500 drawcall budget “in scene”
Low as possible CPU usage on PC - old phones are nowhere as quick as PCs are
Aim for < 1.3GB client memory usage in order to support 2 GB phones (this article isn’t about memory usage, but this is good intel)
Aim for under 50KB/s network receive down on client - (40-60 moving assemblies - more on this later!)
use rendering statistics to check your budget: https://devforum-uploads.s3.dualstack.us-east-2.amazonaws.com/uploads/original/5X/d/6/3/5/d6357a2277b950d2da02f4dc950cce9bc3cffab7.png
tells you how many drawcalls (95 in this case) and triangles (138634) your current view is rendering.
As mentioned, you should be regularly using this to look around your world and figure out where your hotspots are. In this map’s case, it never really gets above about 100 draws or about 150k triangles, which is well under our budget
- shadow map is preferred for lighting and reducing memory usage
kitbashing
Kitbashing is the process where you assemble, intersect and reuse lots of mesh parts instead of carefully building custom geometry and fussing about hidden face removal. Games such as Elden Ring, Fallout and Skyrim are famous for using this technique to speed up building.
Why kitbashing is efficient is due to drawcalls. As aforementioned you only have a limited budget of drawcalls per frame.
An example of it here is this tree that gets used all over the map:
Even though the trees are made up of four meshes each - three top parts and a trunk - by using different colors, rotations and scales, it ends up only costing 2 drawcalls to draw all of them. If they were using different materials on each tree, or were unique mesh ids, they would be using a lot more drawcalls and our budget wouldn’t go very far.
*if your mesh basically still looks the same when it’s in wireframe mode, it’s perhaps too many triangles.
Collisions:
(meshes mostly)
-
CanCollide - Toggles whether the object can register collision from other objects
-
CanTouch - Toggles whether the object can be registered by Touched events
-
CanQuery - Toggles whether the object can be registered by spatial querys (e.g RayCasts)
-
CollisionFidelity - Sets the underlying geometry of the objects collider (Hull, Box, Precise, Default)
- Set the appropriate collision fidelity https://devforum-uploads.s3.dualstack.us-east-2.amazonaws.com/uploads/optimized/4X/1/7/2/172bbe5ee38aa47e0dce205f6fc5b355f5d3dec9_2_517x238.png
If you are not using touch events on the object then set CanTouch = false (Keep this on for Seats and VehicleSeats)
If the object isnt being used in spatial querys (e.g RayCasts) set CanQuery = false (Not recommended for fps games where gun RayCasts would be used a lot)
If the object is small or insignificant (e.g Plate,Cup,Pebble etc) or the object is inaccessible to the player the then set CanColide = false
Do not use the Default or Precise CollisionFidelity options unless absolutely necesery (these options use the mesh as the collider meaning your essentially doubling the triangle count) You should be using Box collisions or Hull collisions depending on how realistic want the object to collide
If an object has CanCollide = false always use the Box collision mode (reduces the poly count of the un used collider to just a box)
Surface decals and Mesh textures
When applying decals and texture instances to your objects they will double the surface geometry of which they adorn to which is not good at all
Never place a decal/texture onto an object that isnt a cube this is to avoid as much geometry duplication as possible
When applying a decal/texture onto an invisible object set CastShadow = false on the object as this will cause the surface geometry to be doubled
Avoid using image/texture resolutions over 512x512 (unless you are using a surface appearance or material variant) as they are stored uncompressed in memory
Textures and decals:
-
Avoid using textures above 1080p
-
Avoid using too many different textures, each image takes up memory
-
Try to reuse existing textures/images by messing around with the setting
Parts
When possible:
-
Disable: CanTouch, CanQuery, CanCollide, CastShadow.
-
Make the parts have SmoothPlastic as a material.
Misc. (IMPORTANT)
- If you can’t see it, it shouldn’t be there
- ^ HELPS WITH PERFORMANCE A LOT!
As listed in [Extra Source (3)], we may implement custom render distance mechanics to reduce lag for lower-end players. To accomplish this, the city may be divided up into different “zones” and only an x amount of zones around the player will be rendered. To script this, we will need a model for each zone in the city. The builders can decide the amount and size of zones. All parts must be grouped together inside that zone. The model should have an attribute called “Zone” which states its identification/index number.
→ ZoneManager.rbxl - Google Drive
^demo file of zoning. Look at organization (primarypart, tag, and attribute). Credits to MrChickenRocket (1).
We are also considering distance culling. By applying an attribute or tag to objects which provide small details (ex pebbles, small rocks), we can unload those when out of distance. To accomplish this, we will need builders to apply an attribute or tag on small-detail objects called “SmallDetailObject”
These two are not set in stone yet and may or may not be implemented. However, you should keep it in mind in case we decide to use those techniques.
I highly suggest you to study on drawcalling, triangles, and optimization concepts.
Extra sources:
A small guide to optimizing your maps and builds (1)
Performance Optimization | Documentation - Roblox Creator Hub (2)
Real world building and scripting optimization for Roblox (3)