Como evitar o objeto deus do GameManager?


51

Acabei de ler uma resposta para uma pergunta sobre a estruturação do código do jogo . Isso me fez pensar sobre a GameManagerclasse onipresente e como ela frequentemente se torna um problema em um ambiente de produção. Deixe-me descrever isso.

Primeiro, há prototipagem. Ninguém se importa em escrever um ótimo código, apenas tentamos executar algo para ver se a jogabilidade aumenta.

Depois, há um sinal verde e, em um esforço para limpar as coisas, alguém escreve a GameManager. Provavelmente para segurar um monte de GameStates, talvez para armazenar alguns GameObjects, nada grande, realmente. Um gerente bonitinho e pequeno.

No reino pacífico da pré-produção, o jogo está se moldando bem. Os programadores têm noites de sono adequadas e muitas idéias para arquitetar a coisa com ótimos padrões de design.

Então a produção começa e logo, é claro, há tempo de crise. A dieta balanceada se foi há muito tempo, o rastreador de bugs está cheio de problemas, as pessoas estão estressadas e o jogo tem que ser lançado ontem.

Nesse ponto, geralmente, a bagunça GameManageré realmente grande (para ser educado).

A razão para isso é simples. Afinal, ao escrever um jogo, bem ... todo o código fonte está aqui para gerenciar o jogo . É fácil apenas adicionar esse pequeno recurso extra ou correção de erros no GameManager, onde todo o resto já está armazenado de qualquer maneira. Quando o tempo se torna um problema, não há como escrever uma classe separada ou dividir esse gerente gigante em sub-administradores.

É claro que este é um anti-padrão clássico: o objeto divino . É uma coisa ruim, uma dor para mesclar, uma dor para manter, uma dor para entender, uma dor para transformar.

O que você sugeriria para impedir que isso acontecesse?

EDITAR

Eu sei que é tentador culpar o nome. É claro que criar uma GameManagerclasse não é uma ótima idéia. Mas a mesma coisa pode acontecer com um pano limpo GameApplicationou GameStateMachineou GameSystemque acaba por ser o duto-fita favorita até o final de um projeto. Não importa o nome, você tem essa classe em algum lugar do código do jogo, é apenas um embrião por enquanto: você ainda não sabe que monstro ele se tornará. Portanto, não estou esperando respostas para "culpar os nomes". Eu quero uma maneira de impedir que isso aconteça, uma arquitetura de codificação e / ou um processo que uma equipe possa seguir sabendo que isso acontecerá em algum momento da produção. É muito ruim jogar fora, digamos, um mês de correções de erros e recursos de última hora, apenas porque todos eles estão agora em um gerente enorme e inatingível.


Quer se chame GameManager ou ControllerOfTheGame, ou o que você quiser chamá-lo, quero dizer evitar uma classe encarregada de controlar a lógica de todo o jogo. Você sabe que está fazendo isso quando faz, independentemente do que planeja chamá-lo. Eu acho que no meu último jogo eu tinha um controlador de nível que começou apenas para manter o estado do nível de economia, mas se eu tivesse parado e pensado sobre isso, era óbvio que esse objeto era para lidar com mais detalhes do que deveria.
Brandon

@brandon Deixe-me reformular minha pergunta para você: como você controla a lógica de todo o jogo sem se expor ao GameManagerefeito que descrevi acima?
Laurent Couvidou

Depende da lógica. As coisas óbvias, como jogador, inimigo, estrutura, etc., que claramente possuem sua própria lógica, são colocadas em suas respectivas classes. O que você parece estar perguntando mais é lidar com o estado do jogo. Geralmente haverá uma classe que monitora o estado de todo o jogo, mas, na minha experiência, é uma das últimas coisas com as quais você precisa se preocupar ao prototipar, e seu objetivo deve ser muito claro. Basicamente, você faz um planejamento inicial antes da criação de protótipos ou começa do zero quando inicia a produção para evitar o uso de classes indesejadas que eram apenas para teste.
Brandon

