Como os componentes físicos ou gráficos são tipicamente criados em um sistema orientado a componentes?

19

Passei as últimas 48 horas lendo sistemas de componentes de objetos e sinto que estou pronto o suficiente para começar a implementá-lo. Criei as classes Object e Component de base, mas agora que preciso começar a criar os componentes reais, estou um pouco confuso. Quando penso neles em termos de HealthComponent ou algo que basicamente seria apenas uma propriedade, faz todo o sentido. Quando é algo mais geral como componente de Física / Gráficos, fico um pouco confuso.

Minha classe Object se parece com isso até agora (se você perceber alguma alteração que eu deva fazer, por favor me avise, ainda é novo nisso) ...

typedef unsigned int ID;

class GameObject
{
public:

    GameObject(ID id, Ogre::String name = "");
    ~GameObject();

    ID &getID();
    Ogre::String &getName();

    virtual void update() = 0;

    // Component Functions
    void addComponent(Component *component);
    void removeComponent(Ogre::String familyName);

    template<typename T>
    T* getComponent(Ogre::String familyName)
    {
        return dynamic_cast<T*>(m_components[familyName]);
    }

protected:

    // Properties
    ID m_ID;
    Ogre::String m_Name;
    float m_flVelocity;
    Ogre::Vector3 m_vecPosition;

    // Components
    std::map<std::string,Component*> m_components;
    std::map<std::string,Component*>::iterator m_componentItr;
};

Agora, o problema que estou enfrentando é o que a população em geral colocaria em componentes como Física / Gráficos? Para o Ogre (meu mecanismo de renderização), os Objetos visíveis consistirão em vários Ogre :: SceneNode (possivelmente múltiplos) para anexá-lo à cena, Ogre :: Entity (possivelmente múltiplos) para mostrar as malhas visíveis, e assim por diante. Seria melhor adicionar vários GraphicComponent ao objeto e permitir que cada GraphicComponent lidasse com um SceneNode / Entity ou é a idéia de ter um de cada componente necessário?

Para a física, estou ainda mais confuso. Suponho que talvez esteja criando um RigidBody e mantendo o controle de massa / interia / etc. faria sentido. Mas estou tendo problemas para pensar em como realmente colocar detalhes em um componente.

Depois de concluir alguns desses componentes "Necessários", acho que fará muito mais sentido. A partir de agora, ainda estou um pouco perplexo.

Aidan Knight
fonte

Respostas:

32

Antes de tudo, quando você cria sistemas baseados em componentes, não precisa adotar a abordagem de transformar tudo em um componente. Na verdade, você geralmente não deveria - é um erro de neófito. Na minha experiência, a melhor maneira de unir os sistemas de renderização e física em uma arquitetura baseada em componentes é tornar esses componentes pouco mais que recipientes burros para o objeto real de renderização ou física primitivo.

Isso tem a vantagem adicional de manter os sistemas de renderização e física separados do sistema de componentes.

Para sistemas de física, por exemplo, o simulador de física geralmente mantém uma grande lista de objetos rígidos do corpo que ele manipula toda vez que executa o trabalho. Seu sistema de componentes não mexe com isso - seu componente de física simplesmente armazena uma referência ao corpo rígido que representa os aspectos físicos do objeto ao qual o componente pertence e converte todas as mensagens do sistema de componentes em manipulações apropriadas do corpo rígido.

Da mesma forma com a renderização - seu renderizador terá algo que representa os dados da instância a serem renderizados, e seu componente visual simplesmente mantém uma referência a isso.

É relativamente simples - por qualquer motivo que pareça confundir as pessoas, mas muitas vezes é porque elas estão pensando demais nisso. Não há muita mágica lá. E como é mais importante fazer as coisas do que agonizar com o design perfeito, considere fortemente uma abordagem em que pelo menos alguns componentes tenham interfaces definidas e sejam membros diretos do objeto do jogo, como o momboco sugeriu. É tão válida quanto uma abordagem e tende a ser mais fácil de grunhir.

Outros comentários

Isso não está relacionado às suas perguntas diretas, mas essas são as coisas que notei ao analisar seu código:

