2010-02-11 9 views
8

Estoy haciendo un juego de lanzamiento de Java para teléfonos Android. Tengo 20 enemigos extraños en el juego que tienen comportamientos únicos pero ciertos comportamientos son reutilizados por la mayoría de ellos. Necesito modelar balas, explosiones, asteroides, etc. y otras cosas que también actúen como enemigos. Mi diseño actual favorece composición sobre la herencia y representa objetos juego un poco como esto:¿Cómo modelar la representación y el comportamiento del objeto de juego de forma modular?

// Generic game object 
class Entity 
{ 
    // Current position 
    Vector2d position; 

    // Regular frame updates behaviour 
    Behaviour updateBehaviour; 
    // Collision behaviour 
    Behaviour collideBehaviour; 

    // What the entity looks like 
    Image image; 
    // How to display the entity 
    Renderer renderer; 

    // If the entity is dead and should be deleted 
    int dead; 
} 

abstract class Renderer { abstract void draw(Canvas c); } 

abstract class Behaviour { abstract void update(Entity e); } 

Para simplemente dibujar cualquier cosa que se almacena la imagen como entidad, se puede adjuntar un procesador sencillo, por ejemplo,

class SimpleRenderer extends Renderer 
{ 
    void draw(Canvas c) 
    { 
    // just draw the image 
    } 
} 

Para que la entidad volar sobre al azar cada cuadro, simplemente adjuntar un comportamiento así:

class RandomlyMoveBehaviour extends Behaviour 
{ 
    void update(Entity e) 
    { 
    // Add random direction vector to e.position 
    } 
} 

O añadir un comportamiento más complejo como esperando hasta que el jugador está cerca antes de recalada en:

class SleepAndHomeBehaviour extends Behaviour 
{ 
    Entity target; 
    boolean homing; 

    void init(Entity t) { target = t; } 

    void update(Entity e) 
    { 
    if (/* distance between t and e < 50 pixels */) 
    { 
     homing = true; 
     // move towards t... 
    } 
    else 
    { 
     homing = false; 
    } 
    } 
} 

Estoy muy contento con este diseño hasta ahora. Es agradable y flexible en cuanto a que puedes, por ejemplo, modularizar la última clase para que pueda suministrar el comportamiento "dormido" y el comportamiento "despierto" para que pueda decir algo como nuevo WaitUntilCloseBehaviour (jugador, 50/píxeles /, nuevo MoveRandomlyBehaviour(), nuevo HomingBehaviour()). Esto hace que sea realmente fácil hacer nuevos enemigos.

La única parte que me molesta es cómo se comunican los procesadores y los procesadores. Por el momento, Entity contiene un objeto Image que un Behavior podría modificar si así lo deseara. Por ejemplo, un comportamiento podría cambiar el objeto entre una imagen de reposo y despierto y el procesador dibujaría la imagen. No estoy seguro de cómo esto va a escalar embargo ej .:

  • Qué pasa con un enemigo torreta similar al que se enfrenta a una cierta dirección? Creo que podría agregar un campo de rotación a la entidad que Behavior and Renderer pueda modificar/leer.

    • ¿Qué pasa con un tanque donde el cuerpo del tanque y el arma del tanque tienen direcciones separadas? Ahora el renderizador necesita acceso a dos rotaciones desde alguna parte y las dos imágenes a usar. En realidad, no desea hinchar la clase Entity con esto si solo hay un tanque.

    • ¿Qué pasa con un enemigo que brilla mientras su arma se recarga? Realmente desea almacenar el tiempo de recarga en el objeto Comportamiento, pero la clase Renderer no puede verlo.

estoy teniendo problemas para pensar en formas de modelar lo anterior por lo que los procesadores y los comportamientos pueden mantenerse un tanto separada. El mejor enfoque que puedo pensar es hacer que los objetos de comportamiento contengan el estado adicional y el objeto del procesador, luego los objetos de comportamiento llaman al método de dibujar representadores y pasan el estado extra (por ejemplo, rotación) si así lo desean.

