Eu li em muitos lugares que DrawableGameComponents deve ser salvo para coisas como "níveis" ou algum tipo de gerente, em vez de usá-los, por exemplo, para caracteres ou blocos (como esse cara diz aqui ). Mas não entendo por que isso é assim. Eu li este post e fez muito sentido para mim, mas essas são as minorias.
Normalmente, eu não prestaria muita atenção a coisas como essas, mas, neste caso, gostaria de saber por que a aparente maioria acredita que esse não é o caminho a seguir. Talvez esteja faltando alguma coisa.
xna
game-component
Kenji Kina
fonte
fonte
DrawableGameComponent
bloqueia você em um único conjunto de assinaturas de método e em um modelo de dados retido específico. Geralmente, essa é a escolha errada inicialmente, e o bloqueio dificulta significativamente a evolução do código no futuro. "Mas deixe-me dizer o que está acontecendo aqui ...DrawableGameComponent
faz parte da API XNA principal (novamente: principalmente por razões históricas). Programadores iniciantes o usam porque está "lá" e "funciona" e não o conhecem melhor. Eles veem minha resposta dizendo que estão " errados " e - devido à natureza da psicologia humana - rejeitam isso e escolhem o outro "lado". Que passa a ser a não resposta argumentativa de VeraShackle; que por acaso ganhou impulso porque eu não o chamei imediatamente (veja esses comentários). Sinto que minha eventual refutação lá permanece suficiente.Respostas:
"Componentes do jogo" foram uma parte muito inicial do design do XNA 1.0. Eles deveriam funcionar como controles para o WinForms. A idéia era que você seria capaz de arrastá-los e soltá-los em seu jogo usando uma GUI (mais ou menos como a adição de controles não visuais, como temporizadores, etc.) e definir propriedades neles.
Então você pode ter componentes como talvez uma câmera ou um ... contador de FPS? ... ou ...?
Entende? Infelizmente, essa ideia não funciona realmente para jogos do mundo real. O tipo de componentes que você deseja para um jogo não é realmente reutilizável. Você pode ter um componente "player", mas será muito diferente do componente "player" de todos os outros jogos.
Eu imagino que foi por esse motivo e por uma simples falta de tempo que a GUI foi puxada antes do XNA 1.0.
Mas a API ainda é utilizável ... Veja por que você não deve usá-la:
Basicamente, tudo se resume ao que é mais fácil escrever, ler, depurar e manter como desenvolvedor de jogos. Fazendo algo parecido com isto no seu código de carregamento:
É muito menos claro do que simplesmente fazer isso:
Especialmente quando, em um jogo do mundo real, você pode acabar com algo assim:
(É verdade que você pode compartilhar a câmera e o spriteBatch usando a
Services
arquitetura semelhante . Mas isso também é uma má idéia pelo mesmo motivo.)Essa arquitetura - ou a falta dela - é muito mais leve e flexível!
Posso alterar facilmente a ordem do desenho ou adicionar várias viewports ou adicionar um efeito de destino de renderização, apenas alterando algumas linhas. Para fazer isso no outro sistema, é necessário pensar no que todos esses componentes - espalhados por vários arquivos - estão fazendo e em que ordem.
É como a diferença entre programação declarativa e programação imperativa. Acontece que, para o desenvolvimento de jogos, a programação imperativa é muito mais preferível.
fonte
DrawableGameComponent
.player.DrawOrder = 100;
método do que o imperativo para a ordem do sorteio. Estou terminando um jogo Flixel agora, que desenha objetos na ordem em que você os adiciona à cena. O sistema FlxGroup alivia um pouco disso, mas ter algum tipo de classificação do Z-index embutido seria bom.Hummm. Tenho desafio este por uma questão de equilíbrio aqui, eu tenho medo.
Parece haver 5 cobranças na resposta de Andrew, então tentarei lidar com cada uma delas:
1) Os componentes são um design desatualizado e abandonado.
A idéia do padrão de design dos componentes existia muito antes do XNA - em essência, é simplesmente uma decisão de fazer objetos, em vez do objeto de jogo abrangente, a casa do seu próprio comportamento de Atualização e Empate. Para objetos em sua coleção de componentes, o jogo chamará esses métodos (e alguns outros) automaticamente no momento apropriado - crucialmente, o objeto do jogo não precisa, por si só, 'conhecer' esse código. Se você deseja reutilizar suas classes de jogos em jogos diferentes (e, portanto, objetos de jogos diferentes), esse desacoplamento de objetos ainda é uma boa idéia. Uma rápida olhada nas mais recentes demos (produzidas profissionalmente) do XNA4.0 do AppHub confirma minha impressão de que a Microsoft ainda está (justamente na minha opinião) firmemente por trás disso.
2) Andrew não consegue pensar em mais de duas classes de jogo reutilizáveis.
Eu posso. Na verdade, eu posso pensar em cargas! Acabei de verificar minha lib compartilhada atual e ela compreende mais de 80 componentes e serviços reutilizáveis . Para dar um sabor, você pode ter:
Qualquer ideia de jogo em particular precisará de pelo menos 5 a 10 itens deste conjunto - garantida - e eles podem ser totalmente funcionais em um jogo completamente novo com uma linha de código.
3) É preferível controlar a ordem de desenho pela ordem das chamadas de desenho explícitas do que usar a propriedade DrawOrder do componente.
Bem, eu basicamente concordo com Andrew neste caso. MAS, se você deixar todas as propriedades DrawOrder de seu componente como padrão, ainda poderá controlar explicitamente a ordem dos desenhos pela ordem em que os adiciona à coleção. Isso é realmente idêntico em termos de 'visibilidade' à ordenação explícita de suas chamadas de sorteio manual.
4) Chamar uma carga dos métodos Draw do objeto é mais 'leve' do que tê-los chamados por um método de estrutura existente.
Por quê? Além disso, em todos os projetos, exceto os mais triviais, sua lógica de atualização e o código Draw ofuscarão totalmente sua chamada de método em termos de desempenho atingido em qualquer caso.
5) É preferível colocar o código em um arquivo, durante a depuração, do que tê-lo no arquivo do objeto individual.
Bem, primeiro, quando estou depurando no Visual Studio, a localização de um ponto de interrupção em termos de arquivos em disco é quase invisível atualmente - o IDE lida com a localização sem nenhum drama. Mas considere também por um momento que você tem um problema com o desenho do Skybox. Ir para o método Draw do Skybox é realmente mais oneroso do que localizar o código de desenho do skybox no método de desenho mestre (presumivelmente muito longo) no objeto Game? Não, na verdade não - na verdade eu diria que é exatamente o contrário.
Então, em resumo - por favor, dê uma boa olhada nos argumentos de Andrew aqui. Não acredito que eles realmente dêem motivos substanciais para escrever código de jogo emaranhado. As vantagens do padrão de componente desacoplado (usado criteriosamente) são enormes.
fonte
Drawable
)GameComponent
para fornecer sua arquitetura de jogo, devido à perda de controle explícito da ordem de desenho / atualização (com a qual você concorda em # 3) a rigidez de suas interfaces (que você não endereça).Discordo um pouco de Andrew, mas também acho que há tempo e lugar para tudo. Eu não usaria um
Drawable(GameComponent)
para algo tão simples quanto um Sprite ou Inimigo. No entanto, eu o usaria para umSpriteManager
ouEnemyManager
.Voltando ao que Andrew disse sobre desenhar e atualizar a ordem, acho
Sprite.DrawOrder
que seria muito confuso para muitos sprites. Além disso, é relativamente fácil configurar umDrawOrder
eUpdateOrder
para uma pequena (ou razoável) quantidade de gerentes que são(Drawable)GameComponents
.Eu acho que a Microsoft teria acabado com eles se eles realmente estivessem tão rígidos e ninguém os usasse. Mas não o fizeram porque funcionam perfeitamente bem, se o papel estiver correto. Eu mesmo os uso para desenvolver
Game.Services
atualizações em segundo plano.fonte
Eu estava pensando sobre isso também, e me ocorreu: Por exemplo, para roubar o exemplo no seu link, em um jogo RTS, você poderia transformar cada unidade em uma instância de componente do jogo. OK.
Mas você também pode criar um componente UnitManager, que mantém uma lista de unidades, as desenha e as atualiza conforme necessário. Acho que o motivo pelo qual você deseja transformar suas unidades em componentes de jogos é compartimentar o código, então essa solução alcançaria adequadamente esse objetivo?
fonte
Nos comentários, publiquei uma versão condensada da minha resposta antiga resposta:
Usar
DrawableGameComponent
bloqueia você em um único conjunto de assinaturas de método e em um modelo de dados retido específico. Normalmente, essas são as escolhas erradas inicialmente, e o bloqueio dificulta significativamente a evolução do código no futuro.Aqui tentarei ilustrar por que esse é o caso, publicando algum código do meu projeto atual.
O objeto raiz que mantém o estado do mundo do jogo tem um
Update
método parecido com este:Existem vários pontos de entrada para
Update
, dependendo se o jogo está rodando na rede ou não. É possível, por exemplo, que o estado do jogo seja revertido para um ponto anterior no tempo e depois simulado novamente.Existem também alguns métodos adicionais de atualização, como:
Dentro do estado do jogo, há uma lista de atores a serem atualizados. Mas isso é mais do que uma simples
Update
ligação. Existem passes adicionais antes e depois para lidar com a física. Há também algumas coisas sofisticadas para desovar / destruir adiado de atores, bem como transições de nível.Fora do estado do jogo, há um sistema de interface do usuário que precisa ser gerenciado de forma independente. Mas algumas telas na interface do usuário também precisam ser executadas em um contexto de rede.
Para o desenho, devido à natureza do jogo, existe um sistema personalizado de classificação e seleção que recebe entradas desses métodos:
E então, depois de descobrir o que desenhar, chama automaticamente:
O
tag
parâmetro é necessário porque alguns atores podem se apegar a outros atores - e, portanto, precisam ser capazes de desenhar acima e abaixo do ator retido. O sistema de classificação garante que isso aconteça na ordem correta.Há também o sistema de menus para desenhar. E um sistema hierárquico para desenhar a interface do usuário, ao redor do HUD, ao redor do jogo.
Agora compare esses requisitos com o que
DrawableGameComponent
fornece:Não há uma maneira razoável de passar de um sistema
DrawableGameComponent
para o sistema descrito acima. (E esses não são requisitos incomuns para um jogo de complexidade ainda modesta).Além disso, é muito importante observar que esses requisitos não surgiram simplesmente no início do projeto. Eles evoluíram ao longo de muitos meses de trabalho de desenvolvimento.
O que as pessoas normalmente fazem, se iniciam o
DrawableGameComponent
caminho, é começar a desenvolver hacks:Em vez de adicionar métodos, eles fazem uma conversão feia para o seu próprio tipo base (por que não usar isso diretamente?).
Em vez de substituir o código para classificar e chamar
Draw
/Update
, eles fazem algumas coisas muito lentas para alterar os números de pedidos rapidamente.E o pior de tudo: em vez de adicionar parâmetros aos métodos, eles começam a "passar" "parâmetros" em locais de memória que não são a pilha (o uso
Services
para isso é popular). Isso é complicado, propenso a erros, lento, frágil, raramente seguro para threads e provavelmente se tornará um problema se você adicionar redes, criar ferramentas externas e assim por diante.Ele também faz reutilização de código mais difícil , porque agora as suas dependências estão escondidos dentro do "componente", em vez de publicado no limite API.
Ou, eles quase veem o quão louco isso tudo é e começam a fazer "gestores"
DrawableGameComponent
para solucionar esses problemas. Exceto que eles ainda têm esses problemas nos limites do "gerente".Invariavelmente, esses "gerentes" poderiam ser facilmente substituídos por uma série de chamadas de método na ordem desejada. Qualquer outra coisa é muito complicada.
Obviamente, quando você começa a empregar esses hacks, é preciso um trabalho real para desfazê- los quando você perceber que precisa de mais do que aquilo que
DrawableGameComponent
oferece. Você se torna " trancado ". E a tentação é muitas vezes continuar acumulando hacks - o que na prática o atrapalha e limita os tipos de jogos que você pode fazer aos relativamente simplistas.Muitas pessoas parecem chegar a esse ponto e têm uma reação visceral - possivelmente porque usam
DrawableGameComponent
e não querem aceitar "estar errado". Então eles se prendem ao fato de que é pelo menos possível fazê-lo "funcionar".Claro que pode ser feito para "trabalhar"! Somos programadores - fazer as coisas funcionarem sob restrições incomuns é praticamente o nosso trabalho. Mas essa restrição não é necessária, útil ou racional ...
O que é particularmente espantosa sobre é que é surpreendentemente trivial para side-passo toda esta questão. Aqui, eu vou te dar o código:
(É simples adicionar a classificação de acordo com
DrawOrder
eUpdateOrder
. Uma maneira simples é manter duas listas e usá-laList<T>.Sort()
. Também é trivial manipularLoad
/UnloadContent
.)Não é importante o que esse código realmente é . O importante é que eu possa modificá-lo trivialmente .
Quando percebo que preciso de um sistema de temporização diferente
gameTime
, ou preciso de mais parâmetros (ou umUpdateContext
objeto inteiro com cerca de 15 coisas), ou preciso de uma ordem de operações completamente diferente para desenhar, ou preciso de alguns métodos extras para diferentes passes de atualização, posso apenas fazer isso .E eu posso fazer isso imediatamente. Não preciso me preocupar em escolher
DrawableGameComponent
meu código. Ou pior: corte-o de alguma forma que tornará ainda mais difícil a próxima vez que essa necessidade surgir.Na verdade, existe apenas um cenário em que é uma boa escolha
DrawableGameComponent
: se você estiver literalmente criando e publicando middleware no estilo arrastar e soltar componentes para o XNA. Qual era o seu propósito original. O que - acrescentarei - ninguém está fazendo!(Da mesma forma, só faz sentido usar
Services
ao criar tipos de conteúdo personalizados paraContentManager
.)fonte
DrawableGameComponent
certos componentes, especialmente em itens como telas de menu (menus, muitos botões, opções e outros itens) ou sobreposições de FPS / depuração você mencionou. Nesses casos, você geralmente não precisa de um controle refinado sobre a ordem de desenho e acaba com menos código que precisa escrever manualmente (o que é uma coisa boa, a menos que você precise escrever esse código). Mas acho que esse é o cenário de arrastar e soltar que você estava mencionando.