2010-02-09 11 views
21

Estoy diseñando un juego simple, que usa la física 2D y Newtoniana de Java. Actualmente mi principal "bucle del juego" se ve algo como:Juego de diseño de manera OO

do { 
    for (GameEntity entity : entities) { 
    entity.update(gameContext); 
    } 

    for (Drawable drawable : drawables) { 
    drawable.draw(graphics2d); 
    } 
} while (gameRunning); 

Cuando una entidad se le instruye para actualizarse a sí mismo que ajustará su velocidad y posición sobre la base de las fuerzas de corriente aplicada a la misma. Sin embargo, necesito entidades para exhibir otro comportamiento; p.ej. si un "jugador malo" es disparado por un jugador, la entidad debería ser destruida y eliminada del mundo del juego.

Mi pregunta: ¿Cuál es la mejor manera de lograr esto de una manera orientada a objetos? Todos los ejemplos que he visto hasta ahora incorporan el bucle del juego en una clase de Dios llamada algo así como Game, que realiza los pasos: detectar colisiones, verificar si el chico malo mató, verificar si el jugador murió, volver a pintar , etc. y encapsula todo el estado del juego (vidas restantes, etc.). En otras palabras, es muy procedural y toda la lógica está en la clase de juego. ¿Alguien puede recomendar un mejor enfoque?

Estas son las opciones que he pensado hasta ahora:

  • pasar una GameContext a cada entidad de la que la entidad puede sustraerse si se requiere o actualizar el estado del juego (por ejemplo, a "no funcionando" si el jugador es asesinado).
  • Registre cada GameEntity como escucha de la clase central Game y adopte un enfoque orientado a eventos; p.ej. una colisión resultaría en el disparo de un CollisionEvent a los dos participantes en la colisión.
+2

Creo que he encontrado el error - que es 'while (gameRunning)' quiere allí –

Respuesta

14

He trabajado en estrecha colaboración con dos motores de juego comerciales y siguen un patrón similar:

  • objetos representan componentes o aspectos de una entidad de juego (como, sea cual sea física, renderable), en lugar de toda entidad . Para cada tipo de componente, hay una lista gigante de componentes, uno para cada instancia de entidad que tiene el componente.

  • El tipo de 'entidad de juego' en sí es solo un ID único. Cada lista gigante de componentes tiene un mapa para buscar el componente (si existe) que corresponde a una ID de entidad.

  • Si un componente requiere una actualización, lo llama un servicio u objeto del sistema. Cada servicio se actualiza directamente desde el bucle del juego. Alternativamente, puede llamar a servicios desde un objeto del planificador que determina el orden de actualización desde un gráfico de dependencia.

Estas son las ventajas de este enfoque:

  • Es posible combinar libremente funcionalidad sin escribir nuevas clases para cada combinación o el uso de herencia compleja árboles.

  • No hay casi ninguna funcionalidad que se puede asumir sobre todo el partido entidades que se puede poner en una clase base entidad juego (lo que hace un ligero tienen en común con un coche de carreras o un cielo caja ?)

  • el Id-al componente look-ups pueden parecer caro, pero los servicios están haciendo mayor parte del trabajo intensivo por parte iteración a través de todos los componentes de un tipo particular. En estos casos, funciona mejor para almacenar todos los datos que necesita en una sola lista de orden.

+0

@Evan: gracias por su respuesta. Quería preguntarte: ¿cómo manejarías la eliminación de una entidad en este caso? Por ejemplo, supongamos que tiene un aspecto Collidable, y su CollisionManager detecta una colisión, lo que debería causar que la entidad sea eliminada. Es de suponer que el CollisionManager solo hace referencia al aspecto colideable de la entidad, y entonces, ¿qué enfoque sigue para eliminar ** todos los aspectos ** de la entidad (Drawable, Collidable, etc.) de las distintas listas? – Adamski

+1

La pieza faltante que no mencioné es mensajes o eventos. Cada sistema puede suscribirse a cualquier tipo de mensaje o publicar mensajes. El sistema de física podría publicar un mensaje de "Choque" al que un sistema de juego podría suscribirse, y podría publicar un mensaje de "eliminar entidad" en respuesta. Otro sistema responsable de crear y eliminar entidades puede suscribirse al tipo de mensaje eliminar entidad. Esto es mucho más trabajo que las llamadas a funciones directas, pero todo es en nombre del desacoplamiento. –

6