Curiosamente, o XNA cria esse desastre em potencial para você. Por padrão, uma classe Game é criada para gerenciar tudo. Ele contém os principais métodos de atualização, desenho, inicialização e carregamento. Na verdade, estou no processo de migrar para um novo projeto, porque meu jogo (não tão complexo) se tornou muito difícil de gerenciar com tudo o que estava lotado nessa classe. Além disso, os tutoriais da Microsoft parecem encorajá-lo.
Fibericon

Respostas:


23

Depois, há um sinal verde e, em um esforço para limpar as coisas, alguém escreve um GameManager. Provavelmente para armazenar um monte de GameStates, talvez para armazenar alguns GameObjects, nada grande, na verdade. Um gerente bonitinho e pequeno.

Sabe, enquanto eu lia isso, eu tinha pequenos alarmes na minha cabeça. Um objeto com o nome "GameManager" nunca será bonito ou pequeno. E alguém fez isso para limpar o código? Como era antes? OK, piadas à parte: o nome de uma classe deve ser uma indicação clara do que a classe faz, e isso deve ser uma coisa (aka: princípio de responsabilidade única ).

Além disso, você ainda pode acabar com um objeto como o GameManager, mas claramente ele existe em um nível muito alto e deve se preocupar com tarefas de alto nível. Mantendo um catálogo de objetos do jogo? Possivelmente. Facilitar a comunicação entre objetos do jogo? Certo. Calculando colisões entre objetos? Não! É também por isso que o nome Manager é desaprovado - é muito amplo e permite muitos abusos sob esse banner.

Uma regra prática rápida no tamanho das classes: se você estiver executando várias centenas de linhas de código por classe, algo está começando a dar errado. Sem ser excessivamente zeloso, qualquer coisa acima de, digamos, 300 LOC é um cheiro de código para mim, e se você estiver ultrapassando as 1000, os sinos de aviso devem disparar. Acreditando que, de alguma forma, que 1000 linhas de código são mais simples de entender do que 4 classes bem estruturadas de 250 cada, você está se iludindo.

Quando o tempo se torna um problema, não há como escrever uma classe separada ou dividir esse gerente gigante em sub-administradores.

Eu acho que é esse o caso apenas porque o problema pode se propagar até o ponto em que tudo está uma bagunça completa. A prática de refatoração é realmente o que você está procurando - você precisa melhorar continuamente o design do código em pequenos incrementos .

O que você sugeriria para impedir que isso acontecesse?

O problema não é tecnológico; portanto, você não deve procurar por soluções tecnológicas. O problema é o seguinte: há uma tendência em sua equipe de criar trechos de código monolíticos e a crença de que, de alguma forma, é benéfico a médio / longo prazo trabalhar dessa maneira. Parece também que a equipe não possui uma forte liderança arquitetônica, que guiaria a arquitetura do jogo (ou pelo menos, essa pessoa está ocupada demais para executar esta tarefa). Basicamente, a única saída é fazer com que os membros da equipe reconheçam que esse pensamento está errado. Ninguém favorece. A qualidade do produto piorará e a equipe passará apenas mais noites corrigindo as coisas.

A boa notícia é que os benefícios imediatos e tangíveis da escrita de código limpo são tão grandes que quase todos os desenvolvedores percebem seus benefícios rapidamente. Convença a equipe a trabalhar dessa maneira por um tempo, os resultados farão o resto.

A parte difícil é que desenvolver uma sensação do que constitui um código ruim (e um talento para criar rapidamente um design melhor) é uma das habilidades mais difíceis de aprender no desenvolvimento. Minha sugestão se baseia na esperança de que você tenha alguém sênior na equipe que possa fazer isso - é muito mais fácil convencer as pessoas dessa maneira.

Editar - Um pouco mais de informação:

