Начнем с базового подхода систем-компонентов-сущностей .
Давайте создадим сборки (термин, полученный из этой статьи) просто из информации о типах компонентов . Это выполняется динамически во время выполнения, точно так же, как мы добавляем / удаляем компоненты к объекту один за другим, но давайте просто назовем его более точно, так как он касается только информации о типе.
Затем мы создаем объекты, определяющие сборку для каждого из них. Как только мы создаем сущность, ее сборка является неизменной, что означает, что мы не можем напрямую изменить ее на месте, но все же мы можем получить подпись существующей сущности в локальной копии (вместе с содержимым), внести в нее надлежащие изменения и создать новую сущность из этого
Теперь о ключевой концепции: всякий раз, когда сущность создается, она присваивается объекту, называемому корзиной сборки , что означает, что все сущности с одинаковой сигнатурой будут находиться в одном контейнере (например, в std :: vector).
Теперь системы просто перебирают все интересующие их группы и выполняют свою работу.
Этот подход имеет ряд преимуществ:
- Компоненты хранятся в нескольких (а точнее, в количестве блоков) смежных кусках памяти - это повышает удобство памяти и упрощает вывод всего игрового состояния.
- системы обрабатывают компоненты линейным образом, что означает улучшенную когерентность кеша - пока словари и случайные скачки памяти
- создать новую сущность так же просто, как сопоставить сборку с корзиной и перенести необходимые компоненты в ее вектор
- удалить сущность так же просто, как один вызов std :: move, чтобы поменять последний элемент на удаленный, потому что в данный момент порядок не имеет значения
Если у нас много сущностей с совершенно разными сигнатурами, преимущества когерентности кэша уменьшаются, но я не думаю, что это произойдет в большинстве приложений.
Существует также проблема с недействительностью указателя после перераспределения векторов - это можно решить, введя такую структуру:
struct assemblage_bucket {
struct entity_watcher {
assemblage_bucket* owner;
entity_id real_index_in_vector;
};
std::unordered_map<entity_id, std::vector<entity_watcher*>> subscribers;
//...
};
Поэтому всякий раз, когда по какой-то причине в нашей игровой логике мы хотим отслеживать вновь созданную сущность, внутри корзины мы регистрируем entity_watcher , и как только сущность должна быть std :: move'd во время удаления, мы ищем ее наблюдатели и обновляем их real_index_in_vector
к новым ценностям. В большинстве случаев это требует только одного поиска в словаре для каждого удаления объекта.
Есть ли еще недостатки этого подхода?
Почему решение нигде не упоминается, несмотря на то, что оно довольно очевидно?
РЕДАКТИРОВАТЬ : я редактирую вопрос, чтобы «ответить на ответы», так как комментариев недостаточно.
вы теряете динамическую природу подключаемых компонентов, которая была создана специально для того, чтобы уйти от создания статического класса.
Я не. Возможно я не объяснил это достаточно ясно
auto signature = world.get_signature(entity_id); // this would just return entity_id.bucket_owner->bucket_signature or so
signature.add(foo_component);
signature.remove(bar_component);
world.delete_entity(entity_id); // entity_id would hold information about its bucket owner
world.create_entity(signature); // automatically assigns new entity to an existing or a new bucket
Это так же просто, как просто взять подпись существующей сущности, изменить ее и снова загрузить как новую сущность. Pluggable, динамический характер ? Конечно. Здесь я хотел бы подчеркнуть, что существует только один класс «сборка» и один класс «ведро». Контейнеры управляются данными и создаются во время выполнения в оптимальном количестве.
вам нужно будет пройти через все сегменты, которые могут содержать действительную цель. Без внешней структуры данных обнаружение столкновений может быть столь же трудным.
Вот почему у нас есть вышеупомянутые внешние структуры данных . Обходной путь так же прост, как введение итератора в системный класс, который определяет, когда перейти к следующему сегменту. Прыжки будут чисто прозрачными для логики.