Содержание
- Введение
- 1. Что влияет на производительность
- 2. Способы оптимизации кода
- 3. Оптимизация движка на основе языка java
- 4. Оптимизация на основе openGL
- 5. Высокоуровневая оптимизация
- 6. Низкоуровневая оптимизация
- 7. Оптимизация игр для смартфонов
- Выводы
- Список источников
Введение
Производительность является важным компонентом любой игры, от неё зависит популярность игры, комфорт пользователя и общее впечатление [1]. Под производительностью обычно подразумевают средний уровень FPS в игре за определённый промежуток времени. FPS (Frame Per Second) - число кадров в секунду, кадры - это изображения, которые обрабатывает графический адаптер по команде центрального процессора. Чем больше кадров демонстрируется в секунду, тем плавнее и комфортнее игра.
Вопреки ожиданиям немногие игроки требуют высоких показателей фпс, для большинства достаточно средних показателей в 30 единиц, а для некоторых даже меньше. Исследования проделанные компанией Lesta Studio , показывают, что для всей совокупности пользователей связь между производительностью и комфортной игрой достаточно слабая (рис.1).

Рисунок 1 – Диаграмма зависимости между восприятием комфорта и средним FPS
Однако уровень комфорта зависит так же от жанра игры и от самого пользователя. Например, многие консольные игры работают на частоте 30fps, но с такими показателями картинка недостаточно плавная, а отзыв на манипуляции игрока запаздывает, что мешает полному погружению в игру. Для игроков в казуально -аркадные игры будет достаточно 40+fps, для шутеров от первого лица, стратегий, ритм-игр, MOBA понадобится около 60-100fps [2]. Показатель Fps выше 100 потребуется игрокам соревновательных шутеров или киберспортсменов.
Таким образом, если не брать во внимание предпочтения в fps профессиональных игроков, то для большинства будет комфортным средний показатель в 60fps, этот показатель считается комфортным минимумом.
Что влияет на производительность? [3]
Рассмотрим самые популярные факторы, влияющие на оптимизацию

