Hazards: movement and body detection
An Area2D with a circle shape, BodyEntered signal and cleanup below the arena.
Hazard prefab
Create Prefabs/Enemy.rprefab with an Area2D root named Enemy and a child CollisionShape2D. Its Shape is a CircleShape2D with Radius = 14 and local position (0, 0). Disable all root collision layers (Collision Layer = 0) and enable only layer 1 in its mask (Collision Mask = 1). Keep Monitoring on.
This is a detection area, not a solid obstacle. It finds the player's CharacterBody2D on layer 1. Use BodyEntered for a body; AreaEntered concerns other areas and is not suitable here.
Build with the DodgePlayer class from the previous chapter: the handler below checks that type. Running the hazard alone shows falling; hit detection requires a player in the same level.
DodgeEnemy.cs
Attach the complete script to the root:
using Reckfell;
public partial class DodgeEnemy : Area2D
{
[Export] public Vector2 Velocity { get; set; } = new Vector2(0, 150);
public override void _EnterTree() => BodyEntered += OnBodyEntered;
public override void _ExitTree() => BodyEntered -= OnBodyEntered;
public override void _Draw() => DrawCircle(Vector2.Zero, 14, Colors.White);
public override void _PhysicsProcess(double delta)
{
Position += Velocity * (float)delta;
if (Position.Y > 384)
QueueFree();
}
private void OnBodyEntered(Entity2D body)
{
if (!IsQueuedForDeletion() && body is DodgePlayer player)
player.TakeHit();
}
}_Draw() draws a radius-14 circle, matching the collision shape. Velocity uses pixels per second; directly changing Position needs multiplication by delta. Unlike the player, this code does not call MoveAndSlide().
The circle falls and is deleted beyond Y = 384, below the arena. QueueFree() is deferred, so an object can receive an event before actual deletion. IsQueuedForDeletion() prevents an old hazard from ending a newly restarted round.
BodyEntered connects on tree entry and disconnects on exit. Do not add a second connection in the editor. The player's TakeHit() guards against further hits after the round has ended.
Test alone and in the level
Temporarily set the root position to (320, 20), save and run the prefab with F6: the circle should fall and disappear. It does not start a round or show score by itself. The level controller creates multiple instances and places them above the arena.
If a visible circle crosses the player without ending the round, check the area mask, body layer, both shapes and attached script type. Do not immediately change Monitoring or a shape inside a physics overlap callback; those changes can require deferral. This example only changes game state and safely schedules deletion.