I’ve always wanted to try and experiment as a personal project with making procedural terrain generation. I’ve found multiple ways to do so, however the bottleneck in every situation is always lag. Generating thousands of individual parts isn’t very helpful on performance because of the sheer amount of properties a part can have. My question here is if there is a way to make them not store as much information, or to be less impact in performance in any way so I can make larger areas of terrain? Or if not, if there is a good solution to reducing lag whilst still being able to add stuff post-generation (i.e. an ore vein, basically cannot just void parts I guess)
You could probably add a weldconstraint to all the parts of the cube, then set the CFrame of the root part using math.noise or something like that.
Sorry, but I don’t really understand what you mean by this. Why would I want to weld all of the parts together and then move them? The thing I want to accomplish here is to make each individual part have less of a performance impact so I can, for example, have millions of parts loaded (like minecraft does with blocks) while still keeping the same general performance. Unless I’m not understanding properly, all a WeldConstraint does it make 2 parts move relative to each other
Edit: I see what you mean now, but I believe that having a hollow cube would be even more performance degrading than just one.
Well I didn’t test the performance difference, so I don’t know if it’s better.
This is a good idea, but if we take a minecraft chunk (16 x 255 x 16), that would still be 65536 parts per chunk at their maximum and I think that would still cause lag.
I’ll test it later but I feel like that would only work once I can lower performance impact on a single chunk. All the generation I’ve done is small scale and it still decides to lag out at a point
A way to be more efficient is to implement culling.
So any parts that are not visible (none of it’s neighbours are air) are simply not rendered in or in this case, the part for the block is not created.
This way instead of thousands of redundant blocks you only have the blocks that the player can see which obviously makes it more efficient.
If you’re trying to make efficient parts like minecraft, a chunk-loading system would be the way to go.
Minecraft isn’t actually the most anti-lag game. I would argue that it actually is too precise; a lot of blocks are rendered precisely whenever it is loaded to the player, even if the player is not close to or looking at those blocks.
This is obviously filtered out by their chunk-loading system, which is what I would suggest doing. Have it be a sort of system that the player can decide how many chunks they want to load in dynamically.
I see a way I could go with this, but I have one other question.
Say I were to implement the culling feature, and not render / not place the blocks that are not visible. If I were to store redundant blocks in a table, for example, how can I make sure that the table does not get too large / take up too much memory?
This might not be the best solution, but I would assign the information of a block type like material, colour, and more to an Integer value. When a new chunk loads, store all the blocks in that specific chunk as an Integer with the Vector3 (can be a relative position, can be a world position) instead of as a BasePart in a table. When the chunk needs to be loaded again after the blocks despawn, generate the blocks based on the Integers and place them at the Vector3 position.
What I’d recommend (this is basically what Minecraft does, so I’m assuming your world is voxel):
- Chunks: Your “world” is stored in a 3d matrix (table with tables with tables). The client uses a remote event to request chunks then removes the memory of the chunks a little bit after it doesn’t need them anymore
- As someone said, culling. Hide/remove textures and parts that aren’t in view.
- Don’t create blocks that aren’t on “the surface”. If the block is surrounded by blocks, it’s not needed. Adding on to this, if a texture is next to a block it isn’t needed either.
- If you’re using textures, you can also use greedy meshing. Consume everything - how greedy meshing works
If you’re making triangle terrain, you should use culling and chunks (or something similar – send the terrain from the server).
Edit:
(First image I found on google)
This is what I mean by hiding parts that aren’t on the surface. A Roblox version would show part tubes with no textures though. Don’t use transparent parts and textures.
To answer your topic title’s question, yes. Elttob, the person who wrote the greedy meshing article, created BLOX, which, from the player’s perspective, has hundreds of thousands of parts.
Minecraft generates meshes for each chunk and doesn’t render the faces that are hidden behind blocks to reduce lag and as far as I know Roblox has no built in way of doing this. However there probably is still ways of improving performance like not rendering parts when they are behind other ones.
I was also thinking about doing something similar a while ago. my ideas were something along the lines of getting a “plane” mesh 1 faced mesh and putting a decal/texture on it. and like the other suggestions you need to have a good system to only load the faces that can be visible. You could even go as far to store data on the client about the terrain and only load it when visible ( maybe you do raycasts to an area of blocks ) or another system.
This has actually already been made by a smart person who made an uncopylocked Minecraft clone on Roblox. Sadly, it got reuploaded everywhere so I don’t know who made the original copy. Here is an example uncopylocked place that I found: Minecraft UNCOPYLOCKED - Roblox
You can see what techniques are used there to optimize the game as much as possible. By the way, always store block data on the server and have the client request the info whenever they get far enough. This way the server can generate the terrain using a Seed, store it in its database, and any changes made will only replicate to players who have that chunk loaded.
oh yea I understand why I was thinking you sould send small amounts of data on the client, so it could handle some of the workload trying to limit the amount rendered.
The solution to this problem is voxels. You cannot create them yourself, but you can use Roblox’s Terrain, which is a voxel system. I know Roblox’s Terrain is not what you want, but you will not be able to compete in the same scale or efficiency without the infrastructure that Terrain has.
Have you looked into StreamingEnabled?
It will probably help with performance in this situation and works similar to Minecraft chunks.
Ohhh I completely forgot about this ima go mess around with it and see what I can do.
Thanks!
No problem, It can be a bit finicky and I noticed that Adornees do not work with it enabled, but that isn’t that big of a deal.
Alright, so by using the culling technique I was able to make a 144 * 128 * 144 area that runs at a smooth framerate:
However, there are two issues:
- Roblox is using a LOT of memory to store these giant arrays. If this were to be scaled larger (i.e. players can load chunks as they go) this could probably end up crashing a server or slowing it to a crawl
- I think this same issue is also why roblox studio makes me force quit instead of being able to just click stop, because it can’t clean up the massive table. Is there any solution to this?
My tables are currently formatted as mapArray[x][y][z] = blockID
. Is this inefficient? I appreciate all of the help so far!
Wow! That’s a lot of memory. I think some of that is coming from having both an array and parts.
What I’d recommend:
- Client sided parts: No parts on the server, just the
mapArray
(so the server doesn’t have both the array AND the parts). Then send chunks to the client for viewing and physics. - This might be difficult, but regenerating chunks and only saving changes made to chunks (like how Minecraft creates an infinite world).
What you can do to trouble shoot:
- To test if the memory problem is caused by the array, try this code or something similar on the server:
local mapArray = {}
for x = 1, 144 do
mapArray[x] = {}
for y = 1, 128 do
mapArray[x][y] = {}
for z = 1, 144 do
mapArray[x][y][z] = math.random(1,64)
end
end
end
game:GetService("RunService").Heartbeat:Connect(function()
print(mapArray[math.random(1,144)][math.random(1,128)][math.random(1,144)])
end)
You’ll notice that the server seems to be doing fine (quick tip, you can press F9 to view some useful info, like server stats). This means the problem is probably having so many parts on the server, so client sided parts should fix your problem.