reckfellENGINE

Viewport: миры, ввод и захват кадра

Независимый предпросмотр, режимы обновления, размеры и корректный PNG.

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

Viewport отрисовывает мир в поверхность. Корневой Window — главный viewport игры; SubViewport создаёт отдельный render target для предпросмотра модели, мини-карты, разделённого экрана или текстуры внутри сцены.

Создание SubViewport само по себе не показывает изображение. Выведите GetTexture() через TextureRect, Sprite или материал либо поместите SubViewport непосредственно в SubViewportContainer. Текстура на 3D-меше не передаёт нажатия в viewport автоматически.

Создайте независимый 3D-предпросмотр

  1. Создайте SubViewport размером не меньше 2 × 2 пикселей.
  2. Назначьте новый World3D, если предпросмотр должен быть изолирован от мира рендеринга/физики основной игры.
  3. Добавьте содержимое и Camera3D под этот SubViewport. Сделайте нужную камеру текущей. Освещаемым материалам нужны подходящие свет и Environment: одна камера их не освещает.
  4. Выберите режим обновления. Для непрерывной анимации вне экрана используйте Always, для одного обновления — Once.
  5. Покажите GetTexture() в нужном месте интерфейса или сцены. Стоимость отрисовки оценивайте по реальному размеру SubViewport.

Класс ниже создаёт изолированный viewport с ручным управлением и сохраняет завершённый кадр. Он не наполняет сцену и не создаёт готовый красивый предпросмотр модели.

C#
using System;
using System.Threading.Tasks;
using Reckfell;

public static class ViewportTools
{
    public static SubViewport CreateIndependent3D(Entity parent, Vector2I size)
    {
        if (!parent.IsInsideTree() || size.X < 2 || size.Y < 2)
            throw new ArgumentException("Use a parent in the tree and a size of at least 2 by 2.");
        var view = new SubViewport
        {
            Size = size,
            World3D = new World3D(),
            RenderTargetUpdateMode = SubViewport.UpdateMode.Disabled,
        };
        parent.AddChild(view);
        return view;
    }

    // Call on the main thread. Keep the viewport alive until this task completes.
    // Use on a manually managed viewport, not one controlled by a container.
    public static async Task<Error> SavePngAsync(SubViewport view, string path)
    {
        if (!view.IsInsideTree() || view.Size.X < 2 || view.Size.Y < 2 ||
            view.GetParent() is SubViewportContainer)
            throw new ArgumentException("Use a valid, manually managed SubViewport.");
        var previous = view.RenderTargetUpdateMode;
        try
        {
            view.RenderTargetUpdateMode = SubViewport.UpdateMode.Once;
            await view.ToSignal(RenderingServer.Singleton, RenderingServer.SignalName.FramePostDraw);
            using var image = view.GetTexture().GetImage();
            return image.SavePng(path);
        }
        finally
        {
            view.RenderTargetUpdateMode = previous;
        }
    }
}

После CreateIndependent3D(this, new Vector2I(512, 512)) добавьте камеру и содержимое в полученный viewport. Изначально отрисовка Disabled. Для живого предпросмотра задайте RenderTargetUpdateMode = Always; для одного PNG дождитесь ViewportTools.SavePngAsync(view, "user://preview.png") и проверьте Error.Ok.

Вызывайте async-метод из главного потока при работающем цикле рендеринга. Не удаляйте viewport до завершения задачи. Метод запрашивает кадр, ждёт RenderingServer.FramePostDraw, сохраняет Image и восстанавливает прежний запрошенный режим обновления. Он отклоняет viewport под управлением SubViewportContainer, который контролирует размер/видимость. Чтение данных GPU и запись файла могут задерживать кадр; не используйте этот метод для записи каждого кадра игры. PNG не является захватом исходного HDR-буфера.

Различайте обновление и очистку

Режим обновленияНазначение
DisabledСохранить последнюю отрисованную текстуру до следующего запроса.
OnceОтрисовать кадр и остановить автоматическую отрисовку. Повторное назначение Once запрашивает следующий кадр.
WhenVisibleОбновлять при видимом использовании текстуры; это не просто флаг видимости Entity.
WhenParentVisibleСвязать обновление с видимостью родительского viewport.
AlwaysОбновлять, даже если текстуру никто не показывает. Тратит работу рендеринга каждый кадр.

После выполнения Once getter свойства SubViewport в 1.0.0 по-прежнему возвращает запрошенный Once. Он не является индикатором «кадр ожидает отрисовки» или завершения. Дождитесь кадра и проверьте результат. Восстановление прежнего Once может запланировать ещё одно обновление; для ручных снимков используйте Disabled как состояние покоя.

