218
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

Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

  • Upload
    jfg8721

  • View
    3.441

  • Download
    25

Embed Size (px)

Citation preview

Page 1: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

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

Page 2: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas
Page 3: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

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.:

Page 4: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas
Page 5: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

I

Í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 Hardware................................................................................................ 6

2.1.1 Hardware adicional para cargar aplicaciones .....................................................6

2.2 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

Page 6: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

II

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

Page 7: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

III

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

Page 8: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

IV

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

Page 9: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas
Page 10: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Introducción

Fernando Garcia Bernal 1

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

Page 11: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Introducción

Fernando Garcia Bernal 2

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

Page 12: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Introducción

Fernando Garcia Bernal 3

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

Page 13: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Introducción

Fernando Garcia Bernal 4

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.

Page 14: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Videoconsola Nintendo DS

Fernando Garcia Bernal 5

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.

Page 15: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Videoconsola Nintendo DS

Fernando Garcia Bernal 6

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

Page 16: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Videoconsola Nintendo DS

Fernando Garcia Bernal 7

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):

Page 17: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Videoconsola Nintendo DS

Fernando Garcia Bernal 8

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.

Page 18: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Videoconsola Nintendo DS

Fernando Garcia Bernal 9

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

Page 19: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Videoconsola Nintendo DS

Fernando Garcia Bernal 10

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

Page 20: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Videoconsola Nintendo DS

Fernando Garcia Bernal 11

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.

Page 21: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Videoconsola Nintendo DS

Fernando Garcia Bernal 12

Page 22: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 13

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].

Page 23: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 14

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

Page 24: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 15

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

Page 25: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 16

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.

Page 26: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 17

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.

Page 27: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 18

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 referencia a algo comentado anteriormente. Se trata de

Page 28: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 19

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

Page 29: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 20

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.

Page 30: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 21

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:

Page 31: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 22

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:

Page 32: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 23

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

Page 33: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 24

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, // Pantalla 0, // Número de paleta (void*)sprite0_Pal); // Nombre de paleta PA_CreateSprite( 0, // Pantalla 0, // Número de sprite (void*)vaisseau_Sprite, // Nombre de sprite OBJ_SIZE_32X32, // Tamaño de sprite 1, // Modo de color 256

Page 34: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 25

0, // Número de paleta de sprite 50, 50); // 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.

Page 35: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 26

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.

Page 36: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 27

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

Page 37: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 28

#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, // Pantalla 0, // Número de paleta (void*)sprite0_Pal); // 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 PA_SetSpriteXY(0, 0, x, y); por esta

Page 38: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 29

PA_SetSpriteXY(0, 0, Stylus.X, 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, // Pantalla 0, // Número de paleta (void*)sprite0_Pal); // 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

Page 39: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 30

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

Page 40: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 31

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, // Pantalla 0, // Número de paleta (void*)sprite0_Pal); // 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

Page 41: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 32

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.

Page 42: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 33

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, 1, 0, 0, 50); PA_CreateSprite( 0, 1, (void*)mollusk_Sprite, OBJ_SIZE_32X32, 1, 0, 64, 50); PA_CreateSprite( 0, 2, (void*)mollusk_Sprite, OBJ_SIZE_32X32, 1, 0, 128, 50); PA_CreateSprite( 0, 3, (void*)mollusk_Sprite, OBJ_SIZE_32X32, 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

Page 43: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 34

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();

Page 44: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 35

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.

Page 45: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 36

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.

Page 46: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 37

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);

Page 47: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 38

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.

Page 48: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 39

Á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);

Page 49: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 40

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.

Page 50: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 41

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 ", angle);

Page 51: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 42

// 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.

Page 52: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 43

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.

Page 53: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 44

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 para PA_Lib #include "saberoff.h" // 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.

Page 54: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 45

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 para PA_Lib #include "modfile.h" // 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.

Page 55: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 46

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();

Page 56: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 47

// 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);

Page 57: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 48

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

Page 58: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 49

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…

