С++: проектирование системы сущностей на основе компонентов - передовые проблемы

В моем игровом движке, написанном на С++, я отошел от классической иерархической системы сущностей и создал компонентную систему. Он работает примерно так:

Сущность - это просто контейнер для компонентов. Некоторые примеры компонентов: Point, Sprite, Physics, Emitter.

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

Итак, все работает отлично с системой компонентов, но теперь мне трудно реализовать более специализированные объекты, например:

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

Теперь я мог бы легко решить это с наследованием. Просто выведите камеру из объекта и добавьте дополнительные функции масштабирования и элементы. Но это просто неправильно.

Мой вопрос:

  • Как я могу решить проблему специализированных объектов с компонентной системой в С++?

Ответ 1

Вы, кажется, сомневаетесь в отношениях IS-A здесь. Так почему бы не сделать отношения HAS-A? Вместо того, чтобы быть сущностью, камера и проигрыватель могут быть объектами, которые имеют объект (или ссылку на объект), но существуют вне вашей компонентной системы. Таким образом, вы можете легко сохранить единообразие и ортогональность вашей системы компонентов.

Это также прекрасно соответствует значению этих двух примеров (камера/плеер) как "клей". Плеер приклеивает систему сущности к системе ввода и действует как контроллер. Камера приклеивает систему сущностей к рендереру и действует как вид наблюдателя.

Ответ 2

Как просто создавать компоненты, которые позволяют это поведение? Например, InputComponent может обрабатывать входные данные от плеера. Тогда ваш дизайн остается тем же, и игрок - это просто объект, который позволяет вводить с клавиатуры, а не вводить с AI-контроллера.

Ответ 3

Компонентная система обычно имеет общий метод, позволяющий отправлять "сообщения" сущностям, например, функции send(string message_type, void* data). Затем объект передает его всем компонентам, и только некоторые из них будут реагировать на него. Например, ваш компонент Point может реагировать на send("move", &direction). Или вы можете ввести компонент moveable, чтобы иметь больше контроля. То же самое для вашей камеры, добавьте компонент view и заставьте его обработать сообщение "увеличить".

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

Конечно, некоторые оптимизации могут понадобиться, особенно для часто используемых сообщений.

Ответ 4

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

Ответ 5

Общим решением является использование шаблона посетителя. В принципе, вы будете "посещаться" вашей сущностью классом посетителя. Внутри вашей сущности у вас будет:

void onVisitTime(Visitor* v)
{
   // for each myComponent...
   v->visit(myComponent);
   // end for each
}

И тогда у вас будет класс Visitor:

void visit(PointComponent* p);
void visit(CameraComponent* c);

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