Quero me livrar do meu padrão de design do tipo tudo estático e global, mas como?

11

Estou fazendo um pequeno rastreador de masmorra no espaço e gostaria de ouvir alguns conselhos sobre como melhorar o back-end do motor. Basicamente, atualmente tudo é baseado em um upload de gerentes:

  • BackgroundManager: possui um AddBackground(image, parallax)método para criar efeitos interessantes de fundo.
  • ConfigManager: lê / cria o arquivo de configuração e também retém os dados lidos nesse arquivo de configuração.
  • DrawManager: possui um Draw(sprite)método para desenhar coisas na tela, coisas assim SetView(view), GetView()etc.
  • EntityManager: mantém todas as entidades ativas e possui métodos para adicionar, remover e localizar entidades.
  • DungeonManager (na verdade chamado de GridManager, mas isso é para simplificar): tem métodos como GenerateDungeon(), PlaceEnemies(), PlaceFinish()etc.

Agora nem estou na metade do caminho listando todos os meus gerentes, então acho que isso precisa mudar. Meu jogo também é baseado em Telas, o que o torna mais irritante porque não preciso da metade das coisas que meus gerentes estão gerenciando, por exemplo, no menu principal (não preciso de física / entidades / masmorras no meu menu principal). !)

Agora, pensei em tornar os gerentes não estáticos e em dar ao meu ScreenMainGame uma instância de todos os gerentes específicos de jogos. Mas isso torna a chamada / obtenção de algo relacionado aos gerentes uma grande bagunça ... por exemplo, se eu quisesse desenhar minha masmorra de qualquer lugar que não ScreenMainGame.Draw()estivesse, eu teria que fazer isso ...

((ScreenMainGame)ScreenManager.CurrentScreen).GridManager.Draw()

Isso é realmente feio.

Então, colegas desenvolvedores de jogos, algum de vocês tem uma idéia de como resolver essa bagunça? Eu seria apreciado!

Dlaor
fonte
Não sei exatamente como ajudá-lo, mas recomendo a leitura de um bom livro sobre padrões de design. Eu tenho lido os padrões de design do Head First e é muito fácil de entender. Os exemplos estão em Java, mas acho que seria perto o suficiente do C # para ajudá-lo. Desculpe se minha sugestão é muito iniciante.
Amplify91
O que você está usando para interagir com a placa gráfica? XNA? SlimDX?
BlueRaja - Danny Pflughoeft
@BlueRaja: Estou usando o SFML.NET.
Dlaor
Nesse caso, você deve simplesmente usar os métodos normais para lidar com esses problemas em C #: veja meu último link abaixo e também O que é injeção de dependência?
BlueRaja - Danny Pflughoeft 29/07

Respostas:

10

E quanto a um mecanismo baseado em componente ?

Você teria uma classe principal denominada Engine, que manteria uma lista de GameScreens, e eles próprios manteriam uma lista de Components.

O motor tem um Updatee um Drawmétodo e ambos chamada os GameScreen's Updatee Drawmétodos, que se passar por cada componente e chamada Updatee Draw.

Apresentado assim, concordo que parece um design pobre e repetitivo. Mas acredite, meu código ficou muito mais limpo usando uma abordagem baseada em componentes do que com todas as minhas classes antigas de gerenciador .

É muito mais simples manter esse código também, já que você está passando por uma grande hierarquia de classes e não precisa pesquisar BackgroundManagertodos os diferentes contextos específicos. Você só tem um ScrollingBackground, ParallaxBackground, StaticBackground, etc., que todos derivam de uma Backgroundclasse.

Você criará um mecanismo bastante sólido que poderá ser reutilizado em todos os seus projetos com muitos componentes e métodos auxiliares usados ​​com frequência (por exemplo, FrameRateDisplayercomo um utilitário de depuração, uma Spriteclasse como um sprite básico com métodos de textura e extensão para vetores e geração de números aleatórios).

Você não teria mais uma BackgroundManagerclasse, mas uma Backgroundclasse que se autodirecionaria.

Quando o jogo começa, tudo o que você precisa fazer é basicamente isso:

