Преимущества и недостатки массивов и карт для компонентных игровых объектов

Моя абстрактная реализация для GameObject для моего игрового движка на основе компонентов это следующее:

GameObject

  • Уникальный идентификатор
  • isActive флаг
  • Массив компонентов

Я читал о проектировании на основе компонентов, и одна вещь, которая привлекла мое внимание, заключается в том, что линейный обход массивов происходит быстро благодаря кешу, поэтому я решил использовать базовые массивы для хранения сущностей / игровых объектов.

Еще одна вещь, которая привлекла мое внимание, это то, что некоторые ресурсы отделяли компоненты от игрового объекта. Вместо этого у них есть карта внутри менеджера сущностей. Карта имела игровые объекты в качестве ключей и массив компонентов в качестве значений. В этой ситуации А GameObject имел только удостоверение личности.

Есть ли преимущество (с точки зрения производительности и / или дизайна) наличия карты, прикрепляющей игровые объекты к компонентам вместо наличия компонентов внутри GameObject учебный класс?

К тому же, GameObjectS собираются в пул (переработанный) из Pool объект, чтобы избежать частого выделения памяти.

1

Решение

В большинстве игровых компонентов вам понадобятся массивы и словари (карты).

Для линейного обхода (т.е. при обновлении) массив используется для перечисления объектов и компонентов.

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

Кроме того, могут быть дополнительные массивы, хранящие игровые объекты, отсортированные по компонентам, например, массив игровых объектов, имеющих определенный компонент, такой как CombatComponent.

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

Короче: всегда есть компромиссы. Начать использовать дополнительные словари и массивы — это в основном вопрос удобства и производительности, но это никогда не решение.

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

3

Другие решения

Это также зависит от типа вашей игры.

По моему опыту, слабо связанные объекты почти всегда работают лучше, поскольку выделение памяти происходит очень редко, тогда как ссылки на объекты обычно происходят в пакетах и ​​для конкретных задач. Мне понравился этот подход, потому что он не позволяет вам связываться с множеством виртуальных таблиц и иерархий классов. Виртуальные указатели нарушают кеш кода. Так что в этом случае я держу объекты вместе в разных менеджерах, но с одинаковым дескриптором / идентификатором, и просто перебираю соответствующий набор.

Если требуются дорогостоящие запросы видимости, зачастую дешевле держать под рукой несколько списков и обновлять их только при необходимости (например, с разделением по времени)

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

Если вы можете векторизовать свои расчеты и получить 100-кратное ускорение, это хороший повод взглянуть на выравнивания и упаковку. Когда-то мы использовали кеш небольшого объекта, чтобы выровнять в памяти только те биты, которые часто требовались, такие как преобразования и т. Д. В реальности, хотя в конечном итоге вы получаете так много вызовов isXYZ (), прежде чем даже доберетесь до трансформация, что это больше не окупается. Идея была хорошей, и в то время было некоторое ускорение, но логика запуталась до такой степени, что стала бессмысленной.

Правильно сказать, конечно, что это зависит от задач и количества объектов или их подмножеств, с которыми вам приходится иметь дело. Если вы не знаете этого из первых рук, но подозреваете, что это может выйти из-под контроля, это зависит от времени, которое у вас есть: если много, то слабосвязанная может быть просто вещь. Дополнительным бонусом является то, что типы рефакторинга не сильно повлияют на вас. В противном случае, вам может быть лучше просто хранить все вместе и надеяться на лучшее ..

1