Em geral, não acho que seu problema esteja limitado ao desenvolvimento de jogos. Na sua essência, é um problema de engenharia de software, portanto, meus comentários nessa direção. O que pode ser diferente é a natureza da indústria de desenvolvimento de jogos, se seus resultados são mais orientados a prazos do que outros tipos de desenvolvimento, não tenho certeza.

Especificamente para o desenvolvimento de jogos, a resposta aceita para esta pergunta no StackOverflow sobre dicas "especialmente de arquitetura de jogos", diz:

Siga os princípios sólidos de design orientado a objetos ....

Isto é essencialmente exatamente o que estou dizendo. Quando estou sob pressão, também me pego escrevendo grandes pedaços de código, mas ensinei que isso é dívida técnica. O que tende a funcionar bem para mim é passar a primeira metade (ou três quartos) do dia produzindo uma grande quantidade de código de qualidade média, e depois relaxar e pensar um pouco; faça um pouco de design na minha cabeça, ou no papel / quadro branco, sobre como melhorar um pouco o código. Freqüentemente, percebo código repetitivo e sou capaz de reduzir o total de linhas de código, interrompendo tudo, melhorando a legibilidade. Desta vez, o investimento se paga tão rapidamente, que chamá-lo de "investimento" parece bobagem - muitas vezes, apanhamos bugs que poderiam ter desperdiçado metade do meu dia (uma semana depois), caso eu permitisse continuar.

  • Corrija as coisas no mesmo dia em que você as codifica.
  • Você ficará feliz em fazer isso em poucas horas.

Vir a acreditar realmente no que foi dito acima é difícil; Consegui fazer isso para o meu próprio trabalho apenas porque experimentei repetidamente os efeitos. Mesmo assim, ainda é difícil para mim justificar a correção do código quando eu poderia estar produzindo mais ... Então, eu definitivamente entendo de onde você é. Infelizmente, esse conselho é talvez genérico demais e não é fácil de executar. Eu acredito fortemente nisso, no entanto! :)

Para responder seu exemplo específico:

O Código Limpo do tio Bob faz um trabalho incrível ao resumir como é um código de boa qualidade. Por acaso concordo com quase todo o seu conteúdo. Então, quando penso no seu exemplo de uma classe de gerente de 30.000 LOC, não posso realmente concordar com a parte "boa razão". Não quero parecer ofensivo, mas pensar dessa maneira levará ao problema. Não há uma boa razão para ter tanto código em um único arquivo - são quase 1000 páginas de texto! Qualquer benefício da localidade (velocidade de execução ou "simplicidade" do projeto) será imediatamente anulado pelos desenvolvedores que estão completamente atolados, tentando navegar nessa monstruosidade, e isso é antes mesmo de discutirmos a fusão etc.

Se você não estiver convencido, minha melhor sugestão seria pegar uma cópia do livro acima e dar uma olhada nele. A aplicação desse tipo de pensamento leva as pessoas a criar voluntariamente um código limpo e auto-explicativo, que é bem estruturado.


Esta não é uma falha que eu já vi uma vez. Eu já vi isso em várias equipes, para vários projetos. Eu já vi muitas pessoas talentosas e estruturadas lutando com isso. Quando sob pressão, o limite de 300 linhas de código não é algo com que seu produtor se preocupa. Nem o jogador aliás. Claro que isso é agradável e doce, mas o diabo está nos detalhes. O pior caso de um GameManagerque eu vi até agora foi em torno de 30.000 linhas de código, e tenho certeza de que a maioria estava aqui por uma boa razão. Isso é extremo, é claro, mas essas coisas acontecem no mundo real. Estou pedindo uma maneira de limitar esse GameManagerefeito.
Laurent Couvidou

@lorancou - Adicionei algumas informações extras ao post, foi um pouco demais para um comentário, espero que lhe dê uma idéia um pouco mais, mesmo que eu não consiga apontar nenhuma arquitetura concreta exemplos.
Daniel B

