I have re-spawning enemies in an object pool. When an enemy dies (and is removed from scene, returning to the object pool), it drops a coin at the position of its death. The coin(s) use on_body_entered to detect when they come in contact with the player or another enemy (as enemies can pick up coins as well).
Once the enemy-that-died re-spawns and is placed back into the scene at its “start” location (which is far away from the location of its death/dropped coin), I’m seeing _on_body_entered() in the coin being instantly triggered at the enemy’s return to the scene, which then triggers the re-spawned enemy to pick up the coin right away despite being nowhere near it.
I’ve 100% confirmed that it is indeed the coin’s former owner picking it up automatically via the debugger, and I’m using self.global_location.distance_to(body.global_distance) in the coin’s _on_body_entered() to confirm that the physical distance between the coin and enemy is very large – so its not a matter of the enemy being briefly placed at its location-of-death upon entering the scene before relocating to the enemy start position on the other side of the map.
Turning on “visible collision shapes” does not visibly show any collision shapes touching/overlapping the coins Area2D when _on_body_entered() is being fired.
Any tips/workarounds on how I can avoid this type of behavior?
I’m setting global position first and then adding to scene. I’ve confirmed that monster.global_position is correct (i.e., at it’s starting position, and not it’s death position) before add_child() is run to return it to the scene.
Here is how the monster is being removed from the pool and added to scene:
Removing the pooled-object from the scene-tree somewhat defeats the purpose of your object-pool, since adding and removing objects from the scene-tree is an expensive task, while creating objects isnt.
Sorry, that was supposed to be a reply to somdat! Not very helpful as a reply to your question! So let me try and help now...
Short answer: no, I don't keep them on the tree. Here's my process in a nutshell:
Startup (I use a singleton but that's not required):
Load a scene from a resource
Instantiate loads of it
Put each one in an array
Running:
Pop objects from the array when you need them, e.g. enemy is spawned. array.shuffle() is good for randomisation
Use something_in_tree.add_child(object_from_array) to put them in the tree
The object will do whatever it does
When you no longer need the object, e.g. enemy is killed, use something_in_tree.remove_child(object_from_array) to orphan the object
Add the object to the array again
That works for me, I hope there's something in there you can use. Good luck!
can you help me with a better approach to activating/deactivating pooled objects?
For coin dropping, I have a signal being emitted by the monster on “death” that is heard by the object manager (coins are also pooled) with the following:
func _on_create_coin(pos: Vector2, worth: int) -> void:
if _coin_pool.size() == 0 or worth < 1:
return
var c: Coin = _coin_pool[0]
_coin_pool.remove_at(0)
c.setup(worth)
call_deferred("add_object", c, pos)
I should also note that there is at least 5 seconds between when a monster dies (and drops a coin) and when it is respawned, so the coin is sitting out in the world untouched for a good amount of time before the monster respawns and triggers the coin’s on_body_entered from across the map somehow.
Monster_1 has died at position (1374.414, 1373.522)
signal 'on_create_coin()' heard by ObjectManager
signal 'on_create_coin()' emitted for position (1374.414, 1373.522)
coin being added at position (1374.414, 1373.522)
activate() started for Monster_1. current pos: (1375.168, 1373.23)
global position was reset. new position: (648.0, 1081.0)
activate() has finished
preparing to add_child() for 'Monster_1'. Monster current global_pos: (648.0, 1081.0)
_activate() has finished
on_body_entered() triggered by 'Monster_1' for coin:
...coin position is at (1374.414, 1373.522)
...triggering body is at (648.7548, 1080.705)
distance from body to coin = 782
on_body_EXITED() triggered by 'Monster_1' for coin:
...coin position is at (1374.414, 1373.522)
...triggering body is at (649.5107, 1080.406)
Here’s something else: I added a call to change CollisionShape2D.disabled to true when the monster dies. I wait for the monster to respawn and visually appear in the scene. I press pause in the debugger and then manually uncheck the monster’s CollisionShape2D.disabled value so that it is no longer disabled. The coin stays in position and is not picked up by the monster.
However, if i introduce CollisionShape2D.disabled = false into the monster.activate() function, it is again picking up the coin when it respawns
Replaced add_child and remove_child to bring monster in and out of scene with show()/hide() and changing process_mode (similar to your scene pooler) – same result
before the monster dies and emits the signal to generate a coin, reset the monster’s global position to 0,0 – same result
I only read through quickly, but it sounded a bit like perhaps the coins are children to the monster node?
If so and in combination with some other line of code here or there, perhaps some unexpected behavior causes the coins to respawn at the same spot as the monster and then get picked up - or some other weird thing.
I would keep the coin in a separate container node separate from all other monster stuff and spawn it in with a signal, to try to sever whatever strange tie there is that causes the issue.
Thanks for the help! Unfortunately, monsters and coins are already pretty divorced. The monster emits a “drop a coin here” signal, which is heard by an independent ObjectManager scene that will have the coin as its child.
Not going to have time testing it today, but I’m wondering if it has to do with the monster being a CharacterBody2D and I’m manually changing its global position outside of physics_process (according to docs, “you should not set the CharacterBody2D position directly”).
Maybe disabling physics_process when the monster dies, and then modifying global_position before physics_process is reenabled (ie, after the monster is respawned) will help?
Yeah, this makes sense to me. Unfortunately all my messing around with set_physics_process(false) and changing process_mode today has been fruitless.
At this point, I think I’m going to keep using pooling for simple non-physics stuff like projectiles, coins, etc, but instantiate/queue_free CharacterBody2D scenes like Enemy without doing any pooling.
set_physics_process doesnt disable the physics-collision i believe. It only disables the physics-process-method. Try to set the collision-shape to disabled
If I disable the collision shape during die(), and then wait for the respawn to visually appear before manually pausing the debugger and re-enabling the collision shape, then everything works as desired (ie the coin isn’t picked up).
However, if I try to re-enable the collision shape anywhere in the code, it goes back to undesired behavior.
Is there any way to “await” for .show() to be completely finished before I allow the collision shape to be re-enabled?
I’m now setting CollisionShape2D.disabled = true during monster.die().
Trying to have CollisionShape2D.disabled = false set during monster.activate() wasn’t helping.
So, instead, I put CollisionShape2D.disabled = false into _physics_process() and had physics process re-enable collision like this:
func _physics_process(delta: float) -> void:
if not self.targetable:
return
if $CollisionCircle.disabled:
$CollisionCircle.disabled = false
# other stuff
And it still wasn’t working. But then I added this:
func _physics_process(delta: float) -> void:
if not self.targetable:
return
if $CollisionCircle.disabled:
if self.frame_count == 0:
self.frame_count += 1
return
else:
$CollisionCircle.disabled = false
self.frame_count = 0
# other stuff
So it’s essentially not turning back on collision until the second physics frame after the monster is re-spawned – basically just skipping the first physics frame after respawn completely.
And now it’s working.
I would prefer a more elegant solution, but I could live with this. Could you think of anything in particular to look that would cause this issue to appear in the first physics frame after respawn, but not the second?
I have no idea. May i ask how many spawners do you use? Because object pooling isnt really necessary in godot and you might just get away with just creating new monsters. This would also get rid of this problem
Just the one spawner for the monsters.
Right now I’m not so much making a game as I am with both learning godot and working to understand common game design patterns — and for this mini-project, its object pooling.
So I think my take away from this is object pooling works fine in godot for simple non-physics stuff (eg projectiles, coins), but is more trouble than it’s worth for CharacterBody2D (and likely other physics based nodes)