Советы по обработке сообщений на основе компонентов Entity System


8

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

Класс Entity:

class Entity{
public:
    Entity(unsigned int id):
    id_(id)
    {};
    void handleMessage(BaseMessage &message){
        for(auto element: components_){
            element.second->handleMessage(message);
        }
    }
    template<class T>
    void attachComponent(T *component){
        //Consider making safer in case someone tries to attach same component type twice
        components_[typeid(T).hash_code()] = component;
    }
    template<class T>
    void detachComponent(void){
        components_.erase(typeid(T).hash_code());
    }
    template<class T>
    T* getComponent(void)const{
        return *components_.find(typeid(T).hash_code());
    }
    unsigned int getInstanceID(void)const{
        return id_;
    }
private:
    unsigned int id_;
    std::map<size_t, BaseComponent*> components_;
};

Классы базового компонента и сообщения:

class BaseComponent{
public:
    virtual void handleMessage(BaseMessage &message){};
};

class BaseMessage{
public:
    virtual int getType(void) = 0;
};

1. Обработка типа сообщения

Мой первый вопрос заключается в том, как я должен обрабатывать различные (полученные из BaseMessage) типы сообщений.

Я подумал о двух способах обработки типов сообщений из производных типов сообщений. Один из них состоит в том, чтобы сгенерировать хеш (то есть, используя FNV) из строки, в которой указан тип сообщения, и использовать этот хеш для определения типа сообщения. Таким образом, handleMessage(BaseMessage &message)функция сначала извлечет этот хеш из сообщения, а затем сделает static_cast для соответствующего типа.

Второй метод заключается в использовании шаблона следующим образом (аналогично attachComponentметодам класса сущности),

template<class T>
handleMessage(T& message){};

и сделайте специализации для каждого типа сообщения, который собирается сделать определенный компонент.

Есть ли недостатки при использовании второго метода? Что касается производительности, почему я не вижу такого рода использование чаще?

2. Обработка ввода

Мой второй вопрос: каков оптимальный (с точки зрения времени ожидания и простоты использования) способ обработки ввода?

Я хотел создать InputHandlerComponentрегистр с классом клавиатуры для прослушивания определенных нажатий клавиш, определенных, возможно, в каком-то файле. Например

keyboard.register( player.getComponent<InputHandler>() , 'W')

Хотелось бы, чтобы было более краткое руководство по компонентным системам, но я думаю, что есть много разных способов сделать то же самое. У меня есть еще вопросы, но я думаю, что было бы разумнее сначала попытаться реализовать то, что я могу.

Ответы:


3

В типичной системе сущностей вы оставляете системы, чтобы иметь всю логику для вашей игры и компоненты для хранения только данных , а ваши сущности - это просто простой идентификатор. Обработка ввода будет намного проще, не говоря уже о гибкости вашей игры.

Существует множество способов обработки событий, таких как: шаблон наблюдателя или сигналы / слоты. Вы можете выполнять обработку сообщений с помощью шаблонов / виртуальных функций или указателей на функции, однако, вероятно, будет проще использовать boost :: сигналы , даже если известно, что это не так уж хорошо для производительности. Если вы хотите производительность *, я предлагаю вам использовать шаблоны и виртуальные функции или указатели функций.

* Я заметил, что ваш код действительно может быть оптимизирован. Есть альтернативы typeidоператору, которые на самом деле быстрее, чем использовать typeid. Например, использование шаблонов / макросов и простых целых чисел для определения идентификатора класса.

Вы можете взглянуть на мою Entity System, если вам нужно немного вдохновения (которое вдохновлено фреймворком Java Artemis ). Хотя я не реализовал способ обработки событий (кроме событий, связанных с сущностями ), я оставил это на усмотрение пользователя, но после выяснения библиотеки entityx , которую я нашел в reddit. Я подумал, что смогу добавить систему событий в мою библиотеку. Обратите внимание, что моя система сущностей не на 100% готова к тому, какие функции я хочу, но она работает и имеет приличную производительность (но я мог бы оптимизировать, особенно с тем, как я храню сущности).

