• Как организовать Tween систему в рамках ECS (entt)?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Одним компонентом и одной системой в таком случае не обойтись. Когда затронуто произвольное количество иных компонентов, связывать их все с каким-то одним компонентом - это зарывать ECS обратно в землю.

    Почему дизайн с одним компонентом обречен. Каждая система в ECS должна работать с выборкой по компонентам. В EnTT это делается с помощью представления потока компонентов - entt::registry::view.
    Такое представление оптимальным образом организует последовательность сущностей, в которых точно присутствуют обозначенные в представлении компоненты. Если начать проверять наличие у сущности иных компонент, помимо того что это противоречит концепции ECS, это затронет выборку из памяти за пределами отображения и сильно снизит производительность системы. Поэтому системе не стоит работать с компонентами за пределами своей выборки.

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

    Как быть в таком случае. Нужно выделять функциональность в более локальные системы вместо одной большой системы интерполяции. Вот есть у тебя компонент цвета сущности - ColorComponent, и этот цвет у тебя может интерполироваться. Значит тебе нужен компонент интерполируемости цвета - ColorInterpolationComponent. В этом компоненте интерполируемости нужно определить закон интерполяции, начальное и конечное значение, а так же время интерполяции. ColorInterpolationComponent говорит о том, что все данные ColorComponent интерполируются по определенному закону между определенными значениями и за определенное время.

    Функции интерполяции у тебя всегда чистые, т.е. зависят только от переданных им аргументов. Для любого типа данных, от скаляра и до матрицы, можно определить функцию интерполяции между двумя значениями. Это все - внешние относительно ECS вещи, а внутри ECS на каждый ***InterpolationComponent у тебя будет своя система, которая делает выборку по целевому компоненту и по компоненту интерполируемости. Таким образом, уже на стадии выборки компонентов у тебя останутся только подходящие сущности, а все остальные - отфильтруются.
    Заметить стоит еще то, что такой подход позволяет одно конкретное значение интерполировать только одной функцией за раз. Если для одного значения требуется несколько одновременных интерполяций, такой подход уже не подходит. С другой стороны, для того чтобы правильно свести несколько таких анимаций для одного значения, требуется значительно более сложная система, чем просто функция интерполяции.
    Ответ написан
    2 комментария
  • Variadic template, как перебрать в шаблоне все типы по очереди?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Пусть у нас есть некий аксессор:
    template< typename TComponent >
    TComponent* const QueryComponent();


    Пусть у нас есть, для простоты, некоторый обработчик:
    void PerformWorkflow( A*, B*, C* );
    Тут A, B и C - это типы компонентов.

    Чтобы подружить обработчик с аксессором необходимых ему компонентов, понадобится самый простой Parameter pack expansion.
    template< typename... TComponents >
    void ApplyComponents( void (*workflow)( TComponents*... ) )
    {
    	workflow( QueryComponent<TComponents>()... );
    }

    Здесь можно посмотреть как работает пример этого кода.

    В комментарии я вижу несоответствие типа параметра функции для each и типа результата get. Стоит напомнить что разыменование nullptr, который может вернуть get, приводит к неопределенному поведению [?].
    Поэтому, если распаковку переписать на *QueryComponent<TComponents>()..., т.е. на лету разыменовывать полученные указатели, то перед запуском функции для твоей entity следует убедиться что у нее есть все необходимые компоненты. Такая проверка является необходимой для запуска систем в ECS, т.к. система должна обрабатывать только удовлетворяющие своим фильтрам сущности.

    Для этого уже потребуется использовать Fold expression из C++17.
    template <typename... T>
    void each(typename identity<std::function<void(T& ...)>>::type func)
    {
    	for(auto &entity: m_entities)
    	{
    		if( !( (entity.HasComponent<T>() && ...) ) )
    		{
    			continue;
    		}
    		
    		func( *entity.get<T>()... );
    	}
    }
    Ответ написан
    1 комментарий