In my game I’ve set up enemies that are able to drop items upon death. They are instanced as Area2D’s and added as new children to the parent scene. However, sometimes when the drops spawn in, they spawn directly on the player, and the collisions do not update (the player has to leave the drop hitbox and run back into it). How can I make it so when the drop is instanced onto the player it will update and be able to be picked up right then and there?
Code for drops:
public partial class Collectable : Area2D
{
[Export] private InventoryItem item;
[Signal] public delegate void PlayerCollectEventHandler(InventoryItem item);
private player player;
private bool onCooldown = true;
public override void _Ready()
{
player = GetNode<player>("/root/Player");
}
private void OnBodyEntered(Node2D body)
{
if (onCooldown == false)
{
if (body.IsInGroup("Player"))
{
player = (player)body;
PlayerCollecting();
}
}
}
private void PlayerCollecting()
{
player.Collect(item);
QueueFree();
}
private void OnTimerTimeout()
{
onCooldown = false;
GD.Print("timer off");
}
Enemy code to create instances:
private void DropItem()
{
var drop = (Area2D)itemDrop.Instantiate();
drop.GlobalPosition = new Vector2((float)GD.RandRange(Position.X - 15, Position.X + 15), (float)GD.RandRange(Position.Y - 15, Position.Y + 15));
GetParent().AddChild(drop);
}
This may be “bad form”, and I honestly don’t know if it will work (I don’t know if body_entered() is commutative).
But the solution/idea I have would be to on ready() for the spawned/dropped item, set its position to (0,0) or somewhere far outside the bounds of the level/camera, then immediately set its position back to its initial position. Similar to the old dev trick of pre-loading objects and hiding them beneath the floor.
In theory, if body_entered() emits when it enters another object while being the entity that changed position (commutative, a entering b == b entering a), then this should trigger body_entered, which should trigger the pickup.
Again, sorry if this isn’t what you’re looking for, as it is more of a hacky solution than a technical one.
Area2D only work when something entering collision and not checking if there is something inside on spawn.
My solution will be to use Shape cast from code on enter tree or ready. node shape cast can work too, but is more heavy than Area2D.
Area2D node generally don’t emit the entered signal for body/areas already inside them (sometimes work, sometimes not, so is not a good way to do) also in your case even if the signal has emmited, will not work because you block the collect for a time, so the signal will be emmited but onCooldown will be true and block the PlayerCollecting() call. For this case you need to do a manual check using Area2D.get_overlapping_bodies()
public partial class Collectable : Area2D
{
[Export] private InventoryItem item;
[Signal] public delegate void PlayerCollectEventHandler(InventoryItem item);
private player player;
private bool onCooldown = true;
public override void _Ready()
{
player = GetNode<player>("/root/Player");
}
private void OnBodyEntered(Node2D body)
{
if (onCooldown == false)
{
if (body.IsInGroup("Player"))
{
player = (player)body;
PlayerCollecting();
}
}
}
private void PlayerCollecting()
{
player.Collect(item);
QueueFree();
}
private void OnTimerTimeout()
{
onCooldown = false;
GD.Print("timer off");
# I'll write using GDScript because i'm not familiar with C#
# so you can covert later
await get_tree().physics_frame
await get_tree().physics_frame
for body in get_overlapping_bodies():
emit_signal("body_entered", body)
}
Yep, using the GetOverlappingBodies method ended up working out. To answer your question, I’m not sure how familiar you are with Minecraft’s item drop system, but basically there is a slight delay before it is picked up which I’m assuming is done so the player can see that they are actually getting something. If it happened instantly it would be easy to miss. It also gives me room in case I decide to add a drop animation.
I’m not comfortable with using custom ShapeCasts and I didn’t want to just copypaste the code, so I’d have to do more research on this first before I work with it. Thank you though.
await in C# work different. We needed await ToSignal and method with await needed be async private void OnTimerTimeout() like this: private async void OnTimerTimeout() ToSignal like this: