Skip to content
E
Egmatic
оптимизация 2d игрпроизводительность игрывызовы отрисовкиобъединение спрайтовпул объектов60 fps

Как оптимизировать 2D-игру: практическое руководство до стабильных 60 FPS

2D-игра, в которой падают кадры, почти всегда проигрывает одному из четырёх: слишком много вызовов отрисовки, выделение памяти в каждом кадре, которое запускает сборку мусора, работа над тем, чего игрок не видит, и симуляция, привязанная к частоте кадров. Лечится это всегда одной и той же последовательностью — сначала измерить, затем сократить вызовы отрисовки через объединение спрайтов и атласы, убрать выделения памяти с помощью пула объектов, перестать рисовать то, что вне кадра, и вести симуляцию фиксированным шагом. Это руководство проходит каждый шаг в рамках бюджета 16,67 миллисекунды, который даёт кадр при 60 FPS, с привычками профилирования, отличающими плавную игру от дёрганой. Никаких выдуманных замеров — каждая техника опирается на то, как рендереры и среда выполнения .NET работают на самом деле.

Vladislav Kovnerov9 июля 2026 г.10 мин

2D-игра, которая заикается, почти никогда не страдает от загадочной причины. Стоит её измерить — и кадровый бюджет укажет на одного из четырёх привычных подозреваемых: слишком много вызовов отрисовки, сжигающих время процессора на накладных расходах; выделение памяти в каждом кадре, запускающее паузы сборки мусора; работа над объектами, которых камера не видит; и симуляция, привязанная к частоте кадров, из-за чего игра бежит быстрее или медленнее на разных машинах. Лечится это одинаково в любом движке: сначала измерить, затем бить по этим четырём по порядку.

Кадр при 60 FPS располагает бюджетом в 16,67 миллисекунды. Всё — ввод, симуляцию, отрисовку — нужно успеть за это время, иначе кадр пропускается и игрок видит рывок. Оптимизация — это дисциплина, которая помогает уложить игру в этот бюджет и удержать её там. Это руководство — практическая версия, написанная для 2D-игр и опирающаяся на то, как рендереры и среда выполнения .NET работают в действительности, а не на выдуманные проценты ускорения.

Если замедление происходит при сборке, а не во время игры, это другая проблема — см. наше руководство по ускорению сборки игры. Эта статья — о кадре, который вы отдаёте игроку.

Первый шаг всегда такой: профилировать, а не угадывать

Самая дорогая ошибка в оптимизации — менять код без измерений. У большинства игр есть одна-две функции, которые доминируют в кадре, и редко это те, в которых вы подозреваете проблему. Решение — сначала их найти.

ИнструментЧто показываетКогда обращаться
График времени кадраСтоимость каждого кадра в миллисекундах, с пикамиНачинать всегда отсюда — выявляет рывки, которые прячет средний счётчик FPS
Профилировщик процессораВремя в каждой функцииКогда кадр ограничен процессором и нужен виновник
Профилировщик видеокарты / отладчик кадраВызовы отрисовки, перерисовку, стоимость шейдеровКогда кадр ограничен видеокартой или скоростью заполнения
Профилировщик памятиВыделения на кадр, события сборки мусораКогда игра заикается, а счётчик FPS выглядит нормально

Читайте цифры, а не свою интуицию. Функция, которая «кажется тяжёлой», но занимает 0,1 мс, — не ваша проблема; скучный цикл на 6 мс — вот он. Каждая оптимизация ниже имеет смысл только там, где профилировщик говорит, что она нужна.

Сократите вызовы отрисовки через объединение спрайтов и атласы

Каждый вызов отрисовки — это одна команда от процессора к видеокарте: «нарисуй эту геометрию с этим материалом в такой позиции». Цена — не само рисование (современные видеокарты рисуют миллионы пикселей мгновенно), а фиксированные накладные расходы на подготовку каждого вызова. Сцена, рисующая тысячу спрайтов по одному, тратит почти весь кадровый бюджет на эти накладные расходы, а видеокарта в это время простаивает в ожидании следующей команды.

