Area2D Not Detecting Collision Soon Enough

Godot Version

4.5.1

Question

I’m trying to do something I thought would be relatively simple: I want to randomly spawn trees into my randomly generated world, and have them reposition themselves if they spawn inside of water, or another tree. The way the world is generated is it gets a noise map, and depending on the height of a certain pixel in the noise map, it either places grass or water from a TileMapLayer. Water has a physics body, set to layer 2. When the world is done generating, it broadcasts a global signal to tell the game that the world is done. Once the signal is recieved, I have the “TreeSpawner” in my main scene (which has the world generator instantiated as a child). TreeSpawner is an Area2D with a CollisionShape2D child. Here is the code that runs when the main scene recieves the signal that the world is done generating:

func callTreeSpawn():
tree_spawner.collision_mask = 23
await get_tree().process_frame
treeSpawn()

And then, here’s the treeSpawn() function:

func treeSpawn():
tree_spawner.position = Vector2(randi_range(-1427, 1427), randi_range(-727, 727))
await get_tree().physics_frame
while tree_spawner.has_overlapping_bodies():
print(“BLOCKED”)
tree_spawner.position = Vector2(randi_range(-1427, 1427), randi_range(-727, 727))
await get_tree().physics_frame
var newTree = WorldTree.spawn(tree_spawner.position)
print(“ALLOWED”)
get_parent().add_child(newTree)

This typically works fine for 1-3 loops before it outpaces the engine and doesn’t detect the collision, placing a tree despite it being on top of a body. Just to double check, I had a single tree instantiated in the middle of the map, and checked to see if it collided properly (I have checked at least half a dozen times, everything has the proper masking and collision, and everything is both monitoring and monitorable). It takes anywhere from 1-5 frames on average to detect collision, and to do so, I have to manually set the collision mask in code to the exact same thing it is in the editor, or it won’t work. Same issue with the TreeSpawner. I’m not expert programmer, but none of this makes sense. It feels like it should be very simple but nothing is working. Please help.

i have an idea ,

if a tree spawned in a lake or other tree ; you can “queue_free()” it , and make an other tree ; instead of repositioning it .

1 Like

You could use a local maxima sample on your noise map, this would prevent trees from overlapping and if you only sample values above the water line, no trees would spawn in the water.

1 Like

Unfortunately I still had the issues, which is confusing since there’s no outpacing the engine with this method. Thank you for trying though, it was a clever idea

I’m gonna try this out, but my game is like a city-builder type game, and I want new trees to spawn every year cycle, and for said trees to not spawn on buildings that the player can place on any grass tiles, and I’m afraid that trying to detect collision with buildings will have the same issue, and that this is just pushing the problems aside for later. Thank you for the thorough response though, finding maxima might help me with something later down the line

Spawn in a checker first that returns true if the location is suitable for a tree and a false if not suitable?

1 Like

That’s what TreeSpawner is supposed to do, it goes to a random position and checks if it’s suitable for a tree, but the data isn’t coming back accurate for some reason (I know it’s always the programmers fault and not the program but ykwim)

That makes sense! Instead of using node-based collision you can query physics collisions from the world, though you may see better performance by keeping the query object between tree-spawns and only changing the position.

func treeSpawn():
	var world := get_world_2d().direct_space_state
	var query := PhysicsShapeQueryParameters2D.new()
	query.shape = tree_spawner_shape # A Shape2D resource, such as a CircleShape2D
	query.transform.origin = Vector2(randi_range(-1427, 1427), randi_range(-727, 727))

	while world.collide_shape(query, 1).size() > 0:
		print("BLOCKED")
		query.transform.origin = Vector2(randi_range(-1427, 1427), randi_range(-727, 727))

	var newTree = WorldTree.spawn(query.transform.origin)
	print("ALLOWED")
	get_parent().add_child(newTree)
1 Like

I’ve never heard of this before, but it looks promising, I’ll test it out!

Couldnt you use a signal with true/false and then use the await keyword in the treespawner script.

I think signals and await would be the easiest way to have the code wait for the check and return value before continuing.

1 Like

It places trees and runs without errors and all that, but it still spawns trees on top of other trees. I made the query.shape pull from a preloaded resource that has the exact shape of the tree’s collision, if that matters. However, I reran the project and paid more attention, and it’s not spawning in water at all, which is awesome! I don’t know how this works, so if you know any pages on the docs or anything like that that can help me understand this more, I might be able to figure it out. Thank you for all your help!

Sure physics querys are an instant one-time collision detection. As you add trees you may need to wait a frame still for them to populate the physics service.

There are similar functions for 3D and other query types for raycasts, which are used more often as “firing” weapons make good use of instant one-time collision detections rather than the more expensive Raycast3D checking every frame.

1 Like

OMG I FEEL LIKE AN IDIOT Your code works beautifully, but since it only detects bodies by default, it wasn’t detecting my trees, which are StaticBodies without CollisionShapes, which instead have Area2Ds with CollisionShapes, so technically your code wasn’t detecting bodies because there were no bodies to be detected! This is awesome, thank you so much for all the help, you rock!

1 Like

Yeah the default shape query only detects bodies, so you will have to add this line to detect areas too

query.collide_with_areas = true
1 Like