XNA - Classes estáticas de bibliotecas de jogos em execução após extensões de pipeline de conteúdo, como evitar o problema?

8

Tudo bem, formulado dessa maneira, tenho certeza que parece obscuro, mas farei o possível para descrever meu problema.

Primeiro, deixe-me explicar o que há de errado com meu código-fonte real.

Estou fazendo um jogo de plataformas, e aqui estão os componentes atuais do meu jogo: um Level , que contém sua própria grade de peças e também as folhas de peças e uma peça , que apenas mantém o nome da sua peça e o índice dentro do folha de azulejo. Tudo isso está em um projeto de jogo XNA separado, chamado GameLib.

Aqui está como meus níveis são formatados em um arquivo de texto:

x x . . . . .

. . . b g r .

. . . . . . .

X X X X X X X

Onde .representa um bloco vazio, brepresenta uma plataforma azul, Xrepresenta um bloco com uma cor aleatória etc.

Em vez de fazer todo o trabalho de carregar um nível na classe Level , decidi aproveitar a extensão do pipeline de conteúdo e criar um LevelContentPipelineExtension.

Assim que comecei, percebi rapidamente que não queria codificar mais de 20 linhas do tipo:

if (symbol == 'b')
    return new Tile(/*...*/);

Portanto, na esperança de pelo menos centralizar toda a criação de conteúdo em uma classe, criei uma classe estática, chamada LevelContentCreation , que contém as informações sobre qual símbolo cria o que e todo esse tipo de coisa. Ele também contém uma lista de todos os caminhos de ativos das folhas de mosaico, para carregá-los posteriormente. Esta classe está dentro do meu projeto de biblioteca de jogos GameLib.

O problema é que estou usando essa classe estática LevelContentCreation na minha classe Level (para carregar as folhas de bloco reais) e na extensão do pipeline de conteúdo (para saber qual símbolo representa qual bloco) ...

E aqui está como minha classe LevelContentCreation se parece:

public static class LevelContentCreation
{
    private static Dictionary<char, TileCreationInfo> AvailableTiles =
        CreateAvailableTiles();

    private static List<string> AvailableTileSheets { get; set; }

    /* ... rest of the class */
}

Agora, como a classe LevelContentCreation faz parte da biblioteca de jogos, a extensão do pipeline de conteúdo tenta usá-lo, mas a chamada para CreateAvailableTiles () nunca acontece e não consigo carregar um objeto Level do pipeline de conteúdo.

Eu sei que isso ocorre porque o pipeline de conteúdo é executado antes da compilação real ou algo parecido, mas como posso corrigir o problema?

Não consigo mover o LevelContentCreation classe para a extensão do pipeline, porque a classe Level não seria capaz de usá-lo ... Estou muito perdida e não sei o que fazer ... Eu sei disso são decisões de design muito ruins, e eu gostaria que alguém pudesse me apontar na direção certa.

Obrigado pelo seu precioso tempo.

Se alguma coisa não estiver clara o suficiente ou se devo fornecer mais informações / código, deixe um comentário abaixo.


Um pouco mais de informação sobre meus projetos:

  • Jogo - projeto de jogo do XNA Windows. Contém referências a: Engine, GameLib e GameContent
  • GameLib - projeto de biblioteca de jogos XNA. Contém referências a: Mecanismo
  • GameContent - projeto de conteúdo XNA. Contém referências a: LevelContentPipelineExtension
  • Engine - projeto da biblioteca de jogos XNA. Sem referências.
  • LevelContentPipelineExtension - extensão de pipeline de conteúdo XNA. Contém referências a: Engine e GameLib

Não sei quase nada sobre o XNA 4.0 e estou um pouco perdido sobre como o conteúdo funciona agora. Se precisar de mais informações, me diga.

Jesse Emond
fonte
Não sei exatamente o que - mas acho que você está perdendo algumas informações. Você poderia explicar exatamente quais projetos você tem (jogo, biblioteca de jogos, extensão do pipeline de conteúdo, projeto de conteúdo) e quais referências o quê?
Andrew Russell
Claro, eu sabia que não seria claro o suficiente. Editando agora.
precisa
A propósito, tudo parece bem, exceto que uma exceção é lançada em tempo de compilação na extensão do pipeline. A exceção realmente me diz que o conteúdo do arquivo de nível não pode ser convertido, pois os símbolos disponíveis ainda não foram carregados no meu arquivo LevelContentCreation. Até testei colocando uma exceção na extensão do pipeline e na classe estática, e a exceção da extensão do pipeline foi lançada primeiro, portanto o problema pode realmente vir daí e preciso reestruturar meu código.
precisa
Para referência futura: eis um método para depurar extensões de pipeline de conteúdo .
Andrew Russell

Respostas:

11

Nos casos em que você está usando singletons, classes estáticas ou variáveis ​​globais, 99% das vezes o que você realmente deveria estar usando é um serviço de jogo . Os serviços de jogos são usados ​​quando você deseja que uma instância de uma classe esteja disponível para todas as outras classes, mas o forte acoplamento das outras opções oferece um sabor engraçado. Os serviços também não têm os problemas de instanciação que você viu, são mais reutilizáveis ​​e podem ser ridicularizados (se você se preocupa com o teste de unidade automatizado) .

