consol08: Presentacion
Transcripción
consol08: Presentacion
Desarrollo de videojuegos con MotorJ 02/20/08 Alejandro Valenzuela Roca, LIDSOL, UNAM Desarrollo de videojuegos con MotorJ Alejandro Valenzuela Roca Laboratorio de Investigación y Desarrollo de Software Libre, UNAM ¿Qué es? MotorJ es una colección de bibliotecas que contienen funciones útiles para el desarrollo de videojuegos. Está desarrollado en lenguaje C y C++ y se emplea ampliamente OpenGL para gráficos y Simple DirectMedia Layer para las ventanas y el sonido. Es un proyecto siendo desarrollado en LIDSOL, UNAM, pensado para facilitar el desarrollo de videojuegos. 2 Desarrollo de videojuegos con MotorJ Introducción ¿Cómo puede usarse? MotorJ es Software Libre bajo la licencia GNU LGPL v3, por lo que es utilizable para desarrollos tanto libres como cerrados, pero poniendo énfasis en los desarrollos libres. MotorJ puede utilizarse tanto en GNU/Linux como en FreeBSD y Windows y uno de los objetivos es que sea posible realizar desarrollos en todos los sistemas soportados sin tener que modificar el código. 3 Requerimientos Computadora decente, 800+ MHz, 256 MB RAM Tarjeta de video decente, Intel Medio Acelerada (Media Accelerator), que tenga soporte para al menos lo básico de OpenGL Programación en un lenguaje estructurado y POO (Cuando menos lo básico) Lo básico de vectores y álgebra vectorial, física de prepa. Perseverancia Curiosidad Recomendaciones Buena computadora, 1.5+ Ghz [No Celeron], 512+ MB RAM Tarjeta de video NVidia con driver NVidiot (privativo >:( ) o Tarjeta de video ATI con un driver que sirva. C++ con clase(s), OpenGL, Shaders, optimización, etc. Geometría analítica en el espacio, álgebra lineal, cinemática, dinámica, cálculos, programación, inteligencia artificial, etcéteras. Las mates rulan. Necedad No dormir (no mucho..) 4 Software adicional GIMP Audacity Vorbis tools Blender? Editor de textos favorito ( Anjuta, Emacs, vim, etc.) Calculadora científica, preferiblemente que maneje vectores, aún más preferible en físico 5 Motor de videojuegos básico 6 El funcionamiento de un motor de videojuegos básico Se establecen los valores iniciales del juego (vidas, mundo, etc). Se inicia el ciclo de simulación, el cual se repetirá hasta que el usuario quiera salir: Leer controles Procesar como afectan al juego Realizar una actualización del mundo interno del juego – mover objetos, verificar colisiones, etc. Representar el mundo interno del videojuego mediante instrucciones de “algún” API gráfico. Si el usuario desea salir, 7 terminar el programa. El funcionamiento de MotorJ Pero antes... 8 El funcionamiento de MotorJ (¿Qué es un “universo”?) Un universo en MotorJ es un objeto que se deriva de la clase Universe y que contiene las siguientes funciones: Initialize, inicializar, donde se establecen las características iniciales del universo (variables, etc.). Aquí podría haber una manera de abrir save-states. ProcessEvent, procesar evento, donde se procesan las interacciones del usuario con los controles. Update, actualizar, donde se modifica el estado actual del universo de acuerdo a lo que se obtuvo en el procedimiento anterior. En este paso se realizan todos los cálculos físicos, de colisiones y se actualizan también. Redraw, redibujar, donde se representa gráficamente el estado actual del universo. Cleanup, limpiar, donde se libera la memoria ocupada por la mayor parte del universo para ser re-inicializado posteriormente. Aquí podría haber una manera de guardar save-states. 9 Más sobre los “universos” En otras palabras... Un universo contiene la manera como el juego responde a los controles del usuario, controla como funciona la física en su interior, así como las reacciones de las entidades inteligentes (enemigos, NPCs, etc.) y también debe tener un inicio y un fin. Pueden haber tantos universos como se necesiten. ¿Por qué no llamarles “mundos” en vez de “universos”? La idea es que un mismo universo pueda cargar muchos escenarios (“mundos”) distintos, pero que todos ellos contengan las mismas reglas físicas, y que cuando se necesiten modificar las reglas físicas (o que incluso no existan, como en un menú), se utilice otro universo. El programa podría cambiar entre varios universos distintos según el transcurso del juego lo requiera. De esta manera es más fácil mantener por separado situaciones del juego muy distintas entre sí, y queda más legible el código, además de evitar problemas de nombramiento de variables, horrorosas variables globales, etc. 10 El funcionamiento de MotorJ (Ahora sí..) 11 Configuración del entorno de programación 12 Configurando el entorno Para desarrollar con MotorJ, es necesario tener instaladas todas las bibliotecas de las cuales depende. Estas son: libsdl libsdl-image libsdl-mixer libsdl-net libsdl-ttf Estas bibliotecas se encuentran disponibles en los manejadores de paquetes de la mayoría de las distros 13 Configurando el entorno (paquetes en Synaptic) 14 Configurando el entorno (obteniendo el código fuente) Para crear un proyecto basado en MotorJ, es necesario bajar el código fuente del repositorio de LIDSOL mediante SubVersion SubVersion también se encuentra en los sistemas de paquetes de la mayoría de las distros; una vez instalado, se debe crear un directorio donde se pondrá una copia local del repositorio. Para obtener el código fuente, hay que abrir una terminal, navegar hasta el directorio creado en el paso anterior y ejecutar la siguiente orden: svn checkout http://svn.lidsol.net/motorj 15 Configurando el entorno (obteniendo el código fuente - svn) Nota: aparecerán muchos más mensajes en la terminal. Se acortó la lista para propósitos de la imagen. 16 Creando un nuevo proyecto basado en MotorJ Una vez obtenido el código fuente, desde la misma terminal donde se efectuó el svn checkout, se cambia al directorio motorj/trunk/pc_sdl y se ejecuta el siguiente script: ./create_new_pc_project.sh El script nos pedirá un lugar para el nuevo proyecto, debemos proporcionarle una ruta completa a un directorio que se encuentre dentro de un sistema de archivos nativo de GNU/Linux (o FreeBSD). El directorio será creado si no existía. 17 Creando un nuevo proyecto (create_new_pc_project.sh) 18 Compilando el demo 19 Corriendo el demo 20 Creando un nuevo proyecto (¿Qué hay dentro del proyecto default?) El proyecto “default” creado por el script contiene varios subdirectorios y archivos: bin, donde aparecerá el binario al ser compilado pixmaps, donde se establecen los íconos src, donde está alojado el código fuente Makefile, licencia, archivo TODO (por hacer), README y un ícono y el proyecto .dev para DevC++ (para compilar en Windows) Para compilar el proyecto, se debe escribir el comando make en una terminal ubicada en este directorio. 21 Creando un nuevo proyecto (¿Qué hay dentro de src?) Dentro del directorio src se encuentra el código fuente para un demo con dos universos, menu-universe y game-universe. universes.cpp contiene la información de qué universo será puesto a funcionar primero (por defecto, MenuUniverse), así como la creación de todos los universos del programa. main-universe se encarga de configurar OpenGL e inicializar todos los sistemas básicos del juego (sonido, red, etc) y hacer funcionar los demás universos. menu-universe está pensado para mostrar al usuario un menú donde sea introducido al juego y pueda configurar distintas opciones del mismo. game-universe está pensado para ser la parte principal del videojuego, donde el usuario interactúe y se divierta con él. 22 ¿Qué hay dentro de menu-universe? Dentro del directorio menu-universe hay dos archivos: menu-universe.h menu-universe.h contiene la declaración (la forma, el esqueleto) de lo que es el universo del menú, descendiente de la clase Universe. Esto quiere decir que es la particularización de la clase Universe, con variables adicionales que no están en Universe. También se usa para incluir otras bibliotecas y otras clases que sean útiles al juego, por ejemplo, la biblioteca para cargar imágenes como texturas y la biblioteca para renderizar texto como una textura. menu-universe.cpp contiene la implementación de lo que está descrito en menuuniverse.h, es decir, el código que realiza las operaciones descritas por Universe más otras propias de menu-universe. 23 El menu-universe default MenuUniverse es class MenuUniverse : public Universe descendiente de Universe { public: Estas funciones y void Init(void); variables fueron void ProcessEvent(s_event & event); especificadas por Universe como funciones void Update(float t_trans); virtuales (es decir, void Redraw(void); funciones que NO están implementadas en void Cleanup(void); Universe pero DEBEN bool mustCleanup; ser implementadas por sus descendientes). bool mustInit; private: Estas variables no están en Universe; son particulares a MenuUniverse y se cam_ctl camera; usarán para controlar algunos detalles float rot_x, rot_y, rot_z; de la animación (NO necesitan ser privadas, pero en este caso es }; conveniente). 24 El menu-universe default: Init() void MenuUniverse::Init() { la cámara y sus parámetros internos en init_cam_ctl_def(&camera);Inicializar la posición y orientación default mjEnableSimpleLighting(); Inicializar la iluminación con parámetros bastante sencillos rot_x = rot_y = rot_z = 0; Poner la rotación de “algo” (un cubo, pero desde aquí no se “ve”) en ceros (¡¡ceros, joven!!) glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE); Usar el parámetro de material “color difuso” en las caras al verlas de frente y por detrás glEnable(GL_COLOR_MATERIAL); glColor3f(1,0,0); } Se establece el rojo como color actual (R,G,B), cada uno de 0 a 1. Las superficies tendrán color (al habilitar la iluminación, se desactiva el utilizar el color actual para la superficie; esta función junto con la anterior lo vuelve a activar) 25 El menu-universe default: ProcessEvent() void MenuUniverse::ProcessEvent(s_event & event) { if (event.quit) Este evento es recibido cuando se quiere cerrar el programa, ya sea con el botón de “cerrar” de la ventana, o con kill. { SetNextUniverse(NULL); Para activar la secuencia de terminación de MotorJ, se manda NULL como el “siguiente Universo”. } switch(event.key_down){ Cuando se presiona una tecla, se recibe un número distinto a 0 en key_down. El número case 'a':{ camera.dsc_ctl.fwd = 1; corresponde a carácteres en minúsculas para las letras y a números que no son carácteres para }break; teclas especiales (F1, flechas, etc.) //... } switch(event.key_up){ En el menu-universe default se controla una case 'a':{ cámara usando las flechas y las teclas “A” y Z”. camera.dsc_ctl.fwd = 0; Aquí se está desactivando el movimiento “avanzar” de la cámara, cuando se suelta la }break; tecla “A”. //... } } 26 El menu-universe default: Update(t_elapsed) void MenuUniverse::Update(float t_elapsed) Se utiliza la rutina automática de actualización { de la cámara (la cámara volteará y se moverá update_cam_auto(&camera); de acuerdo al comportamiento establecido). rot_x += 0.02*t_elapsed; rot_y += 0.04*t_elapsed; rot_z += 0.01*t_elapsed; Se incrementan los ángulos de rotación de “algo” (un cubo) para simular movimiento. Nótese que el incremento es dependiente del tiempo utilizado en el frame anterior (si la máquina va a pocos fps, se verá menos fluido pero el cubo girará a la misma velocidad que en una máquina rápida que pueda proporcionar más fps). if (rot_x > 360) rot_x -= 360; if (rot_y > 360) rot_y -= 360; if (rot_z > 360) rot_z -= 360; Se limitan los ángulos de 0 a 360°. OpenGL maneja de manera nativa los grados, no es necesario convertir a radianes. } 27 El menu-universe default: Redraw() void MenuUniverse::Redraw(void) { Se reinicia la pila de transformaciones de OpenGL glLoadIdentity(); (¡¡Ceros, joven!!) gluLookAt(camera.pos.x, camera.pos.y, camera.pos.z, camera.pos.x+camera.dir.x, camera.pos.y+camera.dir.y, camera.pos.z+camera.dir.z, camera.dirv.x, camera.dirv.y, camera.dirv.z); Se simula la perspectiva de la cámara } mjDefLightPos(); Se simula el posicionamiento de la luz default “retroceden” 5 unidades con respecto a la posición glTranslatef( 0, 0, -5); Se actual. Todo lo que siga aparecerá recorrido 5 unidades glRotatef(rot_x, 1,0,0); hacia adelante en “Z”. glRotatef(rot_y, 0,1,0); Se realizan las rotaciones (todo lo que aparezca después cada glRotatef estará rotado el número de grados glRotatef(rot_z, 0,0,1); de especificados alrededor del vector) mjSolidCube(); Se dibuja el cubo. Voilá Ninguna variable debe modificarse en este procedimiento, para eso están ProcessEvent y Update 28 Cambiando entre universos Cleanup() está vacío puesto que no hicimos ninguna asociación de memoria de manera dinámica y no es necesario liberarla. En Cleanup() también podríamos reiniciar algunas variables de OpenGL (por ejemplo, glEnable(GL_COLOR_MATERIAL); ) pero para no hacer esto más complicado, lo dejaremos así y simplemente tendremos en cuenta que sus efectos se aplicarán también a game-universe cuando cambiemos. Por omisión, el game-universe está “vacío” y listo para usarse. Aprovecharemos game-universe para elaborar ahí nuestro juego de video. Pondremos un pedazo de código para cambiar de menu-universe a game-universe. 29 El proceso de creación de universos: universes.cpp Universe * create_universes() { Universe * result; Se crean los universos (ambas variables son de tipo Universe * y están declaradas más arriba) menu = new MenuUniverse(); Los tipos MenuUniverse y GameUniverse se game = new GameUniverse(); pueden usar porque en universes.h se incluyeron menu-universe.h y game-universe.h menu->Init(); Se realiza la inicialización del primer universo Se agregan los universos a lista de universos (Nota: list_append(0, (void **) &menu, &App.universes); la el formato de lista es list_append(1, (void **) &game, &App.universes); candidato a ser reemplazado próximamente). game-universe será accesible con el número 1 result = menu; result->SetNextUniverse(result); Se establece el menú como el primer universo a ejecutar return result; } 30 Cambiando de un universo a otro void MenuUniverse::ProcessEvent(s_event & event) { // switch(event.key_down){ case RETURN:{ Universe * gameUni; gameUni = (Universe *) list_find_type(1, 0, &App.universes)->data; SetNextUniverse(gameUni); }break; case 'a':{ camera.dsc_ctl.fwd = 1; }break; //... } //... } 31 Funcionamiento de un juego (desde un punto de vista abstracto) Un juego de video, desde un punto de vista abstracto, debe funcionar en este orden: Recibir comandos del usuario Usar esas órdenes para modificar variables Actualizar las entidades “inteligentes” dentro del juego (moverlas, verificar reglas de interacción, tomar decisiones) Verificar cómo afectan las modificaciones y las condiciones actuales en la simulación física Verificar si se ha cumplido alguna de las reglas de juego (condiciones de salida, etc.) Mostrar los resultados y repetir el ciclo 32 Propuesta de mini-juego de video Juego estilo Mario64 Personaje humanoide que camine sobre un terreno desigual El terreno es relativamente grande Se aparece en un extremo y se debe llegar al otro El terreno es volcánico con charcos de lava Hay malignos cubos que perseguirán al personaje si se acerca demasiado a ellos Hay gravedad El humanoide puede saltar Texturas y música acordes al juego 33 Ajuste de la propuesta de mini-juego al funcionamiento (abstracto) de un juego Recibir comandos del usuario (Las flechas desplazarán al humanoide) Usar esas órdenes para modificar variables (La posición del humanoide será modificada con respecto a la perspectiva de la cámara) Actualizar las entidades “inteligentes” dentro del juego (Los enemigos se moverán de manera aleatoria hasta que se encuentren cerca del humanoide; en ese caso irán en dirección al humanoide) Verificar cómo afectan las modificaciones y las condiciones actuales en la simulación física (Detección de colisiones contra el piso: en caso de no haber colisión contra el piso, simular gravedad; prueba de altura para determinar si el humanoide ha tocado los lagos de lava) Verificar si se ha cumplido alguna de las reglas de juego (Distancia entre el humanoide y el lugar del fin del juego) 34 Mostrar los resultados y repetir el ciclo ¡Genial! Y ahora ¿Qué?.. ¿Cómo lograremos hacer todo eso? Veamos qué hay en el directorio “motorj”.. 35 ¿Qué contiene MotorJ? app.h: Contiene la clase mjApplication. Esta clase proporciona varios datos sobre la aplicación actual (resolución, soporte de sonido, soporte de red, etc.) cam_ctl.h: Contiene la estructura (pronto clase) cam_ctl, que implementa una cámara con dos modos de operación – manual y seguidora-, y su funcionamiento. collisions.h: Contiene funciones para calcular colisiones – esfera vs esfera, triángulo vs rayo, caja orientada vs caja orientada, caja alineada vs caja alineada, etc. data_structs.h: Contiene la implementación de una lista genérica. También es candidata a volverse clase, para facilitar su manejo. events.h: Contiene la estructura “evento” y la definición de varias teclas “especiales” (RETURN, LEFT_KEY, etc.). glContexts.h: Intento (todavía infructuoso) de poder usar OpenGL directamente en varios hilos. No está lista, no usar. lanjobot.h: Humanoide formado por cubos. Con 30+ ángulos configurables, puede tomar casi cualquier pose humana imaginable. musicplayer-class.h y networkctl-class.h: Clases que controlan la música de fondo y la red (PERO solo en la versión para Nintendo DS – no usar). objects.h: Contiene instrucciones para trazar varios objetos. Entre ellos un cubo sólido y uno de marco de alambre. ovejota.h: Permite interpretar archivos en formato .obj para emplearlos como modelos 3d. 36 ¿Qué contiene MotorJ? (continuación) sound.h: Permite cargar, a través de SDL_Mixer, archivos .ogg y .wav para usar como música de fondo y efectos de sonido. Podría cambiar sustancialmente dentro de poco tiempo. stenciltricks.h: Realiza trucos visuales con el buffer de stencil, p.ej., como una “cortinilla” de caricatura (de hecho es el único efecto que contiene en este momento, pero se planea incorporarle más). support.h: Contiene las estructuras de datos más usadas, así como todas las funciones de geometría analítica básicas. Casi todos los demás archivos lo incluyen; podría ser considerado como la base de MotorJ textrender.h: Convierte de texto a una textura fácilmente utilizable por OpenGL, usando SDL_TTF. textures.h: Carga imágenes en casi cualquier formato para emplearse como texturas en OpenGL, usando SDL_Image. sound.h: Permite utilizar archivos .ogg y .wav como música de fondo y efectos de sonido, respectivamente, usando SDL_Mixer. universe-class.h: Establece la “forma” general de los universos. *.cpp: Contienen la implementación. Si hay algún bug o quieren ver como se hizo, es una buena idea consultarlos y modificarlos. La documentación está en proceso; la página oficial es http://wiki.lidsol.net/wiki/index.php?title=Motorj_doc_index 37 Contacto • • • • • • • LIDSOL http://www.lidsol.org Blog http://mexinetica.com/~lanjoe9/bloginetica Email Lanjoe9 at mexinetica . com Alejandro Valenzuela Roca 38