reckfellENGINE

Камера с препятствиями: SpringArm3D

Настроить вращение и дистанцию камеры, исключить тело персонажа и разобраться с Shape, Margin и слоями.

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

Что получится

Камера вращается вокруг неподвижного объекта и приближается перед стеной. Стрелки ←/→ вращают пружину, ↑/↓ меняют требуемую дистанцию от 2 до 40. Это отдельный пример камеры: движения персонажа, мыши и плавного следования здесь нет.

SpringArm3D проверяет препятствия вдоль локальной +Z и перемещает прямых Entity3D-потомков на найденную дистанцию. Camera3D смотрит вдоль своей -Z, поэтому камера на положительной Z направлена обратно к объекту. Не помещайте тело персонажа прямым потомком пружины: оно тоже будет перемещаться.

Запустить полный пример

Нужны C#-проект Reckfell.NET.Sdk 1.0.0, net10.0 и уровень .rscene с корнем Entity3D. Сохраните все три файла ниже, выполните dotnet build и прикрепите ViewLab3D.cs к корню. Включите экспортированное UseWall до запуска для проверки стены. По умолчанию оно выключено, чтобы видеть объект и переключение его моделей без препятствия.

В проекте должны существовать действия ui_left, ui_right, ui_up и ui_down с назначенными стрелками. Если их нет в Input Map, добавьте их. Ресурсы, камера, подпись, пол и коллизии создаются кодом; внешние модели не нужны. Точные действия интерфейса редактора этим примером не проверяются.

LodVisual3D.cs
C#
using Reckfell;

public partial class LodVisual3D : MeshInstance3D
{
    public int ProcessTicks { get; private set; }
    public override void _Process(double delta) => ProcessTicks++;
}
ViewLabMeshes.cs
C#
using Reckfell;

public static class ViewLabMeshes
{
    public static StandardMaterial3D Material(Color color) => new()
    {
        AlbedoColor = color,
        ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded
    };

    public static MeshInstance3D Box(Vector3 size, Color color) => new()
    {
        Mesh = new BoxMesh { Size = size },
        MaterialOverride = Material(color)
    };

    public static TextMesh Text(string text) => new()
    {
        Text = text,
        FontSize = 64,
        PixelSize = 0.01f,
        Depth = 0.15f
    };
}
ViewLab3D.cs
C#
using Reckfell;

public partial class ViewLab3D : Entity3D
{
    [Export] public float Distance { get; set; } = 6;
    [Export] public bool UseWall { get; set; }
    public SpringArm3D Boom { get; private set; } = null!;
    public Camera3D Camera { get; private set; } = null!;
    public LodVisual3D NearModel { get; private set; } = null!;
    public LodVisual3D FarModel { get; private set; } = null!;
    public Label3D Caption { get; private set; } = null!;
    public MeshInstance3D Floor { get; private set; } = null!;
    public StaticBody3D ActorBody { get; private set; } = null!;
    public StaticBody3D? WallBody { get; private set; }

    public override void _Ready()
    {
        ActorBody = new StaticBody3D { Position = new Vector3(0, 1, 0) };
        AddChild(ActorBody);
        ActorBody.AddChild(new CollisionShape3D { Shape = new BoxShape3D { Size = new Vector3(1.4f, 2, 1.4f) } });
        NearModel = new LodVisual3D
        {
            Position = ActorBody.Position,
            Mesh = new SphereMesh { Radius = 0.7f, Height = 1.4f, RadialSegments = 64, Rings = 32 },
            MaterialOverride = ViewLabMeshes.Material(Colors.White),
            VisibilityRangeEnd = 12
        };
        FarModel = new LodVisual3D
        {
            Position = ActorBody.Position,
            Mesh = new BoxMesh { Size = new Vector3(1.2f, 1.2f, 1.2f) },
            MaterialOverride = ViewLabMeshes.Material(new Color(0.08f, 0.25f, 1)),
            VisibilityRangeBegin = 12
        };
        AddChild(NearModel);
        AddChild(FarModel);
        Floor = new MeshInstance3D
        {
            Mesh = new PlaneMesh { Size = new Vector2(20, 20) },
            MaterialOverride = ViewLabMeshes.Material(new Color(0.1f, 0.12f, 0.15f))
        };
        AddChild(Floor);
        Caption = new Label3D
        {
            Text = "Reckfell",
            Position = new Vector3(0, 2.1f, 0),
            FontSize = 48,
            PixelSize = 0.008f,
            Billboard = BaseMaterial3D.BillboardModeEnum.Enabled,
            Modulate = new Color(0.6f, 0.75f, 1)
        };
        AddChild(Caption);
        if (UseWall)
        {
            WallBody = new StaticBody3D { Position = new Vector3(0, 1, 3) };
            AddChild(WallBody);
            WallBody.AddChild(new CollisionShape3D { Shape = new BoxShape3D { Size = new Vector3(6, 4, 0.4f) } });
            WallBody.AddChild(ViewLabMeshes.Box(new Vector3(6, 4, 0.4f), new Color(0.1f, 0.12f, 0.15f)));
        }
        Boom = new SpringArm3D
        {
            Position = new Vector3(0, 1, 0),
            Shape = new SphereShape3D { Radius = 0.2f },
            CollisionMask = 1,
            Margin = 0.15f
        };
        AddChild(Boom);
        Boom.AddExcludedObject(ActorBody.GetRid());
        Camera = new Camera3D { Current = true, Near = 0.05f, Fov = 60 };
        Boom.AddChild(Camera);
        SetDistance(Distance);
    }

