Viewport: миры, ввод и захват кадра
Независимый предпросмотр, режимы обновления, размеры и корректный PNG.
Viewport отрисовывает мир в поверхность. Корневой Window — главный viewport игры; SubViewport создаёт отдельный render target для предпросмотра модели, мини-карты, разделённого экрана или текстуры внутри сцены.
Создание SubViewport само по себе не показывает изображение. Выведите GetTexture() через TextureRect, Sprite или материал либо поместите SubViewport непосредственно в SubViewportContainer. Текстура на 3D-меше не передаёт нажатия в viewport автоматически.
Создайте независимый 3D-предпросмотр
- Создайте SubViewport размером не меньше 2 × 2 пикселей.
- Назначьте новый World3D, если предпросмотр должен быть изолирован от мира рендеринга/физики основной игры.
- Добавьте содержимое и
Camera3Dпод этот SubViewport. Сделайте нужную камеру текущей. Освещаемым материалам нужны подходящие свет и Environment: одна камера их не освещает. - Выберите режим обновления. Для непрерывной анимации вне экрана используйте
Always, для одного обновления —Once. - Покажите
GetTexture()в нужном месте интерфейса или сцены. Стоимость отрисовки оценивайте по реальному размеру SubViewport.
Класс ниже создаёт изолированный viewport с ручным управлением и сохраняет завершённый кадр. Он не наполняет сцену и не создаёт готовый красивый предпросмотр модели.
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. Физическое перенаправление указателя, аудиослушатель, другие рендереры, платформы и экспорт этим примером не подтверждены.