¿Cómo organizar un motor de juego en C ++? ¿Es mi uso de la herencia una buena idea?

11

Soy un principiante tanto en desarrollo de juegos como en programación.

Estoy tratando de aprender algunos principios en la construcción de un motor de juego.
Quiero crear un juego simple, estoy en el punto donde estoy tratando de implementar el motor del juego.

Entonces pensé que mi motor de juego debería controlar estas cosas:

- Moving the objects in the scene
- Checking the collisions
- Adjusting movements based on collisions
- Passing the polygons to the rendering engine

Diseñé mis objetos así:

class GlObject{
private:
    idEnum ObjId;
    //other identifiers
public:
    void move_obj(); //the movements are the same for all the objects (nextpos = pos + vel)
    void rotate_obj(); //rotations are the same for every objects too
    virtual Polygon generate_mesh() = 0; // the polygons are different
}

y tengo 4 objetos diferentes en mi juego: avión, obstáculo, jugador, bala y los diseñé así:

class Player : public GlObject{ 
private:
    std::string name;
public:
    Player();
    Bullet fire() const; //this method is unique to the class player
    void generate_mesh();
}

Ahora, en el motor del juego, quiero tener una lista general de objetos donde pueda verificar, por ejemplo, la colisión, mover objetos, etc., pero también quiero que el motor del juego tome los comandos del usuario para controlar al jugador ...

¿Es esta una buena idea?

class GameEngine{
private:
    std::vector<GlObject*> objects; //an array containg all the object present
    Player* hPlayer; //hPlayer is to be read as human player, and is a pointer to hold the reference to an object inside the array
public:
    GameEngine();
    //other stuff
}

El constructor de GameEngine será así:

GameEngine::GameEngine(){
    hPlayer = new Player;
    objects.push_back(hPlayer);
}

El hecho de que estoy usando un puntero es porque necesito llamar al fire()que es exclusivo del objeto Player.

Entonces mi pregunta es: ¿es una buena idea? ¿Mi uso de la herencia está mal aquí?

Pella86
fuente
Los he visto después de publicarlos, ¡y en realidad la composición es una gran idea! ¡Siempre me da miedo terminar usando c con clases en lugar de tener un diseño real de c ++!
Pella86
Creo que hay muchas más razones para usar la composición que la herencia ... Mi respuesta da algunos ejemplos sobre las ventajas de flexibilidad que ofrece. Para un buen ejemplo de implementación, consulte los artículos que he vinculado.
Coyote

Respuestas:

12

Depende de lo que quieras decir con "bueno". Hará el trabajo, sin duda. Tiene muchos lados positivos y negativos. No los encontrará hasta que su motor sea mucho más complejo. Vea esta pregunta sobre SO , para una discusión sobre el tema.

Hay muchas opiniones en ambos sentidos, pero si el código de su motor aún está en esa etapa inicial, será difícil explicarle por qué podría ser malo.

En general, Scott Meyers tiene algunas cosas buenas que decir sobre la herencia. Si todavía está en esa etapa de desarrollo, lea este libro (Effective C ++) antes de continuar. Es un libro fantástico y es un requisito para cualquier programador que trabaje en nuestra empresa. Pero para resumir el consejo: si está escribiendo una clase y está considerando usar la herencia, sea absolutamente claro que la relación entre la clase y su antepasado es una relación 'es una'. Es decir, cada B es un A. No solo use la herencia como una forma fácil de dar acceso a B a la funcionalidad que A proporciona, de esa manera se generarán graves problemas arquitectónicos, y hay otras formas de hacerlo.

En pocas palabras; para un motor de cualquier tamaño, descubrirá rápidamente que los objetos rara vez caen en una estructura jerárquica ordenada para la que funciona la herencia. Úselo cuando sea apropiado, pero no caiga en la trampa de usarlo para todo: aprenda otras formas de interrelacionar objetos.

