Generated maps make caves and its breaking my game

Bumping again since i haven’t gotten a response for a lot of time and its a big issue for my game that i cant find on why its happening

2 Likes

Alright, I just took a deep dive into Perlin noise and how it is influencing your terrain generation. Things got mathy REAL quick :rofl:

Your density range may be your problem here. Your density function is as follows:

xnoise = math.noise (x/23, z/23, seed) * 15
znoise = math.noise (z/noisescale, x/noisescale, seed) * amplitude
local	density = xnoise - znoise + y`

Lower noisescale values create a more varied change from block to block (local), while a higher frequency create greater sustained changes in elevation on a more larger and grand scale on your map (global).

Then, this is the range you accept:

 if density < 5 and density > 2 then

First things first, I do not see the point of setting a lower bound to Density. I personally believe this is the main culprit as to why you are having these lower level caves. Change the line to:

 if density < 10 then

Next, I would reccommend changing your amplitude and noisescale values. If you want some real dynamic changes, what I implemented is variation of the amplitude and frequency per map spawn, where the minimum and maximum follow a normal distribution. Here is the code for this:

function module:LoadMap(DelayTime:number,CustomSeed,SpawnCharacter:boolean,RandomGeneration,Coins)

local function boxMuller()
	-- box-muller method for normal distribution
	local u1 = math.random()
	local u2 = math.random()
	local r = math.sqrt(-2.0 * math.log(u1))
	local theta = 2.0 * math.pi * u2
	return r * math.cos(theta)
end

local function scaleToDesiredRangeNorm(x, desiredMin, desiredMax)
	local normalized = (x + 3.09) / (7.18)
	return normalized * (desiredMax - desiredMin) + desiredMin
end

function NormalDistribution(desiredMin, desiredMax)
	local x = boxMuller()
	if x >= -3.09 and x <= 3.09 then
		local normvar = scaleToDesiredRangeNorm(x, desiredMin, desiredMax)
		return normvar
	else if x < -3.09 then
			local normvar = desiredMin
			return normvar
		else
			local normvar = desiredMax
			return normvar
		end
	end
end

local	GeneralSize = 50
	local	mapxsize = 25
	local	mapysize = 26
	local	mapzsize = 25
	local seed
	local	ExtraPosition = Vector3.new(0,15,0)
	if CustomSeed == nil then
		local Randomer = Random.new()
		seed = Randomer:NextInteger(1,100000)
	else
		seed = CustomSeed
	end
	workspace.GenerationData.Seed.Value = seed
	print(tostring(seed))
        local minAmplitude = 6
        local maxAmplitude = 17
        local minNoiseScale = 8
        local maxNoiseScale = 26
	local	noisescale = NormalDistribution(minNoiseScale, maxNoiseScale)
	local	amplitude = NormalDistribution(minAmplitude, maxAmplitude)
	local	blocksize = 5
	local	xnoise = 0
	local	ynoise = 0
	local	znoise = 0
	local	AllParts = 0
	local AllPartsFolder = workspace.AllParts
        -- rest of code

Let me know if this fixes your problems!

2 Likes

Also, you might want to take a look into FBM (Fractal Brownian Motion), where you create multiple “octaves” of math.noise each with different scaled amplitude and frequency (and then adding them all together with a for loop). You will need to implement the numbers for the changes in amplitude and frequency between the octaves.

The changes between the frequency of octaves is known as lacunarity. A higher lacunarity means that the changes in frequency between the octaves increases, which makes the pattern of the generation more complex and jagged.

The changes between the amplitude of octaves is known as persistence. A lower persistence makes the differences between the amplitude of octaves less, thus making the pattern of the generation more smoother.

Im going to try this when i come back home ( in about 1-2 days but i want to ask something for some reason the generation works perfectly if i use the test generation do you know why?

1 Like

What do you mean by test generation?

I have 2 codes i used
•Game management
•Test generation
They are 2 completely different scripts i generate maps with though it executes on the module still
But they have kind of different results

Im back home now first thing is that i put destiny like that to have less parts rendered and so it loads the map faster

Secondly this sadly did not work
as shown here this is the result after exactly 3 maps that were generated
its always the third one that has caves
image
also this could be a little related to the issue but sometimes the maps are the same
even though they are generated by perlin noise do you know why this is happening?

Edit: found out there are random holes sometimes in the generation when i test it
image

1 Like

Are you still facing the problem, if so, just let me know.

Bumping again since i have no replies for a long time and i cant find the solution
also i didnt see this but

Yes i am still facing this issue

bumping again since i cant really continue my project without this and its been a week now
also @armenia1997 if you could help it would be good

bumping 1 more time if i dont get any replies i will just make another post

Your test code has fewer than 100 possible seeds, so you’re going to see the same maps a lot. Perlin noise is not random, the function math.noise(x,y,z) always returns the same result for the same x, y, and z values, so your only source of randomization is the seed z value, which is some integer from 5 to 100 in your code.

Your main Perlin Noise generation loop is code that is trying to make a voxel surface that is 3 voxels thick. For each voxel column, the value (xnoise - znoise) determines the height of the surface (the range of y values between which blocks will be placed). If you’re just doing multiple calls, or just one pass on top of some random flat-ish foundation, then you’re going to get caves most of the time. Was a 3-block-thick surface not what you intended to code?

When you voxelize a surface like this, it’s always possible to get holes if the gradient of the noise surface you’re sampling is too steep. In your case, anywhere your offset (xnoise - znoise) differs by more than 3 from its neighbor columns, you can get a hole in the surface. You have to set your amplitude lower or noisescales higher to reduce the maximum possible gradient the surface to below 3.

Also, the way you wrote your LoadMap function, if you have the misfortune of passing in a CustomSeed that results in 1250 blocks or fewer being placed, that function will call itself repeatedly with that same custom seed until the call stack overflows (will break your game)

1 Like

i did that to save performance


How would i make it random then?


it only does this when the result doesn’t have many blocks which just makes it run it again till it finds one with enough blocks
it never breaks it
image
as seen here it only does it like 1 time every generation mostly


Also no one has answered me this yet if i use the simple code to make a generated map it always has a good result but if i use the game management code it makes caves and breaks after 3 attempts

Right, because your test code is all calling the LoadMap function with CustomSeed = nil. I was saying that your code will break if you call this function with an explicit CustomSeed that makes a small map, because the function with then call itself endlessly with that same CustomSeed value. The tail call should probably pass nil for CustomSeed, or just exit with a warning.

This is because you posted a lot of code, but no sample file. People won’t take the time to try to bring all your scripts into a place file one at a time with copy-paste to see what’s wrong. If you want help diagnosing this much code, your best bet is to post a minimal-repro place file (rbxl) that you can just click Play on and see the problem. It’s not clear to me from your screenshots how you’re getting caves from code that makes a 3-block surface. It looks like multiple passes are generating on top of each other, like you’re not waiting for the previous map to despawn before spawning a new one or something like that. Hard to say, it’s too much code to just read and try to figure out what’s going on vs. what you intended.

Maybe try to reload your code? if it’s problem with generating new map via perlin or something, you have to reload entire code, soo it will start from nothing. Despawning can be more simple, use clear all children for this, or loop.

the use of reports of bugs that the seed will be sent for me to look at the generated map its not going to be used in game normally


okay but most of the stuff is deteled but the code should work just as it should for the generation
Here is the file :
GenerationMapDevforumFile.rbxl


also that is just how many coins will spawn on the map which i made it to be random

Ah, right, I see that now. There might still be a problem here though:

and

The problem is that the reference implementation of Improved Perlin noise, which I believe is what math.noise uses, is periodic in every direction with a period of 256. So, even if you have 100k possible seed values, because they are integers, you’ll only get 256 different maps. Try changing your calls to be like this:

math.noise (x/noisescale, z/noisescale, seed/392)

You can still generate an integer seed of 1 to 100k, but divide it down so that your math.noise third value is a float in the range of 0 to 256.

Also, move the local Randomer = Random.new() line outside of your LoadMap function. The way it is right now, your instantiating a new random number generator instance for each random number you need. Not only is this expensive, it’s not necessarily even good randomness; it all depends on how the Random class is seeded internally. How you’re meant to use Random is to make one generator with Random.new() at the top of your file, and then repeatedly call NextNumber() on that same instance.

i did both stuff but still getting caves after 3 attempts on the game management script
but not on the test generation script
which is still not answered on why it works normally with the test generation script and not the game management yet

i found the issue finally by myself
i was using Event.Event:Connect(function()
instead of Event.Event:Wait()
which would run the code i guess more times and break it

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.