Ah, eu não li esse livro, então essa é definitivamente uma boa sugestão. Ah, e eu não quis dizer que há um bom motivo para adicionar código em uma classe de milhares de linhas. Eu quis dizer que todo o código nesse tipo de objeto divino faz algo útil. Provavelmente, eu escrevi isso um pouco rápido demais;)
Laurent Couvidou 16/04

@lorancou Hehe, isso é bom ... que maneira mentiras insanidade :)
Daniel B

"A prática de refatoração é realmente o que você está procurando - você precisa melhorar continuamente o design do código em pequenos incrementos". Eu acho que você tem um ponto muito forte lá. A bagunça atrai a bagunça, essa é a verdadeira razão por trás disso, então a bagunça deve ser combatida a longo prazo. Eu vou aceitar esta resposta, mas isso não significa que não há valor nos outros!
Laurent Couvidou

7

Isso não é realmente um problema com a programação de jogos, mas com a programação em geral.

todo o código fonte está aqui para gerenciar o jogo.

É por isso que você deve desaprovar qualquer codificador orientado a objetos que sugira classes com "Manager" no nome. Para ser justo, acho que é praticamente impossível evitar objetos "semideuses" no jogo, mas apenas os chamamos de "sistema".

Qualquer software tende a ficar sujo quando o prazo está próximo. Isso faz parte do trabalho. E não há nada inerentemente errado com a " programação do tipo de duto ", principalmente quando você deve enviar seu produto em algumas horas. Você não pode se livrar totalmente desse problema, basta reduzi-lo.

Embora obviamente, se você é tentado a colocar coisas no GameManager, isso significa que você não tem uma base de código muito saudável. Pode haver dois problemas:

  • Seu código é muito complexo. Muitos padrões, otimização prematura, ninguém entende mais o fluxo. Tente usar mais TDD ao desenvolver, se puder, pois é uma solução muito boa para combater a complexidade.

  • Seu código não está bem estruturado. Você está (ab) usando a variável globals, as classes não são fracamente acopladas, não há interface alguma, sistema de eventos e assim por diante.

Se o código parecer bom, basta lembrar, para cada projeto, "o que deu errado" e evitá-lo na próxima vez.

Finalmente, também poderia ser um problema de gerenciamento de equipe: havia tempo suficiente? Todo mundo sabia sobre a arquitetura que você estava procurando? Quem diabos permitiu um commit com uma classe com a palavra "Manager"?


Não há nada de errado com a programação da fita adesiva, eu lhe digo isso. Mas tenho certeza de que as classes de gerentes estão presentes nos mecanismos de jogo, pelo menos eu já as vi muito. Eles são úteis desde que não cresçam muito ... O que há de errado com um SceneManagerou um NetworkManager? Não vejo por que devemos evitá-los ou como a substituição de -Manager por -System ajuda. Estou procurando sugestões para uma arquitetura de substituição para o que geralmente entra em GameManageralgo menos propenso a inchaço de código.
Laurent Couvidou

O que há de errado com um SceneManager? Bem, qual é a diferença entre uma cena e um SceneManager? Uma rede e um gerente de rede? Por outro lado: não acho que isso seja um problema de arquitetura, mas se você pudesse dar um exemplo concreto de algo que está no GameManager e não deveria estar lá, seria mais fácil sugerir uma solução.
Raveline

OK, então esqueça a coisa -Manager. Digamos que você tenha uma classe que lida com os estados do seu jogo, vamos chamá-lo GameStatesCollection. No começo, você terá apenas alguns estados de jogo e maneiras de alternar entre eles. Depois, há as telas de carregamento que vêm e complicam tudo isso. Então, algum programador precisa lidar com um erro quando o usuário ejeta o disco enquanto você alterna de Nível_A para Nível_B, e a única maneira de corrigi-lo sem passar meio dia em refatoração é GameStatesCollection. Boom, um objeto divino.
Laurent Couvidou

