Я разрабатываю свой собственный игровой движок на основе ECS.
Это структура ECS, которую я использую:
но я не понимаю, как системы должны обновлять элементы компонентов и информировать другие системы?
Например, у меня есть TransformComponent, как это:
struct TransformComponent
{
Vec3 m_Position;
Float m_fScale;
Quaternion m_Quaternion;
};
Очевидно, что если какой-либо элемент TransformComponent визуализируемой сущности изменяется, RenderSystem также должна обновить унифицированную форму шейдера «worldMatrix» перед рендерингом следующего кадра.
Так что, если я делаю «comp-> m_Position = …» в системе, как RenderSystem должна «заметить» изменение TransformComponent? Я придумал 3 решения:
Отправьте UpdateEvent после обновления участников и обработайте событие в связанной Системе. Это ужасно, потому что, как только система модифицирует данные компонента, она должна отправить событие, подобное этому:
{
...;
TransformComponent* comp = componentManager.GetComponent<TransformComponent>(entityId);
comp->m_Position = ...;
comp->m_Quaternion = ...;
eventDispatcher.Send<TransformUpdateEvent>(...);
...;
}
Сделайте члены частными, и для каждого класса компонентов напишите соответствующую систему с методами set / get (перенос события в методы set). Это принесет много громоздких кодов.
Ничего не меняйте, но добавьте компонент «Подвижный». RenderSystem будет итеративно выполнять обновление для визуализируемых объектов с помощью компонента «Movable» в методе Update (). Это может не решить другие подобные проблемы, и я не уверен в производительности.
Я не могу придумать элегантный способ решить эту проблему. Должен ли я изменить свой дизайн?
Я думаю, что в этом случае самый простой способ будет лучшим: вы можете просто сохранить указатель на Transform
компонент в компонентах, которые читают / пишут это.
Я не думаю, что использование событий (или некоторой другой косвенности, например наблюдателей) решает любую реальную проблему здесь.
Transform
Компонент очень прост — это не то, что будет изменено в процессе разработки. Абстрагирование доступа к нему на самом деле сделает код более сложным и сложным в обслуживании.
Transform
это компонент, который будет часто меняться для многих объектов, возможно, даже большинство ваших объектов будет обновлять его каждый кадр. Отправка событий каждый раз, когда происходит изменение, имеет определенную стоимость — вероятно, намного выше, чем простое копирование матрицы / вектора / кватерниона из одного места в другое.
Я думаю, что использование событий или какой-либо другой абстракции не решит другие проблемы, например, обновление нескольких компонентов одним и тем же Transform
компонент или компоненты, использующие устаревшие данные преобразования.
Как правило, средства визуализации просто копируют все матрицы отображаемых объектов в каждом кадре. Нет смысла кэшировать их в системе рендеринга.
Компоненты как Transform
часто используются. Их чрезмерная сложность может быть проблемой во многих различных частях движка, тогда как использование простейшего решения, указателя, даст вам большую свободу.
Кстати, есть также очень простой способ убедиться, что RenderComponent
будет читать преобразование после оно было обновлено (например, PhysicsComponent
) — вы можете разделить работу на два этапа:
Update()
в которых системы могут модифицировать компоненты, и
PostUpdate()
в которых системы могут только читать данные из компонентов
Например PhysicsSystem::Update()
может скопировать данные преобразования в соответствующие TransformComponent
компоненты, а затем RenderSystem::PostUpdate()
мог просто прочитать с TransformComponent
, без риска использования устаревших данных.
Я думаю, что есть много вещей, чтобы рассмотреть здесь. Я пойду по частям, обсуждая сначала ваши решения.
О вашем решении 1. Учтите, что вы можете сделать то же самое с логическим значением или назначить пустой компонент, действующий как тег. Часто использование событий в ECS усложняет архитектуру вашей системы. По крайней мере, я стараюсь избегать этого, особенно в небольших проектах. Помните, что компонент, выступающий в роли тега, может рассматриваться как основное событие.
Ваше решение 2 следует тому, что мы обсуждали в 1. Но оно обнаруживает проблему в этом общем подходе. Если вы обновляете ваш TransformComponent в нескольких системах, вы не можете знать, действительно ли TransformComponent изменился, пока последняя система не обновила его, потому что одна система могла переместить его в одном направлении, а другая могла переместить его назад, позволяя как в начале вашего тика. Вы можете решить эту проблему, обновив свой TransformComponent только один раз, в одной системе …
Что похоже на ваше решение 3. Но, может быть, наоборот. Вы можете обновлять MovableComponent в нескольких системах, а затем в своем конвейере ECS, иметь единую систему, считывая ваш MovableComponent и записывая в ваш TransformComponent. В этом случае важно, чтобы в TransformComponents разрешалось писать только одной Системе. В то время, имея логическое значение, указывающее, был ли он перемещен или нет, отлично справился бы с этой задачей.
До этого момента мы торговали производительностью (потому что мы избегаем некоторой обработки в RenderSystem, когда TransformComponent не изменился) для памяти (потому что мы каким-то образом дублируем содержимое TransformComponent.
// In your RenderSystem... if (renderComponent.lastTransformUpdate == transformComponent) { continue; } renderComponent.lastTransformUpdate = transformComponent; render(renderComponent);
Это последнее, будет моим предпочтительным решением. Но это также зависит от характеристик вашей системы и ваших проблем. Как всегда, не пытайтесь слепо выбирать производительность. Сначала измерить, а потом сравнить.