    public void SetDistance(float distance)
    {
        if (!float.IsFinite(distance)) return;
        Distance = Mathf.Clamp(distance, 2, 40);
        Boom.SpringLength = Distance;
    }

    public override void _PhysicsProcess(double delta)
    {
        SetDistance(Distance + Input.GetAxis("ui_up", "ui_down") * 8 * (float)delta);
        Boom.RotateY(Input.GetAxis("ui_left", "ui_right") * 1.5f * (float)delta);
    }
}

Метод SetDistance вызывайте после создания Boom в _Ready. До запуска задавайте экспортированное Distance. Не меняйте UseWall во время игры в ожидании появления стены: пример читает его только при создании уровня.

Понять три способа проверки

УсловиеЗапросЧто учитывать
Shape заданаПроверка формыЗдесь сфера радиусом 0.2; её размер определяет зазор
Shape=null, есть прямой Camera3D-потомокАвтоматическая форма камерыКамера внутри дополнительного Entity3D уже не прямой потомок
Shape=null, прямой камеры нетЛучТолько в этой ветке проверенной версии вычитается Margin

Margin не применяется к явно заданной форме и автоматической форме камеры в проверенной версии. Не увеличивайте его бесконечно, если сфера всё ещё подходит близко к стене: подберите Shape и Near камеры. Слишком большой ближний срез или слишком маленькая форма могут оставить видимые проблемы у углов. Пример не доказывает защиту от любого быстрого движения или сложной геометрии.

GetHitLength возвращает последний физический результат. Сразу после присваивания SpringLength ещё нельзя ожидать нового значения; дождитесь физического обновления. Сохраняйте единичный масштаб пружины и её родителей, чтобы длины интерпретировались предсказуемо.

Исключить себя и настроить слои

CollisionMask=1 проверяет первый физический слой. Стена должна иметь CollisionShape3D и соответствующий CollisionLayer; один MeshInstance3D не останавливает камеру. CollisionMask=0 исключает все слои и полезен для диагностики, но отключает защиту от препятствий.

Boom.AddExcludedObject(ActorBody.GetRid()) исключает тело цели. Нужен RID физического тела, не GetInstanceId() и не RID меша. Исключение касается запросов пружины, а не столкновений персонажа с миром.

Все прямые Entity3D-потомки получают позицию на оси пружины; их боковое локальное смещение не сохраняется. Для плечевой камеры смещайте подходящий родительский pivot, а не рассчитывайте на Camera.Position.X, которую пружина перепишет.

Проверить результат и найти ошибку

При UseWall=true на исходной дистанции 6 камера остаётся перед стеной, а GetHitLength меньше SpringLength. Поверните пружину в свободную сторону: камера снова отдаляется. В тесте форму, автоматическую камеру и луч проверяли отдельно; уменьшение длины на величину Margin подтверждено только для луча.

СимптомПроверка
Камера за стенойФизическая форма стены, CollisionLayer, CollisionMask и включённая обработка пружины
Камера упирается в цельИсключён ли RID тела цели
Изменение Margin ничего не даётКакая ветка запроса используется; с Shape это ожидаемо
Камера сместила лишний объектНет ли других прямых Entity3D-потомков пружины
Стрелки не работаютДействия Input Map и фокус окна игры
Нельзя увидеть дальнюю модельОтключите UseWall до запуска: стена сокращает фактическую дистанцию

Следующие задачи: текст в мире, дистанционная видимость, SpringArm3D API.

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

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

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

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