Podría, por ejemplo, tener un objeto de Comportamiento similar a un tanque que quiere un Renderer similar a un tanque, donde este último pide las dos imágenes y dos rotaciones para dibujar. Si quisieras que tu tanque fuera simplemente una imagen, simplemente escribirías una subclase Renderer que ignorara las rotaciones.

¿Alguien puede pensar en alguna alternativa?Realmente quiero simplicidad. Como es un juego, la eficiencia también puede ser una preocupación si, por ejemplo, dibujar una sola imagen enemiga de 5x5, cuando tengo 50 enemigos volando a 60 fps, implica muchas capas de llamadas a funciones.

Respuesta

1

El diseño de la composición es válido, ya que permite mezclar y combinar el (los) comportamiento (s) y el renderizado.

En el juego con el que estamos jugando, hemos agregado una "bolsa de datos" que contiene información básica (en su caso, la posición y el estado muerto/vivo) y datos de variables que el comportamiento establece y subsistema de colisión. El procesador puede usar estos datos (o no si no es necesario). Esto funciona bien y permite un efecto nítido, como establecer un "objetivo" para un efecto gráfico dado.

Unos pocos problemas:

  • si el Procesador de pedir los datos que el comportamiento no se estableció. En nuestro caso, el evento se registra y se utilizan los valores predeterminados (definidos en el renderizador).
  • Es un poco más difícil verificar previamente la información necesaria (es decir, qué datos deben estar en la bolsa de datos para el Renderer A, ¿qué datos establece el Comportamiento B?). Intentamos mantener el documento actualizado, pero estamos pensando en registrar el conjunto/obtener por las clases y generar una página de documento ...

Actualmente estamos utilizando un HashMap para el Databag, pero esto está en una PC, no en un iPhone. No sé si el rendimiento será suficiente, en cuyo caso otra estructura podría ser mejor.

También en nuestro caso, hemos decidido un conjunto de procesadores especializados. Por ejemplo, si la entidad posee un escudo de datos no nulos, el ShieldRenderer visualizar la representación ... En su caso, el tanque podría poseer dos renderizador vinculado a dos datas más de inicialización (definidos):

Renderer renderer1 = new RotatedImage("Tank.png", "TankRotation"); 
Renderer enderer2 = new RotatedImage("Turret.png", "TurretRotation"); 

con " TankRotation "y" TurretRotation "establecidos por el comportamiento. y el renderizador simplemente gira la imagen antes de mostrarla en la posición.

image.rotate (entity.databag.getData(variable)); 

Esperanza esta ayuda

Saludos
Guillaume

+0

Gracias. No lo he perfilado, pero si quiero que haya muchos objetos volando (por ejemplo, viñetas), probablemente querría evitar el uso de hashmaps para actualizar cada comportamiento en Android. Una de las ventajas que he notado al mantener el almacenamiento variable local para los objetos de comportamiento es que hace que el sistema sea mucho más robusto y fácil de probar, ya que no tiene que preocuparse por los comportamientos combinados que juegan con las mismas variables. – BobbyJim

+0

de acuerdo. Como dije, estamos en PC, y no hay demasiados objetos. Además, es por diversión :) –

1

El diseño vas con ve bien para mí. Este capítulo en components puede ayudarlo.

+0

Gracias. Implementé el enfoque anterior y ¡es brillante! Una cosa que nunca veo que la gente sugiera es secuenciar comportamientos (por ejemplo, un comportamiento que llama dos comportamientos en secuencia) y comportamientos condicionales (por ejemplo, un comportamiento llama condicionalmente a otro comportamiento). Parece que ahora tengo un pequeño lenguaje de scripting y es muy ligero. Por ejemplo, al usar una combinación de objetos, puedo decir cosas como "si (estás a 300 píxeles del reproductor) (en casa en el jugador) de lo contrario (dispara al jugador)". Puedo codificar nuevos, p. enemigos en el juego en cuestión de minutos ahora. – BobbyJim

+0

Eso es genial. Básicamente tienes un lenguaje de scripting: es un libro de texto [patrón de intérprete] (http://en.wikipedia.org/wiki/Interpreter_pattern). – munificent

Cuestiones relacionadas