En un motor particular en el que trabajé, desacoplamos la lógica de la representación gráfica y luego teníamos objetos que enviarían mensajes para lo que querían hacer. Hicimos esto para que pudiéramos tener juegos en una máquina local o en una red y no se distinguían entre sí desde el punto de vista del código. (Patrón de comando)

También hicimos el modelado de física real en un objeto separado que podría cambiarse sobre la marcha. Esto nos permite complicarnos fácilmente con la gravedad, etc.

Hicimos un uso intensivo del código de evento (patrón de escucha) y de muchos temporizadores.

Por ejemplo, teníamos una clase base para un objeto que era intersectable y que podía escuchar el evento de colisión. Lo subclasificamos en un cuadro de salud. En caso de colisión, si era golpeado por una entidad de jugador, enviaba un Comando al colisionador que indicaba que debería mejorar, enviaba un mensaje para transmitir un sonido a todos los que podían oírlo, colisiones desactivadas, activaba la animación para eliminar los gráficos el gráfico de escena, y establecer un temporizador para reinstalarse más tarde. Parece complicado, pero realmente no lo fue.

Si no recuerdo (y han pasado 12 años), teníamos la noción abstracta de escenas, por lo que un juego era una secuencia de escenas. Cuando se terminó una escena, se desencadenó un evento que típicamente enviaría un comando para desmontar la escena actual y comenzar otra.

+0

Gracias - Algunos buenos consejos aquí. – Adamski

+0

Probablemente sería una buena idea usar el patrón de mediación aquí para facilitar la invocación/registro/manejo de datos de evento, de lo contrario, tendría que acoplar entre sí todos los subsistemas y entidades de juego. – dvide

3

No estoy de acuerdo porque debido a que tienes una clase principal de Juego, toda la lógica tiene que ocurrir en esa clase.

La simplificación excesiva aquí imitando el ejemplo que se acaba de hacer mi punto:

mainloop: 
    moveEntities() 
    resolveCollisions() [objects may "disappear"/explode here] 
    drawEntities()  [drawing before or after cleanEntitites() ain't an issue, a dead entity won't draw itself] 
    cleanDeadEntities() 

Ahora usted tiene una clase de la burbuja:

Bubble implements Drawable { 

handle(Needle needle) { 
    if (needle collide with us) { 
     exploded = true; 
    } 
} 

draw (...) { 
    if (!exploded) { 
     draw(); 
    } 
    } 
} 

Así que, claro, hay un bucle principal que se encarga de pasando los mensajes entre las entidades, pero la lógica relacionada con la colisión entre una burbuja y una aguja definitivamente no está en la clase principal del juego.

Estoy bastante seguro de que, incluso en su caso, toda la lógica relacionada con el movimiento no está sucediendo en la clase principal.

No estoy de acuerdo con su declaración, que escribió en negrita, que "toda la lógica ocurre en la clase principal".

Esto simplemente no es correcto.

En cuanto al buen diseño: si puede proporcionar fácilmente otra "vista" de su juego (como, por ejemplo, un mini-mapa) y si puede codificar fácilmente un "reproductor perfecto fotograma por fotograma", entonces su el diseño probablemente no sea tan malo (es decir: al registrar solo las entradas y el tiempo en el que ocurrieron, debería poder recrear el juego exactamente como se jugó. Así es como Age of Empires, Warcraft 3, etc. repetición: solo las entradas del usuario y la hora en que sucedieron se graban [también es por eso que los archivos de reproducción son típicamente tan pequeños]).

+0

Gracias. No estaba abogando por la eliminación del ciclo del juego, simplemente manteniéndolo lo más simple posible, por lo que lo que dijiste tiene sentido. – Adamski

1

Este game fue un experimento para mantener el modelo y la vista por separado. Utiliza el patrón del observador para notificar la (s) vista (es) de los cambios en el estado del juego, pero los eventos quizás ofrezcan un contexto más rico. Originalmente, el modelo se basaba en la entrada de teclado, pero la separación facilitaba la incorporación de animaciones controladas por temporizador.

Adición: Debe mantener el modelo del juego por separado, pero puede volver a factorizar ese modelo en tantas clases como sea necesario.

2

Escribo mis propios motores (raw & sucio), pero un motor preconstruido que tiene un modelo de OO decente, es Ogre. Recomiendo echarle un vistazo (es un modelo de objeto/API). La asignación de nodo es un poco funky, pero tiene mucho sentido cuanto más lo miras. También está muy bien documentado con un montón de ejemplos de juegos de trabajo.

He aprendido un par de trucos de él mismo.

+0

Cosas geniales: lo verificaremos. – Adamski

Cuestiones relacionadas