Por que você está misturando Ogre :: String e std :: string no seu objeto de jogo, que é ostensivamente um objeto não gráfico e, portanto, não deve depender do Ogre? Sugiro remover todas as referências diretas ao Ogre, exceto no componente de renderização, onde você deve fazer sua transformação.

update () é um virtual puro, o que implica que é preciso subclassificar GameObject, que é exatamente o oposto da direção que você deseja seguir com uma arquitetura de componente. O ponto principal do uso de componentes é preferir a agregação de funcionalidades ao invés de herança.

Você pode se beneficiar de algum uso de const(especialmente para onde você está retornando por referência). Também não tenho certeza de que você realmente deseja que a velocidade seja escalar (ou se ela e a posição devem estar presentes no objeto do jogo no tipo de metodologia de componentes excessivamente genérica que você parece estar procurando).

Sua abordagem de usar um grande mapa de componentes e uma update()chamada no objeto de jogo é bastante subótima (e uma armadilha comum para quem primeiro constrói esse tipo de sistema). Isso reduz a coerência do cache durante a atualização e não permite que você aproveite a concorrência e a tendência para o processo no estilo SIMD de grandes lotes de dados ou comportamento de uma só vez. Geralmente, é melhor usar um design em que o objeto do jogo não atualize seus componentes, mas o subsistema responsável pelo próprio componente os atualiza todos de uma vez. Isso pode valer a pena ser lido.


fonte
Esta resposta foi excelente. Ainda não descobri uma maneira de espaçar parágrafos nos comentários (a tecla Enter apenas envia), mas abordarei essas respostas. Sua descrição do sistema Objeto / Componente fazia muito sentido. Então, só para esclarecer, no caso de um PhysicsComponent, no construtor do componente, eu poderia chamar a função CreateRigidBody () do meu PhysicsManager e, em seguida, armazenar uma referência a essa no componente?
Aidan Knight
Quanto à parte "Outros comentários", obrigado por isso. Eu misturei strings porque geralmente prefiro usar todas as funções do Ogre como base para os meus projetos que usam o Ogre, mas comecei a alterná-las no sistema de Objetos / Componentes, uma vez que faltam map / pair / etc. Você está certo e eu os converti para os membros std para separá-lo do Ogre.
Aidan Knight
11
1 Essa é a primeira resposta sã relativo a um modelo baseado em componentes que li aqui em um tempo, bom contraste contra o excesso de generalização
Maik Semder
5
Ele permite uma melhor coerência (dos dados e no cache de instruções) e oferece a possibilidade de descarregar as atualizações quase trivialmente em threads distintos ou em processos de trabalho (SPUs, por exemplo). Em alguns casos, os componentes nem precisariam ser atualizados, o que significa que você pode evitar a sobrecarga de uma chamada não operacional para um método update (). Também ajuda a criar sistemas orientados a dados - trata-se de processamento em massa, que tira proveito das tendências modernas da arquitetura da CPU.
2
+1 em "é mais importante fazer as coisas do que agonizar com o design perfeito"
Trasplazio Garzuglio
5

A arquitetura do componente é uma alternativa para evitar as grandes hierarquias que são obtidas herdando todos os elementos de um mesmo nó e os problemas de acoplamento que levam.

Você está mostrando uma arquitetura de componentes com componentes "genéricos". Você tem a lógica básica para anexar e desanexar componentes "genéricos". Uma arquitetura com componentes genéricos é mais difícil de obter porque você não sabe qual é o componente. Os benefícios é que essa abordagem é extensível.

Uma arquitetura de componente válida é alcançada com componentes definidos. Com definido, quero dizer, a interface é definida, não a implementação. Por exemplo, GameObject pode ter um IPhysicInstance, Transformation, IMesh, IAnimationController. Isso tem o benefício de que você conhece a interface e o GameObject sabe como lidar com cada componente.

Respondendo ao termo generalização excessiva

Eu acho que os conceitos não são claros. Quando digo componente, não significa que todo componente de um objeto de jogo deva herdar de uma interface de componente ou algo semelhante. No entanto, essa é a abordagem para componentes genéricos, acho que é o conceito que você nomeia como componente.

