Префаб 3D-противника
Тело с формой 0,8 × 0,8 × 0,8, движение по площадке, однократный сигнал и ограничение времени жизни.
Соберите префаб
Создайте Prefabs/Mob.rprefab с корнем CharacterBody3D, именем Mob, детьми CollisionShape3D и MeshInstance3D. Используйте BoxShape3D и BoxMesh, оба с Size (0.8, 0.8, 0.8). На меш назначьте светлый StandardMaterial3D. Локальные позиции детей — 0, масштабы — 1.
Корень находится на слое 3 (Collision Layer = 4 числом), маска включает только пол — слой 1 (Collision Mask = 1). Противники не блокируют друг друга. Исходная модель из внешнего архива не требуется.
Полный код
Прикрепите Scripts/SquashMob.cs к корню:
using Reckfell;
public partial class SquashMob : CharacterBody3D
{
[Signal] public delegate void SquashedEventHandler();
[Export] public float Speed { get; set; } = 2;
[Export] public Vector3 MoveDirection { get; set; } = Vector3.Forward;
private double _age;
private bool _squashed;
public override void _PhysicsProcess(double delta)
{
_age += delta;
Vector3 direction = new Vector3(MoveDirection.X, 0, MoveDirection.Z).Normalized();
Vector3 velocity = direction * Speed;
velocity.Y = IsOnFloor() ? 0 : Velocity.Y - 24 * (float)delta;
Velocity = velocity;
MoveAndSlide();
if (_age > 10 || Mathf.Abs(Position.X) > 8 || Mathf.Abs(Position.Z) > 8)
QueueFree();
}
public bool TrySquash()
{
if (_squashed || IsQueuedForDeletion()) return false;
_squashed = true;
EmitSignal(SignalName.Squashed);
QueueFree();
return true;
}
}Движение
MoveDirection задаёт направление по X/Z. Вертикальная составляющая отбрасывается, оставшаяся нормализуется; нулевое направление оставляет противника на месте. Speed — единицы в секунду, гравитация удерживает тело на полу. MoveAndSlide() учитывает шаг времени.
Контроллер уровня задаёт положение Y = 0.4: низ куба оказывается на полу. Направление к позиции игрока выбирается в момент появления и затем не пересчитывается. Это прямолинейное движение, а не поиск пути вокруг препятствий.
Удаление и очки
Объект удаляется после выхода X/Z за ±8 или после 10 игровых секунд. Это ограничивает время жизни экземпляров, даже если они не встретили игрока.
TrySquash() возвращает true один раз. Флаг _squashed выставляется до сигнала, затем планируется QueueFree(). Повторная попытка в том же кадре не даёт второе очко. Сигнал Squashed обрабатывает контроллер, а не сам противник.
Проверяйте префаб в Arena.rscene: ему нужны пол, камера и уже собранные скрипты. Появление экземпляров.