5

Então, trazendo uma solução possível do mundo mais corporativo: você verificou a inversão da injeção de controle / dependência?

Basicamente, você obtém essa estrutura que é um contêiner para todo o resto do seu jogo. Ele, e somente ele, sabe como conectar tudo e quais são as relações entre seus objetos. Nenhum de seus objetos instancia nada dentro de seus próprios construtores. Em vez de criar o que precisam, eles pedem o que precisam da estrutura de IoC; após a criação, a estrutura de IoC "sabe" qual objeto é necessário para satisfazer a dependência e a preenche. FTW de acoplamento frouxo.

Quando sua classe EnemyTreasure solicita ao contêiner um objeto GameLevel, a estrutura sabe qual instância deve ser fornecida. Como ele sabe? Talvez ele tenha sido instanciado mais cedo, talvez ele solicite algum GameLevelFactory nas proximidades. A questão é que o EnemyTreasure não sabe de onde veio a instância GameLevel. Só sabe que sua dependência agora está satisfeita e pode continuar com sua vida.

Ah, vejo que sua classe PlayerSprite implementa IUpdateable. Bem, isso é ótimo, porque a estrutura inscreve automaticamente todos os objetos IUpdateable no Timer, que é onde está o loop principal do jogo. Todo Timer :: tick () chama automaticamente todos os métodos IUpdatedable :: update (). Fácil né?

Realisticamente, você não precisa dessa estrutura bighugeohmygod para fazer isso por você: você mesmo pode fazer as partes importantes. O ponto é que, se você se encontra criando objetos do tipo -Manager, é provável que não esteja distribuindo corretamente suas responsabilidades entre suas classes ou que esteja faltando algumas em algum lugar.


Interessante, a primeira vez que ouvi falar de IoC. Isso pode ser demais para a programação de jogos, mas vou verificar!
Laurent Couvidou

IoC, não costumávamos chamar isso de senso comum?
Br16

Com meia chance, um engenheiro criará uma estrutura de qualquer coisa. (= Além disso, eu não estou defendendo usando a estrutura, eu estou defendendo o padrão arquitetônico.
drhayes

3

No passado, geralmente termino com uma classe divina se eu fizer o seguinte:

  • sente-se no editor de jogos / IDE / bloco de notas antes de escrever um diagrama de classes (ou apenas um esboço dos objetos principais)
  • Comece com uma ideia muito vaga do jogo e codifique-o imediatamente, depois comece a iterar em novas idéias à medida que elas surgirem
  • crie um arquivo de classe chamado GameManager quando não estiver criando um jogo baseado em estado, como uma estratégia baseada em turnos, na qual o GameManager é realmente um objeto de domínio
  • não se preocupe com a organização do código desde o início

Isso pode ser controverso, o que eu rirei se for, mas não consigo pensar em uma única vez, mesmo na prototipagem, que você não deve escrever um diagrama de classes muito básico antes de escrever um protótipo reproduzível. Costumo testar idéias de tecnologia antes de planejar, mas é isso. Então, se eu quisesse criar um portal, eu escreveria um código muito básico para que os portais funcionassem, depois diria ok, é ótimo para um planejamento menor, para que eu possa trabalhar no protótipo reproduzível.


1

Eu sei que essa pergunta é antiga agora, mas pensei em adicionar meus 2 centavos.

Ao projetar jogos, quase sempre tenho um objeto Jogo. No entanto, é um nível muito alto e realmente não "faz nada" em si.

Por exemplo, ele diz à classe Graphics para Render, mas não tem nada a ver com o processo de renderização. Pode pedir ao LevelManager para carregar um novo nível, mas não tem nada a ver com o processo de carregamento.

Em resumo, eu uso um objeto semi-divino para orquestrar o uso das outras classes.


2
Isso não é muito semelhante a um deus - isso é chamado de abstração. :)
Vaughan Hilts
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.