My game uses a chunk system for its world, however, when i use the add_child() method to add the chunks to the scene, there is a moment where the game freezes and then continues after the chunk is spawned in. i have tried avoiding this by making smaller chunks, however, since my chunks use MultiMeshInstance3Ds for rendering, its best to keep the size of each chunk larger so i can use the multimeshinstance to its fullest potential.

so, I'm wondering if there is a way to add these chunks without it freezing and effecting frame rates. i would rather the chunks load in slower in exchange for this freezing to go away.

thank you for reading and have a nice day.

  • Jesusemora replied to this.
  • this has been a very eye opening past couple of days for me, in this short amount of time, I have learned a ton and have changed my approach to making my game moving forward.

    but for starters, i guess the answer to the original question is a no, and the solution is to simply divide the spawning of the node into smaller nodes over multiple frames. that seems to be the way to go for spawning large amount of blocks, or any other node in the scene.

    Jesusemora when you hit a wall, there's no way to get past it. you have to go back and fix your foundations and start over, or make compromises, sacrifice features and scale. this happened with every single game, there's cut content, and features that were changed.
    in XCOM it was uneven maps, in plate up the bees, in oblivion the AI. It's just part of game dev, you can't have it all.

    Jesusemora it has nothing to do with talent, it has to do with experience. this is the first voxel you have ever worked with. I have lost count of how many I have made.
    You never succeed the first time, you have to fail and fail and keep trying, and learn every time you fail.

    Thank you so much for your encouraging words, i understand how frustrating it is to see someone making so many mistakes in something you are an expert at, so im very thankful for your patience and feedback.

    so far, i have removed raycasts from my block and no longer use Area3Ds to detect where the player is, as you have suggested. i have also reworked the way chunks spawn, and the results are pretty great! it still needs a little polish, but i think im on the right track. i have also changed the way the game renders the blocks, previously each chunk had its own MultiMeshInstance3D to render its blocks, but now all the blocks in the world are handled by a few MMI3Ds, removing the issue of the cpu sending to many draw calls when there were to many chunks rendered at the same time. i still haven't goten to making an atlas for all my block textures like you suggested, i know the concept behind it since i heard about terraria using this method, but i don't know how to do it in godot with Multimeshinstance3Ds, yet. i would once again be grateful if you could give me some advice about this, even though you have already helped me alot.

    xyz yes i understand. honestly, i was just very exhausted from spending so much time on this and getting nowhere, to the point i just wanted it to be over and move on to the more fun features of my game. im no expert in voxel games, but if im going to do this, then i have to do it right since this will affect every aspect of my game moving forward. as i said, i have been doing some research into voxel and my new chunk system is coming along nicely, ill have something figured out for my different sized blocks after im done with this, but now i know that the old system had to be scrapped and i will definitely make a better one.

    as far as this post goes, i think im ready to move on. again, thank you so much @Jesusemora for all the feedback and suggestions, you have really opened my eye in this matter.

    thank you for your time and have a nice day.

    Rose19 the game freezes and then continues after the chunk is spawned in

    sounds like your script could be badly optimized. could you show some code?

    Rose19 i have tried avoiding this by making smaller chunks

    and does this work???

    Rose19 chunk system for its world

    Rose19 chunks use MultiMeshInstance3Ds

    Rose19 a way to add these chunks without it freezing and effecting frame rates

    what are these chunks? you are giving us nothing that would help us help you.

    here's some hints:
    1 - some code
    2 - godot version
    3 - screenshots of the tree, inspector and or node tab if signals are being used for some reason

      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.

        Would preloading in a separate thread be feasible?

        Rose19 how to post code

        Place ~~~ on lines before and after the code.

          Rose19 Are you someone with little experience with programing?

          First, a Voxel should have a 3D matrix in memory. gdscript only has 1 dimensional arrays so you have to access the items through a custom function. Here's mine for reference:

          func set_voxel(pos : Vector2i, value : int, Xcel : int) -> void:
          	match Xcel:
          		0:
          			if len(Voxel_units) > pos.x * pos.y:
          				Voxel_units[(pos.y * map_size) + (pos.x)] = value
          		1:
          			if len(Voxel_height) > pos.x * pos.y:
          				Voxel_height[(pos.y * map_size) + (pos.x)] = value
          
          func get_voxel(pos : Vector2i, Xcel : int) -> int:
          	match Xcel:
          		0:
          			if len(Voxel_units) > pos.x * pos.y:
          				return Voxel_units[(pos.y * map_size) + (pos.x)]
          			else:
          				return Voxel_units[0]
          		1:
          			if len(Voxel_height) > pos.x * pos.y:
          				return Voxel_height[(pos.y * map_size) + (pos.x)]
          			else:
          				return Voxel_height[0]
          		_:
          			return 0

          I have TWO arrays, one is used for holding units, the other has terrain data.
          In your case you need many more, or more dimensions.

          Second, minecraft had 256x256x512 block chunks. that should be the starting size for a modern game.
          Holding the data, rendering it and physics are different things.

          Holding the data is easy, we keep it in chunks in order to allow the world to be near infinite, instead of an incredibly big Voxel, we have an unlimited number of finite sized voxels.

          Rendering is more difficult. Minecraft used some form of direct rendering, the entire world was rendered as a single mesh. This is what you should do instead of spawning identical blocks. The problem is that multimeshinstance3D doesn't do culling, all the instances are rendered at all times, although with simple geometry you could render many blocks, specially if we do block culling. But there's also the problem of editing the blocks, because the instances are rendered in order, all the instances have to be position shifted in order to remove the last item.
          So first we need to do culling of blocks, you have to access the 6 adjacent blocks IN MEMORY using a for loop similar to how a fragment shader does it. As we do this we have to update a parallel voxel containing the blocks that need to be rendered. 0 usually means nothing is spawned. if the block is adjacent to 6 blocks, 0, otherwise 1, or a number representing the current tile, minecraft used 256 tiles each represented by a number.
          Then we can move to rendering of the generated voxel, either by creating a mesh, or spawning blocks. This is something that needs to happen ONCE, not in process. In order to make it happen once, we need to keep the adjacent chunks, this will mean that no more than 3 or 5 chunks are created at any given time, and the chunks left behind are de-spawned.
          keeping more chunks active at a time means more memory usage but better performance.

          Finally physics. I'm not sure the godot physics using colliders is fit for a voxel. You either need a custom collider fit to the shapes of the chunks, or to calculate the physics manually, which is what minecraft did. The difference is, a conventional physics engine has to calculate collisions with ALL objects in the scene, with some layers, but with a voxel we can obtain the position of the object in the voxel and calculate collisions with just the adjacent blocks.

          Rose19 Area3Ds that detect when the player is near or far away

          noooooo.
          You have a voxel, the player position can be rounded and used to get it's position inside a voxel. don't use Areas.

          Rose19 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.

          don't use raycasting with voxels! this is probably why your game is slow.
          I told you earlier in the post, but here's some code of how you check adjacent blocks, although it's for 2D:

          var directions : Array[Vector2i] = [Vector2i.DOWN, Vector2i.LEFT, Vector2i.UP, Vector2i.RIGHT]

          func check_adjacents(pos : Vector2i) -> Array[Vector2i]:
          	var arr : Array[Vector2i] = []
          	if pos.x > 1 and pos.x < TerrainMap.map_size-1 and pos.y > 1 and pos.y < TerrainMap.map_size-1:
          		for i : Vector2i in directions:
          			var cpos : Vector2i = pos + i
          			var direc : int = TerrainMap.Voxel_road[(cpos.y * TerrainMap.map_size) + cpos.x]
          			if direc == 1:
          				arr.append(cpos)
          	return arr

          as for block types, that should be a shader. all the geometry should be the same mesh and the UV should shift an atlas texture containing all your blocks. this is how minecraft did it, and it allowed for easily swapping the blocks with a skin.

          Voxels are a very well documented topic, it may not be for beginners, but you can learn a lot by doing it.

            DaveTheCoder i tried both preloading the chunks and adding the chunks in the thread using call_deferred("add_child", chunk) and didn't receive any performance improvements. i assume this has something to do with the scene tree not being thread safe, or maybe i am an idiot.

            DaveTheCoder Place ~~~ on lines before and after the code.

            thank you!

            Jesusemora Are you someone with little experience with programing?

            No, quite honestly im new to godot and have never done something like this. i assume i look like child with my code and setup in this post but i try my best.

            firstly, yes, godot is not the best engine for this kind of game, both minecraft and minecraft-like games like vintage story use custom engines developed by themselves, probably because none of the existing engines are specialized enough for this task. so im already shooting myself in the leg trying to do this on godot, there is even a voxel based project that has procedural generation in godot 3 and they even state that godot isnt made for this and has limitations, so I need to kinda change up alot of things for this project to work.
            for starters, i don't think i need a 256x256x512 chunks for my game. it might be confusing , but rather than make the game more limited to make up for performance, im trying to use godot's strength to give the player more freedom than minecraft. the chunks are initially 19x2x19,but there is no height limit, meaning you can infinitely dig down and build up. the world doesnt have layers and layers of stone beneath the player, but instead more stone generates as the player digs, giving the illusion that they are digging into the ground.

            further more, i have already added a bunch of features that would probably not work in a traditional minecraft setting, mainly the ability to turn blocks into rigidbody counterparts that the player can pick up, push and throw, and contrary to how it sounds, i have optimized this feature to death, allowing players to make pools of hundreds of physic objects, then there is the mini-blocks, blocks with half the dimensions of a normal block and tiny-blocks, blocks that are with a quarter of the dimensions of a block.
            in hindsight, i should have made the world generation first before these features but what im trying to say is that im too far in to change now.

            Jesusemora Second, minecraft had 256x256x512 block chunks. that should be the starting size for a modern game.
            Holding the data, rendering it and physics are different things.

            everything you say here is completely correct and i agree with you. if i was more talented, id probably do this, but from my perspective as a beginner and the fact that i have yet to encounter any performance issues before this, i opted to make the game in my own baby code rather than learn something im completely unfamiliar with.

            Jesusemora noooooo.
            You have a voxel, the player position can be rounded and used to get it's position inside a voxel. don't use Areas.

            I KNOOW IM SOORY!!
            its just that it didn't seem to take much up that much processing power and i was still testing so i thought this would be fine for now, ill definitely change this.

            Jesusemora don't use raycasting with voxels! this is probably why your game is slow.
            I told you earlier in the post, but here's some code of how you check adjacent blocks, although it's for 2D:

            this is a good solution, but that wouldn't change the performance when returning back to already generated chunks, as stated they only run once and are disabled afterwards, so they would not matter after the first time the player generates a world. but as you saw, i still got a bit of lag when moving into previously generated chunks, which is my only problem with the project so far. to a player, they might forgive the lag of generating new chunks, but old chunks should not be this laggy to load.

            Jesusemora as for block types, that should be a shader. all the geometry should be the same mesh and the UV should shift an atlas texture containing all your blocks. this is how minecraft did it, and it allowed for easily swapping the blocks with a skin.

            I have thought about this, but since i was testing and there really isn't much variety still, i thought making multiple MultiMeshInstance3Ds for the game would be fine for now, but ill definitely do this too.

            Jesusemora Voxels are a very well documented topic, it may not be for beginners, but you can learn a lot by doing it.

            i will give it a try, thank you.

            im sorry if i made it confusing by mentioning minecraft, but what i really wanted when i made this post was a magical lag free way to add nodes to the scene, however , something like that does not exist, which is fine. because i have already found a solution. instead of creating the entire 19x2x19 chunk at once, i changed it so now the world generator adds the chunk, and that chunk starts spawning 10 blocks at a time, once thats finished its rendered in and it sends a signal to the world generator and it moves on to the next chunk. same with despawning them, this way, i have both the advantages of smaller chunks and bigger chunks at the same time, and im able to render 110 blocks in every direction laglessly. so i guess this problem is already over, but im very thankful for your suggestions and ill definitely keep them in mind moving forward.

            thank you so much for your time and have a nice day!

              xyz 6; and surprisingly not the main source of lag here, but rest easy, I'm going to change it.

              • xyz replied to this.

                Rose19 Having an area plus 6 raycasts for each voxel sounds very heavy and kinda defeats the purpose of using uniform voxels. As @Jesusemora already hinted, with quantized voxels a lot of stuff regarding positioning, visibility etc is implied and easily calculated due to the fact that everything is organized in an uniform grid. That grid is in fact your main optimization weapon. Applying general purpose collision techniques on individual voxel level is redundant and inadequate here. It will surely bog down the performance, sooner or later.

                As for detecting the main source of your current lag, we'll need to see the exact (and whole) structure of your chunk scene as well as all of the code that runs when the chunk is instantiated, initialized and destroyed, inside and outside of the scene itself. Assuming of course, that you eliminated areas and raycasts.

                  Rose19 godot is not the best engine for this kind of game

                  No. An engine is a tool, and godot can be used to create a voxel game like minecraft. I mean Mojang made minecraft in JAVA! in JAVA! can you imagine an entire game made in JAVA? every time I think of it it feels like a joke!

                  But while minecraft looks like a game for kids, there's still a lot of work that went into it, it was in development for years, decades, and had very smart people behind it. And most of why minecraft is the way it is, is because it was the best way to do it, and other engines like unreal copied the technology, and people made games like subnautica, space engineers and ark.

                  Rose19 use custom engines developed by themselves, probably because none of the existing engines are specialized enough for this task

                  I don't think so. I think it's probably easier to code a voxel game from scratch than use an existing engine, but only because you have make everything from scratch in both cases (if you want some specific features that the game's voxel system doesn't offer or you just don't like working with it).

                  Rose19 a voxel based project that has procedural generation in godot 3 and they even state that godot isnt made for this and has limitations

                  godot 3 is stated to be bad for 3D. godot 4 has many improvements over it. I personally don't find many limitations to godot 4: it has a fast scripting language, mesh editing, raycast and you can modify the source code. that's all you need.

                  Rose19 for starters, i don't think i need a 256x256x512 chunks for my game. it might be confusing , but rather than make the game more limited to make up for performance, im trying to use godot's strength to give the player more freedom than minecraft. the chunks are initially 19x2x19,but there is no height limit, meaning you can infinitely dig down and build up. the world doesnt have layers and layers of stone beneath the player, but instead more stone generates as the player digs, giving the illusion that they are digging into the ground.

                  then you need to change the chunk sizes dynamically, not spawn more chunks.

                  Rose19 added a bunch of features that would probably not work in a traditional minecraft setting, mainly the ability to turn blocks into rigidbody counterparts that the player can pick up, push and throw

                  ...
                  in minecraft there are TWO types of objects, you have blocks and entities.
                  blocks are not real, they exist as a single number in the voxel.
                  entities can hold data.
                  in your case a rigidbody is an entity, because it has transforms, physics properties, a collider and more.

                  Rose19 mini-blocks, blocks with half the dimensions of a normal block and tiny-blocks, blocks that are with a quarter of the dimensions of a block.

                  oh no.

                  Rose19 i should have made the world generation first before these features but what im trying to say is that im too far in to change now.

                  when you hit a wall, there's no way to get past it. you have to go back and fix your foundations and start over, or make compromises, sacrifice features and scale. this happened with every single game, there's cut content, and features that were changed.
                  in XCOM it was uneven maps, in plate up the bees, in oblivion the AI. It's just part of game dev, you can't have it all.

                  Rose19 if i was more talented, id probably do this

                  it has nothing to do with talent, it has to do with experience. this is the first voxel you have ever worked with. I have lost count of how many I have made.
                  You never succeed the first time, you have to fail and fail and keep trying, and learn every time you fail.

                  Rose19 opted to make the game in my own baby code rather than learn something im completely unfamiliar with.

                  and that ok. keep practicing.

                  Rose19 that much processing power

                  modern computers are stupidly powerful, you won't notice performance issues for a long time. but you will always get to the point where you hit a wall and everything fails. specially with things that can exponentially grow to infinity, like this.

                    this has been a very eye opening past couple of days for me, in this short amount of time, I have learned a ton and have changed my approach to making my game moving forward.

                    but for starters, i guess the answer to the original question is a no, and the solution is to simply divide the spawning of the node into smaller nodes over multiple frames. that seems to be the way to go for spawning large amount of blocks, or any other node in the scene.

                    Jesusemora when you hit a wall, there's no way to get past it. you have to go back and fix your foundations and start over, or make compromises, sacrifice features and scale. this happened with every single game, there's cut content, and features that were changed.
                    in XCOM it was uneven maps, in plate up the bees, in oblivion the AI. It's just part of game dev, you can't have it all.

                    Jesusemora it has nothing to do with talent, it has to do with experience. this is the first voxel you have ever worked with. I have lost count of how many I have made.
                    You never succeed the first time, you have to fail and fail and keep trying, and learn every time you fail.

                    Thank you so much for your encouraging words, i understand how frustrating it is to see someone making so many mistakes in something you are an expert at, so im very thankful for your patience and feedback.

                    so far, i have removed raycasts from my block and no longer use Area3Ds to detect where the player is, as you have suggested. i have also reworked the way chunks spawn, and the results are pretty great! it still needs a little polish, but i think im on the right track. i have also changed the way the game renders the blocks, previously each chunk had its own MultiMeshInstance3D to render its blocks, but now all the blocks in the world are handled by a few MMI3Ds, removing the issue of the cpu sending to many draw calls when there were to many chunks rendered at the same time. i still haven't goten to making an atlas for all my block textures like you suggested, i know the concept behind it since i heard about terraria using this method, but i don't know how to do it in godot with Multimeshinstance3Ds, yet. i would once again be grateful if you could give me some advice about this, even though you have already helped me alot.

                    xyz yes i understand. honestly, i was just very exhausted from spending so much time on this and getting nowhere, to the point i just wanted it to be over and move on to the more fun features of my game. im no expert in voxel games, but if im going to do this, then i have to do it right since this will affect every aspect of my game moving forward. as i said, i have been doing some research into voxel and my new chunk system is coming along nicely, ill have something figured out for my different sized blocks after im done with this, but now i know that the old system had to be scrapped and i will definitely make a better one.

                    as far as this post goes, i think im ready to move on. again, thank you so much @Jesusemora for all the feedback and suggestions, you have really opened my eye in this matter.

                    thank you for your time and have a nice day.