0
\$\begingroup\$

I'm new to Godot and following a tutorial, which spawns enemies randomly. However, I'm trying to improve on this by checking that the enemies don't spawn on top of the player and cause an instant loss.

At first I tried using _on_area_entered on the Enemy, checking if the area entered was the Player (with is_in_group), and choosing a new position if so. However, this doesn't seem to work because the _on_area_entered in Player also detects the collision and the player dies. It seems like collisions are detected before I update position, and not after.

So then I tried spawning CollisionRectangle2D when spawning the Enemy, but this didn't work and I'm still not entirely sure why. The basic idea was to spawn colliders and put them in the tree and check if they collide with the player, and then set the enemy to their position if not. However, I must be doing something wrong with the coordinates, because this never logs "bonk":

func set_spawn() -> void:
 var collision_tester = CollisionShape2D.new()
 collision_tester.shape = $CollisionShape2D.shape
 var player_shape: CollisionShape2D = get_tree().get_first_node_in_group("player").get_collision_shape()
 
 var loop_stop = 100
 
 collision_tester.transform.origin = Vector2(randi_range(0, screensize.x), randi_range(0, screensize.y))
 
 while collision_tester.shape.collide(
 collision_tester.transform,
 player_shape,
 player_shape.transform
 ) and loop_stop > 0:
 print("bonk")
 loop_stop -= 1
 collision_tester.transform.origin = Vector2(randi_range(0, screensize.x), randi_range(0, screensize.y))
 
 position = collision_tester.transform.origin

I thought maybe there was some sort of deferral where the physics state and the positions desynch in a single frame? So I tried using PhysicsShapeQueryParameters2D:

func set_spawn() -> void:
 var query = PhysicsShapeQueryParameters2D.new()
 query.collide_with_areas = true
 
 query.shape = RectangleShape2D.new()
 query.shape.size = $CollisionShape2D.shape.size
 query.transform.origin = Vector2(randi_range(0, screensize.x), randi_range(0, screensize.y))
 
 while get_world_2d().direct_space_state.intersect_shape(query, 1).size() > 0:
 print("Bonk")
 query = PhysicsShapeQueryParameters2D.new()
 query.collide_with_areas = true
 query.collide_with_bodies = true
 query.shape = RectangleShape2D.new()
 query.shape.size = $CollisionShape2D.shape.size
 query.transform.origin = Vector2(randi_range(0, screensize.x), randi_range(0, screensize.y)) 
 
 position = query.transform.origin
 $CollisionShape2D.set_deferred("disabled", false)

However, that doesn't work either - it logs "bonk" and then just ... the player still dies.

My kind-of-working project code is online at https://github.com/ertyseidohl/coin-dash, at 60d1196 at the time of writing.

DMGregory
140k23 gold badges256 silver badges395 bronze badges
asked Sep 28 at 21:02
\$\endgroup\$
2
  • \$\begingroup\$ I've gotten around this for now by adding an "age" which increments every _process tick, and if the player collides with a cactus with age < 10, it ignores it and the cactus moves. Seems to work well enough for now, although it's not perfect, since the cactus could potentially spawn on top of the player 10 times in a row. \$\endgroup\$ Commented Sep 28 at 21:57
  • \$\begingroup\$ This information should be added as an edit to your question, not lost in the comments section :) \$\endgroup\$ Commented Sep 30 at 21:25

1 Answer 1

1
\$\begingroup\$

I tried spawning CollisionRectangle2D when spawning the Enemy, but this didn't work and I'm still not entirely sure why.

Very often in game engines, data about which collidable entities are intersecting is only generated during the world physics tick. If the object you want to check against was newly-spawned this frame, it hasn't yet gone through a full update tick of the entire physics world, so it doesn't yet know what it's touching.

This thread suggests that's what's happening in this Godot example.

What we can do instead is query the zone of space we want the new object to occupy, before we spawn it, using PhysicsDirectSpaceState2D.intersect_shape(). This will tell us if any colliders in the world intersect that zone. If so, we try a different zone.

(If your world can be densely populated, you may want to try doing this in a non-random way to limit the worst-case performance - like dividing your space into grid cells, checking each once, and giving up if they're all full)

Here is a version of the code OP shared in the Reddit thread linked above, adapted to 2D and Godot 4+ based on what I've found in the docs. The thread is 3 years old, so I can't vouch for whether it's the most up-to-date way to solve this problem:

# magic code to test an area if its empty
func CheckSpace(location: Vector2) -> Object:
 # create test-shape
 var boxShape = PhysicsServer2D.rectangle_shape_create()
 #set half extents
 PhysicsServer2D.shape_set_data(boxShape, Vector2.ONE * 0.45)
 # set location
 var params = PhysicsShapeQueryParameters2D.new()
 params.shape_rid = boxShape
 params.transform.origin = location
 # check for any overlap in default 2D physics world
 var world = get_world_2d().direct_space_state
 var result = world.intersect_shape(params, 1)
 # free memory
 PhysicsServer2D.free_rid(boxShape)
 ## when nothing is hit, check if completely inside object via pointcheck
 if result.is_empty():
 var pointparams := PhysicsPointQueryParameters2D.new()
 pointparams.position = location
 result = world.intersect_point(pointparams)
 if result.is_empty():
 return null
 return result[0].collider
answered Sep 30 at 0:46
\$\endgroup\$
2
  • \$\begingroup\$ Thank you! So, this is interesting - it allows me to place the new enemies, but those enemies (or any other objects like powerups) don't become part of the physics state until the next tick. I'm thinking about two approaches: - have a short loading state where I add an entity, run one tick, and another, run another tick, etc, until all enemies are loaded - run a totally separate simulation by generating boxes for each entity added in memory (not physics) and checking against those boxes for collision, although obviously this won't be as optimized as the native physics collision detection \$\endgroup\$ Commented Oct 1 at 17:47
  • \$\begingroup\$ You should be able to just spawn the enemies and use the code above to check for them in the same update tick. While enemy-enemy collisions won't be processed yet, intersect_shape() should still be able to detect the newly spawned enemies without waiting for a full physics tick in between. Test to confirm — I don't have a Godot project on the go at the moment to prove this in. \$\endgroup\$ Commented Oct 1 at 19:01

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.