Объединение спрайтов (sprite batching) решает задачу, собирая множество спрайтов с одним материалом в один вызов отрисовки. Чтобы объединение работало, спрайты должны иметь общую текстуру — отсюда и парный приём: атлас спрайтов — одна большая текстура, в которую плотно упаковано множество мелких спрайтов. С атласом целый уровень из тайлов, кадры анимации персонажа или рой одинаковых врагов могут свернуться в один или несколько вызовов отрисовки вместо сотен.

СценаБазовый подход (вызов на спрайт)С атласом и объединением
500 тайлов~500 вызовов отрисовки~1 вызов
Анимированный персонаж (8 кадров)1 на видимый кадр1
Шквал снарядов (300 штук)~300~1 (общий атлас)

Практические правила: держите спрайты, которые появляются рядом, на одном атласе; не разрывайте группы переключением текстуры или режима наложения посреди сцены; упаковывайте атласы размерами, кратными степени двойки, где этого требует платформа. Это задокументированное поведение любого крупного 2D-рендерера, а не специфические для движка хитрости.

Не выделяйте память в каждом кадре: используйте пул объектов

В среде со сборкой мусора, такой как .NET (её используют и Unity, и MonoGame), занятую память рано или поздно приходится возвращать. Этим занимается сборщик мусора, и когда он запускается, то приостанавливает игру на несколько миллисекунд — достаточно, чтобы пропустить кадр и выдать видимый рывок. Приём в том, чтобы перестать выделять память во время игры, а главный нарушитель — создание и уничтожение объектов на горячем участке.

Пул объектов — стандартный ответ. Вместо того чтобы создавать новую пулю при выстреле и уничтожать её при попадании, вы держите заранее созданный набор пуль. Выстрел берёт неактивную пулю, включает её и сбрасывает позицию. Попадание выключает пулю и возвращает её в пул. Никаких выделений, никакого уничтожения, никакой сборки мусора.

Пулируйте всё, что часто появляется и исчезает:

  • Снаряды и частицы
  • Врагов в волновой игре
  • Всплывающие числа урона и текст
  • Источники звука для повторяющихся эффектов

Более глубокая привычка — вообще избегать выделений в каждом кадре: не пользоваться LINQ и склейкой строк в часто выполняемых циклах, избегать упаковки значимых типов и переиспользовать коллекции вместо создания новых. Кадр, который ничего не выделяет, идёт плавно; кадр, который выделяет по несколько килобайт сотню раз, рано или поздно дёрнется.

Не рисуйте то, чего игрок не видит

Отрисовка объектов вне экрана — это впустую проделанная работа, которую видеокарте всё равно приходится принимать, а процессору — отправлять. Два простых приёма убирают большую её часть:

  • Отсечение (culling): пропускайте объекты целиком, если они вне поля камеры. Если враг за тремя экранами отсюда, ему незачем рисоваться в этом кадре.
  • Дисциплина слоёв и порядка отрисовки: группируйте статичный фоновый арт так, чтобы его можно было нарисовать один раз или закэшировать, а не обрабатывать заново каждый кадр.

Та же логика применима к логике, а не только к отрисовке. Врагу вне экрана не нужно полное обновление ИИ каждый кадр; удалённую систему частиц можно обновлять вдвое реже. Тратьте бюджет там, куда смотрит игрок.

Отвяжите симуляцию от частоты кадров

Если ваша игра двигает объекты по принципу «сдвинуть позицию на скорость в каждом кадре», то на мониторе 144 Гц игра бежит быстрее, а на устройстве с 30 FPS — медленнее. Это уже не оптимизация, а корректность, но напрямую влияет на воспринимаемую плавность: фиксированная частота симуляции в сочетании с интерполяцией выглядит и ощущается стабильно на любом дисплее.

Установившийся приём — фиксированный шаг с аккумулятором: симуляция продвигается фиксированными порциями (скажем, 60 раз в секунду) вне зависимости от частоты обновления экрана, а рендерер интерполирует между двумя последними состояниями симуляции для текущего кадра. Широко цитируемая статья Гленна Фидлера «Fix Your Timestep!» — классическое объяснение этого метода. Выгода двойная: детерминированная, воспроизводимая физика и игра, которая одинаково ощущается на любой машине.

