XNA - Clases estáticas de bibliotecas de juegos que se ejecutan después de extensiones de canal de contenido, ¿cómo evitar el problema?


8

Bien, formulado de esta manera, estoy seguro de que suena oscuro, pero haré todo lo posible para describir mi problema.

Primero, déjame explicarte lo que está mal con mi código fuente real.

Estoy haciendo un juego de plataformas, y aquí están los componentes de la corriente de mi juego: un nivel , que está llevando a cabo su propia rejilla de baldosas y también las hojas de baldosas y una teja , que sólo mantiene el nombre de su hoja de baldosas y el índice dentro de la hoja de azulejos Todos estos están dentro de un proyecto de juego XNA separado, llamado GameLib.

Así es como mis niveles están formateados en un archivo de texto:

x x . . . . .

. . . b g r .

. . . . . . .

X X X X X X X

Donde .representa un mosaico vacío, brepresenta una plataforma azul, Xrepresenta un bloque con un color aleatorio, etc.

En lugar de hacer todo el trabajo de cargar un nivel dentro de la clase Level , decidí aprovechar la extensión de la canalización de contenido y crear una LevelContentPipelineExtension.

Tan pronto como comencé, me di cuenta rápidamente de que no quería codificar más de 20 líneas de este tipo:

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

Entonces, con la esperanza de al menos centralizar toda la creación de contenido en una clase, hice una clase estática, llamada LevelContentCreation , que contiene la información sobre qué símbolo crea qué y todo este tipo de cosas. También contiene una lista de todas las rutas de activos de las hojas de mosaico, para cargarlas más adelante. Esta clase está dentro de mi proyecto de biblioteca de juegos GameLib.

El problema es que estoy usando esta clase estática LevelContentCreation tanto en mi clase Level (para cargar las hojas de mosaico reales) como en mi extensión de canalización de contenido (para saber qué símbolo representa qué mosaico) ...

Y así es como se ve mi clase LevelContentCreation :

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

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

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

Ahora, dado que la clase LevelContentCreation es parte de la biblioteca del juego, la extensión de la tubería de contenido intenta usarla, pero la llamada a CreateAvailableTiles () nunca ocurre y no puedo cargar un objeto Level de la tubería de contenido.

Sé que esto se debe a que la canalización de contenido se ejecuta antes de la compilación real o algo así, pero ¿cómo puedo solucionar el problema?

Realmente no puedo mover la clase LevelContentCreation a la extensión de canalización, porque entonces la clase Level no podría usarla ... Estoy bastante perdido y no sé qué hacer ... Sé estos son decisiones de diseño bastante malas, y me gustaría que alguien pudiera señalarme en la dirección correcta.

Gracias por tu precioso tiempo.

Si algo no está lo suficientemente claro o si debo proporcionar más información / código, deje un comentario a continuación.


Un poco más de información sobre mis proyectos:

  • Juego - Proyecto de juego XNA para Windows. Contiene referencias a: Motor, GameLib y GameContent
  • GameLib - Proyecto de biblioteca de juegos XNA. Contiene referencias a: Motor
  • GameContent - Proyecto de contenido XNA. Contiene referencias a: LevelContentPipelineExtension
  • Motor - Proyecto de biblioteca de juegos XNA. Sin referencias
  • LevelContentPipelineExtension - Extensión de canalización de contenido XNA. Contiene referencias a: Motor y GameLib

No sé casi nada sobre XNA 4.0 y estoy un poco perdido sobre cómo funciona el contenido ahora. Si necesitas más información, dímelo.


No puedo entender qué, pero creo que te falta algo de información. ¿Podría explicar exactamente qué proyectos tiene (juego, biblioteca de juegos, extensión de canal de contenido, proyecto de contenido) y qué referencias hace qué?
Andrew Russell

Claro, sabía que no sería lo suficientemente claro. Editando ahora mismo.
Jesse Emond

Por cierto, todo parece estar bien, excepto que se produce una excepción en tiempo de compilación dentro de la extensión de la tubería. La excepción en realidad me dice que el contenido del archivo de nivel no se puede convertir, ya que los símbolos disponibles aún no están cargados dentro de mi archivo LevelContentCreation. Incluso probé colocando una excepción tanto en la extensión de la tubería como en la clase estática, y la excepción de la extensión de la tubería se lanzó primero, por lo que el problema puede provenir de allí y necesitaría reestructurar mi código.
Jesse Emond

