2008-10-07 11 views
9

Recientemente comencé a trabajar en un pequeño juego para mi propio entretenimiento, usando Microsoft XNA y C#. Mi pregunta es sobre el diseño de un objeto del juego y los objetos que lo heredan. Definiré un objeto de juego como algo que se puede representar en la pantalla. Así que para esto, decidí hacer una clase base que heredarán todos los demás objetos que se necesitarán representar, llamada GameObject. El código siguiente es la clase que hice:Diseño de Game Objects

class GameObject 
{ 
    private Model model = null; 
    private float scale = 1f; 
    private Vector3 position = Vector3.Zero; 
    private Vector3 rotation = Vector3.Zero; 
    private Vector3 velocity = Vector3.Zero; 
    private bool alive = false; 
    protected ContentManager content; 

    #region Constructors 
    public GameObject(ContentManager content, string modelResource) 
    { 
     this.content = content; 
     model = content.Load<Model>(modelResource); 
    } 
    public GameObject(ContentManager content, string modelResource, bool alive) 
     : this(content, modelResource) 
    { 
     this.alive = alive; 
    } 
    public GameObject(ContentManager content, string modelResource, bool alive, float scale) 
     : this(content, modelResource, alive) 
    { 
     this.scale = scale; 
    } 
    public GameObject(ContentManager content, string modelResource, bool alive, float scale, Vector3 position) 
     : this(content, modelResource, alive, scale) 
    { 
     this.position = position; 
    } 
    public GameObject(ContentManager content, string modelResource, bool alive, float scale, Vector3 position, Vector3 rotation) 
     : this(content, modelResource, alive, scale, position) 
    { 
     this.rotation = rotation; 
    } 
    public GameObject(ContentManager content, string modelResource, bool alive, float scale, Vector3 position, Vector3 rotation, Vector3 velocity) 
     : this(content, modelResource, alive, scale, position, rotation) 
    { 
     this.velocity = velocity; 
    } 
    #endregion 
} 

He dejado de lado los métodos adicionales que hacen cosas tales como rotar, mover y dibujar el objeto. Ahora, si quisiera crear otro objeto, como un barco, crearía una clase Ship, que heredaría GameObject. Código de muestra a continuación:

class Ship : GameObject 
{ 
    private int num_missiles = 20; // the number of missiles this ship can have alive at any given time 
    private Missile[] missiles; 
    private float max_missile_distance = 3000f; // max distance a missile can be from the ship before it dies 

    #region Constructors 
    public Ship(ContentManager content, string modelResource) 
     : base(content, modelResource) 
    { 
     InitShip(); 
    } 
    public Ship(ContentManager content, string modelResource , bool alive) 
     : base(content, modelResource, alive) 
    { 
     InitShip(); 
    } 
    public Ship(ContentManager content, string modelResource, bool alive, float scale) 
     : base(content, modelResource, alive, scale) 
    { 
     InitShip(); 
    } 
    public Ship(ContentManager content, string modelResource, bool alive, float scale, Vector3 position) 
     : base(content, modelResource, alive, scale, position) 
    { 
     InitShip(); 
    } 
    public Ship(ContentManager content, string modelResource, bool alive, float scale, Vector3 position, Vector3 rotation) 
     : base(content, modelResource, alive, scale, position, rotation) 
    { 
     InitShip(); 
    } 
    public Ship(ContentManager content, string modelResource, bool alive, float scale, Vector3 position, Vector3 rotation, Vector3 velocity) 
     : base(content, modelResource, alive, scale, position, rotation, velocity) 
    { 
     InitShip(); 
    } 
    #endregion 
} 

Una vez más, he omitido cualquier método adicional específico de la nave, como disparar un misil. ¿Crees que este tipo de diseño es bueno o debería mejorarse de alguna manera o cambiarse por completo? Parece que los constructores de las clases para niños son desordenados, pero tal vez esa sea la única forma de hacerlo. Nunca he hecho algo como esto y me pregunto si estoy muy lejos de la pista.


Gracias a todos los que han dejado una respuesta. Todos fueron muy útiles. Parece haber un consenso general de que cambiarlo para usar un patrón MVC sería lo mejor. Voy a profundizar en exactamente cómo hacer eso. También eliminaré la mayoría de los constructores y tendré solo un constructor, porque todos los argumentos posteriores a modelResource no son necesarios para crear el objeto, y todos pueden cambiarse más tarde a través de llamadas a métodos.

Respuesta

4

Este diseño no separa las partes de la interfaz de usuario de un objeto y las partes de comportamiento de un objeto, el "modelo" y la "vista", por así decirlo. Esto no es necesariamente algo malo, pero es posible encontrar los siguientes refactorizaciones difícil si continúa con este diseño:

  • Re-pelar el juego cambiando todos los bienes de arte
  • Cambiar el comportamiento de muchos diferentes en -juego de objetos, por ejemplo, las reglas que determinan si un objeto está vivo o no
  • Cambiando a un motor de sprite diferente.

