Dicas para manipulação de mensagens do sistema de entidades com base em componentes


8

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 attachComponentmé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 InputHandlerComponentregistrador 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.

Respostas:


3

Em um sistema de entidade típico, você deixa os sistemas com toda a lógica do seu jogo e componentes para armazenar apenas dados , e suas entidades são apenas um identificador simples. O manuseio de entrada seria muito mais fácil dessa maneira e sem mencionar a flexibilidade do seu jogo.

Existem várias maneiras de lidar com eventos, como: o padrão do observador ou sinais / slots. Você pode manipular mensagens com modelos / funções virtuais ou ponteiros de função, no entanto, provavelmente seria mais fácil usar os sinais boost :: , mesmo sabendo que não é tão bom para desempenho. Se você deseja desempenho *, sugiro que você use modelos e funções virtuais ou ponteiros de função.

* Percebi que seu código poderia realmente ser otimizado. Existem alternativas para o typeidoperador, que são realmente mais rápidas que o uso typeid. Como usar modelos / macros e números inteiros simples para definir o ID de uma classe.

Você pode olhar para o meu sistema de entidades se precisar de alguma inspiração (que é inspirada na estrutura Java Artemis ). Embora eu não tenha implementado uma maneira de lidar com eventos (exceto eventos relacionados a entidades), deixei isso para o usuário, mas depois de analisar a biblioteca entityx , que encontrei no reddit. Imaginei que poderia adicionar um sistema de eventos à minha biblioteca. Observe que meu sistema de entidades não é 100% completo sobre os recursos que eu quero, mas funciona e tem um desempenho decente (mas eu poderia otimizar, especialmente com a maneira como estou armazenando entidades).

Aqui está o que você poderia fazer para lidar com eventos (inspirados no entityx ):

BaseReceiver / BaseListener

  • Uma classe base para que você possa armazenar ouvintes / receptores.

Receptor / Ouvinte

  • Esta é uma classe de modelo e requer que você substitua a função virtual recieve(const T&), onde T é informação do evento.
  • As classes que desejam ser notificadas por eventos que enviam informações específicas quando ocorre um evento devem herdar dessa classe.

EventHandler

  • Emite / aciona eventos
  • Possui uma lista de objetos Receivers / Listeners que serão notificados por eventos disparados

Eu implementei esse design em C ++, agora, sem o uso de boost :: signs . Você pode vê-lo aqui .

Prós

  • Digitado estaticamente
  • Funções virtuais (mais rápidas que boost :: signs, bem deve ser)
  • Erros em tempo de compilação se você não emitiu notificações corretamente
  • C ++ 98 compatível (eu acredito)

Contras

  • Requer que você defina functores (você não pode manipular eventos em uma função global)
  • Nenhuma fila de eventos; basta registrar e disparar. (que pode não ser o que você deseja)
  • Chamadas não diretas (não devem ser tão ruins para eventos)
  • Não há manipulação de múltiplos eventos na mesma classe (isso pode ser modificado para permitir vários eventos, via herança múltipla, mas pode levar algum tempo para ser implementado)

Além disso, tenho um exemplo no meu sistema de entidades, que lida com renderização e manipulação de entradas, é bastante simples, mas apresenta muitos conceitos para entender minha biblioteca.


Você pode dar um exemplo de uma maneira mais rápida de descobrir tipos sem typeid?
Luke B.

"Em um sistema de entidades típico, você deixa os sistemas com toda a lógica do jogo e dos componentes para armazenar apenas dados" - não existe um sistema de entidades 'típico'.
Den

@LukeB. Veja o código-fonte do sistema da minha entidade, eu não uso 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]
miguel.martin

@ miguel.martin. Obrigado pela sua resposta. Penso que a minha confusão provém principalmente do facto de existirem várias formas de implementar o sistema de entidades e, para mim, todas as suas desvantagens e benefícios. Por exemplo, na abordagem que você usa, é necessário registrar novos tipos de componentes usando o CRTP, o que pessoalmente não gosto, pois esse requisito não é explícito. Acho que vou ter que reconsiderar minha implementação.
Grievheart

11
@MarsonMao fixado
miguel.martin

1

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 : IMessagemé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 UserInputMessagequadro 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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.