MrCranky
fuente
Secundo la opinión sobre la herencia. Mi regla general es: no lo hagas a menos que tengas que hacerlo o hacerlo de otra manera sería estúpido. En otras palabras, la herencia es una mala idea en el 99% de los casos (al menos :)). Además, puede ser bueno separar la lógica del juego (rotar / mover) del renderizado (generate_mesh ()). Otra cosa es fire (): considere tener una lista de acciones y una función de acción genérica (my_action), de esta manera no terminará con un billón de funciones diferentes distribuidas en todas las clases. En cualquier caso, manténgalo simple y refactorice sin piedad a medida que se encuentre con problemas.
Galaad
8

Para los datos lógicos y las funciones de los objetos (entidades) del juego, recomendaría encarecidamente utilizar la composición sobre la herencia. Un buen comienzo sería el enfoque basado en componentes. Está bien descrito en este artículo de Programación Cowboy que describe cómo se puede implementar esta arquitectura y cómo benefició a la franquicia Tony Hawk.

También este artículo sobre sistemas de entidades me inspiró cuando lo leí por primera vez.

La herencia es una gran herramienta para el polimorfismo. Excepto para proyectos muy simples, debe evitar usarlo como una forma de estructurar las entidades y las lógicas de su juego. Si opta por la herencia, seguramente tendrá que replicar y mantener copias del mismo código para implementar funciones que no pueden heredarse de un padre común. Y probablemente comenzará a contaminar sus clases con las lógicas y los datos más utilizados para evitar replicar demasiado código.

Los componentes son muy útiles en numerosas situaciones. Puedes lograr mucho más usándolos:

Flexibilidad con sus tipos de entidad : hay momentos en el desarrollo de juegos en los que algunas entidades evolucionan repentinamente y mientras el diseñador del juego toma la decisión, el desarrollador tendrá que implementarlo. Solo se requieren unos pocos pasos para agregar un comportamiento existente sin replicar el código. Los sistemas basados ​​en componentes permiten que más programadores realicen más cambios. Por ejemplo, cada tipo de entidad en lugar de estar representado por una clase puede representarse mediante un archivo de descripción que contiene todos los nombres de componentes y parámetros necesarios para configurar el tipo. Esto permite a los no programadores hacer y probar cambios sin volver a compilar el juego. Simplemente editando los archivos de descripción será suficiente para agregar, eliminar o cambiar los componentes utilizados por cada tipo de entidad.

Flexibilidad con instancias particulares y escenarios específicos : hay casos en los que necesita que su jugador pueda equipar armas u objetos, puede agregar un componente de inventario a su jugador. Durante el desarrollo de tu juego necesitarás otras entidades para tener un inventario (enemigos por ejemplo). Y en un momento tienes en tu escenario una situación en la que se supone que un árbol contiene algún tipo de elemento oculto. Con los componentes, puede agregar un componente de inventario incluso a entidades cuyo tipo no está diseñado originalmente para tener uno sin ningún esfuerzo de programación adicional.

Flexibilidad en tiempo de ejecución : los componentes ofrecen una forma muy efectiva de cambiar el comportamiento de las entidades en tiempo de ejecución. Puede agregar y eliminar componentes en tiempo de ejecución y, por lo tanto, crear o eliminar comportamientos y propiedades cuando sea necesario. También le permiten separar diferentes tipos de datos en grupos que (a veces) se pueden calcular por separado en paralelo en múltiples GPU.

Flexibilidad en el tiempo : los componentes se pueden utilizar para mantener la compatibilidad entre versiones. Por ejemplo, cuando se realizan cambios entre lanzamientos de un juego, puede (no siempre) simplemente agregar nuevos componentes que implementen los nuevos comportamientos y cambios. Luego, el juego instanciará los componentes correctos conectados a sus entidades dependiendo del archivo guardado cargado, conectado por pares o servidor al que se une el jugador. Esto resulta extremadamente útil en escenarios en los que no puede controlar las fechas de lanzamiento de la nueva versión en todas las plataformas (Steam, Mac App Store, iPhone, Android ...).

Para una mejor visión, eche un vistazo a las preguntas etiquetadas [ basadas en componentes] y [sistema de entidad] .

Coyote
fuente