Page 59: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 50

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

Page 60: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 51

gluLookAt( 0.0, 0.0, 1.0, // Posición de la cámara 0.0, 0.0, 0.0, // Hacia donde apunta 0.0, 1.0, 0.0); // 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

Page 61: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 52

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

Page 62: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 53

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); // Establece el color de // vértice a Rojo glVertex3f( 0.0f, 1.0f, 0.0f); // Vértice superior glColor3f(0.0f,1.0f,0.0f); // Establece el color de // vértice a Verde glVertex3f(-1.0f,-1.0f, 0.0f); // Vértice inferior // izquierdo glColor3f(0.0f,0.0f,1.0f); // Establece el color de // vértice a Azul glVertex3f( 1.0f,-1.0f, 0.0f); // Vértice inferior derecho // Fin Triángulo glEnd(); // 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, 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(); 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

Page 63: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 54

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; // Ángulo para el triángulo float rquad; // Ángulo para el cuadrado int DrawGLScene() // Aquí es donde se pinta todo

Page 64: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 55

{ // 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); // Establece el color de // vértice a Rojo glVertex3f( 0.0f, 1.0f, 0.0f); // Vértice superior glColor3f(0.0f,1.0f,0.0f); // Establece el color de // vértice a Verde glVertex3f(-1.0f,-1.0f, 0.0f); // Vértice inferior // izquierdo glColor3f(0.0f,0.0f,1.0f); // Establece el color de // vértice a Azul glVertex3f( 1.0f,-1.0f, 0.0f); // Vértice inferior derecho // 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,-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;

Page 65: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 56

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.

Page 66: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 57

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); // Rojo glVertex3f( 0.0f, 1.0f, 0.0f); // Vértice superior (Frente) glColor3f(0.0f,1.0f,0.0f); // Verde glVertex3f(-1.0f,-1.0f, 1.0f); // Vértice inferior // izquierdo (Frente) glColor3f(0.0f,0.0f,1.0f); // Azul glVertex3f( 1.0f,-1.0f, 1.0f); // Vértice inferior derecho // (Frente) glColor3f(1.0f,0.0f,0.0f); // Rojo glVertex3f( 0.0f, 1.0f, 0.0f); // Vértice superior // (Derecha) glColor3f(0.0f,0.0f,1.0f); // Azul glVertex3f( 1.0f,-1.0f, 1.0f); // Vértice inferior // izquierdo (Derecha) glColor3f(0.0f,1.0f,0.0f); // Verde glVertex3f( 1.0f,-1.0f, -1.0f); // Vértice inferior derecho // (Derecha) glColor3f(1.0f,0.0f,0.0f); // Rojo glVertex3f( 0.0f, 1.0f, 0.0f); // Vértice superior (Atrás) glColor3f(0.0f,1.0f,0.0f); // Verde glVertex3f( 1.0f,-1.0f, -1.0f); // Vértice inferior // izquierdo (Atrás) glColor3f(0.0f,0.0f,1.0f); // Azul glVertex3f(-1.0f,-1.0f, -1.0f); // Vértice inferior derecho // (Atrás) glColor3f(1.0f,0.0f,0.0f); // Rojo glVertex3f( 0.0f, 1.0f, 0.0f); // Vértice superior // (Izquierda) glColor3f(0.0f,0.0f,1.0f); // Azul glVertex3f(-1.0f,-1.0f,-1.0f); // Vértice inferior // izquierdo (Izquierda)

Page 67: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 58