Para registrar um serviço, você precisa dar à sua classe sua própria interface. Então é só ligar Game.Services.AddService(). Para usar posteriormente esse serviço em outra classe, chameGame.Services.GetService() .

No seu caso, sua classe seria mais ou menos assim:

public interface ILevelContentCreation
{
    void SomeMethod();
    int SomeProperty { get; }
}

public class LevelContentCreation : ILevelContentCreation
{
    private Dictionary<char, TileCreationInfo> availableTiles =
        CreateAvailableTiles();

    private List<string> AvailableTileSheets { get; set; }

    public void SomeMethod() { /*...*/ }
    public int SomeProperty { get; private set; }

    /* ... rest of the class ... */
}

Em algum momento no início do programa, você precisará registrar seu serviço Game.Services. Eu prefiro fazer isso no início de LoadContent(), mas algumas pessoas preferem registrar cada serviço no construtor dessa classe.

/* Main game */
protected override void LoadContent()
{
    RegisterServices();

    /* ... */
}

private void RegisterServices()
{
    Game.Services.AddService(typeof(ILevelContentCreation), new LevelContentCreation());
}

Agora, sempre que você quiser acessar sua LevelContentCreationclasse em qualquer outra classe, basta fazer o seguinte:

ILevelContentCreation levelContentCreation = (ILevelContentCreation) Game.Services.GetService(typeof(ILevelContentCreation));
levelContentCreation.SomeMethod(); //Tada!

Como uma observação lateral, esse paradigma é conhecido como Inversão de Controle (IoC) e também é amplamente usado fora do desenvolvimento do XNA. A estrutura mais popular para IoC no .Net é o Ninject , que possui uma sintaxe melhor do que os métodos Game.Services:

Bind<ILevelContentCreation>().To<LevelContentCreation>(); //Ninject equivalent of Services.AddService()
ILevelContentCreation levelContentCreation = kernel.Get<ILevelContentCreation>(); //Ninject equivalent of Services.GetService()

Ele também suporta injeção de dependência , uma forma de IoC que é um pouco mais complexa, mas muito mais agradável de se trabalhar.

[Edit] É claro, você também pode obter essa boa sintaxe com o XNA:

static class ServiceExtensionMethods
{
    public static void AddService<T>(this GameServiceContainer services, T service)
    {
        services.AddService(typeof(T), service);
    }

    public static T GetService<T>(this GameServiceContainer services)
    {
        return (T)services.GetService(typeof(T));
    }
}
BlueRaja - Danny Pflughoeft
fonte
11
Uau, mesmo que eu tenha corrigido o meu problema, isso tornará meu código muito mais limpo. Obrigado pela ajuda, aceitando sua resposta.
perfil completo de Jesse Emond
1

Na verdade, encontrei uma maneira de corrigir o problema: removi a classe estática e a tornei uma classe normal. Dessa forma, tenho certeza de que as instruções corretas são executadas no momento certo.

Eu gostaria de não ter desperdiçado seu tempo assim, mas realmente tive a ideia de fazê-lo lendo os comentários / respostas.

Obrigado mesmo assim.

Jesse Emond
fonte
Outra observação não relacionada à minha resposta acima / abaixo: se você se encontra fazendo if/ switchdeclarações que simplesmente retornam tipos diferentes, provavelmente deseja que esses valores façam parte da classe. No seu caso, eu criaria uma TileTypeclasse que contém informações (textura, salvar arquivo-letra, etc.) sobre cada tipo de bloco. Em seguida, para obter uma TileTypecarta, basta pesquisar na sua lista de TileTypes, ou se você estiver preocupado com o desempenho, armazene uma estática Dictionary<char, TileType>na TileTypeclasse (você pode preenchê-la automaticamente no TileTypeconstrutor).
BlueRaja - Danny Pflughoeft
Então cada Tile(classe que representa um único bloco exibido na tela) referencia um único TileType. Se um número suficiente de seus blocos tiver um comportamento exclusivo que exija código especial, convém usar uma hierarquia de classes.
BlueRaja - Danny Pflughoeft
0

Dê uma olhada no seguinte: http://msdn.microsoft.com/en-us/library/bb447745.aspx

Para o layout de nível, basta colocar uma função dentro da minha classe Level chamada "LoadFromFile (string name)". Porque se você quiser adicionar suporte para mapas personalizados no seu jogo mais tarde, precisará salvar e carregar os níveis do seu próprio formato de arquivo.

Você também pode criar um importador de conteúdo, para carregar dos ativos incluídos e também dos arquivos fornecidos pelo usuário. Consulte esta página para obter mais informações sobre como criar um importador.

http://msdn.microsoft.com/en-us/library/bb447743.aspx

Riki
fonte
Obrigado pela sua resposta. Meu problema não vem da criação da extensão do pipeline de conteúdo, mas do fato de que minha classe de criação de nível é usada tanto pela minha classe Level quanto pela minha extensão do pipeline de conteúdo, portanto, o código do pipeline é executado antes das declarações da classe estática e nada é inicializado lá.
precisa