I’d got this procedural generated terrain (using perlin noise) and I want to create caves in it.
Caves like in minecraft!
Issue is I can’t use math.random or perlin worms because the worldmap is loaded by chunks.
So I cant load chunk A then have chunk B loaded afterwards with a cave that’s stretching through chunk A (because chunk A is already loaded). I can’t load both chunk A and B because that’d require me to load the whole world before the game ever starts, and that’s too big of a job.
Does anyone know a simple good way to create a cave system that’s seed based and can be loaded in chunks?
Have you looked into how the mining simulators work?
I ask as I believe you could use the principle it uses for generating new blocks as you progress throughout the dig.
If you picture it working more sideways than down you maybe able to get something like you want.
There would be no continuity between the loaded chunks then.
If chunk A has already loaded then it can’t suddenly be changed just because a cave that started in a nearby chunk wants to cross chunk A.
I recall reading somewhere that Minecraft actually does use Perlin worms (1), but with a limited horizontal length, e.g. they can travel at most 3 chunks from their start position. The Perlin worms are then generated 3 chunks ahead of the actual terrain. That way, when a new chunk needs to know about all Perlin worms that might influence it, they’ve already been generated.
EDIT 1: Here’s a link to the gamedev.stackexchange thread where I originally read it. Don’t think MC is actually mentioned there. It’s the very last answer. The whole thread and the linked threads are pretty good reading.
EDIT 2: Actually that first link I provided isn’t that great, but here are some of the gems that I dug up by following the linked threads:
gamedev.stackexchange thread with a slighly better/more in-depth explanation of the method I talked about originally.
I don’t know if this is the best idea, but here goes:
Seeding:
Don’t forget the ever so useful option:
math.randomseed() --insert your seed in here
Unless I am severely incorrect, I do believe that operation yields the same “randomness” across worlds if the seed is same.
Chunk loading: Style 1:
Perlin noise already sections parts into boxes (if you are using 3D perlin) or at least contains a 3D space in between 4 points, which we can call a chunk. Since each point must have a position relative to the other blocks (Which are already assigned from the algorithm) you can determine the distance the player has to each point, by making sure each point has a Vector3 point (In case you are using fancy array, tables, and OOP to do perlin).
Selecting the points is a system that kind of eludes me, but choosing to load the closest 32 or 64 points is probably what I would do.
Style 2:
Determining distance to each chunk could be done by averaging the 4 points of a chunk that comes out to a position for the chunk. This would lower the amount of points you would need to check possibly making the system/algorithm much faster.
Once you’ve gotten all the chunks sorted out and distanced to the player, selecting the chunks to load would have to be the 3-9 closest horizontally and then load the 6-18 chunks that are a single chunk above and below the horizontal ones you’ve chosen to load.
To see if the method I posted would work, I tried making a simple 2D cave generation system. The caves and chunks are 2D, but it should be pretty easy to change it to 2D chunks + 3D caves, or even 3D chunks + 3D caves.
You can check it out here. It’s uncopylocked, so if you’re interested in implementation details you can check it out. Feel free to ask questions if you have any, and I’ll try to clarify.
It uses the method I described, i.e. generating the Perlin worms ahead of the view distance. Just keeping the coordinates for each segment of each perlin worm shouldn’t be much of a problem, compared to the entire generated map. And of course, Perlin worms that start outside the currently generated part of the map are still seen when they move into the view distance.
Here are some screenshots showing it in action :)
Perlin worms are visualized. They start at the neon part, and get darker as they go along. The big transparent squares show chunks that have worms, but where the caves/terrain has yet to be generated.
Here, the blue worm was in a part of the terrain that was generated. I then moved towards the purple chunk, and it successfully used the blue worm to generate it’s caves. Even though the two chunks were generated at different times, they still influenced each other.
The chunks are super helpful. Each chunk has 20x20=400 tiles, and checking the distance to each and every one (400x25 = 10000) every 1/4 of a second would take too long. Instead I just check the distance to 5x5=25 chunks, and load/generate the tiles inside that chunk regardless of their individual distance to the player.
If you go to the place to test it, there might be some weird bugs because if a lot is going on, it might take a frame after calling Instance.new(“Part”) before parts are registered with raycasts. Also creating tonnes of new parts every time the player moves to a new chunk bogs down the network connection, I think.