Противник: движение и обнаружение тела
Area2D с круговой формой, сигнал BodyEntered и удаление за нижней границей поля.
Префаб противника
Создайте Prefabs/Enemy.rprefab с корнем Area2D, именем Enemy и дочерней CollisionShape2D. Её Shape — CircleShape2D, Radius = 14, локальная позиция (0, 0). У корня выключите все слои столкновения (Collision Layer = 0), в маске оставьте только слой 1 (Collision Mask = 1). Monitoring включён.
Это область обнаружения, а не твёрдое препятствие. Она находит CharacterBody2D игрока на слое 1. Для тела нужен сигнал BodyEntered; AreaEntered относится к другим областям и здесь не подойдёт.
Перед сборкой нужен класс DodgePlayer из предыдущей главы: обработчик ниже проверяет этот тип. Отдельный запуск противника показывает падение, а проверка попадания требует игрока в том же уровне.
DodgeEnemy.cs
Прикрепите к корню полный скрипт:
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() рисует круг радиуса 14. Здесь внешний вид и форма столкновения совпадают. Velocity задаётся в пикселях в секунду; непосредственное изменение Position требует умножения на delta. В отличие от игрока, этот код не вызывает MoveAndSlide().
Круг движется вниз и удаляется после Y = 384, уже за полем. QueueFree() отложен: до фактического удаления объект может получить событие. Проверка IsQueuedForDeletion() не даёт старому противнику завершить только что перезапущенный раунд.
Подключение BodyEntered выполняется при входе в дерево, отключение — при выходе. Не добавляйте второе соединение этого же сигнала через редактор. TakeHit() игрока защищает от повторных попаданий в уже завершённом раунде.
Проверка отдельно и в уровне
Временно задайте корню позицию (320, 20), сохраните и запустите префаб F6: круг должен падать вниз и исчезнуть. Сам по себе он не запускает раунд и не показывает счёт. Контроллер уровня создаст несколько экземпляров и расположит их над верхним краем.
Если видимый круг проходит через игрока без поражения, проверьте маску области, слой тела, обе формы и тип прикреплённого скрипта. В обработчике физического пересечения не меняйте Monitoring или форму немедленно: такие изменения могут требовать отложенного вызова. Этот пример меняет только состояние игры и планирует удаление безопасно.