glColor3f(0.0f,1.0f,0.0f); // Verde glVertex3f(-1.0f,-1.0f, 1.0f); // 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); // Verde glVertex3f( 1.0f, 1.0f,-1.0f); // Vértice superior derecho // (Arriba) glVertex3f(-1.0f, 1.0f,-1.0f); // Vértice superior // izquierdo (Arriba) glVertex3f(-1.0f, 1.0f, 1.0f); // Vértice inferior // izquierdo (Arriba) glVertex3f( 1.0f, 1.0f, 1.0f); // Vértice inferior derecho // (Arriba) glColor3f(1.0f,0.5f,0.0f); // Color naranja glVertex3f( 1.0f,-1.0f, 1.0f); // Vértice superior derecho // (Abajo) glVertex3f(-1.0f,-1.0f, 1.0f); // Vértice superior // izquierdo (Abajo) glVertex3f(-1.0f,-1.0f,-1.0f); // Vértice inferior // izquierdo (Abajo) glVertex3f( 1.0f,-1.0f,-1.0f); // Vértice inferior derecho // (Abajo) glColor3f(1.0f,0.0f,0.0f); // Rojo glVertex3f( 1.0f, 1.0f, 1.0f); // Vértice superior derecho // (Frente) glVertex3f(-1.0f, 1.0f, 1.0f); // Vértice superior // izquierdo (Frente) glVertex3f(-1.0f,-1.0f, 1.0f); // Vértice inferior // izquierdo (Frente) glVertex3f( 1.0f,-1.0f, 1.0f); // Vértice inferior derecho // (Frente) glColor3f(1.0f,1.0f,0.0f); // Amarillo glVertex3f( 1.0f,-1.0f,-1.0f); // Vértice superior derecho // (Atrás) glVertex3f(-1.0f,-1.0f,-1.0f); // Vértice superior // izquierdo (Atrás) glVertex3f(-1.0f, 1.0f,-1.0f); // Vértice inferior // izquierdo (Atrás) glVertex3f( 1.0f, 1.0f,-1.0f); // Vértice inferior derecho // (Atrás) glColor3f(0.0f,0.0f,1.0f); // Azul glVertex3f(-1.0f, 1.0f, 1.0f); // Vértice superior derecho // (Izquierda) glVertex3f(-1.0f, 1.0f,-1.0f); // Vértice superior // izquierdo (Izquierda) glVertex3f(-1.0f,-1.0f,-1.0f); // Vértice inferior // izquierdo (Izquierda)

Page 68: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 59

glVertex3f(-1.0f,-1.0f, 1.0f); // Vértice inferior derecho // (Izquierda) glColor3f(1.0f,0.0f,1.0f); // Violeta glVertex3f( 1.0f, 1.0f,-1.0f); // Vértice superior derecho // (Derecha) glVertex3f( 1.0f, 1.0f, 1.0f); // Vértice superior // izquierdo (Derecha) glVertex3f( 1.0f,-1.0f, 1.0f); // Vértice inferior // izquierdo (Derecha) glVertex3f( 1.0f,-1.0f,-1.0f); // 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.

Page 69: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 60

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();

Page 70: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 61

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, -1.0f, 1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // Cara trasera glNormal3f( 0.0f, 0.0f,-1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // Cara superior glNormal3f( 0.0f, 1.0f, 0.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // Cara inferior glNormal3f( 0.0f,-1.0f, 0.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);

Page 71: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 62

glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // Cara derecha glNormal3f( 1.0f, 0.0f, 0.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // Cara izquierda glNormal3f(-1.0f, 0.0f, 0.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); glEnd(); 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.

Page 72: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 63

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

Page 73: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 64

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

Page 74: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 65

// 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;

Page 75: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 66

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); }

Page 76: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 67

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.

Page 77: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 68

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> // Librería usada para detectar posiciones

Page 78: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 69

// Definición de tipos typedef enum { NOTHING, RED, GREEN, BLUE } Clickable; // Variables globales Clickable clicked; // Contiene qué ha sido pulsado int closeW; // Distancia más cercana a la cámara int polyCount; // 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)

Page 79: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 70

{ 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; } }

Page 80: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 71

} 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

Page 81: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 72

// 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, -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 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 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 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

Page 82: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 73

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 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(); }

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.

Page 83: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Librería PAlib

Fernando Garcia Bernal 74

Page 84: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Fase de Análisis del Juego

Fernando Garcia Bernal 75

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

