reckfellENGINE

Лучевые запросы: слои, исключения и выбор мышью

Мировые координаты DirectSpaceState, локальная цель RayCast и полный обработчик выбора 3D из viewport.

На этой странице

Два способа пустить луч

RayCast2D/3D — сущность с обновляемым результатом. Её Position задаёт начало, TargetPosition — локальную конечную точку относительно неё. DirectSpaceState.IntersectRay — разовый запрос с мировыми From/To. To — конец отрезка, не направление и не длина. Для конца по направлению вычислите origin + normalizedDirection × distance.

В самостоятельном уровне 2D и уровне 3D есть оба способа. Методы Cast показаны целиком в этих классах; LastRayHit у 2D обновляется в _PhysicsProcess. Для RayCast сначала проверьте IsColliding и только затем GetCollider/GetCollisionPoint/GetCollisionNormal. Если в этом же шаге меняли цель и нужен новый результат, ForceRaycastUpdate принудительно обновляет его, в том числе при Enabled = false. Это не выполняет новый шаг всей физики.

Доступ к миру и результат

Берите GetWorld2D/3D().DirectSpaceState у сущности в нужном viewport/world. Вход и выход должны быть конечными координатами в одной мировой системе. Выполняйте запрос в _PhysicsProcess: при потоковой физике пространство может быть заблокировано в неподходящей фазе. Обработчик ввода сохраняет намерение, а физический callback выполняет запрос.

IntersectRay возвращает пустой Dictionary при промахе. При попадании полезны collider, position, normal, rid и shape. Проверяйте Count/ключи перед чтением и тип collider перед приведением к игровому классу. Ссылка на найденный объект не гарантирует, что он ещё существует при последующей отложенной обработке. Сохраняйте нужные данные или проверяйте IsInstanceValid/IsQueuedForDeletion.

Фильтрация

CollisionMask запроса сравнивается с CollisionLayer кандидатов. Mask тела, которое держит оружие или камеру, автоматически в запрос не копируется. CollideWithBodies и CollideWithAreas выбирают категории; зоны по умолчанию не включены. Для исключения используйте RID физического объекта в Exclude, не GetInstanceId и не ссылку на визуальный MeshInstance. У RayCast есть AddException/RemoveException и исключение родительского тела; его область не равна исключению всей игровой иерархии.

Если начало внутри формы, HitFromInside меняет поведение поддерживаемых форм; при таком попадании normal может быть нулевой. Не нормализуйте её без проверки. Полые concave-поверхности и обратные стороны треугольников имеют отдельные ограничения; HitBackFaces в 3D не превращает trimesh в заполненный объём. Пример проверяет inside-hit у BoxShape3D в установленном backend, а не все формы всех backend.

Выбрать объект в 3D

Добавьте ScreenPicker3D.cs к обычной Entity в вашем 3D-уровне и назначьте Camera в Inspector. На выбираемых телах включите Layer 3 (бит 4), либо измените Mask. Distance должна быть конечной и положительной. Готовый CollisionLab3D создаёт и назначает этот компонент сам.

_UnhandledInput получает щелчок, который не был обработан интерфейсом. Координаты click.Position относятся к его viewport; Camera должна смотреть в тот же viewport. ProjectRayOrigin/Normal учитывают тип проекции. Нельзя всегда брать Camera.GlobalPosition за начало: это неверно для ортографической проекции. При SubViewportContainer или UI-масштабировании сначала согласуйте координаты viewport.

ScreenPicker3D.cs
C#
using Reckfell;
using Hit = Reckfell.Collections.Dictionary;

public partial class ScreenPicker3D : Entity
{
    [Export] public Camera3D? Camera { get; set; }
    [Export] public uint Mask { get; set; } = 4;
    [Export] public float Distance { get; set; } = 100;
    private Vector2? _pending;
    public Hit LastHit { get; private set; } = new();
    public int QueryCount { get; private set; }
    public void RequestPick(Vector2 viewportPosition) { if (viewportPosition.IsFinite()) _pending = viewportPosition; }
    public override void _UnhandledInput(InputEvent @event)
    {
        if (@event is InputEventMouseButton { Pressed: true, ButtonIndex: MouseButton.Left } click)
            RequestPick(click.Position);
    }
    public override void _PhysicsProcess(double delta)
    {
        if (_pending is not Vector2 screen) return;
        _pending = null; LastHit = new();
        if (Camera is null || !ReckfellObject.IsInstanceValid(Camera) || !Camera.IsInsideTree() || !float.IsFinite(Distance) || Distance <= 0) return;
        Vector3 origin = Camera.ProjectRayOrigin(screen);
        Vector3 end = origin + Camera.ProjectRayNormal(screen) * Distance;
        var query = PhysicsRayQueryParameters3D.Create(origin, end, Mask);
        query.CollideWithAreas = false;
        LastHit = Camera.GetWorld3D().DirectSpaceState.IntersectRay(query);
        QueryCount++;
    }
}

LastHit обновится после физического callback. Следующий промах очищает прежнее попадание. Повторные запросы до шага объединяются в последний щелчок — это выбранное поведение компонента; для очереди выстрелов храните все события отдельно. Скрипт не открывает ссылки, не удаляет объекты и не рисует выделение. GUI должен действительно обрабатывать щелчок, если он не должен проходить в мир.

Если попадает не туда

Промах — проверьте From/To, длину, Layer цели, Mask, категории тел/зон и текущий мир. Попадает в себя — RID-исключение и родителя RayCast. Неверный конец — локальный TargetPosition перепутан с мировым To. Неверный выбор мышью — координаты окна смешаны с viewport или выбрана другая камера. Луч не имеет толщины: для объёма снаряда используйте подходящий ShapeCast/shape-query, а не ряд случайно сдвинутых лучей без проверки.

Документация Reckfell EngineНаверх ↑
БЫСТРЫЙ ПЕРЕХОДНачало работыEntity и иерархияC# & .NET 10Сцены и префабы
Руководства и полный API Reckfell K
reckfellENGINE
RECKFELL ACCOUNT

Вход в аккаунт

Войдите в аккаунт Reckfell.

Один аккаунт для всей платформы Reckfell.