Я на правильном пути с этой компонентной архитектурой?

9

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

Раньше у меня была иерархия, которая выглядела примерно так:

Item -> Equipment -> Weapon
                  -> Armor
                  -> Accessory
     -> SyntehsisItem
     -> BattleUseItem -> HealingItem
                      -> ThrowingItem -> ThrowsAsAttackItem

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

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

Вот что я сейчас думаю о настройке компонента:

У меня есть базовый класс предметов, в котором есть слоты для различных компонентов (то есть слот для компонента оборудования, слот для компонента лечения и т. Д., А также карта для произвольных компонентов), поэтому что-то вроде этого:

class Item
{
    //Basic item properties (name, ID, etc.) excluded
    EquipmentComponent* equipmentComponent;
    HealingComponent* healingComponent;
    SynthesisComponent* synthesisComponent;
    ThrowComponent* throwComponent;
    boost::unordered_map<std::string, std::pair<bool, ItemComponent*> > AdditionalComponents;
} 

Все компоненты элемента наследуются от базового класса ItemComponent, и каждый тип компонента отвечает за указание движку, как реализовать эту функциональность. т.е. HealingComponent сообщает механике сражения, как использовать предмет как лечебный предмет, в то время как ThrowComponent сообщает движку, как обращаться с предметом как бросаемым предметом.

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

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

Мой вопрос, это хороший дизайн? Если нет, как это можно улучшить? Я подумал о группировании всех компонентов на карте, но использование строковой индексации казалось ненужным для основных компонентов

user127817
источник

Ответы:

8

Это кажется очень разумным первым шагом.

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

В любом случае, я бы предостерег вас от предоставления открытого API для объекта Item, который позволяет вам запрашивать любой компонент - жестко закодированный и дополнительный - единообразно. Это упростит изменение базового представления или баланса жестко закодированных / не закодированных компонентов без необходимости рефакторинга всех клиентов компонентов.

Вы можете также рассмотреть возможность предоставления «фиктивных» неоперативных версий каждого из жестко-закодированных компонентов и гарантировать, что они всегда назначаются - тогда вы можете использовать ссылочные элементы вместо указателей, и вам никогда не понадобится проверять NULL-указатель перед взаимодействием с одним из классов жестко-закодированных компонентов. Вы по-прежнему будете нести расходы на динамическую диспетчеризацию для взаимодействия с элементами этого компонента, но это будет происходить даже с элементами-указателями. Это скорее проблема чистоты кода, поскольку влияние на производительность, по всей вероятности, будет незначительным.

Я не думаю, что это хорошая идея иметь два разных вида жизненных областей (иными словами, я не думаю, что bool, который вы имеете в карте дополнительных компонентов, является отличной идеей). Это усложняет систему и подразумевает, что разрушение и высвобождение ресурсов не будут ужасно детерминированными. API для ваших компонентов будет намного понятнее, если вы выберете одну стратегию управления временем жизни или другую - либо сущность управляет временем жизни компонента, либо подсистема, реализующая компоненты, (я предпочитаю последний, потому что он лучше сочетается с внешним компонентом подход, о котором я расскажу далее).

Большой недостаток, который я вижу в вашем подходе, заключается в том, что вы объединяете все компоненты вместе в объекте «сущность», который на самом деле не всегда лучший дизайн. Из моего связанного ответа на другой компонентный вопрос:

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

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

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

Сообщество
источник
1
+1 за предложение избавиться от предмета. В то время как это будет больше работать заранее, это приведет к созданию лучшей системы компонентов.
Джеймс
У меня было еще несколько вопросов, я не был уверен, стоит ли мне начинать новый topiuc, поэтому сначала попробую здесь: для моего класса элементов нет методов, которые я буду вызывать evert frame (или даже close). Что касается моих графических подсистем, я приму ваш совет и оставлю все объекты для обновления в системе. Другой вопрос, который у меня возник, заключается в том, как мне обрабатывать проверки компонентов? Как, например, я хочу посмотреть, смогу ли я использовать элемент в качестве X, поэтому, естественно, я бы проверил, есть ли у элемента компонент, необходимый для выполнения X. Это правильный способ сделать это?
Еще