Consejos para el manejo de mensajes del sistema de entidad basado en componentes

8

Estoy tratando de implementar un sistema de entidad basado en componentes, pero estoy un poco confundido sobre cómo debo manejar los mensajes. Hay dos problemas que me gustaría resolver para poder probar el sistema. A continuación se muestra el código que tengo hasta ahora,

La clase de entidad:

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_;
};

El componente base y las clases de mensaje:

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

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

1. Manejo de tipo de mensaje

Mi primera pregunta es cómo debo manejar los diferentes tipos de mensajes (derivados de BaseMessage).

He pensado en dos formas de manejar los tipos de mensajes de los tipos de mensajes derivados. Una es generar un hash (es decir, usar FNV) a partir de una cadena que nombra el tipo de mensaje y usar ese hash para determinar el tipo de mensaje. Entonces, la handleMessage(BaseMessage &message)función, primero extraería este hash del mensaje y luego haría un static_cast al tipo apropiado.

El segundo método es usar una plantilla de la siguiente manera (similar a los attachComponentmétodos de la clase de entidad),

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

y hacer especializaciones para cada tipo de mensaje que hará el componente específico.

¿Hay algún inconveniente con el segundo método? ¿Qué pasa con el rendimiento, por qué no veo este tipo de uso con más frecuencia?

2. Manejo de entrada

Mi segunda pregunta es ¿cuál sería la forma óptima (en términos de latencia y facilidad de uso) para manejar la entrada?

Mi pensamiento era crear uno InputHandlerComponentque se registre con la clase de teclado para escuchar pulsaciones de teclas específicas definidas posiblemente en algún archivo. Por ejemplo

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

Desearía que hubiera una guía más concisa de los sistemas basados ​​en componentes, pero creo que hay muchas maneras diferentes de hacer lo mismo. Tengo más preguntas, pero creo que sería más prudente intentar primero implementar lo que pueda.

Corazón triste
fuente

Respuestas:

3

En un sistema de entidad típico, dejas que los sistemas tengan toda la lógica para que tu juego y componentes almacenen datos solamente , y tus entidades son solo un identificador simple. El manejo de la entrada sería mucho más fácil de esa manera y sin mencionar la flexibilidad de su juego.

Hay muchas maneras de manejar eventos, como: el patrón de observación o las señales / ranuras. Podría manejar mensajes con plantillas / funciones virtuales o punteros de función, sin embargo, probablemente sería más fácil usar las señales boost :: , incluso sabiendo que no es tan bueno para el rendimiento. Si desea rendimiento *, le sugiero que use plantillas y funciones virtuales o punteros de función.

* Noté que tu código realmente podría optimizarse. Existen alternativas al typeidoperador, que en realidad son más rápidas que el uso typeid. Como el uso de plantillas / macros y enteros simples para definir la identificación de una clase.

Puede ver mi sistema de entidades si necesita algo de inspiración (que se inspira en el marco de Java Artemis ). Aunque no he implementado una forma de manejar eventos (que no sean eventos relacionados con la entidad), se lo dejé al usuario, pero después de descubrir la biblioteca de la entidadx , que encontré en reddit. Pensé que podría agregar un sistema de eventos a mi biblioteca. Tenga en cuenta que mi sistema de entidades no está 100% completo en las características que quiero, pero funciona y tiene un rendimiento decente (pero podría optimizar, especialmente con la forma en que estoy almacenando entidades).

Esto es lo que podría hacer para manejar eventos (inspirados en entityx ):

BaseReceiver / BaseListener

  • Una clase base para que pueda almacenar oyentes / receptores.

Receptor / Oyente

  • Esta es una clase de plantilla y requiere que anule la función virtual recieve(const T&), donde T es información de eventos.
  • Las clases que desean ser notificadas por eventos que envían información específica cuando ocurre un evento deben heredar de esta clase.

Controlador de eventos

  • Emite / dispara eventos
  • Tiene una lista de objetos de receptores / oyentes que serán notificados por eventos disparados

He implementado este diseño en C ++, justo ahora, sin el uso de boost :: señales . Puedes verlo aquí .