Вот что вы могли бы сделать для обработки событий (по мотивам entityx ):

BaseReceiver / BaseListener

  • Базовый класс, чтобы вы могли хранить слушателей / получателей.

Приемник / Слушатель

  • Это шаблонный класс, который требует переопределения виртуальной функции recieve(const T&), где T - информация о событии.
  • Классы, которые хотят получать уведомления от событий, отправляющих конкретную информацию, когда происходит событие, должны наследоваться от этого класса.

Обработчик события

  • Излучает / зажигает события
  • Имеет список объектов Receivers / Listeners, которые будут уведомлены о событиях

Я реализовал этот дизайн в C ++, только сейчас, без использования boost :: сигналов . Вы можете увидеть это здесь .

Pros

  • Статически типизированный
  • Виртуальные функции (быстрее, чем boost :: сигналов, ну, это должно быть)
  • Ошибки во время компиляции, если вы не правильно отправляли уведомления
  • C ++ 98 совместимый (я считаю)

Cons

  • Требует определения функторов (вы не можете обрабатывать события в глобальной функции)
  • Нет очереди событий; просто зарегистрируйтесь и начните огонь. (что может быть не то, что вы хотите)
  • Не прямые звонки (не должно быть так плохо для событий)
  • Отсутствует обработка нескольких событий в одном и том же классе (это можно изменить, чтобы разрешить несколько событий через множественное наследование, но реализация может занять некоторое время)

Кроме того, у меня есть пример в моей системе сущностей, который имеет дело с рендерингом и обработкой ввода, он довольно прост, но в нем представлено много концепций для понимания моей библиотеки.


Можете ли вы привести пример более быстрого способа обнаружения типов без typeid?
Люк Б.

«В типичной системе сущностей вы оставляете системы, чтобы иметь всю логику для вашей игры и компоненты для хранения только данных», - нет такой вещи, как «типичная» система сущностей.
Ден

@LukeB. Посмотрите на исходный код моей системы сущностей, я не использую typeid(посмотрите на Component и ComponentContainer). По сути, я храню «тип» компонента как целое число, у меня есть глобальное целое число, которое я увеличиваю для каждого типа компонента. И я храню компоненты в 2d массиве, где первый индекс - это идентификатор объекта, а 2-й - это идентификатор типа компонента, то есть компоненты [entityId] [componentTypeId]
miguel.martin

@ miguel.martin. Спасибо за ваш ответ. Я думаю, что мое замешательство происходит главным образом из-за того, что существует несколько способов реализации системы сущностей, и для меня у всех есть свои недостатки и преимущества. Например, в подходе, который вы используете, вы должны регистрировать новые типы компонентов, используя CRTP, что мне лично не нравится, поскольку это требование не является явным. Я думаю, что мне придется пересмотреть мою реализацию.
Grieverheart

1
@MarsonMao исправлено
miguel.martin

1

Я работаю над компонентной системой сущностей в C #, но общие идеи и шаблоны все еще применимы.

Способ обработки типов сообщений заключается в том, что каждый подкласс компонента вызывает защищенный RequestMessage<T>(Action<T> action) where T : IMessageметод компонента . На английском языке это означает, что подкласс компонента запрашивает определенный тип сообщения и предоставляет метод, который вызывается, когда компонент получает сообщение этого типа.

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

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

Что касается ввода, я решил сделать что-то совершенно другое, чем вы думаете. Обработчик ввода отдельно преобразует ввод с клавиатуры / мыши / геймпада в перечисление флагов (битовое поле) возможных действий (MoveForward, MoveBackwards, StrafeLeft, Use и т. Д.) В зависимости от настроек пользователя / того, что проигрыватель подключил к компьютеру. Каждый компонент получает UserInputMessageкаждый кадр, который содержит как битовое поле, так и ось взгляда игрока в виде трехмерного вектора (направление развития в сторону шутера от первого лица). Это также позволяет игрокам менять привязки клавиш.

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.