A arquitetura do componente é outra das arquiteturas existentes para o modelo de objeto de tempo de execução. Não é o único e não é o melhor para todos os casos, mas a experiência demonstrou que possui muitos benefícios, como baixo acoplamento.

O principal recurso que distingue a arquitetura do componente é converter a relação is-a em has-a. Por exemplo, uma arquitetura de objeto é-a:

é uma arquitetura

Em vez de uma arquitetura de objeto has-a (componentes):

arquitetura has-a

Também há arquiteturas centradas em propriedades:

Por exemplo, uma arquitetura de objeto normal é assim:

  • Objeto1

    • Posição = (0, 0, 0)
    • Orientação = (0, 43, 0)
  • Objeto2

    • Posição = (10, 0, 0)
    • Orientação = (0, 0, 30)

Uma arquitetura centrada em propriedades:

  • Posição

    • Objeto1 = (0, 0, 0)
    • Objeto2 = (10, 0, 0)
  • Orientação

    • Objeto1 = (0, 43, 0)
    • Objeto2 = (0, 0, 30)

É melhor a arquitetura do modelo de objeto? Na perspectiva do desempenho, é melhor centrar-se na propriedade porque é mais amigável ao cache.

O componente é referido à relação é-a em vez de tem-a. Um componente pode ser uma matriz de transformação, um quaternion etc. Mas a relação com o objeto do jogo é uma relação é-a, o objeto do jogo não tem a transformação porque é herdado. O objeto do jogo tem (a relação tem-a) a transformação. A transformação é então um componente.

O objeto do jogo é como um hub. se você deseja um componente que sempre está lá, talvez o componente (como a matriz) deva estar dentro do objeto e não um ponteiro.

Não existe um objeto de jogo perfeito para todos os casos, depende do mecanismo, das bibliotecas que o mecanismo usa. Talvez eu queira uma câmera que não tenha uma malha. O problema da arquitetura is-a é que, se o objeto do jogo possui uma malha, a câmera que herda do objeto do jogo deve ter uma malha. Com os componenets, a câmera tem uma transformação, um controlador para o movimento e tudo o que você precisa.

Para obter mais informações, o capítulo "14.2. Arquitetura de modelo de objeto de tempo de execução" do livro "Game Engine Architecture" de "Jason Gregory" trata especificamente desse assunto.

Os diagramas UML são extraídos de lá

momboco
fonte
Não concordo com o último parágrafo, há uma tendência de generalizar demais. O modelo baseado em componentes não significa que toda e qualquer funcionalidade deve ser um componente; ainda é preciso considerar os relacionamentos de funcionalidades e dados. Um AnimationController sem uma malha é inútil; portanto, coloque-o onde pertence, na malha. Um Objeto de Jogo sem transformação não é um Objeto de Jogo; ele pertence por definição ao mundo 3D; portanto, coloque-o como um membro normal no Objeto de Jogo. As regras normais da funcionalidade e dados do encapsulamento ainda se aplicam.
Maik Semder
@Maik Semder: Concordo com você em geral generalizando em termos gerais, mas acho que o termo componente não é claro. Eu editei minha resposta. Leia e me dê suas impressões.
Momboco 18/06
@momboco bem, você menos repetiu os mesmos pontos;) Transform e AnimationController ainda são descritos como componentes, o que, no meu ponto de vista, é um uso excessivo do modelo de componente. A chave é definir o que deve ser colocado em um componente eo que não:
Maik Semder
Se for necessário algo para fazer o Game-Object (GO) funcionar, em outras palavras, se não for opcional, mas obrigatório, não será um componente. Os componentes devem ser opcionais. A transformação é um bom exemplo, não é opcional para um GO, um GO sem uma transformação não faz sentido. Por outro lado, nem todo GO precisa de física, de modo que pode ser um componente.
Maik Semder
Se houver uma forte relação entre duas funções, como entre o AnimationController (AC) e o Mesh, de qualquer forma, não quebre esse relacionamento pressionando o AC em um componente, ao contrário, o Mesh é um componente perfeito, mas um AC pertence na malha.
Maik Semder