Pros

  • Mecanografiado estáticamente
  • Funciones virtuales (más rápido que boost :: señales, bueno debería ser)
  • Errores en tiempo de compilación si no emitió notificaciones correctamente
  • C ++ 98 compatible (creo)

Contras

  • Requiere que defina los functores (no puede manejar eventos en una función global)
  • Sin cola de eventos; solo regístrate y dispara. (que puede no ser lo que quieres)
  • No llamadas directas (no debería ser tan malo para eventos)
  • No hay manejo de eventos múltiples en la misma clase (esto podría modificarse por lo que permite múltiples eventos, a través de herencia múltiple, pero puede tomar algún tiempo implementarlo)

Además, tengo un ejemplo en mi sistema de entidad, que se ocupa de la representación y el manejo de entrada, es bastante simple pero presenta muchos conceptos para comprender mi biblioteca.

miguel.martin
fuente
¿Puedes dar un ejemplo de una forma más rápida de descubrir tipos sin typeid?
Luke B.
"En un sistema de entidad típico, dejas que los sistemas tengan toda la lógica para que tu juego y componentes almacenen datos solamente", no existe un sistema de entidad 'típico'.
Den
@LukeB. Mire el código fuente del sistema de mi entidad, no lo uso typeid(mire Component y ComponentContainer). Básicamente, almaceno el "tipo" de un componente como un entero, tengo un entero global que incremente por tipo de componente. Y almaceno componentes en una matriz 2d, donde el primer índice es el ID de la entidad y el segundo índice es el ID del tipo de componente, es decir, componentes [entityId] [componentTypeId]
miguel.martin
@ miguel.martin. Gracias por su respuesta. Creo que mi confusión proviene principalmente del hecho de que hay varias formas de implementar el sistema de entidades y para mí todos tienen sus inconvenientes y beneficios. Por ejemplo, en el enfoque que utiliza, debe registrar nuevos tipos de componentes utilizando CRTP, lo que personalmente no me gusta ya que este requisito no es explícito. Creo que tendré que reconsiderar mi implementación.
Grieverheart
1
@MarsonMao solucionado
miguel.martin
1

Estoy trabajando en un sistema de entidad basado en componentes en C #, pero las ideas y patrones generales todavía se aplican.

La forma en que manejo los tipos de mensajes es que cada subclase de componente llama al RequestMessage<T>(Action<T> action) where T : IMessagemétodo protegido de Component . En inglés, eso significa que la subclase de componente solicita un tipo específico de mensaje y proporciona un método para ser llamado cuando el componente recibe un mensaje de ese tipo.

Ese método se almacena y cuando el componente recibe un mensaje, utiliza la reflexión para obtener el tipo de mensaje, que luego se utiliza para buscar el método asociado e invocarlo.

Puede reemplazar el reflejo con cualquier otro sistema que desee, los hashes son su mejor opción. Sin embargo, mire el patrón de despacho múltiple y visitante para algunas otras ideas.

En cuanto a la entrada, elegí hacer algo completamente diferente de lo que estás pensando. Un controlador de entrada convertirá por separado la entrada del teclado / mouse / gamepad en una enumeración de banderas (campo de bits) de posibles acciones (MoveForward, MoveBackwards, StrafeLeft, Use, etc.) según la configuración del usuario / lo que el jugador ha conectado a la computadora. Cada componente obtiene UserInputMessagecada cuadro que contiene tanto el campo de bits como el eje de aspecto del jugador como un vector 3D (desarrollo de engranaje hacia un tirador en primera persona). Esto también facilita que los jugadores cambien sus combinaciones de teclas.

Como dijiste al final de tu pregunta, hay muchas formas diferentes de crear un sistema de entidad basado en componentes. He orientado mucho mi sistema hacia el juego que estoy haciendo, por lo que, naturalmente, algunas de las cosas que hago pueden no tener sentido en el contexto de, por ejemplo, un RTS, pero aún es algo que se ha implementado y que ha estado funcionando para yo hasta ahora

Robert Rouhani
fuente