Page 85: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Fase de Análisis del Juego

Fernando Garcia Bernal 76

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.

Page 86: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Fase de Análisis del Juego

Fernando Garcia Bernal 77

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

Page 87: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Fase de Análisis del Juego

Fernando Garcia Bernal 78

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.

Page 88: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Fase de Análisis del Juego

Fernando Garcia Bernal 79

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:

Page 89: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Fase de Análisis del Juego

Fernando Garcia Bernal 80

• 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.

Page 90: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Fase de Diseño

Fernando Garcia Bernal 81

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

Page 91: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Fase de Diseño

Fernando Garcia Bernal 82

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:

Page 92: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Fase de Diseño

Fernando Garcia Bernal 83

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.

Page 93: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Fase de Diseño

Fernando Garcia Bernal 84

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.

Page 94: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Fase de Diseño

Fernando Garcia Bernal 85

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.

Page 95: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Fase de Diseño

Fernando Garcia Bernal 86

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.

Page 96: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Fase de Diseño

Fernando Garcia Bernal 87

17 - Estructura UML de la clase Escenario.

Page 97: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Fase de Diseño

Fernando Garcia Bernal 88

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.

Page 98: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Fase de Diseño

Fernando Garcia Bernal 89

18 - Relaciones para las clases personalizadas de Escenario.

Page 99: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Fase de Diseño

Fernando Garcia Bernal 90

Page 100: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 91

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.

Page 101: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 92

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.

Page 102: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 93

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.

Page 103: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 94

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.

Page 104: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 95

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.

Page 105: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 96

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

Page 106: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 97

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 Contiene FrameTransformMatrix Frame

Frame

Mesh MeshNormals MeshTextureCoords MeshMaterialList SkinMeshHeader

Mesh

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.

Page 107: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 98

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;

[...]

}

Page 108: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 99

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.

Page 109: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 100

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 {

Page 110: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 101

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;

Page 111: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 102

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;;

}

}

}

Page 112: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 103

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

Page 113: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 104

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;

Page 114: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 105

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

Page 115: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 106

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

Page 116: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 107

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;

Page 117: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 108

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’.

Page 118: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 109

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:

/************************************

Page 119: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 110

* 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.

Page 120: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 111

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:

Page 121: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 112

/** * 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);

Page 122: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 113

/** 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

Page 123: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 114

/** Asignación */ vector3 &operator = (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

Page 124: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 115

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

Page 125: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 116

* @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 m_w; float m_z; float m_y; float 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:

Page 126: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 117

/** 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]; // nombre del material char fichero[MAXCAD]; // nombre del fichero donde se almancena // el material int ancho, alto; // componentes ancho y alto int veces; // 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

Page 127: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 118

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)

Page 128: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 119

{ 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();

Page 129: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 120

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:

Page 130: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 121

/** 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;

Page 131: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 122

/** 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));

Page 132: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 123

// 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

Page 133: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 124

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

Page 134: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 125

* @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

Page 135: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 126

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.

Page 136: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 127

*/ 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]; /**

Page 137: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 128

* 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

Page 138: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 129

* @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

Page 139: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 130

*/ 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

Page 140: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 131

* @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

Page 141: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 132

* @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); }

Page 142: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 133

/** * 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)); }

Page 143: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 134

/** * 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

Page 144: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 135

* @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 */

Page 145: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 136

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.

Page 146: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 137

• 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.

Page 147: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 138

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&ntilde;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); } }

Page 148: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 139

Para acceder a los vértices se observan llamadas 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; }

Page 149: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 140

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)) {

Page 150: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 141

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&ntilde;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;

Page 151: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 142

// 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,

Page 152: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 143

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; }

Page 153: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 144

// 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 */

Page 154: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 145

#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:

Page 155: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 146

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; } }

Page 156: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 147

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

Page 157: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 148

// 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

Page 158: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 149

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);

Page 159: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 150

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

Page 160: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 151

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; }

Page 161: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 152

} 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

Page 162: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 153

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)"

