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

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

GameObject

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

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

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

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

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

2 ответа

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

Другие вопросы по тегам