Estou tentando implementar um sistema de entidade baseado em componentes, mas estou um pouco confuso sobre como devo lidar com as mensagens. Gostaria de resolver dois problemas para poder testar o sistema. Abaixo está o código que tenho até agora,
A classe 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_;
};
As classes Componente Base e Mensagem:
class BaseComponent{
public:
virtual void handleMessage(BaseMessage &message){};
};
class BaseMessage{
public:
virtual int getType(void) = 0;
};
1. Tratamento do tipo de mensagem
Minha primeira pergunta é como devo lidar com os diferentes tipos de mensagens (derivados do BaseMessage).
Pensei em duas maneiras de lidar com os tipos de mensagem dos tipos de mensagem derivados. Uma é gerar um hash (ou seja, usando FNV) a partir de uma string que nomeie o tipo de mensagem e use esse hash para determinar o tipo de mensagem. Portanto, a handleMessage(BaseMessage &message)
função primeiro extraia esse hash da mensagem e depois faz um static_cast para o tipo apropriado.
O segundo método é usar um modelo da seguinte maneira (semelhante aos attachComponent
métodos da classe de entidade),
template<class T>
handleMessage(T& message){};
e faça especializações para cada tipo de mensagem que o componente específico fará.
Existem desvantagens usando o segundo método? E quanto ao desempenho, por que não vejo esse tipo de uso com mais frequência?
2. Manuseio de Entrada
Minha segunda pergunta é qual seria a maneira ideal (em termos de latência e facilidade de uso) para lidar com as entradas?
Meu pensamento era criar um InputHandlerComponent
registrador com a classe do teclado para ouvir pressionamentos de tecla específicos definidos possivelmente em algum arquivo. Por exemplo
keyboard.register( player.getComponent<InputHandler>() , 'W')
Eu gostaria que houvesse um guia mais conciso para sistemas baseados em componentes, mas acho que existem muitas maneiras diferentes de fazer as mesmas coisas. Tenho mais perguntas, mas acho que seria melhor tentar primeiro implementar o que posso.
fonte
typeid
(veja Component e ComponentContainer). Basicamente, eu armazeno o "tipo" de um componente como um número inteiro, tenho um número inteiro global que incremento por tipo de componente. E eu armazenar componentes em uma matriz 2D, onde o primeiro índice é ID da entidade e da 2ª índice é o ID do tipo de componente, ou seja componentes [entityId] [componentTypeId]Estou trabalhando em um sistema de entidade baseado em componente em C #, mas as idéias e padrões gerais ainda se aplicam.
A maneira como lida com os tipos de mensagem é que cada subclasse de componente chama o
RequestMessage<T>(Action<T> action) where T : IMessage
método protegido de Component . Em inglês, isso significa que a subclasse do componente solicita um tipo específico de mensagem e fornece um método a ser chamado quando o componente recebe uma mensagem desse tipo.Esse método é armazenado e, quando o componente recebe uma mensagem, ele usa reflexão para obter o tipo de mensagem, que é usado para procurar o método associado e invocá-lo.
Você pode substituir o reflexo por qualquer outro sistema que desejar, os hashes são sua melhor aposta. Observe o padrão de despacho múltiplo e visitante para outras idéias.
Quanto à entrada, eu escolhi fazer algo totalmente diferente do que você está pensando. Um manipulador de entrada converterá separadamente a entrada do teclado / mouse / gamepad em uma enumeração de sinalizadores (campo de bits) de possíveis ações (MoveForward, MoveBackwards, StrafeLeft, Use, etc.) com base nas configurações do usuário / no que o jogador conectou ao computador. Cada componente obtém um
UserInputMessage
quadro em cada quadro que contém o campo de bits e o eixo de aparência do jogador como um vetor 3d (direcionando o desenvolvimento para um jogo de tiro em primeira pessoa). Isso também facilita deixar que os jogadores alterem suas combinações de teclas.Como você disse no final da sua pergunta, existem muitas maneiras diferentes de criar um sistema de entidades baseado em componentes. Eu direcionei meu sistema fortemente para o jogo que estou criando, então, naturalmente, algumas das coisas que faço podem não fazer sentido no contexto de, digamos, um RTS, mas ainda é algo que foi implementado e que está trabalhando para eu até agora.
fonte