Pero si estás de acuerdo con estas concesiones y este diseño tiene sentido para ti, no veo nada terriblemente incorrecto con él.

6

Personalmente, creemos que el gran número de constructores que tiene un poco muy desagradable, pero eso es su elección, nada fundamentalmente malo en ello :)

Con respecto a la estrategia general de derivar los objetos del juego a partir de una base común, que es una forma bastante común de hacer cosas. Estándar, incluso. He tendido a empezar a usar algo mucho más parecido a MVC, con objetos del juego "modelo" increíblemente livianos que solo contienen datos.

Otros enfoques comunes que he visto en XNA incluyen/cosas que puede tener en cuenta:

  • Implementación de una interfaz IRenderable en lugar de hacer cualquier render código en la base o en las clases derivadas. Por ejemplo, es posible que desee objetos de juego que nunca se representan - waypoints o somesuch.
  • Puede hacer que su clase base sea abstracta, no es probable que desee crear una instancia de un GameObject.

De nuevo, puntos menores. Tu diseño está bien.

+0

@NM: +1, aquí, ¡aquí hay menos constructores más propiedades! – user7116

1

Dependiendo de la frecuencia con la que se llamarán los constructores, podría considerar la posibilidad de anular todos los parámetros adicionales y pasarles nulos cuando no tienen valores. Messier creando los objetos, menos constructores

1

Recorrí este camino la primera vez que creé juegos en XNA, pero la próxima vez haría más de un tipo de enfoque de modelo/vista. Los objetos de modelo serían los que trataría en su bucle de actualización, y sus vistas se usarían en su bucle de dibujo.

Tuve problemas para no separar el modelo de la vista cuando quería usar mi objeto sprite para manejar objetos 3D y 2D. Se puso muy sucio muy rápido.

3

Estoy muy interesado en su método de almacenamiento para la rotación en un vector. ¿Como funciona esto? Si almacena los ángulos de los ejes X, Y, Z (también llamados ángulos de Euler), debe reconsiderar esta idea, si desea crear un juego 3D, ya que se encontrará poco después de su primera representación del mal GIMBAL LOCK

Personalmente, no me gustan tanto los constructores allí, ya que puede proporcionar 1 constructor y establecer todos los parámetros no necesarios para nulo. de esta manera, su interfaz se mantiene más limpia.

+0

No tenía conocimiento de bloqueo cardánico. Estoy almacenando ángulos X, Y, Z en un vector, como dijiste. Miré tu enlace e intenté recrearlo teniendo X = 90, Y = 0, Z = 0 (esto hizo que la miniatura volteara). Luego aumenté Y y giró como se esperaba. Pero aumentar Z hizo que pareciera que giraba alrededor de Y. – Kobol

+0

. Querrá una representación que sea un poco más estable. Sé que Second Life usa Quaternions para operaciones de rotación para evitar este tipo de problemas. –

1

Las personas aquí hablando sobre separar el modelo de la vista son correctas. Para ponerlo en términos más orientados al desarrollador de juegos, debes tener clases separadas para GameObject y RenderObject. Piense de nuevo a su bucle principal del juego:

// main loop 
while (true) { 
    ProcessInput(); // handle input events 
    UpdateGameWorld(); // update game objects 
    RenderFrame(); // each render object draws itself 
} 

desea que el proceso de actualización de su mundo del juego y la prestación de su marco actual de ser lo más independiente posible.

+0

En mi ejemplo de un objeto Ship, ¿heredaría GameObject y tendría una variable miembro de tipo RenderObject? ¿O tendrías un objeto RenderObject disponible para el bucle principal del juego, que podría tomar un GameObject y renderizarlo? O ninguno de los anteriores? – Kobol

2

También recomiendo consultar un diseño basado en componentes utilizando composición en lugar de herencia. La esencia de la idea es modelar objetos en función de sus propiedades subyacentes, y permitir que las propiedades hagan todo el trabajo por usted.

En su caso, un GameObject puede tener propiedades como poder moverse, ser renderable o tener algún estado. Estas funciones separadas (o comportamientos) se pueden modelar como objetos y compilarse en GameObject para crear la funcionalidad que se desee.

Honestamente, para un proyecto pequeño en el que estás trabajando me centraría en hacer el juego funcional en lugar de preocuparme por detalles como el acoplamiento y la abstracción.

1

alternativa, se puede hacer una clase struct o ayudante para contener todos los datos y tener los muchos constructores en la estructura, que forma sólo tiene que tener toneladas de constructores en 1 objeto en lugar de en cada clase que hereda de GameObject

5

No solo el número de constructores es un problema potencial (especialmente tener que volver a definirlos para cada clase derivada), sino que la cantidad de parámetros para el constructor puede convertirse en un problema. Incluso si hace que los parámetros opcionales sean nulables, se vuelve difícil leer y mantener el código más tarde.

si escribo:

new Ship(content, "resource", true, null, null, null, null); 