Page 163: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 154

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--; }

Page 164: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 155

// 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

Page 165: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 156

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);

Page 166: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 157

// 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],

Page 167: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 158

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); } }

Page 168: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 159

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

Page 169: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 160

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;

Page 170: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 161

// í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);

Page 171: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 162

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)

Page 172: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 163

{ 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.

Page 173: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 164

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

Page 174: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 165

// 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);

Page 175: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 166

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

Page 176: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 167

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

Page 177: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 168

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:

Page 178: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 169

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

Page 179: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 170

// 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; }

Page 180: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 171

} 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

Page 181: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 172

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,

Page 182: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 173

// 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())

Page 183: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 174

{ 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

Page 184: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 175

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:

Page 185: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 176

34 - Vista perspectiva.

35 - Vista frontal.

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 {

Page 186: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 177

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:

Page 187: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 178

43 - Vista perspectiva.

44 - Vista frontal.

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:

Page 188: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 179

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:

Page 189: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 180

52 - Vista perspectiva.

53 - Vista frontal.

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:

Page 190: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 181

55 - Vista perspectiva.

56 - Vista frontal.

57 – Textura.

• Pared:

58 - Vista perspectiva.

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:

Page 191: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 182

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

Page 192: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 183

// 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 &&

Page 193: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 184

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;

Page 194: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 185

}

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)

Page 195: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 186

{ // 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)

Page 196: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 187

{ 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);

Page 197: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 188

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(); }

Page 198: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 189

Escena2->mostrarTextoPantalla(0, 2); return 0; }

Page 199: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Implementación

Fernando Garcia Bernal 190

Page 200: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Conclusiones

Fernando Garcia Bernal 191

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

Page 201: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Conclusiones

Fernando Garcia Bernal 192

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

Page 202: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Conclusiones

Fernando Garcia Bernal 193

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

Page 203: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Conclusiones

Fernando Garcia Bernal 194

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.

Page 204: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Conclusiones

Fernando Garcia Bernal 195

• 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].

Page 205: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Conclusiones

Fernando Garcia Bernal 196

Page 206: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Apéndice A. Glosario

Fernando Garcia Bernal 197

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.

Page 207: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Apéndice A. Glosario

Fernando Garcia Bernal 198

• 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.

Page 208: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Apéndice A. Glosario

Fernando Garcia Bernal 199

• 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.

Page 209: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Apéndice A. Glosario

Fernando Garcia Bernal 200

64 - Representación del mapeado UV de un cubo.

Page 210: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Apéndice B. Contenido del

CD-ROM

Fernando Garcia Bernal 201

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

Page 211: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Apéndice B. Contenido del

CD-ROM

Fernando Garcia Bernal 202

• 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.

Page 212: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Apéndice B. Contenido del

CD-ROM

Fernando Garcia Bernal 203

Page 213: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Apéndice B. Contenido del

CD-ROM

Fernando Garcia Bernal 204

Page 214: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Bibliografía

Fernando Garcia Bernal 205

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-v-

edicion/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/

Page 215: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Bibliografía

Fernando Garcia Bernal 206

[8] 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-8edd-

aab15c5e04f5&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/

Page 216: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Bibliografía

Fernando Garcia Bernal 207

[24] 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/en-

us/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/

Page 217: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Bibliografía

Fernando Garcia Bernal 208

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-8edd-

aab15c5e04f5&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

Page 218: Curso de Programacion Para Nintendo DS Orientado a Videojuegos - 218 Paginas

DESARROLLO DE VIDEOJUEGO 3D PARA LA VIDEOCONSOLA NINTENDO DS Bibliografía

Fernando Garcia Bernal 209

<DIRECTORIO DE INSTALACIÓN 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/en-

us/dx81_c/directx_cpp/Graphics/Reference/FileFormat/FileFormat.asp

http://www.andytather.co.uk/Panda/directxmax_downloads.aspx

Quateriones

http://msdn2.microsoft.com/es-

es/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.