java - programacion - Diseño de juegos en una manera OO
manual de programacion android pdf (6)
En un motor en particular en el que trabajé, desacoplamos la lógica de la representación gráfica y luego tuvimos objetos que enviarían mensajes por lo que querían hacer. Hicimos esto para que pudiéramos tener juegos en una máquina local o en red y no se pudieran distinguir entre sí desde el punto de vista del código. (Patrón de comando)
También tuvimos el modelo de física real hecho en un objeto separado que podría cambiarse sobre la marcha. Esto nos permite fácilmente meternos con la gravedad, etc.
Hicimos un uso intensivo del código impulsado por el evento (patrón de escucha), y muchos y muchos temporizadores.
Por ejemplo, teníamos una clase base para un objeto que era intersectable que podía escuchar el evento de colisión. Subclasificamos en una caja de salud. En caso de colisión, si fue golpeado por una entidad de jugador, envió un comando al colisionador para que ganara salud, envió un mensaje para transmitir un sonido a todos los que podían escucharlo, colisiones desactivadas, activó la animación para eliminar los gráficos. El gráfico de la escena, y establecer un temporizador para reinstalarse más tarde. Suena complicado, pero en realidad no lo fue.
Si recuerdo (y han pasado 12 años), tuvimos la noción abstracta de escenas, por lo que un juego era una secuencia de escenas. Cuando se terminó una escena, se disparó un evento que normalmente enviaría un comando para eliminar la escena actual y comenzar otra.
Estoy diseñando un juego simple, que utiliza Java 2D y física newtoniana. Actualmente mi principal "bucle de juego" se ve algo como:
do {
for (GameEntity entity : entities) {
entity.update(gameContext);
}
for (Drawable drawable : drawables) {
drawable.draw(graphics2d);
}
} while (gameRunning);
Cuando se instruye a una entidad para que se actualice, ajustará su velocidad y posición en función de las fuerzas actuales que se le apliquen. Sin embargo, necesito entidades para exhibir otro comportamiento; por ejemplo, si un jugador dispara a un "chico malo", 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 como Game
, que realiza los pasos: detectar colisiones, comprobar si hay matanzas de jugadores malos, verificar si se han matado jugadores, volver a pintar , etc. y encapsula todo el estado del juego (vidas restantes, etc). En otras palabras, es muy procedimental y toda la lógica está en la clase de Juego . ¿Alguien puede recomendar un mejor enfoque?
Aquí están las opciones que he pensado hasta ahora:
- Pase un
GameContext
a cada entidad de la que la entidad pueda eliminarse si es necesario o actualice el estado del juego (por ejemplo, "no se ejecuta" si el jugador muere). - Registre cada
GameEntity
como oyente de la clase deGame
central y tome un enfoque orientado a eventos; por ejemplo, una colisión resultaría en unCollisionEvent
despedido a los dos participantes en la colisión.
Escribo mis propios motores (crudo y sucio), pero un motor preconstruido que tiene un modelo OO decente, es Ogre . Recomiendo echarle un vistazo (es el modelo de objeto / API). La asignación de nodos es un poco funky, pero tiene mucho sentido a medida que uno lo mira. También está muy bien documentado con un montón de ejemplos de juegos de trabajo.
Yo mismo he aprendido un par de trucos.
Este game fue un experimento para mantener el modelo y la vista separados. Utiliza el patrón de observador para notificar a la vista (s) cambios en el estado del juego, pero los eventos tal vez ofrezcan un contexto más rico. Originalmente, el modelo estaba controlado por la entrada del teclado, pero la separación facilitaba la adición de animaciones controladas por temporizador.
Anexo: debe mantener el modelo del juego separado, pero puede modificar el modelo en tantas clases como sea necesario.
He trabajado estrechamente con dos motores de juegos comerciales y siguen un patrón similar:
Los objetos representan componentes o aspectos de una entidad de juego (como físico, rendible, lo que sea), en lugar de toda la entidad. Para cada tipo de componente hay una lista gigante de componentes, una para cada instancia de entidad que tiene el componente.
El tipo de ''entidad de juego'' en sí es solo una ID única. Cada lista gigante de componentes tiene un mapa para buscar el componente (si existe) que corresponda a un ID de entidad.
Si un componente requiere una actualización, es llamado por 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 a partir de un gráfico de dependencia.
Aquí están las ventajas de este enfoque:
Puede combinar libremente la funcionalidad sin escribir nuevas clases para cada combinación o utilizar árboles de herencia complejos.
Casi no hay ninguna funcionalidad que puedas asumir sobre todas las entidades de juego que podrías poner en una clase base de entidad de juego (¿qué tiene una luz en común con un auto de carrera o un sky-box?)
Las búsquedas de ID a componentes pueden parecer costosas, pero los servicios están realizando la mayor parte del trabajo intensivo mediante la iteración de todos los componentes de un tipo en particular. En estos casos, funciona mejor almacenar todos los datos que necesita en una sola lista ordenada.
No estoy de acuerdo en que porque tienes una clase principal de Juego, toda la lógica tiene que suceder en esa clase.
La simplificación excesiva aquí imita su ejemplo solo para explicar 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 tienes una clase de burbuja:
Bubble implements Drawable {
handle( Needle needle ) {
if ( needle collide with us ) {
exploded = true;
}
}
draw (...) {
if (!exploded) {
draw();
}
}
}
Entonces, seguro, hay un bucle principal que se encarga de pasar 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á ocurriendo en la clase principal.
Por lo tanto, no estoy de acuerdo con su afirmación, que escribió en negrita, que "toda la lógica sucede 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 una "reproducción perfecta de cuadro por cuadro", entonces probablemente su diseño no sea tan malo (es decir: al registrar solo las entradas y el momento en que ocurrieron, deberías poder recrear el juego exactamente como se jugó. Así es como Age of Empires, Warcraft 3, etc. hacen su repetición: es solo las entradas del usuario y el momento en el que ocurrieron las grabaciones [por eso los archivos de reproducción suelen ser tan pequeños]).
Solo publico esto como una respuesta porque todavía no puedo hacer comentarios sobre otras publicaciones.
Como una adición a la excelente respuesta de Evan Rogers, podría interesarle estos artículos:
http://www.gamasutra.com/blogs/MeganFox/20101208/6590/Game_Engines_101_The_EntityComponent_Model.php
http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/