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 attachComponent
mé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 InputHandlerComponent
que 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.
fuente
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]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 : IMessage
mé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
UserInputMessage
cada 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
fuente