Entity Component System: где поставить логику рендеринга
В настоящее время я изучаю "Компонентную систему сущностей". После прочтения многих учебных пособий и веток форума я все еще задаюсь вопросом, куда должна идти логика рендеринга. Я не говорю о реальном коде рендеринга OpenGL/DirectX, который, например, берет спрайт и отображает его. Я имею в виду логику, которая решает, какой спрайт визуализировать.
Видимая сущность требует трех аспектов:
- Оценка ИИ (изменение позиции, состояния, ...)
- Оценка состояния рендеринга. Например, какой цикл спрайтов использовать, когда объект идет, поднимается, получает удар, ...
- На самом деле рендеринг спрайта
В большинстве уроков предлагается использовать что-то вроде AISystem (1.) для логики и RenderSystem (3.) для отображения спрайта (цикла), определенного в RenderComponent. То, что они не говорят, - то, где обновляется RenderComponent. Насколько я понимаю, просто поместить (2.) в (1.), смешивая логику символов с логикой рендеринга, было бы плохим дизайном.
Прямым решением было бы добавить определенную логическую систему рендеринга для каждого врага. Так, например, для Gumba я мог бы добавить GumbaLogicSystem, GumbaRenderLogicSystem и, для фактического рендеринга, универсальную SpriteRenderSystem, которую используют все сущности, основанные на спрайтах. Однако это означает создание двух компонентов * и двух систем для каждого типа сущности, что не кажется хорошим решением.
Есть ли хорошее решение, которое разделяет логику символов и логику рендеринга, сохраняя при этом небольшое количество систем?
Спасибо
(* = в простом подходе система обрабатывает сущность в зависимости от ее компонента. Чтобы система GumbaRenderLogicSystem работала над сущностью, ей требуется GumbaRenderingLogicComponent для распознавания этой системой. То же самое верно для символьной логики.)
Edit1: я знаю, что ECS является абстрактным паттерном. Мой вопрос направлен на передовой опыт о том, как сохранить количество систем небольшим. Мой пример основан на программировании игр, но не ограничивается этой областью. Позвольте мне объяснить более абстрактный пример:
Представь, что у меня есть какая-то сущность, которая видна. В иерархической архитектуре у меня было бы что-то вроде:
- SomeModel (наследуется от AbstractModel)
- SomeController (наследуется от AbstractController)
- SomeRenderer (наследует от PixelRenderer, который, в свою очередь, наследует от AbstractRenderer).
В ECS мне нужна целая куча новых классов:
- SomeModelSpecificDataComponent (то есть данные, которые являются определенными для этой семантической сущности)
- SomeModelSystem (это логика)
- SomeModelSpecificRenderComponent (т. Е. Рендеринг данных, относящихся к этой семантической сущности)
- SomeModelSpecificRendererLogicSystem (система, которая решает, как визуализировать)
- PixelRendererSystem
Можно ли как-то уменьшить количество новых систем, которые необходимо внедрить? Одним из решений может быть добавление "агентов" или "объектов поведения": к общему RenderingComponent прикрепляется экземпляр некоторого класса "RenderingAgent", который имеет единственный метод "Update", который вызывается в RenderSystem. Технически, компонент не содержит саму логику. Я боюсь, что это может быть слишком сильным, хотя.
2 ответа
Подумав об этом некоторое время и много обсуждений, я понял, что мой образ мышления мог быть неправильным. То, что я описал, на самом деле было бы верно для ванильного подхода ECS.
Единственный способ предотвратить взрыв систем и компонентов - создать правильную абстракцию игровых элементов, чтобы можно было описывать различные требования рендеринга, а не программировать.
Я имею в виду, что, например, механизм спрайтов должен обеспечивать достаточную абстракцию, чтобы вы могли выражать различные анимации и состояния только с помощью описаний, которые хранятся в компоненте рендеринга, который оценивается системой рендеринга. Что вам также нужно сделать, это правильно разделить ваше решение на части многократного использования.
Таким образом, ECS заставляет вас больше, чем другие шаблоны, задумываться о хорошей архитектуре.
Entity Component System - это шаблон, то есть способ решения проблем сложности в игровом коде. Будучи "шаблоном", он только описывает подход и остается довольно абстрактным. Отсюда нет явного упоминания.
Итак, давайте рассмотрим использование обычного цикла, когда вы явно вызываете этапы один за другим.
while(true)
{
callAI(); // AI entities
checkCollisions(); // some physics here
updatePositions(); // movable objects
render(); // visual elements.
}
С ECS у вас будет два цикла, внешний для каждой подсистемы, внутренний - для каждого подходящего компонента.
var types = {'ai', 'mechanic', 'visual'};
while(true)
{
for(var t in types)
{
var handler = getHandlerForType(t);
for(var e in entities)
{
var c = e.getComponents(t); // receive e's components of required type
// So this would handle ai, mechanics or visual component respectfully.
handler.handle(c);
}
}
}
(и эти циклы, хотя и иллюстрируют общий подход, далеки от идеальной реализации. В действительности вы, вероятно, извлекаете все компоненты и затем обрабатываете их пакетами).
Таким образом, у вас больше нет явных этапов цикла. Вместо этого рендеринг превращается в одну из "подсистем", которая обрабатывает только те компоненты, которые способны визуализировать себя. Теоретически вы можете иметь эту часть где угодно, но, возможно, было бы более разумно иметь ее в конце этого конвейера.