¿Qué hace el segundo NULL?

Hace código más legible (pero con más detalles) utilizar una estructura para sostener sus parámetros, si la lista de parámetros va más allá de cuatro o cinco parámetros:

GameObjectParams params(content, "resource"); 
params.IsAlive = true; 
new Ship(params); 

Hay un montón de maneras en que esto se puede hacer .

1

¿Primer juego? Comience simple. Su GameObject base (que en su caso se llama más apropiadamente DrawableGameObject [observe la clase DrawableGameComponent en XNA]) solo debe contener la información que pertenece a todos los GameObjects. Tus variables de vida y velocidad realmente no pertenecen. Como han mencionado otros, probablemente no quieras mezclar tu dibujo y actualizar la lógica.

La velocidad debe estar en una clase móvil, que debe manejar la lógica de actualización para cambiar la posición del GameObject. También puede considerar realizar un seguimiento de la aceleración y hacer que aumente constantemente a medida que el jugador mantiene presionado un botón, y disminuir constantemente después de que el jugador suelta el botón (en lugar de usar una aceleración constante) ... pero eso es más un diseño de juego decisión que una decisión de la organización de la clase.

Lo que significa "vivo" puede ser muy diferente según el contexto. En un juego de disparos en el espacio simple, algo que no está vivo probablemente debería ser destruido (¿posiblemente reemplazado por unos pocos pedazos de desechos espaciales?). Si es un jugador que reaparece, querrás asegurarte de que el jugador esté separado del barco. La nave no reaparece, la nave se destruye y el jugador obtiene una nueva nave (reciclar la vieja nave en la memoria en lugar de eliminarla y crear una nueva usará menos ciclos; simplemente puede mover la nave a la de nadie) -land o sacarlo de la escena de tu juego por completo y volver a ponerlo después de reaparecer).

Otra cosa que debe tener en cuenta ... No deje que sus jugadores miren directamente hacia arriba o hacia abajo en su eje vertical, o se encontrará con, como Peter Parker mencionó, problemas con Gimbal Lock (que no ¡Si quieres desarrollar juegos por diversión, no te preocupes!). Mejor aún ... ¡comience con 2D (o al menos, limite el movimiento a solo 2 dimensiones)! Es mucho más fácil cuando eres nuevo en el desarrollo de juegos.

Sugeriría seguir el XNA Creators Club Getting Started Guides antes de hacer más programación, sin embargo. También debería considerar buscar otras guías de desarrollo de juegos más generales para obtener algunas sugerencias alternativas.

5

Como usted está haciendo una pregunta relacionada con el diseño, trataré de dar algunas pistas sobre eso, en lugar de sobre los muchos códigos que publicó, porque otros ya han comenzado con eso.

Los juegos se prestan mucho al Model-View-Controller pattern. Está hablando explícitamente de la parte de visualización, pero piénselo: tiene vectores allí, etc., y eso generalmente forma parte de lo que está modelando. Piensa en el mundo del juego y los objetos allí como los modelos. Tienen una posición, pueden tener velocidad y dirección. Esa es la parte modelo de MVC.

Me di cuenta de que deseaba despedir el barco. Es probable que desee que el mecánico haga que el barco dispare en una clase de controlador, algo que requiere la intervención del teclado, por ejemplo. Y esta clase de controlador enviará mensajes a la pieza modelo. Un sonido y fácil cree que probablemente quiera tener algún método fireAction() en el modelo. En su modelo base, ese podría ser un método reemplazable (virtual, abstracto). En el barco, podría agregar una "bala" al mundo del juego.

Al ver que ya escribí sobre el comportamiento de su modelo, aquí hay otra cosa que me ha ayudado mucho: use el strategy pattern para hacer que el comportamiento de las clases sea intercambiable. El patrón de estrategia también es excelente si piensa AI y quiere comportamiento para cambiar algunos cuando en el futuro. Puede que no lo sepas ahora, pero en un mes podrías querer usar misiles balísticos como misiles predeterminados y puedes cambiarlo fácilmente más adelante si hubieras utilizado el patrón de estrategia.

Así que eventualmente querrá hacer algo como una clase de pantalla. Tendrá nombres primitivos como tú: rotar, traducir, etc. Y desea derivar de esto para agregar más funcionalidad o cambiar la funcionalidad. Pero piénselo, una vez que tenga más de un barco, un tipo especial de barco, otro tipo especial de barco, se encontrará con el infierno de derivación y usted replicará una gran cantidad de código. De nuevo, usa el patrón de estrategia. Y recuerda mantenerlo simple. ¿Qué necesita tener una clase Displayble?

  • Significa conocer su posición relativa a la pantalla. Puede, gracias al modelo de su objeto en el mundo y algo así como el modelo del mundo del juego.
  • Debe conocer un mapa de bits para mostrar para su modelo y sus dimensiones.
  • Debe saber si algo debe impedir que se dibuje, si eso no lo maneja el motor de dibujo (es decir, si otro objeto lo ocluye).