Disclamer: Eu sei o que é um padrão de sistema entidade é e eu estou não usá-lo.
Eu li muito sobre a separação de objetos e renderizações. Sobre o fato de que a lógica do jogo deve ser independente do mecanismo de renderização subjacente. Tudo isso é bom e elegante, e faz todo o sentido, mas causa muitas outras dores também:
- necessidade de sincronização entre o objeto lógico e o objeto de renderização (aquele que mantém o estado da animação, os sprites etc.)
- é necessário abrir o objeto lógico ao público para que o objeto de renderização leia o estado real do objeto lógico (geralmente levando o objeto lógico a se transformar facilmente em um objeto getter e setter burro)
Isso não parece uma boa solução para mim. Por outro lado, é muito intuitivo imaginar um objeto como sua representação em 3D (ou 2D) e também é muito fácil de manter (e possivelmente muito mais encapsulado).
Existe uma maneira de manter a representação gráfica e a lógica do jogo acopladas (evitando problemas de sincronização), mas abstraindo o mecanismo de renderização? Ou existe uma maneira de separar a lógica do jogo e a renderização que não causa as desvantagens acima?
(possivelmente com exemplos, não sou muito bom em entender conversas abstratas)
fonte
Respostas:
Suponha que você tenha uma cena composta por um mundo , um jogador e um chefe. Ah, e este é um jogo de terceira pessoa, então você também tem uma câmera .
Portanto, sua cena fica assim:
(Pelo menos, esses são os dados básicos . Como você os contém, é com você.)
Você só deseja atualizar e renderizar a cena quando estiver jogando, não quando estiver em pausa ou no menu principal ... para anexá-la ao estado do jogo!
Agora o seu estado do jogo tem uma cena. Em seguida, você deseja executar a lógica na cena e renderizar a cena. Para a lógica, você acabou de executar uma função de atualização.
Dessa forma, você pode manter toda a lógica do jogo na
Scene
classe. E apenas por uma questão de referência, um sistema de componentes de entidade pode fazê-lo assim:De qualquer forma, agora você conseguiu atualizar sua cena. Agora você deseja exibi-lo! Para o qual fazemos algo semelhante ao acima:
Ai está. O renderSystem lê as informações da cena e exibe a imagem apropriada. Simplificado, o método para renderizar a cena pode ser assim:
Realmente simplificado, você ainda precisará, por exemplo, aplicar uma rotação e tradução com base em onde seu jogador está e onde ele está. (Meu exemplo é um jogo em 3D, se você for em 2D, será um passeio no parque).
Espero que isto seja o que você estava procurando? Como você pode se lembrar do exposto acima, o sistema de renderização não se importa com a lógica do jogo . Ele usa apenas o estado atual da cena para renderizar, ou seja, extrai as informações necessárias para renderizar. E a lógica do jogo? Não se importa com o que o renderizador faz. Caramba, não importa se é exibido!
E você também não precisa anexar informações de renderização à cena. Deve ser suficiente que o renderizador saiba que precisa renderizar um orc. Você já carregou um modelo orc, que o renderizador sabe exibir.
Isso deve satisfazer seus requisitos. A representação gráfica e a lógica são acopladas , porque ambas usam os mesmos dados. No entanto, eles são separados , porque nenhum deles depende do outro!
EDIT: E apenas para responder por que alguém faria assim? Porque é mais fácil é a razão mais simples. Você não precisa pensar em "tal e tal aconteceu, agora devo atualizar os gráficos". Em vez disso, você faz as coisas acontecerem, e cada quadro do jogo examina o que está acontecendo no momento e o interpreta de alguma forma, fornecendo um resultado na tela.
fonte
Seu título faz uma pergunta diferente do conteúdo do seu corpo. No título, você pergunta por que lógica e renderização devem ser separadas, mas no corpo solicita implementações de sistemas de lógica / gráficos / renderização.
A segunda questão já foi abordada anteriormente , portanto, vou me concentrar na primeira.
Razões para separar a lógica e a renderização:
Em uma configuração de POO, instanciar novos objetos tem um custo, mas, na minha experiência, o custo para os recursos do sistema é um preço pequeno a pagar pela capacidade de pensar e implementar as coisas específicas que preciso executar.
fonte
Essa resposta é apenas para criar uma intuição de por que é importante separar renderização e lógica, em vez de sugerir diretamente exemplos práticos.
Vamos supor que temos um elefante grande , ninguém na sala pode ver o elefante inteiro. talvez todo mundo discorde do que realmente é. Porque todo mundo vê uma parte diferente do elefante e só pode lidar com essa parte. Mas no final, isso não muda o fato de ser um grande elefante.
O elefante representa o objeto do jogo com todos os seus detalhes. Mas ninguém realmente precisa saber tudo sobre o elefante (objeto de jogo) para poder fazer sua funcionalidade.
Acoplar a lógica do jogo e a renderização é como fazer todo mundo ver o elefante inteiro. Se algo mudou, todo mundo precisa saber. Embora na maioria dos casos, eles só precisem ver a parte na qual estão interessados. Se algo mudou a pessoa que o conhece, precisa apenas contar à outra pessoa sobre o resultado dessa mudança, é isso que é importante apenas para ela. (pense nisso como comunicação via mensagens ou interfaces).
Os pontos que você mencionou não são desvantagens, são apenas desvantagens se houver mais dependências do que deveriam existir no mecanismo; em outras palavras, os sistemas veem partes do elefante mais do que deveriam. E isso significa que o mecanismo não foi "corretamente" projetado.
Você só precisa sincronizar com sua definição formal se estiver usando um mecanismo multithread em que ele coloque a lógica e a renderização em dois threads diferentes, e mesmo um mecanismo que precise de muita sincronização entre sistemas não seja especialmente projetado.
Caso contrário, a maneira natural de lidar com esse caso é projetar o sistema como entrada / saída. A atualização faz a lógica e gera o resultado. A renderização é alimentada apenas com os resultados da atualização. Você realmente não precisa expor tudo. Você expõe apenas uma interface que se comunica entre os dois estágios. A comunicação entre diferentes partes do mecanismo deve ser via abstrações (interfaces) e / ou mensagens. Nenhuma lógica ou estados internos devem ser expostos.
Vamos dar um exemplo simples de gráfico de cena para explicar a ideia.
A atualização geralmente é feita através de um único loop chamado loop do jogo (ou possivelmente através de vários loops de jogos, cada um rodando em um thread separado). Uma vez que o loop atualizou o objeto de qualquer jogo. Ele só precisa dizer por meio de mensagens ou interfaces que os objetos 1 e 2 foram atualizados e alimentá-lo com a transformação final.
O sistema de renderização leva apenas a transformação final e não sabe o que realmente mudou sobre o objeto (por exemplo, colisão específica aconteceu etc). Agora, para renderizar esse objeto, ele precisa apenas do ID desse objeto e da transformação final. Depois disso, o renderizador alimentará a API de renderização com a malha e a transformação final sem saber mais nada.
fonte