Eu sou muito novo no desenvolvimento de jogos, mas não na programação.
Estou (de novo) brincando com um jogo do tipo Pong usando o canvas
elemento JavaScript .
Eu criei um Paddle
objeto que possui as seguintes propriedades ...
width
height
x
y
colour
Eu também tenho um Pong
objeto que possui propriedades como ...
width
height
backgroundColour
draw()
.
O draw()
método atualmente está redefinindo o canvas
e é aí que surgiu uma pergunta.
Se o Paddle
objeto tiver um draw()
método responsável por seu desenho ou draw()
o Pong
objeto for responsável por desenhar seus atores (presumo que esse seja o termo correto, corrija-me se estiver incorreto).
Imaginei que seria vantajoso Paddle
desenhar, como instanciamos dois objetos, Player
e Enemy
. Se não estivesse no Pong
's draw()
, eu precisaria escrever código semelhante duas vezes.
Qual é a melhor prática aqui?
Obrigado.
fonte
Respostas:
Ter atores desenhando a si mesmos não é um bom design, por duas razões principais:
1) viola o princípio da responsabilidade única , pois esses atores provavelmente tinham outro trabalho a fazer antes de você inserir o código para eles.
2) dificulta a extensão; se todo tipo de ator implementa seu próprio desenho, e você precisa alterar a maneira como desenha em geral , pode ser necessário modificar muito código. Evitar o uso excessivo de herança pode aliviar isso em certa medida, mas não completamente.
É melhor para o seu renderizador lidar com o desenho. Afinal, é isso que significa ser um renderizador. O método de desenho do renderizador deve usar um objeto "descrição da renderização", que contém tudo o que você precisa para renderizar uma coisa. Referências a dados de geometria (provavelmente compartilhados), transformações específicas da instância ou propriedades do material, como cor, etc. Ele então desenha isso e não se importa com o que essa descrição de renderização deveria "ser".
Seus atores podem se apegar a uma descrição de renderização que eles mesmos criam. Como os atores são tipicamente tipos de processamento lógico, eles podem enviar alterações de estado para a descrição da renderização conforme necessário - por exemplo, quando um ator sofre dano, ele pode definir a cor da descrição da renderização para vermelho para indicar isso.
Em seguida, você pode simplesmente iterar todos os atores visíveis, colocar suas descrições de renderização no renderizador e deixá-lo funcionar (basicamente; você pode generalizar isso ainda mais).
fonte
actor.draw(renderer,x,y)
querenderer.draw(actor.getAttribs(),x,y)
.struct RenderDetail { glVAO* vao; int shader; int texture; Matrix4f* transform; }
mais ou menos para o meu renderizador. Dessa forma, o renderizador não se importa com o que os dados representam, apenas os processa. Com base nessas informações, também posso decidir se classificar a ordem das chamadas de empate para reduzir as chamadas gerais da GPU.O Padrão do Visitante pode ser útil aqui.
O que você pode fazer é ter uma interface Renderer que saiba desenhar cada objeto e um método "desenhe você mesmo" em cada ator que determine qual método (específico) de renderizador chamar, por exemplo
Dessa forma, ainda é fácil portar para outra biblioteca ou estrutura gráfica (uma vez eu portava um jogo do Swing / Java2D para o LWJGL e fiquei muito feliz por ter usado esse padrão em vez de dar uma
Graphics2D
volta.)Há outra vantagem: o renderizador pode ser testado separadamente de qualquer código de ator
fonte
No seu exemplo, você deseja que os objetos Pong implementem draw () e se renderizem.
Embora você não note nenhum ganho significativo em um projeto desse tamanho, geralmente separar a lógica do jogo e sua representação visual (renderização) é uma atividade que vale a pena.
Com isso, quero dizer que você teria seus objetos de jogo que podem ser atualizados (), mas eles não têm idéia de como são renderizados, eles se preocupam apenas com a simulação.
Então você teria uma classe PongRenderer () que tem uma referência a um objeto Pong e, em seguida, se encarrega de renderizar a classe Pong (), isso pode envolver renderizar os Paddles ou ter uma classe PaddleRenderer para cuidar dele.
A separação de preocupações nesse caso é bastante natural, o que significa que suas classes podem ser menos inchadas e é mais fácil modificar a maneira como as coisas são renderizadas, não é mais necessário seguir a hierarquia que sua simulação faz.
fonte
Se você o fizesse no
Pong
draw (), não seria necessário duplicar o código - basta chamar aPaddle
função draw () de cada um dos seus remos. Então, no seuPong
sorteio () você teriafonte
Cada objeto deve conter sua própria função de desenho, que deve ser chamada pelo código de atualização do jogo ou pelo código de desenho. Gostar
Este não é um código, mas apenas para mostrar como os objetos de desenho devem ser feitos.
fonte