Всегда держите в голове кадровый бюджет

Чтобы удержать 60 FPS, кадр должен укладываться в 16,67 мс. Это число и есть вся игра. Вот примерно как здоровый кадр 2D-игры его тратит:

ЭтапТипичная доля из 16,67 мсЧто ломается
Ввод и симуляция2–5 мсСлишком много активных объектов, выделения в кадре
Отсечение и подготовка групп1–3 мсНет объединения спрайтов, частые переключения текстур
Отрисовка на видеокарте и выдача кадра2–6 мсБольшая перерисовка, крупные прозрачные частицы
Запасвсё остальноеРывки съедают его первым

Колонка «запас» — и есть суть. Кадр, использующий 16 мс, не имеет места для всплеска, поэтому следующее выделение памяти, следующий взрыв частиц или очередной запуск сборщика мусора его уронит. Старайтесь тратить заметно меньше бюджета, а не впритык к нему.

Ошибки, которые сводят всё на нет

  • Оптимизировать до измерений. Вы будете настраивать не то.
  • Гнаться за средним FPS. Плавный график времени кадра важнее высокого числа. Следите за всплесками.
  • Игнорировать сборку мусора. Главная причина «FPS высокий, а всё равно заикается».
  • Оптимизировать редко выполняемый код. Меню, которое грузится раз в минуту, не нуждается в пуле.
  • Привязывать скорость игры к частоте кадров. Это ломает ощущение на разных дисплеях и перечёркивает все остальные усилия.
  • Добавлять память вместо правки логики. Больший кэш помогает, только если он прогрет; холодный кэш импортирует всё заново.

Роль Egmatic

Egmatic — это 2D-редактор и движок, построенный на базе MonoGame. Этот выбор важен для производительности по двум честным причинам. Во-первых, MonoGame — компактная среда выполнения C#: она даёт прямой контроль над конвейером отрисовки без тяжёлого слоя абстракции между вами и «железом», поэтому при плотном кадровом бюджете узким местом оказывается ваш собственный код, а не движок, который вам сопротивляется. Во-вторых, живой предпросмотр Egmatic позволяет поменять значение и тут же увидеть влияние на время кадра, превращая профилирование из медленного цикла «правка — сборка — запуск» в быструю обратную связь — ту самую привычку, которую рекомендует вся остальная статья.

Egmatic нацелен на десктоп, мобильные устройства и консоли через базу MonoGame, поэтому та же работа по оптимизации сохраняется на всех платформах. Если вы только знакомитесь с лежащим в основе движком, наше руководство о MonoGame объясняет, что это за база и почему она компактна.

Заключение

Плавная 2D-игра — не плод секретных приёмов. Она рождается из измерений в рамках бюджета 16,67 мс, сокращения вызовов отрисовки через атласы и объединение спрайтов, устранения выделений памяти в каждом кадре с помощью пула объектов, отсечения того, чего камера не видит, и ведения симуляции фиксированным шагом. Делайте эти пять вещей в таком порядке, всегда опираясь на профилировщик, а не на интуицию, — и кадровый бюджет перестанет быть борьбой и станет привычкой.

Похожие статьи

переходы между сценамиэкран загрузкиасинхронная загрузка

Лаги при переходах между сценами: как сделать плавную игру

Подёргивание при переходе между сценами почти никогда не связано с отрисовкой. Причина в другом: тот самый кадр, который должен нарисовать новую сцену, заодно вынужден её загружать и строить. Решение — не делать эту работу в кадре перехода: загружать следующую сцену асинхронно и заранее, прятать остатки за экраном загрузки или затемнением и использовать пул объектов, чтобы переключение не вызывало всплеск создания объектов и сборки мусора. В руководстве разобраны три схемы загрузки (блокирующая, асинхронная с экраном загрузки и предзагрузка), API Unity и Godot, на которых они построены, и частые ошибки — тяжёлый код инициализации, несжатые ассеты и отсутствие экрана загрузки, из-за которых рывок видно игроку.

22 июля 2026 г.9 мин