Дрожание, рывки и задержка ввода: диагностика
Различите физическое движение, длинные кадры и задержку ввода; проверяйте настройки измерениями.
Определите вид проблемы
| Симптом | Что измерять первым |
|---|---|
| Объект мелко дрожит при ровном движении, камера и фон движутся по-разному | Порядок обновления камеры и объекта, физический шаг и интерполяцию |
| Игра временами замирает или пропускает движение | Время отдельных кадров, CPU/GPU, загрузку ресурсов и компиляцию конвейеров |
| Нажатие заметно опережает реакцию на экране | Обработку ввода, физический шаг, очередь кадров и путь до дисплея |
Эти проблемы могут сочетаться. Средний FPS не показывает редкие длинные кадры: при цели 60 FPS бюджет кадра — примерно 16,67 мс. Сравнивайте распределение времени кадров и всплески с моментом появления дефекта.
Получите воспроизводимый результат
- Зафиксируйте сборку, сцену, маршрут камеры, разрешение и настройки качества. Начните с минимального участка, где проблема повторяется.
- Сравните запуск в редакторе и экспортированную игру на том же устройстве. Запишите фактический рендерер, драйвер, режим окна, частоту дисплея, V-Sync и лимит FPS.
- Измерьте время кадров и затраты CPU/GPU. Сравните первый и повторный проход: появление рывка только при первом новом эффекте — повод проверить загрузку и компиляцию, а не доказательство одной конкретной причины.
- Меняйте одну настройку за раз, повторяйте тот же маршрут и сохраняйте результат.
Для измерений используйте производительность и профилирование. Снижение разрешения помогает проверить гипотезу о стоимости GPU, но само по себе не определяет точное узкое место.
Дрожание движения: согласуйте физику и камеру
Двигайте физическое тело в _PhysicsProcess(double delta). Физический шаг и отрисовка имеют разные частоты; перестановка камеры в _Process(double delta) по необработанной позиции тела может сделать эти ступени заметными.
Включите и настройте физическую интерполяцию. Она сглаживает отображение между физическими состояниями, но не ускоряет расчёт физики и не гарантирует плавность при любом времени кадра.
Проверьте режимы интерполяции родителей и потомков. После телепортации сначала задайте новую трансформацию, затем вызовите ResetPhysicsInterpolation(), чтобы не рисовать переход из старого положения. Для вручную управляемой 3D-камеры учитывайте GetGlobalTransformInterpolated() цели вместо смешивания интерполированного изображения с необработанной физической позицией.
Engine.PhysicsTicksPerSecond задаёт частоту физики, не FPS рендера. Увеличение частоты уменьшает длительность шага, но повышает стоимость симуляции и может изменить её результат. Это не универсальное средство от дрожания, туннелирования или сетевых расхождений. Проверяйте игру на выбранной частоте, особенно при конкурентной сетевой симуляции.
Редкие остановки: ищите работу в длинном кадре
| Когда появляется остановка | Где искать |
|---|---|
| При загрузке уровня или модели | Синхронное чтение, разбор, создание ресурсов и экземпляров |
| При первом показе материала или эффекта | Создание и компиляция графических конвейеров; проверка компиляций |
| При массовом создании объектов | Скрипты, выделения памяти, сборка мусора, регистрация физики и рендера |
| Постоянно при сложном кадре | Профиль CPU/GPU, тени, геометрия, прозрачность и разрешение |
Показывать каждый эффект один кадр недостаточно, чтобы гарантировать отсутствие всех будущих компиляций: варианты зависят от проходов, материалов и конфигурации. Проверяйте реальный маршрут и счётчики. Переключение V-Sync не устраняет синхронное чтение файла или дорогой расчёт.
Задержка ввода: меняйте конкретный этап
Input.UseAccumulatedInput объединяет совместимые события, например несколько движений мыши, сохраняя суммарное перемещение. Это не обещание объединить все типы событий в одно или вызвать все обработчики строго один раз за кадр.
Отключение накопления может понадобиться при рисовании или точном отслеживании движения, но увеличивает число доставляемых событий. При input_devices/buffering/agile_event_flushing=true очередь используется даже с Input.UseAccumulatedInput=false. Само переключение свойства не сбрасывает уже накопленную очередь.
Input.FlushBufferedEvents() доставляет накопленные события; движок и сам вызывает сброс в цикле работы. Явный сброс нужен только там, где ваш алгоритм требует контролируемой доставки: он может вызвать обработчики ввода прямо во время вызова. Не добавляйте его повсюду как универсальное ускорение.
| Настройка | Что проверить |
|---|---|
| V-Sync | Разрывы кадра, время кадров и задержку на целевом дисплее; не предполагайте, что отключение всегда лучше |
Лимит FPS / Engine.MaxFps | Стабильность времени кадров и загрузку; это отдельная настройка от частоты физики |
| Число изображений swapchain | Поддержку рендерером/драйвером и фактическую очередь; запрошенное число не гарантирует одинаковое поведение на всех устройствах |
| VRR и режим окна | Фактический режим дисплея и результат измерения, а не название опции |
Для сравнения запусков есть --disable-vsync и --max-fps <fps>; драйвер может принудительно включать V-Sync. Подробности запуска — в командной строке. Не применяйте одну формулу лимита FPS ко всем дисплеям и драйверам.
Что приложить к отчёту
Укажите сборку, ОС, GPU/драйвер, фактический рендерер, разрешение, частоту дисплея, режим окна, V-Sync, лимит FPS, частоту физики и настройки интерполяции. Приложите минимальный проект, точные шаги и запись времени кадров с моментом проблемы.
Для задержки «нажатие → изображение» нужна запись, на которой одновременно видны устройство ввода и экран. FPS и программное время обработки события не измеряют весь этот путь. Указывайте частоту съёмки и предел точности; не обещайте задержку в миллисекундах только по настройкам проекта.