// when declaring variables:
Engine engine;

// when initializing:
engine = new Engine();
engine.Initialize();
engine.LoadContent();
engine.AddGameScreen(new MainMenuScreen());

// when updating:
engine.Update();

// when drawing:
engine.Draw();

E é isso para o seu código de início do jogo.

Em seguida, para a tela do menu principal:

class MainMenuScreen : MenuScreen // where MenuScreen derives from the GameScreen class
{
    const int ENEMY_COUNT = 10;

    StaticBackground background;
    Player player;
    List<Enemy> enemies;

    public override void Initialize()
    {
        background = new StaticBackground();
        player = new Player();
        enemies = new List<Enemy>();

        base.AddComponent(background); // defined within the GameScreen class
        base.AddComponent(player);
        for (int i = 0; i < ENEMY_COUNT; ++i)
        {
            Enemy newEnemy = new Enemy();
            enemies.Add(newEnemy);
            base.AddComponent(newEnemy);
        }
    }
}

Você entende a ideia geral.

Você também manteria a referência Enginedentro de todas as suas GameScreenclasses, para poder adicionar novas telas mesmo dentro de uma GameScreenclasse (por exemplo, quando o usuário clicar no botão StartGame enquanto estiver dentro do seu MainMenuScreen, você poderá fazer a transição para oGameplayScreen ).

O mesmo vale para a Componentclasse: ela deve conter a referência de seu pai GameScreen, para ter acesso à Engineclasse e ao pai GameScreenpara adicionar novos componentes (por exemplo, você pode criar uma classe relacionada ao HUD chamada DrawableButtonque contém um DrawableTextcomponente e um StaticBackgroundcomponente).

Você pode até aplicar outros padrões de design depois disso, como o "padrão de design de serviço" (não tenho certeza sobre o nome exato), onde você pode manter diferentes serviços úteis em sua Engineclasse (basta manter uma lista de se IServicepermitir que outras classes adicionem serviços ) por exemplo, eu manteria umCamera2D componente em todo o meu projeto como um serviço para aplicar sua transformação ao desenhar outros componentes. Isso evita ter que passar como parâmetro em qualquer lugar.

Em conclusão, certamente pode haver outros projetos melhores para um mecanismo, mas achei o mecanismo proposto por esse link muito elegante, extremamente fácil de manter e reutilizar. Pessoalmente, eu recomendaria pelo menos tentar.

Jesse Emond
fonte
1
O XNA suporta tudo isso pronto para uso por meio de GameComponentserviços e serviços. Não há necessidade de uma Engineaula separada .
BlueRaja - Danny Pflughoeft
+1 para esta resposta. Além disso, faria sentido colocar o desenho em um segmento separado do segmento de atualização do jogo?
F20k
1
@BlueRaja - DannyPflughoeft: De fato, mas eu assumi que o XNA não foi usado, então expliquei um pouco mais sobre como fazê-lo.
precisa
@ f20k: Sim, da mesma maneira que o XNA faz. Eu não sei como isso é implementado. = / Deve haver um artigo em algum lugar na internet sobre como fazê-lo. :)
Jesse Emond 29/07
Sim, eu não estou usando XNA, mas isso parece uma boa alternativa para o que estou fazendo. Atualmente, estou lendo o livro "Head First Design Patterns" para ver se há mais alternativas. Felicidades!
Dlaor
1

O que você deseja fazer é separar seu código em componentes e serviços. O XNA já possui suporte interno para eles.

Veja este artigo no GameComponentse serviços. Então veja esta resposta sobre o uso de serviços no XNA e essa resposta mais geral sobre serviços em qualquer idioma.

BlueRaja - Danny Pflughoeft
fonte
-3

Só porque eles são estáticos ou globais não significa que eles sempre devem ser carregados / inicializados. Use um padrão Singleton e permita que os gerentes sejam liberáveis ​​e carregados novamente sob demanda.

Bo Buchanan
fonte
4
Um singleton é apenas uma fachada para o problema subjacente que o OP está tentando resolver aqui.