Respuestas:


11

En los casos en los que usa singletons, clases estáticas o variables globales, el 99% del tiempo lo que realmente debería usar es un servicio de juegos . Los servicios de juego se usan cuando quieres que una instancia de una clase esté disponible para todas las demás clases, pero el acoplamiento estrecho de las otras opciones te da un sabor divertido. Los servicios tampoco tienen los problemas de creación de instancias que has visto, son más reutilizables y se pueden burlar (si te interesan las pruebas unitarias automatizadas) .

Para registrar un servicio, debe darle a su clase su propia interfaz. Entonces solo llama Game.Services.AddService(). Para usar más tarde ese servicio en otra clase, llame al Game.Services.GetService().

En su caso, su clase se vería así:

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 ... */
}

En algún momento al comienzo del programa, tendría que registrar su servicio Game.Services. Prefiero hacer esto al comienzo LoadContent(), pero algunas personas prefieren registrar cada servicio en el constructor de esa clase.

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

    /* ... */
}

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

Ahora, cuando quiera acceder a su LevelContentCreationclase en cualquier otra clase, simplemente haga esto:

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

Como nota al margen, este paradigma se conoce como Inversión de Control (IoC), y también se usa ampliamente fuera del desarrollo de XNA. El marco más popular para IoC en .Net es Ninject , que tiene una sintaxis más agradable que los métodos Game.Services:

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

También es compatible con la inyección de dependencia , una forma de IoC que es un poco más compleja, pero mucho más agradable para trabajar.

[Editar] Por supuesto, también puedes obtener esa buena sintaxis con 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));
    }
}

1
Wow, aunque solucioné mi problema, esto hará que mi código sea mucho más limpio. Gracias por la ayuda, aceptando su respuesta.
Jesse Emond

1

En realidad, encontré una manera de solucionar el problema: eliminé la clase estática y la convertí en una clase normal. De esta manera, estoy seguro de que las declaraciones correctas se ejecutan en el momento correcto.

Desearía no haber perdido su tiempo así, pero en realidad tuve la idea de hacerlo leyendo los comentarios / respuestas.

Gracias de todos modos.


Otra nota no relacionada con mi respuesta arriba / abajo: si te encuentras haciendo if/ switchdeclaraciones que simplemente devuelven diferentes tipos, probablemente quieras que esos valores formen parte de la clase. En su caso, crearía una TileTypeclase que contiene información (textura, guardar-archivo-letra, etc.) sobre cada tipo de mosaico. Luego, para obtener una TileTypecarta, simplemente busque en su lista TileTypeso, si le preocupa el rendimiento, almacene una estática Dictionary<char, TileType>en la TileTypeclase (puede completarla automáticamente en el TileTypeconstructor).
BlueRaja - Danny Pflughoeft

Entonces cada Tile(clase que representa un único mosaico que se muestra en la pantalla) haría referencia a un solo TileType. Si suficientes de sus mosaicos tienen un comportamiento único que requiere un código especial, deberá utilizar una jerarquía de clases en su lugar.
BlueRaja - Danny Pflughoeft

0

Eche un vistazo a esto: http://msdn.microsoft.com/en-us/library/bb447745.aspx

Para el diseño de nivel, simplemente pondría una función dentro de mi clase de nivel llamada "LoadFromFile (nombre de cadena)". Porque si deseas agregar soporte para mapas personalizados en tu juego más tarde, tendrás que guardar y cargar los niveles desde tu propio formato de archivo de todos modos.

También puede crear un importador de contenido para cargar desde los activos incluidos y también desde los archivos proporcionados por el usuario. Consulte esta página para obtener más información sobre cómo crear un importador.

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


Gracias por tu respuesta. Mi problema en realidad no proviene de la creación de la extensión de canalización de contenido, sino del hecho de que mi clase de creación de nivel es utilizada tanto por mi clase de Nivel como por mi extensión de canalización de contenido, por lo que el código de canalización se ejecuta antes de las declaraciones de clase estática y nada se inicializa allí.
Jesse Emond
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.