So we now have a procedurally generated mesh! Obviously there is much left to do getting it culled and optimized. There's a bigger problem right now, though. Instantiating as few as 16 chunks kills my computer, just have a look at the framerate in the image below.
Disabling all collision, shadows, physics, overlaps and whatnot didn't do anything. Everything is using the same material... This must be because I'm creating so many procedural mesh sections. I think that each mesh section might be creating its own draw call. The Unreal Docs don't explain what a 'mesh section' is, so I'm going to have to go with my gut.
Here's how I plan to fix this:
Where I would usually create a new mesh section, I will instead add to a new array--this will mean that I will only create one mesh section at the end of all calculations. Then, we will create one big 'mesh section' per chunk.
My gut was right.
I'm now getting better performance even though I messed something up and my triangles are all over the place. (although, that does further prove my point)
Creating new mesh sections for each quad is definitely the culprit. It takes a while to generate a chunk this way, but once it's generated, it's snappy as can be. I'm hoping to make optimizations to the mesh generation logic moving forward.
Here is the simplest and most performant solution I could come up with to correctly place triangles. Creating a new integer and incrementing it each time we create a quad, then creating a new set of triangle indices at that index, multiplied by 4.
(Since each quad has 4 vertices, they therefore have 4 indices.)
Back to ~120 fps!
Things are snappy again. I wonder, with just this culling, how many chunks can we create? The chunks seem to do very well at runtime, but the expensive computation is *generating* the chunks. That's where I expect things to fall apart. Generating a 4x4 chunk with the existing code is already rather expensive.
But there's another issue at hand.
A for-loop in Unreal Engine is run between frames, and until now I've been doing much of my logic within for-loops. That is why the engine crashes when I try to generate a world larger than 4x4 chunks. Blueprints don't allow access to truly multithreaded operations or coroutines, and we can't hang up the main game thread, because that will cause the game to actually crash.
After some investigation, the best way I can fathom to handle this is by using callbacks with delay nodes. At the end of each Chunk's instantiation loops, I'll call a delay node, and then a callback function on LevelManager. I'll call it CreateNextChunk, and within that function I can add to a new array ChunkArray (existing variable)check which Chunks are created, and do a pseudo-for-loop that increments through NotCreatedYetChunks. (new array of vectors, since we only need the location information for now)
This will allow me to keep my original for-loop within OnBeginPlay, but break apart the creation logic into something a bit more async.
One thing I haven't mentioned yet is Perlin noise, and how I'm going to use it to create my game worlds. I probably want to do a small perlin noise generation function at the beginning of each match, and every chunk I create within the LevelManager will be created from this perlin noise. Then, when I actually build out the chunks asynchronously, the information for each chunk will be pre-determined. Since I only have dirt blocks as of now, it's not a great time to implement this kind of world generation, but this is definitely where it should be done when it's time to do so.
This also means my level should have a cool chunk generation loading screen, just like that of Minecraft. 👀 Adding that to the icebox now. I could also have a few pre-generated levels to make things faster since Mordhau and not Minecraft.
A delay node with 0.0 seconds is needed for the callback to run in another frame, and not crash the engine. Framerate will be incredibly slow as the level builds. We will hide this behind a full-screen loading screen later on.
Why don't they document this stuff? 🤦♂️
Another idea for chunk generation
We could even hide players in another zone, and hide the rest of the level, giving them time to build up their base (Frontline mode) while this stuff builds in the background. That way, we could time the loading of the level perfectly with the time we give players to build up their base (which will probably not be randomly generated.)
Now that I was able to figure this out, it gives me a lot of optimism for the future of this project :)
Now that I have the systems in place to allow me to create the optimized meshes, it's time to actually implement a greedy mesh implementation.