Desarrollo de Videojuego 3D Para La Videoconsola

Anuncio
ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA
INGENIERO TÉCNIDO EN INFORMÁTICA DE SISTEMAS
Desarrollo de Videojuego 3D Para La Videoconsola Nintendo DS
Realizado por
Fernando García Bernal
Dirigido por
Francisco R. Villatoro Machuca
Departamento
Lenguajes y Ciencias de la Computación
UNIVERSIDAD DE MÁLAGA
MÁLAGA, ABRIL 2008
UNIVERSIDAD DE MÁLAGA
ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA
INGENIERO TÉCNICO EN INFORMÁTICA DE SISTEMAS
Reunido el tribunal examinador en el día de la fecha, constituido por:
Presidente Dº/Dª.
Secretario Dº/Dª.
Vocal Dº/Dª.
para juzgar el proyecto Fin de Carrera titulado:
Desarrollo de videojuego 3D para la videoconsola Nintendo DS
del alumno D. Fernando García Bernal
dirigido por Dr. D. Francisco R. Villatoro Machuca
ACORDÓ POR _________________ OTORGAR LA CALIFICACIÓN DE ___________________
Y PARA QUE CONSTE, SE EXTIENDE FIRMADA POR LOS COMPARECIENTES DEL TRIBUNAL, LA
PRESENTE DILIGENCIA.
Málaga, a
de
del 2007
El Presidente
El Secretario
El Vocal
Fdo.:
Fdo.:
Fdo.:
Índice
Capítulo 1. Introducción ...................................................................1
1.1
Antecedentes........................................................................................... 1
1.2
Objetivos ................................................................................................. 2
1.3
Contenidos de la memoria ..................................................................... 3
Capítulo 2. Videoconsola Nintendo DS ............................................5
2.1
2.1.1
2.2
Hardware................................................................................................ 6
Hardware adicional para cargar aplicaciones .....................................................6
Desarrollo en Nintendo DS .................................................................... 8
2.2.1
Libnds....................................................................................................................9
2.2.2
PAlib....................................................................................................................10
Capítulo 3. Librería PAlib ..............................................................13
3.1
Configuración del Entorno de Desarrollo y Emuladores................... 13
3.2
Programación en Nintendo DS Usando la Librería PAlib ................. 17
3.2.1
Plantilla PAlib .....................................................................................................17
3.2.2
Primera aplicación ..............................................................................................18
3.2.3
Textos ..................................................................................................................19
3.2.4
Entrada................................................................................................................21
3.2.4.1
Pad.......................................................................................................................21
3.2.4.2
Stylus ...................................................................................................................21
3.2.5
Imágenes..............................................................................................................22
3.2.5.1
Mostrando imágenes ...........................................................................................24
3.2.5.2
Desplazando imágenes ........................................................................................26
3.2.5.3
Rotaciones y zoom...............................................................................................29
3.2.5.4
Volteo de imágenes..............................................................................................33
3.2.5.5
Transparencias....................................................................................................34
3.2.5.6
Profundidad ........................................................................................................35
3.2.6
Funciones Matemáticas ......................................................................................36
3.2.6.1
Números aleatorios .............................................................................................36
I
3.2.6.2
Decimales con punto fijo.....................................................................................37
3.2.6.3
Trayectorias y ángulos........................................................................................38
3.2.7
Sonido..................................................................................................................42
3.2.7.1
Archivos de sonido Raw......................................................................................43
3.2.7.2
Archivos de sonido mod......................................................................................45
3.2.7.3
Funciones adicionales de sonido .........................................................................46
3.2.8
Programación Hardware DS ..............................................................................46
3.2.8.1
Fecha y hora ........................................................................................................46
3.2.8.2
Información de usuario.......................................................................................47
3.2.8.3
Pausa al cerrar ....................................................................................................48
3.2.8.4
Iluminación de pantallas.....................................................................................49
3.3
Programación 3D ................................................................................. 49
3.3.1
Inicializar funciones 3D ......................................................................................50
3.3.2
Polígonos .............................................................................................................52
3.3.3
Rotación ..............................................................................................................54
3.3.4
Figuras Básicas 3D..............................................................................................56
3.3.5
Texturas ..............................................................................................................59
3.3.6
Iluminación .........................................................................................................64
3.3.7
Seleccionar objetos..............................................................................................67
Capítulo 4. Fase de Análisis del Juego ...........................................75
4.1
Idea........................................................................................................ 76
4.2
Guión .................................................................................................... 77
4.3
Modelado conceptual ........................................................................... 77
4.3.1
Fases del juego ....................................................................................................77
4.3.2
Elementos ............................................................................................................78
4.4
Diagrama de casos de uso .................................................................... 79
4.4.1
Diagrama de casos de uso del actor....................................................................79
4.4.2
Diagrama de casos de uso del usuario ................................................................79
Capítulo 5. Fase de Diseño ..............................................................81
5.1
Diseño de Personaje y Objetos............................................................. 81
5.2
Diseño de la Interfaz del Usuario ........................................................ 82
II
5.3
Estudio de las clases ............................................................................. 84
5.3.1
Diagrama de clases..............................................................................................84
5.3.2
Definición de las clases........................................................................................85
Capítulo 6. Implementación............................................................91
6.1
Recursos gráficos.................................................................................. 91
6.1.1
Sprites..................................................................................................................92
6.1.2
Creación objetos 3D............................................................................................92
6.1.2.1
Formato DirectX .................................................................................................96
6.1.2.2
Conversión de formato ..................................................................................... 105
6.2
Arquitectura de la aplicación ............................................................ 111
6.2.1
Librerías............................................................................................................ 111
6.2.1.1
Vectores.h.......................................................................................................... 111
6.2.1.2
glQuaternion.h .................................................................................................. 114
6.2.1.3
Librería lib ........................................................................................................ 116
6.2.2
Clase Figura ...................................................................................................... 120
6.2.3
Clase Objeto3D ................................................................................................. 121
6.2.4
Clase Entidad .................................................................................................... 123
6.2.5
Clase Personaje ................................................................................................. 125
6.2.6
Clase Escenario ................................................................................................. 126
6.2.6.1
Constructor Escenario ...................................................................................... 136
6.2.6.2
Ciclo de juego .................................................................................................... 141
6.2.6.3
Búsqueda de camino ......................................................................................... 159
6.2.7
Crear nuestra propia aventura. Especializaciones de escenario y entidad..... 163
6.2.7.1
Herencia de Entidad ......................................................................................... 164
6.2.7.2
Herencia de Escenario ...................................................................................... 165
6.2.8
Fichero main ..................................................................................................... 173
6.3
Implementar nuestra aventura.......................................................... 174
6.3.1
Protagonista ...................................................................................................... 175
6.3.2
Entidades........................................................................................................... 175
6.3.2.1
Mesa .................................................................................................................. 175
6.3.2.2
Estatua............................................................................................................... 176
6.3.2.3
Puerta ................................................................................................................ 177
III
6.3.2.4
Fregona.............................................................................................................. 177
6.3.2.5
Llave .................................................................................................................. 178
6.3.2.6
Árbol.................................................................................................................. 179
6.3.2.7
Coco................................................................................................................... 179
6.3.3
Escenarios ......................................................................................................... 180
6.3.3.1
Escenario Habitación........................................................................................ 180
6.3.3.2
Escenario Exterior ............................................................................................ 185
6.3.4
Fichero main ..................................................................................................... 188
Capítulo 7. Conclusiones ...............................................................191
7.1
Inconvenientes y opiniones ................................................................ 191
7.2
Futuros desarrollos ............................................................................ 194
Apéndice A. Glosario.....................................................................197
Apéndice B. Contenido del CD-ROM ..........................................201
Bibliografía.....................................................................................205
IV
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Introducción
Capítulo 1. Introducción
1.1 Antecedentes
Los videojuegos han acompañado a la informática prácticamente desde su nacimiento
convirtiéndose en una muestra de los avances hardware y software que se iban realizando en
el sector. Se convirtieron en una de las maneras de entretenerse para la mayoría de aquellos
que estaban relacionados con la informática, quedando apartados de la gente ajena a este
entorno. Una vez que se empieza a ver la popularidad que alcanzan los videojuegos,
comienzan a formarse grandes empresas que darán lugar a la llamada industria de los
videojuegos.
Estas
empresas
desarrollarán
juegos
para
ordenadores
personales,
microordenadores (Commodore 64 o Spectrum) y sistemas domésticos o videoconsolas. Hoy
día es una industria consolidada que mueve mucho dinero y en continuo aumento, que ha
demostrado la capacidad que tienen los videojuegos para entretener y enseñar.
El desarrollo de los videojuegos se ha convertido en una tarea multidisciplinaria, que
involucra profesionales de la informática, el diseño, el sonido, la actuación… Los estudios en
informática sobre: inteligencia artificial, redes neuronales, modelado de procesos físicos, etc.
tienen aplicación directa en el desarrollo de videojuegos. Recientemente están surgiendo en
universidades españolas los primeros másters de diseño y desarrollo de videojuegos para
completar la formación de los informáticos. Como por ejemplo “I Máster Universitario en
Fernando Garcia Bernal
1
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Introducción
Creación y Desarrollo de Videojuegos” de la Universidad de Málaga [1], “Máster en
Desarrollo de Videojuegos” de la Universidad Complutense de Madrid [2] o el “Máster en
Diseño y Programación de Videojuegos” de la Universidad Europea de Madrid [3].
Actualmente las empresas desarrollan sus propias herramientas de desarrollo y además, las
que poseen alguna videoconsola en el mercado disponen de sus propios kit’s de desarrollo con
el hardware y software necesario para probar las aplicaciones. Sin embargo, existen
alternativas a los SDK (Software Development Kit) propietarios de las compañías, que surgen
gracias al aporte de personas que programan y distribuyen librerías con licencias abiertas que
permiten desarrollar para videoconsolas. Estos videojuegos creados de manera no profesional
son conocidos como homebrew (aplicaciones informáticas realizadas por aficionados para
plataformas de videojuegos propietarias). En el caso concreto de la videoconsola portátil
Nintendo DS (NDS) de Nintendo, existen dos librerías popularizadas llamadas libnds [4] y
PAlib [5] que están incluidas en Devkitpro [6], que es un software que incluye todas las
herramientas necesarias para el desarrollo de varias consolas, incluída Nintendo DS [7].
Libnds es la librería de desarrollo estándar para NDS que permite programar en C/C++ a un
nivel de abstracción algo bajo ya que requiere conocer los registros de la consola. Por tal
motivo existe una segunda opción, la librería PAlib, que está implementada sobre la anterior y
que facilita muchas tareas, por lo que se recomienda para comenzar. Con estas herramientas
se pueden crear archivos ejecutables para la consola. Además existen emuladores que
funcionan en PC, que permiten probar estos ejecutables sin tener que cargarlos en la consola.
Ejemplos de estos emuladores son: DSemu [8], Daulis [9], Ideas [10].
1.2 Objetivos
El objetivo de este proyecto será desarrollar un videojuego de tipo aventura en 3D para la
videoconsola Nintendo DS haciendo uso de las librerías libnds y PAlib. El lenguaje utilizado
será C++ y para la programación gráfica 3D se hará uso de una versión limitada de OpenGL
implementada en la videoconsola.
La videoaventura estará compuesta por dos niveles por los cuales habrá que manejar al
protagonista para que recoja e interactúe con objetos de los escenarios para poder ir
avanzando. Para manejar al personaje, el jugador usará los botones de la consola y un puntero
Fernando Garcia Bernal
2
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Introducción
para la pantalla táctil. Cada nivel tendrá un escenario fijo del que será visible sólo una parte,
según la posición del personaje. Será una imagen que mostrará la proyección plana del
escenario tridimensional. El personaje del juego y los objetos con los que interactúan serán
modelados tridimensionalmente y se moverán dentro del escenario. El estilo visual de la
videoaventura estará inspirado en aventuras clásicas como Alone in de Dark [11] o Grim
Fandago [12].
Se hará uso de una metodología que abarque los diferentes campos para el desarrollo del
videojuego. Elaboración de un guión que refleje los diferentes estados en los que estarán la
aplicación y el personaje principal. Diseño de la estructura lógica de los niveles y definición
de los mismos en base al guión. Diseño y creación de personajes, objetos y escenarios.
Modelado y texturizado de los elementos en 3D y animación del personaje. Obtener música y
efectos sonoros. Estructura de la aplicación orientada a objetos. Implementación de técnicas
de colisión de objetos.
1.3 Contenidos de la memoria
La presente memoria se divide en los siguientes capítulos:
•
Capítulo 2. Videoconsola Nintendo DS:
Se explican los detalles técnicos que pueden ser de utilidad sobre la videoconsola sobre la que
se va a desarrollar la aplicación objetivo de este proyecto. Dentro del capítulo se abarca los
aspectos relacionados con el hardware y las librerías que existen para desarrollar aplicaciones.
•
Capítulo 3. Librería PAlib:
Este apartado se centra en el desarrollo de aplicaciones para Nintendo DS usando una de las
librerías más comunes. A lo largo del capítulo se explica cómo configurar el entorno
dejándolo listo para el desarrollo, las secciones más importantes de la librería y, finalmente,
se dedica al desarrollo de aplicaciones gráficas 3D.
•
Capítulo 4. Fases de Análisis del Juego:
A partir de este capítulo, comienza a centrarse en la aplicación objetivo de esta memoria,
empezando por la fase de análisis de requisitos que debe tener el juego. Se divide en la
Fernando Garcia Bernal
3
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Introducción
definición de la idea, el guión de la aventura, los conceptos que formarán parte del juego y
finalmente los casos de uso de los elementos que lo componen.
•
Capítulo 5. Fase de Diseño:
Tras analizar el proyecto en el capítulo anterior, en este se pasa a diseñar los elementos que
formarán parte de juego, como son el personaje protagonista y los objetos con los que
interactúa, la interfaz gráfica del usuario, y las clases que componen la aplicación.
•
Capítulo 6. Implementación:
Se trata del capítulo con mayor contenido de la memoria, que explica todo el desarrollo de la
aplicación basándose en los dos capítulos anteriores de análisis y diseño. Primero se
implementan los recursos gráficos como imágenes 2D y 3D, después el núcleo de la
aplicación y por último de qué manera se puede utilizar esta estructura para crear la aventura
que hemos definido.
•
Capítulo 7. Conclusiones:
Por último, un capítulo de conclusiones donde se expresan las impresiones recopiladas a lo
largo de la elaboración del proyecto. Esto incluye inconvenientes que han podido ir surgiendo
o simplemente mejoras u opiniones de la aplicación, y un segundo apartado dedicado a
posibles desarrollos que puedan mejorarla.
Fernando Garcia Bernal
4
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Videoconsola Nintendo DS
Capítulo 2. Videoconsola Nintendo DS
La videoconsola Nintendo DS es una consola portátil, desarrollada por Nintendo cuya primera
aparición fue en 2004 [7]. La principal característica que la distingue del resto es que tiene
dos pantallas LCD, siendo además la inferior táctil (figura 1).
1 - Foto de la consola NintendoDS mostrando
sus dos pantallas y sus botones.
Los controles habituales como el D-pad (mando de direcciones), los botones de acción,
Power, Start y Select; están situados en la parte inferior de la consola a ambos lados de la
pantalla. Tiene además dos botones colocados en las esquinas superiores de la parte inferior.
En la zona superior se encuentra la segunda pantalla, la cual no es sensible al tacto, y los
altavoces estéreo. En la parte izquierda de la pantalla inferior se encuentra un micrófono.
Fernando Garcia Bernal
5
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Videoconsola Nintendo DS
2.1 Hardware
Algunas características hardware de la videoconsola NDS que pueden resultar necesarias
conocer para desarrollar aplicaciones son:
•
La resolución de las pantallas LCD es de 256 x 192 píxeles cada una.
•
Dispone de dos procesadores ARM: un ARM9 principal, y un co-procesador ARM7; de
67MHz y 33 MHz, respectivamente, con 4 MB de memoria principal. El ARM7 se
encarga de manejar la salida de audio y la pantalla táctil mientras que el procesador
principal maneja los gráficos y el resto de procesamiento incluyendo el cómputo de 3D.
•
El sistema hardware 3D permite transformaciones y luces, transformaciones de
coordenadas de textura, mapeado de texturas, transparencias alfa, anti-aliasing, cel
shading y z-buffering. El sistema es capaz teóricamente de manejar 120000 triángulos por
segundo, a 60 frames por segundo (FPS). Además del límite de 2048 triángulos por frame
a 60 FPS. El sistema está diseñado para renderizar sobre una pantalla la escena 3D,
aunque existen algunas posibilidades hacerlo sobre ambas pantallas, esto provocaría una
pérdida de rendimiento significativa.
•
Permite comunicaciones inalámbricas Wi-Fi con un punto de acceso estándar, o bien con
otra consola Nintendo DS. Al conectarse a Internet, se puede acceder a la red Nintendo
Wi-Fi para poder competir con otros usuarios.
•
Existen dos ranuras en la parte inferior, para introducir los juegos: la más pequeña está
situada arriba y se utiliza para poder jugar a los videojuegos de la propia consola Nintendo
DS, la más grande que se encuentra abajo sirve para introducir juegos de la videoconsola
portátil antecesora de Nintendo, Game Boy Advance, permitiendo retrocompatibilidad y
seguir disfrutando de juegos antiguos en la nueva videoconsola.
2.1.1 Hardware adicional para cargar aplicaciones
El objetivo del proyecto es desarrollar aplicaciones para la videoconsola Nintendo DS. Pero
esto requiere o bien de un emulador para probar los códigos compilados, o bien probar la
aplicación en el hardware destino final, la propia videoconsola. Esto último presenta un
problema y es que para ejecutar los juegos oficiales basta con introducir el cartucho de juego
original en su ranura correspondiente, pero para las aplicaciones que desarrollemos esto no es
Fernando Garcia Bernal
6
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Videoconsola Nintendo DS
posible. Se necesitará disponer de un hardware que emule a los cartuchos oficiales, con una
memoria de almacenamiento que podamos gestionar para poder cargar o borrar nuestras
aplicaciones. La técnica empleada ha ido evolucionando y perfeccionándose con el tiempo. Al
principio se hacía uso de la ranura de juegos para Game Boy Advance para cargar las
aplicaciones homebrew por el simple motivo de que ya estaba implantado un sistema de carga
de aplicaciones de este tipo para dicha consola predecesora, por tanto se aprovechaba el
sistema de retrocompatibilidad de la consola reutilizando el hardware ya desarrollado. Por
tanto, con este sistema, es necesario un cartucho similar al de Game Boy Advance que
disponga de un sistema de memoria, bien podía estar incluída en el propio cartucho o podía
tener un ranura para introducir una tarjeta de memoria Flash SD (figura 2):
2 - Foto de cartucho de Game Boy Advance al que se le puede
introducir una tarjeta MiniSD con aplicaciones homebrew.
Sin embargo, al ser esta una tecnología reutilizada, no funcionaba por sí sola ya que por la
ranura de juegos de Game Boy Advance sólo se pueden ejecutar juegos de Game Boy
Advance, por tanto si la aplicación homebrew era para Nintendo DS hacía falta alguna manera
de hacérselo saber a la consola. Para lograr esto existen varias técnicas, por ejemplo una de
las primeras fue flashear, lo que significaba cambiar el firmware de esta con el riesgo que esto
suponía si había algún problema durante este proceso. Para evitar tener que modificar el
software interno de la consola se crearon las herramientas de inicio (booting tools) que se
situaban en la ranura de juegos para Nintendo DS, haciendo que la cuando la consola vaya a
cargar la aplicación y pretenda hacerlo en la ranura de Nintendo DS, la herramienta de inicio
indique que la información a cargar está en la ranura de Game Boy Advance. Por tanto, con
esta solución hardware no es necesario modificar el firmware, haciendo falta sin embargo un
segundo cartucho similar a los de Nintendo DS (figura 3):
Fernando Garcia Bernal
7
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Videoconsola Nintendo DS
3 - Foto de cartucho de Nintendo DS para poder cargar
aplicaciones desde la ranura de Game Boy Advance.
Otra solución aparecida con posterioridad, consiste en utilizar tan sólo un dispositivo
hardware para la carga de las aplicaciones. Se trata de un cartucho similar al de los juegos de
Nintendo DS que dispone de un sistema de almacenamiento, bien interno o con la posibilidad
de insertarle una tarjeta flash. De este modo no hace falta ningún sistema adicional para que la
consola cargue las aplicaciones, ya que estás se encuentran en la ranura correspondiente en
lugar de la ranura disponible para los juegos de Game Boy Advance como ocurría antes. Al
hacer falta tan sólo un único dispositivo, este es el sistema que actualmente más se utiliza para
la carga de homebrew (figura 4):
4 - Foto de cartucho de Nintendo DS que permite la grabación directa
de aplicaciones en la tarjeta de memoria microSD que se le incorpora.
2.2 Desarrollo en Nintendo DS
En este capítulo se pretende dar una idea general de la situación actual del desarrollo para la
videoconsola Nintendo DS.
Fernando Garcia Bernal
8
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Videoconsola Nintendo DS
Lo primero que hay que tener en cuenta es la gran velocidad de evolución que presenta el
desarrollo expuesto a la comunidad de Internet. Es algo que se puede apreciar en el punto
anterior enfocado al hardware donde en muy poco tiempo se ha mejorado mucho la tecnología
coexistiendo en el mercado los productos más recientes junto con los primeros que
aparecieron, debido a que estos últimos no han sido absorbidos todavía por la demanda. Lo
mismo ocurre con las soluciones software ofrecidas para el desarrollo, existen varias librerías,
entornos de desarrollo, distintos lenguajes de programación disponibles y gran cantidad de
homebrew que da la impresión de mal organizado y caótico. Todo esto antes no ha existido ya
que se apoya en el uso de Internet donde grupos de usuarios se aglutinan en comunidades
temáticas y pueden fácilmente compartir sus librerías o aplicaciones para que otros las usen,
mejoren o simplemente les den más publicidad. De tal manera que se debe ver como una
ventaja y no como algo agobiante ya que por cada herramienta software disponible, está
detrás una comunidad de usuarios con foros de ayuda donde podrán resolver las dudas si es
que no están resueltas ya.
2.2.1 Libnds
Libnds [4], o también conocida en su versión anterior como ndslib, fue creada y mantenida
por Michael Noland (de apodo Joat), y Jason Rogers (Dovoto), además de haber contribuido
mucha gente en su mejora. Esta librería comenzó a ser la primera alternativa al SDK oficial de
Nintendo para DS. Permite crear aplicaciones que pueden ser cargadas por los elementos
hardware propios de la videoconsola Nintendo DS, explicados anteriormente. Con esta
librería es posible programar todas las características de la videoconsola como son: pantalla
táctil, micrófono, hardware 3D, hardware 2D y protocolo wifi. Esta librería contiene
información sobre los registros, abstrayendo la información de las direcciones de memoria.
Esto supone las mismas ventajas e inconvenientes que cualquier otra herramienta de
desarrollo que tienen bajo nivel de abstracción. El programador tiene mucha responsabilidad
al trabajar con los registros lo que supone tener un gran conocimiento de la plataforma,
pudiendo ser esto una ventaja porque otorga mucha flexibilidad y poder exprimir al máximo
la funcionalidad de la videoconsola.
La librería libnds se divide en varios archivos que abarcan las diferentes funciones y registros
para interactuar con la consola. La parte que maneja la visualización 2D tiene por ejemplo
Fernando Garcia Bernal
9
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Videoconsola Nintendo DS
funciones específicas para trabajar con la VRAM (memoria de video) donde se situarán las
paletas, texturas, sprites… también incluye una apartado 3D que ofrece una versión reducida
de la famosa librería openGL. Para la programación gráfica es necesario indicar el modo de
video antes de poder representar nada, cada modo tiene características diferentes como por
ejemplo el número y tipo de fondos, permitir rotaciones de los gráficos, otro servirá para
representación en 3D, etc.
Con esta librería se generan dos códigos principales: arm9.cpp y arm7.cpp. Cada uno de
los cuales es manejado por el procesador correspondiente, realizando las tareas específicas de
cada uno teniendo en cuenta los usos anteriormente descritos.
Otra parte importante para la programación haciendo uso de esta librería son las
interrupciones. Están vinculadas a los eventos del teclado para la comunicación con el
usuario, o con el pintado de buffer sobre la pantalla para manejar temporizaciones o para
controlar los frames por segundo de la aplicación, por ejemplo. Cuando sucede la interrupción
se guarda en un registro de peticiones de interrupción siendo tarea del programador atenderlas
o no.
Con esta aproximación se muestra el nivel de abstracción que usa esta librería y dependerá de
cada proyecto si puede venir mejor o peor usarla. De este modo la siguiente librería que se va
a explicar se convierte en la alternativa perfecta ya que está basada en libnds, pero presenta
multitud de funciones de alto nivel para simplificar la programación sin tener que estar
trabajando con registros, ni memorias de vídeo ni interrupciones.
2.2.2 PAlib
PAlib [5], se trata de una librería open source basada en libnds. Está compuesta por muchas
funciones que facilitan en gran medida el desarrollo de aplicaciones para la videoconsola
Nintendo DS. Al contrario que ocurría con la librería libnds que hacía falta conocer los
registros que tiene, con esta librería no se trata con el hardware directamente ya que dispone
de funciones de más alto nivel de abstracción que realizan funciones más complejas que a su
vez se comunican con la librería libnds. Esto ha hecho que mucha gente que comienza a hacer
aplicaciones para esta videoconsola opte por la librería PAlib. El apodo del creador de esta
Fernando Garcia Bernal
10
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Videoconsola Nintendo DS
librería es Mollusk y mantiene en la página web [5] gran cantidad de información como por
ejemplo: tutoriales en varios idiomas que abarcan la gran mayoría de las cosas que se pueden
hacer con la librería mediante ejemplos sencillos, documentación de las funciones
implementadas en la librería, un foro donde participan desarrolladores de todas partes de la
red y que pueden ayudar a resolver cualquier duda, índice con las aplicaciones que va creando
la comunidad e incluso para algunas de las cuales facilitan también su código, recursos
multimedia de distribución libre que otros desarrolladores pueden usar para sus proyectos...
Actualmente el creador mantiene una API [13] que divide a la librería en treinta módulos
diferenciados que van ampliándose según se incorporan nuevas funcionalidades. Por ejemplo,
hace poco la videoconsola ha adquirido la capacidad detectar movimientos o vibraciones
gracias a un cartucho que posee un sensor de movimiento y ya ha sido incluido en la librería
el módulo “DS Motion Commands” explicando las funciones que hacen falta para poder
manejar esta característica. La categoría de tutoriales es muy recomendable para ir
conociendo toda la funcionalidad que ofrece esta librería, aunque no está totalmente completa
pese a que trae mucha información. Esta organizada de momento en más de quince capítulos
que comienzan con la instalación de las librerías y un repaso a C, cubren la parte de
programación gráfica, sonido, programación de eventos, sistema de archivos, … y también
tratan conceptos más complejos como la programación en redes inalámbricas. En el siguiente
capítulo se estudiará esta librería para después plantear el análisis del juego final y finalmente
llevarlo a su desarrollo.
Fernando Garcia Bernal
11
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Fernando Garcia Bernal
Videoconsola Nintendo DS
12
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
Capítulo 3. Librería PAlib
3.1 Configuración del Entorno de Desarrollo y Emuladores
Antes de comenzar a escribir programas con la ayuda de esta librería es necesario tener
instalado y configurado una serie de herramientas.
La primera utilidad a instalar es devkitpro [6] que se define como un conjunto de
herramientas para desarrollar videojuegos homebrew. Actualmente da cobertura a las
siguientes plataformas: PlayStation Portable (PSP), Game Cube, Game Boy Advance, GP32,
y por supuesto Nintendo DS. Una vez descargado el fichero de instalación ejecutable desde su
página web, se instala teniendo en cuenta que tan sólo es necesario las librerías que hacen
referencia a Nintendo DS siendo éstas las que están bajo el menú devkitARM. En el momento
de escribir esto, la última versión disponible de devkitpro es la 1.4.4 la cual incorpora el
devkitARM release 20.
Para poder usar aplicaciones incluidas con PAlib como son PAGfx (conversor de gráficos),
PAFS (sistema de ficheros) o VHam (Ide de desarrollo); es necesario tener instalado .Net
Framework [14].
Fernando Garcia Bernal
13
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
Hecho esto ya se puede instalar la librería PAlib a través de su instalador que se puede
descargar en su página web [5]. Su creador recomienda instalarla en la misma carpeta que
devkitpro para que todo funcione correctamente. También indica que se instalen devkitpro y
PAlib en rutas que no contengan espacios. Finalmente reiniciar el ordenador.
Para comprobar que se ha instalado correctamente, vamos a intentar compilar un código de
ejemplo. Dentro de PAlibExamples\Text\Normal\HelloWorld arrancar el archivo
por lotes build.bat si todo ha ido correcto se generan al menos tres archivos: HelloWorld.nds,
HelloWorld.sc.nds y HelloWorld.ds.gba. Si ha sucedido algún error lo más probable esque
haya que configurar las rutas. Considerando que el error es parecido a este: “make main.c
arm-eabi-gcc.exe:
CreatProcess:
No
such
file
or
directory
make[1]: * [main.o] Error 1 make: * [build] Error 2” entonces hay
que indicar al sistema operativo dentro del path donde se encuentran los binarios que hacen
falta para realizar la compilación. Para ello hay que aceder a Propiedades Avanzadas
de Mi PC -> Variables de entorno ->Panel de Control y en las variables
del sistema, modificar la variable Path (o crearla si no existe) para que incluya los siguientes
directorios:
“C:
\devkitPro\
devkitARM\
bin;
C:\
devkitPro
\devkitARM \arm-eabi \bin; C:\ devkitPro \devkitARM \libexec
\gcc \arm-eabi \4.1.1”. Suponiendo que devkitpro está instalado en el directorio
raíz C:\devkitPro.
Si ya se han conseguido generar los ficheros .nds, .sc.nds y .ds.gba es hora de
arrancarlos. La opción más cómoda para el desarrollador es usar un emulador. Existen varios
funcionales, entre los mejores está No$GBA [15] que tiene una versión comercial y otra
gratuita pero con restricciones, además de otros como Dualis [9], iDeas [10], DSemu [8].
Algunos vienen incluidos con la instalación de devkitPro. Hay que decir que ningún emulador
funciona al 100%. Aunque no he llegado a utilizar el emulador No$GBA, hablan muy bien de
su rendimiento. Sin embargo, he comprobado que en algunos emuladores funcionan mejor
unas características mientras que otras no tienen un buen resultado, por tanto lo mejor es tener
instalado al menos dos emuladores distintos y por supuesto no fiarse de los resultados que
estos puedan ofrecer teniendo que testear las aplicaciones en el hardware de la videoconsola
NDS cuando sea posible para cerciorarse de que funcione correctamente. Si sirve de
referencia el emulador que he usado como principal es iDeaS Emulator [10], aunque esto no
Fernando Garcia Bernal
14
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
sirva de mucho ya que es algo en continua evolución como se ha comentado a lo largo de esta
documentación y en cualquier momento pueden aparecer otros emuladores que funcionen
perfectamente.
Ya se ha explicado anteriormente los distintos métodos y hardware que son necesarios para
cargar aplicaciones homebrew en la NDS, ahora basta con mencionar cual de los archivos
generados durante la compilación es el que hay que descargar, ya sea en la memoria interna o
en la tarjeta flash. Hablamos antes de tres ficheros creados al ejecutar el archivo por lotes
build.bat: HelloWorld.nds, HelloWorld.sc.nds y HelloWorld.ds.gba.
Todos son la misma aplicación compilada y sólo hará falta uno de ellos. El archivo con la
extención .ds.gba será el necesario si nuestro método para cargar aplicaciones es
introduciendo un cartucho de almacenamiento en el slot de Game Boy Advance (recordar que
si esto fuera así haría falta además un sistema para insertar en el slot de DS para que la
consola cargue las aplicaciones de DS en un slot distinto al que le corresponde). Sin embargo,
si se dispone de un sistema de carga de aplicaciones más actual, es decir de los que sólo hace
falta un dispositivo de almacenamiento en la ranura para DS, entonces el archivo que hace
falta es el que tiene la extensión .nds. Para métodos de la marca supercard, G6 o M3 se usa
el fichero con extención .sc.nds.
Otra parte importante es la edición del código. Existen distintos editores con los que se puede
programar para DS. En principio bastaría con un editor de texto simple como el bloc de notas
y luego utilizar alguna herramienta de generación de código como Make que relacione los
fuentes con el compilador, pero es preferible usar un editor inteligente que tenga utilidades
como coloreo de código, sugerencia de nombres reservados, vinculación con Make, que
pueda ejecutarse el código directamente desde el entorno sobre un emulador, o incluso que
con la ayuda de un script se envíe el compilado a la DS… Junto con devkitpro se incluyen
varios editores que ofrecen algunas de estas posibilidades. Quizás el más simple sea
Programmers Notepad 2 [16] que colorea el código según los estándares de C/C++ y es
posible compilar sin salir del entorno. El otro editor incluido es Visual HAM [18] que además
de colorear código y compilar, da la posibilidad de ejecutarlo en alguno de los emuladores
que estén instalados y configurados con el entorno. Otra opción más potente es usar Microsoft
Visual Studio C++ [18] ya sea en su versión completa o versión express. Este editor no viene
incluido con devkitpro y habrá que obtenerlo aparte. La versión express, Microsoft Visual
Fernando Garcia Bernal
15
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
C++ Express Edition, es gratuita y se puede descargar desde la página de Microsoft. Para
configurarlo de manera que pueda usarse toda la potencia de esta herramienta con la librería
PAlib se adjunta un enlace [19] que indica los pasos a seguir. Una vez configurado
correctamente aparecerá un nuevo tipo de proyecto NintendoDS, Palib Application (figura 5):
5 - PAlib configurado en Visual Studio.
Ahora el programador tendrá todas las facilidades de este editor como por ejemplo hacer uso
de la herramienta IntelliSense, o crear un script para que el compilado se envíe al dispositivo
de almacenamiento de DS.
Existe alguna utilidad interesante como por ejemplo instalar un servidor ftp en la
videoconsola al que poder conectarse con un cliente ftp vía wifi para enviar los ficheros
compilados sin tener que sacar de la DS el cartucho y la tarjeta de memoria. Esta cómoda
utilidad se llama DSFTP [20] y en su página web se puede encontrar una guía para hacerla
funcionar.
Fernando Garcia Bernal
16
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
Lo siguiente es desarrollar aplicaciones que abarquen los aspectos más importantes de la
librería para aprender a utilizarla.
3.2 Programación en Nintendo DS Usando la Librería PAlib
3.2.1 Plantilla PAlib
Palib incluye una carpeta llamada PAlibTemplate que lleva dentro la estructura más básica
pero completamente funcional de una aplicación para DS. Esta plantilla se compone de los
siguientes ficheros:
•
makefile: fichero que compila el proyecto. No es necesario modificarlo aunque tiene
variables internas por si hace falta.
•
clean.bat: limpia el directorio de los archivos generados por posibles compilaciones
anteriores.
•
make.bat: primero realiza la acción clean y luego hace una compilación del proyecto.
•
logo.bmp y logo_wifi.bmp: se trata de dos imágenes que representarán los iconos
de la aplicación cuando se vaya a transferir el archivo compilado vía wifi. El primero debe
tener 32x32 pixeles de tamaño y una paleta de 16 colores, mientras que el segundo es de
104x16 colores y sólo puede tener dos colores: blanco y negro.
•
Template.pnproj: es el fichero de proyecto para el editor Programmers Notepad 2, si
se abriera este fichero con dicho programa, se abriría con la estructura de ficheros del
proyecto.
•
Project.vhw: Igual que Template.pnproj pero para el editor Virtual Ham.
•
Carpeta source: contiene el fichero main.c que será ejecutado como el inicio de la
aplicación. En él viene un código que carga e inicializa la librería y que comentaremos
más adelante.
•
Carpeta include: lugar donde se sitúan los ficheros de cabecera .h de C++.
•
Carpeta data: en esta carpeta se incluyen los recursos necesarios por la aplicación, como
pueden ser imágenes, archivos de audio, modelos 3D o ficheros binarios en general.
Fernando Garcia Bernal
17
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
3.2.2 Primera aplicación
Una vez vista la plantilla, comenzaremos explicando su código para incluir más adelante
alguna cosa más y terminar compilándolo y ejecutándolo. Este es el código de main.c:
// Includes
#include <PA9.h>
// Include para PA_Lib
// Función principal
int main(int argc, char ** argv)
{
PA_Init();
// Inicializa PA_Lib
PA_InitVBL(); // Inicializa un estándard VBL
// Bucle infinito para mantener el programa en ejecución
while (1)
{
PA_WaitForVBL();
}
return 0;
} // Fin main()
La primera línea es una directiva include y aparecerá siempre que vayamos a usar la librería
PAlib, PA9.h. Ya dentro del main llama a dos funciones de inicialización de la librería (se
ditinguen porque comienzan por PA_). La primera, PA_Init() es una inicialización general
y si no se pusiera no funcionaría correctamente PAlib. La siguiente línea es PA_InitVBL()
que hace referencia al VBL (Vertical Blank Line) que es el tiempo que tarda la pantalla en
refrescarse. Al inicializarlo permite al programa sincronizarlo con la pantalla, a 60 fps (frames
por segundo). Si no se hiciera esto la aplicación iría más rápido pudiendo incluso dejar de
funcionar. Por tanto, estas dos líneas serán siempre fundamentales junto con el primer
include.
Lo siguiente que aparece en el código es un bucle infinito que se corresponde con el ciclo de
ejecución de la aplicación. Hasta este punto la aplicación debe haberse encargado de
inicializar todos los datos y objetos necesarios, para que después se pueda acceder a este bucle
que se encargará básicamente de analizar los eventos que sucedan para actualizar la
información que se tenga que mostrar por la pantalla. En este caso dentro del bucle sólo existe
una
línea
que
hace
Fernando Garcia Bernal
referencia
a
algo
comentado
anteriormente.
Se
trata
de
18
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
PA_WaitForVBL(), esta es la línea que hace sincronizar la aplicación a 60 fps, situándola
al final del bucle se asegura que este se repetirá con la periodicidad indicada. Por último se
realiza una llamada a return 0 para acabar correctamente con la aplicación, aunque
recordemos que previamente existe un bucle infinito por tanto nunca se llegará hasta aquí a no
ser que se salga explícitamente del bucle, con un break por ejemplo, lo cual no es nada
deseable.
Si ejecutáramos esta aplicación no aparecería nada, sólo una pantalla en negro. Esto es
correcto puesto que no se ha hecho uso de ninguna función que imprima por pantalla. Esta
será la próxima tarea.
3.2.3 Textos
En la documentación de la librería existe un módulo que hace referencia a mostrar textos por
pantalla, este es Text Output System. Aquí aparecen todas las funciones necesarias para esta
tarea. Un ejemplo de uso se puede ver en el siguiente código:
// Includes
#include <PA9.h>
// Include para PA_Lib
// Función: main()
int main(int argc, char ** argv)
{
PA_Init();
// Inicializa PA_Lib
PA_InitVBL(); // Inicializa una estándard VBL
PA_InitText(1, 2);
PA_OutputSimpleText(1, 1, 2, "Hola Mundo DS");
// Bucle infinito para mantener el programa en ejecución
while (1)
{
PA_WaitForVBL();
}
return 0;
} // Fin main()
He aquí una aplicación que inicia la librería correctamente, para poder mostrar un texto
también hace falta iniciar dicho módulo con PA_InitText. Después hay que llamar a la
función PA_OutputSimpleText para que se muestre el texto por la pantalla. Sin
embargo, para usar estas funciones hace falta indicarles una serie de parámetros. La primera
Fernando Garcia Bernal
19
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
función, PA_InitText (u8 screen, u8 bg_select) tiene dos parámetros. El
primero es para indicar en cual pantalla se va a pintar (0 = inferior, 1 = superior), y el segundo
hace referencia al fondo dentro de la pantalla donde se pintará (0-3). De momento basta con
saber que NDS puede mostrar hasta cuatro fondos por cada pantalla, donde cada fondo tiene
su propia paleta de 256 colores. Estos fondos pueden ser formados por tiles o bien de 8 o 16
bits de información. La otra función es PA_OutputSimpleText (u8 screen, u16
x, u16 y, const char *text). El primer parámetro representa lo mismo, la
pantalla. Los dos siguientes son las coordenadas dentro de la pantalla, ‘x’ es la posición
horizontal e ‘y’ es la vertical. La posición (0,0) se corresponde con la esquina superior
izquierda y los valores positivos van hacia la derecha para la ‘x’ y hacia abajo para la ‘y’. La
esquina inferior derecha tiene como coordenada (255,191) que se corresponden con las
dimensiones de la pantalla en píxeles, 256x192. Sin embargo, para hacer referencia a las
posiciones del texto debemos darnos cuenta de que el texto son imágenes cargadas en tiles,
por tanto si un tile está formado por 8x8 píxeles, entonces la pantalla está formada por
256/8x192/8 = 32x24 tiles siendo (31,23) el último tile donde se podrá escribir texto en la
pantalla. El último parámetro es el texto que se quiere mostrar. El código anterior se
corresponde con la aplicación que muestra el texto “Hola Mundo DS”.
De este modo se puede imprimir texto de una manera simple, pero existe otra función muy
importante que hace las veces de printf, esta es PA_OutputText (u8 screen, u16
x, u16 y, char *text, arguments...). Los primeros cuatro parámetros son
iguales pero además al final se pueden incluir una serie de argumentos que irán incluidos
dentro de la cadena text. Dependiendo del tipo que se vaya a mostrar se usarán unos
indicadores u otros:
•
Integer: %d, Ejemplo: PA_OutputText(1,0,0,"valor: %d", 3);
•
Float: PA_OutputText(1,0,0,"Float value : %f3", 4.678);
•
String: PA_OutputText(1,0,0,"Hola %s", "mundo");
Del mismo modo que en los argumentos se pasan valores constantes, se podrían pasar
variables que contengan valores de cada tipo.
Fernando Garcia Bernal
20
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
3.2.4 Entrada
Existen dos tipos de entrada en DS: pad (todos los botones) y stylus (puntero). Además hay
otras entradas, como por ejemplo un teclado implementado por PAlib que se muestra en
pantalla y se puede usar junto con el puntero de la videoconsola; también existe una
característica de reconocimiento de formas usando lo que se conoce en la librería como
PA_Graffiti que permite reconocer figuras predefinidas o configurar nuevas; otro tipo de
entrada de información sería el micrófono.
3.2.4.1 Pad
El estado del pad se almacena en una estructura homónima Pad. Dentro se puede diferenciar
por tres tipos de eventos que definen los estados posibles de los botones: Held, Released y
Newpress. Held es cuando una tecla está pulsada, permanecerá a 1 mientras se encuentre
en esa situación. Released se activará cuando el botón haya dejado de estar pulsado,
permaneciedo a 1 durante un frame. Por último NewPress valdrá 1 durante un frame cuando
se pulse la tecla. Un ejemplo sencillo sería:
if(Pad.Held.Up)
{
MoveUp();
}
if(Pad.Held.Down)
{
MoveDown();
}
Cuyo significado en un hipotético juego podría ser, si está pulsada la dirección arriba,
desplázate arriba, y si está pulsada la dirección abajo, desplázate hacia abajo.
3.2.4.2 Stylus
La lectura del Stylus es similar a la de Pad, existe una variable Stylus que almacena el
estado del puntero y se actualiza cada frame. Se pueden consultar los mismos estados: Held,
Released y NewPress. Además se puede conocer la posición del mismo consultando X e
Y de la variable Stylus. Un ejemplo de código es el siguiente:
Fernando Garcia Bernal
21
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
if (Stylus.Held)
{
PA_SetSpriteXY(screen, sprite, Stylus.X, Stylus.Y);
}
Su significado es: si está el puntero pulsado sobre la pantalla, posiciona una imagen en la
posición donde se encuentra el puntero.
3.2.5 Imágenes
Por un lado hará falta mostrar textos por pantalla, por ejemplo, cuando queramos depurar será
fundamental para conocer los valores de las variables y el estado de la aplicación. Y por otra
parte, junto con los gráficos en tres dimensiones, el resto de cosas que se mostrarán serán
sprites, es decir, imágenes. Antes de comenzar es necesario conocer las características que
trae DS. Es capaz de mostrar 128 imágenes diferentes por cada pantalla, por tanto un total de
256 diferentes. Cada imagen puede ser girada horizontal/verticalmente, desplazada por toda la
pantalla, puede ser animada (actualizando la imagen de una secuencia), puede tener
transparencias o incluso hacerse un mosaico. Las imágenes también pueden ser rotadas o
aumentadas (o disminuidas). Existe una limitación y es que se pueden definir rotsets
(rotaciones/aumentos) y aplicarlos a las imágenes, pero no se pueden definir más de 32 rotsets
diferentes por pantalla. De tal modo se podrán rotar/aumentar las imágenes que sean ya que se
puede aplicar un mismo rotset a diferentes imágenes que se transformarán igual, pero sólo de
32 maneras distintas.
Si a partir de ahora trabajaremos pintando imágenes en pantalla, hará falta indicar en qué
posición de esta se va a mostrar. Por tanto, será necesario conocer las posibles coordenadas de
esta. Como se puede apreciar en la figura 6:
Fernando Garcia Bernal
22
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
6 - Distribución pantalla Nintendo DS.
Como se puede apreciar, las coordenadas posibles en cada pantalla serán de 0 a 255 pixeles en
acho y de 0 a 191 pixeles en alto; siendo (0,0) la posición más arriba a la izquierda y
(255,191) la más abajo a la derecha.
También es necesario conocer los tres diferentes modos de colores que existen en DS para los
sprites:
•
Paleta de 16 colores: muy usada en GBA.
•
Paleta de 256 colores: usa más memoria.
•
Imágenes de 16 bits: Sin paleta, no se suele usar mucho.
Lo más común es usar paleta de 256 colores ya que es el término medio con mejor
rendimiento.
Además las imágenes deben tener unos tamaños específicos, estos son: 8x[8,16,32] (esto
significa: 8x8, 8x16, 8x32), 16x[8,16,32], 32x[8,16,32,64] y 64x[30, 64]. En el caso de que la
Fernando Garcia Bernal
23
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
imagen que se vaya a usar tenga unas proporciones diferentes, habría que encapsularla a la
siguiente superior que sea reconocida.
En muchas ocasiones ocurre que no interesa que la imagen se vea completamente sino que
sólo se muestre un contenido interior y el resto no se vea, mostrándose hasta el perfil de la
figura. Para esto será posible definir que un color en concreto sea transparente.
Todas estas cosas a tener en cuenta sobre imágenes pueden hacer que haya que dedicar
bastante tiempo retocando y ajustando e incluso perder tiempo con errores que se creen que
tienen su origen en el código y a lo mejor el problema era de compatibilidad de proporciones
o de paleta de colores. Por este motivo el creador de la librería PAlib también ha dispuesto de
una herramienta gráfica para transformar imágenes a tipos compatibles. Se trata de PAGfx. A
continuación unos ejemplos de carga y transformaciones de sprites.
3.2.5.1 Mostrando imágenes
El primer ejercicio es uno de los que trae la librería PAlib para el que hará falta una imagen
que
trae
en
una
de
sus
subcarpetas.
El
ejemplo
está
en
PAlibExamples/Sprites/Basics/CreateSprite.
El código del fichero main es:
#include <PA9.h>
// PAGfxConverter Include
#include "gfx/all_gfx.h"
#include "gfx/all_gfx.c"
int main(void){
PA_Init(); //PAlib inicialización
PA_InitVBL();
PA_LoadSpritePal(
0,
0,
(void*)sprite0_Pal);
PA_CreateSprite(
0,
0,
(void*)vaisseau_Sprite,
OBJ_SIZE_32X32,
1,
Fernando Garcia Bernal
// Pantalla
// Número de paleta
// Nombre de paleta
//
//
//
//
//
Pantalla
Número de sprite
Nombre de sprite
Tamaño de sprite
Modo de color 256
24
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
0,
50, 50);
Librería PAlib
// Número de paleta de sprite
// Posiciones X e Y en pantalla
while(1) // Bucle infinito
{
PA_WaitForVBL();
}
return 0;
}
Las dos líneas de include, que no son para incluir la librería PAlib, tratan de cargar la imagen
y la librería previamente convertidas a código C con la herramienta antes comentada PAGfx.
Después de la inicialización se llama a una nueva función PA_LoadSpritePal que carga
la paleta de la imagen y su uso es el siguiente: PA_LoadSpritePal (u8 screen, u8
palette_number, void *palette). En el caso del código escrito lo que hace es
cargar una paleta en la pantalla 0, en la primera paleta (la número 0) y la paleta cargar está en
la dirección sprite0_Pal. La otra función que hace posible mostrar la imagen es
PA_CreateSprite (u8 screen, u8 obj_number, void *obj_data, u8
obj_shape, u8 obj_size, u8 color_mode, u8 palette, s16 x, s16
y). Lo que hace esta función en el ejemplo es lo siguiente: crea un sprite en la pantalla 0 (la
pantalla inferior donde ya está cargada la paleta, si no existiera esta correspondencia no se
mostraría la imagen ya que cada pantalla maneja paletas diferentes), el segundo parámetro
indica que el sprite se asociará con el índice 0 (de los 128 posibles, 0-127) el cual lo
representará para posibles futuras transformaciones del mismo, además de representar el nivel
de profundidad al que se encuentra. Esto es que cuanto más pequeño es el valor del sprite, se
encuentra por delante de los demás. El tercer parámetro es el puntero hacia la imagen
previamente convertida, después se le indica el tamaño de la imagen (lo mejor es usar la
macro como en el ejemplo que engloba los dos parámetros reales que hay que pasarle), a
continuación se indica el modo de color siendo 0 para 16 colores o 1 para 256 (normalmente
será 1), después el número de paleta donde almacenamos la paleta correspondiente a esta
imagen (en este caso 0), para terminar se indica la coordenada donde se representará la
imagen.
Con esto se muestra la imagen por pantalla, lo próximo será desplazarla por ella atendiendo a
eventos del usuario.
Fernando Garcia Bernal
25
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
3.2.5.2 Desplazando imágenes
PA_MoveSprite
El siguiente código seguirá sin tener demasiada complejidad pero se trata de una tarea
fundamental para poder realizar aplicaciones interactivas como es el caso de los videojuegos o
aplicaciones gráficas.
// Los includes
#include <PA9.h>
// PAGfx Include
#include "gfx/all_gfx.c"
#include "gfx/all_gfx.h"
//Función principal
int main(void)
{
//Inicialización de PAlib
PA_Init();
PA_InitVBL();
// Carga la paleta de imágenes
PA_LoadSpritePal(
0,
// Pantalla
0,
// Número de paleta
(void*)sprite0_Pal);
// Nombre de paleta
// Carga de 16 sprites
u8 i = 0;
for (i = 0; i < 16; i++)
PA_CreateSprite( 0, i,(void*)vaisseau_Sprite, OBJ_SIZE_32X32,1,
0, i << 4, i << 3);
while(1)
{
// Usa la función PA_MoveSpite sobre todos los sprites
for (i = 0; i < 16; i++) PA_MoveSprite(i);
// La función PA_MoveSprite comprueba si está
// pulsado un sprite con el puntero, y se mueve con él.
PA_WaitForVBL();
}
return 0;
}
El comienzo de este programa es muy parecido del anterior. Se inicializa PAlib y se carga la
paleta de imágenes. Y ahora en lugar de crear un sprite se crean 16 haciendo uso de la misma
imagen pero en posiciones diferentes.
Fernando Garcia Bernal
26
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
El operador >> se utiliza en C para desplazar la cantidad de la izquierda el número de bits que
se le indique en el operando derecho, lo que se corresponde con dividir entre potencias de dos
la cantidad indicada. Si en cambio se usa el operador << la operación que se realiza es
introducir un bit por la derecha, por tanto se multiplica la cantidad por potencias de dos. En
este código en cada iteración se multiplica i por 24, 16, para obtener la coordenada x y se
multiplica y por 23, 8, para la coordenada en y. Como i va desde 0 a 15 lo que resulta es una
línea diagonal descendente desde el eje de coordenadas en (0,0) (hay que recordar que esto se
corresponde con la posición arriba a la izquierda) hasta la coordenada (15*16,15*8),
(240,120).
Como nombre de referencia de cada sprite se le da el de la variable i, que como ya se ha dicho
toma valores de 0 a 15, siendo este el que se usará para hacer mover cada sprite junto con el
puntero. Se trata de una tarea sencilla haciendo uso de la función PA_MoveSprite
(idsprite). Llamando a esta función e indicándole el id del sprite, este se desplazará si el
puntero pasa por encima y se desvinculará cuando este se despegue.
PA_SetSpriteXY
Con esta función habrá más libertad para desplazar los sprites a la coordenada que más
convenga, en lugar de cómo sucedía con la función PA_MoveSprite que siempre se
desplazaba al lugar donde estuviera el puntero. La función a utilizar será la siguiente
PA_SetSpriteXY (u8 screen, u8 sprite, s16 x, s16 y). Es necesario
indicarle la pantalla donde se encuentra el sprite, el identificador de este y la coordenada x e
y a donde se quiera mover. Hay que tener en cuenta que la posición del sprite se corresponde
con la esquina superior izquierda de este, no con su centro. Para recuperar las coordenadas de
un
sprite
se
usan
las
macros
PA_GetSpriteX(screen,
obj) y
PA_SetSpriteY(screen, obj, y).
Como ejemplo de uso, con este código el sprite se desplazará en función de la pulsación del
cursor:
//Mover un sprite usando los cursores
#include <PA9.h>
// PAGfx Include
Fernando Garcia Bernal
27
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
#include "gfx/all_gfx.c"
#include "gfx/all_gfx.h"
s32 x = 0; s32 y = 0; // posición del sprite
//Función main
int main(void){
PA_Init(); //Inicialización de PAlib
PA_InitVBL();
PA_InitText(0,0);
PA_LoadSpritePal( 0,
0,
(void*)sprite0_Pal);
// Pantalla
// Número de paleta
// Nombre de paleta
//Creación del sprite
PA_CreateSprite(
0, 0,(void*)vaisseau_Sprite, OBJ_SIZE_32X32,1, 0, 0, 0);
while(1){ // Bucle principal
// Actualiza la posición de acuerdo con la pulsación
// del cursor
x += Pad.Held.Right - Pad.Held.Left;
y += Pad.Held.Down - Pad.Held.Up;
// Establece la posición del sprite
PA_SetSpriteXY(
0, // pantalla
0, // sprite
x, // posición x
y); // posición y
PA_WaitForVBL();
}
return 0;
}
Dentro del bucle principal se actualizan dos variables x e y. La línea x
+=
Pad.Held.Right - Pad.Held.Left; incrementará la variable x en 1 si se ha
pulsado hacia la derecha o se decrementará si se ha pulsado izquierda. En el caso de que
ambos lados estuviesen pulsados la resta devolvería cero y se mantendría el valor de x. Lo
mismo ocurre con la variable y pero atendiendo a las pulsaciones de arriba y abajo. Una vez
que se conoce la posición x e y a la que debe desplazarse el sprite, se llama a la función
PA_SetSpriteXY para moverlo.
Si, por ejemplo, se quisiera desplazar la imagen al lugar donde se pulse con el puntero, habría
que
cambiar
la
línea
Fernando Garcia Bernal
PA_SetSpriteXY(0,
0,
x,
y);
por
esta
28
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
PA_SetSpriteXY(0,
0,
Stylus.X,
Librería PAlib
Stylus.Y);. Ya que las variables
Stylus.X y Stylus.Y almacenan la coordenada donde se encuentra pulsado el puntero.
Lo siguiente será ver cómo se pueden aplicar transformaciones a las imágenes como por
ejemplo rotaciones y zoom.
3.2.5.3 Rotaciones y zoom
Como se dijo anteriormente, los sprites pueden ser rotados y/o escalados pero existe una
pequeña limitación: todos los sprites pueden ser rotados sin embargo un sprite rotado necesita
que se le aplique una rotación previamente definida llamada rotset y se pueden establecer
hasta 32 rotsets diferentes por pantalla pudiendo aplicar un mismo rotset a diferentes sprites
que presentarán la misma deformación. A continuación tres ejemplos: rotación, escalado y
rotación/escalado.
Rotación
Este es el código del ejemplo:
// Activa rotación de sprite y lo hace girar
#include <PA9.h>
// PAGfxConverter Include
#include "gfx/all_gfx.c"
#include "gfx/all_gfx.h"
int main(void){
PA_Init();
PA_InitVBL();
// Carga la paleta
PA_LoadSpritePal(
0,
0,
(void*)sprite0_Pal);
// Pantalla
// Número de paleta
// Nombre de paleta
// Carga el sprite
PA_CreateSprite(
0, 0,(void*)vaisseau_Sprite, OBJ_SIZE_32X32,1, 0, 50, 50);
// Activa las rotaciones para el sprite
PA_SetSpriteRotEnable(
0,// pantalla
0,// Número de sprite
0);// Número de rotset. Hay 32 (0-31) por pantalla
Fernando Garcia Bernal
29
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
u16 angle = 0; // Ángulo de rotación...
while(1)
{
++angle; // modifica el ángulo
// Limita el rango de 0-511.
// Funciona sólo con 1, 3, 7, 15, 31... (2^n - 1)
angle &= 511;
// Función para rotaciones sin zoom
PA_SetRotsetNoZoom(
0,
//pantalla
0,
// rotset
angle);
// ángulo, de 0 a 511
PA_WaitForVBL(); // Sincronización
}
return 0;
}
La primera función nueva que aparece es PA_SetSpriteRotEnable(screen,
sprite, rotset). Con esta función se vincula una sprite existente en una pantalla con un
rotset. A partir de esta llamada el sprite está listo para ser transformado, si se quisiera
deshabilitar
esta
opción
habría
que
usarse
la
función
PA_SetSpriteRotDisable(screen, sprite). Después se calcula el valor del
ángulo de rotación que se le aplicará al sprite. Como se puede ver no se trata de un rango de
ángulos normal de 360 grados sino que la vuelta completa cubre las posiciones que van de 0 a
511. La razón es porque de este modo es mucho más eficiente la Nintendo DS que con los
ángulos normales. También indicar que el giro se produce en sentido contrario a las agujas del
reloj.
El operador & se utiliza para calcular el módulo de una manera mucho más eficiente que con
el operador %. El operador % se utiliza para obtener el módulo o lo que es lo mismo, el resto
de la división. Sin embargo, utilizando el operador &, se realiza la operación a nivel de bit
con la mejora en la velocidad de cálculo que esto supone. Sin embargo, tiene como restricción
que el número que se le pase en el lado derecho debe ser una potencia de dos y además en
lugar de pasarle ese número en concreto, por ejemplo b, se le debe pasar b-1.
Por
último,
se
realiza
la
transformación
haciendo
uso
de
la
función
PA_SetRotsetNoZoom (u8 screen, u8 rotset, s16 angle). De este modo
se modifica el rotset, como se puede apreciar no se hace referencia al id del sprite ya que este
Fernando Garcia Bernal
30
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
se vinculó anteriormente y al modificar el rotset ya se aplica la transformación al sprite. Hay
que indicar en cual pantalla se encuentra el rotset, su id (0-31) y el ángulo de giro (0-511). El
giro del sprite se realiza desde su punto central.
Zoom
Código que muestra imágenes rotando:
// Activa zoom de sprite
#include <PA9.h>
// PAGfxConverter Include
#include "gfx/all_gfx.c"
#include "gfx/all_gfx.h"
int main(void){
PA_Init();
PA_InitVBL();
// Carga la paleta
PA_LoadSpritePal(
0,
0,
(void*)sprite0_Pal);
// Pantalla
// Número de paleta
// Nombre de paleta
// Carga el sprite
PA_CreateSprite(
0, 0,(void*)vaisseau_Sprite, OBJ_SIZE_32X32,1, 0, 50, 50);
// Habilita doble tamaño, lo cual significa que el sprite puede
// ser más grande que su tamaño normal
PA_SetSpriteDblsize(0, 0, 1);
// Este es otro sprite sin doble tamañao para mostrar la diferencia
PA_CreateSprite(
0, 1,(void*)vaisseau_Sprite, OBJ_SIZE_32X32,1, 0, 120, 66);
// Activa las rotaciones para el sprite
PA_SetSpriteRotEnable(
0,// pantalla
0,// Número de sprite
0);// Número de rotset. Hay 32 (0-31) por pantalla
// Mismo rotset para el otro sprite, se le aplicacrá la misma
// transformación
PA_SetSpriteRotEnable(0, 1, 0);
// Zoom. 256 significa no zoom, 512 es el doble de pequeño,
// 128 es el doble de grande
u16 zoom = 256;
while(1)
{
// Modifica el ángulo según las teclas
Fernando Garcia Bernal
31
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
zoom -= Pad.Held.Up - Pad.Held.Down;
// Función para zoom sin rotaciones
PA_SetRotsetNoAngle(
0, // pantalla
0, // rotset
zoom, zoom); // Horizontal zoom, Vertical zoom
PA_WaitForVBL(); // Sincronización
}
return 0;
}
Ejemplo muy similar al anterior debido a que un rotset puede representar tanto zoom como
rotaciones, por tanto sólo hay que modificar el tipo de transformación que se aplica a la
imagen. Sin embargo, es necesario comentar algunos detalles que no aparecían anteriormente.
Ahora usamos una variable llamada zoom que se va modificando para aplicar un zoom
distinto en cada ocasión. Se inicializa con 256 ya que esta cifra se corresponde al tamaño
normal, es decir 100% de su tamaño. Si la cifra es mayor se le aplicará una reducción, por
ejemplo 512 se corresponde con el 50% del tamaño original. Por consiguiente, si la cifra es
inferior se aumenta el tamaño, con un valor de 128 la imagen se muestra al 200% de su
tamaño.
Lo siguiente es aplicar la transformación, como ocurría en el ejemplo anterior esto es
modificando las propiedades del rotset que está vinculado al sprite. En este caso usaremos una
función que permite modificar exclusivamente el zoom, obviando la transformación de
rotación. Esta función es PA_SetRotsetNoAngle (u8 screen, u8 rotset,
u16 zoomx, u16 zoomy) que permite aplicar un zoom horizontal y otro vertical por
separado. En este código se ha aplicado un mismo rotset a dos imágenes y como se puede
observar, con hacer una sola llamada a esta función se modifica el rotset que a su vez se
observa como el zoom se aplica a ambas imágenes.
El problema de aumentar una imagen es que si esta ocupa por ejemplo 32x32 píxeles, al hacer
zoom se pasará de sus límites y se saldrá de los límites que tiene establecidos. Por ese motivo
existe la función PA_SetSpriteDblsize(screen, obj, dblsize), esta permite
habilitar la opción para que la imagen pueda abarcar el doble de su tamaño. El efecto se puede
comprobar en el ejemplo ya que esta función se ha aplicado solamente a una de las imágenes.
Fernando Garcia Bernal
32
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
Zoom y rotación
Para esta ocasión no considero necesario copiar aquí ningún ejemplo aunque si irá incluido
dentro de los códigos fuentes por si resulta necesario. La función usada para realizar estas dos
operaciones es PA_SetRotset (u8 screen, u8 rotset, s16 angle, u16
zoomx, u16 zoomy), que consiste en una combinación de las dos vistas anteriormente
donde se puede especificar el ángulo de rotación y el zoom horizontal y vertical del rotset.
3.2.5.4 Volteo de imágenes
Se trata de conseguir que una imagen se vea reflejada bien horizontalmente o bien
verticalmente. Las funciones para conseguir son PA_SetSpriteHflip(screen, obj,
hflip) y PA_SetSpriteVflip(screen, obj, vflip) respectivamente.
Lo
único que hay que hacer es indicar en el tercer parámetro con 0 ó 1 si se quiere realizar la
acción. El ejemplo que ilustra cómo hacer esta funcionalidad es el siguiente:
#include <PA9.h>
// PAGfxConverter Include
#include "gfx/all_gfx.c"
#include "gfx/all_gfx.h"
int main(void){
PA_Init(); //Inicialización de PAlib
PA_InitVBL();
PA_LoadSpritePal(
0, // Pantalla
0, // Número de paleta
(void*)sprite0_Pal);
// Nombre de paleta
// Se crearán cuatro sprites que serán volteados
// cada uno de diferente manera
PA_CreateSprite(
0, 0, (void*)mollusk_Sprite, OBJ_SIZE_32X32,
PA_CreateSprite(
0, 1, (void*)mollusk_Sprite, OBJ_SIZE_32X32,
PA_CreateSprite(
0, 2, (void*)mollusk_Sprite, OBJ_SIZE_32X32,
PA_CreateSprite(
0, 3, (void*)mollusk_Sprite, OBJ_SIZE_32X32,
1, 0, 0, 50);
1, 0, 64, 50);
1, 0, 128, 50);
1, 0, 192, 50);
// Flip para los sprites 1 a 3
PA_SetSpriteHflip(0, 1, 1); // HFlip -> Horizontal flip
PA_SetSpriteVflip(0, 2, 1); // VFlip -> Vertical flip
// Horizontal y Vertical flip
Fernando Garcia Bernal
33
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
PA_SetSpriteHflip(0, 3, 1); PA_SetSpriteVflip(0, 3, 1);
while(1) // Bucle infinito
{
}
return 0;
}
3.2.5.5 Transparencias
También conocido como alpha-blending. PAlib usa el hardware de Nintendo DS para llevar a
cabo esta tarea. Existe una limitación y es que se puede aplicar la transparencia a todas las
imágenes que sean pero sólo se podrá disponer de un grado de transparencia, es decir, que
todas las imágenes a las que le afecte esta modificación se verán con la misma transparencia.
Lo primero que hay que realizar para poder dar transparencia a un sprite es habilitar dicha
opción con PA_SetSpriteMode(screen,
sprite,
obj_mode). En el tercer
parámetro se establece el modo siendo 0 para modo normal, 1 para transparencia y 2 para
modo ventana.
Una vez activado, el sprite no se mostrará transparentado todavía. Antes es necesario activar
el sistema de efectos especiales para DS, estableciendo el modo de transparencia de nuevo
con PA_EnableSpecialFx(Screen, SFX_ALPHA (Alpha blending mode),
0 (leave to 0 for now), SFX_BG0 | SFX_BG1 | SFX_BG2 | SFX_BG3
| SFX_BD). Por último se establece el nivel de alpha al sprite con PA_SetSFXAlpha.
Comentar además que este ejemplo no funciona bien con los emuladores que he probado, es
necesario hacerlo funcionar en el hardware de la videoconsola Nintendo DS. El código de
ejemplo es el siguiente:
#include <PA9.h>
// PAGfxConverter Include
#include "gfx/all_gfx.c"
#include "gfx/all_gfx.h"
int main(void){
PA_Init(); //Inicialización de PAlib
PA_InitVBL();
Fernando Garcia Bernal
34
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
PA_LoadSpritePal(
0, // Pantalla
0, // Número de paleta
(void*)sprite0_Pal);
// Nombre de paleta
// Se crearán dos sprites
PA_CreateSprite(
0, 0, (void*)mollusk_Sprite, OBJ_SIZE_32X32, 1, 0, 0, 50);
PA_CreateSprite(
0, 1, (void*)mollusk_Sprite, OBJ_SIZE_32X32, 1, 0, 64, 50);
PA_SetSpriteMode(
0, // Pantalla
0, // Sprite
1); // Alphablending
s16 alpha = 7; // Nivel de transparencia
// Habilita el alpha-blending
PA_EnableSpecialFx(
0, // Pantalla
SFX_ALPHA, // Modo alpha blending
0, // Nada
SFX_BG0 | SFX_BG1 | SFX_BG2 | SFX_BG3 | SFX_BD); // Lo normal
while(1) // Bucle infinito
{
alpha += Pad.Newpress.Up - Pad.Newpress.Down;
PA_SetSFXAlpha(
0, // Pantalla
alpha, // Nivel de alpha, 0-15
15); // Dejar a 15
PA_WaitForVBL();
}
return 0;
}
3.2.5.6 Profundidad
Este es también un aspecto importante ya que si existen varias imágenes superpuestas, ¿cuál
se verá por encima? Con el concepto de profundidad se podrá establecer estar prioridad.
Existen dos tipos de prioridades en DS:
•
Prioridad de sprite: una imagen con un número de sprite menor se verá por encima de otro
sprite con un número mayor.
•
Prioridad de fondo: Está por encima de la prioridad de sprite. Por defecto todos los sprites
están en el fondo número 0. Se puede establecer que un sprite se sitúe en frente de otro
fondo, 0-3. Una imagen puesta en el fondo número 2 estará detrás de todos los sprites con
un fondo de prioridad 0 ó 1, y por delante de todos los sprites con un fondo de prioridad 3.
Fernando Garcia Bernal
35
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
Para establecer prioridades se usa la función PA_SetSpritePrio(screen, obj,
prio).
3.2.6 Funciones Matemáticas
3.2.6.1 Números aleatorios
Los números aleatorios hacen falta en muchas ocasiones ya que permiten conseguir
comportamientos que parecen no definidos, por ejemplo, son muy útiles en algoritmos de
inteligencia artificial. A continuación se explican las funciones que generan estos números
aleatorios.
PA_Rand()
Esta es la función más sencilla, genera un número aleatorio pero de un tamaño muy grande,
por tanto no suele usarse mucho. Las siguientes funciones evitarán tener que usar esta función
y posteriores transformaciones sobre su resultado.
PA_RandMax(max)
A esta función se le puede pasar un valor máximo de manera que si por ejemplo se le pasa el
valor de 6, devolverá un valor aleatorio entre 0 y 6 ambos inclusive.
PA_RandMinMax(min,max)
Con PA_RanMinMax se puede establecer un valor mínimo y otro máximo inclusives para el
valor devuelto.
PA_InitRand()
Llamando a esta función se podrá inicializar la semilla de números aleatorios con la hora y
fecha en el momento de su invocación. Usándola una vez antes del bucle principal se podrán
conseguir que cada ejecución de la aplicación genere números distintos con mayor
probabilidad.
Fernando Garcia Bernal
36
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
3.2.6.2 Decimales con punto fijo
Este apartado explica un punto importante en relación con el rendimiento. Algo que siempre
penaliza mucho las operaciones realizadas por el procesador son los cálculos con números
decimales. Esto puede tener mayor repercusión incluso en sistemas con potencia limitada
como es el caso de una videoconsola portátil. La DS es muy lenta trabajando con tipos de
coma flotante como float. Sin embargo, evitar usarlos no es una solución en muchas ocasiones
que es necesario una dimensión real.
Usando s32, variable de 32 bit, nosotros podremos reservar los últimos 8 bits para almacenar
la parte decimal y los 24 restantes para la parte entera. Los 8 bits pueden tener valores entre 0
y 255, por tanto la precisión que se puede llegar a tener es de 1/256 la cual no está nada mal.
Por tanto, en el punto fijo, un valor de 256 significa la unidad, 512 significa 2, 1024 significa
4… Del mismo modo para obtener valores decimales, 128 se corresponde con 0.5.
A continuación se muestra un ejemplo de cómo desplazar imágenes a velocidades inferiores a
la unidad. Se trata de una aplicación que mostrará 3 sprites alineados verticalmente donde
cada uno se moverá con las velocidades de 1, 0.5 y 0.25 respectivamente. El código es el
siguiente:
#include <PA9.h>
// PAGfxConverter Include
#include "gfx/all_gfx.c"
#include "gfx/all_gfx.h"
int main(void){
PA_Init(); //Inicialización PAlib
PA_InitVBL();
PA_InitText(0, 0);
s32 speed1 = 256; // Velocidad 1
PA_OutputText(0, 15, 2, " 1
pixel/frame");
s32 speed2 = 128; // Velocidad 0.5
PA_OutputText(0, 15, 10, "0.5 pixel/frame");
s32 speed3 = 64; // Velocidad 0.25
PA_OutputText(0, 15, 18, "0.25 pixel/frame");
PA_LoadSpritePal(0, 0, (void*)sprite0_Pal);
Fernando Garcia Bernal
37
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
PA_CreateSprite(
0, 0, (void*)vaisseau_Sprite, OBJ_SIZE_32X32, 1, 0, 0, 0);
PA_CreateSprite(
0, 1, (void*)vaisseau_Sprite, OBJ_SIZE_32X32, 1, 0, 0, 64);
PA_CreateSprite(
0, 2, (void*)vaisseau_Sprite, OBJ_SIZE_32X32, 1, 0, 0, 128);
// Todos las imágenes comienzan a la izquierda
s32 spritex1 = 0; s32 spritex2 = 0; s32 spritex3 = 0;
while(1) // Bucle infinito
{
// Cada imagen se mueve con su velocidad correspondiente
spritex1 += speed1; spritex2 += speed2; spritex3 += speed3;
// Posiciona todos los sprites, >>8 lo devuelve a
// su posición normal
PA_SetSpriteX(0, 0, spritex1>>8);
PA_SetSpriteX(0, 1, spritex2>>8);
PA_SetSpriteX(0, 2, spritex3>>8);
PA_WaitForVBL();
}
return 0;
}
Se observan las tres variables de tipo s32 que controlan las velocidades de las imágenes:
speed1, speed2 y speed3. Sus valores son 256, 128 y 64 respectivamente que se corresponden
con velocidades de 1, 0.5 y 0.25 píxeles por frame. Después dentro del bucle principal se
actualizan las posiciones en x según la velocidad que tiene cada imagen. Por último se
posicionan los sprites realizando una conversión. Se hace uso la operación >> 8 para dicha
tarea ya que como se ha comentado previamente esto es equivalente a dividir entre 28, 256.
He aquí que 256 se corresponda con 1, ya que se realiza la operación 256/256; 128 es 0.5 ya
que 128/256 es dicha cantidad; y por último 64/256 es igual a 0.25. Siendo esto la parte
importante porque realizar la división entre 256 es lento mientras que realizando la operación
>>8 que al trabajar a nivel de bit, se convierte en una operación mucho más rápida. Si se
quisiera convertir de la cantidad transformada, es decir pasar por ejemplo de 256 a 1 o 128 a
0.5, será necesario realizar la operación inversa <<8 que multiplicará por 28, 256.
3.2.6.3 Trayectorias y ángulos
En esta sección se va a explicar cómo se usan los ángulos en PAlib, como aplicar funciones
trigonométricas como senos y cosenos, y después se verá un ejemplo.
Fernando Garcia Bernal
38
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
Ángulos en PAlib
Algo a lo que se ha hecho mención anteriormente es que los ángulos en PAlib no son como
los cotidianos. El rango en DS va desde 0º hasta 511º y en sentido contrario a las agujas del
reloj. El orden es el siguiente, 0º es el lado derecho, 128 apunta hacia arriba, 256 es el lado
izquierdo y 384 se corresponde con abajo. Se puede ver ilustrado en la figura 7.
7 - Ángulos con PAlib.
PAlib ofrece una función que simplifica el trabajar con estos ángulos, se trata de
PA_GetAngle (s32 startx, s32 starty, s32 targetx, s32 targety)
y devuelve el ángulo formado entre la línea horizontal y la línea compuesta por los dos puntos
(startx, starty) y (targetx, targety).
El código para probar esta función es el siguiente:
// Includes
#include <PA9.h>
#include "gfx/vaisseau.raw.c"
#include "gfx/master.pal.c"
int main(void){
PA_Init();
PA_InitVBL();
PA_InitText(1,0); // Pantalla superior
PA_LoadSpritePal(0, 0, (void*)master_Palette);
Fernando Garcia Bernal
39
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
PA_CreateSprite(
0, 0,(void*)vaisseau_Bitmap,
OBJ_SIZE_32X32,1, 0, 128-16, 96-16);
while(1)
{
PA_OutputText(
1, 5, 10, "Angulo : %d ",
PA_GetAngle(128, 96, Stylus.X, Stylus.Y));
PA_WaitForVBL();
}
return 0;
}
Muestra en la pantalla inferior una imagen de una nave y en la pantalla superior se muestra el
grado que existe entre la línea horizontal que atraviesa la nave y la línea que se forma desde el
centro de la nave con la pulsación del puntero sobre la pantalla.
Senos y Cosenos
La primera diferencia entre estas funciones es que no devuelven un valor entre -1 y 1, sino
entre -256 y 256. De esta manera es mucho más eficiente ya que hace uso de números
decimales con punto fijo. A continuación se muestra una imagen que representa esta
situación:
8 - Seno y coseno con PAlib.
Fernando Garcia Bernal
40
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
Para obtener el seno y el coseno a partir de un ángulo se hace uso de las funciones
PA_Sin(angle) y PA_Cos(angle) respectivamente.
Ejemplo de trayectoria
Este ejemplo muestra una nave en el centro de la pantalla que puede desplazarse hacia
delante, atrás y girar a derecha e izquierda. Aquí está el código:
// Includes
#include <PA9.h>
// PAGfxConverter Include
#include "gfx/all_gfx.c"
#include "gfx/all_gfx.h"
int main(void){
PA_Init();
PA_InitVBL();
PA_InitText(1,0); // Pantalla superior
PA_LoadSpritePal(0, 0, (void*)sprite0_Pal);
// Crea la nave en el centro
PA_CreateSprite(
0, 0,(void*)vaisseau_Sprite,
OBJ_SIZE_32X32,1, 0, 128-16, 96-16);
// Habilita rotaciones y establece el Rotset 0
PA_SetSpriteRotEnable(0,0,0);
// Posición x de la nave en 8bit con punto fijo
s32 x = (128-16) << 8;
// Posición Y
s32 y = (96-16) << 8;
// Dirección a la que mover
u16 angle = 0;
while(1)
{
angle += Pad.Held.Left - Pad.Held.Right;
// Gira la nave en la dirección correcta
PA_SetRotsetNoZoom(0, 0, angle);
if (Pad.Held.Up){ // Mueve hacia delante
x += PA_Cos(angle);
y -= PA_Sin(angle);
}
if (Pad.Held.Down){ // Mueve hacia atrás
x += -PA_Cos(angle);
y -= -PA_Sin(angle);
}
PA_OutputText(1, 5, 10, "Angulo : %d
Fernando Garcia Bernal
", angle);
41
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
// Posición de imagen convertida a normal
PA_SetSpriteXY(0, 0, x>>8, y>>8);
PA_WaitForVBL();
}
return 0;
}
Primero se crea una imagen y se le vincula a un rotset para poder girarla. Después se
inicializan las variables que se usarán, en este caso las posiciones x e y, y el ángulo de
dirección. Las posiciones x e y son variables de tipo s32 por tanto estamos usando decimales
con punto fijo por lo que hay que hacer uso de la operación << 8.
Dentro del bucle lo primero que se actualiza es el ángulo, increméntandose si se gira a la
izquierda o decreméntandose si es a la derecha (sentido inverso a las agujas del reloj).
Después se actualiza la transformación del rotset para girar el sprite. Por último es necesario
actualizar las coordenadas de posición. A la posición x se le añade el coseno del ángulo,
mientras que a la posición y es necesario restarle el seno del ángulo ya que la parte superior
de la pantalla tiene y=0 lo contrario a los ejes de coordenadas habituales que las posiciones
bajas de y están en la parte inferior, por eso es necesario restar. Finalmente se actualiza la
posición con la función PA_SetSpriteXY teniendo en cuenta usar >> 8 para contrarrestar la
operación << 8 usada en la inicialización de las variables.
3.2.7 Sonido
El sonido se presenta como una parte importante en cualquier aplicación gráfica ya que sin
sonido quedaría incompleta quedando por muy cuidados que estuvieran el resto de detalles.
Ya sea desde acompañar el juego por una música que revele el estado de las situaciones
sumergiendo al usuario en el ambiente de lo que está viendo, o bien ayudando a la trama con
efectos de sonidos que acompañan a las acciones o eventos que se produzcan. Tal como lo
representa la palabra audiovisual, la parte gráfica junto con el sonido se complementan la una
con la otra para conseguir un mismo objetivo, transmitir sensaciones al usuario. Por tanto, se
convierte el audio en una parte imprescindible que no puede dejar de mencionarse.
Fernando Garcia Bernal
42
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
En PAlib existen dos métodos diferentes para cargar sonidos en la salida de audio: el formato
raw para reproducir sonido wav, usado sobre todo para efectos especiales debido a que ocupa
mucho espacio; y la reproducción mod que es perfecta para sonido de fondo en bucle ya que
ocupa muy poco espacio.
3.2.7.1 Archivos de sonido Raw
La DS no puede reproducir archivos wav ni otros formatos conocidos como mp3 u ogg, hace
falta convertir los audios a un formato raw para luego reproducirlos.
La conversión a formato raw debe llevarse a cabo usando algún programa. Uno de los más
sencillos es Switch que en su versión gratuita permite la conversión entre varios formatos
incluido raw. Basándome en este conversor, además de seleccionar un formato de salida .raw,
es posible especificar algunos criterios de codificación. Para obtener archivos de poco tamaño
con una calidad aceptable se pueden dejar estas opciones:
9 - Ejemplo exportar a .raw.
Para que la conversión sea válida es necesario dejar el formato de 8 bit signed, sin embargo
las otras dos opciones se pueden mejorar para obtener una mejor calidad. Por ejemplo,
poniendo mayor cantidad de muestreo (sample) o incluso establecer sonido estéreo.
Fernando Garcia Bernal
43
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
Para poder reproducir un archivo de sonido raw en una aplicación es necesario que este se
aloje en la carpeta data que está al mismo nivel que la carpeta source, en caso de que no exista
será necesario crearla. El código siguiente reproduce un sonido al pulsar el botón A:
// Ejemplo de sonido. Formato Raw
// Includes
#include <PA9.h>
#include "saberoff.h"
// Include para PA_Lib
// Include para el sonido
//(se aloja en la carpeta data en formato .raw)
// Función: main()
int main(int argc, char ** argv)
{
PA_Init();
// Inicializa PA_Lib
PA_InitVBL(); // Inicializa un standard VBL
PA_InitText(0, 0);
PA_InitSound();
// Inicializa el sistema de sonido
PA_OutputSimpleText(0, 0, 10, "Pulsa A para reproduc. el sonido");
// Bucle infinito
while (1)
{
// Reproduce el sonido si A ha sido pulsado
if (Pad.Newpress.A)
PA_PlaySimpleSound(0, saberoff);
PA_WaitForVBL();
}
return 0;
} // Fin de main()
Lo primero importante que se hace antes de comenzar las instrucciones es incluir el archivo
raw con #include "saberoff.h". Después cuando se quiera reproducir el sonido
bastará con usar su nombre ya que se ha establecido como un puntero al contenido de dicho
fichero. A continuación es necesario inicializar el sistema de sonido de PAlib (sirve también
para la reproducción de archivos mod) estableciendo el tipo de archivo por defecto a muestreo
11025 y 8 bit signed format. Por último queda efectuar la acción de reproducir el sonido en el
momento que sea necesario, en nuestro caso cuando se pulse el botón A. Para este fin se llama
a la función PA_PlaySimpleSound(PA_Channel, sound) a la que se le indica un
canal de salida de 0-7 y un archivo de sonido previamente incluido en el código. Pueden
usarse hasta 8 canales de reproducción al mismo tiempo. Por defecto los otros 8 canales
disponibles están reservados para la reproducción de archivos mod, aunque esto es
modificable.
Fernando Garcia Bernal
44
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
3.2.7.2 Archivos de sonido mod
Los archivos mod tienen como ventaja que ocupan muy poco espacio y suenan bien. Sin
embargo, no son fáciles de crear y por tanto se recomienda buscar en la web los archivos ya
existentes en bancos de sonidos gratuitos como por ejemplo: The Mod Archive, Exotica , and
ModLand. Ahora mediante un ejemplo se verá como reproducir estos archivos:
// Reproduce un archivo mod
// Includes
#include <PA9.h>
#include "modfile.h"
// Include para PA_Lib
// Include el archivo mod (el archivo .mod se aloja
// en la carpeta data)
// Función: main()
int main(int argc, char ** argv){
PA_Init();
PA_InitVBL();
PA_InitSound();
// Inicializa el sonido
PA_InitText(0, 0); PA_InitText(1, 0);
PA_PlayMod(modfile);
// Reproduce el archivo mod
PA_OutputText(0, 5, 10, "Reproduciendo mod...");
u8 i;
while(1){
// Bucle infinito
// Muestra si los cancales están ocupados
for (i = 0; i < 16; i++)
PA_OutputText(1, 0, i, "Canal %02d ocupado : %d ", i,
PA_SoundChannelIsBusy(i));
// 0 libre, 1 ocupado
PA_WaitForVBL();
}
return 0;
} // Fin de main()
Es similar al ejemplo de la sección 3.2.7.1, sin embargo, es necesario usar otra función para la
reproducción. En este caso se llama a PA_PlayMod(mod_snd). Una cosa que hay que
comentar es que el archivo mod puede tener hasta 16 canales, pero es recomendable usar 8
para dejar los 8 restantes libres para efectos de sonido.
Fernando Garcia Bernal
45
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
3.2.7.3 Funciones adicionales de sonido
Hasta ahora se han visto las funciones básicas para reproducir los dos tipos de formatos que
permite PAlib, sin embargo existen más funcionalidades a parte de reproducir sonidos. Las
más importantes son las siguientes.
Para archivos mod existe la posibilidad de detener la reproducción con la función
PA_StopMod() y también es posible pausarlo con PA_PauseMod(u8). Una función útil
para archivos raw es la siguiente: PA_SetDefaultSound (u8 volume, int freq,
s16 format), que establece las opciones del archivo como son volumen, frequencia y
formato.
Además el volumen se puede modificar a nivel de canal con PA_SetSoundChannelVol
(u8 PA_Channel, u8 Volume) y también es posible controlar el master del volumen
con PA_SetSoundVol (u8 Volume).
3.2.8 Programación Hardware DS
En este apartado se estudia las diferentes funciones que aporta PAlib para trabajar con el
hardware de DS como por ejemplo apagar la consola, información del usuario, reloj…
3.2.8.1 Fecha y hora
Para poder gestionar en las aplicaciones diferentes eventualidades a lo largo del tiempo será
necesario conocer las siguientes variables que harán uso del reloj interno que trae la consola y
que debe estar previamente configurado por el usuario de la videoconsola. Con este sencillo
ejemplo se accede a todo el contenido necesario:
// Muestra la fecha y hora
#include <PA9.h>
// Función Main
int main(void)
{
// Inicialización de PAlib
PA_Init();
PA_InitVBL();
Fernando Garcia Bernal
46
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
// Inicializa el texto
PA_InitText(1, 0);
while(1)
{
// Día, Mes y Año
PA_OutputText(
1, 2, 10, "%02d/%02d/%02d", PA_RTC.Day, PA_RTC.Month,
PA_RTC.Year);
// Hora HH:MM SS
PA_OutputText(1, 2, 12, "%02d:%02d %02d segundos", PA_RTC.Hour,
PA_RTC.Minutes, PA_RTC.Seconds);
PA_WaitForVBL();
}
return 0;
}
PA_RTC es una estructura que se actualiza cada frame la cual contiene toda la información
sobre la fecha y hora actual. Estas son las variables que contiene:
•
PA_RTC.Day, de 1 a 31.
•
PA_RTC.Month, de 1 a 12.
•
PA_RTC.Year, de 00 (para 2000) a 99 (para 2099).
•
PA_RTC.Hour, de 0 a 23.
•
PA_RTC.Minutes, de 0 a 59.
•
PA_RTC.Seconds, de 0 a 59.
3.2.8.2 Información de usuario
La información de usuario se encuentra almacenada en la variable PA_UserInfo. A
continuación un ejemplo que la muestra por pantalla:
// Muestra la información del usuario
#include <PA9.h>
// Función Main
int main(void)
{
// Inicialización de PAlib
PA_Init();
PA_InitVBL();
// Inicializa el texto
PA_InitText(1, 0);
Fernando Garcia Bernal
47
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
while(1)
{
// Nombre de usuario, cumpleaños
PA_OutputText(
1, 2, 10, "Nombre usuario : %s, %02d/%02d",
PA_UserInfo.Name, PA_UserInfo.BdayDay,
PA_UserInfo.BdayMonth);
// Alarma
PA_OutputText(
1, 2, 12, "Alarma : %02d:%02d", PA_UserInfo.AlarmHour,
PA_UserInfo.AlarmMinute);
// Idioma DS (0 - japonés, 1 - inglés,
// 2 - francés .. 5 - español)
PA_OutputText(1, 2, 14, "Idioma : %d", PA_UserInfo.Language);
// Mensaje especial
PA_OutputText(1, 2, 16, "Mensaje : %s", PA_UserInfo.Message);
PA_WaitForVBL();
}
return 0;
}
El contenido de la estructura PA_UserInfo es el siguiente:
•
PA_UserInfo.Name, nombre de usuario.
•
PA_UserInfo.BdayDay, día de cumpleaños.
•
PA_UserInfo.BdayMonth, mes de cumpleaños.
•
PA_UserInfo.Language, idioma identificado por un número (0 - japonés, 1 - inglés, 2 –
francés, 3 – Alemán, 4 – Italiano, 5 - español).
•
PA_UserInfo.Message, mensaje de bienvenida establecido por el propietario.
•
PA_UserInfo.AlarmHour, hora de la alarma (de 0 a 23).
•
PA_UserInfo.AlarmMinute, minuto de la alarma.
•
PA_UserInfo.Color, color elegido por el propietario.
3.2.8.3 Pausa al cerrar
En los juegos comerciales de se puede observar como al plegar la videoconsola cerrando las
tapas, el juego se pausa automáticamente hasta que se vuelve a abrir. Esto se puede realizar
también usando PAlib llamando simplemente a la función PA_CheckLid (void) que en
Fernando Garcia Bernal
48
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
caso de detectar que se han cerrado las tapas, pausa la consola y devuelve un 1. Aquí se puede
ver un ejemplo:
// Muestra la información del usuario
#include <PA9.h>
// Función Main
int main(void)
{
// Inicialización de PAlib
PA_Init();
PA_InitVBL();
// Establece un fondo de color rojo para poder comprobar
// si está pausada o no fácilmente
PA_SetBgPalCol(0, 0, PA_RGB(31, 0, 0));
while (1)
{
PA_CheckLid(); // Comprueba si está cerrada
PA_WaitForVBL();
}
return 0;
}
Para probar este ejemplo es necesario disponer de una consola DS.
3.2.8.4 Iluminación de pantallas
Con la función void PA_SetScreenLight (u8 screen, u8 light) se puede
activar/desactivar cualquiera de las dos pantallas.
3.3 Programación 3D
Hasta ahora se ha visto la mayor parte de herramientas que dispone un desarrollador para
construir aplicaciones en Nintendo DS. La parte que viene a continuación trata sobre las
posibilidades que ofrecen las librerías ya comentadas para trabajar con escenarios en 3D. Se
verá cómo poder inicializar estas funciones, pintar elementos básicos como quads y
triángulos, aplicar transformaciones 3D, manejar texturas…
Fernando Garcia Bernal
49
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
El soporte para programación gráfica 3D lo ofrece una implementación de openGL dentro de
la librería libnds. PAlib también incluye un módulo de funciones 3D que facilitan las tareas de
libnds aunque se centra en el tratamiento de sprites 3D. Por tanto, y como el objetivo final de
este proyecto consistirá en mostrar un escenario 3D no será tan necesario hacer uso del
módulo 3D incluido dentro de PAlib.
3.3.1 Inicializar funciones 3D
Hasta ahora la librería PAlib que se ha estudiado era la responsable de llamar a las funciones
de más bajo nivel que componen la otra librería libnds, simplificando significativamente la
tarea. Sin embargo, como se ha explicado anteriormente, el módulo de PAlib para la
programación gráfica 3D no llega a sustituir completamente a la librería libnds. Por tanto, a
partir de este momento será necesario combinar ambas librerías para poder simplificar las
tareas lo máximo posible. El siguiente ejemplo no muestra nada por la pantalla, simplemente
inicializa todo lo necesario para que después se puedan pintar componentes 3D correctamente.
// Ejemplo inicializar 3D
#include <PA9.h>
//Función encargada de pintar la escena
int DrawGLScene();
// Función Main
int main(void)
{
// Inicialización de PAlib
PA_Init();
PA_InitVBL();
//Inicialización 3D con PAlib
PA_Init3D();
// Habilita antialias
glEnable(GL_ANTIALIAS);
// Activa matriz de proyección para modificar
glMatrixMode(GL_PROJECTION);
// Carga la matriz identidad
glLoadIdentity();
// Pone la proyección en perspectiva ajustando
// al tamaño de pantalla de NDS
gluPerspective(35, 256.0 / 192.0, 0.1, 100);
// Orienta la cámara
Fernando Garcia Bernal
50
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
gluLookAt(
0.0, 0.0, 1.0,
0.0, 0.0, 0.0,
0.0, 1.0, 0.0);
Librería PAlib
// Posición de la cámara
// Hacia donde apunta
// Vector up
while (1)
{
// Push de la matriz activa (salva el estado)
glPushMatrix();
DrawGLScene();
// Pop de la matriz actual (carga el estado salvado en la pila)
glPopMatrix(1);
PA_WaitForVBL();
// Limpia el buffer de la pantalla
glFlush(0);
}
return 0;
}
int DrawGLScene(void)
{
// Carga la matriz identidad sobre la matriz activa
glLoadIdentity();
return TRUE;
}
Comienza incluyendo la librería PAlib como siempre se ha hecho, ya que se seguirán usando
algunas funciones de ésta. No es necesario incluir la librería libnds porque ya se hace de
manera implícita al cargar PAlib. Después se declara una función llamada DrawGLScene()
que contendrá en los ejemplos siguientes la escena 3D a pintar. Dentro de la función principal
main se inicializa PAlib como siempre. A continuación se inicializan las funciones 3D
llamando a PA_Init3D(). En su interior se inicializa lo básico para poder usar las
funciones 3D pero será necesario establecer más adelante una serie de características de
configuración que serán distintas según las circunstancias. La función glEnable se encarga
de habilitar propiedades, en este caso activa un filtro antialias que mejora la visualización, por
ejemplo suavizando los contornos de las figuras. Con la función glMatrixMode se
establece a cuál de las matrices que utiliza openGL se le aplicarán las transformaciones
resultantes de las funciones que aparezcan a continuación de dicha llamada, es decir, que
mientras no se establezca una matriz con glMatrixMode, esta no podrá ser modificada. Las
matrices más usadas de openGL son dos: GL_PROJECTION y GL_MODELVIEW. La primera
es la responsable de establecer la perspectiva de nuestra escena. La segunda matriz contiene
Fernando Garcia Bernal
51
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
las transformaciones aplicadas a los objetos de la escena. La siguiente instrucción es
glLoadIdentity(), cuya tarea es cargar la matriz identidad sobre la matriz establecida
con glMatrixMode. De este modo se inicializa el contenido de la matriz eliminando todas
las transformaciones que se hubieran aplicado a esta. Estando activada la matriz de
proyección, es necesario cargar sobre esta el tipo de proyección que se quiere mostrar en la
escena. Para ello se llama a la función gluPerspective(35, 256.0/192.0, 0.1,
100); cuyos parámetros significan respectivamente: 35 grados de ángulo de visión de altura
(eje Y), relación de aspecto acho/alto, 256/192, que determina el ángulo de visión a lo largo
del eje X, distancia desde el observador al plano de corte cercano, distancia desde el
observador al plano de corte lejano. Lo siguiente es posicionar y orientar la cámara
correctamente con gluLookAt. Sus parámetros se dividen en tres ternas: la primera
establece la coordenada donde estará situada la cámara, la segunda el punto del escenario 3D
hacia el que apunta la cámara, la última terna expresa el vector alzado de la cámara que forma
90º con el vector que se construye entre la posición hacia donde mira y la posición donde se
encuentra. Dentro del bucle principal, se llama a glPushMatrix que guarda la matriz
activada en lo alto de una pila. Con esto se salva su estado para poder recuperarlo más
adelante desde la pila. Después se llama a la función que pintará la escena. Finalmente se
recupera el estado de la matriz de transformaciones de modelos con glPopMatrix y se
limpia el buffer de memoria con glFlush.
3.3.2 Polígonos
Cuando ya se ha inicializado convenientemente la librería openGl, el sistema está dispuesto
para dibujar elementos 3D y poder mostrarlos. Por tanto, sólo será necesario modificar la
función DrawGLScene() ya que es la que se encarga en cada iteración del bucle infinito
de pintar sobre la pantalla los elementos deseados:
// Aquí es donde se pinta todo
int DrawGLScene()
{
// Activa la matriz de modelos de escena
glMatrixMode(GL_MODELVIEW);
// Inicializa la matriz activa
glLoadIdentity();
// Mueve 1.5 unidades a la izquierda y 6.0 hacia la pantalla
Fernando Garcia Bernal
52
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
glTranslatef(-1.5f,0.0f,-3.0f);
// Formato de polígonos, establece alpha a 31 (desactivado
// transparencias) y el modo de procesado de
// superficies ocultas a none
glPolyFmt(POLY_ALPHA(31) | POLY_CULL_NONE);
// Dibuja triángulos
glBegin(GL_TRIANGLES);
glColor3f(1.0f,0.0f,0.0f);
//
//
//
//
//
//
//
//
//
//
glVertex3f( 0.0f, 1.0f, 0.0f);
glColor3f(0.0f,1.0f,0.0f);
glVertex3f(-1.0f,-1.0f, 0.0f);
glColor3f(0.0f,0.0f,1.0f);
glVertex3f( 1.0f,-1.0f, 0.0f);
// Fin Triángulo
glEnd();
Establece el color de
vértice a Rojo
Vértice superior
Establece el color de
vértice a Verde
Vértice inferior
izquierdo
Establece el color de
vértice a Azul
Vértice inferior derecho
// Desplaza 3 unidades a la derecha
glTranslatef(3.0f,0.0f,0.0f);
// Establece el color para los siguientes vértices a azul
glColor3f(0.5f,0.5f,1.0f);
// Dibuja un Quad
glBegin(GL_QUADS);
glVertex3f(-1.0f, 1.0f,
glVertex3f( 1.0f, 1.0f,
glVertex3f( 1.0f,-1.0f,
glVertex3f(-1.0f,-1.0f,
// Fin Quad
glEnd();
0.0f);
0.0f);
0.0f);
0.0f);
//
//
//
//
Vértice
Vértice
Vértice
Vértice
superior
superior
inferior
inferior
izquierdo
derecho
derecho
izquierdo
return TRUE;
}
OpenGL trabaja cargando sobre sus matrices las transformaciones que se van solicitando.
Cada vez que se realiza una nueva operación, ya sea pintar un polígono, rotar la escena o
cualquier otra tarea, esta se aplica sobre el resto de modificaciones anteriores. Por este motivo
lo que se realiza al comienzo de la función es establecer como matriz activa la matriz que
carga los modelos de la escena junto con sus transformaciones y resetearla, es decir, se carga
la matriz identidad sobre ella perdiendo su contenido anterior. Así queda lista para trabajar
con ella desde el principio. La siguiente instrucción se corresponde con void
glTranslatef(float x, float y, float z) que como su nombre indica
realiza una translación sobre los elementos que se pinten a continuación de esta instrucción.
Si no se pusiera esta instrucción, lo que se pintara se haría sobre el punto (0,0) que se
Fernando Garcia Bernal
53
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
encuentra en el centro de la pantalla. Los parámetros que se le pasan a esta función le indican
la cantidad de desplazamiento en x (horizontal), y (vertical) y z (profundidad)
respectivamente. Siendo las coordenadas positivas las que desplazan hacia la derecha, arriba y
hacia la pantalla en cada caso. Para este ejemplo, la translación es en 1.5 unidades hacia la
izquierda y 6 unidades alejadas de la pantalla con respecto del usuario.
Con la función glPolyFmt se establecen las propiedades asociadas a los polígonos de la
escena. Para este caso se deja el valor de alpha (transparencia) a 31, sin transparencia, y se
establece que ambos lados de los polígonos sean renderizados.
Después comienza el pintado de un triángulo con glBegin(GL_TRIANGLES). Desde esta
línea hasta glEnd() todos los pares de 3 vértices que se pinten formarán triángulos. Existen
varios tipos de figuras simples que se pueden pintar. Para esta implementación de openGL
son
las
siguientes:
GL_TRIANGLES,
GL_QUADS,
GL_TRIANGLE_STRIP
y
GL_QUAD_STRIP. La primera es para dibujar triángulos, la segunda forma un cuadrado por
cada grupo de 4 vértices, y las dos últimas son similares a las dos primeras salvo que cada
grupo de vértice se encadenará al siguiente. En este ejemplo, dentro de los bloque glBegin
y
glEnd
aparecen
dos
funciones
distintas.
La
más
significativa
es
void
glVertex3f(float x, float y, float z) que pinta un vértice de la figura en las
coordenadas x, y, z indicadas. La otra función es void glColor3f(float r, float
g, float b) que establece el color a los vértices que se pinten a continuación según la
terna rojo, verde y azul. En el ejemplo a cada vértice del triángulo se le aplica un color
diferente, mientras que el cuadrado está pintado con todos los vértices del mismo color azul.
3.3.3 Rotación
En este caso además de modificar la función DrawGLScene, también se han creado dos
variables globales para controlar las rotaciones. Estas guardarán un valor que se modificará
tras cada iteración para que dé la sensación de que las figuras están girando cuando en
realidad se están volviendo a pintar con distintas inclinaciones.
float rtri;
float rquad;
// Ángulo para el triángulo
// Ángulo para el cuadrado
int DrawGLScene() // Aquí es donde se pinta todo
Fernando Garcia Bernal
54
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
{
// Activa la matriz de modelos de escena
glMatrixMode(GL_MODELVIEW);
// Inicializa la matriz activa
glLoadIdentity();
// Mueve 1.5 unidades a la izquierda y 6.0 hacia la pantalla
glTranslatef(-1.5f,0.0f,-4.0f);
// Gira el triángulo en el eje Y
glRotatef(rtri,0.0f,1.0f,0.0f);
// Formato de polígonos, establece alpha a 31 (desactivado
// transparencias) y el modo de procesado de
// superficies ocultas a none
glPolyFmt(POLY_ALPHA(31) | POLY_CULL_NONE);
// Dibuja triángulos
glBegin(GL_TRIANGLES);
glColor3f(1.0f,0.0f,0.0f);
glVertex3f( 0.0f, 1.0f, 0.0f);
glColor3f(0.0f,1.0f,0.0f);
glVertex3f(-1.0f,-1.0f, 0.0f);
glColor3f(0.0f,0.0f,1.0f);
glVertex3f( 1.0f,-1.0f, 0.0f);
// Fin Triángulo
glEnd();
//
//
//
//
//
//
//
//
//
//
Establece el color de
vértice a Rojo
Vértice superior
Establece el color de
vértice a Verde
Vértice inferior
izquierdo
Establece el color de
vértice a Azul
Vértice inferior derecho
// Reinicia la actual matriz Modelview
glLoadIdentity();
// Mueve 1.5 unidades a la derecha y 6.0 hacia la pantalla
glTranslatef(1.5f,0.0f,-4.0f);
// Gira el cuadrado en el eje X
glRotatef(rquad,1.0f,0.0f,0.0f);
// Establece el color para los siguientes vértices a azul
glColor3f(0.5f,0.5f,1.0f);
// Dibuja un Quad
glBegin(GL_QUADS)
glVertex3f(-1.0f, 1.0f, 0.0f);
// Vértice superior
// izquierdo
glVertex3f( 1.0f, 1.0f, 0.0f);
// Vértice superior derecho
glVertex3f( 1.0f,-1.0f, 0.0f);
// Vértice inferior derecho
glVertex3f(-1.0f,-1.0f, 0.0f);
// Vértice inferior
// izquierdo
// Fin Quad
glEnd();
// Aumenta la rotación variable para el triángulo
rtri+=0.9f;
// Decrementa la rotación variable para el triángulo
rquad-=0.75f;
Fernando Garcia Bernal
55
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
return TRUE;
}
La función que aparece nueva esta ocasión es void glRotatef(float angle,
float x, float y, float z). Se encarga de transformar la escena con una rotación
a todos los elementos que se pinten a continuación, Su primer parámetro es el ángulo de
rotación y los tres siguientes son los ejes sobre los que se aplica. En esta aplicación se realiza
una rotación diferente a cada polígono. Al comienzo de la función se inicializa la matriz
olvidando transformaciones previas y se aplica una traslación como sucedía en el ejemplo
anterior. Después se aplica la rotación sobre el eje Y. Se pinta el triángulo. Lo siguiente es
Inicializar de nuevo la matriz modelview, ¿por qué? Es importante comprenderlo. El objetivo
de este ejemplo es aplicar dos rotaciones diferentes e independientes entre sí a cada polígono.
La primera rotación sobre el eje Y, y la segunda sobre el eje X. Si se aplicara la translación y
la rotación sin más sucedería que se acumularían ambas rotaciones ya que sólo existe una
matriz de escena modelview que es común a todos, por tanto es necesario volverla a
inicializar para que las aplicaciones previas no se acumulen con las siguientes. Por tanto, a
partir de su nueva inicialización será necesario volver a trasladar con respecto a la coordenada
(0,0) y aplicar la rotación sobre el objeto. Es interesante comentar la línea de la segunda
inicialización de la matriz para ver qué sucede en tal caso. Por último se actualizan los valores
de las variables globales para conseguir el efecto de girar.
3.3.4 Figuras Básicas 3D
Este apartado no añade ninguna función nueva sin embargo resulta necesario para comprender
el nivel de abstracción con el que trabaja la librería de programación gráfica openGL.
Para poder dibujar objetos 3D es necesario representar las caras que lo forman, vértice por
vértice. Esto quiere decir que para construir una pirámide cuadrangular hace falta pintar
cuatro caras, prescindiendo de la cara inferior que serviría de base cerrando la figura. Si la
figura a representar es un cubo serán necesarias las seis caras que forman un dado. Para la
representación de cada cara se usa el tipo de estructura más conveniente de las vistas
anteriormente. Recordemos que básicamente en este implementación para DS existen dos:
GL_TRIANGLES y GL_QUADS de 3 y 4 vértices respectivamente. Las otras dos que existen
GL_TRIANGLE_STRIP y GL_QUAD_TRIP son agrupaciones de elementos de las otras dos.
Fernando Garcia Bernal
56
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
A continuación el código del ejemplo que es una extensión del anterior ya que donde había un
triángulo ahora se dibuja una pirámide y donde aparecía un cuadrado ahora hay un cubo.
Existe otra modificación, en lugar de girar el cubo sobre un eje ahora lo hace sobre dos.
// Aquí es donde se pinta todo
int DrawGLScene()
{
// Activa la matriz de modelos de escena
glMatrixMode(GL_MODELVIEW);
// Inicializa la matriz activa
glLoadIdentity();
// Mueve 1.5 unidades a la izquierda y 6.0 hacia la pantalla
glTranslatef(-1.5f,0.0f,-6.0f);
// Gira el triángulo en el eje Y
glRotatef(rtri,0.0f,1.0f,0.0f);
// Formato de polígonos, establece alpha a 31 (desactivado
// transparencias) y el modo de procesado de
// superficies ocultas a none
glPolyFmt(POLY_ALPHA(31) | POLY_CULL_NONE);
// Dibuja triángulos
glBegin(GL_TRIANGLES);
glColor3f(1.0f,0.0f,0.0f);
glVertex3f( 0.0f, 1.0f, 0.0f);
glColor3f(0.0f,1.0f,0.0f);
glVertex3f(-1.0f,-1.0f, 1.0f);
glColor3f(0.0f,0.0f,1.0f);
glVertex3f( 1.0f,-1.0f, 1.0f);
glColor3f(1.0f,0.0f,0.0f);
glVertex3f( 0.0f, 1.0f, 0.0f);
glColor3f(0.0f,0.0f,1.0f);
glVertex3f( 1.0f,-1.0f, 1.0f);
glColor3f(0.0f,1.0f,0.0f);
glVertex3f( 1.0f,-1.0f, -1.0f);
glColor3f(1.0f,0.0f,0.0f);
glVertex3f( 0.0f, 1.0f, 0.0f);
glColor3f(0.0f,1.0f,0.0f);
glVertex3f( 1.0f,-1.0f, -1.0f);
glColor3f(0.0f,0.0f,1.0f);
glVertex3f(-1.0f,-1.0f, -1.0f);
glColor3f(1.0f,0.0f,0.0f);
glVertex3f( 0.0f, 1.0f, 0.0f);
glColor3f(0.0f,0.0f,1.0f);
glVertex3f(-1.0f,-1.0f,-1.0f);
Fernando Garcia Bernal
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
Rojo
Vértice superior (Frente)
Verde
Vértice inferior
izquierdo (Frente)
Azul
Vértice inferior derecho
(Frente)
Rojo
Vértice superior
(Derecha)
Azul
Vértice inferior
izquierdo (Derecha)
Verde
Vértice inferior derecho
(Derecha)
Rojo
Vértice superior (Atrás)
Verde
Vértice inferior
izquierdo (Atrás)
Azul
Vértice inferior derecho
(Atrás)
Rojo
Vértice superior
(Izquierda)
Azul
Vértice inferior
izquierdo (Izquierda)
57
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
glColor3f(0.0f,1.0f,0.0f);
glVertex3f(-1.0f,-1.0f, 1.0f);
Librería PAlib
// Verde
// Vértice inferior derecho
// (Izquierda)
// Fin Triángulo
glEnd();
// Reinicia la actual matriz Modelview
glLoadIdentity();
// Mueve 1.5 unidades a la derecha y 6.0 hacia la pantalla
glTranslatef(1.5f,0.0f,-6.0f);
// Gira el cuadrado en el eje X
glRotatef(rquad,1.0f,1.0f,1.0f);
// Dibuja un Quad
glBegin(GL_QUADS);
glColor3f(0.0f,1.0f,0.0f);
glVertex3f( 1.0f, 1.0f,-1.0f);
glVertex3f(-1.0f, 1.0f,-1.0f);
glVertex3f(-1.0f, 1.0f, 1.0f);
glVertex3f( 1.0f, 1.0f, 1.0f);
glColor3f(1.0f,0.5f,0.0f);
glVertex3f( 1.0f,-1.0f, 1.0f);
glVertex3f(-1.0f,-1.0f, 1.0f);
glVertex3f(-1.0f,-1.0f,-1.0f);
glVertex3f( 1.0f,-1.0f,-1.0f);
glColor3f(1.0f,0.0f,0.0f);
glVertex3f( 1.0f, 1.0f, 1.0f);
glVertex3f(-1.0f, 1.0f, 1.0f);
glVertex3f(-1.0f,-1.0f, 1.0f);
glVertex3f( 1.0f,-1.0f, 1.0f);
glColor3f(1.0f,1.0f,0.0f);
glVertex3f( 1.0f,-1.0f,-1.0f);
glVertex3f(-1.0f,-1.0f,-1.0f);
glVertex3f(-1.0f, 1.0f,-1.0f);
glVertex3f( 1.0f, 1.0f,-1.0f);
glColor3f(0.0f,0.0f,1.0f);
glVertex3f(-1.0f, 1.0f, 1.0f);
glVertex3f(-1.0f, 1.0f,-1.0f);
glVertex3f(-1.0f,-1.0f,-1.0f);
Fernando Garcia Bernal
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
Verde
Vértice superior derecho
(Arriba)
Vértice superior
izquierdo (Arriba)
Vértice inferior
izquierdo (Arriba)
Vértice inferior derecho
(Arriba)
Color naranja
Vértice superior derecho
(Abajo)
Vértice superior
izquierdo (Abajo)
Vértice inferior
izquierdo (Abajo)
Vértice inferior derecho
(Abajo)
Rojo
Vértice superior derecho
(Frente)
Vértice superior
izquierdo (Frente)
Vértice inferior
izquierdo (Frente)
Vértice inferior derecho
(Frente)
Amarillo
Vértice superior derecho
(Atrás)
Vértice superior
izquierdo (Atrás)
Vértice inferior
izquierdo (Atrás)
Vértice inferior derecho
(Atrás)
Azul
Vértice superior derecho
(Izquierda)
Vértice superior
izquierdo (Izquierda)
Vértice inferior
izquierdo (Izquierda)
58
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
glVertex3f(-1.0f,-1.0f, 1.0f);
glColor3f(1.0f,0.0f,1.0f);
glVertex3f( 1.0f, 1.0f,-1.0f);
glVertex3f( 1.0f, 1.0f, 1.0f);
glVertex3f( 1.0f,-1.0f, 1.0f);
glVertex3f( 1.0f,-1.0f,-1.0f);
//
//
//
//
//
//
//
//
//
//
//
Librería PAlib
Vértice inferior derecho
(Izquierda)
Violeta
Vértice superior derecho
(Derecha)
Vértice superior
izquierdo (Derecha)
Vértice inferior
izquierdo (Derecha)
Vértice inferior derecho
(Derecha)
// Fin Quad
glEnd();
// Aumenta la rotación variable para el triángulo
rtri+=0.9f;
// Decrementa la rotación variable para el triángulo
rquad-=0.75f;
return TRUE;
}
3.3.5 Texturas
Ya se han visto las funcionalidades básicas que aporta esta implementación de openGL, con
las cuales técnicamente se podría hacer cualquier figura y aplicarle cualquier color. Sin
embargo, a la hora de mostrarlo dejaría bastante que desear ya que estéticamente no
impresionarían demasiado esas formas poligonales con colores tan planos, dando la sensación
de algo tosco y no llamaría la atención lo cual suele ser el gran objetivo de cualquier producto
audiovisual. Por tanto, quedarían dos aspectos visuales más que mencionar. Uno sería cómo
cargar en nuestras aplicaciones modelos 3D realizados con software de diseño especializado y
otro cómo se pueden aplicar texturas. En este apartado se verá lo segundo.
Para comenzar una textura es un método para añadir detalle a los gráficos o modelos 3D. La
técnica más común es aplicar una imagen ya existente sobre una malla de vértices. Los
resultados pueden ser tan buenos como lo sea la textura, pudiendo conseguir que un modelo
3D tenga un aspecto más real. También significa una mejora del rendimiento ya que, por
ejemplo, en lugar de modelar una piedra que podría tener una gran cantidad de vértices,
debido a su superficie irregular, que harían enlentecer el juego, podría simularse el aspecto de
piedra aplicando una textura a un modelo mucho más simple consiguiente la apariencia
deseada sin apenas costes de rendimiento. En el siguiente ejemplo se pintará un cubo girando
con una textura de caja de madera, ver figura 10.
Fernando Garcia Bernal
59
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
10 - Textura para el cubo.
// Texturas
#include <PA9.h>
#include "Crate.h" // Textura a cargar
float rtri;
float rquad;
int textureID; // variable id de la textura
int DrawGLScene();
int main(void)
{
PA_Init();
PA_InitVBL();
PA_Init3D();
glEnable(GL_ANTIALIAS);
glEnable(GL_TEXTURE_2D); // Habilita el uso de texturas
glGenTextures(1, &textureID); //Genera una textura sobre textureID
glBindTexture(0, textureID); // Vincula la textura
glTexImage2D(
// Asigna sobre la textura 0 la textura Crate
0, 0, GL_RGB, TEXTURE_SIZE_128 , TEXTURE_SIZE_128,
0, TEXGEN_TEXCOORD, (u8*)Crate);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(35, 256.0 / 192.0, 0.1, 100);
gluLookAt(
0.0, 0.0, 1.0,
0.0, 0.0, 0.0,
0.0, 1.0, 0.0);
while (1)
{
glPushMatrix();
Fernando Garcia Bernal
60
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
DrawGLScene();
glPopMatrix(1);
glFlush(0);
PA_WaitForVBL();
}
return 0;
}
int DrawGLScene()
{
// Inicializa la matriz de texturas
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// Establece las propiedades de los materiales
glMaterialf(GL_AMBIENT, RGB15(31,31,31));
glMaterialf(GL_DIFFUSE, RGB15(31,31,31));
glMaterialf(GL_SPECULAR, BIT(15) | RGB15(31,31,31));
glMaterialf(GL_EMISSION, RGB15(31,31,31));
glTranslatef(0.0f,0.0f,-4.0f);
glRotatef(rtri,0.6f,1.0f,0.8f);
glPolyFmt(POLY_ALPHA(31) | POLY_CULL_NONE);
glBindTexture(0, textureID); // Carga la textura 0
glBegin(GL_QUADS);
// Cara frontal
glNormal3f( 0.0f, 0.0f, 1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,
// Cara trasera
glNormal3f( 0.0f, 0.0f,-1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f,
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f,
// Cara superior
glNormal3f( 0.0f, 1.0f, 0.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,
// Cara inferior
glNormal3f( 0.0f,-1.0f, 0.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f,
Fernando Garcia Bernal
-1.0f,
-1.0f,
1.0f,
1.0f,
1.0f);
1.0f);
1.0f);
1.0f);
-1.0f,
1.0f,
1.0f,
-1.0f,
-1.0f);
-1.0f);
-1.0f);
-1.0f);
1.0f, -1.0f);
1.0f, 1.0f);
1.0f, 1.0f);
1.0f, -1.0f);
-1.0f, -1.0f);
-1.0f, -1.0f);
-1.0f, 1.0f);
61
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f,
// Cara derecha
glNormal3f( 1.0f, 0.0f, 0.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f,
// Cara izquierda
glNormal3f(-1.0f, 0.0f, 0.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f,
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,
glEnd();
Librería PAlib
-1.0f,
1.0f);
-1.0f, -1.0f);
1.0f, -1.0f);
1.0f, 1.0f);
-1.0f, 1.0f);
-1.0f, -1.0f);
-1.0f, 1.0f);
1.0f, 1.0f);
1.0f, -1.0f);
rtri+=0.9f;
rquad-=0.75f;
return TRUE;
}
Es necesario incluir la textura en el programa de alguna manera. En nuestro caso partimos de
una imagen textura.bmp y es necesario convertirla a un formato inteligible por NDS. Para esto
usamos una herramienta homebrew llamada TexConv. Existen otras muchas como
PAGfxconverter del creador de PAlib, pero con esta es suficiente. En el directorio de códigos,
en la ruta /Software/TexConv se encuentra la aplicación. Para poder convertir la textura
es necesario copiarla en dicho directorio y modificar el fichero build.bat. La línea de
ejecución tiene la siguiente estructura: TexConv
{textura
a
convertir}
{textura destino}. Para este caso la textura origen se llama textura.bmp y como
destino le damos el nombre Crate.bin. Ejecutando el archivo build.bat comprobamos que sale
una ventana ms-dos que si todo es correcto mostrará un mensaje como el siguiente:
Loading 'textura.bmp'...
Image size: 128 x 128...
Converting...
Done!
Esto indica el nombre de la textura a convertir, su tamaño en ancho x alto y finalmente que la
conversión se ha realizado con éxito. Finalmente copiamos el archivo generado,
Crate.bin, en la subcarpeta data de nuestra aplicación.
Fernando Garcia Bernal
62
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
En el código se hace referencia al fichero “Crate.h” que obviando el cambio de extensión
se trata de nuestra textura. Después aparece una variable global textureID. Esta será la
referencia que tengamos sobre la textura, sobre ella se generará la imagen y se usará cuando
haga falta cargarla. La siguiente línea de interés es glEnable(GL_TEXTURE_2D). Como
ya se ha comentado, glEnable habilita opciones disponibles, y en este caso posibilita poder
hacer uso de texturas. Las siguientes tres líneas se encargar de generar la textura. Con
glGenTextures se generan nombres de texturas, el primer parámetro indica el número de
nombres de texturas a ser generadas y el segundo el lugar donde serán generadas. En este
ejemplo generamos una sola textura en la variable textureID. La función
glBindTexture permite crear o generar un nombre de textura. El primer parámetro es el
objetivo que en openGL indica el tipo de textura sin embargo, en esta implementación se
ignora este parámetro siendo todas las texturas como GL_TEXTURE_2D. El siguiente
parámetro es el nombre de la textura a vincular. Cuando se llama a esta función las
vinculaciones anteriores dejan de existir y las siguientes operaciones referentes a texturas se
aplican sobre la textura vinculada. Para especificar la imagen a la textura vinculada se usa
glTexImage2D. Los parámetros pasados son: objetivo, nivel (obviados, sólo aparecen por
compatibilidad con openGL), tipo, ancho, alto, borde (también obviado), formato, textura.
Aquí se asocia la imagen con la variable global.
La siguiente línea de código relacionada con la aparición de texturas se encuentra dentro de la
función DrawGLScene. Al igual que se hace con la matriz de modelos de la escena, se carga
la matriz de texturas GL_TEXTURE y se inicializa. El siguiente paso es configurar los
materiales, en este caso se aplicará a las propiedades de la textura. Estas propiedades se
corresponden con la iluminación ambiental, difusa, especular y de emisión. Como ahora no
estamos trabajando con iluminación y nos interesa que se vea lo más claro posible, se le dan
los máximos valores para que la luz sea blanca. Si por ejemplo se modificarán estos valores se
podrían apreciar distintos tonos de colores (¡advertencia! no funciona correctamente en todos
los emuladores pero, por supuesto, sí se puede apreciar en el hardware original). Ahora antes
de comenzar a pintar las figuras hay que volver a vincular la textura para que esta se aplique
sobre los vértices. Se añaden dos funciones nuevas dentro de glBegin y glEnd. La primera
es glNormal3f que se le indica el vector normal con tres float. Esto se usa para que la
librería gráfica sepa cómo aplicar la iluminación en función de la posición del foco. La otra
función es glTexCoord2f y expresa la correspondencia de la textura con los vértices, se
Fernando Garcia Bernal
63
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
expresa con dos componentes ya que la textura está en dos dimensiones. Este proceso es
conceptualmente similar a envolver un objeto con un papel estableciendo puntos en común,
de este modo, se hace corresponder una coordenada del papel (2D) con una coordenada del
objeto (3D).
3.3.6 Iluminación
Antes se ha mencionado un poco el tema de la iluminación, pero en este capítulo se estudia
las posibilidades que ofrece la adaptación de openGL para la videoconsola NDS y como
iluminar una escena.
En openGL existen varios tipos de luces con las cuales se pueden simular las distintas
iluminaciones que pueden observarse en el mundo real. Algunas de estas son iluminación
global cuya luz es homogénea y cubre la escena por igual, luz de foco con un cuerpo cónico
que tiene un diámetro que aumenta con la distancia, luz puntual que se extiende en todas
direcciones a partir de su posición… Sin embargo, la versión de openGL que estamos usando
sólo ofrece un tipo de luz llamada paralela. Esta tiene como característica que su posición se
sitúa en una paralela de la escena en el infinito. Para crear una luz hay que establecer un
número que la identifique, un color RGB de iluminación y una coordenada (x,y,z). La
posición se expresa normalizada, entre -1 y +1, e indica en cual paralela del infinito se sitúa el
foco. Por ejemplo, si la coordenada es (0,+1,0) la luz estará situada por delante de la escena,
se verá que ilumina de frente; si la luz está en (0,-1,0) estará por detrás. Si la posición x es +1
iluminará por la izquierda y -1 lo hará por la derecha. Y así con el resto de coordenadas.
En el ejemplo que se va a mostrar en este apartado se muestra un cubo girado a cuyo
alrededor existe una luz que desplaza en torno a él. El foco recorre el perímetro de un
cuadrado imaginario situado en los bordes infinitos de la escena, de esta manera se observa
como el cubo se iluminar parcialmente por cada cara a medida que se desplaza la luz. Estas
son las variables y constantes que intervienen en el ejemplo:
// Constantes que definen el estado del movimiento de la luz
#define TRAMO_SUPERIOR 0
#define TRAMO_DERECHO 1
#define TRAMO_INFERIOR 2
#define TRAMO_IZQUIERDO 3
Fernando Garcia Bernal
64
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
// Variables que establecen las propiedades de la luz para este ejemplo
float xCam = -1.0f, yCam = -1.0f;
int tramo = TRAMO_SUPERIOR;
float inc = 0.2f;
Las macros se van a usar para definir los estados de movimiento de la luz, respecto al
cuadrado imaginario descrito antes. La posición está contenida en las variables xCam e yCam,
el tramo actual en tramo y el desplazamiento en cada instante por inc.
En el bucle principal se encarga de mostrar por pantalla la posición actual de la luz, un
mensaje para indicar que si se pulsa el botón Start se desactiva la luz, pinta la escena, y
actualiza la posición de la luz en función de su posición actual y del tramo que se encuentre
recorriendo:
while (1)
{
PA_ClearTextBg(1);
// Muestra por pantalla la posición actual de la luz
PA_OutputText(1,1,1,"Pos x %f3: ",xCam);
PA_OutputText(1,1,2,"Pos y %f3: ",yCam);
glPushMatrix();
DrawGLScene();
glPopMatrix(1);
glFlush(0);
PA_WaitForVBL();
// Actualiza la posición de la luz
switch(tramo){
case TRAMO_SUPERIOR:
if(xCam < 1.0f)
xCam += inc;
else{
tramo = TRAMO_DERECHO;
yCam += inc;
}
break;
case TRAMO_DERECHO:
if(yCam < 1.0f)
yCam += inc;
else{
tramo = TRAMO_INFERIOR;
xCam -= inc;
}
break;
Fernando Garcia Bernal
65
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
case TRAMO_INFERIOR:
if(xCam > -1.0f)
xCam -= inc;
else{
tramo = TRAMO_IZQUIERDO;
yCam -= inc;
}
break;
case TRAMO_IZQUIERDO:
if(yCam > -1.0f)
yCam -= inc;
else{
tramo = TRAMO_SUPERIOR;
xCam += inc;
}
break;
}
}
Por último, dentro de la función DrawGLScene se crea la luz con glLight, se activa o no
en función de si está pulsado el botón Start y finalmente se dibuja el cubo. Es importante
darse cuenta de que es necesario que se definan las normales del objeto para que se pueda
iluminar, sino no se vería nada en la pantalla. Igual de necesario es definir las propiedades de
iluminación con glMaterialf.
int DrawGLScene()
{
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// Crea la luz en la posición indicada por xCam e yCam
glLight(0, RGB15(31,0,0) , floattov10(xCam), floattov10(yCam), 0);
glTranslatef(0.0f,0.0f,-4.0f);
glRotatef(45.0f,1.0f,1.0f,0.0f);
glMaterialf(GL_AMBIENT, RGB15(2,2,2));
glMaterialf(GL_DIFFUSE, RGB15(20,0,0));
glMaterialf(GL_SPECULAR, BIT(15) | RGB15(31,0,0));
glMaterialf(GL_EMISSION, RGB15(15,0,0));
glMaterialShinyness();
// Mientras no esté pulsado Start se activa la luz
if(!Pad.Held.Start){
glPolyFmt(
POLY_ALPHA(31) | POLY_CULL_BACK | POLY_FORMAT_LIGHT0);
}
else{
glPolyFmt(POLY_ALPHA(31) | POLY_CULL_BACK);
}
Fernando Garcia Bernal
66
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
glColor3f(1.0f,0.0f,0.0f);
glBegin(GL_QUADS);
// Cara frontal
glNormal3f( 0.0f, 0.0f, 1.0f);
glVertex3f(-1.0f, -1.0f, 1.0f);
glVertex3f( 1.0f, -1.0f, 1.0f);
glVertex3f( 1.0f, 1.0f, 1.0f);
glVertex3f(-1.0f, 1.0f, 1.0f);
// Cara trasera
glNormal3f( 0.0f, 0.0f,-1.0f);
glVertex3f(-1.0f, -1.0f, -1.0f);
glVertex3f(-1.0f, 1.0f, -1.0f);
glVertex3f( 1.0f, 1.0f, -1.0f);
glVertex3f( 1.0f, -1.0f, -1.0f);
// Cara superior
glNormal3f( 0.0f, 1.0f, 0.0f);
glVertex3f(-1.0f, 1.0f, -1.0f);
glVertex3f(-1.0f, 1.0f, 1.0f);
glVertex3f( 1.0f, 1.0f, 1.0f);
glVertex3f( 1.0f, 1.0f, -1.0f);
// Cara inferior
glNormal3f( 0.0f,-1.0f, 0.0f);
glVertex3f(-1.0f, -1.0f, -1.0f);
glVertex3f( 1.0f, -1.0f, -1.0f);
glVertex3f( 1.0f, -1.0f, 1.0f);
glVertex3f(-1.0f, -1.0f, 1.0f);
// Cara derecha
glNormal3f( 1.0f, 0.0f, 0.0f);
glVertex3f( 1.0f, -1.0f, -1.0f);
glVertex3f( 1.0f, 1.0f, -1.0f);
glVertex3f( 1.0f, 1.0f, 1.0f);
glVertex3f( 1.0f, -1.0f, 1.0f);
// Cara izquierda
glNormal3f(-1.0f, 0.0f, 0.0f);
glVertex3f(-1.0f, -1.0f, -1.0f);
glVertex3f(-1.0f, -1.0f, 1.0f);
glVertex3f(-1.0f, 1.0f, 1.0f);
glVertex3f(-1.0f, 1.0f, -1.0f);
glEnd();
return TRUE;
}
3.3.7 Seleccionar objetos
No hay duda que una de las características diferenciadoras de esta videoconsola con respecto
a otro hardware es el uso de la pantalla táctil mediante el puntero o Stylus. Como se ha visto
es posible usar un registro especial para conocer si se ha pulsado sobre la pantalla y en tal
caso es posible saber en qué posición. En el ámbito de 2D es fácil conocer si se ha pulsado
sobre un elemento u otro si se conocen las posiciones y dimensiones de los elementos
simplemente viendo si la posición del puntero está contenida sobre el área de los elementos.
Fernando Garcia Bernal
67
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
Pero cuando hablamos de 3D la cosa cambia. Los objetos están inmersos en una escena de
tres dimensiones y lo único que conocemos de la pulsación es una coordenada en dos
dimensiones. Por tanto, se pierde la correspondencia totalmente, entonces ¿cómo se podría
averiguar si un elemento ha sido pulsado en una escena 3D? En este apartado se verá un
“truco” desarrollado por Gabe Ghearing, colaborador de libnds [4].
Antes de nada definimos el objetivo, queremos pintar una escena donde aparezcan varios
elementos 3D por ejemplo tres cubos, uno rojo otro verde y otro azul, y que cuando el usuario
pulse sobre alguno de ellos el programa sepa sobre cuál lo ha hecho y lo identifique de alguna
manera.
La idea es la siguiente, primero se pinta la escena como ya sabemos hacer, luego lo que hay
que hacer es usar la función de openGL gluPickMatrix, que restringe la zona de
renderizado a un área cuadrada que definamos, pintamos sobre una zona reducida en torno a
la pulsación del puntero. Como la variable GFX_POLYGON_RAM_USAGE almacena la
cantidad de polígonos pintados en pantalla, se compara si ha aumentado el número de
polígonos al pintar sobre ese pequeño área, en tal caso significará que había un objeto sobre la
zona pulsada, si no varía el número de polígonos entonces no había nada en ese área. Bien,
ahora se conoce si había algo allá donde estaba el puntero, falta resolver qué elemento era el
que ahí estaba. Para esto lo que hay que hacer es ir comprobando el número de polígonos en
pantalla por cada elemento que exista. Sin embargo, no tendremos la solución con el primer
elemento que aparezca bajo el puntero ya que puede ser que hubiera varios superpuestos, por
tanto habrá que comprobar además si el elemento que está en esa posición es el más cercano a
la cámara. Esto se puede resolver con ayuda de una librería que trae libnds también creada por
Gabe Ghearing llamada PosTest. Esta contiene un método PosTestWresult() que
habiendo establecido previamente lo que llama una posición de prueba, devuelve la distancia
entre esta y la cámara. De este modo ya sabremos distinguir entre cuál de los objetos se ha
pulsado y cuál está más cerca de la cámara. A continuación se muestra el código del ejemplo:
// Selección de figuras
// Includes
#include <PA9.h>
#include <nds/arm9/postest.h>
Fernando Garcia Bernal
// Librería usada para detectar posiciones
68
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
// Definición de tipos
typedef enum {
NOTHING,
RED,
GREEN,
BLUE
} Clickable;
// Variables globales
Clickable clicked;
int closeW;
int polyCount;
// Contiene qué ha sido pulsado
// Distancia más cercana a la cámara
// Guarda el número de polígonos dibujados
uint32 rotateX = 0;
uint32 rotateY = 0;
// Guarda la posición pulsada
int touchX, touchY;
// usado por gluPickMatrix()
int viewport[]={0,0,255,191};
// Funciones
int DrawGLScene();
// Ejecutada antes de dibujar un objeto durante el recorte
void startCheck();
// Ejecutado después de dibujar un objeto durante el recorte
void endCheck(Clickable obj);
void drawCube(float x, float y, float z, float r, float g, float b);
int main()
{
PA_Init();
PA_InitVBL();
PA_Init3D();
PA_InitText(1,3);
// Habilita dibujar el borde, para mostrar el objeto seleccionado
glEnable(GL_OUTLINE);
// Establece el color del borde
glSetOutlineColor(0,RGB15(31,31,31));
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(35, 256.0 / 192.0, 0.1, 100);
gluLookAt(
0.0, 0.0, 1.0,
0.0, 0.0, 0.0,
0.0, 1.0, 0.0);
while(1)
Fernando Garcia Bernal
69
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
{
PA_ClearTextBg(1);
if(clicked==RED)
PA_OutputText(1,1,1,"Pulsado cubo Rojo");
else if(clicked==GREEN)
PA_OutputText(1,1,1,"Pulsado cubo Verde");
else if(clicked==BLUE)
PA_OutputText(1,1,1,"Pulsado cubo Azul");
glPushMatrix();
DrawGLScene();
glPopMatrix(1);
glFlush(0);
PA_WaitForVBL();
// Actualiza la rotación si se pulsa algún cursor
if(Pad.Held.Up)
rotateX += 3;
if(Pad.Held.Down)
rotateX -= 3;
if(Pad.Held.Left)
rotateY += 3;
if(Pad.Held.Right)
rotateY -= 3;
// Obtiene la posición pulsada
touchX = Stylus.X;
touchY = Stylus.Y;
}
return 0;
}
void startCheck() {
while(PosTestBusy()); // Espera a que la posición de prueba termine
while(GFX_BUSY); // Espera a que se dibujen todos los
// polígonos del último objeto
PosTest_Asynch(0,0,0); // Crea una posición de prueba
// sobre la actual posición trasladada
polyCount = GFX_POLYGON_RAM_USAGE; // Guarda el número de polígonos
// actuales
}
void endCheck(Clickable obj) {
while(GFX_BUSY); // Esepera a que los polígonos sean dibujados¡
while(PosTestBusy()); // Espera a que la posición de prueba termine
if(GFX_POLYGON_RAM_USAGE>polyCount) // Si se ha dibujado algún
// polígono más..
{
if(PosTestWresult()<=closeW) {
// Si el objeto actual es el más cercano bajo el cursor..
closeW=PosTestWresult();
clicked=obj;
}
}
Fernando Garcia Bernal
70
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
}
int DrawGLScene(){
glViewPort(0,0,255,191); // Establece la vista a la pantalla completa
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(30, 256.0 / 192.0, 0.1, 20);
glMatrixMode(GL_MODELVIEW);
glPushMatrix(); // Salva el estado de modelview
{
glTranslate3f32(0,0,floattof32(-6));
glRotateXi(rotateX);
glRotateYi(rotateY);
glPushMatrix();
// Salva el estao de modelview antes de la
// primera pasada
{
// Dibuja la escena para mostrarla
if(clicked==RED) {
// Establece poly ID para mostrar línea externa
glPolyFmt(
POLY_ALPHA(31) | POLY_CULL_NONE | POLY_ID(1));
} else {
// Establece poly ID para no mostrar la línea
// externa (del mismo color que el fondo)
glPolyFmt(
POLY_ALPHA(31) | POLY_CULL_NONE | POLY_ID(0));
}
drawCube(2.9f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
if(clicked==GREEN) {
glPolyFmt(
POLY_ALPHA(31) | POLY_CULL_NONE | POLY_ID(1));
} else {
glPolyFmt(
POLY_ALPHA(31) | POLY_CULL_NONE | POLY_ID(0));
}
drawCube(-3.0f, 1.8f, 2.0f, 0.0f, 1.0f, 0.0f);
if(clicked==BLUE) {
glPolyFmt(
POLY_ALPHA(31) | POLY_CULL_NONE | POLY_ID(1));
} else {
glPolyFmt(
POLY_ALPHA(31) | POLY_CULL_NONE | POLY_ID(0));
}
drawCube(0.5f, -2.6f, -4.0f, 0.0f, 0.0f, 1.0f);
}
glPopMatrix(1); // Restaura modelview a donde fue rotada
// Dibuja la escena recortada
{
clicked = NOTHING; // Reset de clicked
closeW = 0x7FFFFFFF; // Reset de la distancia
Fernando Garcia Bernal
71
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Librería PAlib
// Deja la vista fuera de la pantalla, así se oculta
// todos los render que se hagan durante esta fase
glViewPort(0,192,0,192);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
// Renderiza sólo lo que esté bajo el cursor
gluPickMatrix(touchX,(191-touchY),4,4,viewport);
// La perspectiva debe ser la misma que la original
gluPerspective(30, 256.0 / 192.0, 0.1, 20);
glMatrixMode(GL_MODELVIEW);
startCheck();
drawCube(2.9f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
endCheck(RED);
startCheck();
drawCube(-3.0f, 1.8f, 2.0f, 0.0f, 1.0f, 0.0f);
endCheck(GREEN);
startCheck();
drawCube(0.5f, -2.6f, -4.0f, 0.0f, 0.0f, 1.0f);
endCheck(BLUE);
}
}
glPopMatrix(1); // Restaura modelview
return TRUE;
}
void drawCube(float x, float y, float z, float r, float g, float b){
glTranslate3f32(floattof32(x),floattof32(y),floattof32(z));
glColor3f(r,g,b);
glBegin(GL_QUADS);
// Cara frontal
glVertex3f(-1.0f,
glVertex3f( 1.0f,
glVertex3f( 1.0f,
glVertex3f(-1.0f,
// Cara trasera
glVertex3f(-1.0f,
glVertex3f(-1.0f,
glVertex3f( 1.0f,
glVertex3f( 1.0f,
// Cara superior
glVertex3f(-1.0f,
glVertex3f(-1.0f,
glVertex3f( 1.0f,
glVertex3f( 1.0f,
// Cara inferior
glVertex3f(-1.0f,
glVertex3f( 1.0f,
glVertex3f( 1.0f,
glVertex3f(-1.0f,
// Cara derecha
Fernando Garcia Bernal
-1.0f,
-1.0f,
1.0f,
1.0f,
1.0f);
1.0f);
1.0f);
1.0f);
-1.0f,
1.0f,
1.0f,
-1.0f,
-1.0f);
-1.0f);
-1.0f);
-1.0f);
1.0f, -1.0f);
1.0f, 1.0f);
1.0f, 1.0f);
1.0f, -1.0f);
-1.0f, -1.0f);
-1.0f, -1.0f);
-1.0f, 1.0f);
-1.0f, 1.0f);
72
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
glVertex3f( 1.0f,
glVertex3f( 1.0f,
glVertex3f( 1.0f,
glVertex3f( 1.0f,
// Cara izquierda
glVertex3f(-1.0f,
glVertex3f(-1.0f,
glVertex3f(-1.0f,
glVertex3f(-1.0f,
glEnd();
Librería PAlib
-1.0f, -1.0f);
1.0f, -1.0f);
1.0f, 1.0f);
-1.0f, 1.0f);
-1.0f, -1.0f);
-1.0f, 1.0f);
1.0f, 1.0f);
1.0f, -1.0f);
}
Para identificar al objeto pulsado del resto se hace uso de una propiedad de openGL que
ofrece la posibilidad de dibujar el contorno de un elemento 3D. Esto se activa llamando a
glEnable(GL_OUTLINE). La siguiente línea es glSetOutlineColor que permite
establecer el color de dicha línea con una terna RGB. Además se muestra un mensaje de texto
en la pantalla superior indicando el cubo que se ha seleccionado. Es importante observar que
esto sigue funcionando aunque la escena se gire usando los cursores.
Fernando Garcia Bernal
73
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Fernando Garcia Bernal
Librería PAlib
74
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Fase de Análisis del Juego
Capítulo 4. Fase de Análisis del Juego
Aquí comienza la segunda parte de la memoria de este proyecto. La primera parte, ya
explicada, abarca todos los aspectos necesarios, que se podrían considerar como introducción
aportando los conocimientos y herramientas necesarias para llevar a cabo un desarrollo
completo. Que para este caso se considera como producto final, la elaboración de un juego.
Este capítulo se centra en la fase de análisis, fundamental en cualquier elaboración que
contenga una arquitectura. Se parte de una necesidad y a partir de esta se toman las decisiones
necesarias para llevar a la práctica un proyecto real. Como se trata de un desarrollo en el que
el autor de la idea es la misma persona que va a llevarlo hasta últimos términos, se va a
comenzar desde la base primigenia, es decir, desde la toma de decisiones de qué idea se
quiere convertir en juego.
En primer lugar se va a plantear qué tipo de juego se va a realizar y por qué, a continuación se
decide el guión del juego, a partir de este comienza la fase de análisis infiriéndose en esta
cómo se va a estructurar (número de niveles, elementos que lo componen,…).
Otra parte importante del análisis, después de modelar el concepto en los distintos objetos que
se puede descomponer, consiste en analizar los estados en los que se pueden encontrar estos y
Fernando Garcia Bernal
75
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Fase de Análisis del Juego
cómo transitar de unos a otros. Para ello se usarán técnicas de modelado como UML para la
elaboración de diagramas de casos de uso.
Una vez esté analizada y definida la idea se pasará al siguiente capítulo, fase de diseño, que
abarcará cada elemento por separado de la estructura y lo acercará más todavía a la realidad
del desarrollador aunque sin llegar todavía a la implementación.
4.1 Idea
Para definir lo que se quiere conseguir, se parte de la ventaja de conocer los límites que se
poseen. Podemos aprovechar esta condición para definir un tipo de juego acorde con la
situación.
Se conocen muchos tipos diferentes de juegos, prácticamente puede haber tantas categorías
como ocurrencias distintas se puedan tener. En este paso no tiene tampoco porque ser
necesario justificar cada planteamiento con un motivo técnico/práctico, a parte de la necesidad
de dar algo de libertad a las ideas, simplemente porque en esta etapa tan primaria por mucho
que se pueda imaginar nunca se pueden conocer realmente las consecuencias de las
decisiones.
Por tanto, la idea que va a tratar de desarrollar durante esta segunda parte de la memoria
consiste en un juego de tipo aventura gráfica. La representación del personaje, objetos y
escenario se hará en 3D. Mientras que la interfaz de usuario será implementada en 2D. Usará
la pantalla táctil que proporciona la videoconsola además del resto de botones que esta posee.
El género de aventura gráfica es un concepto bastante genérico donde interviene uno o más
protagonistas, es una aventura donde habrá que explorar, investigar y resolver puzzles. Esta
categoría tiene una fuerte carga en la narración de los hechos sin embargo no será este el
punto fuerte en nuestro caso. La parte atractiva de elegir este género en lugar de otro, es la
posibilidad de crear un pequeño mundo donde intervengan los elementos necesarios para
hacerlo completo. Pudiendo de este modo, cualquier persona que se lo proponga, ampliarlo
sin demasiados inconvenientes dedicándole el peso correspondiente al hilo argumental.
Fernando Garcia Bernal
76
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Fase de Análisis del Juego
4.2 Guión
La existencia de Internet ha dado la posibilidad de que muchos “pequeños” desarrolladores
muestren con facilidad los programas que van realizando. Muchos son los que hacen sus
prácticas de aprendizaje elaborando juegos ya que ofrece una muy buena posibilidad para
tocar muchos campos que abarcan los desarrollos informáticos. Una de las plataformas más
llamativas que combina de un modo excelente la parte gráfica con la programación es Adobe
Flash [21]. Todo esto viene a que, junto a este tipo de desarrollos llevados a cabo
principalmente por personas que lo hacen por diversión, realizan juegos más o menos simples
que luego cuelgan en la red, conocidos popularmente como minijuegos, han creado un tipo de
aventuras que será la empleada para nuestro desarrollo. Esta categoría es la conocida como
‘Scape Room’. Consiste en juegos de aventura en los que el protagonista aparece, sin saber
muy
bien cómo, en una habitación que le resulta desconocida y siente la necesidad
(comprensible por otra parte) de que debe salir de ella. ¿Por qué es tan popular un tipo de
aventura así? Parece lógico pensar que es la mejor manera de realizar una aventura sin tener
que dar muchas explicaciones al usuario. Así que el guión de este juego será algo similar.
La historia completa sería la siguiente: el protagonista, amanece sin saber por qué (puede
suponerse que tampoco le interesa) en una habitación que no le resulta familiar. Allí parece
que no hay nadie, aunque es capaz de reconocer que existe una salida. Se trata de una puerta
que se encuentra cerrada con llave. A su alrededor se encuentran una serie de objetos que
pueden ayudarle a salir de su prisión. ¿Será capaz de conseguirlo? ¿Abrir la puerta será la
solución? ¿Bastará con salir al exterior? ¿Podrá aguantar mucho tiempo sin comer?
4.3 Modelado conceptual
Ya está definida la idea del juego. Esta no abarca todos los detalles, por tanto en esta fase
también se asumen decisiones.
4.3.1 Fases del juego
Esta aventura se va a componer de dos fases. La primera consiste en una habitación. Será
donde aparezca por primera vez el personaje. Se trata de un escenario cerrado y delimitado
por las paredes que lo componen. En ella se pueden encontrar una serie de objetos con los que
Fernando Garcia Bernal
77
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Fase de Análisis del Juego
el usuario puede interactuar, serán definidos más adelante. El objetivo para completar el
primer escenario es salir de la habitación por la puerta.
Cuando se logre escapar de la habitación, se accede a la siguiente fase. Esta será como un
bosque. Aquí también se pueden encontrar algunos objetos. El objetivo del personaje en este
escenario es diferente. Al haber salido al exterior y encontrarse perdido en el bosque, se
modifican sus prioridades y el protagonista entiende que es mejor estar alimentado que estar
perdido.
4.3.2 Elementos
El elemento más importante y que se muestra a lo largo de la partida es el personaje
protagonista. Este es la representación del usuario en el juego, y como se ha dicho, aparece
desde el comienzo hasta el final. Se enumeran a continuación el resto de objetos que aparecen
en los escenarios.
Es necesario indicar, que de entre todos los objetos que existen, algunos podrá usarlos el
usuario y otros no, y de entre los que se puedan usar, unos servirán para avanzar en la historia
y otros no.
•
Primer escenario:
o Mesa: oculta la llave que permite salir del escenario.
o Estatua: mientras esté apoyada encima de la mesa, está no se podrá desplazar.
o Puerta: se abre con la llave. Es por donde debe salir el personaje para continuar en
la siguiente pantalla.
o Fregona: elemento innecesario para la aventura.
o Llave: con esta se podrá abrir la puerta.
•
Segundo escenario:
o Árbol: se trata del objeto que debe usar el personaje para obtener el alimento.
o Coco: al obtenerlo el usuario, se acaba la partida.
Fernando Garcia Bernal
78
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Fase de Análisis del Juego
4.4 Diagrama de casos de uso
Conociendo como se va a desarrollar la partida, ya se pueden definir los diagramas de casos
de uso. Aunque los posibles estados no presentan muchas posibilidades diferentes, es
interesante dividirlos según conceptos: diagrama de eventos del usuario y diagrama del actor.
4.4.1 Diagrama de casos de uso del actor
Este diagrama muestra las acciones generales que puede realizar el personaje protagonista.
Se resumen las posibles acciones:
•
Ir a: El personaje se desplaza hacia una zona del escenario o bien hacia un objeto con el
que pueda interactuar.
•
Recoger: Si se encuentra activada esta acción y se selecciona un objeto, este es
almacenado en el inventario del protagonista. Existe la posibilidad de que no se pueda
recoger pero dependerá del guión de la aventura.
•
Usar: Podrá utilizar un objeto para avanzar en la aventura. Existen dos posibilidades: usar
un objeto que se encuentre en el escenario, por ejemplo una mesa para desplazarla; o bien
combinar un objeto de su inventario junto con otro que se encuentre en el escenario, por
ejemplo usar una llave que haya encontrado junto con una puerta.
4.4.2 Diagrama de casos de uso del usuario
El diagrama de los eventos que realiza el usuario durante la partida es de la siguiente manera:
Por tanto, quedan los siguientes eventos:
Fernando Garcia Bernal
79
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
•
Fase de Análisis del Juego
Selección: Al usar el puntero se realiza una selección sobre algún elemento del escenario.
Bien puede tratarse de una zona del suelo a la que el actor pueda desplazarse o puede
tratarse de un objeto con el que puede interactuar. Si no es ninguno de los dos casos
anteriores, entonces no se realiza ninguna acción.
•
Mover inventario: Las fechas de dirección o cursores, se utilizan para poder seleccionar
alguno de los objetos que haya recogido el protagonista y almacene en su inventario. Esto
sirve para poder usar un objeto del inventario junto con otro objeto que pertenezca a la
escena.
•
Cambiar acción: Existen tres acciones que puede realizar el protagonista: ‘Ir a’, ‘Recoger’
y ‘Usar’. Por tanto, se hará uso de tres botones concretos de la videoconsola para marcar
estas acciones.
•
Girar cámara: Usando los botones laterales que posee la videoconsola, se gira la cámara
para cambiar la perspectiva de la escena. Con el botón ‘L’ se gira la cámara a la izquierda
y con el botón ‘R’ se cambia hacia la derecha.
Fernando Garcia Bernal
80
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Fase de Diseño
Capítulo 5. Fase de Diseño
En el capítulo anterior ya se dejó finalizada la fase de análisis. Esta se abstrajo de relacionarse
con ningún componente de más bajo nivel que organizar las ideas del producto final. Esta fase
de diseño debe tomar los análisis de la etapa anterior y realizar un diseño exhaustivo de los
componentes que componen la idea, para que después en la etapa posterior de
implementación esté todo tan claro como para no tener que replantear ningún aspecto.
Por tanto, en esta fase se abarcan los siguientes puntos: diseño del personaje y objetos, diseño
de la interfaz de usuario y estudio de las clases que componen el proyecto.
5.1 Diseño de Personaje y Objetos
La parte visible de la aplicación es con la que tiene que lidiar el usuario, es importante hacerla
atractiva ya que se trata de un producto que debe satisfacer al cliente. Los dos aspectos más
importantes con los que un usuario trata directamente en este juego en el aspecto gráfico son:
la interfaz gráfica de usuario y los elementos que componen las escenas. En este punto se
muestran los diseños de estos últimos.
El diseño de los elementos gráficos corresponde al equipo de arte del proyecto y pueden ser
tan complicados como el director lo requiera. Para este caso concreto se necesitan modelos en
Fernando Garcia Bernal
81
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Fase de Diseño
3D. Se ha definido ya la lista de objetos que forman parte del juego. A excepción del
personaje protagonista, el resto de objetos son simples: mesa, llave, puerta, árbol… y sobre
estos no se presentan diseños previos. Sin embargo, para el protagonista se muestra a
continuación el boceto de lo que debe ser el personaje visto de perfil y de frente.
11 - Dibujo frente y perfil del personaje Protagonista.
La figura 11 es suficiente para que el diseñador encargado pueda elaborar el modelo en tres
dimensiones como se mostrará en el siguiente capítulo de la implementación. El resto de
componentes incluido el escenario, se crean directamente sobre el programa de modelado.
Es oportuno en este momento dar las gracias a la ilustradora Sandra Arteaga [22] por ceder
sus ilustraciones a este proyecto.
5.2 Diseño de la Interfaz del Usuario
La interfaz de usuario es la encargada de interactuar con el usuario y la lógica de la
aplicación. Debe mostrar de una manera clara e intuitiva las posibles acciones que puede
llevar a cabo el usuario de la aplicación. Para saber cuáles son las posibles acciones a realizar
se puede observar el diagrama de casos de uso del usuario. La figura 12 representa a la
interfaz de usuario es la siguiente:
Fernando Garcia Bernal
82
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Fase de Diseño
12 - Interfaz de usuario.
Las zonas de color magenta se corresponden con las transparentes, que cuando esté
funcionando la aplicación se verán de color negro ya que no hay nada detrás. Los círculos con
un tono verde claro bordeado por un color verde más oscuro representan los botones que
puede usar el usuario para realizar la acción que viene acompañada en el rótulo más cercano:
•
: Gira la cámara a la izquierda.
•
: Gira la cámara a la derecha.
•
: Selecciona la acción ‘Ir a’
•
: Selecciona la acción ‘Usar’
•
: Selecciona la acción ‘Recoger’
El conjunto de cinco filas que se encuentra en el lateral izquierdo muestra un listado con los
objetos que posee el usuario en su inventario. Cuando esté seleccionada la opción ‘Usar’ se
podrá seleccionar alguno de estos elementos del inventario para usarlo junto con algún otro
que se encuentre en el escenario, o bien no seleccionar ninguno del inventario para
simplemente usar un objeto de la escena.
La barra más larga que se encuentra en la zona inferior de la figura 12 muestra el estado
actual en el que el actor se encuentra. Estos pueden ser: “Ir a X”, “Recoger X”, “Usar X”,
“Usar Y con X”. Siendo X objetos que se encuentren en la escena e Y objetos que se hayan
recogido y formen parte del inventario.
Fernando Garcia Bernal
83
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Fase de Diseño
Para indicar al usuario la acción en la que se encuentra, además de mostrar en el rótulo
inferior el texto que lo explique, es necesario hacer uso de un selector que resalte la acción en
concreto. Para remarcar la acción que se encuentra seleccionada se usa la imagen 13 y para
saber que objeto se tiene seleccionado del inventario, la imagen 14.
13 - Selector acción.
14 - Selector inventario.
5.3 Estudio de las clases
El estudio de las clases será el transmitido al equipo de desarrollo para que implementen las
clases que componen el código del juego. Este diagrama describe las clases y objetos de la
aplicación y las relacionen que lo componen. Cada clase está compuesta por atributos y
métodos encapsulados con diferente visibilidad.
5.3.1 Diagrama de clases
Para la representación del conjunto de clases, se emplea el estándar UML [23]. Donde una
clase se muestra como un rectángulo dividido en tres partes: nombre de la clase, atributos y
métodos.
15 - Clase UML.
A continuación se muestra el diagrama de clases.
Fernando Garcia Bernal
84
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Fase de Diseño
5.3.2 Definición de las clases
Para entender mejor el esquema de clases, se muestran agrupadas por criterios de
funcionalidad.
En primer lugar se observa el diagrama de clases que corresponde con la representación de un
objeto 3D en el escenario.
Fernando Garcia Bernal
85
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Fase de Diseño
16 - Estructura UML de la clase Entidad.
•
Figura
El elemento básico es la ‘Figura’. Un objeto se puede componer de varias, por
ejemplo el protagonista tiene cinco figuras: brazo izquierdo, brazo derecho, pierna
izquierda, pierna derecha y torso; de esta manera se puede animar cada figura por
separado. También se debe ver la ‘Figura’ como el objeto que se rellene a partir del
objeto modelado en tres dimensiones, es decir a partir del fichero que se obtenga desde
el programa de diseño 3D. Se observa en el diagrama cómo cada figura puede aportar
un vector de la estructura animación que indica por cada instante o frame su posición,
rotación y escalado.
•
Objeto3D
La clase ‘Objeto3D’ es un contenedor de figuras, ofreciendo además la posibilidad
de aportar una caja de colisiones, o bounding box, al conjunto de estas por si fuera
necesario. También posee un vector de materiales del conjunto de figuras que lo
conforman. Se observa como la relación que existe entre ‘Objeto3D’ y ‘Figura’ es
de asociación, pudiendo un ‘Objeto3D’ tener una o muchas ‘Figura’.
•
Entidad
Esta clase ya supone un componente de más alto nivel de abstracción. Es el usado en
el desarrollo para representar a los elementos que componen la escena. Por ello tiene
componentes que lo ubican como caraMasCercana, que indica la cara del suelo que
tiene más cercana, o los mensajes que se deben mostrar cuando no se pueda recoger o
usar; o el estado que es una estructura del instante en el que se encuentra en la escena.
La relación entre ‘Entidad’ y ‘Objeto3D’ es de generalización (o especialización
según se mire).
El siguiente diagrama muestra la clase ‘Escenario’, la más importante de la aplicación.
Fernando Garcia Bernal
86
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Fase de Diseño
17 - Estructura UML de la clase Escenario.
Fernando Garcia Bernal
87
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Fase de Diseño
La clase Escenario es el centro neurálgico de la aplicación. En esta se relaciona tanto la
escena, como los objetos que la componen como el personaje protagonista.
La escena, que se corresponde con el lugar donde el personaje se encuentra, se almacena en la
variable Suelo. Los objetos que pueden interactuar con el protagonista se recogen en los
vectores elementosSeleccionables y elementosNoSeleccionables. Por último el personaje
principal se corresponde con la variable Protagonista.
En esta clase se implementa la lógica del juego, se encarga de actualizar los eventos de
teclado y puntero, actualizar las posiciones de los elementos, desencadenar los eventos
correspondientes según el estado en el que se encuentre la aplicación, y finalmente pinta la
escena.
Hasta ahora se ha mostrado la estructura básica de la aplicación. Con esta estructura se puede
partir para la elaboración de cualquier historia diferente. Para este caso en concreto se muestra
en la figura 18 el diagrama de clases utilizado.
Se observa como las clases EscenarioHabitación y EscenarioExterior son especializaciones
de Escenario e implementan los métodos virtuales usar, recoger y eventoFinalizarAnimacion
que cada escenario en particular debe saber implementar para responder a los eventos
correspondientes según se desarrolle la partida. Además estos escenarios contienen un puntero
a cado uno de los elementos que componen la escena de una clase que especializa a
Entidad.
A parte de estas clases, se utiliza una librería para simplificar algunas operaciones como por
ejemplo trabajar con vectores o cuaterniones. Esta librería permanece fuera de la estructura de
clases y se incluye en el código según sea necesario.
Fernando Garcia Bernal
88
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Fase de Diseño
18 - Relaciones para las clases personalizadas de Escenario.
Fernando Garcia Bernal
89
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Fernando Garcia Bernal
Fase de Diseño
90
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
Capítulo 6. Implementación
En este capítulo se va a utilizar el análisis y diseño llevado a cabo en los apartados anteriores
para realizar la implementación del juego. Se tendrá en cuenta los diagramas elaborados y el
estudio previo de las clases. Sin embargo, también es necesario elaborar los componentes
gráficos que forman parte de la aplicación.
Este capítulo se divide en varias partes. Un primer bloque va a desarrollar cómo implementar
el sistema/esqueleto que hará posible crear esta aventura además de ser el soporte necesario
para que cualquier desarrollador sea capaz de implementar sus aventuras sin tocar en el
núcleo salvo que desee ampliar funcionalidades. El otro bloque implementará la aventura
descrita con todas las clases que es necesario heredar y especializar para dicho fin.
Dentro del primer bloque se distinguen dos grupos que llevarán los dos primeros apartados. El
primero se abarca cómo obtener los componentes gráficos que necesita el juego: imágenes y
objetos 3D. La segunda parte se centra en la implementación del código de la aplicación.
6.1 Recursos gráficos
Empezamos por estudiar cómo se obtienen los componentes gráficos de la aplicación, ya que
hará falta invocarlos cuando nos centremos en el desarrollo del código del juego.
Fernando Garcia Bernal
91
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
En la fase de diseño se mencionó que existe una parte gráfica en 2D y otra en 3D que se
corresponden con la interfaz de usuario y con los modelos respectivamente.
6.1.1 Sprites
Un sprite es un mapa de bits que representa una imagen que se puede cargar en la aplicación.
Esto es lo que queremos obtener a partir de las imágenes que se vieron en la fase de análisis
para crear la interfaz de usuario. Si recordamos eran tres imágenes, una contiene la interfaz
gráfico de usuario mostrando los botones que debe pulsar el usuario para cambiar la acción,
otra imagen era el selector para marcar la acción seleccionada por el usuario, y la última se
trataba del selector de objetos del inventario. Estas tres imágenes se almacenan en tres
ficheros diferentes: menu.png, selector.png y selectorItem.png. Como ya se
comentó en el capítulo 3 donde se explicaba cómo cargar imágenes haciendo uso de la librería
PAlib, es necesario utilizar la herramienta PAGfx para convertir las imágenes png en sprites.
Esta operación viene explicada en el apéndice de cómo utilizar esta herramienta. El resultado
de dicha operación son ocho ficheros: all_gfx.h, all_gfx.c, menu.c, menu.pal.c,
selector.c, selector.pal.c, selectorItem.c y selectorItem.pal.c. Los
dos primeros contienen alias para poder incluir los gráficos en el proyecto de una manera
sencilla. El resto de ficheros representan el mapa de bits y la paleta asociada de las imágenes
convertidas. El conjunto de estos ficheros se copiará en la carpeta /gfx de la carpeta
source de nuestra aplicación.
6.1.2 Creación objetos 3D
El objetivo consiste en obtener la representación de los modelos en 3D que nos interesen, en
unos ficheros que puedan ser importados y representados en nuestra aplicación. El problema
consiste en que los programas que se utilizan para modelar no aportan ninguna herramienta
para exportar a un formato que se pueda usar directamente en la elaboración de aplicaciones
para Nintendo DS. Por tanto, se debe usar alguno de los formatos para los que sí exporta, y
realizar nosotros mismos la conversión que necesitamos. Realizaremos un paso a paso para
poder conseguir que nuestro personaje protagonista pueda ser cargado en la aplicación que
queremos implementar.
Fernando Garcia Bernal
92
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
Lo primero consiste en obtener el modelo 3D. Existe una gran variedad de herramientas de
modelado en 3D muchas de ellas muy sofisticadas. Para nuestro trabajo no hace falta que
ofrezca grandes posibilidades ya que estas suelen estar enfocadas en la producción de
imágenes o vídeos renderizados en 3D. No es nuestro caso, el objetivo es obtener una malla
texturizada con pocos polígonos para poder representarlos en tiempo real. Por estos motivos
se ha usado una herramienta llamada wings3d [24]. Se trata de una aplicación de modelado
3D gratuita, de libre distribución, y de código abierto. Es sencilla, ocupa poco, y permite crear
modelos en 3D, aplicarles texturas y poder exportarlos a una gran variedad de formatos
diferentes. Quizás una desventaja sea que de momento no soporta animar los modelos, por lo
que se deberá usar otra herramienta para ello. Una vez elegida la herramienta, se procede a la
creación del modelo. Partimos de una imagen que se vio en el capítulo anterior que
representaba la imagen del protagonista visto desde perfil y de frente. El objetivo de esta guía
no es aprender a crear modelos en 3D pero a continuación se dejan algunos pasos que se han
seguido para este proceso. Primero se colocó la imagen de perfil y de frente como dos planos
de pie en el suelo formando un ángulo de 90º como se puede observar en la imagen 19. De
esta manera es fácil ir creando primitivas 3D como cubos, esferas, cilindros… e ir dándole la
forma que se necesita. Es importante tener en cuenta que este modelo va a animarse, esto
implica que no puede estar compuesto por una sola pieza, debe estar compuesto por diferentes
partes que se puedan animar individualmente. En nuestro caso serán cinco piezas: pierna
derecha, pierna izquierda, brazo derecho, brazo izquierdo y torso.
19 - Antes de crear el modelo 3D se sitúan las imágemes de referencia.
Fernando Garcia Bernal
93
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
20 - Generación de modelo basándose en las referencias.
Una vez obtenido el modelo (ver figura 20), el siguiente paso es dotarlo de textura. Esto se
consigue mediante el proceso conocido como mapeo UV que consiste en desenvolver la malla
de la que está compuesto el modelo en un plano de dos dimensiones. En la figura 21 se puede
apreciar como se ha distribuido la malla sobre la imagen que se corresponde con la textura, y
a su lado el resultado final del modelo texturizado.
21 - Textura usada.
Fernando Garcia Bernal
94
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
22 - Modelo texturizado.
Ya hemos conseguido un modelo 3D (ver figura 22), el siguiente paso es animarlo. Como ya
se ha comentado, el programa wings3D no da soporte para animación, así que se debe usar un
programa diferente. En principio sirve cualquiera que soporte las animaciones básicas de
traslación, rotación y escalado. Elegimos por ejemplo uno de los programas más populares,
Autodesk 3ds Max [25] que se puede obtener una versión de prueba de 30 días. Este es uno de
los más completos y más usados, aunque lo hemos elegido como podría haber sido cualquier
otro que permitiera animar. Además nos sirve para el paso posterior que consiste exportar el
modelo animado al formato DirectX. Para ello es necesario instalar un plugin desarrollado por
la empresa PandaSoft que se puede descargar en este enlace [26].
La animación de un modelo también puede ser algo muy elaborado. Existen diferentes
técnicas que dan distintos resultados según sea la finalidad. Por ejemplo, si se quiere obtener
un movimiento realista, lo mejor es adjuntar al modelo 3D un esqueleto donde cada hueso se
vincule con una sección de la malla. Con esto se consigue que al animar algún hueso, los que
se encuentren conectados a este se desplacen arrastrados por él. Para el caso que nos ocupa no
es necesario invertir tanto sacrificio y podemos usar una de las técnicas de animación más
tradicionales. Consiste en que teniendo una línea de tiempo en la que transcurre la animación,
se seleccionan varios fotogramas clave donde disponemos del modelo en la posición que nos
interese. El programa de modelado rellenará los fotogramas intermedios realizando una
interpolación de la malla.
Fernando Garcia Bernal
95
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
En nuestro caso la animación que nos interesa es que nuestro protagonista camine. Para
simplificarlo, lo dejaremos en un movimiento contrario de piernas. La secuencia se muestra
en las figuras 23 a 26.
23 - Frame 0.
24 - Frame 5.
25 - Frame 10.
26 - Frame 14.
Ya tenemos el personaje animado. Ahora viene la parte de obtener un formato que se pueda
interpretar y convertir en una estructura reconocible por nuestro programa. Estudiando los
distingos formatos que existen, hemos elegido uno de los formatos de entre todas las
posibilidades que cumplían nuestras necesidades. Esto es que sea sencillo leer la estructura
que forma el modelo, almacene animaciones y sea sencillo de generar. Se trata del formato
DirectX [27]. Es un formato bastante extendid, propietario de Microsoft y empleado en
Microsoft Windows.
6.1.2.1 Formato DirectX
Un malla poligonal es una lista estructurada de puntos (llamados vértices) conectados que
describen una superficie. En nuestro caso, la malla define la superficie de nuestro modelo.
Para aplicar una textura al modelo, asociamos las coordenadas de la imagen con cada vértice
Fernando Garcia Bernal
96
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
para conocer que parte de la imagen pintar sobre la figura. Cada cara de la malla puede ser
asociada a un material diferente.
El formato propuesto por DirectX describe la geometría, jerarquía estructural y animaciones
del modelo en cuestión. Este formato se puede elaborar en texto plano o en fichero binario de
datos. Aquí usaremos el modo de texto. Se organiza en bloques que describen elementos del
modelo. En las páginas de MSDN (Microsoft Developer Network [28]) se puede acceder a las
descripciones de todos los elementos detallados. Las llaves son los elementos que delimitan
cada bloque. Existen diferentes tipos de bloques que describen distintas partes, y algunos de
estos deben estar contenidos dentro de otros. En la tabla 1 se aprecia esta jerarquía:
Bloque
Frame
Contiene
FrameTransformMatrix
Frame
Mesh
Mesh
MeshNormals
MeshTextureCoords
MeshMaterialList
SkinMeshHeader
SkinWeights
MeshMaterialList Material
Material
TextureFileName
AnimationSet
Animation
Animation
AnimationKey
Tabla 1 - - Estructura fichero X
Vamos a estudiar la estructura de un fichero X basándonos en un ejemplo sencillo. El modelo
se puede ver en la figura 27.
Fernando Garcia Bernal
97
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
27 - Modelo 3D de prueba.
Se trata de un modelo 3D compuesto por dos componentes, uno localizado arriba de color
amarillo con nombre ‘cabeza’ y otro de color verde turquesa nombrado ‘cuerpo’ a las que se
le ha aplicado una textura plana a cada uno. Además el modelo está animado de manera que el
componente amarillo da un giro completo en 40 frames. Iremos viendo paso a paso cada
sección del fichero DirectX obtenido.
•
Plantillas
xof 0303txt 0032
template FVFData {
<b6e70a0e-8ef9-4e83-94ad-ecc8b0c04897>
DWORD dwFVF;
DWORD nDWords;
array DWORD data[nDWords];
}
template EffectInstance {
<e331f7e4-0559-4cc2-8e99-1cec1657928f>
STRING EffectFilename;
[...]
}
Fernando Garcia Bernal
98
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
template EffectParamFloats {
<3014b9a0-62f5-478c-9b86-e4ac9f4e418b>
STRING ParamName;
DWORD nFloats;
array FLOAT Floats[nFloats];
}
template EffectParamString {
<1dbc4c88-94c1-46ee-9076-2c28818c9481>
STRING ParamName;
STRING Value;
}
template EffectParamDWord {
<e13963bc-ae51-4c5d-b00f-cfa3a9d97ce5>
STRING ParamName;
DWORD Value;
}
Aquí se muestran las plantillas usadas para este documento, que aparecen al comienzo del
fichero. Estas son: plantilla FVFData para especificar los datos de la malla restando la
posición, plantilla EffectInstance para definir plantillas de efectos, plantilla
EffectParamFloats para definir efectos con números en coma flotante, plantilla
EffectParamString
para
definir
efectos
con
cadenas
de
texto
y
plantilla
EffectParamDWord para definir efectos con el tipo DWord.
•
Materiales
La etiqueta Material define un material color básico que puede ser aplicado bien a una malla
completa o un conjunto individual de caras. Cada grupo Material define los siguientes
aspectos: color de la cara en formato RGBA, exponente del color especular como un Float,
color del material especular y color del material emisivo, ambos últimos en formato RGB.
Fernando Garcia Bernal
99
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
Adicionalmente se puede añadir la subplantilla TextureFilename para indicar una textura
al material, como se hace en este caso con los dos materiales que se muestran a continuación.
Material cabeza_auv {
1.000000;1.000000;1.000000;1.000000;;
0.000000;
0.000000;0.000000;0.000000;;
0.000000;0.000000;0.000000;;
TextureFilename {
"cabeza_a.bmp";
}
}
Material rotor_auv {
1.000000;1.000000;1.000000;1.000000;;
0.000000;
0.000000;0.000000;0.000000;;
0.000000;0.000000;0.000000;;
TextureFilename {
"rotor_au.bmp";
}
}
•
Frame cabeza
Es una plantilla abierta que puede contener cualquier objeto en una subplantilla Mesh junto a
una transformación en una plantilla FrameTransformMatrix.
Frame cabeza {
FrameTransformMatrix {
Fernando Garcia Bernal
100
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000
,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.00000
0,0.000000,1.000000;;
}
Mesh
{
329;
0.300000;-0.007822;1.121040;,
0.212132;-0.219954;1.121040;,
0.000000;-0.307822;1.121040;,
…continúa…
0.000000;-0.181689;1.645504;,
0.000000;-0.181689;1.645504;,
0.000000;-0.181689;1.645504;;
110;
3;231,252,226;,
3;239,249,234;,
3;248,254,245;,
…continúa…
3;244,252,240;,
3;36,40,35;,
3;218,251,246;;
MeshNormals
{
176;
-0.106596;-0.257344;-0.960422;,
-0.110311;0.000000;-0.993897;,
-0.106596;-0.257344;-0.960422;,
…continúa…
0.257344;0.106596;-0.960422;,
0.257344;0.106596;-0.960422;,
0.257344;0.106596;-0.960422;;
110;
Fernando Garcia Bernal
101
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
3;0,1,2;,
3;3,4,5;,
3;6,7,6;,
…continúa…
3;171,1,171;,
3;172,172,172;,
3;173,174,175;;
}
MeshMaterialList
{
1;
110;
0,
0,
0,
…continúa…
0,
0,
0;
{ cabeza_auv }
}
MeshTextureCoords
{
329;
0.285878;0.113956;,
0.401557;0.113956;,
0.000000;0.113956;,
…continúa…
0.600760;0.876275;,
0.653137;0.113956;,
0.956158;1.000000;;
}
}
}
Fernando Garcia Bernal
102
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
La primera línea que encontramos indica el nombre de la figura que representa este frame, en
este caso se trata de la figura ‘cabeza’. Lo siguiente es una plantilla denominada
FrameTransformMatrix que indica la transformación local de representación aplicada al
frame aplicando una matriz 4x4. Después aparece la plantilla Mesh que representa la
estructura que forma la malla, a su vez contiene varias plantillas: MeshNormals,
MeshMaterialList y MeshTextureCoords. El frame Mesh comienza indicando el
número de vértices de la estructura ‘cabeza’, 329, y continúa con una lista de ese tamaño de
coordenadas 3D. Sin embargo, la estructura se organiza en caras, por tanto lo siguiente que
aparece tras la lista de vértices es un número entero que representa el número de caras que
compone la estructura ‘cabeza’, y después de este dato aparece la lista de caras que se forma
de la siguiente manera: <número n de vértices que compone la cara>, <vértice 1 que compone
la cara>, …<vértice i que compone la cara>, …<vértice n que compone la cara>. Como la
lista de vértices es la que precede a esta, no es difícil conocer como está creada la malla del
objeto. Después de esta aparece, MeshNormals que describe las normales, gracias a las
cuales un modelo puede ser iluminado. De un modo similar a como se especifica la malla, lo
primero que se observa es un número entero que identifica la cantidad de coordenadas que
representan normales y tras esto aparece otro valor numérico entero que coincide con el
número de caras de manera que cada una de las líneas siguientes indica tres normales del
listado inmediatamente anterior, que se corresponde cada una de ellas con la normal que tiene
asociada cada uno de los tres puntos que forma la cara. Por ejemplo, en nuestro caso la
primera cara se forma con las coordenadas 231, 252 y 226; que se corresponde con las
normales
representadas
por
los
índices
0,
1
y
2.
El
siguiente
bloque
es
MeshMaterialList para hallar la correspondencia entre caras y materiales. El primer
número representa el número de materiales asociados, el siguiente es el número de caras (que
coincide con las otras ocasiones en las que aparece este valor), después viene el listado de
correspondencia entre cada cara y el material. Por último se muestra el nombre de los
materiales. En nuestro caso la malla cabeza sólo tiene asociado un material (aparece un 1), el
número de caras es 110, en el listado de 110 caras, todas están asociadas al material 0, que
como se observa después se trata de cabeza_auv. El último bloque se utiliza para asociar la
textura a la malla. La idea es que cada coordenada del modelo en tres dimensiones, se
corresponde con una coordenada de la textura en dos dimensiones. El primer número de este
Fernando Garcia Bernal
103
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
bloque es el número de coordenadas de la malla, y después aparece el listado de
correspondencia entre cada coordenada del modelo, con una coordenada de la textura.
Animation Anim-cabeza {
{ cabeza }
AnimationKey {
0;
41;
0;4;1.000000,0.000000,0.000000,0.000000;;,
1;4;1.000000,0.000000,0.000000,0.000000;;,
2;4;1.000000,0.000000,0.000000,0.000000;;,
…continúa…
38;4;1.000000,0.000000,0.000000,0.000000;;,
39;4;1.000000,0.000000,0.000000,0.000000;;,
40;4;1.000000,0.000000,0.000000,0.000000;;;
}
AnimationKey {
1;
41;
0;3;1.000000,1.000000,1.000000;;,
1;3;1.000000,1.000000,1.000000;;,
2;3;1.000000,1.000000,1.000000;;,
…continúa…
38;3;1.000000,1.000000,1.000000;;,
39;3;1.000000,1.000000,1.000000;;,
40;3;1.000000,1.000000,1.000000;;;
}
AnimationKey {
2;
41;
Fernando Garcia Bernal
104
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
0;3;0.000000,0.000000,0.000000;;,
1;3;0.000000,0.000000,0.000000;;,
2;3;0.000000,0.000000,0.000000;;,
…continúa…
38;3;0.000000,0.000000,0.000000;;,
39;3;0.000000,0.000000,0.000000;;,
40;3;0.000000,0.000000,0.000000;;;
}
Este último bloque se encarga de definir como se realiza la animación de un modelo. La
primera línea indica un nombre a la animación, en el ejemplo ‘Anim-cabeza’ que como se
entiende es la animación de la figura que hemos denominado ‘cabeza’, lo cual se comprueba
porque en la siguiente línea aparece la figura que está asociada a este bloque. A su vez se
compone de un bloque anidado llamado ‘AnimationKey’ donde se encuentra la
información importante. Esta estructura que pasamos a explicar ahora se usa tanto para las
transformaciones de rotación, escalado y translación. Es posible diferenciar cuándo se trata de
cada una, por el primer dígito que aparece. Si se trata de un 0, entonces describe una
animación de rotación, si es 1 entonces escalado y si fuera 2 se trataría de translación. Para
cualquiera de los tres casos, el siguiente valor que aparece es la cantidad de frames que
compone la animación, en el caso del ejemplo son 41 frames (que van de 0 a 40). A partir de
aquí el contenido del subloque ‘AnimationKey’ es la cantidad de la transformación. Se
compone por un dígito que indica el frame, un valor entero que indica el número de valores
decimales que describen la transformación y por último dichos valores. En el caso de la
rotación, se elabora con cuaterniones, que se explicarán en la implementación del juego; ahora
basta con saber que un cuaternión describe un giro en tres dimensiones pero se especifica con
cuatro componentes. Para el caso del escalado y la translación se usan tres componentes en
lugar de cuatro.
6.1.2.2 Conversión de formato
Ya conocemos como se obtiene un objeto modelado con una herramienta de diseño. Ahora la
parte siguiente es transformarlo a una estructura de datos que pueda interpretar nuestra
aplicación para poder representarlo con primitivas openGL. Para ello se ha construido una
Fernando Garcia Bernal
105
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
aplicación con C++, que posee una interfaz gráfica sencilla. La idea es generar a partir del
archivo .X en formato texto un archivo binario de datos con una estructura que podamos
utilizar.
•
Estructuras de datos
o coordFloat
Representa una coordenada y se compone por los tres componentes x, y, y z de la malla y por
las dos componentes u y v de la textura.
typedef struct{
float x, y, z;
float u, v;
} coordFloat;
o coordCaras
Representa una cara, que siendo fiel a la estructura del fichero X, está formado por tres
índices que se corresponde cada uno con una estructura coordFloat, y además el índice que
indica el material que se asocia con esa cara.
typedef struct{
int pto1, pto2, pto3;
int material;
} coordCaras;
o material
Cada material que compone el modelo 3D contiene un nombre identificativo, un nombre de
fichero, un ancho, un alto, y una metainformación necesaria para conocer el número de
repeticiones que tiene en el modelo.
typedef struct{
char nombre[MAXCAD];
char fichero[MAXCAD];
int ancho, alto;
int veces;
} material;
o animacion
Fernando Garcia Bernal
106
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
Cada frame de animación de un modelo se identifica por una rotación compuesta por cuatro
componentes (esto se debe a que no son las típicas coordenadas en (x, y,z) sino se trata de
cuaterniones que se explicarán más adelante) y un cantidad de escalado y de posición en
(x,y,z).
typedef struct{
float rot[4], sca[3], pos[3];
} animacion;
o figura
En este caso se ha utilizado una clase para representar a cada figura. Recordamos que un
modelo puede estar formado por varias figuras. En el caso que mostramos arriba, el modelo
está formado por la figura ‘cabeza’ y la figura ‘cuerpo’. La figura posee un nombre
identificativo, un número de frames, un número de caras, un número de puntos, un número de
materiales, un array de nCaras donde cada posición contiene el índice del material que se
asocia con cada cara, un array de nMateriales donde cada posición se corresponde con las
veces que se repite un material, un array que se corresponde con el índice de vértices y un
array de estructura animacion.
class figura{
public:
char nombre[MAXCAD];
int nFrames;
int nCaras;
int nPuntos;
int nMateriales;
int *numeroMat;
int *vecesMat;
float *v;
animacion *a;
};
o modelo
La clase modelo es la que representa al objeto 3D en su totalidad. Se compone de un nombre,
un número de figuras, un vector de figuras, un número de materiales y un vector de materiales
class modelo{
public:
char nombre[MAXCAD];
int nFiguras;
figura *f;
Fernando Garcia Bernal
107
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
int nMateriales;
material *mat;
};
•
Implementación
Esta aplicación de escritorio presenta la siguiente interfaz mostrada en la figura 28.
28 - Aspecto de la interfaz conversor ficheros X.
Como se puede observar, la única información de entrada que necesita es la ruta del fichero X
y la ruta destino del fichero binario. Una vez introducidas se pulsa sobre el botón ‘Convertir’
y se muestra en ‘Salida’ si la conversión se ha realizado correctamente. La lógica de la
aplicación se encuentra en la función:
void convertirArchivos(char *rutaX, char *rutaDestino, char *nombre, char
*salida)
Esta recibe la ruta del fichero a leer X, la ruta de destino, el nombre del fichero y el mensaje
que se debe mostrar finalmente en la caja de texto ‘Salida’. Esta función se divide en dos
partes bien diferenciadas: lectura del fichero X y creación del fichero binario.
•
Lectura del fichero X
Esta primera parte consiste en ir leyendo el fichero explicado previamente, identificando cada
bloque y almacenarlo en la estructura de datos que hemos declarado. En la siguiente fase se
volcará la información interpretada en un fichero binario que cargaremos desde la aplicación.
Se distinguen tres diferentes bloques a un mismo nivel de anidamiento: ‘Material’,
‘Animation’ y ‘Frame’.
Fernando Garcia Bernal
108
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
El bloque ‘Material’ almacena una nueva estructura material de la que obtiene su nombre y el
nombre de la textura; despreciando la información del color y resto de valores previamente
explicados. Con el nombre del archivo lo que también hace es llamar a la función
obtenerDimensionesBMP que carga el material de la estructura de ficheros y a través de
una librería obtiene el valor de ancho y alto de esta para incorporarlo a la información de la
estructura de datos.
Si al leer el fichero de texto, se encuentra la palabra ‘Animation’, entonces se carga el
bloque correspondiente. Primero se guarda el nombre de la figura de la que se va a describir
su animación, después se localiza el objeto figura y sobre él se actualiza el número de frames
con la información que se recupera del fichero X. Por último se guarda en una estructura
animacion, la información relativa a la rotación, escalado y translación de la figura
correspondiente.
El tercer tipo de bloque que se puede encontrar, comienza con la cadena de texto ‘Frame’.
Este es más completo que los otros dos ya que aporta más información. Lo primero es obtener
el nombre de la figura. Tras esto se busca la cadena ‘Mesh’ que será la que describa la
formación de la malla del modelo. Como ya se ha estudiado, de aquí se obtiene el listado de
vértices y caras que forman la figura. Estos se guardan en las estructuras coordFloat y
coordCaras. Después se guarda la lista de materiales que interviene en el modelo almacenado
dentro del subbloque ‘MeshMaterialList’. El siguiente bloque que aparece se encuentra
precedido por la cadena ‘MeshTextureCoords’. Aquí se guardan las componentes u y v
de la estructura coordFloat.
•
Creación fichero binario
El fichero X de texto deja aquí de tener utilidad. Ya se ha leído su contenido y almacenado en
las estructuras de datos pertinentes. El siguiente paso es persistir esta información en un
fichero de datos que mantenga una estructura definida que podamos usar en la aplicación 3D
para pintar los modelos usando primitivas openGL. A continuación se muestra el código
capaz de generarlo:
/************************************
Fernando Garcia Bernal
109
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
* Crear fichero .bin
************************************/
char nomfich[MAXCAD];
sprintf(nomfich,"%s\\%s.bin",rutaDestino,mod.nombre);
// Creo el fichero binario
ofstream fos(nomfich,ios::out|ios::binary);
// Guardo la estructura modelo
fos.write(reinterpret_cast<char *>(&mod),sizeof(modelo));
// A continuación se concatenan las estructuras material que
// le correspondan
for(int i=0; i<mod.nMateriales; i++)
{
material maux=mat[i];
fos.write(reinterpret_cast<char *>(&maux),sizeof(material));
}
for(int i=0; i<mod.nFiguras; i++)
{
// Después por cada figura…
figura f=fig[i];
// Se guarda la información estática con la que cuenta
// la estructura figura
fos.write(reinterpret_cast<char *>(&f), sizeof(figura));
// Se guarda la lista con el índice de cada material y las
// veces que se usa
fos.write(reinterpret_cast<char *>(f.numeroMat),
sizeof(int)*f.nMateriales);
fos.write(reinterpret_cast<char *>(f.vecesMat),
sizeof(int)*f.nMateriales);
// Aquí se guarda la lista de vértices. El número
// de vértices es igual al número de caras multiplicado por
// tres (número de vértices por cada cara) y multiplicado por
// cinco (porque son cinco float para representar las
// componentes (x,y,z) y las componentes (u,v) de la textura.
fos.write(reinterpret_cast<char *>(f.v),
sizeof(float)*f.nCaras*3*5);
// Finalmente se almacena la animación
fos.write(reinterpret_cast<char *>(f.a),
sizeof(animacion)*f.nFrames);
}
fos.close();
/************************************
* Fin fichero .bin
************************************/
Tras estos pasos hemos conseguido obtener un fichero de datos que podremos cargar en
nuestra aplicación para poder mostrar una figura 3D.
Fernando Garcia Bernal
110
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
6.2 Arquitectura de la aplicación
Ahora la implementación se enfoca sobre el código que permite crear un ejecutable para la
videoconsola Nintendo DS. Ya se han creado los recursos necesarios que deben ser
importados por este código. En este paso se hace uso tanto de algoritmos ad hoc como de las
funcionalidades explicadas de las librerías PAlib y libnds. Este apartado se descompone en las
diferentes partes que forman el código de la aplicación. La sección librerías trata sobre las
herramientas que se han implementado como utilidades de manejo de vectores,
cuaterniones,… Después continúa con cada una de las clases que se definieron en el capítulo
de diseño, se irán tratando según van apoyándose unas clases a otras mediante la herencia
siendo en mi opinión la manera más didáctica de comprenderlo. El último subapartado trata
de cómo se ha implementado el fichero principal main, valga la redundancia.
6.2.1 Librerías
Se han creado tres grupos de herramientas que se usan como librerías para facilitar la
estructura del código. Por un lado se encuentra Vectores.h que implementa clases que
simplifican
el
trabajo
con
vectores,
después
unas
herramientas
en
la
librería
glQuaternion.h que permiten usar los mencionados pero no explicados cuaterniones, y
por último lib.h que contiene algunas estructuras de datos y funciones varias.
6.2.1.1 Vectores.h
En este fichero se crean dos clases: vector2 y vector3. Ambas definen a vectores con
tipo de dato float, la primera de dos componentes y la segunda de tres. La ventaja que
aporta esta implementación de vectores, es que se han definido una serie de operadores para
simplificar la escritura del código al trabajar con ellos.
•
vector2
A continuación se muestra la declaración de esta clase que se encuentra en el fichero
Vectores.h:
Fernando Garcia Bernal
111
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
/**
* Clase vector2
* Vector de dos componentes (x,y) de tipo float. Contiene funciones
* y operadores que facilita trabajar con esta clase
* @author Fernando García Bernal
*/
class vector2
{
public:
// Miembros
float x, y;
public:
// Constructores
/** Constructor vacío */
vector2();
/** Constructor con inicialización de valores float */
vector2(float _x, float _y);
~vector2();
public:
// Operadores
/** Asignación */
vector2 &operator = (const vector2 &v);
/** Suma un vector2 a este */
vector2 &operator += (const vector2 &v);
/** Resta un vector2 a este */
vector2 &operator -= (const vector2 &v);
/** Multiplica el vector2 por un float */
vector2 &operator *= (float f);
/** Divide el vector2 entre un float */
vector2 &operator /= (float f);
/** ¿Son iguales estos dos vector2? */
friend bool operator == (const vector2 &a, const vector2 &b);
/** ¿Son diferentes estos dos vector2? */
friend bool operator != (const vector2 &a, const vector2 &b);
/** Resta dos vector2 */
friend vector2 operator - (const vector2 &a, const vector2 &b);
/** Suma dos vector2 */
friend vector2 operator + (const vector2 &a, const vector2 &b);
/** Multiplica un vector2 por un float */
friend vector2 operator * (const vector2 &v, float f);
/** Multiplica un vector2 por un float */
friend vector2 operator * (float f, const vector2 &v);
/** Divide un vector2 entre un float */
friend vector2 operator / (const vector2 &v, float f);
Fernando Garcia Bernal
112
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
/** Niega un vector2 */
friend vector2 operator - (const vector2 &a);
public:
// Métodos
/** Obtiene el tamaño de un vector2 */
float length() const;
/** Normaliza un vector2 */
vector2 &normalize();
/** Gira 90 grafos un vector2 */
vector2 &girar90();
/** Devuelve la distancia entre una coordenada y esta */
float distancia(const vector2 &v);
};
Se observan constructores vacíos, con inicialización de parámetros; operadores de asignación,
adición, resta, multiplicación, división; y métodos para obtener la longitud, normalizar, girar
90 grados el vector y para obtener la distancia de una coordenada a otra.
•
vector3
Las posibilidades que ofrece la clase vector3 son similares a las de la clase vector2. A
continuación se muestra su definición.
/**
* Clase vector3
* Vector de tres componentes (x,y,z) de tipo float. Contiene
* funciones y operadores que facilita trabajar con esta clase
* @author Fernando García Bernal
*/
class vector3
{
public:
// Miembros
float x, y, z;
public:
// Constructores
/** Constructor vacío */
vector3() {};
/** Constructor con inicialización de valores float */
vector3(float inX, float inY, float inZ);
public:
// Operadores
Fernando Garcia Bernal
113
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
/** Asignación */
vector3 &operator =
Implementación
(const vector3 &v);
/** Asignación desde vector2 */
vector3 &operator = (const vector2 &v);
/** Suma un vector3 a este */
vector3 &operator += (const vector3 &v);
/** Resta un vector3 a este */
vector3 &operator -= (const vector3 &v);
/** Multiplica el vector3 por un float */
vector3 &operator *= (float f);
/** Divide el vector3 entre un float */
vector3 &operator /= (float f);
/** ¿Son iguales estos dos vector3? */
friend bool operator == (const vector3 &a, const vector3 &b);
/** ¿Son diferentes estos dos vector3? */
friend bool operator != (const vector3 &a, const vector3 &b);
/** Niega un vector3 */
friend vector3 operator - (const vector3 &a);
/** Suma dos vector3 */
friend vector3 operator + (const vector3 &a, const vector3 &b);
/** Resta un vector3 a otro */
friend vector3 operator - (const vector3 &a, const vector3 &b);
/** Multiplica un vector3 por un float */
friend vector3 operator * (const vector3 &v, float f);
/** Multiplica un vector3 por un float */
friend vector3 operator * (float f, const vector3 &v);
/** Divide un vector3 entre un float */
friend vector3 operator / (const vector3 &v, float f);
public:
// Métodos
/** Obtiene el tamaño de un vector3 */
float length() const;
/** Normaliza un vector3 */
vector3 &normalize();
};
6.2.1.2 glQuaternion.h
Esta librería ha sido implementada para poder trabajar con cuaterniones, pero primero… ¿qué
es un cuaternión? Un cuaternión describe un vector de cuatro dimensiones que generaliza a
Fernando Garcia Bernal
114
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
los números complejos. Hamilto los inventó en el sigo XIX para las rotaciones, antes de que
se inventaran las matrices de rotaciones. Las operaciones con cuaterniones son
computacionalmente más eficaces que las multiplicaciones de matrices 4x4, y se utilizan para
transformaciones y rotaciones. Además, la ventaja está en la animación, ya que interpolar un
cuaternión resulta sencillo, al contrario que interpolar una matriz de rotación que al ser
ortogonal debe garantizar que el interpolante sigua siendo una matriz de rotación ortogonal.
Los cuaterniones agregan un cuarto elemento a los valores (x,y,z) que definen un vector,
generando vectores arbitrarios de cuatro dimensiones. Sin embargo, las fórmulas siguientes
muestran cómo cada elemento de un cuaternión se refiere a una rotación de un ángulo
alrededor de un eje. Por el motivo de la simplificación del coste de computación que supone
trabajar con estos elementos, se opta por su utilización en la mayoría de los desarrollos de
gráficos 3D para tiempo real. El siguiente código muestra la definición de la clase
glQuaternion:
class glQuaternion
{
public:
/**
* Multiplicación de cuaterniones:
* Esta es la operacion mas comun en cuaternions.
* Estas operaciones pueden ser consultadas en la Url inidicada
* en la cabecera anterior.
* @param q cuaternion por el que se quiere multiplicar
*/
glQuaternion operator *(glQuaternion q);
/**
* A partir de un cuaternion generamos una matriz que podemos usar
* para mandar la rotacion en forma de matriz a openGL con el comando
* glMultMatrix()
* @param *pMatrix Puntero a la zona de memoria que se ha reservado
* para llenarse con el resultado de la multiplicacion. La matriz ha
* de ser float[16], si no dara problemas
*/
void CreateMatrix(float *pMatrix);
/**
* Crear un cuaternion a partir de un angulo y tres ejes. Los
* parametros estan dispuestos casi de la misma manera que el comando
* de openGL glRotatef( el angulo va delante en la rotacion de
* openGL). El significado de esta funcion es crear una rotacion.
* Como cada cuaternion representa una rotacion, despues sólo tenemos
* que multiplicar los cuaterniones para crear una rotacion a medida.
* @param x representa el eje x de la rotacion
* @param y representa el eje y de la rotacion
* @param z representa el eje z de la rotacion
Fernando Garcia Bernal
115
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
* @param grados representa el angulo por el que se quiere formar la
* rotacion
*/
void CreateFromAxisAngle(float x, float y, float z, float grados);
/**
* Suma dos cuaternions.
* @param q cuaternion suma
* @return cuaternion que representa la suma del cuaternion q con el
* representado por la clase
*/
glQuaternion operator +(glQuaternion & q);
/**
* Resta dos cuaternions.
* @param q cuaternion suma
* @return cuaternion que representa la resta del cuaternion q con el
* representado por la clase
*/
glQuaternion operator -(glQuaternion & q);
/**
* Contructor de la clase. Resetea los valores internos de la clase
*/
glQuaternion();
/**
* Contructor de la clase
*/
glQuaternion(float x, float y, float z, float w);
virtual ~glQuaternion();
public:
float
float
float
float
m_w;
m_z;
m_y;
m_x;
};
Lo más utilizado de esta librería es la llamada al constructor de cuaterniones a partir de sus
cuatro componentes float y el uso de los operadores +, - y *.
6.2.1.3 Librería lib
Dentro de esta librería se han incluido constantes, las estructuras de datos utilizadas en la
aplicación y varias funciones. Estos elementos no se han podido asociar a una entidad lógica
concreta y por tanto se colocan en esta librería genérica.
•
Constantes
Estas constantes se usan para indicar longitudes máximas:
Fernando Garcia Bernal
116
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
/** usado para el algoritmo A estrella */
#define MAX 1000
/** define la máxima longitud de cadena */
#define MAXCAD 500
•
Tipos de datos
Algunas de estas estructuras ya se han comentado y otras se usarán más adelante:
/** almacena los vértices de una cara */
typedef float vert[3][5];
/** define un material */
typedef struct{
char nombre[MAXCAD];
char fichero[MAXCAD];
int ancho, alto;
int veces;
//
//
//
//
//
//
nombre del material
nombre del fichero donde se almancena
el material
componentes ancho y alto
número de caras en las que se usa el
material
} material;
/** define una animación */
typedef struct{
float
rot[4], // rotación {x,y,z,w} en forma de cuaternion, por
// eso son 4 componentes
sca[3], // escalado {x,y,z}
pos[3]; // posición {x,y,z}
} animacion;
/** define el estado en el que se puede encontrar un objeto */
typedef struct{
float
pos[3], // posición {x,y,z}
rot[3], // rotación {x,y,z}
esc[3]; // escalado {x,y,z}
} estado;
/** Estructura de un nodo usada para el algoritmo A* */
typedef struct TNodo{
int loc;
// número del nodo en carasDesplazables
int indice; // índice de la posición en carasDesplazables
float costFromStart;
// coste desde el inicio hasta este nodo
float costToGoal;
// coste desde este nodo al destino
float totalCost;
// coste total
TNodo *parent;
// nodo padre
} nodo;
•
Funciones
Fernando Garcia Bernal
117
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
Dos funciones genéricas: una para cargar texturas y otra que devuelve el signo (-1, 0 ó 1) de
un valor decimal. Después se encuentran tres funciones usadas en el fichero principal para
inicializar aspectos del juego.
/**
* Carga una textura para después poder vincularla y pintarla
* usando las primitivas correspondientes de openGL
* @param texture puntero que se corresponde con la textura en memoria
* @param identificador al que se asocia la textura
* @param tamaño en ancho
* @param tamaño en alto
*/
void LoadTex(u8* texture, int id, int sizeX, int sizeY);
/**
* Devuelve el signo del float n pasado como parámetro
* @param n valor decimal del que se quiere conocer su signo
* @return devuelve 0 si n es 0.0f, -1 si es menor que 0.0f o 1 si es
* mayor de 0.0f
*/
int signo(float n);
/**
* Función utilizada para inicializar y cargar
* las imágenes de la interfaz de usuario
*/
void inicializarMenu();
/**
* Inicializa la escena 3D
* con las directivas openGL
*/
void inicializaEscena3D();
/**
* Reinicia la escena para que
* pueda cargarse un escenario
*/
void reiniciarEscena();
La implementación de estas funciones es la siguiente:
void LoadTex(u8* texture, int id, int sizeX, int sizeY) {
glBindTexture(GL_TEXTURE_2D, id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, sizeX, sizeY, 0,
TEXGEN_TEXCOORD | GL_TEXTURE_WRAP_S |
GL_TEXTURE_WRAP_T, (u8*)texture);
}
int signo(float n)
Fernando Garcia Bernal
118
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
{
if(n>0.0f)
return 1;
else if(n<0.0f)
return -1;
else
return 0;
}
void inicializarMenu(){
PA_Init();
PA_InitVBL();
PA_Init3D();
PA_InitText(1,0);
PA_EasyBgLoad(1, 3, menu);
PA_LoadSpritePal(
// Pantalla
1,
// Número de paleta
0,
// Nombre de paleta
(void*)selector_Pal);
// Carga el selector de botones
PA_CreateSprite(1, 0, (void*)selector_Sprite, OBJ_SIZE_32X32, 1,
0,
256, 0);
// Carga el selector de elementos del inventario. Se realiza
// dentro de un bucle ya que es un srpite reducido que debe
// repetirse a lo ancho
for(int i=1; i<=3; i++)
{
PA_CreateSprite(1, i, (void*)selectorItem_Sprite,
OBJ_SIZE_32X16, 1, 0,
256, 0);
PA_SetSpritePrio(1, i, 3);
}
}
void inicializaEscena3D(){
glEnable(GL_ANTIALIAS);
// Habilita dibujar el borde, para mostrar el objeto
// seleccionado
glEnable(GL_OUTLINE);
// Establece el color del borde
glSetOutlineColor(0,RGB15(31,31,31));
// Establece la vista a la pantalla completa
glViewPort(0,0,255,191);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(35, 256.0 / 192.0, 0.1, 20);
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
Fernando Garcia Bernal
119
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glMaterialf(GL_AMBIENT, RGB15(31,31,31));
glMaterialf(GL_DIFFUSE, RGB15(31,31,31));
glMaterialf(GL_SPECULAR, BIT(15) | RGB15(31,31,31));
glMaterialf(GL_EMISSION, RGB15(31,31,31));
glMaterialShinyness();
gluLookAt(
// Posición de la cámara
0.0, 0.0, -1.0,
// Look at
0.0, 0.0, 0.0,
// Vector alzado
0.0, 1.0, 0.0);
// Desactiva la iluminación del contorno de las Entidades
glPolyFmt(POLY_ALPHA(31) | POLY_CULL_NONE | POLY_ID(0));
}
void reiniciarEscena()
{
PA_ClearTextBg(1);
// desplaza los sprites selector de
// botones y selector de inventario
// al margen derecho para que no
// se muestren
for(int i=0; i<3; i++)
{
PA_SetSpriteX(1,i+1,255);
}
}
6.2.2 Clase Figura
La clase figura está definida exactamente igual que la que se definió para la generación del
fichero binario que representa al modelo 3D. Así se consigue que se pueda realizar una
asignación desde el puntero que apunta al fichero binario generado, hacia un objeto de la clase
Figura simplemente realizando un casting. Esto se puede ver en el apartado que explica la
clase Objeto3D que es quién se encarga de cargar el objeto desde la memoria. Esta es la
definición:
/**
* Conjunto de vértices y caras que forma una figura.
* Una o varias figuras forman un objeto 3d.
* @author Fernando García Bernal
*/
class Figura{
public:
Fernando Garcia Bernal
120
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
/** nombre de la figura */
char nombre[MAXCAD];
/** número de frames que compone la figura */
int nFrames;
/** número de caras que compone la figura */
int nCaras;
/** número de puntos que compone la figura */
int nPuntos;
/**
* número de materiales ó texturas que compone la figura
*/
int nMateriales;
/** vector con los índices de los materiales */
int *numeroMat;
/** vector con los números de veces que se repite cada material */
int *vecesMat;
/** conjunto de vértices que dibujan a la figura */
float *v;
/** conjunto de frames que componen la animación */
animacion *a;
Figura();
~Figura();
};
6.2.3 Clase Objeto3D
Esta clase también tiene correspondencia con la otra clase que se encontraba en el programa
que generaba el fichero binario a partir del modelo 3D. De nuevo el motivo es evidente, la
carga a partir del fichero de datos se simplifica mucho al compartir la misma clase. La única
diferencia entre las dos clases es su nombre, aquí se llama Objeto3D y en la otra aplicación
se trata de modelo. Su definición en Objeto3D.h es la siguiente:
/**
* La clase Objeto3D es un contenedor de figuras. También posee un
* vector de materiales del conjunto de figuras que lo conforman.
*/
class Objeto3D{
public:
/** nombre del objeto 3D */
char nombre[MAXCAD];
/** número de figuras que componen el objeto */
int nFiguras;
Fernando Garcia Bernal
121
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
/** vector de figuras que componen el objeto */
Figura *f;
/** número de materiales que componen el objeto */
int nMateriales;
/** vector de materiales que componen el objeto */
material *mat;
/** Constructor vacío */
Objeto3D(){
memset(nombre, '\0', MAXCAD);
nFiguras=0;
nMateriales=0;
};
/**
* Constructor con inicialización
* @param _nombre Nombre del archivo binario donde se encuentra
* el objeto 3D
*/
Objeto3D(const void *_nombre){
Objeto3D();
cargarObjeto3D(_nombre);
}
~Objeto3D(){};
/**
* carga el objeto3D
* @param _nombre Nombre del archivo binario donde se encuentra
* el objeto 3D
*/
void cargarObjeto3D(const void *_nombre);
};
El método más importante de esta clase es el que implementa la lógica necesaria para cargar
el fichero binario en un objeto que pueda ser interpretado en la aplicación.
void Objeto3D::cargarObjeto3D(const void *_nombre){
// Se lee el objeto Objeto3D del fichero
Objeto3D *ptr = (Objeto3D*)_nombre;
// Después de cargar el objeto es necesario
// cargar manualmente los datos que están apuntados
// desde un puntero para acceder a las direcciones
// de memoria
sprintf(nombre,"%s",ptr->nombre);
nMateriales = ptr->nMateriales;
nFiguras = ptr->nFiguras;
// se avanza el puntero el tamaño de Objeto3D
ptr=(Objeto3D*)(((int)ptr)+sizeof(Objeto3D));
Fernando Garcia Bernal
122
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
// carga de la lista de materiales
mat=(material *)ptr;
ptr=(Objeto3D*)(((int)ptr)+sizeof(material)*nMateriales);
// crea array de figuras
f=new Figura[nFiguras];
// se carga cada figura
for(int posF=0; posF<nFiguras; posF++)
{
// lee los elementos que no son punteros de Figura
f[posF]=*(Figura *)ptr;
ptr=(Objeto3D*)(((int)ptr)+sizeof(Figura));
// lee los materiales de la figura
f[posF].numeroMat=(int *)ptr;
ptr = (Objeto3D*) (((int)ptr) + sizeof(int) *
f[posF].nMateriales);
f[posF].vecesMat=(int *)ptr;
ptr = (Objeto3D*) (((int)ptr) + sizeof(int) *
f[posF].nMateriales);
// lee los vértices de la figura. El tamaño para avanzar el
// puntero en la figura será el tamaño de float por el
// número de caras multiplicadas por tres (tres vértices
// por cara) y multiplicado por cinco float (coordenadas
// (x,y,z) y de la textura (u,v))
f[posF].v=(float *)ptr;
ptr = (Objeto3D*) (((int)ptr) + sizeof(float) *
f[posF].nCaras*3*5);
// carga las animaciones, tantas como nFrames
f[posF].a=(animacion *)ptr;
ptr = (Objeto3D*) (((int)ptr) + sizeof(animacion) *
f[posF].nFrames);
}
}
6.2.4 Clase Entidad
La clase Entidad representa un objeto que se puede encontrar en el escenario. Extiende de
la clase Objeto3D y a diferencia de esta, la clase Entidad ya confiere al objeto de
propiedades propias de un elemento del juego. Esta tiene un estado que puede ser modificado
durante la partida, definido por una posición, traslación y rotación. También cabe la
posibilidad de que pueda iluminarse su contorno, capacidad que se utiliza cuando el usuario
selecciona el objeto por ejemplo. En un instante del juego puede comenzar a realizar una
animación que se hubiera definido en el diseño del objeto, por tanto dispone de una variable
que representa el frame actual. Además, según las necesidades de la aventura, puede
establecerse que una entidad pueda ser recogida del escenario, con lo cual desaparecería de la
escena y se almacenaría en el inventario del protagonista; o también un objeto puede tener la
Fernando Garcia Bernal
123
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
opción de usarse o combinarse con otros. Todas estas opciones comentadas son modificables
al antojo del diseñador del juego accediendo a las herramientas de este objeto Entidad.
Aquí se muestra la definición de la clase:
/**
* La clase Entidad representa un objeto que se puede encontrar
* en el Escenario. Esta Entidad tiene una posición, un frame,
* puede ser iluminada y puede recogerse o usarse según el
* estado de la partida
*/
class Entidad : public Objeto3D
{
public:
/** vector con los id en los cuales se vincula las texturas */
int *texId;
/** estado Actual */
estado instante;
/** frame Actual */
int fr;
/** número total de frames que compone la entidad */
int nFrames;
/** indica si iluminar el objeto. Usado para testear */
bool iluminar;
/** tamaño de la textura, por defecto es 128 */
int tamanoText;
/** cara del suelo más cercana a la entidad, dónde se
desplazará el protagonista para alcanzar la entidad */
int caraMasCercana;
/** indica si el objeto se puede recoger */
bool puedeRecogerse;
/** variable de uso opcional para condicionar el uso de una
entidad dentro del método virtual usar() */
bool puedeUsarse;
/** mensaje que se muestra cuando no se puede recoger la figura.
No es obligatorio */
char mensajeNoRecoger[50];
/** mensaje que se muestra cuando no se puede usar la figura. No
es obligatorio */
char mensajeNoUsar[50];
Entidad(){};
/**
* Constructor que recibe el nombre de la figura y la textura
* que le corresponde. El tamaño de la textura se toma por
* defecto como 128
* @param _nombre nombre del archivo binario donde se encuentra
* la Entidad
* @param _textura nombre de la textura
*/
Entidad(const void * _nombre, const void * _textura);
/**
* Constructor que recibe el nombre de la figura, la textura que
* le corresponde y el tamaño de esta
Fernando Garcia Bernal
124
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
* @param _nombre nombre del archivo binario donde se encuentra
* la Entidad
* @param _textura nombre de la textura
* @param tamanoText tamaño de la textura
*/
Entidad(const void * _nombre, const void * _textura, int
tamanoText);
virtual ~Entidad(){};
/** Pinta la Entidad en la escena */
void pintarGL();
/**
* Devuelve la posición de la cara más cercana del
* escenario para poder ubicar donde se encuentra la entidad
* @return cara más cercana del escenario
*/
int situacionEntidad();
private:
/**
* inicializa las texturas
* @param _textura nombre de la textura que se asocia a la
* Entidad
*/
void inicializar(const void * _textura);
/**
* Carga las texturas. Es llamado desde inicializar
* @param texturas nombre de la textura que se asocia a la
* Entidad
* @see inicializar
*/
void cargarTexturas(const void* texturas);
/**
* Vincula la textura con su id a la hora de pintar la Entidad
* en la escena
* @param nMat id de la textura de la Entidad que se va a pintar
*/
void vincularTextura(int nMat);
};
6.2.5 Clase Personaje
La clase Personaje es una especialización de la clase Entidad. Realmente no aporta
ninguna característica propia a su clase padre, simplemente se ha creado para poder definir
que en los métodos que así lo requieran, que acepten una clase de tipo Personaje en lugar de
una entidad cualquiera. Este es el caso de un método que se verá en el siguiente apartado y
que pertenece a la clase Escenario, requiere que la entidad que se vincule al escenario sea
Fernando Garcia Bernal
125
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
un Personaje ya que sólo puede existir una clase de este tipo. Definición de la clase
Personaje:
/**
* Clase Personaje. Sólo puede haber una por Escenario ya que
* sólo puede manejarse un protagonista. El resto de elementos
* que componen la escena son de tipo Entidad
*/
class Personaje : public Entidad
{
public:
/**
* Constructor que recibe el nombre de la figura y la textura
* que le corresponde. El tamaño de la textura se toma por
* defecto como 128
* @param _nombre nombre del archivo binario donde se encuentra
* la Entidad
* @param _textura nombre de la textura
*/
Personaje (const void * _nombre, const void * _textura) :
Entidad(_nombre, _textura){};
Personaje (){};
~Personaje (){};
};
6.2.6 Clase Escenario
Esta puede considerarse la clase más importante de la aplicación. Desde aquí se coordinan
todos los elementos que componen el juego. Al escenario se vincula el Personaje, el suelo y
las entidades seleccionables y no seleccionables por el usuario. Por tanto, gracias a que esta
clase tiene visibilidad sobre todos los elementos, se convierte en el centro y nexo. Posee un
método actualizar que hace las funciones de bucle de juego. Esto se refiere al bucle que
implementan la mayoría de los juegos donde se realizan las acciones rutinarias básicas:
lectura de eventos, actualización de componentes y muestra de información al usuario. A
continuación se muestra la definición de esta clase, y posteriormente se pasan a explicar las
funciones que incorpora con mayor detalle.
/**
* Clase que representa un escenario completo.
* Desde esta clase se coordinan todos los elementos que componen la
* escena: un suelo, un personaje y cero o más elementos.
* Primero se actualizan los eventos hardware, después se realizan las
* acciones oportunas para que finalmente se actualice la escena.
Fernando Garcia Bernal
126
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
*/
class Escenario
{
public:
/** Entidad que representa el suelo del escenario */
Entidad Suelo;
/**
* Entidad que contiene al personaje que el usuario
* manejará
*/
Personaje Protagonista;
/**
* Vector de entidades seleccionables que conforman la
* totalidad del escenario junto con el suelo, el personaje y
* los elementos no seleccionables
*/
vector<Entidad*> elementosSeleccionables;
/**
* Vector de entidades no seleccionables que conforman la
* totalidad del escenario junto con el suelo, el personaje y
* los elemento seleccionables
*/
vector<Entidad> elementosNoSeleccionables;
/**
* Vector de entidades que corresponde con el inventario
* recogido por el protagonista
*/
vector<Entidad> inventario;
/**
* Determinado el estado actual del juego. Pueden ser:
* ESTADO_PARADO, ESTADO_DESPLAZAMIENTO
* ESTADO_ANIMACION
*/
int estado;
/**
* Acción del menú seleccionada. Pueden ser:
* ACCION_IR
* ACCION_RECOGER
* ACCION_USAR
*/
int accion;
/** indica el elemento seleccionado del inventario */
int seleccionInventario;
/**
* Mensaje de información a mostrar en la pantalla
* superior
*/
char mensajePantalla[30];
/**
Fernando Garcia Bernal
127
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
* Tiempo que se mostrará el mensaje por pantalla (Por
* defecto -1)
*/
int tiempoMensajePantalla;
/** Entidad que se animará a consecuencia de un evento */
Entidad *entidadEnAnimacion;
/**
* indica el fin de la animación
* @see entidadEnAnimacion
*/
int finAnimacion;
/**
* vector de cadenas que se utiliza para que cada
* Escenario ponga los textos que debe mostrar al
* usuario
* @see mostrarTextoPantalla
*/
vector<string> mensajes;
private:
/**
* Subconjunto de las caras que componen el suelo, de las cuales
* el personaje se puede desplazar por encima suya. Por
* convenio, son estas las caras que tienen sus tres
* vértices a la misma altura y están por encima
* del plano Y=0
*/
vector<int> carasDesplazables;
/** Propiedades del suelo para su correcta visualización */
float inclinacion, distancia, altura;
/**
* Array de arrays que por cada cara desplazable contiene una
* lista de las caras desplazables que son contiguas a esta.
* cont[caraDesplazable_i] = lista de caras contiguas
*/
vector<int> *cont;
/**
* Variable global establecida por la función zonaPulsada.
* Contiene la cara de suelo o elemento pulsado.
* @see zonaPulsada()
*/
int clicked;
/**
* Variable global usada por la función zonaPulsada.
* Distancia más cercana a la cámara.
* @see zonaPulsada()
*/
int closeW;
/**
* Variable global usada por la función zonaPulsada.
* Guarda el número de polígonos dibujados
Fernando Garcia Bernal
128
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
* @see zonaPulsada()
*/
int polyCount;
/**
* Variables globales usadas por la función zonaPulsada.
* Guarda la posición pulsada.
* @see zonaPulsada()
*/
int touchX, touchY;
/**
* Variables globales usadas por la función zonaPulsada.
* usado por gluPickMatrix()
* @see zonaPulsada()
*/
int *viewport;
/**
* Camino a recorrer por el personaje durante el desplazamiento
*/
vector<int> camino;
/** velocidad de desplazamiento del personaje por frame */
float velocidadDesplazamiento;
/** entidad que se desplaza a recoger */
int entidadSeleccionada;
/**
* Cámara: posición(x,y,z), lookat(x,y,z), vector alzado(x,y,z)
*/
float cam[3][3];
public:
/**
* Constructor
* @param _suelo datos binarios que contiene los datos de la
* Entidad Suelo
* @param _textura datos binarios que contiene la textura del
* suelo
* @param _tamanoText tamaño de la textura
* @param _inc grados de inclinación del suelo
* @param _dist distancia de representación en el eje Z
* @param _alt altura de representación en el eje Y
*/
Escenario(const void * _suelo, const void * _textura, int
_tamanoText, float _inc, float _dist, float _alt);
virtual ~Escenario(){};
/**
* Vincula una Entidad Personaje al escenario que tomará el
* papel de personaje de la escena,
* controlado por el usuario
* @param _personaje Entidad personaje
* @param x Posición x donde aparece el Personaje
* @param y Posición y donde aparece el Personaje
* @param z Posición z donde aparece el Personaje
Fernando Garcia Bernal
129
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
*/
void vincularProtagonista(Personaje *_personaje, float x, float
y, float z);
/**
* Vincula una Entidad seleccionable al escenario.
* @param _elementoS Entidad elemento seleccionable
* @param x Posición x donde aparece la Entidad
* @param y Posición y donde aparece la Entidad
* @param z Posición z donde aparece la Entidad
*/
void vincularElementoSeleccionable(Entidad *_elementoS, float x,
float y, float z);
/**
* Desincula una Entidad seleccionable al escenario.
* @param nombre nombre de la Entidad elemento seleccionable
*/
void desvincularElementoSeleccionable(char *nombre);
/**
* Vincula una Entidad no seleccionable al escenario.
* @param _elementoNoS Entidad elemento no seleccionable
* @param x Posición x donde aparece la Entidad
* @param y Posición y donde aparece la Entidad
* @param z Posición z donde aparece la Entidad
*/
void vincularElementoNoSeleccionable(Entidad *_elementoNoS,
float x, float y, float z);
/**
* Según el estado del juego realiza las acciones oportunas.
* Estas son: actualizar los eventos hardware, actualizar la
* acción
* que realiza el Personaje, actualizar el estado de la escena y
* actualizar los textos que se muestran en la pantalla.
* @return devuelve true si el Escenario ha llegado a su fin,
* false si todavía no ha finalizado
*/
bool actualizar();
/**
* Devuelve la cara sobre la que se encuentra el personaje
* @return cara del Suelo donde se encuentra el personaje
*/
int situacionPersonaje();
/**
* Devuelve el camino que hay entre las dos caras del Suelo
* pasadas como parámetros
* @param ini Origen del camino
* @param dest Destino del camino
* @see buscarCamino
* @return vector de caras del Suelo que forman el camino
*/
vector<int> devuelveCamino(int ini, int dest);
/**
* Indica si el protagonista tiene cierta Entidad
* en su inventario
Fernando Garcia Bernal
130
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
* @param nombre nombre de la Entidad a comprobar
* @return true si se encuentra la entidad en el inventario
* false en caso contrario
*/
bool poseeEnInventario(char *nombre);
/**
* Con este método se pinta sobre
* la pantalla mensajes en las
* transiciones entre un escenario y otro
* @param inicio primer mensaje a mostrar
* @param fin último mensaje a mostrar
*/
void mostrarTextoPantalla(int inicio, int fin);
private:
/**
* Actualiza los eventos que provocan el hardware de la
* videoconsola. Comprueba qué botones se han pulsado y si el
* usuario ha pulsado algo con el Stylus
* @param elementoPulsado devuelve el identificador del elemento
* que se ha pulsado. -1 si no se ha pulsado nada
* @param tipoElementoPulsado devuelve el tipo de elemento que
* se ha pulsado. -1 si no se ha pulsado nada
*/
void actualizarEventos(int *elementoPulsado, int
*tipoElementoPulsado);
/**
* Actualiza la acción que está realizando el personaje en
* función de los eventos que hayan sucedido y el transcurso de
* la partida
* @param elementoPulsado devuelve el identificador del elemento
* que se ha pulsado. -1 si no se ha pulsado nada
* @param tipoElementoPulsado devuelve el tipo de elemento que
* se ha pulsado. -1 si no se ha pulsado nada
*/
void actualizarAccion(int *elementoPulsado, int
*tipoElementoPulsado);
/**
* Actualiza el estado de la escena en función de la acción y
* los eventos sucedidos
* @param elementoPulsado devuelve el identificador del elemento
* que se ha pulsado. -1 si no se ha pulsado nada
* @param tipoElementoPulsado devuelve el tipo de elemento que
* se ha pulsado. -1 si no se ha pulsado nada
* @return devuelve true si el Escenario ha llegado a su fin,
* false si todavía no ha finalizado
*/
bool actualizarEstado(int *elementoPulsado, int
*tipoElementoPulsado);
/**
* Actualiza los textos que se muestran en la pantalla para
* informar al usuario del estado actual de las acciones que
* toma
* @param elementoPulsado devuelve el identificador del elemento
* que se ha pulsado. -1 si no se ha pulsado nada
Fernando Garcia Bernal
131
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
* @param tipoElementoPulsado devuelve el tipo de elemento que
* se ha pulsado. -1 si no se ha pulsado nada
*/
void actualizarTextos(int *elementoPulsado, int
*tipoElementoPulsado);
/**
* Función invocada en actualizarEventos cuando se realiza una
* pulsación con el stylus que puede desembocar
* en un nuevo desplazamiento o en la acción sobre un objeto
* @param elementoPulsado devuelve el identificador del elemento
* que se ha pulsado. -1 si no se ha pulsado nada
* @param tipoElementoPulsado devuelve el tipo de elemento que
* se ha pulsado. -1 si no se ha pulsado nada
* @see actualizarEventos
*/
void actualizarStylus(int *elementoPulsado, int
*tipoElementoPulsado);
/**
* Comprueba los botones pulsados y actualiza el estado
* @see actualizarEventos
*/
void actualizarBotones();
/**
* Pinta todo el escenario: suelo, personaje y elementos.
*/
void pintarGL(){
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0f,altura,distancia);
glRotateX(inclinacion);
glPushMatrix();
gluLookAt(
cam[0][0], cam[0][1], cam[0][2], // posición cámara
cam[1][0], cam[1][1], cam[1][2],
// punto de mira
cam[2][0], cam[2][1], cam[2][2]);
// vector alzado
Suelo.pintarGL();
Protagonista.pintarGL();
for(unsigned int
i=0;i<elementosNoSeleccionables.size();i++){
elementosNoSeleccionables[i].pintarGL();
}
for(unsigned int i=0;i<elementosSeleccionables.size();i++){
elementosSeleccionables[i]->pintarGL();
}
glPopMatrix(0);
glFlush(0);
}
Fernando Garcia Bernal
132
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
/**
* Pasada una coordenada (x,y) devuelve la cara de Suelo pulsada
* @param x coordenada eje x
* @param y coordenada eje y
* @param tipo tipo de elemento pulsado, puede ser SUELO o
* ENTIDAD
* @see startCheck()
* @see endCheck()
* @return cara del escenario pulsada con el Stylus
*/
int zonaPulsada(int x, int y, int *tipo);
/**
* Función usada por zonaPulsada.
* Ejecutada antes de dibujar un objeto durante el recorte.
* Guarda en una variable global el número de polígonos
* dibujados hasta el momento.
* @see zonaPulsada()
* @see endCheck()
*/
void startCheck();
/**
* Función usada por zonaPulsada.
* Ejecutado después de dibujar un objeto durante el recorte.
* Comprueba si el número de polígonos ha aumentado desde la
* llamada a startCheck, sabiendo entonces que en el área
* indicada por zonaPulsada había una cara.
* @see zonaPulsada()
* @see startCheck()
*/
void endCheck(int obj);
/**
* Función usada en el constructor Escenario para detectar las
* áreas contiguas. Se le pasa una coordenada (x,y) en un vector
* v y comprueba si está contenida dentro de alguna de las caras
* desplazables del suelo.
* @param v vector con coordenada a comprobar si pertenece a
* alguna cara desplazable
* @see Escenario()
* @return cara donde está contenido el vector v
*/
int perteneceArea(vector2 v);
/**
* Función usada en el constructor Escenario para detectar las
* áreas contiguas. Se le pasa una coordenada (x,y) en un vector
* v y comprueba si está contenida dentro de alguna de las caras
* desplazables del suelo.
* @param x coordenada x a comprobar si pertenece a álguna cara
* desplazable
* @param y coordenada y a comprobar si pertenece a álguna cara
* desplazable
* @see Escenario()
* @return cara donde está contenido el vector v
*/
int perteneceArea(float x, float y){
return perteneceArea(vector2(x,y));
}
Fernando Garcia Bernal
133
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
/**
* Función usada en el constructor de Escenario.
* Comprueba que el elemento x está en el vector v
* @param x elemento a comprobar que existe en v
* @param v vector de elemento
* @return si existe devuelve la posición del elemento x en v,
* sino -1
*/
template<class T>
int esta(T x, vector<T> v);
/**
* Busca el camino entre un índice inicio y otro destino del
* suelo. Este algoritmo devuelve el primer camino que encuentra
* entre dos puntos
* @param indIni índice del nodo inicio
* @param indDest índice del nodo destino
* @param c camino actual
* @see devuelveCamino
* @see buscarCaminoAEstrella_Original
* @see buscarCaminoAEstrella_ModificadoOptimo
* @return variable booleana que indica si se ha encontrado el
* camino
*/
bool buscarCamino(int indIni, int indDest, vector<int>* c);
/**
* Busca el camino entre un índice inicio y otro destino del
* suelo. Este algoritmo devuelve el primer camino que encuentra
* entre dos puntos usando la heurísitica del camino euclídeo
* entre dos puntos
* @param indIni índice del nodo inicio
* @param indDest índice del nodo destino
* @param fin camino actual
* @see devuelveCamino
* @see buscarCamino
* @see buscarCaminoAEstrella_ModificadoOptimo
*/
void buscarCaminoAEstrella_Original(int indIni, int indDest,
vector<int>* fin);
/**
* Busca el camino entre un índice inicio y otro destino del
* suelo. Este algoritmo es una modificación del algoritmo A*
* que devuelve el camino más corto entre dos puntos
* @param indIni índice del nodo inicio
* @param indDest índice del nodo destino
* @param fin camino actual
* @see devuelveCamino
* @see buscarCaminoAEstrella_Original
* @see buscarCamino
*/
void buscarCaminoAEstrella_ModificadoOptimo(int indIni, int
indDest, vector<int>* fin);
/**
* Función que determina el coste entre dos nodos del suelo.
* Es parte del algoritmo A*
* @param startLoc localización de inicio
Fernando Garcia Bernal
134
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
* @param goalLoc localización objetivo
* @see buscarCaminoAEstrella_Original
* @return coste entre dos puntos
*/
float PathCostEstimate(int startLoc, int goalLoc);
/**
* Función auxiliar para dado un vector de nodos y un
* identificados devuelve el nodo que tiene ese id
* @param nodos vector de nodos
* @param idNodo identificador del nodo a buscar
* @return puntero al nodo encontrado
*/
nodo* encuentraNodoPorId(vector<TNodo*> *nodos, int idNodo);
/**
* Función auxiliar del algoritmo A*
* Inserta un nodo n en el vector de nodos v
* @param nodos vector de nodos necesario para la consultad del
* id del nodo
* @param n nodo que se desea insertar
* @param v vector donde insertar el nodo en orden
*/
void insertar_en_orden(vector<TNodo*> *nodos, nodo
*n,vector<int> *v);
/**
* Devuelve el índice correspondiente a la posición de la
* caraDesplazable pasada por parámetro
* @param caraDesplazable cara desplazable del Escenario
* @return índice de la cara desplazable sobre el array
* carasDesplazables
*/
int devuelveIndice(int caraDesplazable);
/**
* Devuelve la coordenada del centro de la cara desplazable
* @param cara identificador de la cara que se desea conocer
* su centro
* @return coordenada (x,y) con el centro de la cara
*/
vector2 devuelveCentroArea(int cara);
/**
* Acción ejecutada al usar un elemento en la partida
* @param obj1 objeto usado
* @param obj2 (opcional) objeto sobre el que se usa el objeto
* obj1. Si este es NULL entonces se habrá usado solamente el
* obj1
* @return devuelve true si usar este objeto implica finalizar
* el escenario false en caso contrario
*/
virtual bool usar(Entidad *obj1, Entidad *obj2) = 0;
/**
* Acción ejecutada al recoger un elemento en la partida
* @param obj1 objeto a recoger
* @return devuelve true si usar este objeto implica finalizar
* el escenario false en caso contrario
*/
Fernando Garcia Bernal
135
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
virtual bool recoger(Entidad *obj1) = 0;
/**
* Esta función es invocada cuando se finaliza una animación
* para realizar los eventos que puedan ocurrir posteriormente
* @param obj1 objeto que ha finalizado su animación
* @return devuelve true si usar este objeto implica finalizar
* el escenario false en caso contrario
*/
virtual bool eventoFinalizarAnimacion(Entidad *obj1) = 0;
};
6.2.6.1 Constructor Escenario
Como ya se ha comentado, el escenario será el objeto que coordine los diferentes
componentes que forman la partida. Recordemos la signatura del constructor:
/**
* Constructor
* @param _suelo datos binarios que contiene los datos de la
* Entidad Suelo
* @param _textura datos binarios que contiene la textura del
* suelo
* @param _tamanoText tamaño de la textura
* @param _inc grados de inclinación del suelo
* @param _dist distancia de representación en el eje Z
* @param _alt altura de representación en el eje Y
*/
Escenario(const void * _suelo, const void * _textura, int
_tamanoText, float _inc, float _dist, float _alt);
Al crear este objeto se pide especificar el suelo. Hasta ahora no se ha explicado cómo se
define un suelo. Este componente consiste en un objeto en 3D como ya se ha visto que se
puede crear, pero con algunas particularidades. A la hora de definir este objeto se deben
cumplir los siguientes requisitos:
•
La superficie puede tener la forma que se desee, por ejemplo en la figura 29 presenta una
forma serpenteante, pero debe estar compuesta por dos partes planas y paralelas, una
superior que será sobre la que se desplace el protagonista, y una inferior que nunca se verá
ya que será la parte trasera del suelo.
•
La cara superior debe estar por encima del plano Y=0, es decir que tengan la
componente Y de altura positiva, y todos los puntos que formen el plano horizontal
superior deben estar a la misma altura.
Fernando Garcia Bernal
136
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
•
Implementación
La cara inferior debe estar por debajo del plano Y=0, es decir que tengan la componente
Y de altura negativa, y todos los puntos que formen el plano horizontal inferior deben
estar a la misma altura.
•
Las caras deben formar triángulos.
La imagen siguiente muestra un ejemplo de modelo 3D que sirve como suelo por cumplir las
condiciones.
29 - Vista pespectiva de un suelo.
30 - Vista frontal del mismo suelo.
Los demás componentes que necesita el constructor son la textura del suelo, y tres parámetros
decimales que indican la inclinación, la distancia y la altura con las que se debe mostrar el
escenario.
Fernando Garcia Bernal
137
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
La parte más importante dentro del constructor es la de interpretar la disposición del suelo,
distinguir las zonas que son desplazables por el protagonista y obtener para cada cara
desplazable una lista de sus caras contiguas. Con esta técnica esta aplicación podrá utilizar
cualquier suelo que cumpla las reglas antes descritas, de ahí su importancia. Iremos viendo
paso a paso que acciones realiza esta tarea de constructor. Primero inicializa las variables
oportunas:
// Inicialización
estado=ESTADO_PARADO;
accion = ACCION_IR;
entidadSeleccionada = -1;
tiempoMensajePantalla = -1;
seleccionInventario = -1;
cam[0][0] = cam[0][1] = .0f; cam[0][2] = -.5f; //posición
cam[1][0] = cam[1][1] = cam[1][2] = .0f; //look at
cam[2][0] = .0f; cam[2][1] = 1.0f; cam[2][2] = .0f; //vector
alzado
inclinacion = _inc;
distancia = _dist;
altura = _alt;
// Se establecen los valores de la variable viewport
viewport = (int*)malloc(sizeof(int)*4);
viewport[0]=0; viewport[1]=0; viewport[2]=255; viewport[3]=191;
// crea la Entidad suelo
Suelo = Entidad(_suelo, _textura, _tamanoText);
El siguiente paso es identificar las caras por las que se desplaza el personaje protagonista:
// Almaceno en carasDesplazables las caras cuyos tres
// vértices tienen su componente Y por encima de 0.0.
// Esto se debe a que durante el diseño de los suelos hay
// que dejar las caras por las que desplaza el personaje por
// encima de 0.0. Así se pueden distinguir las caras
// desplazables de las caras ocultas.
vert *vaux=(vert *)Suelo.f[0].v;
for(int c=0; c<Suelo.f[0].nCaras; c++){
if(vaux[c][0][3] > 0.0f && vaux[c][1][3] > 0.0f &&
vaux[c][2][3] > 0.0f){
carasDesplazables.push_back(c);
}
}
Fernando Garcia Bernal
138
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Para
acceder
a
los
vértices
se
observan
llamadas
Implementación
como:
vaux[c][0][3],
vaux[c][1][3] ó vaux[c][2][3]. Esto se debe a la distribución de los vértices. La
variable vaux es un array multidimensional de esta manera: vaux[caras][vértice de
la cara][componente (u,v,x,y,z)]. Recordar que las componentes (u,v) se
refieren a la correspondencia entre el modelo 3D y la textura. De esta manera, si se accede a:
vaux[c][0][3], vaux[c][1][3] ó vaux[c][2][3], se está devolviendo las tres
componentes Y (altura) de los tres vértices que forman la cara c. Como se ve en el código, si
los tres vértices de una misma cara, tienen su componente Y por encima de 0, entonces se
considera que es una cara por la que se puede desplazar el personaje. Lo siguiente que se
calcula son las caras contiguas a cada cara, esto será necesario para cuando sea necesario
calcular el camino entre dos puntos. Para realizar estos cálculos se hace uso de la clase
vector2 para facilitar las operaciones trigonométricas, y de la función auxiliar
perteneceArea que dada una coordenada (x,y) devuelve la cara desplazable a la que
pertenece. A continuación su implementación:
int Escenario::perteneceArea(vector2 v){
float x = v.x;
float y = v.y;
int cara = -1;
vert *vaux=(vert *)Suelo.f[0].v;
for(unsigned int i=0; i<carasDesplazables.size(); i++)
{
int c = carasDesplazables[i];
// Los 3 vértices que componen la cara
vector2 v0(vaux[c][0][2], vaux[c][0][4]);
vector2 v1(vaux[c][1][2], vaux[c][1][4]);
vector2 v2(vaux[c][2][2], vaux[c][2][4]);
int l1, l2, l3;
l1=signo((v1.x-v0.x)*(y-v0.y)-(v1.y-v0.y)*(x-v0.x));
l2=signo((v2.x-v1.x)*(y-v1.y)-(v2.y-v1.y)*(x-v1.x));
l3=signo((v0.x-v2.x)*(y-v2.y)-(v0.y-v2.y)*(x-v2.x));
if (l1==l3 && l1==l2 && l2==l3)
cara = c;
}
return cara;
}
Fernando Garcia Bernal
139
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
El parámetro que recibe es una coordenada (x,y) que se consulta en el listado de caras
desplazables creado anteriormente. Si dicha coordenada está contenida dentro de alguna cara.
Se recuera que todas las caras deben ser triángulos (se trataba de un requisito que debía
cumplir el suelo), entonces por cada cara se rescatan los tres vértices (x,y) que la componen.
En las variables l1, l2, l3 se calcula en qué lado se encuentra el punto que se quiere
consultar con respecto a los tres vectores que se pueden formar restando los tres vértices de la
cara uno a uno. La única posibilidad que existe de que la coordenada que se busca, se
encuentre al mismo lado de cada uno de estos vectores, es cuando la coordenada está
contenida dentro del triángulo que forma la cara. Vista esta función auxiliar, se retoma la
explicación del método constructor que se iba a encargar de recoger las caras contiguas:
// Calculo las caras contiguas a cada cara
cont = (vector<int>*) malloc(sizeof(vector<int>)
*carasDesplazables.size());
for(unsigned int i=0; i<carasDesplazables.size(); i++)
cont[i] = vector<int>();
int c=-1;
vector<int> listaContiguas;
for(unsigned int i=0; i<carasDesplazables.size(); i++){
c=carasDesplazables[i];
listaContiguas.clear();
// Compruebo si a cada uno de los 3 lados de la cara c hay
// una cara contigua
for(int l=0; l<3; l++){
//Vértices que componen el lado AB
vector2 v1(vaux[c][l][2], vaux[c][l][4]);
vector2 v2(vaux[c][(l+1)%3][2], vaux[c][(l+1)%3][4]);
//Calculo el punto medio del lado
vector2 pm = v1*0.5+v2*0.5;
//Calculo el vector director del segmento AB y lo
// normalizo
vector2 vd=v1-v2;
vd.normalize();
vd/=20.0f;
//Giro 90º el vector director
vd.girar90();
// Compruebo si hay un área contigua en el
// exterior, junto al punto medio, en direccion al
// nuevo vector director
int areaContigua=perteneceArea(pm-vd);
if(areaContigua!=-1 && areaContigua!=int(c))
{
Fernando Garcia Bernal
140
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
if(esta(areaContigua, listaContiguas)==-1)
listaContiguas.push_back(areaContigua);
//Puede ser que la figura contigua no
// esté en contacto con esta figura en
// su punto medio por tanto compruebo si la
// otra figura tiene a esta como contigua y en
// caso contrario la añado
if(cont[i].size()>0 && esta(int(c),
cont[i])==-1)
cont[i].push_back(c);
}
}
cont[i]=vector<int>(listaContiguas);
}
6.2.6.2 Ciclo de juego
Más adelante se explica cómo debe ser el fichero que debe crear toda la estructura de clases
para poder ejecutar esta aplicación, sin embargo es conveniente para poder explicar este punto
observar un trozo de fichero que se encarga de hacer que el juego se esté ejecutando
continuamente. En cierta parte del fichero main.cpp debe aparecer este código:
while (!Escena->actualizar())
{
PA_WaitForVBL();
}
Este es el bucle de juego principal. Se trata de un bucle while que llama continuamente al
método actualizar de la clase Escenario, ya que Escena es una instancia de
Escenario. Mientras el resultado de actualizar no devuelva un valor false, el
escenario continúa ejecutando el ciclo. Visto esto, pasamos a explicar el contenido del método
actualizar del la clase Escenario. La idea es que en este método se atienden todas las
necesidades del ciclo de vida del juego. Veamos primero la implementación:
bool Escenario::actualizar()
{
// limpia la pantalla
PA_ClearTextBg(1);
// inicializa variables
int elementoPulsado[1], tipoElementoPulsado[1];
bool finEscenario = false;
Fernando Garcia Bernal
141
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
// Si está animándose alguna Entidad, entonces no se atiende
// a los posibles eventos que se puedan ejecutar
if(estado == ESTADO_ANIMACION)
{
entidadEnAnimacion->fr++;
if(entidadEnAnimacion->fr >= finAnimacion-1)
{
// Si termina la animación..
finEscenario =
eventoFinalizarAnimacion(entidadEnAnimacion);
entidadEnAnimacion = NULL;
}
}
else
{
*elementoPulsado = *tipoElementoPulsado = -1;
actualizarEventos(elementoPulsado, tipoElementoPulsado);
actualizarAccion(elementoPulsado, tipoElementoPulsado);
finEscenario = actualizarEstado(elementoPulsado,
tipoElementoPulsado);
actualizarTextos(elementoPulsado, tipoElementoPulsado);
}
pintarGL();
return finEscenario;
}
Como siempre empieza inicializando las posibles variables que son necesarias, además de
limpiar los mensajes que se muestran por pantalla. Podemos ver tres variables:
elementoPulsado, tipoElementoPulsado y finEscenario. Las dos primeras se
usan para conocer si el usuario ha utilizado el lápiz o Stylus para pulsar sobre la pantalla. La
otra variable booleana será la que se devuelva al final del método para indicar si el escenario
ha finalizado, y debe pasar entonces al siguiente escenario o bien finalizar el juego. Tras esto
se distinguen dos posibles situaciones: que en este instante alguna entidad esté realizando una
animación, por ejemplo que se esté desplazando una mesa. Si esto ocurre, no pueden
atenderse eventos de ningún tipo hasta que no finalice la animación. Cuando esto sucede se
observa que se llama al método eventoFinalizarAnimacion que en la definición de
esta clase se declaró como un método virtual. Esto se debe a que las actuaciones que se
puedan tomar después de finalizar una animación, serán diferentes dependiendo de en qué
escenario se encuentra. Por ejemplo, si estamos en la primera pantalla de la habitación,
Fernando Garcia Bernal
142
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
cuando se termine la animación de desplazar una mesa, se deberá desbloquear ciertos objetos;
mientras que si estamos en otra fase distinta, cuando se termine la animación de otro objeto
diferente las actuaciones no tendrán nada que ver. Por tanto, con esto se deja entender que la
clase Escenario que estamos explicando sólo se encargará de aspectos genéricos para
todos los juegos, las partes específicas de cada aventura deben ser implementadas en clases
que hereden de las existentes e implementen los métodos virtuales de manera oportuna. En el
punto 6.2.7 se atienden a este tipo de clases y se explica cómo implementar estos métodos
específicos entre los que se encuentra eventoFinalizarAnimacion. Por tanto, la otra
bifurcación del código sucede cuando no se está realizando ninguna animación. En este caso
si se atienden a los posibles eventos y se realiza el ciclo de juego de manera normal. En
primer lugar, se llama al método actualizarEventos que lee cualquier posible evento
que haya podido lanzar el usuario. Este método llama a dos métodos a su vez:
actualizarBotones y actualizarStylus. La implementación del primero se
muestra a continuación:
void Escenario::actualizarBotones(){
if(Pad.Held.Y || Pad.Held.A || Pad.Held.B)
{
// Si se ha pulsado algo...
entidadSeleccionada = -1;
tiempoMensajePantalla = -1;
// Si la acción en la que se encontraba era USAR
// se desmarca el inventario
if(accion == ACCION_USAR)
{
seleccionInventario = -1;
for(int i=0; i<3; i++)
{
PA_SetSpriteXY(1,i+1,255,0);
}
}
}
// Se cambia de acción si ha pulsado algún botón
if(Pad.Held.Y){
accion = ACCION_IR;
}
else if(Pad.Held.A){
accion = ACCION_USAR;
}
else if(Pad.Held.B){
accion = ACCION_RECOGER;
}
Fernando Garcia Bernal
143
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
// Se gira la cámara si ha pulsado
// los botones respectivos
if(Pad.Held.R && cam[0][0] < 1.5f)
cam[0][0]+=.5f;
else if(Pad.Held.L && cam[0][0] > -1.5f)
cam[0][0]-=.5f;
}
En primer lugar se realiza una acción para mantener la consistencia, y es que si estaba pulsada
la acción usar, entonces el usuario podía haber dejando marcado algún objeto del inventario,
por tanto si cambia a otro tipo de acción por ejemplo recoger o ir a, debe dejar de estar
seleccionado algún elemento del inventario. Después se comprueba si se ha pulsado algún
botón del teclado para cambiar a la acción correspondiente. Por último si la acción que se ha
pulsado era la de girar la cámara, se actúa en consecuencia girándola dentro de los límites
posibles. Tras este método, actualizarEventos llama a otro, actualizarStylus.
Primero la implementación de este método:
void Escenario::actualizarStylus(int *elementoPulsado, int
*tipoElementoPulsado){
if(Stylus.Held){
entidadSeleccionada = -1;
tiempoMensajePantalla = -1;
// zonaPulsada comprueba qué se ha pulsado a partir de la
// pulsación del Stylus
*elementoPulsado = zonaPulsada(Stylus.X, Stylus.Y,
tipoElementoPulsado);
}
}
Se comprueba que el Stylus ha sido pulsado con Stylus.Held, en tal caso se hace una
llamada a zonaPulsada pasándole la posición (x,y) sobre la que está presionando el
puntero, además de un puntero y otro que devuelve. Con esto se consigue conocer qué tipo de
elemento se ha pulsado y su identificador para poder reconocerlo cuando sea necesario. Los
tipos de elementos están definidos en constantes y pueden ser:
// Tipo elemento seleccionado
/**
* Se devuelve esta constante cuando el usuario pulse con el puntero
* sobre el suelo
* @see actualizarStylus
*/
Fernando Garcia Bernal
144
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
#define SELECCION_SUELO 0
/**
* Se devuelve esta constante cuando el usuario pulse con el puntero
* sobre una entidad
* @see actualizarStylus
*/
#define SELECCION_ENTIDAD 1
/**
* Se devuelve esta constante cuando el usuario no pulse ni sobre el
* suelo ni sobre una entidad
* @see actualizarStylus
*/
#define SELECCION_AIRE 2
Las acciones que lleva a cabo la función zonaPulsada son las de identificar qué elemento
de la escena ha sido pulsado. Esto se explicó en el apartado 3.3.7 cuando se comentaba como
se podían usar las librerías empleadas. La técnica aquí empleada es idéntica a la explicada,
simplemente que aquí hay que diferenciar entre si el elemento pulsado es de tipo suelo o una
entidad. Esto no tiene demasiada complicación. Si se recuerda cómo funcionaba este
algoritmo, iba recortando la escena alrededor de la zona que se indica que se ha pulsado con
el puntero, y pintando elemento a elemento, antes de pintar cada uno comprobaba el número
de polígonos pintados y luego de pintar comprobaba si esa cantidad había variado, en tal caso
deducía que se había pulsado el elemento pulsado. Lo que hacemos ahora es diferenciar en
dos etapas cuándo estamos comprobando si hemos pulsado el suelo y cuándo los elementos.
Si el número de polígonos aumenta en la primera etapa o en la segunda, ya sabremos qué
hemos pintado.
Después de actualizarEventos, se invoca al método actualizarAccion. Este se
encarga de realizar las medidas oportunas en función de la acción que se encuentre el juego.
Estas son: ACCION_IR, ACCION_RECOGER y ACCION_USAR. Recordamos que estas
acciones son elegidas por el usuario pulsando sobre los botones correspondientes y la lógica
del juego las ha actualizado en el método antes visto actualizarBotones. Por tanto, nos
encontramos en el método actualizarAccion que discrimina según la acción actual para
actuar en consecuencia. A continuación el código de la implementación del método:
void Escenario::actualizarAccion(int *elementoPulsado, int
*tipoElementoPulsado)
{
switch(accion){
case ACCION_IR:
Fernando Garcia Bernal
145
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
if(*elementoPulsado != -1){
// si la acción es ir y se ha pulsado un
// elemento entonces se ilumina, se calcula el
// camino y se cambia el estado a
// desplazamiento
if(*tipoElementoPulsado == SELECCION_ENTIDAD){
// Se ilumina solamente la Entidad que
// haya sido seleccionada
for(int e=0;
((unsigned int) e) <
elementosSeleccionables.size();
e++){
elementosSeleccionables[e]->
iluminar =
(e==*elementoPulsado);
}
// Obtiene el camino desde el personaje
// hasta la entidad seleccionada
vector<int> camino = devuelveCamino(
situacionPersonaje() ,
elementosSeleccionables
[*elementoPulsado] ->
situacionEntidad());
this->camino = camino;
entidadSeleccionada = *elementoPulsado;
// cambia el estado para desplazarse a la
// entidad seleccionada
estado = ESTADO_DESPLAZAMIENTO;
}
// si la acción es ir y se ha pulsado el
// suelo, entonces se deja de iluminar todo
// se calcula el camino hasta ese punto del
// suelo y se cambia el estado para desplazarse
else if(*tipoElementoPulsado ==
SELECCION_SUELO){
for(int e=0;
((unsigned int) e) <
elementosSeleccionables.size();
e++){
elementosSeleccionables[e]->
iluminar = false;
}
vector<int> camino =
devuelveCamino
(situacionPersonaje() ,
*elementoPulsado);
this->camino = camino;
estado = ESTADO_DESPLAZAMIENTO;
}
}
Fernando Garcia Bernal
146
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
break;
case ACCION_RECOGER:
// si la acción es recoger y se ha
// pulsado una entidad se ilumina,
// calcula el camino y se desplaza
// hacia ella
if(*elementoPulsado != -1 &&
*tipoElementoPulsado == SELECCION_ENTIDAD){
for(int e=0;
((unsigned int)e) <
elementosSeleccionables.size();
e++){
elementosSeleccionables[e]->iluminar =
(e==*elementoPulsado);
}
vector<int> camino =
devuelveCamino(situacionPersonaje(),
elementosSeleccionables
[*elementoPulsado]-> situacionEntidad());
this->camino = camino;
entidadSeleccionada = *elementoPulsado;
estado = ESTADO_DESPLAZAMIENTO;
}
break;
case ACCION_USAR:
// Actualiza la posición de
// la selección del inventario
if(Pad.Newpress.Down)
{
seleccionInventario++;
}
else if(Pad.Newpress.Up)
{
seleccionInventario--;
}
if(seleccionInventario < -1)
{
seleccionInventario = inventario.size()-1;
}
else if(seleccionInventario >= (signed
int)inventario.size())
{
seleccionInventario = -1;
}
// si la acción es usar y se ha pulsado una
// entidad, se ilumina, calcula el camino
Fernando Garcia Bernal
147
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
// y se desplaza hacia ella
if(*tipoElementoPulsado == SELECCION_ENTIDAD &&
*elementoPulsado != -1)
{
for(int e=0;
((unsigned int)e) <
elementosSeleccionables.size();
e++)
{
elementosSeleccionables[e]->iluminar =
(e==*elementoPulsado);
}
vector<int> camino =
devuelveCamino(situacionPersonaje(),
elementosSeleccionables
[*elementoPulsado]->situacionEntidad());
this->camino = camino;
entidadSeleccionada = *elementoPulsado;
estado = ESTADO_DESPLAZAMIENTO;
}
break;
}
}
Si la acción es ACCION_IR, entonces primero se comprueba si se ha seleccionado algo con
el puntero, en tal caso si además lo seleccionado es de tipo Entidad (podría ser suelo) se
ilumina el contorno de la entidad con un borde blanco y se obtiene el camino desde la
situación actual del personaje hacia el objeto. Finalmente se transita al estado
ESTADO_DESPLAZAMIENTO para desplazarse hacia ella. En cambio, si la selección ha sido
sobre un elemento suelo, la actuación es similar sólo cambia que el camino se encuentra desde
la posición actual hasta la zona de suelo, y en este caso no se ilumina nada. En el punto
siguiente se explica cómo calcular la posición del personaje y cómo encontrar el camino.
Cuando la acción es ACCION_RECOGER, se comprueba que se ha seleccionado un elemento
entidad, se ilumina y se calcula el camino hacia este. El estado también transita a
desplazamiento para que se desplace.
La última acción posible es ACCION_USAR, y lo primero que hace es actualizar la selección
del inventario en función de los botones pulsados. Esto de recorrer se hace solamente aquí ya
que es algo exclusivo de esta acción. Después se comprueba si se ha seleccionado alguna
Fernando Garcia Bernal
148
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
entidad para como hemos hecho hasta ahora, iluminarla, encontrar el camino y cambiar el
estado.
El siguiente método que se invoca desde actualizar es actualizarEstado. Los posibles
estados son: ESTADO_PARADO y ESTADO_DESPLAZAMIENTO. Si está seleccionado el
primero, no se realizará ninguna acción. En cambio el segundo hará que el personaje se
desplace hasta su destino. Lo siguiente que se muestra es la implementación del método:
bool Escenario::actualizarEstado(int *elementoPulsado, int
*tipoElementoPulsado)
{
bool finEscenario = false;
switch(estado)
{
case ESTADO_PARADO :
break;
case ESTADO_DESPLAZAMIENTO :
// ESTADO_DESPLAZAMIENTO se centra
// en ir recorriendo el camino hasta
// el destino
// obtiene el area de destino
int areaDest = camino.at(camino.size()-1);
// si ha llegado a su destino realiza
// las acciones oportunas según la acción
// que lo había movido a desplazarse
if(situacionPersonaje() == areaDest){
// inicializa el camino
// ya recorrido
camino = vector<int>();
// el protagonista se detiene
// y por tanto vuelve al frame original
Protagonista.fr=0;
// si la acción era recoger la recoge si puede, en
// tal caso quitándola del inventario
if(accion == ACCION_RECOGER && entidadSeleccionada != -1)
{
Entidad ent =
*elementosSeleccionables[entidadSeleccionada];
if(!ent.puedeRecogerse){
sprintf(mensajePantalla,
ent.mensajeNoRecoger);
Fernando Garcia Bernal
149
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
tiempoMensajePantalla =
TIEMPO_MENSAJE_PANTALLA;
}
else{
for(int e=0;
((unsigned int)e)
<elementosSeleccionables.size();
e++){
elementosSeleccionables[e]->
iluminar = false;
}
inventario.push_back(ent);
finEscenario = recoger(&ent);
desvincularElementoSeleccionable
(ent.nombre);
}
}
// si la acción era usar y se ha seleccionado una
// entidad entonces se usa la entidad y con un
// elemento del inventario si este estuviera
// seleccionado
else if(accion == ACCION_USAR &&
entidadSeleccionada != -1)
{
Entidad *entInventario = NULL;
Entidad *ent =
elementosSeleccionables[entidadSeleccionada];
if(seleccionInventario != -1)
{
entInventario =
&inventario[seleccionInventario];
}
finEscenario = usar(ent, entInventario);
}
entidadSeleccionada = -1;
// es necesario este control ya que al recoger o al
// usar, puede ponerse el estado de animacion y en
// tal caso no puede interrumpirse
if(estado != ESTADO_ANIMACION)
estado = ESTADO_PARADO;
}
// si no ha llegado a su destino, sigue desplazándose
// por el camino ya calculado
else{
// avanza un frame en la animación del protagonista
Protagonista.fr=
(Protagonista.fr+1)%Protagonista.nFrames;
// siguiente área a la que desplazarse
Fernando Garcia Bernal
150
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
int sigArea = camino.at(1);
// vectores origen y siguiente área
vector2 orig(Protagonista.instante.pos[0],
Protagonista.instante.pos[2]);
vector2 sig = devuelveCentroArea(sigArea);
// distancia a recorrer
float distancia = orig.distancia(sig);
float distX = orig.x-sig.x, distY = orig.y-sig.y;
// desplazamiento en X e Y en función de la variable
// velocidadDesplazamiento
float despX =
distX/(floorf(distancia/velocidadDesplazamiento)+1);
float despY =
distY/(floorf(distancia/velocidadDesplazamiento)+1);
// si la distancia es pequeña, el personaje
// llega a su destino
if (fabs(distancia) < 0.01f) {
Protagonista.instante.pos[0] = sig.x;
Protagonista.instante.pos[2] = sig.y;
camino.erase(camino.begin(),camino.begin()+1);
}
else{
// alguno que debe girarse el protagonista
// en dirección y sentido al desplazamiento
float angulo =
asin(distY/distancia)*180.0f/PI+270.0f;
if(distX>=.0f){
angulo*=-1.f;
}
Protagonista.instante.rot[1] = angulo;
// cantidad de desplazamiento que debe
// trasladarse el protagonista en el eje X
float cantidadDesplazamientoX =
(fabs(distX)<fabs(despX) || (distY == 0.0f &&
fabs(distX)<velocidadDesplazamiento)) ?
distX : despX;
Protagonista.instante.pos[0] -=
cantidadDesplazamientoX;
// se utiliza el desplazamiento en X para
// mover la cámara anclada al usuario
cam[0][0] -= cantidadDesplazamientoX;
cam[1][0] -= cantidadDesplazamientoX;
// cantidad de desplazamiento que debe
// trasladarse el protagonista en el eje Y
Protagonista.instante.pos[2] -=
(fabs(distY)<fabs(despY) || (distX == 0.0f &&
fabs(distY)<velocidadDesplazamiento)) ?
distY : despY;
}
Fernando Garcia Bernal
151
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
}
break;
}
return finEscenario;
}
Cuando el estado actual es desplazarse, lo primero que se comprueba es si el personaje ha
alcanzado su destino, en tal caso pasa a desempeñar la acción que había desembocado en el
desplazamiento. Si se trataba de la acción ACCION_RECOGER, entonces se consulta si la
entidad en cuestión permite recogerse a través de su propiedad puedeRecogerse. En caso
negativo se muestra un mensaje al usuario para indicárselo. Si se puede recoger, entonces se
suma la entidad al inventario del protagonista, se desvincula la entidad del escenario para que
deje de mostrarse, y se llama al método virtual recoger. Al igual que sucedía con el método
virtual eventoFinalizarAnimacion que se comentó anteriormente, este método no
tiene sentido que se implemente en esta clase Escenario, ya que los eventos que puedan
suceder al recoger cierta entidad dependerán del escenario concreto que se defina en la
historia. Otra posibilidad es que la acción activa sea ACCION_USAR, en este caso se
comprueba si el usuario tiene algún elemento del inventario seleccionado para usar sobre el
elemento seleccionado con el puntero. Después se llama al método virtual usar que sucede
similar a recoger. Cuando se llegue al punto 6.2.7 se verá como implementar clases
especializadas que implementan estos métodos.
La otra situación es cuando el protagonista no ha terminado de desplazarse, entonces se deben
realizar unos cálculos para que continúe su trayectoria. Se incrementa el frame actual del
protagonista para avanzar en la animación de movimiento se obtiene de la variable camino
la siguiente área a la cual se tiene que desplazar. Se crean dos variables que se corresponden
con las coordenadas de origen y destino para poder realizar los cálculos geométricos de
desplazamiento. Haciendo uso de trigonometría y conociendo la distancia que queremos
desplazarnos, velocidadDesplazamiento, se calcula el desplazamiento que debe
hacerse en despX y despY. Se gira la figura en la dirección y sentido del desplazamiento
accediendo a su variable instante que almacena la posición, rotación y escalado. Después
se traslada la cantidad total que le queda, distX, o la proporción necesaria, despX, en
función de si le queda poco por moverse. Finalmente se desplaza la cámara dicha cantidad
Fernando Garcia Bernal
152
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
para que haga el efecto de seguir al personaje. La última línea devuelve la variable booleana
que indica si el escenario ha terminado, según la respuesta de los métodos virtuales usar y
recoger.
La siguiente llamada es actualizarTextos y se encarga de mostrar los mensajes al
usuario sobre la situación del juego. Como en otros casos, también se divide según la acción
actual para dar el mensaje correspondiente. A continuación la implementación:
void Escenario::actualizarTextos(int *elementoPulsado, int
*tipoElementoPulsado)
{
// si se puede cambiar el mensaje en pantalla...
if(tiempoMensajePantalla == -1){
switch(accion){
case ACCION_IR :
// Se muestra "Ir a (elemento seleccionado)"
PA_SetSpriteXY(1,0,137,57);
sprintf(mensajePantalla, "Ir a");
if(entidadSeleccionada != -1){
sprintf(mensajePantalla, "%s %s",
mensajePantalla,
elementosSeleccionables
[entidadSeleccionada]->nombre);
}
break;
case ACCION_RECOGER :
// se muestra "Recoger (elemento seleccionado)"
PA_SetSpriteXY(1,0,171,101);
sprintf(mensajePantalla, "Recoger");
if(entidadSeleccionada!=-1)
{
sprintf(mensajePantalla, "%s %s",
mensajePantalla,
elementosSeleccionables
[entidadSeleccionada]->nombre);
}
break;
case ACCION_USAR :
// se colorea la selección del inventario
// y se muestra "Usar (elemento seleccionado)
// con (elemento inventario)"
Fernando Garcia Bernal
153
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
PA_SetSpriteXY(1,0,203,57);
if(seleccionInventario != -1)
{
PA_SetSpriteXY(1,1,12,
seleccionInventario*24+46);
PA_SetSpriteXY(1,2,32+12,
seleccionInventario*24+46);
//Ajusto el último sprite pq el
// ancho total son 92 px, no 96 px (32*3)
PA_SetSpriteXY(1,3,32*2+8,
seleccionInventario*24+46);
}
else
{
for(int i=0; i<3; i++)
{
PA_SetSpriteX(1,i+1,255);
}
}
sprintf(mensajePantalla, "Usar");
if(seleccionInventario != -1 &&
entidadSeleccionada == -1)
{
sprintf(mensajePantalla, "%s %s con",
mensajePantalla,
inventario[seleccionInventario]
.nombre);
}
if(seleccionInventario != -1 &&
entidadSeleccionada != -1)
{
sprintf(mensajePantalla, "%s %s con %s",
mensajePantalla,
inventario[seleccionInventario].
nombre,
elementosSeleccionables
[entidadSeleccionada]->nombre);
}
else if(entidadSeleccionada != -1)
{
sprintf(mensajePantalla, "%s %s",
mensajePantalla,
elementosSeleccionables
[entidadSeleccionada]->nombre);
}
break;
}
}
// si se debe seguir mostrando un mensaje,
// se decrementa el tiempo que debe seguir
// mostrándose
else
{
tiempoMensajePantalla--;
}
Fernando Garcia Bernal
154
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
// se pintan los elementos que contiene
// en el inventario
int x = 3;
for(unsigned int i=0; i<inventario.size(); i++)
{
x = (accion == ACCION_USAR && seleccionInventario ==
(signed int)i)?6:3;
PA_OutputText(1,x,i*3+6,"%s",inventario[i].nombre);
}
// se pinta el mensaje
PA_OutputText(1,5,22,mensajePantalla);
}
Existe una variable que indica si un mensaje se debe imprimir más de un cierto tiempo en
pantalla, por ejemplo para advertir de algo al usuario. Esta es tiempoMensajePantalla
y mientras tenga valor mayor de -1, entonces se mantiene el mensaje que se estaba pintando
hasta que decremente lo suficiente para que deje de cumplirse esa condición. Cuando su valor
llegue a -1, entonces se comprueba cual es la acción que está activada. En el caso de
ACCION_IR el mensaje a mostrar es "Ir a (elemento seleccionado)". Lo que está entre
paréntesis es para indicar que es opcional. Mientras no se hay seleccionado ninguna entidad
pero se tenga activada esta acción solamente se mostrará “Ir a”. En el momento que se pulse
sobre un objeto seleccionable entonces aparecerá el mensaje completo, por ejemplo “Ir a
mesa”. De forma similar ocurre si la acción es ACCION_RECOGER con el mensaje “Recoger
(elemento seleccionado)”. La acción ACCION_USAR puede tener diversos formatos, en
función de si hay seleccionado un elemento de la escena o si se selecciona un elemento del
escenario. El mensaje más completo es Usar (elemento seleccionado) con (elemento
inventario)". Dónde el primer elemento aparecerá escrito según si se selecciona algún
componente con el puntero o Stylus, y el segundo dependerá de la selección que se efectúe en
el inventario. Por último se pintan los nombres de los elementos que contiene el usuario en el
inventario y después el mensaje previamente construido.
La última llamada que realiza el método actualizar es pintarGL que se encarga de pintar la
escena una vez actualizada. La implementación estaba en el fichero de definición ya copiado,
pero lo recuperamos de nuevo:
void pintarGL(){
// inicializa la escena
Fernando Garcia Bernal
155
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0f,altura,distancia);
glRotateX(inclinacion);
glPushMatrix();
{
// posiciona la cámara
gluLookAt(
// posición cámara
cam[0][0], cam[0][1], cam[0][2],
// punto de mira
cam[1][0], cam[1][1], cam[1][2],
// vector alzado
cam[2][0], cam[2][1], cam[2][2]);
// pinta la entidad suelo
Suelo.pintarGL();
// pinta al protagonista
Protagonista.pintarGL();
// pinta los elementos no seleccionables
for(unsigned int i = 0;
i<elementosNoSeleccionables.size(); i++)
{
elementosNoSeleccionables[i].pintarGL();
}
// pinta los elementos seleccionables
for(unsigned int i = 0;
i<elementosSeleccionables.size(); i++)
{
elementosSeleccionables[i]->pintarGL();
}
}
glPopMatrix(0);
glFlush(0);
}
Aquí se pintan todos los componentes. Primero se inicializan los valores oportunos, a
continuación se posiciona la cámara, luego se pinta el suelo, el protagonista y los elementos
seleccionables y no seleccionables. Lo siguiente que podemos ver es la manera de pintar una
entidad:
void Entidad::pintarGL(){
glEnable(GL_TEXTURE_2D);
glColor3b(255,255,255);
Fernando Garcia Bernal
156
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
// Establece la vista a la pantalla completa
glViewPort(0,0,255,191);
// inicializa
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(30, 256.0 / 192.0, 0.1, 20);
glMatrixMode(GL_MODELVIEW);
// ilumina la entidad si está activo el flag
if(iluminar) {
glPolyFmt(POLY_ALPHA(31) | POLY_CULL_NONE | POLY_ID(1));
} else {
glPolyFmt(POLY_ALPHA(31) | POLY_CULL_NONE | POLY_ID(0));
}
// se pinta cada figura que compone la entidad
for(int i=0; i<nFiguras; i++){
glPushMatrix();
{
// se calcula el cuaternion de la rotación
glQuaternion q(
f[i].a[fr].rot[1],
f[i].a[fr].rot[2],
f[i].a[fr].rot[3],
f[i].a[fr].rot[0]);
// se calculan a partir del ángulo las
// coordenadas (x,y,z) de giro
float angle=2.0f*acos(q.m_w)*180.0f/PI;
float s=sqrt(1-q.m_w*q.m_w);
float x, y, z;
// para evitar división por cero
if (s < 0.001f)
{
x=q.m_x;
y=q.m_y;
z=q.m_z;
}
else
{
x=q.m_x/s;
y=q.m_y/s;
z=q.m_z/s;
}
// se actualiza el instante actual
glTranslatef(
instante.pos[0],
instante.pos[1],
instante.pos[2]);
glRotateX(instante.rot[0]);
glRotateY(instante.rot[1]);
glRotateZ(instante.rot[2]);
glScalef(
instante.esc[0],
Fernando Garcia Bernal
157
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
instante.esc[1],
instante.esc[2]);
// se aplican las transformación de la animación
if(f[i].nFrames>0){
//Transformaciones del modelo
glTranslatef(
f[i].a[fr].pos[0],
f[i].a[fr].pos[1],
f[i].a[fr].pos[2]);
glRotatef(angle,x,y,z);
glScalef(
f[i].a[fr].sca[0],
f[i].a[fr].sca[2],
f[i].a[fr].sca[2]);
}
// se recuperan los vértices
vert *vaux=(vert *)f[i].v;
int nMat=0, vecMat=0;
for(int mat=0; mat<f[i].nMateriales; mat++)
{
nMat=f[i].numeroMat[mat];
vecMat=f[i].vecesMat[mat];
// se vincula la textura correspondiente
vincularTextura(nMat);
glBegin(GL_TRIANGLE);
int c=0;
for(int vC=0; vC<vecMat; vC++)
{
// se pinta cada vértice y su texura con
// las primitivas de openGL
for(int ve=0; ve<3; ve++){
glTexCoord2f(
vaux[c][ve][1],
vaux[c][ve][0]);
glVertex3f(
vaux[c][ve][2],
vaux[c][ve][3],
vaux[c][ve][4]);
}
c++;
}
glEnd();
}
}
glPopMatrix(1);
}
}
Fernando Garcia Bernal
158
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
Este método pintarGL es el mismo para todas las entidades. Primero se inicializan algunos
valores. Se activa la iluminación en el contorno si fuera necesario o se desactiva en el caso
contrario. El siguiente bucle pinta cada figura que compone la Entidad en concreto. Cada
una puede estar sometida a dos transformaciones, una la propia del instante en el que se
encuentra, por ejemplo girada según la dirección del movimiento, y otra transformación según
el frame de su animación, por ejemplo la animación de desplazamiento del personaje. Lo
primero que se hace es calcular el ángulo de la transformación de rotación de la animación a
partir del cuaternión. Después se aplica la transformación del instante, luego la de la
animación para finalmente pintar todas las caras de la figura aplicándole la textura del
material correspondiente.
6.2.6.3 Búsqueda de camino
En este apartado se explica las técnicas usadas para realizar la búsqueda de caminos entre dos
puntos del escenario. La primera llamada que se hace para obtener un camino es al método
devuelveCamino. Aquí lo único que se hace es comenzar el camino con la cara de inicio
pasada por la variable ini, y después llamar al algoritmo oportuno para obtener el camino.
Esta es la implementación del método:
vector<int> Escenario::devuelveCamino(int ini, int dest)
{
vector<int> camino;
// se establece el punto de inicio como
// el inicio del camino
camino.push_back(ini);
buscarCaminoAEstrella_ModificadoOptimo(ini, dest, &camino);
return camino;
}
En este caso vamos a implementar un algoritmo A* modificado para encontrar el camino más
corto, sin embargo también se puede optar por decisiones más rápidas como ir explorando el
espacio hasta encontrar la primera solución posible.
El algoritmo A* busca en un espacio de estados la ruta de menor coste desde un nodo de
comienzo hasta un nodo objetivo examinando los nodos adyacentes. Durante la ejecución, el
Fernando Garcia Bernal
159
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
algoritmo examina repetidamente el nodo más “prometedor” sin explorar. Cuando alcanza un
nodo, acaba si este era su objetivo, o en otro caso anota los nodos colindantes para continuar
explorando.
Este algoritmo es una mejora de los algoritmos voraces ya que hace uso de una función
distancia-más-coste heurística f(x) pero la usa para determinar el orden en el que visitar los
nodos, en lugar de fiarse de su resultado exclusivamente para realizar el siguiente paso de
exploración como es habitual en los algoritmos de búsqueda voraz. La función f(x) de nuestro
algoritmo se descompone en dos funciones, una función de coste g(x) que puede ser heurística
o no, y una función de estimación heurística h(x) que da la distancia al objetivo por una
aproximación sencilla.
En más detalle, el algoritmo A* mantiene dos listas de estados, llamadas open y close que van
almacenando los estados sin examinar y los examinados respectivamente. Al comienzo del
algoritmo, close está vacía y open tiene solamente el nodo de inicio. En cada iteración, el
algoritmo elimina el estado más prometedor de open para examinarlo. Si el estado no es el
objetivo, se ordenan los estados contiguos: si son nuevos, entonces se sitúan en open; si ya
estaban en open, entonces se actualiza su información si son una ruta más corta; si ya estaban
en close se ignoran porque ya habían sido examinados. Si la lista open se encuentra vacía
antes de alcanzar el objetivo, significa que no existe un camino hasta el objetivo desde la
localización origen.
El nodo más prometedor en open es la localización con menor coste estimado. Cada estado
incluye información que determina: el coste de la menor ruta desde el comienzo,
CostFromStart (g(x)); una heurística estimada, CostToGoal (h(x)), del coste aproximado de la
distancia restante al objetivo; y el camino total estimado, definido como CostFromStart(g(x))
+ CostToGoal (f(x)). El nodo con menor camino total estimado será el próximo en examinar.
Además, cada nodo contiene un puntero a su nodo padre, parent hacia el camino más corto,
para poder reconstruir posteriormente dicho camino cuando se alcance el objetivo. A
continuación se muestra la estructura de datos utilizada para la definición anterior:
/** Estructura de un nodo usada para el algoritmo A* */
typedef struct TNodo{
// número del nodo en carasDesplazables
int loc;
Fernando Garcia Bernal
160
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
// índice de la posición en carasDesplazables
int indice;
// coste desde el inicio hasta este nodo
float costFromStart;
// coste desde este nodo al destino
float costToGoal;
// coste total
float totalCost;
// nodo padre
TNodo *parent;
} nodo;
Lo siguiente sería realizar la implementación de este algoritmo, sin embargo no cumple con
todas las necesidades que harían falta. Ya que la búsqueda que va haciendo este algoritmo
asegura ir recorriendo el espacio de estados por los nodos más prometedores basándose en
función de aproximación heurística además de la información que va anotando de todos los
nodos que recorre para realizar la mejor elección. Sin embargo, en el momento que encuentra
el nodo objetivo, finaliza devolviendo la solución. Esto puede llevar a que en algunas
ocasiones no devuelva el camino más corto, sino el más corto de los que ha explorado. Por
tanto, la implementación por la que se ha optado ha sido una adaptación de este algoritmo,
que consiste en no finalizar al encontrar el nodo destino, sino seguir buscando hasta que se
acabe la lista de open, y en ese momento consultar la lista de nodos visitados en close para
elegir el camino con menor coste de entre los encontrados. Una vez explicado esto, se muestra
la implementación:
void Escenario::buscarCaminoAEstrella_ModificadoOptimo(int startLoc, int
goalLoc, vector<int>* fin)
{
// lista de nodos abiertos y cerrados, sin
// explorar y explorados respectivamente
vector<int> open, close;
// aquí se almacenan todos los nodos disponibles
// en carasDesplazables
vector<TNodo*> nodos;
// Inicialización
for(unsigned int i=0; i<carasDesplazables.size(); i++)
{
nodo *n = (nodo*)malloc(sizeof(TNodo));
n->costFromStart=MAX;
n->loc = carasDesplazables[i];
n->indice = devuelveIndice(n->loc);
nodos.push_back(n);
}
fin->clear();
// nodo inicial
nodo *startNode=encuentraNodoPorId(&nodos, startLoc);
Fernando Garcia Bernal
161
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
startNode->costFromStart=0;
startNode->costToGoal=PathCostEstimate(startLoc,goalLoc);
startNode->parent=NULL;
open.push_back(startNode->loc);
// mientras open no esté vacía se explora
// todo el espacio de estados
while(!open.empty())
{
// recoge el nodo más prometedor de la lista
// ordenada open y luego lo saca de ella
nodo *node=encuentraNodoPorId(&nodos, open.back());
open.pop_back();
nodo *newNode;
// itera por cada uno de los nodos contiguos de node
for(unsigned int i=0; i<cont[node->indice].size(); i++)
{
newNode=encuentraNodoPorId(&nodos,
cont[node->indice][i]);
// calcula el nuevo coste
float newCost=node->costFromStart +
PathCostEstimate(node->loc,newNode->loc);
// si no está en open y close Y el coste del nodo
// examinado es menor que el nuevo coste
// entonces se actualiza, sino se ignora
if(
!(
((esta(newNode->loc,open)!=-1) ||
(esta(newNode->loc,close)!=-1))
&& newNode->costFromStart <= newCost
)
)
{
// se actualiza su información
newNode->parent=node;
newNode->costFromStart=newCost;
newNode->costToGoal=
PathCostEstimate(newNode->loc,goalLoc);
newNode->totalCost=
newNode->costFromStart +
newNode->costToGoal;
// variable auxiliar para conocer la posición
// del nodo en la lista
// y poder eliminarlo
int pos;
// si newNode está en close se elimina
if((pos=esta(newNode->loc,close))!=-1)
{
close.erase(close.begin() +
pos,close.end()-(close.size()-pos));
}
// si newNode está en open se inserta en orden
if((pos=esta(newNode->loc,open))!=-1)
Fernando Garcia Bernal
162
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
{
insertar_en_orden(&nodos,newNode,&open);
}
// sino se inserta en la cabeza
else
{
open.push_back(newNode->loc);
}
}
} //for
// una vez examinado, se inserta en close
close.push_back(node->loc);
}//while
// cuando ya ha terminado de examinar el
// espacio de estados completo, se recupera
// el camino más corto
vector<int> f;
// se recupera el nodo objetivo y a partir
// de él se reconstruye el camino
nodo *ptr=encuentraNodoPorId(&nodos, goalLoc);
while(ptr->parent!=NULL)
{
f.push_back(ptr->loc);
ptr=ptr->parent;
}
f.push_back(ptr->loc);
// se da la vuelva a la lista obtenida
// y se almacena en la variable fin
vector<int>::reverse_iterator rit;
for(rit=f.rbegin() ; rit < f.rend(); rit++)
{
fin->push_back(*rit);
}
return;
}
6.2.7 Crear nuestra propia aventura. Especializaciones de escenario y
entidad
Se ha estudiado como crear el esqueleto completo que requiere esta aplicación, sin embargo
en ningún momento se ha hablado de aspectos concretos de la historia que habíamos
planteado en capítulos anteriores. Esto es así porque este proyecto está pensado para que
cualquier otra persona pueda reutilizar y definir su propia historia, en base al motor que aquí
se ha desarrollado.
Fernando Garcia Bernal
163
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
Por tanto, para crear la aventara es necesario heredar de las clases Entidad y Escenario
para especializarlas según los criterios impuestos por el diseñador de juego. A continuación se
aborda cada clase a heredar por separado.
6.2.7.1 Herencia de Entidad
Cada una de las entidades que tengan que aparecer en el transcurso del juego debe tener una
clase propia que herede de Entidad. En esta clase se define cual es el modelo 3D que le
corresponde, su textura, si se puede recoger o usar y los mensajes que pueden mostrar.
A continuación se muestra de manera genérica cómo debe ser la implementación de una
Entidad para un juego, en un fichero .h:
#ifndef ENTIDAD_NOMBRE_H
#define ENTIDAD_NOMBRE_H
#include "Entidad.h"
#include "modelo3D.h"
#include "textura.h"
class NombreEntidad : public Entidad
{
public:
// Se define el constructo pasando como parámetros
// el nombre del modelo 3D en formato X, el fichero
// que se corresponde con la textura y el tamaño de la
// misma que puede ser 64 o 128
NombreEntidad():Entidad(modelo3D, textura, tamañoTextura){
//
//
//
//
(Opcional) Por defecto una entidad no puede
recogerse, si se activa esta opción entonces
si podrá ser recogida por el protagonista
puedeRecogerse = true;
//
//
//
//
(Opcional) Por defecto una entidad no puede
usarse, si se activa esta opción entonces
si podrá ser usada por el protagonista
puedeUsarse = true;
//
//
//
//
//
//
(Opcional) Por defecto existen mensajes
predefinidos para cuando una Entidad
no puede recogerse, si se define aquí un
un mensaje, será mostrado cuando esto suceda
en lugar del mensaje por defecto
sprintf(mensajeNoRecoger,"no se puede recoger");
// (Opcional) Por defecto existen mensajes
// predefinidos para cuando una Entidad
Fernando Garcia Bernal
164
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
//
//
//
//
Implementación
no puede usarse, si se define aquí un
un mensaje, será mostrado cuando esto suceda
en lugar del mensaje por defecto
sprintf(mensajeNoUsar,"no se puede usar");
};
~NombreEntidad(){};
};
#endif
Los valores que aparecen en negrita son para indicarlos manualmente en cada entidad.
ENTIDAD_NOMBRE_H es para utilizar las macros de C #ifndef y #define, puede ser
cualquier nombre aunque lo normal es algo identificativo. La palabra modelo3D es para
indicar el nombre del fichero exportado desde el formato X, que deberá tener extensión .bin
al haber sido creado con la herramienta implementada en esta memoria. textura debe ser el
nombre del fichero que almacena la textura del objeto. El nombre de la clase Entidad lo
definimos en NombreEntidad. La última palabra por declarar es tamañoTextura que
como indica, es el tamaño de la textura y debe ser 64 ó 128 (es decir que la textura sería de
64x64 ó 128x128). Es importante decir que pueden existir problemas si se usan varias
texturas con el tamaño de 128 ya que la videoconsola dispones de un límite de memoria para
texturas, por tanto se recomienda no usar más de 2 texturas de 128.
6.2.7.2 Herencia de Escenario
Como sucede en la clase Escenario, en su clase heredada se deberá definir la lógica del
juego puesto que sigue siendo esta clase la que relacione a todas las demás teniendo
visibilidad sobre estas. En este caso a diferencia de la herencia de entidades, se puede
implementar en dos ficheros distintos debido a su mayor magnitud, para tenerlo así un poco
más ordenado. La implementación del fichero .h genérica es:
#ifndef ESCENARIO_NOMBRE_H
#define ESCENARIO_NOMBRE_H
#include "Escenario.h"
class NombreEscenario : public Escenario
{
public:
NombreEscenario (Personaje *_Prota);
Fernando Garcia Bernal
165
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
virtual ~NombreEscenario(){};
// implementa los métodos específicos
// recoger, usar y eventoFinalizarAnimacion
// para este escenario
bool recoger(Entidad *obj1);
bool usar(Entidad *obj1, Entidad *obj2);
bool eventoFinalizarAnimacion(Entidad *obj1);
// define las variables para el personaje y para las entidades
// de este escenario
Personaje *Prota;
Entidad *EntidadHeredada1,*EntidadHeredada2,..*EntidadHeredadan;
};
#endif
En este fichero es también necesario utilizar un nombre en ESCENARIO_HABITACION_H
para utilizar las macros #ifndef y #define, y evitar problemas al incluir estos ficheros.
Después se especifica un nombre a esta clase en NombreEscenario. Por último, para tener
unas referencias a las entidades que se incluyan dentro de este escenario, se declaran como
punteros en EntidadHeredada.
El otro fichero es el .cpp donde se implementan los métodos recoger, usar y
eventoFinalizarAnimacion. Este es algo más extenso así que iremos viéndolo por
métodos. El primero a implementar se trata del constructor:
NombreEscenario::NombreEscenario(Personaje *_Prota):
Escenario(objetoSuelo, texturaSuelo,
tamañoTexturaSuelo, inclinación,
distancia, altura)
{
// ascocia al protagonista
Prota = _Prota;
// asocia el resto de entidades creadas para este escenario
EntidadHeredada1 = new EntidadHeredada1();
EntidadHeredada2 = new EntidadHeredada2();
…
EntidadHeredadan = new EntidadHeredadan();
// vincula el objeto Personaje con este escenario
vincularProtagonista(Prota,.0f,.0f,.0f);
// vincula los elementos seleccionables con este escenario
Fernando Garcia Bernal
166
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
vincularElementoSeleccionable(EntidadHeredada1, posiciónX1,
posiciónY1, posiciónZ1);
vincularElementoSeleccionable(EntidadHeredada2, posiciónX2,
posiciónY2, posiciónZ2);
…
vincularElementoSeleccionable(EntidadHeredadan, posiciónXn,
posiciónYn, posiciónZn);
// vincula los elementos no seleccionables con este escenario
vincularElementoNoSeleccionable(EntidadHeredada1, posiciónX1,
posiciónY1, posiciónZ1);
vincularElementoNoSeleccionable(EntidadHeredada2, posiciónX2,
posiciónY2, posiciónZ2);
…
vincularElementoNoSeleccionable(EntidadHeredadan, posiciónXn,
posiciónYn, posiciónZn);
// Lo último es almacenar en el vector mensajes
// los mensajes que se deben mostrar al usuario
// en las transciciones entre los escenarios
mensajes.push_back("mensaje1");
mensajes.push_back("mensaje2");
…
mensajes.push_back("mensajen");
}
Se distinguen varios pasos. Primero en el constructor se le deben pasar el nombre del objeto
3D en objetoSuelo correspondiente al suelo, convertido desde un fichero X como ya se ha
visto;
además
se
pasa
su
textura
texturaSuelo
y
el
tamaño
de
esta
tamañoTexturaSuelo (un valor de 64 ó 128). También se le pasan los valores de
inclinación, distancia y altura con los que representar el suelo. Después se van a
vincular los elementos que forman la aventura a las variables que se crearon en el fichero de
definición de esta clase. Estos son el protagonista y las entidades heredadas. Cuando ya están
relacionadas con las variables, es necesario vincularla a las entidades internas del escenario a
través
de
los
métodos
vincularProtagonista,
vincularElementoSeleccionable y vincularElementoNoSeleccionable.
En los dos últimos es necesario declarar el nombre del objeto Entidad y la coordenada
(x,y,z) dónde debe establecerse dentro del Escenario. Lo último que se indica en el
constructor son los mensajes que se muestran al usuario antes, y después de acceder a la
escena con el fin de mantener el hilo argumental. Se deben ir almacenando en un vector al que
Fernando Garcia Bernal
167
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
más tarde se accede indicando qué mensajes se quieren mostrar. El siguiente método a
implementar por esta clase es recoger:
bool NombreEscenario::recoger(Entidad *obj1)
{
// inicialización
// esta variable indica si se debe mostrar un mensaje
// y cuál debería ser. Las diferentes posibilidades
// son:
// MSJ_NO_MOSTRAR: no muestra ningún mensaje después de
//
esta actuación
// MSJ_RECOGER_GENERICO: muestra el mensaje genérico definido
//
al final de este método
// MSJ_RECOGER_OBJETO_ESCENA: muestra el mensaje que se define
//
de manera específica para el objeto a recoger
int mostrarMensaje = MSJ_RECOGER_GENERICO;
// variable que indica si recoger el objeto hace finalizar el
// escenario
bool fin = false;
// aquí se identifica la entidad que se pretende
// recoger y se actúa en consecuencia realizando
// las actuaciones adecuadas
if(strcmp(obj1->nombre, "nombreEntidadHeredada1") == 0)
{
// posibles actuaciones:
// -> la entidad nombreEntidadHeredadai1 deja
//
de poder usarse
// nombreEntidadHeredadai1->puedeUsarse = false;
// -> la entidad nombreEntidadHeredadai2 pasa a
//
poder usarse
// nombreEntidadHeredadai2->puedeUsarse = true;
// -> la entidad nombreEntidadHeredadai3 deja
//
de poder recogerse
// nombreEntidadHeredadai3->puedeRecogerse = false;
// -> la entidad nombreEntidadHeredadai4 pasa a
//
poder recogerse
// nombreEntidadHeredadai4-> puedeRecogerse = true;
// -> no se muestra ningún mensaje
// mostrarMensaje = MSJ_NO_MOSTRAR;
// -> se muestra el mensaje por defecto
// mostrarMensaje = MSJ_RECOGER_GENERICO;
// -> se muestra el mensaje específico del objeto
// mostrarMensaje = MSJ_RECOGER_OBJETO_ESCENA;
// -> recoger esta entidad indica finalizar este escenario
// fin = true
}
else if(strcmp(obj1->nombre, "nombreEntidadHeredada2") == 0)
{
// realizar actuaciones pertinentes
}
// aquí se declara el mensaje a mostrar al usuario
// en función de la variable mostrarMensaje
switch(mostrarMensaje)
{
case MSJ_RECOGER_GENERICO:
Fernando Garcia Bernal
168
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
sprintf(mensajePantalla, "no se puede recoger");
break;
case MSJ_RECOGER_OBJETO_ESCENA:
sprintf(mensajePantalla, obj1->mensajeNoRecoger);
break;
}
if(mostrarMensaje != MSJ_NO_MOSTRAR)
{
tiempoMensajePantalla = TIEMPO_MENSAJE_PANTALLA;
}
return fin;
}
Este método da plena libertad al programador para realizar todo tipo de acciones después de
recoger un objeto. Por ejemplo, puede imaginarse un objeto bloqueado por tener otro objeto
encima, que al empujarlo queda liberado. Por tanto, el método comienza definiendo dos
variables: la primera se utiliza para indicar al sistema si tras la recogida del objeto no se debe
mostrar ningún mensaje al usuario o si en cambio se muestra un mensaje genérico o el
específico del objeto, la otra variable es la de fin de escenario que si se activa durante la
ejecución del método se dará por terminada esta fase. Después se encuentra una bifurcación
if/else if por cada uno de los objetos que puedan ser recogidos. Cada sentencia
selectiva comienza identificando el objeto que ha provocado la ejecución de este método para
realizar las acciones en consecuencia, una vez que se conozca cuál ha sido, en el código se
muestran posibles actuaciones que se pueden realizar por supuesto el programador tiene total
libertad para sus actuaciones hasta que su imaginación se lo impida.
Otro método importante para definir un escenario específico es usar. Su comportamiento es
similar a recoger aunque ahora pueden intervenir dos objetos diferentes y eso conlleva
algunas particularidades. La implementación es la siguiente:
bool NombreEscenario::usar(Entidad *obj1, Entidad *obj2)
{
// La entidad obj1 se identifica con el objeto que se
// encuentra en el escenario, mientras que obj2 (si existe)
// es el objeto que tiene el usuario en su inventario
// inicialización
// esta variable indica si se debe mostrar un mensaje
// y cuál debería ser. Las diferentes posibilidades
Fernando Garcia Bernal
169
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
// son:
// MSJ_NO_MOSTRAR: no muestra ningún mensaje después de
//
esta actuación
// MSJ_USAR_GENERICO: muestra el mensaje genérico definido
//
al final de este método
// MSJ_USAR_OBJETO_ESCENA: muestra el mensaje que se define
//
de manera específica para el objeto a usar
// MSJ_USAR_OBJETO_INVENTARIO: muestra el mensaje que se define
//
de manera específica para el objeto del inventario a usar
int mostrarMensaje = MSJ_USAR_GENERICO;
// variable que indica si usar el objeto hace finalizar el
// escenario
bool fin = false;
// aquí se identifica la entidad de la escena
// que se pretende usar y se actúa en consecuencia realizando
// las actuaciones adecuadas
if(strcmp(obj1->nombre, "nombreEntidadHeredada1") == 0)
{
// es posible que la actuación de usar el objeto
// de la escena sólo sea factible si se ha combinado
// con un objeto que deba poseer el protagonista
// en su inventario
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
posibles actuaciones:
-> si se ha seleccionado un objeto del inventario
y este es nombreEntidadHeredadai1 entonces se actúa
de la siguiente manera
if(obj2 != NULL && strcmp(obj2->nombre,
"nombreEntidadHeredadai1") == 0)
{
actuaciones posibles. Ver actuaciones posibles del
método recoger
}
else
{
mostrarMensaje = MSJ_USAR_OBJETO_ESCENA;
}
}
else if(strcmp(obj1->nombre, "nombreEntidadHeredada2") == 0)
{
// realizar actuaciones pertinentes como por ejemplo:
if
(
nombreEntidadHeredadai2->puedeUsarse &&
obj2 == NULL &&
poseeEnInventario("nombreEntidadHeredadai3") &&
strcmp(obj2->nombre, " nombreEntidadHeredadai3") == 0
)
{
nombreEntidadHeredadai4->puedeRecogerse = true;
nombreEntidadHeredadai5->puedeUsarse = false;
// iniciar animación nombreEntidadHeredadai6
{
entidadEnAnimacion = nombreEntidadHeredadai6;
entidadEnAnimacion->fr = 0;
finAnimacion = entidadEnAnimacion->nFrames;
estado = ESTADO_ANIMACION;
}
Fernando Garcia Bernal
170
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
}
else if(!nombreEntidadHeredadai6->puedeUsarse)
{
mostrarMensaje = MSJ_USAR_GENERICO;
}
else
{
mostrarMensaje = MSJ_USAR_OBJETO_ESCENA;
}
}
else
{
mostrarMensaje = MSJ_USAR_GENERICO;
}
// aquí se declara el mensaje a mostrar al usuario
// en función de la variable mostrarMensaje
switch(mostrarMensaje)
{
case MSJ_USAR_GENERICO:
sprintf(mensajePantalla, "no se puede usar");
break;
case MSJ_USAR_OBJETO_ESCENA:
sprintf(mensajePantalla, obj1->mensajeNoUsar);
break;
case MSJ_USAR_OBJETO_INVENTARIO:
sprintf(mensajePantalla, obj2->mensajeNoUsar);
break;
}
if(mostrarMensaje != MSJ_NO_MOSTRAR)
{
tiempoMensajePantalla = TIEMPO_MENSAJE_PANTALLA;
}
return fin;
}
En este caso el método usar recibe dos parámetros, obj1 y obj2. El primero es el objeto que
el usuario haya pulsado con el Stylus sobre la pantalla, este puntero siempre apuntará a una
entidad, en cambio el segundo objeto es opcional y se trata del objeto que puede tener
seleccionado el usuario en el inventario. La utilidad de esto como ya se ha comentado es
poder combinar un objeto del inventario con otro objeto de la escena. Al igual que con el
método anterior existen dos variables que se inicializan, una para hacer entender al sistema
qué mensaje mostrar al usuario tras la ejecución de este método; la otra para indicar el final
del escenario tras esta actuación. Luego de manera similar se identifica que objeto se pretende
utilizar para realizar la secuencia de acciones necesaria, ahora es cuando además de conocer el
Fernando Garcia Bernal
171
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
objeto que se ha pulsado del escenario, se puede comprobar si además el usuario pretende
combinarlo con un objeto del inventario para concretar las acciones a realizar. En la rama
else if de esta implementación se observa un ejemplo más completo de qué cosas se
pueden
hacer.
Primero
se
comprueba
que
el
objeto
del
escenario
es
nombreEntidadHeredada2 y luego se consulta que otra entidad, llamada por ejemplo
nombreEntidadHeredadai2 esté disponible para usar y además se quiera combinar la
entidad
anterior,
nombreEntidadHeredada2,
con
otra
llamada
nombreEntidadHeredadai3 y que se encuentre en el inventario y es la segunda entidad
que se llama en este método. Las consecuencias de estas condiciones son que pueda usarse la
entidad
nombreEntidadHeredadai4,
pueda
recogerse
la
nombreEntidadHeredadai5, y se desemboque el inicio de la animación de otra entidad
nombreEntidadHeredadai6. Los pasos para que se ejecute una animación son asociar la
entidad en cuestión a la variable global entidadEnAnimacion, inicializar su frame a 0,
indicar en la variable global finAnimacion cuál es el último frame a ejecutar y establecer
que el estado debe ser ESTADO_ANIMACION. Con esto se consigue que tras la ejecución de
este método la escena pase a ejecutar la secuencia de animación del objeto que se apunte
desde entidadEnAnimacion
hasta
llegar
al
final
indicando
en
la
variable
finAnimacion. Cuando esto acabe se pasará a ejecutarse el siguiente método que vamos a
ver eventoFinalizarAnimacion por si quedan eventos que realizar, y luego volvería el
control del juego al usuario. Finalmente, el último método para definir una clase Escenario
personalizada, eventoFinalizarAnimacion:
bool NombreEscenario::eventoFinalizarAnimacion(Entidad *obj1)
{
// variable que indica si al finalizar la animación del objeto
// hace finalizar el escenario
bool fin = false;
// aquí se identifica la entidad que se ha finalizado su
// animación y se actúa en consecuencia realizando
// las actuaciones adecuadas
if(strcmp(obj1->nombre, "nombreEntidadHeredada1") == 0)
{
//
actuaciones posibles. Ver actuaciones posibles del
//
método recoger
}
// Se usan las variables finAnimacion, que es la variable que
// indica el último frame de la animación, y el estado,
Fernando Garcia Bernal
172
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
// para que el método sepa a que estado debe transitar la
// aplicación por defecto para dar por finalizada la animación,
// finAnimacion debe ponerse a -1 y estado a ESTADO_PARADO.
finAnimacion = -1;
estado = ESTADO_PARADO;
return fin;
}
Al igual que en los métodos anteriores, en este se deja la libertad para que el desarrollador
cumpla la historia del juego insertando aquí el código necesario al finalizar el evento de la
entidad adecuada. Son importantes las dos últimas líneas ya que este método debe decidir si
realmente se ha terminado el estado de mostrar una animación, para lo cual debe establecerse
finAnimacion a -1 y estado a ESTADO_PARADO.
6.2.8 Fichero main
El último fichero necesario para finalizar el esquema global de este desarrollo, es el encargado
de inicializar las estructuras necesarias y arrancar la aplicación completa. Para ello se define
un fichero main como punto inicial de la aplicación. Una implementación genérica es:
int main()
{
// inicializa las librerías PAlib
// y la interfaz del usuario que
// representa el menú
inicializarMenu();
// inicializa la escena 3D
inicializaEscena3D();
// Creación de los punteros a las variables necesarias
// La variable protagonista se reutiliza para ambos
// escenarios
Personaje *Prota = new Personaje(prota_centrado, protaText);
Escenario *Escena1 = new NombreEscenario1(Prota);
Escenario *Escena2 = new NombreEscenario2(Prota);
…
Escenario *Escenan = new NombreEscenarion(Prota);
// texto con mensaje inicial
Escena1->mostrarTextoPantalla(0,4);
while(!Escena1->actualizar())
Fernando Garcia Bernal
173
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
{
PA_WaitForVBL();
}
Escena1->mostrarTextoPantalla(5,7);
reiniciarEscena();
while (!Escena2->actualizar())
{
PA_WaitForVBL();
}
Escena2->mostrarTextoPantalla(0, 2);
return 0;
}
Las primeras llamadas son necesarias para inicializar correctamente el sistema:
inicializarMenu para cargar las librerías de manera correcta y la interfaz de usuario; e
inicializaEscena3D carga las componentes 3D de la librería PAlib. Después siempre
tiene que existir una variable de Personaje que será vinculado al conjunto de escenario que se
definan. Después de crear estos pasándoles el objeto del protagonista, ya comienza la
ejecución de los escenarios. Lo normal puede ser que antes de que el usuario tome control del
juego, se muestre un texto que lo sitúe en la escena y le pueda explicar los objetivos que debe
conseguir. Después el bucle while ejecutará el método ya estudiado actualizar que
desencadena todos los acontecimientos para que el usuario se desplace e interactúe con el
escenario. Una vez que la historia decida que el escenario llega a su fin (recordar la variable
finEscenario booleana que se podía activar para dicha tarea) finaliza este bucle,
pudiendo lanzar otro mensaje de información al usuario, para después pasar a otro escenario.
Esto se realizará las veces que sean necesarios hasta que se llegue al final del fichero, cuando
se terminará la aplicación.
6.3 Implementar nuestra aventura
Ya está todo descrito, hasta este punto ya se ha implementado toda la funcionalidad de la que
dispone este sistema. Ahora de lo que se trata es de implementar la aventura descrita en los
capítulos previos. Seguramente pueda parecer una historia sencilla compuesta por dos
escenarios solamente, y puede ser cierto, sin embargo se ha pretendido que la parte a la que se
dedique más tiempo es a crear un sistema reutilizable para que un usuario sin tener que tener
Fernando Garcia Bernal
174
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
mucho conocimiento de esta estructura, pueda crear su propia historia basándose en ejemplos
como el que se va a implementar ahora.
6.3.1 Protagonista
De esta entidad ya se mostraron algunas capturas de su forma, sin embargo se vuelve a
mostrar tal como se va a hacer con el resto de entidades:
31 - Vista perspectiva.
32 - Vista frontal.
33 – Textura.
Del personaje en concreto no se crea ningún fichero de código ya que las interacciones que
pueda hacer se coordinan desde el escenario en concreto. Los elementos que si hace falta son:
animación de movimiento definida, fichero del modelo creado a partir del formato X y fichero
con la textura.
6.3.2 Entidades
Ahora, una a una se ven las implementaciones de las entidades que participan en la aventura,
primero se muestra una captura de su modelo 3D con su textura y luego los ficheros de código
que las definen en la aplicación.
6.3.2.1 Mesa
•
Modelo 3D:
Fernando Garcia Bernal
175
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
34 - Vista perspectiva.
•
35 - Vista frontal.
Implementación
36 – Textura.
EntidadMesa.h:
class EntidadMesa : public Entidad
{
public:
EntidadMesa():Entidad(mesa, mesaText, 64){
sprintf(mensajeNoRecoger,"Pesa demasiado");
};
~EntidadMesa(){};
};
6.3.2.2 Estatua
•
Modelo 3D:
37 - Vista perspectiva.
•
38 - Vista frontal.
39 – Textura.
EntidadEstatua.h:
class EntidadEstatua : public Entidad
{
Fernando Garcia Bernal
176
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
public:
EntidadEstatua():Entidad(estatua, estatuaText, 64){
puedeRecogerse = true;
};
~EntidadEstatua(){};
};
6.3.2.3 Puerta
•
Modelo 3D:
40 - Vista perspectiva.
•
41 - Vista frontal.
42 – Textura.
EntidadPuerta.h:
class EntidadPuerta : public Entidad
{
public:
EntidadPuerta():Entidad(puerta, puertaText, 64){
};
~EntidadPuerta(){};
};
6.3.2.4 Fregona
•
Modelo 3D:
Fernando Garcia Bernal
177
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
43 - Vista perspectiva.
•
44 - Vista frontal.
Implementación
45 – Textura.
EntidadFregona.h:
class EntidadFregona : public Entidad
{
public:
EntidadFregona():Entidad(fregona, fregonaText, 64){
puedeRecogerse = true;
};
~EntidadFregona(){};
};
6.3.2.5 Llave
•
Modelo 3D:
46 - Vista perspectiva.
•
47 - Vista frontal.
48 – Textura.
EntidadLlave.h:
Fernando Garcia Bernal
178
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
class EntidadLlave : public Entidad
{
public:
EntidadLlave():Entidad(llave, llaveText, 64){
};
~EntidadLlave(){};
};
6.3.2.6 Árbol
•
Modelo 3D:
49 - Vista perspectiva.
•
50 - Vista frontal.
51 – Textura.
EntidadArbol.h:
class EntidadArbol : public Entidad
{
public:
EntidadArbol():Entidad(arbol, arbolText, 64){
sprintf(mensajeNoRecoger,"No se puede");
};
~EntidadArbol(){};
};
6.3.2.7 Coco
•
Modelo 3D:
Fernando Garcia Bernal
179
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
52 - Vista perspectiva.
•
53 - Vista frontal.
Implementación
54 – Textura.
EntidadCoco.h:
class EntidadCoco : public Entidad
{
public:
EntidadCoco():Entidad(coco, cocoText, 64){
};
~EntidadCoco(){};
};
6.3.3 Escenarios
Pasamos a describir como se han desarrollado los dos escenarios en los que tiene lugar la
aventura.
6.3.3.1 Escenario Habitación
Este es el primer escenario y donde se encuentran la mayoría de las entidades antes descritas.
Está compuesto además por dos entidades adicionales, una que deben incluir todos los
escenarios y que se trata del suelo, y otra entidad no seleccionable, es decir que el usuario no
puede interactuar con ella, que se corresponde con la pared de la habitación.
•
Suelo:
Fernando Garcia Bernal
180
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
55 - Vista perspectiva.
•
57 – Textura.
Pared:
58 - Vista perspectiva.
•
56 - Vista frontal.
Implementación
59 - Vista frontal.
60 – Textura.
EscenarioHabitacion.h:
class EscenarioHabitacion : public Escenario
{
public:
EscenarioHabitacion(Personaje *_Prota);
virtual ~EscenarioHabitacion(){};
// implementa los métodos específicos
// recoger, usar y eventoFinalizarAnimacion
// para este escenario
bool recoger(Entidad *obj1);
bool usar(Entidad *obj1, Entidad *obj2);
bool eventoFinalizarAnimacion(Entidad *obj1);
// define las variables para el personaje y para las entidades
// de este escenario
Personaje *Prota;
Entidad *Fregona, *Puerta, *Estatua, *Llave, *Mesa,
*HabitacionCompleta;
};
•
EscenarioHabitacion.cpp:
Fernando Garcia Bernal
181
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
EscenarioHabitacion::EscenarioHabitacion(Personaje
*_Prota):Escenario(suelo, sueloText, 64, 15.0f, -5.f, -1.2f)
{
// Enlaza al protagonista
Prota = _Prota;
// Crea las Entidades
Fregona = new EntidadFregona();
Puerta = new EntidadPuerta();
Mesa = new EntidadMesa();
Estatua = new EntidadEstatua();
Llave = new EntidadLlave();
HabitacionCompleta = new Entidad(habitacion, habitacionText,
64);
// Vincula al protagonista, las entidades seleccionables
// y las no seleccionables
vincularProtagonista(Prota,.0f,.0f,.0f);
vincularElementoSeleccionable(Fregona,4.3f,.2f,.7f);
vincularElementoSeleccionable(Puerta,2.2f,.2f,1.3f);
vincularElementoSeleccionable(Mesa,-1.8f,.2f,.8f);
vincularElementoSeleccionable(Estatua,-1.7f,.6f,.2f);
vincularElementoSeleccionable(Llave,-1.8f,.2f,.6f);
vincularElementoNoSeleccionable(HabitacionCompleta,.0f,.22f,.0f);
// crea los mensajes que se mostrarán al usuario
// antes y después de la escena
// primera tanda de mensajes
mensajes.push_back("Te despiertas");
mensajes.push_back("y te encuentras en un cuarto");
mensajes.push_back("que no reconoces.");
mensajes.push_back("Debes salir");
mensajes.push_back("porque tienes hambre.");
// segunda tanda de mensajes
mensajes.push_back("Has podido salir");
mensajes.push_back("...¿donde estas?");
mensajes.push_back("¡cuanta hambre!");
}
bool EscenarioHabitacion::recoger(Entidad *obj1)
{
int mostrarMensaje = MSJ_RECOGER_GENERICO;
bool fin = false;
// Si el usuario recoge la estatua, entonces
Fernando Garcia Bernal
182
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
// al liberar el peso puede desplazar la mesa
// que se encuentra debajo
if(strcmp(obj1->nombre, "estatua") == 0)
{
Mesa->puedeUsarse = true;
mostrarMensaje = MSJ_NO_MOSTRAR;
}
switch(mostrarMensaje)
{
case MSJ_RECOGER_GENERICO:
sprintf(mensajePantalla, "no se puede recoger");
break;
case MSJ_RECOGER_OBJETO_ESCENA:
sprintf(mensajePantalla, obj1->mensajeNoRecoger);
break;
}
if(mostrarMensaje != MSJ_NO_MOSTRAR)
{
tiempoMensajePantalla = TIEMPO_MENSAJE_PANTALLA;
}
return fin;
}
bool EscenarioHabitacion::usar(Entidad *obj1, Entidad *obj2)
{
int mostrarMensaje = MSJ_NO_MOSTRAR;
bool fin = false;
if(strcmp(obj1->nombre, "puerta") == 0)
{
// si el protagonista usa la puerta, y tiene seleccionado
// en el inventario el objeto llave, entonces termina el
// escenario
if(obj2 != NULL && strcmp(obj2->nombre, "llave") == 0)
{
fin = true;
}
else
{
mostrarMensaje = MSJ_USAR_OBJETO_ESCENA;
}
}
// si se usa la mesa y ya ha recogido la
// estatua, entonces la llave pasa a
// poder recogerse y la mesa deja de poder
// usarse. En tal caso se inicia
// la animación de desplazamiento de
// la mesa para despejar el acceso
// hasta la llave
else if(strcmp(obj1->nombre, "mesa") == 0)
{
if
(
Mesa->puedeUsarse &&
obj2 == NULL &&
Fernando Garcia Bernal
183
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
poseeEnInventario("estatua")
)
{
Llave->puedeRecogerse = true;
Mesa->puedeUsarse = false;
// inicia animación mesa
{
entidadEnAnimacion = Mesa;
entidadEnAnimacion->fr = 0;
finAnimacion = entidadEnAnimacion->nFrames;
estado = ESTADO_ANIMACION;
}
}
else if(!Mesa->puedeUsarse)
{
mostrarMensaje = MSJ_USAR_GENERICO;
}
else
{
mostrarMensaje = MSJ_USAR_OBJETO_ESCENA;
}
}
else
{
mostrarMensaje = MSJ_USAR_GENERICO;
}
switch(mostrarMensaje)
{
case MSJ_USAR_GENERICO:
sprintf(mensajePantalla, "no se puede usar");
break;
case MSJ_USAR_OBJETO_ESCENA:
sprintf(mensajePantalla, obj1->mensajeNoUsar);
break;
case MSJ_USAR_OBJETO_INVENTARIO:
sprintf(mensajePantalla, obj2->mensajeNoUsar);
break;
}
if(mostrarMensaje != MSJ_NO_MOSTRAR)
{
tiempoMensajePantalla = TIEMPO_MENSAJE_PANTALLA;
}
return fin;
}
bool EscenarioHabitacion::eventoFinalizarAnimacion(Entidad *obj1)
{
finAnimacion = -1;
estado = ESTADO_PARADO;
return false;
Fernando Garcia Bernal
184
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
}
6.3.3.2 Escenario Exterior
El segundo escenario más simple, contiene dos entidades seleccionables y una no
seleccionable. Además consta del suelo como todos los escenarios.
•
Suelo:
61 - Vista perspectiva.
•
62 - Vista frontal.
63 – Textura.
EscenarioExterior.h:
class EscenarioExterior : public Escenario
{
public:
EscenarioExterior(Personaje *_Prota);
virtual ~EscenarioExterior(){};
// implementa los métodos específicos
// recoger, usar y eventoFinalizarAnimacion
// para este escenario
bool recoger(Entidad *obj1);
bool usar(Entidad *obj1, Entidad *obj2);
bool eventoFinalizarAnimacion(Entidad *obj1);
// define las variables para el personaje y para las entidades
// de este escenario
Personaje *Prota;
Entidad *Arbol, *Coco;
};
•
EscenarioExterior.cpp:
EscenarioExterior::EscenarioExterior(Personaje *_Prota):Escenario(suelo3,
suelo2Text, 64, 15.0f, -5.f, -1.2f)
Fernando Garcia Bernal
185
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
{
// Enlaza al protagonista
Prota = _Prota;
// Crea las Entidades
Arbol = new EntidadArbol();
Coco = new EntidadCoco();
// Vincula al protagonista, las entidades seleccionables
// y las no seleccionables
vincularProtagonista(Prota,.8f,.0f,-.5f);
vincularElementoSeleccionable(Arbol,-4.,.0f,.0f);
vincularElementoSeleccionable(Coco,-3.8,1.4f,-.3f);
// crea los mensajes que se mostrarán al usuario
// después de la escena
// primera tanda de mensajes
mensajes.push_back("Mmm");
mensajes.push_back("ya puedo descansar tranquilo.");
mensajes.push_back("FIN");
}
bool EscenarioExterior::recoger(Entidad *obj1)
{
int mostrarMensaje = MSJ_NO_MOSTRAR;
bool fin = false;
/**
* TODO: realizar acciones oportunas al recoger los objetos
*/
switch(mostrarMensaje)
{
case MSJ_RECOGER_GENERICO:
sprintf(mensajePantalla, "no se puede recoger");
break;
case MSJ_RECOGER_OBJETO_ESCENA:
sprintf(mensajePantalla, obj1->mensajeNoRecoger);
break;
}
if(mostrarMensaje != MSJ_NO_MOSTRAR)
{
tiempoMensajePantalla = TIEMPO_MENSAJE_PANTALLA;
}
return fin;
}
bool EscenarioExterior::usar(Entidad *obj1, Entidad *obj2)
Fernando Garcia Bernal
186
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
{
int mostrarMensaje = MSJ_NO_MOSTRAR;
bool fin = false;
// Si el objeto a user es el árbol,
// entonces provoca que se desprenda el
// coco que de él cuelga
if(strcmp(obj1->nombre, "arbol") == 0)
{
Arbol->puedeUsarse = false;
// inicia animación coco
{
entidadEnAnimacion = Coco;
entidadEnAnimacion->fr = 0;
finAnimacion = entidadEnAnimacion->nFrames;
estado = ESTADO_ANIMACION;
}
}
switch(mostrarMensaje)
{
case MSJ_USAR_GENERICO:
sprintf(mensajePantalla, "no se puede usar");
break;
case MSJ_USAR_OBJETO_ESCENA:
sprintf(mensajePantalla, obj1->mensajeNoUsar);
break;
case MSJ_USAR_OBJETO_INVENTARIO:
sprintf(mensajePantalla, obj2->mensajeNoUsar);
break;
}
if(mostrarMensaje != MSJ_NO_MOSTRAR)
{
tiempoMensajePantalla = TIEMPO_MENSAJE_PANTALLA;
}
return fin;
}
bool EscenarioExterior::eventoFinalizarAnimacion(Entidad *obj1)
{
bool fin = false;
// al terminar la animación del coco
// se termina el segundo escenario,
// y con él la partida
if(strcmp(obj1->nombre, "coco") == 0)
{
// test
PA_HideBg(1,3);
PA_ClearTextBg(1);
Fernando Garcia Bernal
187
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
PA_ShowBg(1,3);
// fin test
finAnimacion = -1;
estado = ESTADO_PARADO;
fin = true;
}
return fin;
}
6.3.4 Fichero main
Por último, el fichero principal donde se cargan todas las clases y se realizan las llamadas a
los bucles de juego.
int main()
{
// inicializa las librerías PAlib
// y la interfaz del usuario que
// representa el menú
inicializarMenu();
// inicializa la escena 3D
inicializaEscena3D();
// Creación de los punteros a las variables necesarias
// La variable protagonista se reutiliza para ambos
// escenarios
Personaje *Prota = new Personaje(prota_centrado, protaText);
Escenario *Escena = new EscenarioHabitacion(Prota);
Escenario *Escena2 = new EscenarioExterior(Prota);
// texto con mensaje inicial
Escena->mostrarTextoPantalla(0,4);
while (!Escena->actualizar())
{
PA_WaitForVBL();
}
Escena->mostrarTextoPantalla(5,7);
reiniciarEscena();
while (!Escena2->actualizar())
{
PA_WaitForVBL();
}
Fernando Garcia Bernal
188
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Implementación
Escena2->mostrarTextoPantalla(0, 2);
return 0;
}
Fernando Garcia Bernal
189
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Fernando Garcia Bernal
Implementación
190
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Conclusiones
Capítulo 7. Conclusiones
En este apartado a modo de colofón, se anotan los pensamientos que han ido surgiendo a lo
largo del desarrollo tanto de la aplicación como de la memoria. Esto abarca mejoras que se
han contemplado una vez comenzado el desarrollo, diferentes implementaciones que por
diversos motivos no se pudieron llevar a cabo o futuros desarrollos que pueden realizarse al
juego.
7.1 Inconvenientes y opiniones
Una de las características más importantes para llevar a cabo el desarrollo de este tipo de
aplicaciones, es que están muy vinculadas a las comunidades que le dan soporte. Es algo que
en los capítulos introductorios se comentó como una ventaja, y ciertamente lo es.
Seguramente hace unos años sería impensable poder participar en colectivos agrupados por un
interés común, como puede ser el desarrollo de aplicaciones para una videoconsola
determinada, al estar tan dispersos por el mundo sus colaboradores. Ahora las herramientas
que existen en la red (blog, wiki, foro,…) permiten que colectivos de este tipo puedan
intercambiar ideas, formación, opiniones e incluso recursos; que permiten facilitar mucho las
cosas.
En el caso de este proyecto, la comunidad que da soporte a la librería más utilizada en él,
PAlib, ha sido determinante gracias a la gran cantidad de documentación que han elaborado y
Fernando Garcia Bernal
191
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Conclusiones
que mejoran de una manera dinámica gracias al sistema wiki. Sin embargo, para la aplicación
que se ha desarrollado, los principales bloqueos encontrados han sido que para algunos
aspectos concretos no se ha encontrado el soporte necesario. A veces por ser temas muy
concretos los que se han tratado, o en otras ocasiones por tener que desarrollarse soluciones
específicas que evidentemente no podían estar documentadas en ningún sitio, a lo sumo
ofreciendo orientación y opiniones.
El caso de la librería PAlib es el ejemplo de cómo una persona sin experiencia en desarrollo
en esta videoconsola puede recibir mucha información necesaria que le facilite el comenzar
poco a poco cada vez realizando pasos más complicados. Sin embargo, la otra librería en la
que PAlib se basa, libnds (recordamos que esta estaba enfocada a más bajo nivel de
abstracción y que era más compleja de utilizar) no existe tanta documentación. En principio
este desarrollo no se ha basado tanto en esta librería como en PAlib y por eso no había
problema, sin embargo una parte importante del proyecto si requería hacer uso de libnds.
Concretamente la parte de 3D. En este caso no ha sido tan fácil encontrar soporte a modo de
guías de desarrollo o personas que participen en foros a los que se pudieran plantear dudas. La
manera de ir aprendiendo ha sido a través de los ejemplos que trae el kit de desarrollo para
esta plataforma, devkitpro. El proceso ha consistido en ir analizando cada uno de los ejemplos
y a partir de estos ir sacando conclusiones. Aunque también han existido un par de ventajas
que han facilitado un poco el aprendizaje para desarrollo 3D en Nintendo DS. La primera ha
sido de la característica open source de la librería libnds que en un momento dado se puede
recurrir a ver directamente como implementa para acertar en un desarrollo adecuado, y la otra
ventaja es que la parte 3D es una adaptación de la conocida librería openGL. Esto último no
significa que se pueda utilizar toda la documentación existente sobre openGL para poder
realizar aplicaciones 3D en Nintendo DS, ya que es una versión bastante reducida, sin
embargo, sí me ha servido en ocasiones para orientarme.
Una de las preocupaciones que he tenido durante la realización del proyecto, desde el
planteamiento hasta el final del desarrollo, ha sido la idea de que llegado a un punto podría
topar con un obstáculo insalvable. Es cierto que las librerías que existen para esta plataforma
están bien hechas, muy documentadas y mucha gente realiza aplicaciones sobre estas. Pero de
nuevo es notable la poca gente que se ha dedicado al 3D con estas. Existía la posibilidad de
encontrar dificultades técnicas y así fue, sin embargo no fueron tan graves como para llegar a
Fernando Garcia Bernal
192
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Conclusiones
hacer inviable la aplicación. Los inconvenientes que más tiempo invertí en resolver fueron
dos y ambos relacionados con la parte 3D. El primero fue cuando ya había creado la
aplicación para convertir modelos 3D a la estructura de C++ para openGL, al probar algunos
modelos creados sucedía que no se mostraban en la aplicación. Sin motivo aparente había
algunos modelos que se mostraban y otros no. Después de varios días buceando en Internet,
en un foro encontré a una persona que comentaba que existe una limitación en el número
decimal que se emplea en las coordenadas de los objetos 3D y que debía ser inferior a 7.0f. El
otro inconveniente fue al empezar a cargar las escenas con diferentes entidades, cada una con
una textura propia. Al cargar más de tres aparecían de color blanco. Del mismo modo acudí al
gran buscador en busca de respuestas. Tras un par de días investigando, en otro foro encontré
la solución. Existe otra limitación en la cantidad de memoria destinada a la carga de texturas
mientras que yo estaba creando todas a una resolución de 128x128 píxeles, se llenaba la
memoria con tres texturas. La solución fue usar todas las texturas de 64x64 excepto las más
complejas como la del usuario protagonista. De esta manera por cada textura de 128x128 se
podían usar cuatro de 64x64.
A parte de los inconvenientes, la tarea que más tiempo dediqué a pensar cómo resolver fue la
carga de figuras 3D. El resultado era la aplicación con interfaz gráfica que convierte un
fichero en formato X en otro binario. Sin embargo, para llegar hasta ese punto tuve que
investigar bastante y tomar varias decisiones. Como ya se ha comentado se utiliza una versión
simplificada de openGL. La única manera que disponía para pintar estructuras 3D en la
escena es a través de las primitivas para pintar triángulos y quads. Para pintar cubos no hay
problema, pero pintar objetos 3D modelados es otro asunto. Pasé un tiempo investigando,
preguntando e informándome si existía algún exportador de formatos pero no llegué a nada en
claro, ya que si lo había no cumplía con todas mis necesidades. La solución era implementarlo
desde cero. Tenía claro que los modelos los haría con alguna herramienta de modelado 3D
simple y gratuita, por ese entonces encontré un grupo de iniciación al modelado que usaban la
herramienta wings3D que me sirvió para aprender a manejarla. Ya había conseguido elaborar
los modelos, ahora necesitaba una estructura que pudiera parsear y convertir a otra estructura
que cumpliera con mis necesidades y pudiera emplear junto con las primitivas de openGL.
Uno de los inconvenientes siguientes fue decidir cuál estructura de entre todos los estándares
que existen podría valerme, realmente aquí el problema no era que no existieran posibilidades
factibles, al revés, existen muchísimas. Buscando en Internet guías para leer ficheros que
Fernando Garcia Bernal
193
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Conclusiones
contenían figuras 3D, encontré varios enlaces que hacían referencia al formato usado por
DirectX decidiéndome por él en su exportación de texto, en lugar de datos binarios. También
tuve que encontrar la manera de obtener dicho formato a partir de wings3D. La solución
consistía en exportar el modelo desde wings3D a formato OBJ y éste llevarlo a 3D Studio
donde a través de un plugin se exporta a formato X. Por último fue necesario decidir qué
estructura definir para organizar la información de la figura 3D.
7.2 Futuros desarrollos
Esta aplicación se ha planteado de manera que sea sencillo crear diferentes aventuras,
modelando nuevas figuras 3D y creando las clases pertinentes. Sin embargo, quedarían
algunos aspectos que pueden aportar mejoras considerables por implementar.
•
Sonido
Esta aplicación consiste en un sistema multimedia y cuánto más atractivo resulte al usuario,
mejor podrá venderse el producto. Uno de los aspectos fundamentales que todos los juegos
incorporan es un sistema de sonido y efectos especiales. En esta aplicación no se ha podido
llevar a cabo pero no resultaría algo demasiado explicado. Está explicado en el apartado de la
librería PAlib cuales son las funciones responsables para esta utilidad. La idea sería que
durante la partida se escuchara una música y que según los eventos se reproduciesen
diferentes eventos. No estaría mal la idea de asociar efectos de sonido a las Entidades puesto
que por ejemplo una puerta puede chirriar al abrirse, o un objeto al caerse producir un ruido.
Del mismo modo cada objeto Escenario puede tener una música asociada de fondo.
•
Guardar partidas
Esta aventura implementada es muy corta. Sin embargo, las aventuras que estamos
acostumbrados duran muchas horas de juego. Esto hace necesario un sistema para guardar los
progresos, acompañado de una interfaz que permita cargar la partida en un tiempo posterior.
Permitiendo al usuario apagar la consola quedando los datos residentes en una memoria no
volátil. Esto es posible gracias a la librería PAlib ya que ha incorporado funciones para
acceder al sistema de ficheros de la consola. Esto puede encontrarse en la sección PA File
System [29] de la API.
Fernando Garcia Bernal
194
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
•
Conclusiones
Micrófono
La videoconsola Nintendo DS tiene además otros componentes hardware, quizás menos
convencionales en juegos, que pueden ser explotados y proporcionar experiencias muy
curiosas. Este es el caso del micrófono que trae incorporado y que también con la librería
PAlib permite ser accedido desde software [30].
Fernando Garcia Bernal
195
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Fernando Garcia Bernal
Conclusiones
196
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Apéndice A. Glosario
Apéndice A. Glosario
• Software Development Kit (SDK o “devkit”)
Acrónimo de software development kit, se trata de un conjunto de aplicación que posibilita a
un programador a desarrollar aplicaciones para una plataforma específica: framework,
plataforma hardware, videoconsola, sistema operativo,…. Normalmente un SDK incluye una
o más API, herramientas de programación y documentación.
• Homebrew
Término aplicado frecuentemente aplicado a los videojuegos que son desarrollados por
aficionados. Podría traducirse por software casero.
• Retrocompatibilidad
En el ámbito de las videoconsolas, capacidad de un sistema para poder cargar juegos
desarrollados para los sistemas predecesores a este.
• Flashear
Utilizado dentro del contexto de manipular la videoconsola Nintendo DS para permitir cargar
aplicaciones homebrew en ella, a través de la modificación del firmware original, con el
riesgo que esto supone si sucediera algún problema durante el proceso.
Fernando Garcia Bernal
197
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Apéndice A. Glosario
• Firmware
Programa que está incluido en un dispositivo hardware, o una memoria ROM; que puede ser
actualizado por el usuario. Este es ejecutado por el microprocesador del dispositivo. La
actualización del firmware suele ser realizada para corregir errores o añadir nuevas
funcionalidades al dispositivo.
• Open source
Se trata de un conjunto de artículos y prácticas sobre cómo escribir software, la más
importante de ellas es que el código fuente está disponible. La definición de Open Source fue
creada por Bruce Pernees para el proyecto Debian y actualmente es mantenida por la “Open
Source Initiative” [31] que añade un significado adicional al término: una persona no sólo
debería poder obtener el código fuente, sino además tener el derecho de usarlo.
• API (Application Program Interface)
Acrónimo de Interfaz de Programación de Aplicaciones, es el conjunto de rutinas, protocolos
y herramientas para desarrollar aplicaciones software. El objetivo de esta es proveer al
desarrollador de una capa de un nivel de abstracción superior que suponga una facilidad para
la construcción de la aplicación.
• supercard, G6 o M3
Distintas compañías que han desarrollado sistemas de carga de backups que se introducen en
los diferentes slots de la videoconsola Nintendo DS.
• Frame
Se trata de un de las muchas imágenes fotográficas que componen el efecto visual de una
animación. Normalmente 24 frames son necesarios para un segundo de película.
• Fps (Frame por segundo)
Acrónimo que indica la frecuencia a la cual un dispositivo que reproduce imágenes, o frames,
por cada segundo.
Fernando Garcia Bernal
198
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Apéndice A. Glosario
• Tiles
Imagen pensada para ser utilizada junto a otras con el fin de formar una imagen de mayor
tamaño como si de un mosaico se tratara.
• Pad
También conocido como gamepad, joypad o pad de control; es un tipo de control para juegos
a través de las manos donde los dígitos son usados para presionar sobre los botones.
Generalmente traen un conjunto de botones de acción para manejarlos con el pulgar derecho y
un control de dirección, o cruceta, manejada con la mano izquierda.
• Sprite
Se trata de una imagen bidimiensinal que puede ser integrada en una escena de animación.
• Alpha-blending
Consiste en el efecto visual de permitir efectos de transparencia en los sprites. El valor de
alpha en el código de color puede tomar desde 0.0 a 1.0, donde el primer valor representa
completamente transparente, y el valor más alto indica un color totalmente opaco.
• Formato raw
Este tipo de formato es un concepto genérico que viene a indicar que los datos se han tomado
en ‘bruto’, es decir, sin tratamiento o sin procesar.
• RGB (Red, Green, Blue)
El modo de color RGB consiste en la combinación de los colores primarios rojo, verde y azul
en distintas proporciones para reproducir el resto de gama de colores.
• mapeo UV
Es el proceso en el modelador 3D, para hacer que una imagen 2D envuelva o represente a un
modelo 3D.
Fernando Garcia Bernal
199
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Apéndice A. Glosario
64 - Representación del mapeado UV de un cubo.
Fernando Garcia Bernal
200
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Apéndice B. Contenido del
CD-ROM
Apéndice B. Contenido del CD-ROM
En este apéndice se explica la distribución de carpetas y ficheros que se sigue en el CDROM
con el que se entrega esta memoria.
•
Aplicación: Almacena los recursos relacionados con la aplicación desarrollada a lo largo
de esta memoria
•
proyectoDS: Aplicación principal de este proyecto que consiste en un juego para
Nintendo DS.
•
Arte: Dentro de esta carpeta se encuentran los sprites, modelos 3D y las texturas
que se usan en la aplicación.
•
Por cada escenario se encuentran las siguientes carpetas:
•
wings: modelos 3D en el formato de wings3D.
•
texturas: texturas utilizadas en los modelos 3D creados.
•
3ds y modelos animados: se incluyen los modelos exportados a 3ds y los
modelos con la animación realizada en 3ds Studio.
•
•
X: ficheros que representan los modelos una vez convertidos al formato X.
•
bin: modelos exportados al formato que debe utilizarse en la aplicación.
Código: Aloja el código de la aplicación
Fernando Garcia Bernal
201
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Apéndice B. Contenido del
CD-ROM
•
Documentación: Documentación de la aplicación generada a partir de los
comentarios escritos en el código fuente.
•
Ejecutables: Incluye los ficheros que se pueden ejecutar en la videoconsola o
emuladores.
•
XtoCPPWin: Aplicación de escritorio que transforma un fichero X en un fichero
binario que puede ser cargado como modelo 3D en las aplicación desarrollada para
Nintendo DS.
•
•
Código: Aloja el código de la aplicación.
•
Ejecutable: Aplicación ya compilada lista para usar.
Memoria: Se guarda esta memoria en formato Word y Pdf, además de los códigos usados
en ésta.
•
Códigos de la memoria: Aquí se pueden encontrar los códigos utilizados durante
algunos capítulos de la memoria.
•
Software: Aplicaciones que se usan para el desarrollo del proyecto.
•
Conversor Sprites: Herramienta que convierte sprites al formato empleado por las
aplicaciones de PAlib.
•
Conversor Texturas: Convierte las texturas al formato empleado en las aplicaciones
gráficas 3D de Nintendo DS.
•
devkitpro: software que incluye las herramientas necesarias para el desarrollo de
aplicaciones para Nintendo DS entre otras videoconsolas. El instalador incluido
necesita conectarse a Internet para descargar la aplicación.
•
Emuladores: Diferentes emuladores mencionados en la memoria que se pueden
utilizar para probar las aplicaciones en la máquina de desarrollo.
•
•
DeSmuME
•
DualiS
•
iDeaS
•
NeonDS
PAlib Wizard para Visual Studio: Plugin que se instala para que aparezca un asistente
de creación de proyectos PAlib en el entorno de desarrollo Visual Studio.
•
Utilidad para compilar: Conjunto de ficheros que se utiliza para compilar los ficheros
fuente y obtener un ejecutable para la videoconsola Nintendo DS.
Fernando Garcia Bernal
202
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Apéndice B. Contenido del
CD-ROM
Fernando Garcia Bernal
203
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Apéndice B. Contenido del
CD-ROM
•
Fernando Garcia Bernal
204
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Bibliografía
Bibliografía
Enlaces
[1]
“I Máster Universitario en Creación y Desarrollo de Videojuegos” de la Universidad
de Málaga
http://www.uma.es/estudios/propias/m81401306.html
[2]
“Máster en Desarrollo de Videojuegos” de la Universidad Complutense de Madrid
http://www.fdi.ucm.es/juegos3d/
[3]
“Máster en Diseño y Programación de Videojuegos” de la Universidad Europea de
Madrid
http://www.uem.es/postgrado/master-en-diseo-y-programacion-de-videojuegos-vedicion/presentacion
[4]
Librería libnds (inglés)
http://www.devkitpro.org/
[5]
Librería PAlib (inglés)
http://palib.com/
[6]
Devkitpro (inglés)
http://www.devkitpro.org/
[7]
Nintendo DS (inglés)
http://www.nintendods.com/
Fernando Garcia Bernal
205
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
[8]
Bibliografía
DSemu (inglés)
http://www.ndsemu.com/
[9]
Daulis (inglés)
http://dualis.1emu.net/
[10]
Ideas (inglés)
http://www.ideasemu.org/
[11]
Alone in de Dark, 1993 (inglés)
http://www.adventureclassicgaming.com/index.php/site/reviews/33/
[12]
Grim Fandago, 1998 (inglés)
http://www.gamespot.com/gamespot/features/all/greatestgames/p-19.html
[13]
API de la librería PAlib (inglés)
http://www.palib.info/Doc/PAlibDoc%20Eng/modules.html
[14]
Net Framework (inglés)
http://www.microsoft.com/downloads/details.aspx?FamilyID=0856eacb-4362-4b0d-8eddaab15c5e04f5&DisplayLang=en
[15]
No$GBA (inglés)
http://nocash.emubase.de/gba.htm
[16]
Programmers Notepad 2 (inglés)
http://www.pnotepad.org/
[17]
Visual HAM (inglés)
http://www.console-dev.de/visualham.html
[18]
Visual Studio C++ 2005 Express Edition
http://www.microsoft.com/spanish/msdn/vstudio/express/VC/default.mspx
[19]
Usar PAlib con Visual Studio C++ 200 Express Edition (inglés)
http://www.palib.info/wiki/doku.php?id=day1#using_palib_with_visual_c_2005_express
[20]
DSFTP (inglés)
http://giesler.biz/bjoern/en/sw_dsftp.html
[21]
Adobe Flash
http://www.adobe.com/go/BPBIQ
[22]
Ilustraciones Sandra Arteaga
http://www.guiadeilustradores.com/portafolio/portafolio.php?opc=galeria&idper=485&idima=0
[23]
UML (inglés)
http://www.uml.org/
Fernando Garcia Bernal
206
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
[24]
Bibliografía
wings3d
http://www.wings3d.es/
[25]
Autodesk 3ds Max (inglés)
http://www.autodesk.es/3dsmax
[26]
PandaSoft (inglés)
http://www.andytather.co.uk/Panda/directxmax.aspx
[27]
Formato DirectX (inglés)
http://msdn.microsoft.com/archive/default.asp?url=/archive/enus/dx81_c/directx_cpp/Graphics/Reference/FileFormat/FileFormat.asp
[28]
Microsoft Developer Network
http://msdn2.microsoft.com/es-es/default.aspx
[29]
API para programar el micrófono de Nintendo DS (inglés)
http://palib.info/Doc/PAlibDoc%20Eng/group___micro.html
[30]
API para programar el micrófono de Nintendo DS (inglés)
http://palib.info/Doc/PAlibDoc%20Eng/group___micro.html
[31]
Open Source Initiative
http://opensource.org/
Documentación utilizada ordenada por capítulos
•
Capítulo 2. Videoconsola Nintendo DS:
Referencia técnica no oficial sobre Nintendo DS.
http://www.bottledlight.com/ds/index.php/Main/HomePage
Hardware para cargar aplicaciones homebrew en Nintendo DS
http://en.wikipedia.org/wiki/Nintendo_DS_homebrew
http://en.wikipedia.org/wiki/Nintendo_DS_booting_tools
http://tobw.net/dswiki/index.php?title=How_to_run_code
Desarrollo aplicaciones para Nintendo DS
http://forum.gbadev.org/index.php?c=8
http://dev-scene.com/
http://www.warioworld.com/
Fernando Garcia Bernal
207
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
Bibliografía
Librería libnds
http://devkitpro.sourceforge.net/devkitProWiki/libnds/
http://www.drunkencoders.com/documents/DS/ndslib.htm
http://www.elotrolado.net/showthread.php?s=&threadid=560011 (mención especial a la
persona tras el nick de webez por la excelencia de los artículos que alojó en este foro)
Librería PAlib
http://palib.com/
http://www.palib.info/Doc/PAlibDoc%20Eng/modules.html
http://www.palib.info/wiki/doku.php
•
Capítulo 3. Librería PAlib:
Preparación del entorno
http://www.palib.info/wiki/doku.php?id=day1#using_palib_with_visual_c_2005_express
http://www.microsoft.com/downloads/details.aspx?FamilyID=0856eacb-4362-4b0d-8eddaab15c5e04f5&DisplayLang=en
http://giesler.biz/bjoern/en/sw_dsftp.html
PAlib
http://sourceforge.net/project/showfiles.php?group_id=142901&package_id=168612
http://www.palib.info/wiki/doku.php
http://www.talfi.net/xoops/modules/newbb/viewtopic.php?topic_id=50&forum=23
http://www.palib.info/Doc/PAlibDoc%20Eng/group___text.html
http://www.palib.info/wiki/doku.php?id=day4
Programación 3D con openGL y Nintendo DS
http://www.palib.info/wiki/doku.php?id=day10
http://delfare.pizz.biz/jour6.htm
http://www.opengl.org/documentation
http://lazmike.nintendev.com/tutorials/
http://nehe.gamedev.net/lesson.asp?index=01
Fernando Garcia Bernal
208
DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS
<DIRECTORIO
DE
INSTALACIÓN
Bibliografía
DEVKITPRO>\examples\nds\Graphics\3D\Misc
(*nota: algunos ejemplos dan problemas porque usan elementos obsoletos. Por ejemplo, es
necesario cambiar glIdentity por glLoadIdentity)
•
Capítulo 5. Fase de Diseño:
Diagramas de clases UML
http://www.agilemodeling.com/artifacts/classDiagram.htm
http://www.ibm.com/developerworks/rational/library/content/RationalEdge/sep04/bell/
•
Capítulo 6. Implementación:
Programación 3D
http://www.console-dev.de/n3d.html
Formatos 3D
http://local.wasp.uwa.edu.au/~pbourke/dataformats/
Evan Pipho, “Focus on 3D Models”, The Premier Press (Game Development Series), 2003.
Formato Direct X
http://www.xbdev.net/3dformats/x/xfileformat.php
http://local.wasp.uwa.edu.au/~pbourke/dataformats/directx/
http://msdn.microsoft.com/archive/default.asp?url=/archive/enus/dx81_c/directx_cpp/Graphics/Reference/FileFormat/FileFormat.asp
http://www.andytather.co.uk/Panda/directxmax_downloads.aspx
Quateriones
http://msdn2.microsoft.com/eses/library/microsoft.windowsmobile.directx.quaternion(VS.80).aspx
Búsqueda de caminos
Mark A. DeLoura, “Game Programming Gems 1”, Charles River Media, 2000.
Stuart Jonathan Russell, Peter Norvig, “Artificial Intelligence: A Modern Approach” 2nd
Edition, Prentice Hall, 2003.
Fernando Garcia Bernal
209
Descargar