Как обновить данные компонентов и информировать системы в рамках ECS? - PullRequest
0 голосов
/ 23 октября 2018

Я занимаюсь разработкой собственного игрового движка на основе ECS.Это структура ECS, которую я использую:

  • Сущность: просто идентификатор, который связывает его компоненты.
  • Компонент: структура, которая хранит чистые данные, никаких методов вообще (поэтому я могунаписать .xsd для описания компонентов и автоматического создания структурного кода C ++).
  • Система: обрабатывать игровую логику.
  • EventDispacher: отправлять события подписчикам (системы)

но я не понимаю, как системы должны обновлять элементы компонентов и информировать другие системы?Например, у меня есть TransformComponent, подобный этому:

struct TransformComponent
{
    Vec3 m_Position;
    Float m_fScale;
    Quaternion m_Quaternion;
};

Очевидно, что если какой-либо элемент TransformComponent визуализируемой сущности изменяется, RenderSystem также должна обновить унифицированную форму шейдера "worldMatrix", прежде чем рендерить следующий кадр.,Так что, если я делаю "comp-> m_Position = ..." в системе, как RenderSystem должна "заметить" изменение TransformComponent?Я предложил 3 решения:

  1. Отправить UpdateEvent после обновления участников и обработать событие в связанной системе.Это уродливо, потому что, как только система модифицирует данные компонента, она должна отправить событие, подобное этому:

    {
        ...;
        TransformComponent* comp = componentManager.GetComponent<TransformComponent>(entityId);
        comp->m_Position = ...;
        comp->m_Quaternion = ...;
        eventDispatcher.Send<TransformUpdateEvent>(...);
        ...;
    }
    
  2. Сделать члены частными, и для каждого класса компонента напишите соответствующую систему сМетоды set / get (отправка события в методах set).Это принесет много громоздких кодов.

  3. Не меняйте ничего, а добавляйте компонент «Подвижный».RenderSystem будет итеративно выполнять обновление для визуализируемых объектов с помощью компонента «Movable» в методе Update ().Это может не решить другие подобные проблемы, и я не уверен в производительности.

Я не могу придумать элегантный способ решить эту проблему.Должен ли я изменить свой дизайн?

Ответы [ 2 ]

0 голосов
/ 24 октября 2018

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

Я не думаю, чтоиспользование событий (или некоторой другой косвенности, например наблюдателей) решает любую реальную проблему здесь.

  1. Transform компонент очень прост - это не то, что будет изменено во время разработки.Абстрагирование доступа к нему на самом деле сделает код более сложным и сложным в обслуживании.

  2. Transform - это компонент, который будет часто изменяться для многих объектов, возможно, даже большинство ваших объектов будет обновлятьсяэто каждый кадр.Отправка событий каждый раз, когда есть изменение, имеет свою стоимость - вероятно, намного выше, чем простое копирование матрицы / вектора / кватерниона из одного места в другое.

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

  4. Как правило, средства визуализации просто копируют все матрицы отображаемых объектов в каждом кадре,Нет смысла кэшировать их в системе рендеринга.

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


Кстати, есть также очень простой способ убедиться, чточто RenderComponent будет читать преобразование после того, как было обновлено (например, PhysicsComponent) - вы можете разделить работу на два шага:

  1. Update() inкакие системы могут изменять компоненты, и

  2. PostUpdate(), в которых системы могут считывать данные только из компонентов

Например, PhysicsSystem::Update() может копироватьпреобразовать данные в соответствующие TransformComponent компоненты, и тогда RenderSystem::PostUpdate() сможет просто прочитать из TransformComponent, без риска использования устаревших данных.

0 голосов
/ 23 октября 2018

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

  1. О вашем решении 1. Учтите, что вы можете сделать то же самое с логическим значением или назначить пустой компонент, действующий как тег.Часто использование событий в ECS усложняет архитектуру вашей системы.По крайней мере, я стараюсь избегать этого, особенно в небольших проектах.Помните, что компонент, выступающий в качестве тега, может рассматриваться как основное событие.

  2. Ваше решение 2 следует тому, что мы обсуждали в 1. Но оно обнаруживает проблему в этом общемподход.Если вы обновляете ваш TransformComponent в нескольких системах, вы не можете знать, действительно ли TransformComponent изменился, пока последняя система не обновила его, потому что одна система могла переместить его в одном направлении, а другая могла бы переместить его назад, позволяя емукак в начале вашего тика.Вы можете решить эту проблему, обновив свой TransformComponent только один раз, в одной Системе ...

  3. Это похоже на ваше решение 3. Но, может быть, наоборот.Вы можете обновлять MovableComponent в нескольких системах, а затем в своем конвейере ECS, иметь единую систему, считывая ваш MovableComponent и записывая в ваш TransformComponent.В этом случае важно, чтобы в TransformComponents разрешалось писать только одной Системе.В то время наличие логического значения, указывающего, было ли оно перемещено или нет, прекрасно сработало бы.

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

  • Другой способ сделать то же самое, не добавляя события, логические значения или компоненты, делаетвсе в RenderSystem. По сути, в каждом RenderComponent, вы можете сохранить копию (или хеш) вашего TransformComponent с момента последнего обновления, а затем сравнить его. Если это не то же самое, отрендерить его и обновить свойкопия.
// In your RenderSystem...

if (renderComponent.lastTransformUpdate == transformComponent) {
    continue;
}
renderComponent.lastTransformUpdate = transformComponent;
render(renderComponent);

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

...