Aun a pesar de que no dispongo todo el tiempo del que gustaría para actualizar este “diario”, he seguido trabajando de modo continuado en el motor gráfico. Estas 2 primeras semanas me he dedicado más que nada a repasar y leer nuevos materiales. También he dedicado bastante tiempo a ver código fuente de otros motores, algunos clásicos como los de Quake o Half Life, para ir cogiendo ideas de cómo desarrollar las cosas. También añadir que en los libros que tengo suelen meter código fuente de motores reales a modo de aprendizaje, lo que sin duda se agradece bastante.
La primera tanda de código base ya está disponible en el GitHub del proyecto: https://github.com/AlbertoFEM/SeventhEngine; podéis echarle un vistazo.
boost::shared_ptr<>
Una de las primeras decisiones que he tomado para programar esto es el tiempo de recursos que voy a usar. Normalmente, en los motores gráficos más potentes, se usa un Resource Manager custom, es decir, personalizado al máximo para el motor en sí. Y no sólo es importante el tipo de memoria que manejamos, sino la estructura y la forma en la que la gestionamos. Son muchas las técnicas que se usan, pero la más común es el Stack-Based Allocator, el cuál por ahora no voy a usar, pero que seguro en el futuro habrá que, o bien implementarlo, o usar alguno ya hecho. Si queréis más información sobre esta técnica, podéis visitar los siguientes enlaces:
No obstante, puesto que memoria dinámica hay que usar sin más remedio, he decidido recurrir a las útil y potente librería Boost++. Puesto que el auto_ptr se quedaba corto (más información: http://www.aristeia.com/BookErrata/auto_ptr-update.html), voy a usar las class templates que dispone Boost para gestionar la memoria dinámica, a saber: shared_ptr, weak_ptr, scoped_ptr, scoped_array y shared_array. Usarlas es bastante fácil:
|
|
boost::shared_ptr<CDisplayCore> Display(new CDisplayCore); |
Además hay que tener cuidado a la hora de incluirlas como miembros de una clase, puesto que inicializarlos puede ser peligroso (sobre todo cuando no podemos, por razones de diseño, hacerlo en el constructor). Simplemente usando el método reset(), lo hacemos de forma segura:
|
|
m_IniHandler.reset(new INIReader(m_ConfigFile)); |
Game Loop & FPS
Otro de los temas que más tiempo me han ocupado ha sido el de los FPS. Un juego por definición es un bucle infinito en el que pasan cosas. Obviamente necesitamos una manera de limitar el número de veces que calculamos lo que va a pasar y cómo se lo mostramos al usuario. Esto se hace desarrollando una clase que controle los tiempos dentro del motor internamente. Hay bastantes métodos para solucionar este problema: Velocidad del juego dependiente de FPSs variables, Velocidad del juego constante con un máximo de FPSs, etc. En este artículo explican bastante bien las diferentes maneras: http://www.koonsolo.com/news/dewitters-gameloop/.
Por ahora voy a usar la implementación de Velocidad del juego constante con un máximo de FPS, puesto que es la que más facilidad dá para desarrollar el resto de módulos del engine. No obstante, he decidido extraer la clase CClock, usando un Singleton, ya que es interesante que en el futuro se pueda implementar diferentes tipos de game loop, incluso dejar al cliente decidir que técnica quiere usar y con qué parámetros quiere usarla. Aquí una vez más se nos plantea el problema de la división de responsabilidad, el qué dejar como algo interno del motor, o cómo trabajo para el programador de videojuegos. enlaces en cuanto a diseño del game loop:
El game loop por ahora tiene la siguiente pinta:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
bool running = true; // init clock Clock->init(); // main game loop while(running) { Clock->reset_loop(); while(Clock->game_update()) { // update game logic // update clock Clock->update(); } // render // stop loop if(s_Running == false) { running = false; } } |
Como podéis observar es bastante sencilla. Aún no he incluido el tema de los eventos ni los respectivos update() y render(). Sin embargo podéis ver como está integrado el reloj interno, definido en la clase include/Engine/CClock.h. Puesto que está en una clase aparte y única, en el futuro será fácil implementar distintos tipos de relojes, y también distintos relojes independientes entre sí. No obstante, puesto que las distintas implementaciones del game loop depende de cómo manejes el reloj, habría que implementar distintos game loops y cargar uno u otro según nos convenga; el método RunGame() está separado del resto por esto mismo: no sería difícil cargar un game loop u otro dependiendo de una variable de configuración.
La semana que viene hablaré sobre 2 sistemas bases que ya he empezado a programar: CDisplayCore y CGameplayCore, y dentro de ellos el CStateManager y el CTextureManager.