Рисунок 2 - Основные факторы, влияющие на оптимизацию (анимация) (6 кадров)
- Клиентское ПО
Если система загружена фоновыми процессами или запущены программы, обращающиеся к диску во время игры, то ресурсов для самой игры может оказаться недостаточно.
- Конфигурация ПК
От комплектующих компьютера напрямую зависит производительность.
- Настройки графики
Графику настраивают пользователи обычно сами или используют пресеты, однако ни тот ни другой вариант не являются гарантией того, что настройки будут выставлены максимально оптимально, из-за чего пользователь теряет производительность.
- Сетевые проблемы
Клиент-серверное взаимодействие не всегда устойчиво, нередко у пользователей возникают проблемы с сетью, что влияет на производительность сетевых игр. Причиной этому могут послужить проблемы на стороне провайдера, большое количество беспроводных клиентов неоптимальный сетевой маршрут доставки трафика, некачественной сетевое оборудование.
- Модификации
Модификации оригинальной игры сторонними разработчиками отрицательно влияют на производительность. Они не только снижают средний fps, но и создают резкие провалы в тех случаях, где чистый клиент работает стабильно.
- Скорость работы приложения
Существует огромное количество разных игровых движков со своими плюсами и минусами, все они потребляют разное количество ресурсов, в зависимости от качества архитектуры и поддерживаемых эффектов. Влияет так же и язык программирования, на котором написан движок, т.к. в разных языках ресурсы распределяются по разному. Об этом и пойдёт речь дальше.
Способы оптимизации кода
Для того, чтоб движок выдавал максимальное количество fps необходимо не только уметь с ним эффективно работать, но и знать тонкости разработки на языке, на котором написан движок, и принципы работы движка для лучшего понимания процесса распределения ресурсов. [4]
Существует много разных факторов, влияющих на скорость обработки кадров, рассмотрим общие методы оптимизации кода. Которые могут подойти практически в любой ситуации:
- Минимизация влияния объектов за пределами экрана
Минимизация объёма вычислений при помощи оптимизации отрисовки объектов чрезвычайно важна. Зачастую этим занимаются движки, или же графический процессор. Для реализации необходимо разделить объект на 2 слоя - первый будет графическим представлением объекта, а второй - данными и функциями. Таким образом, если объект находится за пределами экрана - нам больше не нужно его отрисовывать.
- Независимость от обновления кадров
Обычно в игровых движках большинство объектов обновляются в каждом кадре, это сильно нагружает процессор и понижает производительность. Необходимо, по возможности, избавиться от обновления в каждом кадре. Для этого необходимо отделить функцию рендеринга и вызывать её только при изменении состояния объекта.
- Непосредственные вычисления и поиск значений
Неплохую прибавку к производительности может дать хеширование тригонометрических функций, т.к. эффективнее хранить большую таблицу и брать данные из неё, а не выполнять расчёты на лету.
- Время простоя
Необходимо выделить функции, которые не зависят от времени, например погодные условия и вычислять их, когда пользователь отошёл, занят чтением или другими не затратными к ресурсами вещами. Время которое освобождается, когда пользователь занят вещами не нагружающими процессор, можно использовать для вычисления множества других событий.
Оптимизация движка на основе языка java
Так же не лишним будет упомянуть оптимизацию кода, опираясь на особенности языка программирования java и особенности работы с движками на его основе [5]:
- Циклы
Если вам необходимо обойти большой массив данных, например список прямоугольников (для рендеринга), врагов или любых других тяжёлых объектов и вы собираетесь для этого использовать цикл for, то лучше пользоваться обратным for. Обратный for даёт большую эффективность за счёт того, что ему не нужно каждый раз проверять размер массива, а также это происходит, потому что сравнение с нулём является нормальным, в то время как сравнение с целым числом требует вычитания. Но если нет необходимости использовать цикл for, то более эффективным будет while.
- Потоки
Необходимо разделять тяжёлые действия на разные потоки и не запускать сложную логику в потоке отрисовки. При профилировании ЦП вы увидите поток с именем GLThread. Большинство движков используют GL 2.0/3.0 для рендеринга, и это поток, который содержит контекст GL. Это означает, что каждое изменение пользовательского интерфейса должно производиться через этот поток, иначе случаются плохие вещи — текстуры не загружаются, элементы пользовательского интерфейса меняются одновременно из разных мест, это может быть беспорядок. Проблема в том, что использование этого потока оставляет пользователя с зависшим приложением.
- Память
Не стоит забывать очищать компоненты, которые уже не нужны. Java имеет свой сборщик мусора, который чистит ненужные классы, но некоторые движки, например libGDX, этого не делает. Это связано с тем, что память OpenGL не управляется JVM GC, и, таким образом, если вы создаёте свои собственные текстуры, вам придётся удалять их вручную, иначе вы рискуете трудно отлаживаемой утечкой памяти.
- Пакеты
Вызов начала и конца пакета спрайтов и ShapeRenderer занимает много памяти. Итак, старайтесь изо всех сил вызывать начало и конец только один раз за кадр. Попробуйте визуализировать все свои спрайты, а затем визуализировать фигуры. Вместо того, чтобы рендерить ваши спрайты, затем формы, а затем повторно открывая ваш spritebatch.
- Отдельные методы
Есть некоторые методы, которые требуют много ресурсов (или больше, чем другие), когда они используются, например пересечения класса Rectangle. Например, лучшее, что вы можете сделать для просмотра массива прямоугольников, — это сначала проверить, близки ли оба прямоугольника, и если да, то вызвать для них метод Intersects. Вместо вызова Intersects со всеми прямоугольниками в массиве.
- Визуализация
Метод рисования — это ещё один элемент, который требует большого количества ресурсов, если вам нужно изменить состояние элемента, даже если он находится за пределами экрана, вы можете каждый раз пытаться обновлять его логику/переменные, но вызывать метод рисования только если он находится внутри координат экрана.
- Переменные
Объявление переменных или объектов в плохих местах может вызвать зависание, избегайте объявления переменных в цикле рендеринга и, в основном, не объявляйте их внутри чего-то вроде цикла for, вместо этого объявляйте их вне цикла и обновляйте его значения внутри. Хороший момент в этом, заключается в том, что это не имеет значения, когда мы говорим о примитивных типах данных, поэтому, если переменная имеет тип int, boolean или float, её объявление внутри цикла не окажет большого влияния на производительность.
- Паттерны
Паттерны значительно упрощают разработку и эффективность приложение, а так же поддержку кода. Если вы хотите иметь один элемент, который может взаимодействовать и находиться в любом классе вашей программы, что-то вездесущее, например класс игрока, вы можете сделать его классом Singleton (это очень просто и даст вам более удобный код). Если вы не хотите, чтобы в вашей программе были бесполезные экземпляры, которые, возможно, вы никогда не будете использовать, вы можете использовать шаблон проектирования Factory.
Оптимизация на основе openGL
На первый взгляд может показаться, что производительность приложений, основанных на OpenGL, определяется в первую очередь производительностью реализации самой библиотеки OpenGL [6]. Это верно, однако организация всего приложения (использование локальных и глобальных переменных, структур данных, библиотек, последовательность выполнения команд) также очень важна.
Высокоуровневая оптимизация
Обычно от программы под OpenGL требуется визуализация высокого качества на интерактивных скоростях. Но, как правило, и то и другое сразу получить не удаётся. Следовательно, необходим поиск компромисса между качеством и производительностью. Существует множество различных подходов к данному вопросу [7]:
– отображение геометрии сцены с низким качеством во время анимации, а в моменты остановок показ её с наилучшим качеством;
– объекты, которые находятся полностью вне поля видимости, могут быть эффективно отсечены без передачи на конвейер OpenGL с помощью проверки попадания ограничивающих их простых объёмов (сфер или кубов) в пирамиду зрения;
– визуализация модели с уменьшенным количеством примитивов во время интерактивного вращения (например, при нажатой клавише мыши) и отображение модели полностью при рисовании статичного изображения;
– во время анимации можно отключить псевдотонирование (dithering), плавную заливку, наложение текстуры и включать все это во время демонстрации статичных изображений (этот подход особенно эффективен для систем без аппаратной поддержки OpenGL).
Низкоуровневая оптимизация
Объекты, отображаемые с помощью OpenGL, хранятся в некоторых структурах данных. Скорость визуализации определяется эффективностью использования таких структур. Желательно, чтобы использовались структуры данных, которые могут быть быстро и эффективно переданы на конвейер OpenGL. Например, если необходимо отобразить массив треугольников, то использование указателя на этот массив значительно более эффективно, чем передача его OpenGL поэлементно.
Предположим, что создаётся приложение, которое реализует рисование карты местности. Один из компонентов базы данных - список городов с их шириной, долготой и названием.
Для хранения информации о городе создаётся соответствующая структура данных. Список городов может храниться как массив таких структур. Затем создаётся функция, которая рисует города на карте в виде точек разного размера с подписями: если город маленький, то точки имеют размер 2 px, если большой - 4 px.
Реализация, представленная в первом варианте, неудачна по следующим причинам:
– glPointSize() вызывается для каждой итерации цикла;
– между glBegin() и glEnd() рисуется только одна точка;
– вершины определяются в неоптимальном формате.
Во втором варианте реализации glPointSize() вызывается только дважды, и увеличивается число вершин между glBegin() и glEnd(). Однако остаются ещё пути для оптимизации. Если поменять структуры данных, то можно ещё повысить эффективность рисования точек.
Самым оптимальным можно считать третий вариант. После реорганизации структуры города разных размеров хранятся в разных списках, положения точек хранятся отдельно в динамическом массиве. Исключается необходимость в условном операторе внутри glBegin/glEnd и имеется возможность использовать массивы вершин для оптимизации.
Оптимизация игр для смартфонов [8]
- Скорость загрузки
Игроки хотят как можно быстрее погрузиться в действие вашей игры, поэтому важно максимально сократить время загрузки вашей игры. Следующие меры обычно помогают сократить время загрузки:
– Выполнить ленивую загрузку. Если вы используете одни и те же assets в последовательных сценах или уровнях игры, загружайте эти assets только один раз.
– Уменьшите размер своих assets. Таким образом, вы можете связать несжатые версии этих ресурсов с APK вашей игры.
– Используйте эффективный для диска метод сжатия. Примером такого метода является zlib.
- Держите потоки с большим объёмом памяти на одном процессоре
На многих мобильных устройствах кэши L1 находятся на определённых ЦП, а кэши L2 — на наборе ЦП, которые имеют общие часы. Чтобы максимизировать попадание в кэш L1, как правило, лучше всего, чтобы основной поток вашей игры, наряду с любыми другими потоками с большим объёмом памяти, выполнялся на одном процессоре.
- Откладывайте кратковременную работу на процессоры с меньшей мощностью
Большинство игровых движков умеют откладывать операции рабочего потока на другой ЦП по сравнению с основным потоком вашей игры. Однако движок не знает о конкретной архитектуре устройства и не может предвидеть рабочую нагрузку вашей игры так же хорошо, как вы [9]. Большинство устройств типа «система на кристалле» имеют как минимум 2 общих тактовых генератора. один для быстрых процессоров устройства и один для медленных процессоров устройства. Следствием этой архитектуры является то, что если одному быстрому ЦП необходимо работать на максимальной скорости, все остальные быстрые ЦП также работают на максимальной скорости. В примере отчёта, показанном на рис. 3, показана игра, использующая преимущества быстрых процессоров. Однако, этот высокий уровень активности быстро генерирует большое количество энергии и тепла.
Рисунок 3 – Демонстрация неоптимальное назначение потоков процессорам устройства
- Тепловая нагрузка
Когда устройства перегреваются, они могут тормозить процессор и/или графический процессор, и это может неожиданным образом повлиять на игры. Игры, которые включают сложную графику, тяжёлые вычисления или устойчивую сетевую активность, с большей вероятностью столкнутся с проблемами. Используйте тепловой API, чтобы отслеживать изменения температуры на устройстве и принимать меры для поддержания более низкого энергопотребления и более низкой температуры устройства. Когда устройство сообщает о перегреве, прекратите текущую деятельность, чтобы снизить энергопотребление. Например, уменьшите частоту кадров или тесселяцию полигонов.
Загрузка элементов пользовательского интерфейса. для поддержания постоянной частоты кадров важно учитывать относительно небольшой размер мобильных дисплеев и максимально упростить пользовательский интерфейс.
Отчёт показанный на рисунке 4, является примером фрейма пользовательского интерфейса, который пытается отобразить слишком много элементов по сравнению с возможностями мобильного устройства. Хорошая цель — сократить время обновления пользовательского интерфейса до 2-3 миллисекунд. Таких быстрых обновлений можно добиться, выполнив оптимизацию, подобную следующей:– Обновлять только те элементы на экране, которые были перемещены.
– Ограничить количество текстур и слоёв пользовательского интерфейса. Объединить графические вызовы, такие как шейдеры и текстуры, которые используют один и тот же материал.
– Перенести операции анимации элементов на GPU.
– Выполнить более агрессивное отсечение усечённой пирамиды и окклюзии.
– Если возможно, выполнить операции рисования с помощью Vulkan API.
– Накладные расходы на вызовы отрисовки ниже на Vulkan. [10]
Рисунок 4 – Отчёт для игры, в которой одновременно отображаются десятки элементов пользовательского интерфейса.
Выводы
Существует огромное количество игровых движков, которые предоставляют программисту много различных функций и позволяют создавать качественные игры, однако если она рассчитана и на пользователей со слабым устройством, то необходимо использовать знания языка программирования, архитектуры движка и операционной системы, чтоб добиться удовлетворительных показателей производительности программы. Для создания подобных проектов необходим большой опыт и немалое количество навыков.
В дальнейших исследованиях планируется проверить на практике приведённые выше методы и представить метрики в виде графиков и диаграмм с целью их усовершенствования.
Список источников
- 1. Каким должен быть комфортный FPS в играх [Электронный ресурс] – Режим доступа: https://andpro.ru/blog/pc/kakim-dolzhen-byt-komfortnyy-fps-v-igrakh/
- 2. Почему производительность в играх — это не просто средний FPS [Электронный ресурс] – Режим доступа: https://habr.com/ru/ company/lesta_studio/blog/265487/
- 3. Основы оптимизации кода игр [Электронный ресурс] – Режим доступа: https://habr.com/ru/post/358176/
- 4. Optimizing your Game [Электронный ресурс] – Режим доступа: https://www.reddit.com/r/libgdx/comments /7cw4va/optimizing_your_game/
- 5. The LibGDX performance guide [Электронный ресурс] – Режим доступа: https://yairm210.medium.com/the-libgdx-performance-guide-1d068a84e181
- 6. Документация OpenGL [Электронный ресурс] – https://www.khronos.org/opengl/
- 7. Методы оптимизации под OpenGL [Электронный ресурс] – https://cyberleninka.ru/article/n/metody-optimizatsii-pod-opengl
- 8. Best practices for Android game optimization [Электронный ресурс] – Режим доступа: https://developer.android.com /games/optimize
- 9. libGDX - фреймворк для разработки игр [Электронный ресурс] – Режим доступа: http://www.libgdx.ru/2013 /08/introduction.html
- 10. Vulkan [Электронный ресурс] – Режим доступа: https://www.vulkan.org