С++: проектирование системы сущностей на основе компонентов - передовые проблемы
В моем игровом движке, написанном на С++, я отошел от классической иерархической системы сущностей и создал компонентную систему. Он работает примерно так:
Сущность - это просто контейнер для компонентов. Некоторые примеры компонентов: 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);
Имейте в виду, что это немного нарушает ООП (манипулирование данными обрабатывается вне объекта, поскольку посетитель обработает его). И посетители, как правило, становятся слишком сложными, поэтому это не очень-то хорошее решение.