Acabei de ler uma resposta para uma pergunta sobre a estruturação do código do jogo . Isso me fez pensar sobre a GameManager
classe 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 GameManager
classe não é uma ótima idéia. Mas a mesma coisa pode acontecer com um pano limpo GameApplication
ou GameStateMachine
ou GameSystem
que 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.
fonte
GameManager
efeito que descrevi acima?Respostas:
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.
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 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:
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.
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.
fonte
GameManager
que 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 esseGameManager
efeito.Isso não é realmente um problema com a programação de jogos, mas com a programação em geral.
É 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"?
fonte
SceneManager
ou umNetworkManager
? 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 emGameManager
algo menos propenso a inchaço de código.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.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.
fonte
No passado, geralmente termino com uma classe divina se eu fizer o seguinte:
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.
fonte
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.
fonte