Отключение отрисовки viewport не приостанавливает дочерние Entity, физику и _Process(). При необходимости управляйте ими отдельно. RenderTargetClearMode — другой параметр: очищать каждый отрисованный кадр, не очищать или очистить один раз. Отказ от очистки сохраняет старые пиксели: это полезно для намеренного накопления, но часто объясняет нежелательные шлейфы. Частотой обновления этот параметр не управляет.

Осознанно разделяйте миры

ЗадачаНастройка
Две 3D-камеры показывают одну игруОставьте World3D дочернего viewport неназначенным для наследования либо явно назначьте общий World3D.
Изолированный 3D-предпросмотрНазначьте новый World3D. Объекты регистрируются в эффективном мире ближайшего viewport.
Узнать реально используемый мирВызовите FindWorld3D(). Свойство World3D может быть null, хотя мир наследуется.
Собственный 3D-мир viewportВключите OwnWorld3D. Он приоритетнее назначенного/унаследованного источника и не копирует иерархию Entity.
Независимое 2D-содержимоеПоведение по умолчанию: у каждого viewport свой World2D.
Общее 2D-содержимоеЯвно назначьте общий World2D; разрешайте его через FindWorld2D().

OwnWorld3D может дублировать назначенный ресурс World3D: свойство World3D остаётся источником, а FindWorld3D возвращает эффективный собственный мир. Отключение OwnWorld3D возвращает использование назначенного/унаследованного мира. Отдельный World3D не является копией игровой сцены.

Текущая камера выбирается для каждого viewport отдельно. Назначение Camera3D текущей заменяет камеру этого viewport, а не другого. Общий мир не означает общую камеру. Для физического взаимодействия между отдельными 3D-мирами нужен явный мост; общее EntityTree миры не объединяет.

Размер, контейнеры и координаты 2D

  • SubViewport.Size — разрешение render target. Растягивание показывающего его TextureRect не добавляет детализацию в исходную текстуру.
  • Непосредственный родитель SubViewportContainer с Stretch = true управляет разрешением ребёнка. При StretchShrink = 2 контейнер 128 × 96 создаёт изображение 64 × 48. Меняйте контейнер/коэффициент, а не спорьте с ним через Size дочернего viewport.
  • Size2DOverride вместе с Size2DOverrideStretch = true меняет логическую область координат 2D, а не размер выходной текстуры: target 128 × 96 может показывать растянутый canvas 64 × 48.
  • Обработка показа/скрытия контейнера может менять режим обновления ребёнка. Не управляйте таким viewport как полностью независимым target с ручным расписанием.

Масштабирование корневого окна настраивается отдельно: см. Разные разрешения.

Передавайте ввод в нужную поверхность

Непосредственный родитель SubViewportContainer обслуживает показ и ввод ребёнка с учётом фокуса Control, фильтра мыши и видимости. TextureRect, Sprite и 3D-материал с ViewportTexture не заменяют этот механизм.

При ручной передаче сначала переведите позицию из показываемого изображения в координаты целевого viewport: учтите растяжение, обрезку, полосы соотношения сторон и при необходимости 3D-попадание/UV. Для уже локального события используйте PushInput(event, inLocalCoords: true). Флаг не рассчитывает ваше преобразование. При false движок применяет свой переход из экранных координат в локальные, который не может угадать UV произвольного меша.

PushInput запускает цепочку ввода viewport: _Input, GUI и необработанные этапы, если событие не поглощено. SetInputAsHandled() предотвращает последующую необработанную обработку. Несмотря на имя, GuiDisableInput блокирует вход PushInput, включая _Input, а не только GUI. Состояние поглощения ввода может быть общим через родителя контейнера: не предполагайте его изоляцию для каждого ребёнка.

Найдите причину пустого или неправильного предпросмотра

СимптомЧто проверить
Первый снимок пустой или старыйЗапросите обновление и дождитесь завершения кадра; не читайте сразу в _Ready().
Предпросмотр не обновляетсяРежим обновления и реальный показ текстуры; Always нужен только для непрерывного обновления вне экрана.
В изолированном предпросмотре объекты основной игрыПроверьте FindWorld3D: неназначенный World3D может наследовать мир родителя.
Смена камеры затрагивает не тот видБлижайший viewport камеры и текущую камеру этого viewport.
Назначение размера игнорируетсяРастягивающий SubViewportContainer может управлять им сам.
Нажатия не попадают в элементыПеревод координат, отключение ввода, фокус и поглощение событий.
В 3D нет тениИсточник/меш и атлас теней viewport: не все нужные настройки качества наследуются автоматически.
Не слышно пространственный звукНужную конфигурацию 2D/3D-слушателя viewport, а не только камеру.

Нативные проверки выполнены на Windows Forward+/Vulkan: реальные пиксели, выбор мира/камеры, разделение обновления и обработки, синтетический ввод, размеры и PNG. Физическое перенаправление указателя, аудиослушатель, другие рендереры, платформы и экспорт этим примером не подтверждены.

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

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

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

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