Okey, o que eu sei até agora; A entidade contém um componente (armazenamento de dados) que contém informações como; - Textura / sprite - Shader - etc
E então eu tenho um sistema de renderização que desenha tudo isso. Mas o que não entendo é como o renderizador deve ser projetado. Devo ter um componente para cada "tipo visual". Um componente sem shader, outro com shader, etc?
Só preciso de algumas informações sobre qual é a "maneira correta" de fazer isso. Dicas e armadilhas para observar.
Respostas:
Essa é uma pergunta difícil de responder, porque todos têm sua própria idéia sobre como um sistema de componentes de entidade deve ser estruturado. O melhor que posso fazer é compartilhar com você algumas das coisas que considero mais úteis para mim.
Entidade
Eu adoto a abordagem de classe gorda do ECS, provavelmente porque acho que métodos extremos de programação são altamente ineficientes (em termos de produtividade humana). Para esse fim, uma entidade para mim é uma classe abstrata a ser herdada por classes mais especializadas. A entidade possui várias propriedades virtuais e um sinalizador simples que informa se essa entidade deve ou não existir. Portanto, em relação à sua pergunta sobre um sistema de renderização, é
Entity
assim:Componentes
Os componentes são "estúpidos", pois não fazem nem sabem nada. Eles não têm referências a outros componentes e normalmente não têm funções (trabalho em C #, então uso propriedades para manipular getters / setters - se eles tiverem funções, serão baseados na recuperação de dados que possuem).
Sistemas
Os sistemas são menos "estúpidos", mas ainda são autômatos estúpidos. Eles não têm contexto do sistema geral, não têm referências a outros sistemas e não mantêm dados, exceto por alguns buffers de que talvez precisem para fazer seu processamento individual. Dependendo do sistema, ele pode ter um método especializado
Update
ouDraw
, em alguns casos, ambos.Interfaces
As interfaces são uma estrutura importante no meu sistema. Eles são usados para definir o que um
System
processo pode e o que umEntity
é capaz. As interfaces que são relevantes para a renderização são:IRenderable
eIAnimatable
.As interfaces simplesmente informam ao sistema quais componentes estão disponíveis. Por exemplo, o sistema de renderização precisa conhecer a caixa delimitadora da entidade e a imagem a desenhar. No meu caso, isso seria oe
SpatialComponent
oImageComponent
. Então fica assim:O sistema Rendering
Então, como o sistema de renderização desenha uma entidade? Na verdade, é bastante simples, então mostrarei a classe simplificada para ter uma idéia:
Olhando para a classe, o sistema de renderização nem sabe o que
Entity
é. Tudo o que sabe éIRenderable
e é simplesmente dada uma lista deles para desenhar.Como Tudo Funciona
Também pode ajudar a entender como eu crio novos objetos de jogo e como os alimento para os sistemas.
Criando entidades
Todos os objetos de jogo herdam da Entidade e quaisquer interfaces aplicáveis que descrevem o que esse objeto de jogo pode fazer. Quase tudo o que é animado na tela fica assim:
Alimentando os sistemas
Eu mantenho uma lista de todas as entidades que existem no mundo do jogo em uma única lista chamada
List<Entity> gameObjects
. Cada quadro, então, vasculho essa lista e copio as referências de objetos para mais listas com base no tipo de interface, comoList<IRenderable> renderableObjects
, eList<IAnimatable> animatableObjects
. Dessa forma, se sistemas diferentes precisarem processar a mesma entidade, eles poderão. Depois, simplesmente entrego essas listas a cada um dos sistemasUpdate
ouDraw
métodos e deixo que os sistemas façam seu trabalho.Animação
Você pode estar curioso para saber como o sistema de animação funciona. No meu caso, você pode querer ver a interface IAnimatable:
O principal a notar aqui é o
ImageComponent
aspecto daIAnimatable
interface não é somente leitura; tem um levantador .Como você deve ter adivinhado, o componente de animação contém apenas dados sobre a animação; uma lista de quadros (que são componentes da imagem), o quadro atual, o número de quadros por segundo a ser desenhado, o tempo decorrido desde o último incremento do quadro e outras opções.
O sistema de animação tira proveito do sistema de renderização e da relação do componente de imagem. Simplesmente altera o componente de imagem da entidade à medida que incrementa o quadro da animação. Dessa forma, a animação é renderizada indiretamente pelo sistema de renderização.
fonte
Veja esta resposta para ver o tipo de sistema que estou falando.
O componente deve conter os detalhes sobre o que desenhar e como desenhá-lo. O sistema de renderização pega esses detalhes e desenha a entidade da maneira especificada pelo componente. Somente se você usasse tecnologias de desenho significativamente diferentes, teria componentes separados para estilos separados.
fonte
O principal motivo para separar a lógica em componentes é criar um conjunto de dados que, quando combinados em uma entidade, produzem um comportamento útil e reutilizável. Por exemplo, separar uma entidade em um PhysicsComponent e RenderComponent faz sentido, pois é provável que nem todas as entidades tenham Física e algumas entidades talvez não tenham Sprite.
Para responder sua pergunta, você precisa examinar sua arquitetura e fazer duas perguntas a si mesmo:
Ao dividir um componente, é importante fazer esta pergunta, se a resposta para 1. for sim, você provavelmente terá um bom candidato para criar dois componentes separados, um com um sombreador e outro com textura. A resposta para 2. geralmente é sim para componentes como Posição, onde vários componentes podem usar a posição.
Por exemplo, Física e Áudio podem usar a mesma posição; em vez de ambos os componentes que armazenam posições duplicadas, você as refatorar em um PositionComponent e exigir que as entidades que usam PhysicsComponent / AudioComponent também tenham um PositionComponent.
Com base nas informações que você nos forneceu, não parece que seu RenderComponent seja um bom candidato para se dividir em TextureComponent e ShaderComponent como shader's são totalmente dependentes de Texture e nada mais.
Supondo que você esteja usando algo semelhante ao T-Machine: Entity Systems, uma implementação de amostra de um RenderComponent & RenderSystem em C ++ seria algo como isto:
fonte
Armadilha # 1: código superprojetado. Pense se você realmente precisa de tudo o que implementa, porque terá que conviver com isso por algum tempo.
Armadilha # 2: muitos objetos. Eu não usaria um sistema com muitos objetos (um para cada tipo, subtipo e qualquer outro), porque isso dificulta o processamento automatizado. Na minha opinião, é muito melhor ter cada objeto controlar um determinado conjunto de recursos (em oposição a um recurso). Por exemplo, a criação de componentes para cada bit de dados incluído na renderização (componente de textura, componente de sombreamento) é muito dividida - você normalmente precisa ter todos esses componentes juntos de qualquer maneira, não concorda?
Armadilha 3: controle externo muito estrito. Prefira alterar nomes a objetos de sombreamento / textura, pois os objetos podem mudar com o renderizador / tipo de textura / formato de sombreamento / qualquer outra coisa. Os nomes são identificadores simples - cabe ao representante decidir o que fazer com eles. Um dia, você pode querer ter materiais em vez de shaders simples (adicione shaders, texturas e modos de mesclagem dos dados, por exemplo). Com uma interface baseada em texto, é muito mais fácil implementar isso.
Quanto ao renderizador, pode ser uma interface simples que cria / destrói / mantém / renderiza objetos criados por componentes. A representação mais primitiva poderia ser algo como isto:
Isso permitiria gerenciar esses objetos a partir de seus componentes e mantê-los longe o suficiente para permitir que você os processasse da maneira que desejar.
fonte