Jesusemora im making a minecraft like game and need chunks for procedural world generation. base chunks are 19 x 2 x 19 blocks as you can see here:

and this is the indivisual block:

under each chunk, there is a multi mesh instance for each block variant(currently two for dirt and stone), and after spawning in, the blocks fire raycasts with the ray.force_raycast_update()
to determine which sides has blocks,and which don't, the free sides are rendered and the occupied sides are skipped and since the rays are not enabled afterwards, its very efficient.
now to the profiler:

this is the game when generating chunks and its mostly due to my script that the game lags, the first five take the most, however, UpdateBlocks() and onewayUpdateBlocks() only happen once during world generation and their information is saved, afterwards, they only run on the block that is added to the chunk or destroyed. so they dont matter once the world is loaded.
here is how the game runs after world generation:

as you can see, the lag is purely during generation. i was building a house during this and suffered no lag during it, leading me to believe this is only caused by the generation and nothing else.
the second huge lagspike happened when i moved far enough for new chunks to generate.

moving back to the house and previously generated chunks, the frames are much less affected, which makes sense since the UpdateBlocks() and OneWayUpdateBlocks() and a bunch of other functions dont run this time. even at its peak its barely half as bad as before. which brings me to the _process()
function which is the heart of the world generator.
the code of the generator(sorry in advance for the way this looks i still haven't learned how to post code idk why its like this):
extends Node3D
var spawnarray : Array = []
var despawnarray : Array = []
var limit : int = 50
var distance : float = 2.6
var chunk : PackedScene
var chunkpos : PackedScene
func _ready():
chunk = preload("res://Scenes/chunk_body.tscn")
chunkpos = preload("res://Scenes/Chunkpos.tscn")
spawn()
func _process(delta):
if spawnarray.size() != 0:
var lastindex = spawnarray.size() - 1
var chunked
if lastindex != -1:
chunked = spawnarray[lastindex]
if chunked is Area3D and chunked.attachedchunk == null:
print("im here")
var cb = chunk.instantiate()
chunked.add_child(cb)
chunked.attachedchunk = cb
elif chunked is Area3D and chunked.attachedchunk != null:
chunked.add_child(chunked.attachedchunk)
if lastindex >= 0:
spawnarray.resize(lastindex)
if despawnarray.size() != 0:
var lastindex = despawnarray.size() - 1
var chunked
if lastindex != -1:
chunked = despawnarray[lastindex]
if chunked is Area3D and chunked.attachedchunk != null:
chunked.remove_child(chunked.attachedchunk)
if lastindex >= 0:
despawnarray.resize(lastindex)
func spawn():
for x in limit:
for z in limit:
var chunkinst = chunkpos.instantiate()
call_deferred("add_child", chunkinst)
chunkinst.global_position = Vector3(x * distance, 0 , z * distance)
if z != 0:
var chunkdupe = chunkpos.instantiate()
call_deferred("add_child", chunkdupe)
chunkdupe.global_position = Vector3(x * -distance, 0 , z * -distance)
if x != 0:
var chunkdupe2 = chunkpos.instantiate()
call_deferred("add_child", chunkdupe2)
chunkdupe2.global_position = Vector3(x * -distance, 0 , z * distance)
if z != 0 and x != 0:
var chunkdupe3 = chunkpos.instantiate()
call_deferred("add_child", chunkdupe3)
chunkdupe3.global_position = Vector3(x * distance, 0 , z * -distance)
the short version is that the spawn function makes Area3Ds that detect when the player is near or far away. here is their code:
func _on_body_entered(body):
visible = true
get_parent().spawnarray.push_front(self)
func _on_body_exited(body):
visible = false
get_parent().despawnarray.append(self)
with this the world has an array of the chunks it needs to load and goes through them one by one and deletes the ones that arent in range anymore, currently the range is 140 blocks in every direction, which after the world generates, is pretty good.
you may ask why have an array in the first place, well, spawning all chunks at once was even worse and slower, by using an array to do them one by one, i avoid freezing the game and increase frames a little, but its still not enough.
to answer your question, im on godot 4.2.1 stable mono, and i have tried smaller chunks, the issue as i mentioned is that since the game uses multimeshinstances to load the world, the more chunks there are, the more draw calls the cpu has to send to the gpu, the slower the game becomes, i tried this earlier with 5x2x5 chunks and while world generation was completely lagless, the same render distance suddenly became laggy , as in the middle part of the profiler which was completely lag free now had lag, forcing me to lower the render distance.
so, bigger chunks mean higher render distance, but slower world gen and vice versa. i already have plans to make the world generator run with a 11x2x11 chunks, which seems to be the medium between generation speed and game speed, and i might even switch off a lot of graphics for this if i have to, but i just had to ask before i try anything.
in the func _process():
code of the world generator, which is the main cause of lag according to the profiler, the only code i can see that could have any reason to be this laggy, is instantiate()
and add_child()
, hence the title of this post. if there is a way to make this part of the code run better, please share them.
thank you for reading and have a nice day.