«Оптимальная» игровая петля для 2D бокового скроллера


14

Можно ли описать «оптимальный» (с точки зрения производительности) макет для игрового цикла 2D бокового скроллера? В этом контексте «игровой цикл» принимает вводимые пользователем данные, обновляет состояния игровых объектов и рисует игровые объекты.

Например, наличие базового класса GameObject с глубокой иерархией наследования может быть полезным для обслуживания ... вы можете сделать что-то вроде следующего:

foreach(GameObject g in gameObjects) g.update();

Однако я думаю, что такой подход может создать проблемы с производительностью.

С другой стороны, данные и функции всех игровых объектов могут быть глобальными. Это было бы головной болью при обслуживании, но могло бы быть ближе к оптимально работающему игровому циклу.

Есть предположения? Мне интересны практические применения почти оптимальной структуры игрового цикла ... даже если я получаю головную боль от обслуживания в обмен на отличную производительность.


Ответы:


45

Например, наличие базового класса GameObject с глубокой иерархией наследования может быть полезным для обслуживания ...

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

Однако я думаю, что такой подход может создать проблемы с производительностью. С другой стороны, данные и функции всех игровых объектов могут быть глобальными. Это было бы головной болью при обслуживании, но могло бы быть ближе к оптимально выполненному игровому циклу.

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

Это может увеличить нагрузку на систему, поскольку вам может понадобиться перенести в память соответствующую функцию в память и из нее и чаще заполнять кэш команд, что приведет к более медленному циклу. То, что это можно наблюдать невооруженным глазом (или даже в профилировщике), зависит от того, что именно считается «игровым объектом», сколько из них существует в среднем и что еще происходит в вашем приложении.

Компонентно-ориентированные объектные системы в настоящее время являются популярной тенденцией, используя философию, согласно которой агрегация предпочтительнее наследования . Такие системы потенциально позволяют разделить логику «обновления» компонентов (где «компонент» грубо определяется как некоторая единица функциональности, например, вещь, представляющая физически смоделированную часть некоторого объекта, которая обрабатывается физической системой). ) к нескольким потокам - различаются по типу компонента - если это возможно и желательно, что может привести к увеличению производительности. По крайней мере, вы можете организовать компоненты так, чтобы все компоненты данного типа обновлялись вместе , оптимально используя кэш процессора. Пример такой компонентно-ориентированной системы обсуждается в этой теме .

Такие системы часто сильно разобщены, что также является благом для обслуживания.

Ориентированное на данные проектирование - это связанный подход - он ориентируется на данные, требуемые от объектов, в качестве первоочередной задачи, чтобы эти данные могли эффективно обрабатываться в больших объемах (например). Как правило, это означает организацию, которая пытается объединить данные, используемые для одного и того же целевого кластера, и использовать их одновременно. Это не является принципиально несовместимым с дизайном ОО, и вы можете найти некоторые разговоры по этому вопросу здесь, в GDSE в этом вопросе .

По сути, более оптимальный подход к игровому циклу был бы вместо вашего оригинального

foreach(GameObject g in gameObjects) g.update();

что-то вроде

ProcessUserInput();
UpdatePhysicsForAllObjects();
UpdateScriptsForAllObjects();
UpdateRenderDataForAllObjects();
RenderEverything();

В таком мире, каждый GameObjectможет иметь указатель или ссылку на свой собственный PhysicsDataили Scriptили RenderData, за исключением случаев , когда вам может понадобиться , чтобы взаимодействовать с объектами на индивидуальной основе, но фактическая PhysicsData, Scripts, RenderDataи так далее были бы все собственностью их соответствующих подсистем (физический симулятор, среда размещения скриптов, рендерер) и обрабатываются навалом, как указано выше.

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

К сожалению, нет такого волшебного цикла, который является наиболее оптимальным - каждая игра отличается и может потребовать настройки производительности по-разному. Поэтому очень важно измерить (профилировать) вещи, прежде чем слепо следовать совету какого-то случайного парня в Интернете.


5
Господи, это отличный ответ.
Raveline

2

Стиль программирования игровых объектов подходит для небольших проектов. По мере того, как проект становится действительно огромным, вы получите глубокую иерархию наследования, а база игровых объектов станет очень тяжелой!

В качестве примера ... Предположим, вы начали создавать базу игровых объектов с минимальными атрибутами, например, Position (для x и y), displayObject (это относится к изображению, если оно является элементом рисования), ширина, высота и т. Д.

Теперь позже, если нам нужно добавить подкласс, который будет иметь состояние анимации, то вы, вероятно, переместите «управляющий код анимации» (скажем, currentAnimationId, количество кадров для этой анимации и т. Д.) В GameObjectBase, полагая, что большинство объекты будут иметь анимацию.
Позже, если вы хотите добавить твердое тело, которое является статичным (не имеет никакой анимации), вам нужно проверить «код управления анимацией» в функции обновления GameObject Base (хотя это проверка, есть ли анимация или нет). ..это имеет значение!) В противоположность этому, если вы будете следовать Компонентной структуре, к вашему объекту будет прикреплен «компонент управления анимацией». Если это необходимо, вы присоедините его. Для статического тела вы не будете прикреплять этот компонент. Кстати, мы можем избежать ненужных проверок.

Таким образом, выбор стиля зависит от размера проекта. Структуру GameObject легко освоить только для небольших проектов. Надеюсь, это немного поможет.


@ Джош Петри - действительно отличный ответ!
Айяппа

@Josh Petrie - действительно хороший ответ с полезными ссылками! (Sry, не знаю, как разместить этот комментарий рядом с вашим постом: P)
Ayyappa

Обычно вы можете щелкнуть ссылку «Добавить комментарий» под моим ответом, но для этого требуется больше репутации, чем у вас (см. Gamedev.stackexchange.com/privileges/comment ). И спасибо!
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.