185
Grado en Ingeniería Informática Trabajo Fin de Grado Autor: Daniel Ponsoda Montiel Tutor/es: Francisco José Gallego Durán Mayo 2019

g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

  • Upload
    others

  • View
    4

  • Download
    0

Embed Size (px)

Citation preview

Page 1: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Grado en Ingeniería Informática

Trabajo Fin de Grado

Autor: Daniel Ponsoda Montiel

Tutor/es: Francisco José Gallego Durán

Mayo 2019

Page 2: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM
Page 3: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

U N I V E R S I D A D D E A L I C A N T E

ESCUELA POL ITÉCNICA SUPERIOR Departamento de Ciencia de la Computación e Inteligencia Artificial

GRADO EN INGENIERÍA INFORMÁTICA Trabajo Fin de Grado

Daniel Ponsoda Montiel [email protected]

TUTORES

Francisco José Gallego Durán

27 de mayo de 2019

Acerca de las imágenes, figuras y logos: Las capturas de los motores Unity3D, Unreal Engine y CryEngine están extraídas de la documentación

oficial y son propiedad de sus respectivos autores. La captura del juego “Livingstone, Supongo” es propiedad de Opera Soft. Los logos mostrados en el texto representan marcas de sus respectivos

propietarios. El resto de imágenes y figuras son creación del autor del TFG.

Page 4: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM
Page 5: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

A Manuela, por su inestimable compañía, e interminable paciencia y apoyo.

Page 6: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM
Page 7: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

AGRADECIMIENTOS

Quisiera agradecer en primer lugar a todos aquellos profesores que me han guiado tan

acertadamente en este viaje hasta el último curso de la carrera y que, por su dedicación,

vocación, y gusto por su trabajo hacen grande a la Universidad de Alicante. En especial a

Domingo Gallardo López, David Gil Méndez, Virgilio Gilart Iglesias, Eva Gómez Ballester,

Antonio Jimeno Morenilla, Javier Montoyo Bojo, Jerónimo Mora Pascual, Francisco Moreno

Seco, Cristian Neipp López, José Oncina Carratalá, Jesús Peral Cortés, Carlos Pérez Sancho,

Pedro Ponce de León, Julio Rosa Herranz, Felipe Sánchez Martínez y David Tomás Diaz.

Además, quiero agradecer especialmente por su gran labor docente y por dar a cada una

de sus clases un enfoque tan inspirador que se han convertido en un referente para mí a nivel

profesional y personal, al profesor Sergio Ramón Ferry y a mi tutor Francisco Gallego Durán.

También doy mi más sincero agradecimiento a mis grandes amigos Aarón Adrover López

y Manuel Aniorte Sánchez por su amistad y por transmitirme su fuerza para emprender

proyectos igual de grandes.

Por supuesto, también doy las gracias a toda mi familia. En especial a mis padres y

hermanos por darme un inmejorable ejemplo a seguir y por estar a mi lado ofreciéndome su

incondicional apoyo y afecto, sin el cual no estaría hoy escribiendo estas líneas.

También, por su apoyo diario e inestimable amistad, quiero dar un agradecimiento muy

especial a mis compañeros y amigos Juan Miguel Castillo Zaragoza y Luis Pérez Pérez, que

en estos últimos cuatro años han sido y serán siempre como hermanos para mí.

Y, por último, y no menos importante, a mi señora Manuela Ballester Levia, a quien dedico

mi trabajo y la cual es, sin ninguna duda, la mejor confidente, compañera y amiga que

cualquier persona pudiera soñar tener a su lado. Gracias por todo lo que me das y lo poco que

pides a cambio. Te quiero.

Page 8: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM
Page 9: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

INDICE DE CONTENIDO

Agradecimientos ............................................................................................................. vii

Índice de figuras...............................................................................................................xv

Preámbulo ......................................................................................................................xix

Origen y propósito de los game engines ......................................................................xix

Acerca de la industria de los videojuegos ..................................................................xxiii

1 Introducción ............................................................................................................ 27

1.1 Objetivos ............................................................................................................. 27

1.2 Motivación .......................................................................................................... 27

1.3 Solución inicial .................................................................................................... 29

1.4 Problema en plataformas móviles ....................................................................... 30

1.5 Solución para plataformas móviles ...................................................................... 31

1.6 Alcance del proyecto ........................................................................................... 32

2 Estudio de game engines existentes ........................................................................ 35

2.1 Unity3D .............................................................................................................. 35

2.1.1 El editor ....................................................................................................... 36

2.1.2 El sistema de scripting ................................................................................. 38

2.1.3 Ventajas e inconvenientes ............................................................................ 39

2.2 Unreal Engine ..................................................................................................... 40

2.2.1 El editor ....................................................................................................... 41

Page 10: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

2.2.2 El sistema de scripting ................................................................................ 43

2.2.3 Ventajas e inconvenientes ............................................................................ 44

2.3 CryEngine ........................................................................................................... 45

2.3.1 El editor ...................................................................................................... 45

2.3.2 El sistema de scripting ................................................................................ 47

2.3.3 Ventajas e inconvenientes ............................................................................ 47

2.4 Mejoras que aportará nuestro motor ................................................................... 48

3 Modelo de desarrollo y planificación ....................................................................... 49

3.1 Modelo iterativo .................................................................................................. 49

3.2 Planificación adaptativa ..................................................................................... 50

3.2.1 Plan general ................................................................................................. 51

3.2.2 Plan detallado ............................................................................................. 51

4 Definición de la arquitectura .................................................................................. 53

4.1 Patrón Entidad-Componente-Sistema ................................................................. 53

4.2 Despliegue ........................................................................................................... 55

4.3 Arquitectura del reproductor .............................................................................. 56

4.4 Formato y recursos del proyecto de juego .......................................................... 58

5 Diseño del interfaz .................................................................................................. 59

5.1 Interfaz gráfica del editor .................................................................................... 59

5.2 Interfaz de consola .............................................................................................. 62

6 Elección de las dependencias .................................................................................. 65

6.1 Librería de matemáticas ..................................................................................... 65

6.2 Librería de gráficos ............................................................................................. 66

6.2.1 Ogre ............................................................................................................. 66

Page 11: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

6.2.2 Irrlicht ......................................................................................................... 67

6.2.3 OpenSceneGraph ......................................................................................... 68

6.2.4 Decisión sobre la librería de gráficos ............................................................ 69

6.3 Librería de física .................................................................................................. 69

6.4 Lenguaje de scripting .......................................................................................... 71

6.4.1 Lua .............................................................................................................. 71

6.4.2 JavaScript .................................................................................................... 72

6.4.3 Python ......................................................................................................... 73

6.4.4 Decisión sobre el lenguaje de scripting ......................................................... 73

6.5 Interfaz gráfica de usuario para el editor ............................................................ 74

6.5.1 Qt ................................................................................................................ 74

6.5.2 wxWidgets ................................................................................................... 75

6.5.3 Decisión sobre la librería de GUI ................................................................. 75

6.6 Librería de sockets .............................................................................................. 76

7 Configuración del entorno ....................................................................................... 77

7.1 Preparar OpenSceneGraph .................................................................................. 80

7.1.1 Compilar para Windows .............................................................................. 80

7.1.2 Compilar para Linux ................................................................................... 81

7.1.3 Compilar para Android ................................................................................ 82

7.2 Preparar Bullet Physics ...................................................................................... 83

7.2.1 Compilar para Windows .............................................................................. 83

7.2.2 Compilar para Linux ................................................................................... 85

7.2.3 Compilar para Android ................................................................................ 85

7.3 Preparar Lua ....................................................................................................... 86

Page 12: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

7.3.1 Compilar para Windows .............................................................................. 86

7.3.2 Compilar para Linux ................................................................................... 87

7.3.3 Compilar para Android ............................................................................... 87

7.4 Preparar el framework Qt ................................................................................... 88

7.5 Preparar Asio ...................................................................................................... 88

7.6 Proyecto del reproductor para Windows/Linux.................................................. 89

7.7 Proyecto del reproductor para Android .............................................................. 90

7.8 Proyecto de la consola para Windows/Linux ..................................................... 90

7.9 Proyecto para el editor gráfico ............................................................................ 91

7.9.1 Integración de Qt con OpenSceneGraph (osgQt) ........................................ 92

8 Diseño deL código .................................................................................................. 95

8.1 Sistema de administración de assets ................................................................... 95

8.2 Sistema de configuración ..................................................................................... 96

8.3 Sistema de consola cliente / servidor .................................................................. 97

8.4 Entidad-Componente-Sistema ............................................................................. 98

8.5 Sistema de gráficos ............................................................................................. 99

8.6 Sistema de entrada ........................................................................................... 100

8.7 Sistema de log ................................................................................................... 101

8.8 Sistema de física ............................................................................................... 102

8.9 Sistema de scripting .......................................................................................... 103

8.10 Sistema de transformación ................................................................................ 104

9 Implementación .................................................................................................... 105

9.1 Sistema de log ................................................................................................... 105

9.1.1 Necesidad de derivar la salida de log a múltiples destinos ........................ 105

Page 13: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

9.1.2 Log como stream con información de fichero y línea ................................. 106

9.1.3 Desactivación de logs de depuración en modo release ................................ 107

9.2 Administración de recursos de disco ................................................................. 109

9.3 Dependencias en el sistema ECS ....................................................................... 112

9.4 Interoperabilidad entre librerías de gráficos y física .......................................... 113

9.5 Resolución de riesgos de programación concurrente .......................................... 114

9.5.1 Sistema de input de usuario ....................................................................... 115

9.5.2 Ejecución de script desde consola remota .................................................. 115

9.6 Sincronización de relojes ................................................................................... 116

9.7 Abstracción mediante fachadas ......................................................................... 117

9.8 Acerca del editor gráfico ................................................................................... 119

10 Ejemplos de uso .................................................................................................... 121

10.1 Primer proyecto ................................................................................................ 121

10.2 Uso de la consola ............................................................................................... 122

10.3 Demostración acelerómetro: “DemoBall.lua” .................................................... 124

10.4 Demostración rendimiento: “DemoRandom.lua” ............................................... 126

10.5 Demostración minijuego: “DemoMarble.lua” .................................................... 127

11 Pruebas de rendimiento ........................................................................................ 129

12 Posibles mejoras .................................................................................................... 133

12.1 Mejoras en el sistema de gráficos ...................................................................... 133

12.2 Compilación del script....................................................................................... 133

12.3 Prescindir de algunas dependencias................................................................... 134

12.4 Cifrado del código y assets ................................................................................ 135

13 Conclusiones ......................................................................................................... 137

Page 14: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

14 Referencias ........................................................................................................... 139

15 Glosario ................................................................................................................ 141

Anexo I: Librería de matemáticas ................................................................................. 145

Vectores ..................................................................................................................... 145

Puntos........................................................................................................................ 150

Matrices ..................................................................................................................... 151

Cuaterniones .............................................................................................................. 159

Anexo II. Sistema de gráficos ........................................................................................ 163

Pipeline de procesamiento de gráficos ........................................................................ 163

La cámara .................................................................................................................. 165

Matriz Modelo-Vista-Proyección ................................................................................ 166

Anexo III: Manual del API ............................................................................................ 169

AssetMapper .............................................................................................................. 169

CameraComponent .................................................................................................... 171

DrawableComponent ................................................................................................. 173

Entity ........................................................................................................................ 174

ion (alias de SystemManager) .................................................................................... 176

Material ..................................................................................................................... 178

PhysicsComponent ..................................................................................................... 180

Random ..................................................................................................................... 182

Shapes ........................................................................................................................ 183

TransformComponent ................................................................................................ 184

Page 15: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

ÍNDICE DE FIGURAS

Figura 0.1. Livingstone Supongo, 1986, creado por el estudio español Opera Soft ......... xx

Figura 0.2. Juego Crysis 3. Realizado por Crytek con su propio motor CryEngine ...... xxii

Figura 0.3. Beneficios interanuales de videojuegos según Newzoo ............................... xxiv

Figura 0.4. Crecimiento del mercado de videojuegos según Newzoo ............................ xxiv

Figura 0.5. Salario de desarrolladores de videojuegos según Texterity ......................... xxv

Figura 1.1. Proceso general de ajuste y pruebas .............................................................. 28

Figura 1.2. Boceto inicial de la arquitectura del motor ................................................... 29

Figura 1.3. Esquema de trabajo editor/reproductor ........................................................ 31

Figura 2.1. Interfaz del editor de juegos de Unity3D ....................................................... 36

Figura 2.2. Ventana Inspector de Unity3D ...................................................................... 37

Figura 2.3. Comparativa de editores entre Unity y Unreal ............................................. 42

Figura 2.4. Sistema de Blueprints de Unreal Engine ....................................................... 43

Figura 2.5. Editor de CryEngine ..................................................................................... 46

Figura 2.6. Interfaz de edición de objetos de CryEngine ................................................. 46

Figura 3.1. Tareas de una iteración ................................................................................. 49

Figura 4.1. Ejemplo Entidad-Componente-Sistema ......................................................... 54

Figura 4.2. Despliegue del editor y los reproductores ...................................................... 56

Figura 4.3. Arquitectura del player ................................................................................. 57

Page 16: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Figura 5.1. Mockup pantalla principal del editor ............................................................ 59

Figura 6.1. Logo de Ogre ................................................................................................. 66

Figura 6.2. Logo de Irrlicht ............................................................................................. 67

Figura 6.3. Logo de OpenSceneGraph ............................................................................. 68

Figura 6.4. Logo de Bullet ............................................................................................... 69

Figura 6.5. Logo de Lua .................................................................................................. 71

Figura 6.6. Logo de JavaScript ........................................................................................ 72

Figura 6.7. Logo de Python ............................................................................................. 73

Figura 6.8. Logo de Qt .................................................................................................... 74

Figura 6.9. Logo de wxWidgets ....................................................................................... 75

Figura 8.1. Diagrama de clases. Sistema de administración de assets ............................. 95

Figura 8.2. Diagrama de clases. Sistema de configuración ............................................... 96

Figura 8.3. Diagrama de clases. Sistema de consola cliente / servidor ............................ 97

Figura 8.4. Diagrama de clases. Entidad-Componente-Sistema ....................................... 98

Figura 8.5. Diagrama de clases. Sistema de gráficos ........................................................ 99

Figura 8.6. Diagrama de clases. Sistema de entrada ..................................................... 100

Figura 8.7. Diagrama de clases. Sistema de log ............................................................. 101

Figura 8.8. Diagrama de clases. Sistema de física.......................................................... 102

Figura 8.9. Diagrama de clases. Sistema de scripting .................................................... 103

Figura 8.10. Diagrama de clases. Sistema de transformación ........................................ 104

Figura 9.1. Diagrama de dependencias entre componentes ........................................... 113

Figura 9.2. Sistemas de coordenadas en Bullet y OSG .................................................. 113

Figura 10.1. Captura del primer proyecto de ejemplo ................................................... 122

Figura 10.2. Demostración acelerómetro ....................................................................... 124

Page 17: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Figura 10.3. Demostración física .................................................................................... 126

Figura 10.4. Juego de demostración ............................................................................... 128

Figura 11.1 DemoRandom.lua ....................................................................................... 129

Figura 11.2. Gráfica de rendimiento en la versión PC ................................................... 130

Figura 11.3. Gráfica de rendimiento en la versión Android ........................................... 131

Figura 0.1. Vectores ....................................................................................................... 146

Figura 0.2. Magnitud de vector ..................................................................................... 147

Figura 0.3. Vector normalizado ..................................................................................... 148

Figura 0.4. Regla de mano derecha ................................................................................ 149

Figura 0.5. Multiplicación de matrices ........................................................................... 153

Figura 0.6. Ejes de rotación ........................................................................................... 157

Figura 0.7. Demostración del efecto gimbal lock ........................................................... 160

Figura 0.1. Matriz cámara ............................................................................................. 165

Figura 0.2. Transformación con la matriz modelo ......................................................... 167

Figura 0.3. Proyección de un punto sobre un plano ...................................................... 168

Page 18: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM
Page 19: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

xix

PREÁMBULO

Por simplicidad, podemos definir el motor de un coche como un conjunto de partes, cada

una con distinta finalidad, que colaboran para realizar una tarea común que en este caso sería

mover el vehículo.

Análogamente, un motor de videojuegos o game engine, está construido de la misma forma

(y de ahí su nombre), solo que no se trata de piezas físicas sino de componentes software, y

su finalidad principal es la de reproducir juegos en un ordenador y servir de ayuda en su

proceso de desarrollo.

Un game engine es un sistema compuesto a su vez de varios subsistemas como el de

gráficos, física, colisiones, sonido, matemáticas, inteligencia artificial, scripting, red, interfaz

de usuario, etc. por tanto se presenta como un objeto de estudio muy interesante que

implementa y unifica multitud de disciplinas de varios campos científicos y técnicos. Pero para

conocer qué elementos lo forman exactamente y por qué están ahí, debemos saber primero de

dónde surge el desarrollo de un game engine.

Origen y propósito de los game engines En la historia reciente, ha habido sin duda una explosión en la evolución de la tecnología.

Hace no demasiados años, el hecho de que una persona de a pie pudiera tener un ordenador

en casa era algo que sólo podíamos ver en películas de ciencia ficción. Se consideraba un

producto caro, voluminoso y delicado, dirigido sólo a científicos o expertos en tecnología.

La línea que separaba la realidad de la ficción empezó a disiparse a finales del siglo pasado,

hasta tal punto que, en la actualidad, la mayoría de personas que vivimos en países avanzados

tenemos en nuestro bolsillo un ordenador miles de veces más potente que aquel que llevó al

Page 20: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

xx

hombre a la luna hace 50 años, y tan sencillo de manejar que cualquier niño puede usarlo.

Esto ha permitido la introducción de todo tipo aplicaciones y oportunidades de mercado antes

impensables como son los videojuegos de última generación, capaces de generar en tiempo real

imágenes que rozan el fotorrealismo.

Volviendo atrás, en la gloriosa época de los

primeros ordenadores personales, si conocíamos bien la

máquina, producir un videojuego era en realidad una

tarea relativamente sencilla. De hecho, una sola

persona con habilidad en programación y paciencia

dibujando sprites pixel a pixel podía diseñar y realizar

un juego comercial en cuestión de unos pocos meses (o

incluso semanas).

No obstante, como he mencionado, un aspecto fundamental era conocer bien la máquina.

Y es que, la falta de precedentes, documentación y herramientas de desarrollo era una fuente

de problemas para estos programadores pioneros que, motivados por este nuevo mundo que

les abría la tecnología, sin darse cuenta adquirían competencias transversales del campo de la

programación muy demandadas en la actualidad como la capacidad de desarrollo utilizando

nuevas tecnologías con escasa información disponible y habilidades para exprimir al máximo

las limitaciones técnicas del sistema con el que trabajan.

Por otro lado, el éxito del desarrollador que creaba en solitario un videojuego se debía en

parte a que las limitaciones de la máquina no permitían hacer algo excesivamente grande y,

por otro lado, porque en general todos los sistemas domésticos acababan de llegar al mercado

y había pocas empresas desarrolladoras, con lo cual, el catálogo de software era aún muy

reducido y los usuarios siempre estaban ansiosos de probar nuevos títulos en su ordenador

personal.

A medida que evolucionaba la tecnología y la competencia inundaba el mercado con

productos de calidad creciente, se hacía más necesario contar al menos con un pequeño equipo

Figura 0.1. Livingstone Supongo, 1986, creado por el estudio español Opera Soft

Page 21: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

xxi

de personas especializadas en su campo (programación, gráficos, sonido, diseño) para

desarrollar un videojuego.

Al mismo tiempo, los desarrolladores fueron creando también herramientas y definiendo

nuevas técnicas con el objetivo de realizar productos con el mínimo tiempo y esfuerzo para así

poder seguir siendo competitivos. A raíz del desarrollo de varios videojuegos, los

programadores han ido identificando y aislando en forma de librerías elementos comunes

reutilizables en todas sus producciones, como rutinas de pintado de sprites, inteligencia

artificial, etc. y además también herramientas que les ayudan en su trabajo formando entornos

de desarrollo completos enfocados a videojuegos.

De la combinación de todos estos elementos surgen los motores de juegos (o game engines)

y no al revés como muchos nuevos desarrolladores podrían pensar. Las empresas

desarrolladoras más importantes normalmente han ido creando siempre un motor propio que

han ampliado y mejorado constantemente con cada título que han producido. Esto les ha

proporcionado una identidad y un toque personal que en muchas ocasiones han mantenido

siempre celosamente bajo llave.

No obstante, actualmente la tendencia es el open source y el apoyo de la comunidad

mediante mejoras al código base o al menos a través de plugins para motores con licencias

privativas, ya que se ha demostrado que la comunidad de desarrolladores ofrece un inmenso

potencial creativo que no podemos desaprovechar si queremos seguir siendo competitivos.

Ejemplos populares de esta tendencia abierta que se utilizan en juegos comerciales son los

motores Unreal Engine, CryEngine, o IdTech y recientemente también Unity3D.

Page 22: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

xxii

Dicho esto, para aquellos que quieren aprender a programar realizando un videojuego es

mucho más aconsejable NO utilizar un motor existente, sobre todo si lo que quieren en un

futuro es dedicarse profesionalmente a esto. La mejor forma de aprender sería crearlo todo

desde cero utilizando un lenguaje de propósito general (por ejemplo, C++). Esto les hará

plantearse cuestiones que ya tendrían resueltas si usan un motor, pero que es necesario que

sepan resolverlas por sí mismos de forma que cuando utilicen un game engine puedan tener

una mejor visión general y saber por qué las cosas se han diseñado de cierta manera.

La ventaja que tenemos actualmente (y también el inconveniente) con respecto a cómo

empezaron los primeros programadores es la inmensa cantidad de información que tenemos al

alcance de un click en Internet. Siempre encontraremos alguien que ha tenido el mismo

problema que nosotros y que nos puede ayudar, pero ojo, siempre es mejor intentar resolver

el problema por nosotros mismos para ejercitar nuestra capacidad resolutiva.

Por otro lado, un error bastante común que podemos cometer siguiendo esta vía al hacer

un juego desde cero es crear primero el motor sobre el que funcionará. Como ya hemos visto,

un game engine surge por si solo de la experiencia del diseño y realización de sucesivos

videojuegos. Cuando lo que queremos es realizar un videojuego, debemos centrarnos en nuestro

objetivo y hacer que funcione. Por supuesto no podemos esperar hacer una superproducción

Figura 0.2. Juego Crysis 3. Realizado por Crytek con su propio motor CryEngine

Page 23: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

xxiii

en nuestro primer intento, sino que lo mejor sería empezar por un juego lo más sencillo posible

que sepamos que seremos capaces de terminar.

En cualquier caso, nuestro segundo proyecto estará mejor diseñado y habremos empezado

a construir una pequeña librería de funcionalidades generales para desarrollo de videojuegos

que podrá ir ampliándose y mejorándose con cada nuevo juego ¿Quién sabe si finalmente se

convierte en un motor comercial? Pues como veremos en este documento, ningún engine actual

es perfecto. Todos tienen inconvenientes que podemos tratar de mejorar si partimos desde el

principio con una visión diferente.

Acerca de la industria de los videojuegos En la actualidad los videojuegos son el principal sector en la industria del entretenimiento

en cuanto a volumen de negocio. Por tanto, no es de extrañar que, desde hace ya varios años

mueva más ingresos que el cine y la música juntos. Y es que, al contrario de lo que se solía

asociar a este sector en sus primeros tiempos, en la actualidad está dirigido al público de todas

las edades, y no solo a niños. En realidad, son los adultos quienes más dinero gastan en este

tipo de producto.

Siempre atendiendo a una justa medida y un uso responsable, los videojuegos ejercitan

enormemente la capacidad de resolución lógica, la atención y los reflejos de los jugadores y,

por otro lado, se sabe que ayudan en la rehabilitación a personas con necesidades especiales

como autismo, Alzheimer y otras enfermedades neurológicas o psicomotrices.

Además, se trata de un campo multidisciplinar que abarca no sólo tecnología, sino también

arte gráfico, música, diseño de lógica de juego, guion, etc. Por tanto, se presenta muy atractivo

para infinidad de profesionales de multitud de campos que lo tienen muy claro a la hora de

invertir su esfuerzo. De hecho, existe tal carrera por abrirse paso en este mercado que las

inversiones en los juegos de última generación son multimillonarias.

El gráfico que se muestra a continuación es un extracto del estudio realizado por Newzoo

(Newzoo, 2018) y es una estimación de los beneficios interanuales que generarán los

Page 24: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

xxiv

videojuegos para abril de 2017 según sector (PC, móviles y consolas). Las cifras están indicadas

en miles de millones de dólares (billones en Norteamérica).

Figura 0.3. Beneficios interanuales de videojuegos según Newzoo

El total es de 108,9 mil millones de dólares. Además, como vemos a continuación, según

este mismo estudio, se estima que esta cifra seguirá creciendo a un ritmo constante hasta

llegar a los 128 mil millones en 2020, siendo el sector de los juegos móviles el que más

rápidamente crecerá.

Figura 0.4. Crecimiento del mercado de videojuegos según Newzoo

Page 25: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

xxv

Además de los mencionados sectores por plataformas, el mercado también se divide por

categorías según su grado de inversión. En este caso podríamos distinguir el sector indie y el

sector profesional cuyos juegos son conocidos en ocasiones como triple A. Para este último

caso, el presupuesto para desarrollar un videojuego puede llegar a alcanzar cifras que situamos

entre los 55 y los 285 millones de dólares (Wikipedia, 2018).

Estos números se justifican si tenemos en cuenta que los efectos gráficos, guiones, música,

voces dobladas y demás características que conforman un videojuego actual no tienen nada

que envidiar a los que se realizan para las películas de Hollywood.

Esto es una buena noticia para los desarrolladores, ya que las grandes compañías no suelen

escatimar en gastos cuando se trata de ofrecer un buen salario a los profesionales que poseen

las competencias que demandan.

En la siguiente tabla (Texterity, 2011) podemos ver el salario medio para Estados Unidos,

Canada y Europa según su disciplina dentro de la cadena de desarrollo:

Figura 0.5. Salario de desarrolladores de videojuegos según Texterity

Como vemos, incluso en Europa, con un 9% de los encuestados procedentes de España, el

salario medio para un programador se sitúa en torno a los 48.000 dólares anuales, con lo que

podemos afirmar que se trata de un sector que ofrece unas condiciones más que dignas a

quienes se dedican a él.

Page 26: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM
Page 27: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

27

1 INTRODUCCIÓN

1.1 Objetivos El objetivo de este proyecto es la realización de un game engine, así como el estudio de los

fundamentos básicos de los principales componentes que conforman este tipo de sistemas. Lo

primero puede parecer una contradicción a las recomendaciones comentadas en la

introducción. No obstante, hay varias cuestiones fundamentales que justifican la creación de

este motor y que pasamos a comentar a continuación.

En primer lugar, por su arquitectura, como veremos en el siguiente apartado, el motor que

vamos a realizar plantea una forma de trabajo que reduce drásticamente el tiempo de

desarrollo con respecto a los motores más utilizados actualmente como por ejemplo Unity3D

o Unreal cuando la plataforma objetivo son los dispositivos móviles. Con lo cual nos servirá

para experimentar con ciertas características innovadoras.

En segundo lugar, no partiremos completamente desde cero, sino que integraremos

distintas librerías de apoyo de código abierto para los apartados de gráficos, física e

interpretación de scripts (todo esto se detallará más adelante en esta memoria).

Por último, quien escribe ya tiene experiencia en el desarrollo de videojuegos, con varios

títulos publicados en el mercado de Android e iOS, así que no iremos completamente a ciegas

a la hora de plantear la estructura general del motor.

1.2 Motivación La motivación principal de este motor de videojuegos es proporcionar un sistema de

desarrollo multiplataforma que acelere el modo en que actualmente ajustamos y probamos los

videojuegos durante el desarrollo, especialmente en plataformas móviles.

Page 28: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

28

Cuando hacemos un nuevo juego, algo muy habitual es ajustar constantemente cuestiones

como la velocidad de los enemigos, la altura del salto de un personaje, etc. hasta llegar a

definir experimentalmente la jugabilidad que buscamos por medio de múltiples ajustes y

pruebas. Para ello, tradicionalmente los pasos a seguir son los siguientes:

El primer inconveniente que vemos en el anterior diagrama es el hecho de tener que

compilar el programa por cada pequeño ajuste del juego que queremos probar. Esto puede

ralentizar sensiblemente nuestro ritmo de trabajo. Sin embargo, este es un paso necesario de

los lenguajes compilados como C++.

Para un videojuego donde la el tiempo de ejecución es en casi todo momento crítico, no

podemos prescindir de este tipo de lenguajes. Incluso, para las secciones más críticas suele

emplearse ensamblador, puesto que necesitamos generar código lo más optimizado posible para

estar a la altura de los niveles de eficiencia y calidad exigidos para competir en el mercado

actual.

La solución a estos inconvenientes en el proceso de desarrollo y pruebas en los videojuegos

es el principal objeto y motivación de este proyecto.

1. Hacer cambios

2. Compilar

3. Ejecutar

4. Probar

¿ok?

Si

No

Por ejemplo, aumentar la velocidad de los enemigos.

Puede tardar más o menos dependiendo del tamaño del programa y el lenguaje utilizado (podemos hablar de segundos o incluso minutos)

Podemos hacer que se ejecute directamente desde el punto que queremos testear, aunque esto no siempre es trivial e implica cambios temporales en el código que hay que deshacer

Verificamos que el juego se ajuste a los requisitos de manejo, jugabilidad y dificultad definidos.

Si el cambio no nos satisface, volvemos al primer paso

Figura 1.1. Proceso general de ajuste y pruebas

Page 29: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

29

1.3 Solución inicial En contraste con C++, existen los lenguajes de script, que funcionan a través de una

máquina virtual que los interpreta y ejecuta directamente desde el código que escribimos sin

necesidad de compilar. Desafortunadamente, esta forma de ejecución es al menos 10 veces más

lenta que la tradicional. Sin embargo, podemos considerar hacer una combinación de ambas

aproximaciones aprovechando lo mejor de cada una.

Sabemos que la parte más cambiante del juego va a ser el diseño de la lógica y los pequeños

ajustes en la jugabilidad. Además, esta parte es generalmente la menos crítica, ya que no

requiere de complejos cálculos matemáticos ni se suele ejecutar de forma constante. Así pues,

la mejor solución para evitar recompilar innecesariamente es embeber en el propio motor una

máquina virtual que interprete un lenguaje de script. De esta manera, el núcleo del motor y

sus distintas rutinas más críticas como el dibujado de gráficos o simulación de físicas, entre

otras, pueden permanecer optimizadas usando C++, mientras que las cuestiones más

cambiantes como la lógica del juego se escribirán usado un script, para que sean más rápidas

de modificar.

Haciendo esto podemos eliminar del diagrama anterior los pasos 2 y 3, reduciéndose el

proceso a simplemente “cambiar y probar”, ya que la máquina virtual de la que hablamos

tendría la capacidad de modificar el código en tiempo de ejecución sin detener el programa.

Siendo así, un primer boceto general de la arquitectura del motor podría ser el siguiente:

Lógica del juego (scripts)

Intérprete de scripts

Núcleo del motor (C++)

Gráficos Colisiones Física Sonido …

API de acceso a hardware

Figura 1.2. Boceto inicial de la arquitectura del motor

Page 30: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

30

1.4 Problema en plataformas móviles Cuando realizamos un juego para móviles, es muy común la necesidad de hacer los ajustes

sobre el propio dispositivo. Esto es así porque podemos necesitar probar el juego utilizando

los elementos de control y visualización característicos de los smartphones, como son la

pantalla multitáctil (imposible de imitar con el ratón), o el control de componentes del juego

usando los acelerómetros, giroscopios u otros sensores.

Por supuesto no nos basta con simularlo en el PC por medio de teclas o demás atajos, ya

que cuando desarrollamos un juego es crucial comprobar en todo momento que el sistema de

control que estamos creando es fácil de usar y es jugable. En definitiva, necesitaremos realizar

los ajustes sobre el hardware para el que va destinado el juego si queremos probar la

experiencia real que ofrece lo que estamos desarrollando.

Los pasos vistos anteriormente para cambiar el código, ejecutar y probar se pueden

complicar bastante cuando la máquina de desarrollo es distinta de la de pruebas. desarrollamos

en el PC para luego pasar el programa creado al dispositivo de pruebas. Esta circunstancia

complica enormemente el problema. Los pasos ahora son:

1. Hacer cambios en el código para ajustar cuestiones de jugabilidad 2. Generar el paquete de la aplicación (por ejemplo el .apk) 3. Transmitirlo al dispositivo 4. Instalar el programa 5. Ejecutar el programa 6. Probar los cambios 7. Volver al paso 1 hasta que el resultado sea satisfactorio

No importa que hayamos usado un lenguaje de script. Igualmente tendremos que realizar

los pasos más pesados en cada prueba (desde el 2 al 5) que son generar y transmitir el paquete,

y luego instalar y ejecutar el programa sobre el dispositivo real.

Al principio es un proceso relativamente rápido (aproximadamente un par de minutos

entre una ejecución y otra), pero a medida que el juego crece, no solo lo hace en cuestión de

código, sino también en cuanto a los archivos que utiliza (imágenes, modelos 3D, sonidos,

música, etc.). Todos estos archivos deben volver a empaquetarse y transmitirse junto con todo

Page 31: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

31

el .apk cada vez que queremos hacer cualquier cambio en la aplicación. Por este motivo, el

tiempo para realizar estos pasos puede llegar a ser de 5, 10 o más minutos en juegos de tamaño

medio o grande.

1.5 Solución para plataformas móviles La propuesta se ilustra en la siguiente figura y consiste en dividir el sistema de pruebas

para distribuirlo en red entre un PC (plataforma de desarrollo) y un Móvil (plataforma

objetivo). El PC donde trabajaremos tendrá la parte de edición del juego donde escribimos el

código, mientras que el móvil tendrá el reproductor, que estará a la escucha e interpretará y

ejecutará las órdenes que le enviemos, actualizando en consecuencia su estado y

comportamiento.

De esta forma podremos hacer cambios y probarlos instantáneamente sin tener que volver

a crear, transmitir, reinstalar y ejecutar el paquete en cada prueba.

Esto mejorará en gran medida la productividad del desarrollador de juegos y permitirá

hacer incluso un mayor número de pruebas ayudándole a centrarse en su trabajo y a observar

todas las posibilidades del juego que está desarrollando.

Además, esta solución no tiene por qué limitarse solo a móviles, sino que es perfectamente

extrapolable a la programación para todo tipo de consolas y en general cuando el sistema

objetivo es distinto al que se utiliza para desarrollar.

Editor del juego Reproductor

Órdenes script

Figura 1.3. Esquema de trabajo editor/reproductor

Page 32: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

32

Como último apunte, será también conveniente crear una versión especial de depuración

del motor que permanezca a la escucha de nuevo código por parte del PC y lo ejecute cuando

llegue. Esta versión será distinta de la que se usará para distribuir el juego, la cual estará

desprovista de esta característica con el fin de evitar posibles intrusiones por parte de un

hacker.

1.6 Alcance del proyecto En este proyecto construiremos la infraestructura necesaria para implementar la

funcionalidad descrita en los anteriores apartados. Para ello integraremos los siguientes

componentes, apoyándonos siempre que sea posible en librerías existentes:

Módulo de gráficos. Necesario para renderizar los elementos visuales del juego en

pantalla. Deberá ser capaz de renderizar gráficos 3D con efectos visuales básicos como:

- Mapeado de texturas

- Iluminación direccional y de punto

- Sombras

Módulo de física. Utilizado para simular leyes de dinámica de cuerpos rígidos en el

juego y dar así un movimiento realista a los objetos de la escena. Se necesitarán las

siguientes características:

- Dinámica de cuerpos rígidos

- Detección de colisiones para formas básicas y objetos convexos

- Colisiones

Módulo de scripting. Pieza fundamental para implementar la lógica del juego aplicando

la principal característica del motor que es la agilidad de desarrollo.

Núcleo del motor. Se encargará de orquestar todos los elementos y comunicarlos entre

sí y con el usuario.

Entorno de desarrollo del motor. Incluirá el editor visual de objetos y escenas y editor

de código.

Como requisito común para todos los componentes, necesitaremos que sean lo más

eficientes posible para tratar al menos de aspirar al nivel técnico de motores actuales.

Page 33: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

33

Todo se desarrollará de forma que sea portable, y se compilarán versiones del motor y el

reproductor al menos para PC (Windows o Linux) y Android, mientras que el editor se

compilará solamente para PC.

Page 34: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM
Page 35: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

35

2 ESTUDIO DE GAME ENGINES EXISTENTES

En este apartado vamos a realizar un estudio de las alternativas más interesantes que

existen actualmente con el fin de aprender de sus principales características y analizar sus

ventajas y sobre todo sus inconvenientes. De esta forma podremos tener un mejor criterio a

la hora de tomar decisiones para el diseño de nuestro motor.

El estudio se realizará sobre los motores Unity3D de Unity Technologies, Unreal Engine

de Epic Games y CryEngine de Crytek. Sin embargo, no vamos a entrar en demasiados detalles

concretos sobre cómo se usan ya que nuestro objetivo principal es simplemente aprender de

sus aspectos distintivos.

2.1 Unity3D Comenzamos sin duda alguna por el más utilizado entre los desarrolladores indie. Unity3D

está desarrollado por la compañía californiana Unity Technologies y, desde su publicación en

mayo de 2005, no ha parado de evolucionar y obtener reputación y ventas.

Siempre ha sido de código cerrado, pero recientemente Unity se ha sumado a los muchos

estudios que ya tienden a una política más abierta y desde mayo de 2018 está publicado en

GitHub todo el código fuente (Unity, 2018). Además, es posible extender el motor y el editor

mediante un sistema de plugins extremadamente sencillo y versátil. Y es de esta extensibilidad

de donde surge principalmente su gran éxito.

El apoyo de la comunidad es tan fuerte que el motor dispone de la conocida como Asset

Store, que es un respositorio de plugins y assets clasificados por categorías donde podemos

encontrar cualquier cosa que podamos imaginarnos. Es muy difícil plantear algo nuevo que no

haya sido creado ya por alguien y que no esté publicado en el Asset Store.

Page 36: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

36

El sistema que controla la escena de juego está implementado utilizando el patrón entidad-

componente. Lo cual promueve la facilidad de edición en tiempo de ejecución.

2.1.1 El editor El editor es muy sencillo de utilizar cuando nos habituamos a él, y la curva de aprendizaje

evoluciona a buen ritmo si tenemos cierta experiencia general en el desarrollo de videojuegos.

La siguiente imagen es una captura del interfaz de edición.

Figura 2.1. Interfaz del editor de juegos de Unity3D

El interfaz se divide en 4 ventanas principales que pasamos a describir a continuación.

La ventana Hierarchy (jerarquía) nos da una vista de árbol de los elementos que forman

la escena actual identificados por sus respectivos nombres. Esto nos es útil para encontrar y

seleccionar rápidamente cualquier elemento del juego.

Como podemos deducir de esta estructura de árbol, los elementos pueden estar anidados

unos dentro de otros. Esto significa que las transformaciones que apliquemos como escala,

rotación y traslación sobre una entidad padre se aplicarán en cascada en toda la rama de

forma lineal.

En la zona centro superior tenemos la ventana Scene, que no es más que un visor donde

podemos ver el aspecto que va teniendo nuestro juego a medida que editamos. La cámara

Page 37: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

37

desde la que vemos la escena no pertenece al juego y podemos moverla sin miedo para ver

cualquier parte que estemos editando. Cuando lanzamos el juego se cambia automáticamente

a la pestaña Game donde sí que veremos el juego desde la cámara actual perteneciente a la

escena según la situación.

La ventana Inspector (Figura 2.2. Ventana

Inspector de Unity3D) nos permite editar todas las

opciones disponibles para el objeto actualmente

seleccionado. Dependiendo de su tipo, estas

opciones pueden ser muy diferentes. Un aspecto

muy interesante es que podemos asociar un script

a cada elemento el cual es una clase C#. Si dicha

clase tiene atributos públicos, podemos editar sus

valores en tiempo de ejecución desde la ventana del

inspector sin tocar el código. Esto es muy

conveniente puesto que podemos hacer esto por

cada instancia de la clase que se encuentre en la

escena y que podemos localizar fácilmente

mediante la ventana Hierarchy, lo cual facilita

enormemente las tareas de ajuste y pruebas.

En la parte inferior de la pantalla podemos encontrar la ventana Project, donde

podemos gestionar los assets (archivos de recursos) de nuestro juego. Nos ofrece también una

vista de árbol con la estructura del directorio de disco donde tenemos el proyecto. Al hacer

click en un elemento podemos ver en la ventana de la derecha la lista de assets que contiene,

que pueden ser modelos 3D, archivos de sonido,

imágenes, y demás recursos.

Unity ofrece la posibilidad de crear formas geométricas básicas para nuestra escena como

cubos, esferas, planos, etc. pero la mayoría de estos elementos no se crean mediante el editor.

Por ejemplo, los modelos 3D para los personajes se crean mediante software de diseño 3D

Figura 2.2. Ventana Inspector de Unity3D

Page 38: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

38

especializado de terceros como 3D Studio MAX o Blender, por tanto, existen infinidad de

formatos para cada tipo de asset por lo que es necesario importarlos en cada caso a un formato

común interno del motor para poder trabajar con ellos. Este proceso de importación es

realizado por Unity automáticamente al arrastrar un nuevo elemento al directorio de trabajo.

Además, cuando se producen cambios, el sistema lo detecta automáticamente y reimporta el

elemento en tiempo real.

Además de todas estas ventanas de propósito específico, disponemos también de opciones

de configuración global para todo el juego donde podemos determinar cuestiones como la

calidad general de los gráficos, plataformas objetivo, configuración del comportamiento del

editor, etc.

2.1.2 El sistema de scripting Como se ha mencionado, el código que escribimos para programar la lógica del juego es

C#, y cada script que escribimos representa una clase que puede ser asociada a cualquier

entidad de la escena.

Estas clases representan una entidad dentro de la escena. El patrón utilizado es una

aproximación orientada a objetos del entity-component-system (ECS). Con este patrón,

podemos construir fácilmente tipos de objetos complejos cuyo comportamiento se definirá por

los componentes que contenga y que además podrán ser añadidos en tiempo de ejecución. Esto

es algo que no sería posible con otros enfoques más tradicionales de programación orientada a

objetos como, por ejemplo, si el comportamiento se definiera por medio de una jerarquía de

herencia.

Estos componentes tendrán siempre una finalidad específica (siguiendo el principio de

responsabilidad única) que iremos incorporando a nuestras entidades según los necesitemos.

Para tener una idea de lo que hacen estos componentes, describimos a continuación algunos

ejemplos:

Transform: Contiene la información para transformar el objeto (posición, escala y

rotación), es decir, la matriz de transformación, vectores de posición y escala y el

Page 39: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

39

cuaternión para la rotación. Se encarga de aplicar las transformaciones en cascada

siguiendo la posición del objeto en jerarquía de la escena.

Mesh: Representa la malla 3D del objeto que se dibujará en pantalla.

Material: Contiene las texturas, y los shaders usados para dar el aspecto deseado a la

superficie del objeto.

Rigidbody: Representa la forma del objeto (independiente de su forma real) que se

usará para los cálculos de dinámica de cuerpos rígidos para simular la física en el juego.

Normalmente se usará una forma geométrica más sencilla que la que contiene el

componente Mesh, con el fin de acelerar los cálculos a costa de un menor realismo.

Por supuesto existen muchos otros, cada uno con su propio propósito como Audio, Collider,

GUILayer, Input, Billboard, ParticleEmitter.

2.1.3 Ventajas e inconvenientes Como resumen de las ventajas de Unity3D podemos destacar las siguientes:

La favorable curva de aprendizaje permite introducirse en el desarrollo de videojuegos

a desarrolladores de todos los niveles, lo que lo hace muy atractivo para equipos indie.

Dispone de players del motor para hasta 25 plataformas como Windows, Linux, Mac,

Android, iOS, PlayStation, Xbox, Wii, Nintendo Switch, entre otras, con lo que

nuestras creaciones podrán ser portadas a cualquier sistema actual con un solo click.

Extensibilidad total mediante un sistema de plugins magistralmente implementado

que proporciona un interfaz muy fácil de extender para casi cualquier propósito.

El excelente sistema de organización y composición de la escena permite realizar un

juego sencillo en cuestión de horas, o incluso minutos, mientras que a su vez facilita

la mantenibilidad en proyectos grandes.

El inspector supone una ayuda que no tiene precio a la hora de hacer ajustes sobre el

juego, ya que en combinación con la ventana hierarchy permite manipular de forma

visual cualquier elemento instanciado en la escena en tiempo real.

El sistema automático de importación nos abstrae de los pormenores a la hora de

integrar nuestros assets creados desde fuera de Unity. Simplemente guardamos los

Page 40: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

40

cambios en el otro programa y Unity lo detectará y reintegrará en nuestra escena sin

necesidad de intervenir por nuestra parte.

Pero por supuesto también tiene varios inconvenientes, de los cuales destacamos los más

importantes:

No tiene un editor ni compilador de código propio, con lo que para editar los scripts

en C# es necesario utilizar una herramienta externa capaz de compilar este lenguaje,

como Visual Studio o Monodevelop. Además, el hecho de que no esté integrado en el

editor principal puede provocar despistes al principio si tras hacer un cambio en el

código no nos acordamos de compilar en la ventana del otro IDE antes de ejecutar en

la de Unity3D.

El sistema de ajuste y pruebas del juego puede hacerse eterno cuando la plataforma

objetivo es por ejemplo Android o iOS, debido al hecho de que por cada prueba

debemos compilar los scripts (a bytecode más eficiente), crear el paquete, transmitirlo

al dispositivo, instalarlo y ejecutarlo.

Su extremada facilidad de uso a veces oculta detalles de implementación y cuesta saber

en ocasiones cuál es exactamente la forma más óptima de realizar ciertas tareas.

A pesar de que ofrece un enorme grado de control para tareas típicas, no siempre es

sencillo aplicar ciertos efectos o implementar características más avanzadas para

necesidades concretas, teniendo que recurrir a plugins de terceros.

2.2 Unreal Engine Creado en 1998 por Epic Games para su shooter en primera persona Unreal, este motor es

una auténtica leyenda para cualquier desarrollador de videojuegos, puesto que en su día ofreció

una calidad técnica nunca vista hasta entonces que permitió a esta compañía tener una

importante presencia en un mercado extremadamente competitivo.

Page 41: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

41

El motor ha sido mejorado continuamente y utilizado para todos los títulos de la saga del

juego Unreal. Actualmente va por la versión 4, la cual aporta cambios radicales con respecto

a la primera.

A diferencia de Unity3D cuyo objetivo parece estar más enfocado al mercado indie, el

motor Unreal ha sido utilizado en proyectos de empresas de la talla de Electronic Arts, Disney,

Atari, Ubisoft o Konami por citar sólo algunas.

Además, su código es abierto desde hace tiempo, lo cual nos proporciona como

desarrolladores un valor añadido de estudio, dándonos acceso a los detalles de implementación

de uno de los motores más avanzados tecnológicamente en la actualidad.

En general Unreal nos ofrece mayor grado de control sobre nuestros juegos que Unity, y

por este motivo es la herramienta preferida para títulos comerciales triple A.

2.2.1 El editor Las diferencias en el interfaz del entorno de desarrollo con respecto al de Unity3D (Epic

Games, 2018) se ilustran en las siguientes capturas:

EDITOR EN UNITY3D:

Page 42: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

42

EDITOR EN UNREAL ENGINE:

Figura 2.3. Comparativa de editores entre Unity y Unreal

En cuanto a interfaz del editor, en la mayoría de casos simplemente hay un cambio de

nombre de cada uno de los conceptos vistos en Unity. En las capturas anteriores se asocian

con el mismo color las correspondientes funcionalidades equivalentes de cada motor. La Scene

de Unity, en Unreal se llama Viewport, y de la misma forma, la ventana Hierarchy es ahora

el World outliner, la ventana Project es el Content Browser y el Inspector es la ventana

Details.

No obstante, sí que podemos ver una nueva ventana que no existe en Unity y es la venana

Modes. Esta característica nos permite definir de forma rápida el modo de edición lo cual

habilita a su vez otros interfaces especializados. Los modos son:

Place mode: Para añadir actores (entidades con comportamiento) a la escena

Paint mode: Permite pintar al vuelo la superficie de los objetos sobre el viewport,

modificando el color de los vértices en una malla o incluso su mapa de textura.

Landscape mode: Para edición de terrenos. Permite definir desniveles sobre

superficies de forma muy cómoda.

Foliage mode: Añade vegetación sobre una escena. Incluye múltiples opciones de

personalización. Ideal para editar paisajes sobre un terreno previamente definido en el

modo Landscape.

Geometry mode: Permite editar los vértices de una malla 3D.

Page 43: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

43

2.2.2 El sistema de scripting Basado también en un patrón ECS, en Unreal tenemos dos opciones para programar la

lógica del juego y ampliar funcionalidades: Por medio de C++ o utilizando Blueprints.

El API de C++ nos da un alto grado de control y optimización del código en bajo nivel,

mientras que con Blueprints podemos definir comportamiento en nuestro juego sin recompilar,

pero no por medio de scripts tradicionales tal y como los conocemos, sino utilizando un

innovador sistema visual. Quienes hayan programado shaders para gráficos 3D utilizando

software como Blender o 3D Studio Max encontrarán este sistema muy familiar.

Blueprints es un método para programar sin necesidad de escribir ni una sola línea de

código. Se basa en un sistema de nodos que procesan información y cuyas entradas y salidas

podemos interconectar para programar cualquier tipo de funcionalidad. A continuación, se

muestra una captura del editor de Blueprints:

La intención principal de este sistema es permitir que los miembros no técnicos del equipo

de desarrollo (por ejemplo, el diseñador del juego) puedan, sin conocimientos de programación,

hacer cambios y ajustes sencillos en el juego para hacer pruebas rápidas sin que tenga que

Figura 2.4. Sistema de Blueprints de Unreal Engine

Page 44: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

44

intervenir un programador constantemente. Esto agiliza el flujo de trabajo y reduce el tiempo

de desarrollo y promueve la buena calidad del producto.

2.2.3 Ventajas e inconvenientes En Unreal Engine destacan las siguientes ventajas:

Alto grado de control por medio del API en C++. Permite a los desarrolladores de

juegos programar características avanzadas para sus juegos e innovar, por ejemplo,

con nuevos tipos de efectos visuales o de física, etc. Además, al ser C++, es posible

reutilizar código que ya tengamos o integrar cualquiera de las miles de librerías que

existen escritas en este lenguaje.

El sistema de Blueprints permite intervenir a los diseñadores en cuestiones de ajuste

de la jugabilidad, de forma que cada profesional puede dedicarse a su especialidad. Por

ejemplo, un programador tiene el conocimiento técnico que le permite implementar y

optimizar efectos visuales, pero no tiene por qué saber hacer que el juego sea divertido

o siquiera jugable. Esta tarea ahora la puede hacer directamente el diseñador.

Su Marketplace proporciona una fuente de recursos de todo tipo para incorporar a

nuestros proyectos.

En cuanto a los inconvenientes para este motor no son demasiado importantes, pero

podríamos mencionar los siguientes:

El apoyo de la comunidad, aunque es bastante fuerte, y podemos encontrar fácilmente

cualquier información en Internet, es menos popular que Unity, con lo cual la cantidad

y diversidad de material que podemos encontrar en su Marketplace aún no puede

rivalizar del todo con el Asset Store de Unity3D, por tanto, muchas cosas a las que

tiene acceso Unity, si trabajamos con Unreal deberemos crearlas nosotros desde cero.

Su curva de aprendizaje es cómoda al principio, pero a veces cuesta más aprender a

crear características más avanzadas. No obstante, esto es debido a que nos permite

mucha más libertad que otras alternativas, con lo cual a la larga se convierte en una

ventaja.

Page 45: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

45

El sistema de Blueprints no suele verse con buenos ojos por aquellos que hemos

aprendido a programar escribiendo código, y en la mayoría de ocasiones cuesta cambiar

la mentalidad para adquirir agilidad con este sistema.

Aunque es multiplataforma no llega al número de plataformas compatibles de Unity,

si bien sí que puede portarse a los principales sistemas operativos (Windows, Linux y

Mac), móviles (Android y iOS) y videoconsolas actuales (PS4, XBoxOne y Switch).

2.3 CryEngine CryEngine vio la luz por primera vez en 2002 por la empresa alemana CryTek con el apoyo

de Nvidia para el desarrollo del juego Far Cry. Nvidia utilizó este motor para demostrar todas

las capacidades técnicas de su nueva generación de chips de aceleración de gráficos. Es por

este motivo que CryEngine se ha caracterizado siempre por aprovechar al máximo el hardware

y ofrecer espectaculares gráficos en tiempo real de la más alta calidad técnica disponible hasta

la fecha.

Con el tiempo se seguido mejorando y la versión más utilizada ha sido la 3, publicada en

2009 y con la que se han realizado docenas de juegos comerciales de última generación, como

la saga Crysis, Evolve, o Rise: Son of Rome por mencionar los más conocidos.

Actualmente está en la versión V (de 2016), la cual aún no tiene ningún juego publicado,

pero ya podemos descargarla y utilizarla puesto que es de código abierto al igual que los otros

motores que hemos analizado. Esto nos da acceso a todo un mar de conocimiento sobre las

últimas tecnologías que se han desarrollado en cuanto a computación gráfica, como el

Physically Based Rendering, el Voxel-Based Global Illumination (SVOGI), Sombras

volumétricas o técnicas de imagen HDR.

2.3.1 El editor El editor de CryEngine tiene una apariencia inicial bastante más simple que los de Unreal

y Unity, no obstante, a medida que trabajamos con él vemos que esconde un alto potencial

de edición, el cual nos permite no solo definir las escenas del juego sino también crear y

modificar todo tipo de objetos 3D posibilitando la creación de cualquier forma que necesitemos

sin necesidad de recurrir a un software de terceros.

Page 46: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

46

Tiene todas las características de los anteriores motores que hemos visto pero su interfaz

está más enfocada a la edición de entidades siendo éste su verdadero punto fuerte. Por ejemplo,

la siguiente captura muestra la ventana Designer Tool que nos ofrece un completo set de

herramientas integradas para la manipulación de objetos:

Figura 2.5. Editor de CryEngine

Figura 2.6. Interfaz de edición de objetos de CryEngine

Page 47: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

47

Entre estas herramientas podemos encontrar:

Creación de las típicas primitivas básicas 3D (cubo, cilindro, esfera, cono, etc.)

Creación de formas 2D como planos, círculos o curvas (útiles para definir trayectorias

kinemáticas para los objetos).

Selección y manipulación de objetos a nivel de vértice, cara, loop, etc.

Herramientas de extrusión, conexión, colapso, recorte, mezcla booleana, etc.

Y en definitiva todo lo que necesitamos para diseñar cualquier forma que imaginemos

en el espacio 3D de la escena.

2.3.2 El sistema de scripting Al igual que Unreal, este motor también dispone de un sistema visual para scripting

funcionando bajo un patrón ECS y con el nombre de Flow Graph. Desafortunadamente, no

esta característica en CryEngine no es tan potente como los Blueprints en Unreal.

La principal diferencia es que con Blueprints se puede hacer prácticamente de todo,

mientras que Flow Graph está más limitado a la implementación de mecánicas sencillas o al

control de eventos. Para el resto de situaciones deberemos implementar nuevos nodos escritos

en C++, con los inconvenientes y ventajas que esto conlleva.

Otra importante diferencia es que con Flow Graph no podemos generar código a partir de

los diagramas que hacemos (ni viceversa). Esta característica hace muy potente a Unreal,

porque una vez que tenemos la lógica creada con Blueprints podemos convertir todo a C++

para la versión para producción y así aprovechamos todo el potencial del código compilado.

Sin embargo esto no es posible por el momento en CryEngine.

2.3.3 Ventajas e inconvenientes Las principales características positivas de este motor son:

Al estar inicialmente apoyado por Nvidia, está enfocado para sacar el máximo

provecho de la aceleración por hardware. Además, esto no solo se limita a los gráficos,

sino que también aprovecha al máximo este tipo de hardware para realizar cálculos

para el sistema de físicas por medio del API de Nvidia PhysX.

Page 48: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

48

Implementa los últimos avances en computación gráfica para la aplicación de efectos

visuales.

Su interfaz de edición es muy sencillo de usar y muy completo, permitiendo hacer un

juego sencillo sin utilizar ninguna herramienta externa.

Por otro lado, las desventajas que podemos destacar son:

Su sistema visual de scripting tiene ciertas limitaciones funcionales y no permite la

conversión a C++ y viceversa.

Es ligeramente más difícil de dominar con respecto a Unreal y Unity.

Su orientación a NVidia lo hace ligeramente menos potente que otros motores cuando

la plataforma objetivo utiliza chips ATI.

Es el menos portable de entre los motores analizados. Los juegos creados con

CryEngine funcionan solo para Windows, Oculus Rift, Xbox One y PlayStation 4.

2.4 Mejoras que aportará nuestro motor Los motores que hemos estudiado son sistemas completos, optimizados y maduros,

realizados por equipos enteros de profesionales especializados en múltiples campos y mejorados

por la comunidad de desarrolladores, con lo cual nuestro motor no puede aspirar por ahora a

igualar la inmensa cantidad de funcionalidades que ofrecen.

No obstante, sí que podemos mejorar algunas de las desventajas mencionadas como, por

ejemplo:

El sistema de ajuste y pruebas será mucho más ágil, ya que permitirá enviar código al

dispositivo de pruebas sin tener que reconstruir el paquete. Esto reducirá

drásticamente el tiempo de desarrollo de juegos.

Nuestro entorno de desarrollo tendrá un editor de código integrado, con lo que podrá

configurarse para compilar antes de ejecutar y en el futuro podrá integrarse mejor con

un depurador especializado.

Page 49: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

49

3 MODELO DE DESARROLLO Y PLANIFICACIÓN

El modelo de desarrollo que se utilizará tomará conceptos y planteamientos conocidos de

los modelos ágiles, pero en realidad no seguiremos ninguno íntegramente, ya que gran parte

de la finalidad de éstos se basan en el trabajo en equipo. En cambio, este proyecto será

realizado por una sola persona (el gestor del proyecto, el desarrollador, el tester, el propietario,

etc. es el mismo), además no podremos realizar varias tareas en paralelo por lo que todos los

flujos de trabajo se ejecutarán de forma secuencial, así que podemos obviar algunas cuestiones

que, aparte de la carga que suponen, prácticamente no nos aportarían valor en nuestro caso.

3.1 Modelo iterativo El modelo que utilizaremos será iterativo, con iteraciones de tamaño fijo de 5 días hábiles

(1 semana natural). Manteniendo las iteraciones pequeñas y del mismo tamaño, minimizamos

errores de estimación temporal y mantenemos una rutina que nos permite comparar y aprender

de iteraciones anteriores.

Cada iteración dará como salida una nueva versión de desarrollo que podrá incorporar

corrección de errores y/o un nuevo incremento en la funcionalidad y nueva documentación.

El flujo de trabajo en estas iteraciones se estructura como se muestra en la siguiente figura:

Figura 3.1. Tareas de una iteración

Page 50: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

50

No obstante, no haremos el mismo tipo de tareas en todas las iteraciones, sino que al igual

que ocurre en UP (Unified Process) o en el modelo en espiral, podremos pasar por distintas

etapas (fases) del proyecto con distintos objetivos y por tanto habrá flujos de trabajo (diseño,

implementación, pruebas, documentación, etc.) con más peso que otros.

El diseño se hará previo a la implementación, pero se hará de forma general, sin entrar en

mucho detalle y sólo para la parte del código que se vaya a realizar, ya que estamos aplicando

un modelo ágil. El diseño final podrá ser revisado y modificado tras la implementación o al

refactorizar el código.

Las pruebas unitarias no siempre podrán llevarse a cabo debido a la naturaleza del

proyecto, y es que, al ser esencialmente una aplicación gráfica, salvo algunas excepciones, no

podremos automatizar las pruebas (los resultados son visuales), y las unidades que podrían

ser testables como las funciones matemáticas, de física, colisiones, red, etc. proceden de

librerías de terceros ya probadas. Sin embargo, sí que podremos automatizar pruebas de

integración o de sistema.

3.2 Planificación adaptativa Además de los tipos de tareas mencionados (análisis, diseño, implementación y pruebas),

tendremos otras tareas que también deberán ser realizadas a medida que avanza el proyecto,

como son la planificación de las iteraciones, la documentación o la configuración del entorno.

La planificación será adaptativa, lo que significa que no haremos un plan detallado previo

de todo el proyecto. En nuestro caso tendremos, por un lado, un plan general donde sólo

identificaremos los hitos principales y, por otro lado, un plan detallado con las tareas definidas

para las primeras 2 iteraciones (la actual y la siguiente).

Al final de cada iteración, se incluirá una tarea de cierre donde se hará una valoración del

trabajo realizado y se planificará una nueva, revisando los posibles errores de estimación y

ajustando los tiempos de las tareas si fuera necesario.

Page 51: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

51

3.2.1 Plan general Para el plan general establecemos los objetivos por iteración que se muestran en la

siguiente tabla. Disponemos de 180 horas para terminar el proyecto, por tanto, definiremos el

proyecto en 5 iteraciones asumiendo una dedicación de 40 horas por semana. No obstante, las

tareas se redistribuirán finalmente a lo largo del curso para poderlo compatibilizar con otras

asignaturas de la carrera.

Iteración 1 Análisis problema, estudio motores actuales y documentación preliminar

Iteración 2 Arquitectura y diseño del reproductor y el editor y primera versión

Iteración 3 Integración de los sistemas de gráficos y física

Iteración 4 Integración de los sistemas de script y red

Iteración 5 Implementación de las funciones del editor

3.2.2 Plan detallado A continuación, se muestra una captura de las tareas del plan detallado realizado con

Microsoft Project durante la iteración 2, donde podemos ver las primeras 3 iteraciones, con su

descripción, duración, fecha de ejecución y progreso.

Page 52: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM
Page 53: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

53

4 DEFINICIÓN DE LA ARQUITECTURA

4.1 Patrón Entidad-Componente-Sistema Nuestro motor seguirá el patrón arquitectural Entidad-Componente-Sistema, también

conocido abreviadamente como ECS. Se trata de un patrón fundamentado en el principio de

composición sobre herencia por el cual se trata de evitar la creación de complicadas jerarquías

de objetos en favor de la composición, la cual promueve el bajo acoplamiento y permite

construir objetos de forma más flexible y que pueden modificarse en tiempo de ejecución.

Este patrón se utiliza principalmente en videojuegos, ya que encaja perfectamente en el

modelo de motor que aparece de forma intuitiva, y que responde a un sistema formado a su

vez por varios subsistemas independientes (gráficos, física, sonido…) que interactúan de algún

modo.

A partir de aquí, una entidad en nuestro juego, que podría ser un personaje o un arma,

no es más que un contenedor de varios componentes, cada uno de los cuales procede de uno

(o incluso varios) de los sistemas mencionados. Estos componentes tendrán por tanto una

finalidad concreta por lo que además serán altamente cohesivos. Por ejemplo, un personaje

podría estar formado por:

Componente de transformación: Indica la posición, escala y rotación del objeto en el

espacio 3D que será usado, entre otros, por los sistemas de gráficos, colisiones y física.

Componente de gráficos: Sería una malla de un modelo 3D que representa una figura

humana a dibujar en la pantalla.

Page 54: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

54

Componente de colisiones: Primitiva o forma geométrica utilizada para determinar si

el personaje está en contacto con otro objeto de la escena, como un enemigo, etc.

Componente de física: Condiciones de masa, densidad, rigidez, etc. a tener en cuenta

a la hora de resolver las fuerzas que actúan sobre el personaje para desplazarlo a un

lado y/o modificar su postura de forma realista cuando impacta con un objeto.

Componente de sonido: Fragmentos de audio a reproducir según la situación.

Componente de IA: Código (scripts) con la lógica de comportamiento del personaje.

Cada sistema almacenará en una lista sólo aquellos componentes que sean pertenecientes

a su dominio, con lo que se mantiene el acoplamiento bajo.

Además, se cumple también el principio de responsabilidad única, ya que estos

componentes contienen poco más que información y los métodos para tratar con ella,

delegando a sus respectivos sistemas la responsabilidad de realizar las tareas necesarias. Por

ejemplo, un componente de gráficos tendrá la información de cómo es el objeto, pero no será

éste el responsable de pintarlo en pantalla, sino que será el sistema de gráficos el encargado

de esa tarea.

En nuestro motor tendremos los siguientes sistemas o tipos de componentes:

Transformación (posición, escala, rotación), Gráficos (modelo a renderizar), Colisiones

Figura 4.1. Ejemplo Entidad-Componente-Sistema

Page 55: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

55

(bounding volumes), Física (velocidad, aceleración…), Lógica (código Lua) y Entrada (entrada

de usuario). El diagrama en forma de rejilla que se muestra a continuación representa, un

ejemplo concreto de lo que pretendemos conseguir. Los círculos son instancias de componentes

de sus respectivos sistemas (representados con rectángulos):

Algo que debemos destacar es que los componentes están agregados a sus respectivos

sistemas, y no a las entidades como inicialmente se puede pensar. En realidad, no va a existir

un objeto entidad como tal, sino que una entidad será un concepto lógico para el cual sólo

necesitaremos un identificador para referirnos a él. De este modo, los sistemas almacenarán

los componentes en tablas hash donde se relacionan por su id, y este id representará y será

para nosotros la entidad.

Otro aspecto interesante es que, si nos fijamos en los ejemplos anteriores, vemos que los

contenedores son básicamente ranuras de datos que podrán almacenarse en la misma región

de memoria según dominio. Esto quiere decir que el sistema propicia un diseño guiado por

datos (data-oriented design), el cual nos permite aprovechar el principio de localidad para

optimizar el uso de la cache, evitando gran parte de los fallos de lectura/escritura (cache

misses).

4.2 Despliegue Como ya se ha mencionado, el proyecto se divide en dos grandes unidades: El reproductor

y el editor.

El reproductor es la parte que ejecuta y muestra el juego según la lógica programada,

mientras que el editor es una herramienta visual que permite al desarrollador de juegos editar

el proyecto que está realizando. Este editor tendrá como dependencia al reproductor, ya que

se utilizará para poder visualizar el progreso de nuestro trabajo y realizar pruebas.

Habitualmente necesitaremos trabajar con el editor en un ordenador de escritorio mientras

que podremos tener versiones autónomas del reproductor ejecutándose en las distintas

plataformas objetivo (por ahora sólo Android y PC para nuestro proyecto). El esquema de

despliegue podría quedar así:

Page 56: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

56

En la versión de desarrollo, estos reproductores estarán permanente a la escucha de nuevas

órdenes desde el editor para ejecutarlas en tiempo real mientras que, en la versión de

producción, esta funcionalidad estará deshabilitada y el comportamiento será el programado

en el paquete instalable.

4.3 Arquitectura del reproductor Siguiendo el patrón ECS, aplicamos algunos cambios a la arquitectura con respecto a la

idea inicial planteada en el apartado de introducción (ver 1.3 Solución inicial). Ahora el script

es sólo un componente más perteneciente a una entidad y se agregará a su correspondiente

sistema. Los datos y el comportamiento serán determinados por un gestor de la escena, el cual

será el encargado de actualizar los distintos subsistemas, incluyendo llamadas a los métodos

correspondientes de los scripts para actualizar el estado de la lógica.

Figura 4.2. Despliegue del editor y los reproductores

Page 57: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

57

El siguiente esquema muestra la nueva situación de la arquitectura:

Como se puede ver, debido a que los sistemas estarán implementados en C++,

necesitaremos una capa intermedia entre el sistema de lógica y el resto de subsistemas que se

encargue de adaptar la comunicación entre estos.

También, como podemos ver, existen para estos subsistemas distintos tipos de atributos o

componentes. Por ejemplo, el sistema de colisiones puede tratar con distintos tipos de

primitivas básicas o incluso con una malla de forma arbitraria. Será decisión del programador

de juegos qué tipo de volumen es más adecuado según el caso.

La entrada de jugador también podrá proceder de distintos dispositivos hardware y

podremos asociar uno o varios a una determinada entidad de la escena. En este componente

definiremos el mapeo de la entrada de hardware con determinadas acciones, por ejemplo, el

mapeo de la barra espaciadora con el disparo del personaje. Esto podrá redefinirse por el

usuario en tiempo de ejecución. Además, otro aspecto útil de tener este componente

desacoplado del resto, es que podemos sustituirlo por un grabador / reproductor que puede

sernos útil para grabar una jugada y servir posteriormente como una demo o incluso como

prueba funcional.

Figura 4.3. Arquitectura del player

Page 58: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

58

En cuanto al componente de transformaciones, podrán afectar a la posición, escala y

rotación, y además contendrán un nodo padre que indicará si la entidad está anidada en otra.

Esto hará que las transformaciones a aplicar sean relativas al padre multiplicando las

respectivas matrices en cascada de forma recursiva. Además, como ya hemos visto, este

componente se presenta como un intermediario que desacopla los sistemas de colisiones, física

y gráficos.

El API de hardware a usar dependerá de las dependencias utilizadas. En este caso, por

ejemplo, para los gráficos, tenemos Ogre que a su vez utiliza OpenGL (o DirectX/OpenGL en

Windows).

4.4 Formato y recursos del proyecto de juego Toda la información del juego a crear por nuestro motor se guarda en una carpeta de

proyecto que contiene todos los archivos de recursos (carpeta resoruces), así como otros

archivos especiales de información que definen las entidades (entities), las escenas (scenes), y

el proyecto (project.json). Para estos últimos se empleará el formato json con el fin de que

puedan también editarse manualmente y facilitar la interoperabilidad. La estructura de la

carpeta de proyecto se muestra a continuación:

resources Contiene archivos de script, texturas, modelos 3D, materiales, etc, que pueden ser usados por una o más entidades de nuestro juego. El desarrollador puede organizar esta carpeta libremente.

entities Cada entidad que podemos insertar en una escena se guarda en esta carpeta como como un archivo json que define sus componentes y propiedades por defecto.

scenes

En esta carpeta se guardan los json de las escenas. Estas contienen una estructura jerarquizada de entidades donde, para cada una, se especifican sus propiedades de instanciación concretas, como su posición, rotación, valores de los atributos públicos del script, etc.

project.json Contiene básicamente una lista de escenas y la configuración global del proyecto. La primera escena de la lista será la que se abrirá de forma inicial.

Page 59: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

59

5 DISEÑO DEL INTERFAZ

5.1 Interfaz gráfica del editor La siguiente imagen es un mockup de la pantalla principal del editor. La idea inicial no se

aleja del concepto de otros motores como Unity o Unreal, pero hay algunas diferencias clave

que explicaremos a continuación.

Figura 5.1. Mockup pantalla principal del editor

Page 60: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

60

Como vemos, se divide en 5 partes principales:

Escena: Sería el equivalente a la ventana Hierarchy de Unity y contiene el árbol de

objetos de la escena. Al hacer click sobre un elemento se selecciona en la escena la

entidad correspondiente, y la ventana propiedades muestra también los ajustes de esta

entidad.

Recursos: Es un selector de ficheros que muestra el árbol del directorio de trabajo.

Este selector nos ayudará a la hora de asociar recursos a los componentes de las

entidades, o para crear nuevas entidades pinchando y arrastrando un recurso a la vista

de la escena.

Propiedades: Aquí podemos editar las opciones específicas de cada objeto al igual

que la ventana Inspector de Unity. Podremos seleccionar el componente a editar desde

el correspondiente desplegable. Si un componente no existe para la entidad actual

aparecerá una opción para crearlo. También se podrá activar/desactivar la entidad

asociada. Una entidad desactivada no se muestra en pantalla y se ignora a todos los

efectos durante la ejecución.

Consola: Muestra mensajes de depuración procedentes de la ejecución de los scripts.

La diferencia con la consola de Unity es que aquí podemos escribir código que se

interpretará en tiempo de ejecución.

Área de edición: Situada en la parte central, nos permite ver la escena tal como se

mostrará en el juego y además, a diferencia de Unity, tiene un modo “Código” en el

que podemos editar el script del objeto actualmente seleccionado. Para cambiar de un

modo a otro sólo tenemos que hacer click en la pestaña correspondiente.

Los botones de Stop, Play y Pause que pueden verse en el mockup en la esquina superior

derecha sirven para detener, ejecutar y pausar la ejecución del proyecto en todos los

dispositivos actualmente conectados.

Menú Archivo

El menú archivo tendrá las opciones de Nuevo, Abrir, Guardar y Cerrar con respecto al

proyecto o a la escena, y también la opción de Salir del programa.

Page 61: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

61

Si se abre una escena se abrirá implícitamente el proyecto asociado.

El editor no será multiproyecto, es decir, si se abre o se crea un nuevo proyecto se cerrará

el actual. No obstante, si seleccionamos cualquier orden que implique cerrar un documento

donde tengamos cambios sin guardar se pedirá confirmación.

Menú Edición

Tendrá las opciones del portapapeles: Copiar, Pegar y Cortar que, en el modo Escena,

actuarán sobre la entidad actualmente seleccionada y, en el modo Código, actuarán sobre el

éste tal como lo haría cualquier editor de texto.

También tendremos opciones de Deshacer y Rehacer que se implementarán mediante el

patrón command.

Finalmente, el menú edición tendrá la opción Preferencias donde se podrá editar la

configuración general de la aplicación.

Menu Entidad

Desde aquí podremos añadir nuevas entidades a la escena. Las nuevas entidades creadas

aparecerán en la posición del cursor 3D, y tendrán asociados los componentes necesarios en

función de la naturaleza de la entidad. Estas podrán ser:

Primitivas: Esfera, Cubo, Cono.

Luz

Cámara

Entidad vacía

Menu Ayuda

Aquí se mostrarán enlaces al manual del API, el manual del editor y un apartado “acerca

de” donde aparecerá el nombre de la aplicación, versión, autor e información legal.

Page 62: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

62

5.2 Interfaz de consola La consola nos permitirá enviar código de script en línea o incluso archivos al dispositivo

de pruebas. El código enviado se ejecutará inmediatamente mediante el intérprete Lua en el

reproductor de la máquina destino.

El envío de archivos nos será útil para cuando hagamos cambios en assets en local y

queramos ver el resultado rápidamente sin tener que copiarlos a mano.

La consola se podrá utilizar desde el terminal del sistema operativo o bien desde una

ventana embebida en la aplicación del editor gráfico. En ambos casos se deberá especificar al

inicio la ip y puerto de conexión al dispositivo remoto al que vamos a conectar, así como la

ruta local a la carpeta del proyecto que estamos realizado.

Esta configuración se podrá establecer en la ventana de propiedades en el editor gráfico.

En el caso del terminal, se puede especificar en la línea de comandos. Para ejecutar la consola

en el terminal escribimos:

Console <ip> [puerto] [root]

Donde la ip es obligatoria. El puerto por defecto es 8080, pero se puede cambiar según

nuestra preferencia. El directorio de raíz del proyecto (root) por defecto es el actual (.), pero

también podemos especificar otro en este momento.

Una vez estamos dentro, podemos ejecutar código Lua tecleándolo directamente en la

consola y éste se enviará y ejecutará en el dispositivo conectado. Para ver una referencia

completa del API del motor en Lua, remitimos al capítulo correspondiente (Manual del API

de Lua).

Para acceder a funcionalidades extra de la consola que no pertenecen al lenguaje de script,

empezaremos a escribir la orden comenzando con el carácter | (pipe). De esta forma tenemos

las siguientes funciones:

- |quit Sale de la consola

- |transfer <nombre_fichero> Envía un archivo al dispositivo conectado.

Page 63: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

63

- |update Busca ficheros actualizados de forma recursiva en el directorio del proyecto

y los envía si su fecha de modificación es posterior a la versión que está en el dispositivo

conectado.

Todo ello se podrá hacer conectado por wifi sin necesidad de cables como tradicionalmente

enviaríamos archivos al móvil. Además, obsérvese que ya no es necesario reinstalar la

aplicación con motivo de un cambio en un asset. Los ficheros actualizados son reconocidos

automáticamente por el reproductor previamente instalado.

Page 64: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM
Page 65: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

65

6 ELECCIÓN DE LAS DEPENDENCIAS

Como último paso antes de comenzar con la implementación, en esta sección vamos a

hacer una comparativa de las distintas librerías disponibles como alternativa para cada uno

de los sistemas a integrar. Así podremos decidir con criterio cuáles serán las elegidas para

nuestro motor.

Los factores determinantes serán el tipo de licencia, las funcionalidades, la documentación

y la portabilidad a las plataformas objetivo de nuestro proyecto, que inicialmente serán PC

(Windows/Linux) y Android.

6.1 Librería de matemáticas De entre las librerías de código abierto y libres que podemos encontrar para este propósito,

sin duda la más popular es glm, la cual trata de imitar el interfaz de las funciones del lenguaje

de shader glsl de OpenGL. Esta similitud la hace ideal si usamos este API de gráficos, además

es muy sencillo encontrar documentación tanto para glm como para glsl lo cual hace sencillo

el desarrollo.

No obstante, como veremos en los siguientes apartados, los motores que vamos a usar para

gráficos y física ya incorporan sus propias librerías de matemáticas. Esto es un inconveniente

ya que cada uno utiliza la suya, con lo cual en ocasiones podremos necesitar usar alguna de

las extensiones de integración de estos motores o convertir de una estructura a otra cuando

pasemos datos entre estos dos sistemas.

En el futuro, si implementamos nuestras propias librerías de gráficos y física en el motor,

podemos utilizar una librería de matemáticas común para todo el proyecto o crear la nuestra

Page 66: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

66

propia, pero por ahora nos ceñiremos a lo que nos proporcionan los sistemas que vamos a

utilizar.

6.2 Librería de gráficos Para esta dependencia se han considerado las librerías: Ogre, Irrlicht y OpenSceneGraph

por ser las que tienen más potencial y mayor apoyo de la comunidad.

6.2.1 Ogre Se trata de un motor de gráficos 3D de código

abierto, maduro y sencillo de usar. Fue inicialmente

lanzado en 2005 bajo licencia LGPL pero, actualmente,

desde la versión 1.7 se distribuye bajo licencia MIT

desde su web (https://www.ogre3d.org/).

Recibe actualizaciones muy frecuentemente y tiene un gran apoyo por parte de la

comunidad. A fecha de 23 de junio de 2018, la última versión estable es la 1.11.1 de abril de

2018, y suele actualizarse aproximadamente cada 4 meses. Además, disponemos de la opción

de descargar el último snapshot de desarrollo del repositorio para ver el estado de las últimas

características que se actualizan a diario.

Con Ogre se han desarrollado juegos comerciales de gran éxito, como Torchlight I y II,

Hob o X-Morph: Defense, los cuales podemos comprar actualmente en plataformas como

Steam o GoG.

La documentación es muy completa, y dispone de tutoriales, una wiki oficial y una guía

de referencia, aunque a veces cuesta encontrar algo para la versión actual. Sin embargo,

habitualmente cualquier información especial que necesitemos se resuelve rápidamente en los

foros oficiales, que son muy activos (https://forums.ogre3d.org/).

Internamente funciona por medio del API Direct3D 9 y 11, OpenGL y WebGL, y en

cuanto a características técnicas dispone de todo lo que podamos necesitar para la

representación de gráficos. Entre estas funcionalidades podemos destacar:

Figura 6.1. Logo de Ogre

Page 67: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

67

Sistema cómodo de gestión de materiales (con texturas, shaders, iluminación…).

Mallas progresivas con distintos LOD generados de forma manual o automática.

Batcher estático (optimiza el renderizado).

Soporte de animación por esqueletos y arrays de pesos

Sistema de escena jerárquico basado en nodos

Distintos modos de renderizado de sombras

Sistema de partículas

Soporte para billboards, skyboxes, skyplanes y skydomes

Gestión automática de objetos transparentes

Por último, pero no menos importante, hay que mencionar que es el más portable de todos

los motores analizados, con soporte de serie para Windows, Linux, Mac, Android, iOS y

Windows Phone.

6.2.2 Irrlicht Este motor fue desarrollado por Nikolaus

Gebhardt y lanzado por primera vez en 2003. Su

licencia está basada en la zlib/libpng. Su desarrollo no

es tan activo como el de Ogre, siendo la última versión estable la 1.8.4 del 9 de julio de 2016

(hace más de dos años). No obstante, sigue siendo una opción a considerar ya que su desarrollo

sigue vivo en el repositorio de sourceforge (http://irrlicht.sourceforge.net/), y además, al ser

de código abierto, existe la posibilidad de mejorarlo o corregirlo para nuestras necesidades. De

hecho, si miramos en este repositorio podemos encontrar los avances con la versión 1.9, la cual

utiliza el API de gráficos Vulkan, dejando ya atrás OpenGL.

En cuanto a los juegos o motores creados con Irrlicht, no son tan populares como en el

caso de Ogre ni tan actuales. Entre ellos podemos destacar Bolzplatz o Gekkeiku Online.

La documentación dispone de multitud de tutoriales oficiales que demuestran toda la

funcionalidad del motor. La guía de referencia, aunque escasa (está generada automáticamente

desde el código) está bastante bien estructurada. Sin embargo, no tiene una wiki oficial como

la de Ogre.

Figura 6.2. Logo de Irrlicht

Page 68: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

68

Algo destacable de este motor es sin duda la gran cantidad de bindings (no oficiales) que

existen para multitud de lenguajes como C++, Java, Lua, Python, Perl o Ruby.

Sobre las funcionalidades que ofrece, si nos ceñimos a su última versión estable, no tiene

mucho que destacar con respecto a Ogre, pero podemos mencionar las siguientes:

Sistema de animación de agua. En Ogre también se puede hacer, e incluso de manera

más realista que la simulación que trae Irrlicht, aunque requiere de plugins externos

como el Hydrax creado por Xavier Verguín González.

Sistema de geomipmapping. Se trata de una técnica que optimiza el renderizado de

terrenos utilizando distintos niveles de detalle según la distancia.

6.2.3 OpenSceneGraph Este motor surge originalmente para aplicaciones de simulación

visual o científica en tiempo real, aunque se puede utilizar perfectamente

para videojuegos, de hecho, se le considera el más eficiente y estable de

entre los tres analizados. Este proyecto se inició en 1998 por Don Burns,

y su última versión es la 3.6.0 de Abril de 2018. Utiliza una licencia

personalizada para OpenSceneGraph basada en LGPL.

En realidad, no existe actualmente ningún videojuego o motor de juegos que utilice esta

librería gráfica. Su principal baza es que permite aprovechar la estructura de grafo para

optimizar de forma semi-automática el renderizado de los elementos de la escena ordenándolos

y agrupándolos por tipo de material, shader, textura, etc. y así evitar cambios de contexto en

el hardware.

La documentación no es muy extensa. Aunque las guías de iniciación son bastante útiles

y existen libros dedicados a este motor.

Entre las características que lo distinguen de Ogre e Irrlicht destacamos:

Utiliza un state graph, el cual permite optimizar las llamadas a OpenGL minimizando

el número de cambios de estado.

Figura 6.3. Logo de OpenSceneGraph

Page 69: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

69

Dispone de un profiler de renderizado con el que podemos detectar cuellos de botella.

Es muy estable.

Es modulable y sencillo de extender por medio de lo que denominan OSG “kits”.

6.2.4 Decisión sobre la librería de gráficos Inicialmente, la opción favorita era Ogre, pero tras probar las tres alternativas, surgen

nuevos inconvenientes. El principal de ellos es que en Ogre no hay implementado un interfaz

que nos abstraiga de ciertos detalles dependientes de la plataforma (Windows/Android) como

la gestión de los eventos de hardware, la creación de la ventana o la superficie de OpenGL.

De hecho, la última versión estable, en realidad tiene problemas sin resolver en Android

tal como se puede deducir de los foros oficiales. Por otro lado, si vamos a la versión anterior,

el soporte para Android está pensado para ser utilizado desde Java por medio de JNI, lo cual

no nos interesa porque nuestro motor estará en C++ y por tanto nos dificultaría la

reutilización de código o nos obligaría a crear adaptadores.

Además, no existe un ejemplo sencillo para Android desde el cual partir, sino que la única

aplicación completa que nos proporciona el SDK es el SampleBrowser, el cual incluye toda la

funcionalidad del motor de gráficos en un solo programa y, por tanto, es enorme, demasiado

complejo y con un diseño confuso, con lo cual resulta muy complicado desgranarlo para extraer

lo que necesitamos.

En cuanto a Irrlicht, se descarta principalmente por no disponer de una versión estable

actualizada, con lo cual el que nos queda es OpenSceneGraph que, aunque también tiene

sus inconvenientes como la falta de documentación de nivel intermedio, no nos ha dado

demasiados problemas a la hora de integrar en nuestro proyecto tanto en Windows, Linux o

Android.

6.3 Librería de física Existen pocas alternativas para física 3D tan

maduras, estables y bien documentadas como Bullet

Physics (http://bulletphysics.org/wordpress/), así que Figura 6.4. Logo de Bullet

Page 70: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

70

podríamos decir que la decisión es clara y ya está tomada desde el principio. No obstante,

listamos a continuación otras librerías que se han considerado:

MuJoCo: (http://www.mujoco.com). Viene de “Multi-Joint Dinamics with Contact”.

Es una librería de pago muy eficiente, con multitud de características y principalmente

diseñada para facilitar la investigación en robótica y biomecánica.

ODE: (http://www.ode.org/). Para simulación de dinámica de cuerpos rígidos. Se ha

utilizado en multitud de juegos comerciales como Call of Juarez, Dead Island, Mario

Strikers Charged o Resident Evil: The Umbrella Chronicles. La última versión estable

es de 2014.

Newton Dynamics: (http://newtondynamics.com). Con licencia zlib y muy

actualizada, aunque la lista de funcionalidades no llega al nivel de Bullet, y la

documentación tampoco es tan abundante.

Por su parte, Bullet Physics es un motor de física utilizado en videojuegos (por ejemplo,

Grand Theft Auto V), en el cine (con películas como Shrek 4 o Cómo Entrenar a tu Dragón),

en herramientas de diseño 3D (como Blender) o incluso en otros motores de juegos, como

Torque3D, Xenko o C4 Engine. Esto nos puede dar una idea del grado de madurez de esta

librería.

Bullet se actualiza periódicamente y su documentación es muy completa y accesible.

Además, dispone de una wiki con gran cantidad de tutoriales para todos los niveles, aunque

los más avanzados se encuentran permanentemente en construcción.

Las funcionalidades más destacables de este motor de física son las siguientes:

Simulación de cuerpos rígidos y blandos

Detección de colisiones discreta

Detección de colisiones continua y tiempo de impacto con rotación

Varias primitivas básicas y malla de colisión

Calculo de punto más cercano y estimación de profundidad de penetración

Descomposición en objetos convexos

Page 71: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

71

6.4 Lenguaje de scripting Para el propósito de poder hacer cambios en el código de los juegos sin necesidad de

recompilar e incluso editar el código en tiempo de ejecución, integraremos un motor de script

que deberá ser lo más eficiente posible, ya que tendrá que funcionar sin perjudicar el ritmo

del juego.

Para el script se han considerado los lenguajes Lua, JavaScript y Python que pasamos a

analizar a continuación.

6.4.1 Lua Aparecido en 1993, y actualmente publicado bajo licencia MIT

(https://www.lua.org/), este lenguaje imperativo basado en C se ha

usado en infinidad de aplicaciones y videojuegos comerciales, de los cuales

los más populares son World of Warcraft, Grim Fandango o Minecraft.

Su principal característica es que es sencillo y ligero y su máquina virtual

no requiere de ninguna dependencia para ser embebido en una aplicación

C/C++.

Además, es muy maduro, robusto y sobre todo rápido. De hecho, se le considera por varios

benchmarks como el lenguaje más rápido de entre los que son interpretados. Pero si aun así

necesitamos más velocidad, podemos hacer uso del compilador just-in-time (LuaJIT), aunque

no siempre podremos aprovechar esta ventaja en todas las plataformas. Por ejemplo, Apple

nos prohíbe esta característica para poder publicar en su AppStore.

Sus principales características son:

Tipado dinámico.

Todas las estructuras están basadas en lo que en Lua se conoce como tablas.

A partir de meta-tablas se pueden construir estructuras más complejas como objetos.

Los índices numéricos de los arrays comienzan por 1 (no por 0)

Funciones de orden superior.

Colector de basura.

Figura 6.5. Logo de Lua

Page 72: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

72

En cuanto a las opciones de binding con C/C++, la propia librería ofrece funciones de

acceso a la pila para hacer llamadas al código de manera manual. Pero, para simplificar las

cosas, existen multitud de librerías y herramientas dedicadas a facilitar la conexión entre

nuestro código C++ y Lua, como por ejemplo Sol2 o Swig.

6.4.2 JavaScript Surgido en 1995, y basado en el estándar ECMAScript, es un lenguaje

imperativo, muy similar a C, con ciertas funcionalidades que le dan

características de orientación a objetos. La principal ventaja de este lenguaje

es su popularidad, y es que prácticamente cualquier programador conoce

este lenguaje (sobre todo quien alguna vez ha programado una web), con lo

cual, si lo incorporásemos a nuestro sistema, sería sencillo encontrar

desarrolladores interesados en hacer juegos para nuestro motor.

En cuanto a su uso en videojuegos, JavaScript se utiliza principalmente en juegos web

(HTML5), e incluso es una de las opciones de script del conocido motor Unity3D.

Existen multitud de máquinas JavaScript procedentes principalmente de los navegadores,

como son SpiderMonkey (Firefox), V8 (Chrome), JavaScriptCore (Safari) o Chakra (Microsoft

Edge).

Es posible extraer el motor JavaScript a partir de los fuentes publicados de uno de estos

navegadores para embeber código en nuestro proyecto (aunque en algunos casos no es sencillo

compilarlos para Android). Sin embargo, al estar destinados a usarse para la navegación, esto

tiene como desventaja de la cantidad de carga que aporta a la máquina virtual todas las

funcionalidades de acceso al DOM y la web. Esto ralentiza innecesariamente el motor ya que

no vamos a necesitar todas estas características.

Como alternativa, existen otros intérpretes de JavaScript para C++ destinados a

microcontroladores y, por tanto, más ligeros y posiblemente suficientes para nuestro propósito,

Figura 6.6. Logo de JavaScript

Page 73: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

73

como son Tiny-JS, Espruino, V7 o JerryScript, aunque son tan minimalistas que no permiten

compilación JIT.

6.4.3 Python Este lenguaje hizo su aparición en 1991. Es multiparadigma:

orientado a objetos, imperativo y funcional. Se caracteriza por ser un

lenguaje donde prima la limpieza y organización del código. Por

ejemplo, el uso correcto de la indentación es obligatoria ya que es la

que define los bloques de código al compilar.

A pesar de no ser tan popular como JavaScript, se utiliza en una

gran cantidad de aplicaciones de ámbito científico, de cálculo,

educativo, de diseño gráfico y de desarrollo. Por ejemplo, es el script

de programación de extensiones de Blender.

La máquina Python puede compilarse e integrarse de forma sencilla con nuestro código

C++. Además, la librería está muy bien documentada. Sin embargo, en comparación con los

otros lenguajes que hemos considerado (Lua y JavaScript) su intérprete es el menos eficiente,

y es éste el principal motivo por el que queda prácticamente descartado para el desarrollo de

nuestro motor donde necesitamos exprimir en tiempo real toda la potencia posible del sistema.

6.4.4 Decisión sobre el lenguaje de scripting Finalmente escogemos Lua por ser el más ligero y eficiente. Como sistema de binding con

C++ usaremos Sol2 (https://github.com/ThePhD/sol2).

JavaScript queda descartado por ser más pesado con funcionalidades innecesarias y porque

los mejores motores JS necesitan de ciertas adaptaciones para poder compilarse en Android.

En cuanto a Python, dado que la eficiencia es un aspecto crítico para un motor de juegos,

se descarta por no ser tan eficiente como las otras alternativas analizadas.

Figura 6.7. Logo de Python

Page 74: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

74

6.5 Interfaz gráfica de usuario para el editor Puesto que nuestro motor estará escrito en C++, lo más adecuado sería que el sistema de

GUI para el editor estuviera también escrito en C++ o al menos que fuera fácil de integrar

con código escrito en este lenguaje. Además, queremos que pueda compilarse sin problemas en

varias plataformas (al menos para Windows y Linux, y en un futuro para Mac).

6.5.1 Qt Una librería que ofrece todas estas características es Qt

(https://www.qt.io), la cual tiene un gran soporte de la comunidad

y es un proyecto muy activo, respaldado entre otras compañías por

Nokia. Además, dispone de una serie de facilidades que nos vienen

muy bien dada la naturaleza de nuestro proyecto, por ejemplo:

Es posible integrar OpenSceneGraph directamente con Qt

Tiene un tipo de cuadro de texto con resaltado de sintaxis, ideal para nuestro editor

de código.

Dispone de controles avanzados y muy personalizables para cualquier propósito.

Editor de interfaces visual muy sencillo de usar.

Portable a infinidad de plataformas.

Fácil integración con C++.

Opción de licencia LGPL.

Internamente utiliza OpenGL para el renderizado de controles, lo que permite crear

interfaces avanzados y vistosos que se desmarcan de las típicas librerías del sistema. Además,

su estructura está orientada a un diseño responsive.

Una desventaja es tal vez que Qt ofrece dos licencias: Una comercial (de pago) y otra open

source gratuita (bajo LGPL / GPL). La versión comercial contiene una serie de herramientas

no disponibles en la versión libre. Además, ciertas funcionalidades están condicionadas a su

uso sólo bajo GPL.

Figura 6.8. Logo de Qt

Page 75: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

75

6.5.2 wxWidgets Se trata de otra librería de interfaz gráfica ampliamente

utilizada y disponible para su descarga desde la web oficial

https://www.wxwidgets.org. La principal característica de esta

librería es que está diseñada como un interfaz que sirve de

wrapper para la implementación interna que se usa realmente.

Dicha implementación es básicamente un port del core de la

librería y puede estar implementado bajo distintos sistemas en función de la plataforma

objetivo. Actualmente existen los siguientes ports en desarrollo activo:

wxGTK: Port recomendado para Linux

wxMSW: Port para Windows (XP, Vista, 7, 8 y 10)

wxOSX/Carbon: Para aplicaciones Carbon en Mac OS X 10.5 o superior

wxOSX/Cocoa: Para aplicaciones Cocoa en Mac OS X 10.5 o superior

wxX11: Port para Linux por medio del display X11

WxWidgets dispone de todos los controles que podamos imaginar, los cuales, como

podemos suponer, utiliza en cada plataforma la librería que le ofrece el sistema, así se asegura

el máximo de compatibilidad y evita muchos problemas. El inconveniente es que cada port

debe ser mantenido y actualizado de manera independiente y no todos estos proyectos siguen

el mismo ritmo de actualizaciones.

Un inconveniente es que el desarrollador de wxWidgets no ofrece una herramienta de

edición de ventanas visual, así que debemos recurrir a proyectos de terceros.

6.5.3 Decisión sobre la librería de GUI Por su gran soporte de la comunidad, su facilidad de uso, el editor de interfaz, su variedad

de controles como el editor de código con resaltado de sintaxis, y la posibilidad de integración

con OSG, escogemos Qt como librería de interfaz gráfica de usuario descartando wxWidgets.

Figura 6.9. Logo de wxWidgets

Page 76: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

76

6.6 Librería de sockets Una de las principales bazas de nuestro motor es que es capaz de enviar un script desde el

PC de desarrollo hasta el dispositivo de pruebas (habitualmente Android). Para ello es

necesario establecer una comunicación de red entre las dos máquinas que no necesariamente

tienen el mismo hardware o sistema operativo.

La primera idea fue la de utilizar las librerías de sockets nativas disponibles en cada

plataforma donde se implementaría el motor. Por ejemplo, para Android utilizaríamos los

sockets estándar de Unix, y para Windows elegiríamos WinSockets, por su similitud con los

primeros.

La desventaja de esto es que implica trabajar con estas librerías a bajo nivel y además,

por más que se parezcan, se hace inevitable escribir algunas partes del código específicas para

cada plataforma.

Como solución se propone utilizar Boost:Asio, la cual es una librería que proporciona,

además de multitud de funcionalidades de entrada y salida, un interfaz de alto nivel para

creación y uso de sockets muy sencillo de utilizar y multiplataforma.

El problema que surgió a partir de esta decisión es que esta librería tiene algunas

dependencias con Boost que deben ser compiladas y en este caso daba errores al tratar de

compilar para Android. Además, incrementaba innecesariamente el tamaño y tiempo de

compilación del código.

La solución final ha sido utilizar una versión de Asio directa de sus creadores que no

requiere Boost para funcionar. Podemos encontrar esta librería en la web oficial: https://think-

async.com/Asio/.

Page 77: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

77

7 CONFIGURACIÓN DEL ENTORNO

El proyecto estará programado en C++. El IDE principal será Visual Studio Community

2017 (versión 15.6.4) funcionando bajo Windows 10, pero en cualquier caso lo que haremos

será un proyecto CMake multiplataforma que podrá compilarse desde cualquier SO y podrá

usar el generador adecuado para cualquiera de nuestras plataformas objetivo. Se usará la

versión 3.11.4 de CMake.

Para la creación del interfaz de usuario del editor utilizaremos el IDE Qt Creator 4.6.2

Community, y para el port de Android del reproductor utilizaremos el IDE Android Studio

versión 3.1.3.

Para el port de Linux, dado que esta no será la plataforma principal de desarrollo, no se

utilizará ningún IDE concreto, sino que se compilará todo utilizando la herramienta make de

línea de comandos desde Ubuntu 16.04.

En los siguientes apartados se explicará cómo se han compilado todas las dependencias del

proyecto para las plataformas objetivo (Windows, Linux y Android) para finalmente crear el

proyecto inicial tanto para el editor como para el reproductor.

A continuación, se muestra la relación de todo el software utilizado junto con sus

correspondientes versiones.

Sistema operativo Versión

Windows 10 Pro 64 bits

Ubuntu 16.04 64 bits

Android 5.0.1

Page 78: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

78

IDE Versión

Visual Studio Community 2017 15.6.4

Qt Creator 4.6.2

Android Studio 3.1.3

Librería Versión

OpenSceneGraph 3.6.2

Bullet 2.87

Lua 5.3.5

Sol 2.20

Herramientas desarrollo Linux Versión

gcc 5.4.0

Make 4.1

CMake + CMake-GUI 3.5.1

Otras herramientas Versión

CMake + CMake-GUI (Windows) 3.11.4

Android SDK API Level 28

Android NDK r15c

Siguiendo las instrucciones que se describen, el proyecto creado debería funcionar en

cualquier PC con Windows o Linux, o en cualquier smartphone con Android. No obstante, se

describe a continuación el hardware utilizado durante el desarrollo para así garantizar la

reproductividad de los pasos.

Hardware de desarrollo (escritorio)

Procesador Intel Core i7 2600K

Arquitectura 64 bits

Número de núcleos 4

Velocidad de reloj 3800 Mhz

Page 79: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

79

Memoria RAM 2x4Gb DDR3

GPU NVidia GeForce GTX 750

Memoria gráfica 1Gb GDDR5

Hardware de desarrollo (móvil)

Terminal Samsung Galaxy S4 I9505

Procesador Krait 300

Número de núcleos 4

Velocidad de reloj 1900 Mhz

Memoria RAM 2Gb

GPU Qualcomm Adreno 320

Notas generales

- Todos los fuentes y proyectos se crearán en carpetas cuyas rutas no tengan espacios

ni caracteres especiales.

- Al usar CMake, siempre estableceremos el directorio de instalación a una carpeta para

la que no se necesiten permisos de administración para escribir en ella. Por defecto

está en C:/Program Files/<nombre_paquete>”, y por tanto la cambiaremos.

- Todos los archivos con los que trabajaremos para nuestro proyecto partirán de la ruta

d:/tfg que, para generalizar las explicaciones denominaremos <ruta_tfg>.

- Al compilar las dependencies con Visual Studio lo haremos para x64-Release y x64-

Debug, para poder usarlas en nuestro proyecto de manera indistinta.

Notas para Linux

Necesitaremos tener el entorno preparado para desarrollo en C++. Además, el proyecto

utiliza aceleración gráfica por hardware. Para ello es necesario instalar algunos paquetes con

las ordenes:

sudo apt-get install build-essential sudo apt-get install libfontconfig1 sudo apt-get install mesa-common-dev sudo apt-get install libglu1-mesa-dev -y

Para asegurarnos de que tenemos OpenGL configurado correctamente, escribimos:

Page 80: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

80

glxinfo | grep "OpenGL version"

Esto debería mostrar una versión de OpenGL 3.0 o superior. Si no fuera así, puede que

tengamos un hardware insuficiente, o bien si estamos en una máquina virtual, puede que no

hayamos activado la aceleración de gráficos por hardware.

Un problema que puede ocurrir si usamos VirtualBox es que los drivers de GPU que instala

con el paquete de “Guest Additions” no funcionan correctamente en Ubuntu, y es mejor usar

los que trae la instalación del SO por defecto.

7.1 Preparar OpenSceneGraph Descargamos los fuentes de OpenSceneGraph desde la release de github correspondiente a

la versión que vamos a usar (3.6.2):

https://github.com/openscenegraph/OpenSceneGraph/releases/tag/OpenSceneGraph-3.6.2

Descargamos también la versión simplificada del paquete de dependencias:

https://download.osgvisual.org/3rdParty_VS2017_v141_x64_V11_small.7z

Ahora descomprimimos el contenido de estos paquetes en la siguiente estructura de

carpetas:

<ruta_tfg>/deps/osg-src

3rdParty Dependencias de OpenSceneGraph

OpenSceneGraph Fuentes de OpenSceneGraph

7.1.1 Compilar para Windows Abrimos CMake GUI y en el campo “Where is the source code” escribimos

<ruta_tfg>/deps/osg-src/OpenSceneGraph.

En el campo “Where to build de binaries” ponemos <ruta_tfg>/deps/osg-src/build (crear

la carpeta si es necesario).

Page 81: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

81

Pulsamos “Configure” y en la ventana que aparecerá, seleccioamos el generador Visual

Studio 15 2017 Win64, marcamos “Use default native compileres” y pulsamos “Finish”.

Nos aseguramos de que los siguientes parámetros están así:

ACTUAL_3RDPARTY_DIR <ruta_tfg>/deps/osg-src/3rdparty

CMAKE_INSTALL_PREFIX <ruta_tfg>/deps/osg-sdk

Dejamos el resto de opciones como están y pulsamos “Configure”. Si no ha habido

problemas se pondrán todas en blanco. Pulsamos “Generate” para crear el proyecto de Visual

Studio en la carpeta <ruta_tfg>/deps/osg-src/build.

Hecho esto abrimos con Visual Studio la solución generada (archivo OpenSceneGraph.sln)

y, seleccionando el target x64 (Debug) compilamos el proyecto “BUILD_ALL”. Tardará

varios minutos.

Si no ha habido ningún problema, compilamos “INSTALL” para mover las librerias,

binarios y cabeceras a la carpeta de instalación que definimos en CMake

(<ruta_tfg>/deps/osg-sdk).

Ahora definimos/modificamos las siguientes variables de entorno de Windows:

OSG_ROOT <ruta_tfg>/deps/osg-sdk

PATH Añadir: %OSG_ROOT%/bin y %OSG_ROOT%/3rdParty/bin

La librería OpenSceneGraph ya está lista para usarse en nuestro proyecto Windows.

7.1.2 Compilar para Linux Seguimos los mismos pasos realizados para Windows con CMake-GUI pero con el target

Unix Makefiles. Para Linux sí que usaremos el directorio de instalación establecido por defecto

(/usr/lib/)

Por otro lado, al contrario de la versión Windows, para Linux no existen binarios

compilados de las dependencias, de manera que, si queremos aprovechar todas las

características que nos ofrece OSG, tendremos que instalar previamente estas dependencias

Page 82: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

82

antes de configurar la compilación con CMake. En cualquier caso, afortunadamente, para el

uso que por el momento daremos de la librería, no necesitaremos más dependencias de las que

nos ofrece el paquete build-essentials de Linux (Lo instalaremos si no lo tenemos).

Cuando tengamos el proyecto generado, lo compilamos desde el terminal accediendo al

directorio <ruta_tfg>/deps/osg-src/build y escribiendo la orden:

make -j 4 make install

Por último, añadiremos las mismas variables de entorno que hemos establecido en windows

con las ordenes:

export OSG_ROOT=<ruta_tfg>/deps/osg-sdk export PATH=$PATH:$OSG_ROOT/bin

7.1.3 Compilar para Android Gracias a CMake, la compilación del SDK es fácil de realizar en ambas plataformas. No

obstante, para la versión de Android se han seguido unos pasos especiales.

En primer lugar, no compilaremos en Windows, sino que, excepcionalmente, usaremos

Ubuntu para compilar los fuentes dado que es más sencillo configurar el entorno que

necesitamos para tal fin.

Se ha utilizado Ubuntu 16.04, con CMake 3.5.1. Los pasos a seguir han sido los siguientes:

En primer lugar, si no tenemos el Android NDK, lo descargamos (debe ser la versión r15c),

lo descomprimimos en la carpeta ~/ndk y lo añadimos al PATH con la orden:

export PATH=~/ndk:$PATH export ANDROID_NDK=~/ndk

Descargamos y descomprimimos los fuentes de nuestra versión de OSG en ~/osg-src y

creamos una carpeta paralela a ésta para el destino de la instalación a la que llamamos ~/osg-

sdk.

Ahora entramos en osg-src y escribimos:

mkdir build_android_static_gles2 && cd build_android_static_gles2 cmake .. -DANDROID_NDK=~/ndk \

Page 83: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

83

-DCMAKE_TOOLCHAIN_FILE=../PlatformSpecifics/Android/android.toolchain.cmake \ -DOPENGL_PROFILE="GLES2" -DDYNAMIC_OPENTHREADS=OFF -DDYNAMIC_OPENSCENEGRAPH=OFF \ -DANDROID_NATIVE_API_LEVEL=15 \ -DANDROID_ABI=armeabi-v7a \ -DCMAKE_INSTALL_PREFIX=<ruta-al-directorio-de-instalación> make -j 8 make install

La <ruta-al-directorio-de-instalación> deberemos indicarla de forma absoluta (y tampoco

sirve usar el carácter ~, ya que nos dará un error al instalar). Si no ha habido problemas,

tendremos el sdk listo para usar en el directorio de instalación. Ahora nos lo podemos llevar

para usarlo desde Windows con Android Studio.

7.2 Preparar Bullet Physics Descargamos Bullet 2.87 desde la página de releases del repositorio de Github:

https://github.com/bulletphysics/bullet3/releases

Descomprimimos los fuentes en <ruta_tfg>/deps/bullet-src y creamos una carpeta

<ruta_tfg>/deps/bullet-src/build

7.2.1 Compilar para Windows Esta versión de Bullet viene con los archivos CMake para poder preparar el proyecto para

Visual Studio en Windows.

Abrimos CMake-GUI y establecemos los directorios de fuente y build que hemos creado.

Pulsamos configurar, y seleccionamos Visual Studio 15 2017 Win64.

Cambiaremos varias opciones en la configuración. Lo que haremos es evitar que se

compilen las demos y funcionalidades extra de la librería. Además, como no vamos a necesitar

el entorno gráfico de depuración, evitamos también que Bullet dependa de GLUT y OpenGL.

Por último, activando MSVC_RUNTIME_LIBRARY_DLL, hacemos que la librería utilice

el runtime dinámico, ya que de lo contrario no podría combinarse con OpenSceneGraph que

también está configurado así.

Page 84: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

84

Las opciones que cambian son:

BUILD_BULLET2_DEMOS OFF

BUILD_BULLET3 OFF

BUILD_CPU_DEMOS OFF

BUILD_EXTRAS OFF

BUILD_OPENGL3_DEMOS OFF

CMAKE_INSTALL_PREFIX <ruta_tfg>/deps/bullet-sdk

INSTALL_LIBS ON

USE_GLUT OFF

USE_GRAPHICAL_BENCHMARK OFF

USE_MSVC_RUNTIME_LIBRARY_DLL ON

Cuando pulsemos otra vez “Configure” nos aparecerán nuevas opciones en rojo para

completar. Lo haremos así:

INCLUDE INSTALL_DIR <ruta_tfg>/deps/bullet-sdk/include

LIB_DESTINATION <ruta_tfg>/deps/bullet-sdk/lib

PKGCONFIG_INSTALL_PREFIX <ruta_tfg>/deps/bullet-sdk/lib/pkgconfig

Configuramos y generamos y ya tendremos el proyecto de Visual Studio en

<ruta_tfg>/deps/bullet-src/build

Tras compilar BUILD_ALL e INSTALL con Visual Studio (en Release y Debug), como

último paso, tendremos que editar el archivo:

<ruta_tfg>/deps/bulletsdk/lib/cmake/bullet/BulletConfig.cmake

y cambiar la siguiente línea:

set ( BULLET_LIBRARIES "LinearMath;Bullet3Common;BulletInverseDynamics;\ BulletCollision;BulletDynamics;BulletSoftBody" )

Por:

if(CMAKE_BUILD_TYPE STREQUAL "Debug")

Page 85: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

85

set ( BULLET_LIBRARIES "LinearMath_Debug;Bullet3Common_Debug;BulletInverseDynamics_Debug;\ BulletCollision_Debug;BulletDynamics_Debug;BulletSoftBody_Debug" )

else() set ( BULLET_LIBRARIES

"LinearMath;Bullet3Common;BulletInverseDynamics;\ BulletCollision;BulletDynamics;BulletSoftBody" )

endif()

Hecho esto podremos usar indistintamente una compilación Debug o Release en nuestro

proyecto. Con esto, Bullet ya estaría listo para usar.

7.2.2 Compilar para Linux Seguimos los mismos pasos realizados para Windows con CMake-GUI pero con el target

Unix Makefiles. Al igual que con OSG, en Linux dejaremos la ruta de instalación por defecto

(/usr/lib/)

Cuando tengamos el proyecto generado, lo compilamos e instalamos desde el terminal

accediendo al directorio <ruta_tfg>/deps/bullet-src/build y escribiendo las ordenes:

make -j 4 make install

Esto nos dejará la librería lista para usar en el sistema.

7.2.3 Compilar para Android Para Android, volveremos de nuevo a Ubuntu asegurándonos de que tenemos instalado

Android NDK r15c con las variables de entorno definidas:

export PATH=~/ndk:$PATH export ANDROID_NDK=~/ndk

Descargamos y descomprimimos Bullet en cualquier carpeta y, desde la línea de comandos,

entramos en <ruta_bullet>/build3/Android/jni y ejecutamos:

ndk-build

Esto compilará la librería estática libbullet.a que podremos encontrar en:

<ruta_bullet>/build3/Android/obj/local/armeabi-v7a

Al igual que con OSG, nos la llevamos a Windows para usarla en el proyecto Android

Studio que crearemos más adelante.

Page 86: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

86

7.3 Preparar Lua Utilizaremos la versión 5.3.5 de Lua junto con el binding para C++ Sol2 versión 2.20.

Podemos descargar Lua desde:

https://www.lua.org/ftp/lua-5.3.5.tar.gz

Descomprimimos los fuentes en <ruta_tfg>/deps/lua-src

Por otro lado, Sol2 es una librería sólo de cabeceras y por tanto no requiere compilarse.

Podemos descargar los archivos sol.hpp y sol_forward.hpp listos para usar desde:

https://github.com/ThePhD/sol2/releases

7.3.1 Compilar para Windows En la versión Windows de los fuentes, esta vez no hay un CMakeLists.txt que nos facilite

el trabajo pero, por suerte, la librería Lua es auto-contenida y muy sencilla de compilar, así

que creamos un proyecto vacío de C++ con Visual Studio para este cometido.

Lo primero que hacemos es agregar todos los archivos de fuentes de Lua al proyecto (todos

excepto lua.c y luac.c). Ahora, vamos a Propiedades > General > Tipo de configuración, e

indicamos “Biblioteca estática (lib)”.

Por último, compilamos el proyecto con la misma arquitectura usada para las otras

librerías (x64 debug). Podremos encontrar el fichero lua.lib en la carpeta x64/Debug del

proyecto.

Por comodidad, movemos este fichero junto con los archivos .h y hpp a una carpeta

dedicada a esta librería. En nuestro caso, <ruta_tfg>/deps/lua-sdk

Opcionalmente, para que al usar CMake sea más sencillo encontrar la librería, podemos

crear el archivo LuaConfig.cmake en <ruta_tfg>/deps/lua-sdk/lib/Lua/cmake con el

siguiente código:

set ( LUA_FOUND 1 ) set ( LUA_USE_FILE "<ruta_tfg>/deps/lua-sdk/lib/Lua/cmake/UseLua.cmake" ) set ( LUA_DEFINITIONS "" )

Page 87: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

87

set ( LUA_INCLUDE_DIR "<ruta_tfg>/deps/lua-sdk/include" ) set ( LUA_INCLUDE_DIRS "<ruta_tfg>/deps/lua-sdk/include/lua; <ruta_tfg>/deps/lua-sdk/include/sol" ) if(CMAKE_BUILD_TYPE STREQUAL "Debug") set ( LUA_LIBRARIES "lua-debug" ) else() set ( LUA_LIBRARIES "lua" ) endif() set ( LUA_LIBRARY_DIRS "<ruta_tfg>/deps/lua-sdk/lib" ) set ( LUA_ROOT_DIR "<ruta_tfg>/deps/lua-sdk" ) set ( LUA_VERSION_STRING "2.87" )

Y otro para usar la librería en la misma carpeta con el nombre UseLua.cmake y el código:

add_definitions ( ${LUA_DEFINITIONS} ) include_directories ( ${LUA_INCLUDE_DIRS} ) link_directories ( ${LUA_LIBRARY_DIRS} )

7.3.2 Compilar para Linux La compilación en Linux se hace de forma directa con la orden make gracias al fichero

Makefile. Concretamente, ejecutaremos:

cd <ruta_fuentes_lua> make generic make install

7.3.3 Compilar para Android El desarrollador de Lua no nos proporciona un Android.mk ni un archivo CMake para

facilitarnos el trabajo, pero podemos compilar Lua fácilmente de manera similar a como lo

hemos hecho para Windows.

En este caso podemos añadir directamente los fuentes a un proyecto de Android Studio

con soporte para C++ copiando los ficheros a la carpeta src/main/cpp/lua y declarándolos

en el CMakeLists.txt que genera el IDE de esta forma:

add_library(lua STATIC src/main/cpp/lua/lapi.c src/main/cpp/lua/lapi.h src/main/cpp/lua/lauxlib.c src/main/cpp/lua/lauxlib.h # ... (resto de fuentes de lua)

Luego lo enlazamos con la librería del proyecto:

target_link_libraries(native-lib lua # ... (resto de librerías del proyecto)

Page 88: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

88

Ahora generamos el proyecto con Build > Make Project y se generará un archivo liblua.a

en <carpeta_del_proyecto>/app/.externalNativeBuild/cmake/debug/armeabi-v7a, o en la

carpeta correspondiente a la arquitectura configurada en el fichero gradle (ver más detalles en

la sección de Proyecto de inicio).

7.4 Preparar el framework Qt Utilizaremos Qt versión 5.11.1 con licencia LGPL que podemos descargar desde la web

oficial:

https://www.qt.io/download-qt-installer

Lo que obtenemos según la plataforma donde nos encontremos, es un instalador que nos

facilitará el trabajo para configurar Qt. En este caso se ofrecen directamente los binarios y,

por tanto, no es necesario compilar nada. Durante el proceso de instalación se nos preguntará

qué componentes queremos instalar. Marcaremos los siguientes:

Qt 5.11.1

- MSVS 2017 64 bits

- + Todos los componentes que empiezan por Qt *

Tools

- Qt Creator

Tras finalizar la instalación, si todo ha ido bien, tendremos Qt listo para usar en la carpeta

de destino que hayamos escogido.

7.5 Preparar Asio Esta librería puede usarse en forma de archivos de cabecera (.h) por lo que no requiere

compilación. Además, funciona directamente sin problemas en las plataformas para las que

está escrito el motor (Windows, Linux y Android).

Sin embargo, curiosamente el enlace que se proporciona en el apartado de descargas de

esta la web oficial (https://think-async.com/Asio/) apunta a una versión que se encuentra

desactualizada y que depende de Boost (lo cual queríamos evitar).

Page 89: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

89

Para adquirir la última versión lo haremos directamente desde el repositorio Git:

https://github.com/chriskohlhoff/asio/ En nuestro caso utilizaremos la versión 1.12.2.

7.6 Proyecto del reproductor para Windows/Linux En este apartado se explicará cómo configurar nuestro proyecto para trabajar con las

dependencias que hemos preparado en los apartados anteriores. En primer lugar, haremos el

proyecto para Windows, pero, aunque vamos a usar Visual Studio, crearemos un proyecto

CMake para facilitar la portabilidad.

Una vez creado, abrimos el archivo CMakeLists.txt del proyecto (no el de la solución) y

añadimos las dependencias. Definiremos las rutas a las librerías en las siguientes variables.

Son éstas las que esperan los módulos de find_package:

set(OSG_ROOT <ruta_tfg>/deps/osg-sdk) set(BULLET_ROOT <ruta_tfg>/deps/bullet-sdk) set(LUA_ROOT <ruta_tfg>/deps/lua-sdk)

Ahora buscamos las librerías:

find_package(OpenSceneGraph REQUIRED COMPONENTS osgDB osgGA osgUtil osgViewer) find_package(Bullet REQUIRED) find_package(Lua CONFIG REQUIRED HINTS ${LUA_ROOT})

Y añadimos los directorios de las cabeceras y librerías:

include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS} ${BULLET_INCLUDE_DIR}

${LUA_INCLUDE_DIR}) link_directories(${OPENSCENEGRAPH_LIBRARY_DIRS} ${BULLET_INCLUDE_DIR}

${LUA_LIBRARY_DIRS})

Declaramos el ejecutable:

add_executable (player Main.cpp)

Y enlazamos las librerías:

target_link_libraries(player ${OPENSCENEGRAPH_LIBRARIES} ${BULLET_LIBRARIES}

Page 90: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

90

${LUA_LIBRARIES})

Este archivo CMake debería funcionar para compilar la versión de Windows con Visual

Studio y para la versión Linux en Ubuntu con CMake y make.

7.7 Proyecto del reproductor para Android Para el proyecto Android nos basamos en el app de ejemplo del SDK de OpenSceneGraph:

https://github.com/openscenegraph/OpenSceneGraph/tree/master/examples/osgAndroidEx

ampleGLES2

Lo usaremos con Android Studio utilizando la opción de importar proyecto.

El archivo CMakeLists.txt para Android estará basado en el que nos proporcina Android

Studio en el ejemplo de NativeActivity.

Al añadir las dependencias, en el entorno de desarrollo de Android no nos funcionarán

módulos find_package, con lo que tendremos que indicar las rutas manualmente para los

archivos de cabecera y las librerías de la siguiente forma:

include_directories(<ruta_tfg>/deps/osg-sdk-android/include <ruta_tfg>/deps/bullet-sdk-android/include <ruta_tfg>/deps/bullet-sdk-android/include/bullet <ruta_tfg>/deps/lua-sdk-android/include/lua <ruta_tfg>/deps/lua-sdk-android/include/sol) link_directories(<ruta_tfg>/deps/osg-sdk-android/lib <ruta_tfg>/deps/osg-sdk-android/lib/osgPlugins-3.7.0 <ruta_tfg>/deps/bullet-sdk-android/lib <ruta_tfg>/deps/lua-sdk-android/lib)

Luego, para enlazar con la librería nativa de Android, lo haremos indicando las librerías

necesarias una a una:

target_link_libraries(osgNativeLib EGL GLESv2osgViewer osgGA osgDB osgUtil osg OpenThreads z

Bullet lua)

7.8 Proyecto de la consola para Windows/Linux La consola es una aplicación de línea de comandos que nos permitirá enviar código en línea

y archivos desde el PC de desarrollo al reproductor que tengamos conectado en nuestra red.

Page 91: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

91

Esta vez el archivo CMakeLists.txt es muy sencillo. A continuación mostramos lo

fundamental:

set(PLAYER_ROOT d:/tfg/player/player) set(ASIO_ROOT d:/tfg/deps/asio) set(DIRENT_ROOT d:/tfg/deps/dirent) include_directories(${ASIO_ROOT}/include ${PLAYER_ROOT}) include_directories(${DIRENT_ROOT}/include ${DIRENT_ROOT}) file(GLOB ion_common ${PLAYER_ROOT}/common/fileUtil.h ${PLAYER_ROOT}/common/fileUtil.cpp) add_executable (console "console.cpp" ${ion_common})

Como vemos, vamos a reutilizar código presente en el player. En este caso simplemente

necesitamos las utilidades de acceso a fichero de common/fileUtil. Además, como dependencias

externas, necesitaremos Asio (para acceso a red) tanto en Windows como Linux.

En el caso de Windows utilizaremos una implementación no oficial de dirent para Windows

que necesitamos para explorar los archivos del directorio de trabajo utilizando el mismo API

de dirent para Linux. Este port ha sido creado por Tony Rönkkö, y se encuentra disponible

en el siguiente repositorio de GitHub: https://github.com/tronkko/dirent

7.9 Proyecto para el editor gráfico El interfaz del editor lo desarrollaremos usando Qt Creator. En nuestro caso crearemos un

nuevo proyecto “Qt Widgets Application” utilizando el kit de desarrollo de Visual Studio

(MSVC2017).

Una vez creado, para añadir las librerías, hacemos click derecho sobre el proyecto y

pulsamos “Add library…”. Añadiremos por ejemplo la de OSG indicando sus rutas y, al

hacerlo, se creará un archivo de configuración del proyecto (.pro) que podremos editar.

Modificaremos las siguientes líneas para añadir los componentes restantes de OSG:

win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../deps/osg-sdk/lib/ -losgDB -losgGA -losgUtil -losgViewer -losg else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../deps/osg-sdk/lib/ -losgDBd -losgGAd -losgUtild -losgViewerd -losgd INCLUDEPATH += $$PWD/../../deps/osg-sdk/include DEPENDPATH += $$PWD/../../deps/osg-sdk/include

Page 92: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

92

IMPORTANTE: Siempre que modifiquemos la configuración del proyecto tendremos

que llamar a qmake desde Build -> Run qmake para que se apliquen los cambios.

A continuación, añadimos de la misma forma las dependencias para Bullet y Lua, y ya

tenemos el proyecto listo para empezar.

7.9.1 Integración de Qt con OpenSceneGraph (osgQt) Para poder mostrar una vista de OpenSceneGraph en una ventana Qt, en primer lugar

tendremos que crear un widget personalizado que heredará del QOpenGLWidget de Qt.

Para un funcionamiento básico, en el constructor inicializaremos los objetos osgView y

osgGraphicsWindowEmbedded de OSG, y sobreescribiremos el método virtual paintGL para

que actualice la vista tal como se muestra en el siguiente código:

class QOSGWidget : public QOpenGLWidget { public: QOSGWidget(QWidget* parent = 0) : QOpenGLWidget(parent) { graphicsWindow = new osgViewer::GraphicsWindowEmbedded( 0,0,this->width(),this->height()); viewer = new osgViewer::Viewer(); osg::Camera* camera = viewer->getCamera(); camera->setGraphicsContext( graphicsWindow ); // Escribimos a continuación el código de inicialización de la escena OSG // tal como lo hacemos para el player (añadimos objetos, luces, etc.) // ... } protected: virtual void paintGL() { viewer->frame(); } virtual bool event(QEvent* event){ bool handled = QOpenGLWidget::event(event); this->update(); return handled; } private: osg::ref_ptr<osgViewer::GraphicsWindowEmbedded> graphicsWindow; osg::ref_ptr<osgViewer::Viewer> viewer; osg::ref_ptr<osg::Group> root; };

Ahora, desde el método main del programa, inicializamos la ventana y le añadimos este

widget para que muestre los gráficos OSG:

int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow window; QOSGWidget* widget = new QOSGWidget();

Page 93: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

93

window.setCentralWidget(widget); window.show(); return a.exec(); }

Podemos encontrar más información sobre este método de integración de OSG con Qt en

el código de ejemplo que proporciona Victoria Rudakova en Github:

https://github.com/vicrucann/QtOSG-hello/blob/master/main.cpp

Page 94: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM
Page 95: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

95

8 DISEÑO DEL CÓDIGO

En este apartado se van a exponer los distintos diagramas UML de clases tal como

finalmente quedaron tras adoptar todas las medidas necesarias para que todo encajara. No se

trata del diseño inicial del modelo de dominio.

Los diagramas están ordenados alfabéticamente por sistemas y contienen una breve

descripción con sus características principales. Para más detalles acerca del porqué de algunas

decisiones de diseño, y de cambios sobre el modelo original, nos remitiremos al siguiente

capítulo (Implementación) donde se hace un análisis más exhaustivo sobre las cuestiones más

relevantes.

8.1 Sistema de administración de assets

Figura 8.1. Diagrama de clases. Sistema de administración de assets

Page 96: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

96

El sistema de administración de assets se encarga de gestionar los objetos obtenidos a

partir de ficheros de disco. Sus características son:

Permite mapear los recursos por nombre de fichero

Evita cargar varias veces ficheros a los que ya se ha accedido (a menos que

explícitamente se solicite recargar).

Permite tener múltiples tipos de cargadores para un mismo tipo de asset (por ejemplo,

para texturas, podríamos tener cargadores png, jpg, gif…)

Detecta y elimina del caché recursos que ya no se usan en la escena

Devuelve el tipo de objeto adecuado de forma segura

8.2 Sistema de configuración

Figura 8.2. Diagrama de clases. Sistema de configuración

El sistema de configuración consta de una única clase. Al instanciarla pasamos el nombre

del archivo de configuración al constructor. Este archivo no es más que un fichero de texto

que contiene los pares atributo-valor que conforman la configuración que utilizaremos. No se

trata de una clase de instancia única (Singleton). De hecho, puede utilizarse para varios

propósitos. Por ejemplo, además de la configuración del motor, puede emplearse para el

archivo time.txt del sistema de sincronización de reloj, y también puede ser usado por el

programador usuario del motor para guardar información, opciones o configuración del juego

que está realizando. Las características son:

Permite autoguardado en disco de las opciones al ser establecidas o bien hacer un

guardado manual bajo demanda.

Hace la conversión automáticamente del tipo de atributo

Permite indicar valores por defecto en caso de que el atributo no exista

Page 97: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

97

8.3 Sistema de consola cliente / servidor

Figura 8.3. Diagrama de clases. Sistema de consola cliente / servidor

El cliente/servidor de consola permite controlar el reproductor del motor desde un PC

remoto. Las características son:

Permite ejecutar scripts sobre el dispositivo de pruebas desde la máquina de desarrollo.

Permite enviar/recibir archivos de assets.

Con una sola orden puede comprobar cambios de forma recursiva en el directorio de

trabajo y enviar al dispositivo de prueba todos los ficheros con fecha de modificación

actualizada.

Permite escribir y ejecutar en tiempo real, lo que posibilita ver cambios de código en

vivo mientras el juego está ejecutándose.

Page 98: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

98

8.4 Entidad-Componente-Sistema

Figura 8.4. Diagrama de clases. Entidad-Componente-Sistema

El patrón arquitectural Entidad-Componente-Sistema ha sido el elegido para dar forma a

nuestro motor. Para más información acerca de este patrón de diseño, las características y

ventajas que proporciona y en qué consiste cada uno de los componentes que lo conforman,

ver el capítulo de “Definición de la arquitectura”. Además, en el apartado “Implementación”

podemos ver algunos cambios que se han realizado sobre el patrón original y su justificación.

Page 99: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

99

8.5 Sistema de gráficos

Figura 8.5. Diagrama de clases. Sistema de gráficos

El sistema de gráficos es a quien se delega el control sobre la librería OSG, de ahí que en

muchas de las clases que vemos en este diagrama aparezcan tipos de esta librería. Este sistema

forma parte del ECS y se encarga de objetos de tipo dibujable, cámaras y luces, es decir, todo

lo que tiene que ver con el control de los gráficos.

Page 100: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

100

8.6 Sistema de entrada

Figura 8.6. Diagrama de clases. Sistema de entrada

Este sistema se encarga de recibir la entrada de usuario. En nuestro caso, teclado y ratón

en caso de la versión de escritorio o acelerómetro y pantalla táctil en la versión de móvil. Para

la pantalla táctil, el control se deriva al listener del ratón.

También dispone de un emulador de acelerómetro que se utiliza en la versión PC para

hacer pruebas utilizando el teclado. El eje X se controla con [I,K], el Y con [Y,H] y el Z con

[J,L].

Excepto el control de acelerómetro, los demás eventos utilizan internamente la cola de

eventos de OSG. De hecho, la clase InputSystem en realidad hereda de osg::GUIEventHandler.

Por último, nótese que este sistema es independiente del conjunto ECS. No obstante, es

utilizado por GraphicsSystem ya que es de quien se obtiene la instancia de la ventana OSG

que nos da los eventos.

Page 101: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

101

8.7 Sistema de log

Figura 8.7. Diagrama de clases. Sistema de log

El log se utiliza para enviar por consola o a un fichero mensajes de error, avisos, mensajes

de depuración, o cualquier información de interés. Para ello se emplea un sistema de mensajes.

Las principales características son:

Imita el interfaz de stream de C++ (como cout)

Identifica fichero y número de línea de donde se ha hecho el log

Puede derivar la llamada a múltiples salidas: Consola, fichero, ventana del IDE, etc.

Los logs de depuración se pueden eliminar en la compilación release

Page 102: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

102

8.8 Sistema de física

Figura 8.8. Diagrama de clases. Sistema de física

El sistema de física utiliza internamente la librería Bullet y proporciona un interfaz

simplificado que facilita al programador la creación de objetos con propiedades de física como

masa, gravedad, velocidad lineal o angular e impulso. Además, proporciona un interfaz

CollisionListener que se puede utilizar para capturar una colisión entre dos entidades cuando

ésta se produce. El método proporciona además los puntos de contacto de las entidades en

colisión.

La configuración del objeto es también muy sencilla con solo 4 flags combinables:

DYNAMIC: Crea un objeto con comportamiento de movimiento dinámico clásico.

GHOST: El objeto responde a colisiones (lanzando el evento) pero ningún objeto del

entorno de física puede moverlo. Si alguno lo toca, simplemente lo atravesará (de ahí

el nombre “fantasma”).

TRIGGER: Para poder obtener colisiones con un listener, debemos activar este flag.

KINEMATIC: Permite que el objeto se mueva manualmente acorde con la matriz de

transformación procedente de TransformComponent.

Page 103: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

103

8.9 Sistema de scripting

Figura 8.9. Diagrama de clases. Sistema de scripting

El sistema de scripting gestiona los scripts del programador de juegos y se apoya en lua a

través de la librería de binding Sol2 (de ahí los objetos sol* que aparecen en el diagrama).

Como vemos, la clase sistema tiene un objeto InputSystem que utiliza para poder notificar

a las distintas instancias del sctipt de los eventos de entrada de usuario. El ScriptComponent

es un contenedor de clases e instancias de objetos Lua. La instancia es realmente sobre la que

se trabaja en cada iteración llamando a los eventos correspondientes (update, init o cleanup).

También vemos que es posible obtener el valor “self” que sería como el objeto “this” en C++

para instancias de clase. Y por último, es posible acceder a miembros de instancia del objeto

Lua con setValue, getValue o getFunction.

Page 104: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

104

8.10 Sistema de transformación

Figura 8.10. Diagrama de clases. Sistema de transformación

El sistema de transformación se utiliza para todos los objetos que ocupen un espacio en la

escena. Es otro de los sistemas del ECS y básicamente es el nexo de comunicación entre el

sistema de física y el de gráficos. Su función es muy sencilla: Simplemente guarda una matriz

de transformación que podemos modificar o consultar utilizando los métodos que se

proporcionan para posición, escala y rotación.

Page 105: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

105

9 IMPLEMENTACIÓN

Una vez tenemos la arquitectura y el diseño preliminar definidos, y ya hemos compilado

todas las dependencias y preparado nuestro entorno de desarrollo, nos disponemos a escribir

el código del motor. Todo lo que habíamos planteado de manera abstracta a nivel teórico va

a ponerse a prueba en este momento y, en muchos casos, no encajará en la práctica como nos

habíamos imaginado.

En este apartado se detallarán las cuestiones más importantes en cuanto a decisiones y

cambios en el diseño que se han tenido que adoptar durante la fase de desarrollo, explicando

por qué se han realizado de determinada forma y no de otra, de modo que pueda resultar útil

a cualquier desarrollador que se plantee un reto similar.

9.1 Sistema de log Para empezar con buen pie debemos disponer desde el principio de herramientas que nos

hagan la vida más fácil durante el resto del desarrollo. Tener un buen sistema para hacer

trazas y depurar es vital. Por este motivo lo primero que se implementó fue un completo

sistema de log que además nos resolviera algunos problemas encontrados.

9.1.1 Necesidad de derivar la salida de log a múltiples destinos Al ser un proyecto multiplataforma que se desarrolla en distintos IDE (Android Studio,

Visual C++ y Qt Editor), un problema que surge es que la ventana de depuración no muestra

lo que se escribe por la salida estándar, con lo cual ya no podemos usar los clásicos printf o

cout para mostrar información de depuración, sino que tenemos que ceñirnos al API propio

del sistema en cuestión. Por ejemplo, en Visual Studio, para poder ver algo en la ventana de

Page 106: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

106

salida deberíamos usar OutputDebugString del SDK de Windows, mientras que en Android

emplearíamos la función __android_log_print del SDK nativo (NDK de Android).

Esto se ha resuelto utilizando una clase Log que implementa un sistema de notificaciones

al cual podemos suscribir los listeners que necesitemos. Por ejemplo, en la versión Windows

añadiríamos LogVS (para ver el log por la ventana inmediato de Visual Studio), LogConsole

(para mostrarlo también en el terminal, lo cual es útil para la consola de script), e incluso

LogFile, que deriva todo el log a un archivo de texto. En el capítulo de “Diseño del código”

podemos ver con más detalle el diseño final del sistema de log.

Por supuesto, se podría haber resuelto de otras formas más sencillas, como por ejemplo

mediante una función global que, con directivas del preprocesador (#iddef) seleccione el

método adecuado en función de la máquina objetivo para la cual se compila, pero, si lo

hiciéramos así, el código quedaría menos claro con todo en una misma función y, por otro

lado, el usuario no podría activar/desactivar dinámicamente el log a fichero. Además, como

veremos a continuación, este sistema de log resuelve otras cuestiones que, de hacerlo así, irían

complicando esta función global.

9.1.2 Log como stream con información de fichero y línea Al utilizar una función propia que hace el log debemos encontrar una solución para poder

convertir fácilmente una variable a texto y así poder imprimirla.

Para ello se imita el sistema de streams de C++ de manera que con el operador <<

podemos concatenar cualquier tipo básico o cualquier clase que lo tenga sobrecargado.

La solución no está exenta de cierto ingenio. La clase Logger implementa un constructor

al cual se le pasa el nivel de mensaje y el fichero fuente y el número de línea (usando las

macros estándar __FILE__ y __LINE__ respectivamente). Luego, el método Log recibe el

“tag” o etiqueta del mensaje (útil para filtrar el log en un archivo de texto) y devuelve un

objeto de tipo ostream al que ya podemos añadir datos con <<.

El funcionamiento interno para loguear un mensaje sería por ejemplo así:

Page 107: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

107

Logger(Message::MSGWARNING, __FILE__, __LINE__).Log("prueba") << "Variable de prueba: " << miVariable;

Lo que ocurre en esta línea es lo siguiente:

Se instancia un objeto Logger con los parámetros indicados (tipo, fichero y línea)

Se llama a Log con el tag “prueba” y éste devuelve un ostream

El ostream se usa para concatenar la información que se pasa

Al no haberse asociado a ninguna variable, la instancia Logger se destruye al terminar

de ejecutar la línea

En el destructor se usa el ostream (que está como miembro privado de la clase) donde

tenemos los datos y se notifican éstos a todos los lísteners contenidos en el notificador

(estático) de la clase Logger antes de destruir la instancia.

Finalmente, para poder manejar esto con facilidad, se crean unas macros de este estilo:

#define TLOGE(x) \ ion::log::Logger(ion::log::Message::MSGERROR,__FILE__,__LINE__).Log(x) #define TLOGW(x) \ ion::log::Logger(ion::log::Message::MSGWARNING,__FILE__,__LINE__).Log(x) #define TLOGI(x)\ ion::log::Logger(ion::log::Message::MSGINFO,__FILE__,__LINE__).Log(x)

Ahora, para hacer lo mismo que antes, simplemente escribiríamos:

TLOGW("prueba") << "Variable de prueba: " << miVariable;

El tag es opcional. Podemos también hacer un log sin etiqueta usando las mismas macros

pero sin el prefijo T:

LOGW << "Variable de prueba: " << miVariable;

9.1.3 Desactivación de logs de depuración en modo release Otro problema clásico de introducir líneas de depuración en nuestro código es que podemos

dejarnos olvidadas algunas en la versión release. Esto podría revelar datos no deseados al

usuario final, además de que, según donde aparezcan, podría decrementar el rendimiento.

Para solucionarlo, diferenciamos los logs de error, warning, o info (con LOG) de los que

son sólo para depuración (con LOGD). Esta función LOGD está definida en una macro que

Page 108: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

108

desaparece sin rastro deshabilitando el flag de compilación ION_LOG_LEVEL_DEBUG en

modo release. Los logs de debug no se añadirán a la compilación.

A priori, esto no están sencillo porque si tenemos en cuenta lo explicado en el apartado

anterior, si decidimos hacer algo como esto:

#ifdef ION_LOG_LEVEL_DEBUG #define LOGD(x) \ ion::log::Logger(ion::log::Message::MSGDEBUG,__FILE__, __LINE__).Log(x) #else #define TLOGD(x) #endif

Al desactivar ION_LOG_LEVEL_DEBUG tendríamos inmediatamente errores de

compilación en los logs, ya que por ejemplo este código:

LOGD << "Variable de prueba: " << miVariable;

Se convertiría en esto:

<< "Variable de prueba: " << miVariable;

La solución ha sido añadir un código sustituto que no moleste al compilar cuando no se

usa el debug como se muestra a continuación:

#ifdef ION_LOG_LEVEL_DEBUG #define LOGD(x) \ ion::log::Logger(ion::log::Message::MSGDEBUG,__FILE__, __LINE__).Log(x) #else define TLOGD(x) if (true) {} else std::cerr #endif

Ahora, el mismo código de antes quedaría así al hacer la sustitución:

if (true) {} else std::cerr << "Variable de prueba: " << miVariable;

Esto no solo compila sin problemas, sino que, si el compilador está realmente en modo

release, al aplicar la optimización no generará ningún código, puesto que detectará que la

condición del “if” se cumple siempre y por ello omitirá la parte del “else” y no la añadirá al

binario.

Page 109: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

109

9.2 Administración de recursos de disco Al instanciar una entidad en nuestro motor es muy habitual hacerlo a partir de uno o

varios assets que se cargan desde disco, como puede ser un shader, una malla 3D o un script.

Una forma “naive” de implementar esto para una posible factoría de entidad que recibe como

parámetro el nombre de archivo del objeto a dibujar podría quedar así:

entity = ion:addEntity("jugador", "heroe3D.obj")

Como resultado, la factoría instanciaría la entidad “jugador” y luego cargaría desde disco

el objeto “malla3D.obj” y se lo añadiría como un componente gráfico a la entidad.

Por supuesto, esto no es una forma correcta de administrar los recursos de disco, ya que

podría tratarse de un objeto muy recurrente como, por ejemplo, un enemigo que se instancia

100 veces en una escena. Con la misma factoría anterior, el siguiente código accedería a disco

100 veces para cargar “enemigo3D.obj”:

for i=1,100 do enemigos[i] = ion:addEntity("enemigo" .. i, "enemigo3D.obj") end

En realidad, esto es matizable, ya que el disco dispone de una caché que podría ayudar en

este caso, pero no siempre podemos confiar en que la cache de disco esté como nosotros

queremos. Por ejemplo, podemos cargar el enemigo al principio del programa y más tarde,

tras la carga de varios recursos o incluso debido a tareas internas del propio S.O., el archivo

inicial ha desaparecido de la caché.

Por este motivo, se hace imprescindible utilizar un sistema propio de caché que guarde

instancias de recursos cargados desde disco. Podríamos hacer esto en la propia factoría de la

entidad, pero entonces no podríamos reutilizar el sistema fuera de dicha factoría.

La primera solución sería separar esto en una clase separada AssetMapper que tenga un

contenedor con los assets previamente cargados e instancias de cargadores especializados

(ShaderLoader, MeshLoader…) a partir de un interfaz común AssetLoader. Luego, según el

tipo de archivo (mirando la extensión) usaríamos un loader u otro.

Page 110: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

110

El siguiente diagrama muestra cómo quedaría el diseño inicial simple de esta solución:

Para mantener el principio open-closed, estos loaders estarán mapeados por extensión de

archivo para no tener que comprobar a mano (mediante ifs) qué loader se debe usar en cada

caso.

Otro inconveniente que queremos resolver con esta clase es poder liberar fácilmente de la

caché entidades que ya no vamos a utilizar. Para lograrlo, teniendo en cuenta que para su

almacenamiento estamos usando Smart pointers, crearemos un método removeUnused en

AssetMapper que, por cada recurso, obtendrá el número de objetos referenciados y, si este

número es 1 significa que sólo está en la caché y por tanto se puede eliminar con seguridad.

Este método podrá ser llamado manualmente por el programador en el momento adecuado

como, por ejemplo, tras finalizar una zona o pantalla del juego.

Page 111: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

111

Un último detalle que queremos resolver es el hecho de que ahora el sistema devuelve los

assets como objetos de interfaz, por lo que al obtenerlos no pueden ser usados directamente

sin hacer el correspondiente up-casting. Por ejemplo, en la parte de C++, para obtener un

script y luego acceder al código deberíamos hacer esto:

AssetPtr asset = assetsMapper->get("logica.lua"); ScriptClassPtr miScript = dynamic_pointer_cast<ScriptClass>(asset); string codigo = miScript->getCode();

Como vemos es algo incómodo, y además al ser un casteo dinámico queda en manos del

programador la responsabilidad de elegir el tipo de puntero que necesita. Además, este

inconveniente se traslada también a la hora de hacer el binding en Lua, ya que este lenguaje

no entiende de herencia de objetos o polimorfismo.

Por este motivo, lo que nos interesará será que sea el método get de assetMapper quien

nos proporcione el objeto listo para usar. Para ello, en primer lugar, haremos que el método

sea una plantilla que reciba el tipo que queremos. Ahora, el código anterior se puede simplificar

así:

ScriptClassPtr asset = AssetMapper->get<ScriptClass>("logica.lua"); string codigo = miScript->getCode();

Pero esto sólo deriva el mismo problema a la clase AssetMapper. Sin embargo, ahora

podemos resolverlo creando un nuevo nivel de indirección apoyándonos en el patrón composite.

La clase AssetMapper ya no contendrá los assets y loaders, sino que habrá una clase

AssetContainer dedicada a esto. De esta manera AssetMapper será un almacén que indexa los

AssetContainers por tipo de asset a cargar (es decir el typeid de la clase). Así, al recibir el

parámetro de tipo por plantilla, sólo tenemos que obtener el AssetContainer correspondiente

que tendrá los cargadores adecuados.

Esto también nos da mejor rendimiento al segmentar los assets por tipo y, además permite

tener múltiples tipos de cargadores para un mismo tipo de asset (por ejemplo, para texturas,

podemos tener cargadores png, jpg, gif, etc.). En el capítulo de “Diseño del código” podemos

ver el diagrama UML final para esta implementación.

Page 112: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

112

9.3 Dependencias en el sistema ECS El patrón ECS ofrece un diseño flexible, reusable, modular y eficaz, por lo que es ideal

para nuestro motor. Sin embargo, no todo queda resuelto, y es que esta arquitectura no evita

la necesidad de comunicación entre los distintos sistemas. El ejemplo más representativo tal

vez sea el sistema de script que, según el nivel de control que queramos dar, deberá estar

acoplado a prácticamente todos los componentes para saber la posición de un personaje o el

estado de una colisión, etc.

Para evitar este acoplamiento, la especificación del patrón nos dice que se debería resolver

mediante un sistema de mensajes para comunicar los sistemas empleando el patrón observer,

evitando así también un polling constante cuando no hay cambios de información. En cualquier

caso, como veremos en la implementación, habrá casos en los que, por optimización, no

tendremos más remedio que acoplar algunos componentes entre sí.

Un motor de juegos debe rendir al máximo en tiempo real y no puede permitirse la carga

que le añadiría un sistema de mensajes para componentes que van a estar necesariamente

comunicados como son los procedentes de los sistemas de transformación, gráficos y física.

Por este motivo, se ha hecho una modificación sobre el patrón original permitiendo el

acoplamiento de manera excepcional para sistemas como estos donde la comunicación es

constante. De esta forma, tanto el componente de física como el de gráficos tendrán ambos

una instancia del componente de transformación de la entidad a la que pertenecen.

Además, de cara a la creación del interfaz de las entidades para el script también será

cómodo para el desarrollador de juegos tener en el script de control de la entidad un objeto

entidad (lógico) que contenga punteros a los distintos componentes de ésta. De esta forma, el

script actuaría de mediador entre los componentes, añadiendo la lógica necesaria en cada

momento. Así pues, el diagrama de dependencias quedará como se muestra a continuación:

Page 113: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

113

Como vemos, el componente de transformación sirve de intermediario entre los de gráficos,

colisiones y física, permitiendo que se mantengan desacoplados entre sí.

9.4 Interoperabilidad entre librerías de gráficos y física Como ya hemos visto, este motor integra la librería OSG para gráficos y Bullet para física.

Por un lado, esto nos adelanta el trabajo, ya que no tenemos que implementar un motor

gráfico o de física desde cero, lo cual podría derivar de por sí en dos grandes proyectos

separados de éste.

Como desventaja, al ser dos librerías creadas por distintos autores, presentan problemas

de interoperabilidad entre sí. Esto añadido al hecho de que son dos partes del motor que están

altamente relacionadas, ha requerido de una solución eficiente para resolver el problema de

compatibilidad que introducen.

Para empezar, como vemos en la siguiente ilustración, utilizan sistemas de coordenadas

distintos:

Figura 9.1. Diagrama de dependencias entre componentes

+Z

Bullet OSG

+Z

+Y

+X +X

+Y

Figura 9.2. Sistemas de coordenadas en Bullet y OSG

Page 114: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

114

Esto nos obliga a tener en el componente Transform algún método para convertir las

matrices de un sistema a otro para que los cálculos sean equivalentes al hacer de puente entre

los sistemas de gráficos y física.

Lo haremos en el método onUpdate, que es cuando se actualiza la transformación (una vez

por fotograma). No se hace en los métodos que modifican la matriz como setPosition,

setRotation o setScale, ya que la lógica del juego podría hacer llamadas a estas funciones

varias veces teniendo que calcular todo el tiempo la matriz cuando sabemos que los gráficos

sólo se van a actualizar al final de la iteración.

Otra decisión importante que se ha tomado ha sido almacenar la transformación en el

componente como una instancia de matriz del sistema de física (Bullet) y convertirla al sistema

de gráficos (OSG) al hacer update. Esto se hace por motivos obvios, y es que los gráficos no

escriben en la matriz, sino que sólo la leen para hacer los cálculos. Sólo la lógica y la física

modifican este objeto.

Por otro lado, ambos sistemas emplean su propia librería de matemáticas, es decir, su

propio conjunto de clases para el manejo de vectores, matrices y cuaterniones. Para poder

tener un objeto que exponer al sistema de script de forma que sea coherente se han creado

clases que implementan el patrón Adapter para los objetos necesarios. Además, se han

sobrecargado los operadores de casteo de tipos para que sea sencillo comunicar una librería

con otra usando el mismo objeto.

9.5 Resolución de riesgos de programación concurrente Los riesgos de concurrencia aparecen cuando dos o más procesos simultáneos tratan de

escribir en la misma zona de memoria, o bien cuando uno lee mientras otro puede escribir. El

valor leído o escrito queda indeterminado y dependerá de quién llegó antes, lo cual no podemos

saberlo a priori.

El proyecto tiene dos puntos de riesgo críticos en cuanto a concurrencia de procesos que

pasamos a resolver en los siguientes apartados: uno es el input de usuario en la versión Android

que se hace en un hilo aparte, y otro es la ejecución de scripts desde la consola remota.

Page 115: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

115

9.5.1 Sistema de input de usuario Un input de usuario es, por ejemplo, la pulsación de una tecla o un click sobre la ventana

del juego o bien, en Android, un toque en la pantalla o un movimiento del sensor acelerómetro.

La versión de escritorio no tiene ningún problema en este sentido, puesto que de este

apartado se encarga la librería OSG, la cual utiliza un sistema de notificaciones que acumula

todas las peticiones en una cola para luego atenderlas una vez por fotograma de manera

síncrona antes de la ejecución de la iteración en el bucle principal del motor.

Sin embargo, en Android esto es distinto ya que, el método que nos da el SDK en este

sistema operativo trabaja de forma asíncrona en un hilo diferente del principal.

Inicialmente atendíamos el evento en el mismo momento en que nos llegaba desde el API

de Android. De esta forma, al recibirlo, se derivaba la llamada a todos los listeners registrados

a dicho evento y que esencialmente pertenecen a objetos instanciados en el sistema de script.

El problema de esto es que uno de los scripts destino podía estar en ejecución en ese momento

y, al hacer esa llamada, se ejecutaba el método correspondiente simultáneamente dando

resultados impredecibles en los datos.

En este caso se ha resuelto creando un método en la clase InputSystem que atiende las

peticiones procedentes de Android y sólo guarda los valores obtenidos para luego llamar a

todos los lísteners en orden al comienzo del bucle principal tal como lo hace OSG.

9.5.2 Ejecución de script desde consola remota El servidor de consola funciona mediante TCP y atiende las peticiones remotas del cliente

de forma paralela al núcleo del motor, ya que de otro modo se bloquearía al aceptar conexiones.

Si ejecutamos la orden Lua en el momento en que se recibe ocasionaríamos un problema

similar al descrito en el apartado anterior. Como solución vamos a adoptar un sistema

parecido, haciendo que la ejecución del script se posponga a un momento seguro, es decir, al

comienzo de la siguiente iteración del motor.

Page 116: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

116

Así pues, cuando se hace la petición, se guarda la orden para el próximo frame y se ejecuta

en ese momento dejando en espera el servidor hasta que se produce una respuesta del sistema

de script. Dicha respuesta, que puede ser una cadena de texto con el log o un mensaje de error

en caso de que no compile el script, se enviará de vuelta al cliente.

Aunque así podría quedar resuelto, en nuestro caso daremos una nueva vuelta de tuerca y

esta vez sí que vamos a hacer uso de regiones de código mutex (mutuamente excluyentes)

para evitar problemas en el caso de que dos consolas (de dos programadores diferentes) hagan

una petición al mismo tiempo. Lo que bloquearemos en este caso será la variable que guarda

la cadena de script, así como la cadena de respuesta para que no puedan interferirse dos

consolas.

9.6 Sincronización de relojes Una función importante del cliente/servidor de consola es que es capaz, bajo demanda, de

detectar los cambios en la fecha de modificación en los ficheros del directorio de trabajo para

subir automáticamente al reproductor los cambios recientes.

Esta utilidad es muy potente ya que, de este modo podemos editar los ficheros con nuestro

programa favorito, por ejemplo, los scripts con un editor Lua o las mallas 3D de los objetos

con Blender y, tras guardar los cambios, sólo con una orden se transmitirán los archivos

actualizados inmediatamente al dispositivo de prueba.

Esto no es tan trivial como parece en un principio, ya que para que el sistema pueda saber

qué ficheros están actualizados en el cliente con respecto a la versión que ya está en el destino,

los relojes de ambas máquinas deben estar sincronizados, pues la fecha que se guardará al

recibir el fichero por red será la fecha en la que se recibe (y no la fecha del archivo original)

ya que se crea en ese momento.

La solución más eficaz es sincronizar ambos relojes empleando algún protocolo conocido

como NTP, aunque en nuestro caso utilizaremos un sistema a medida más sencillo que sirve

sobradamente para nuestro caso.

Page 117: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

117

La solución consiste en guardar en el dispositivo de prueba un archivo sync.txt donde se

irán guardando las fechas de modificación originales de los ficheros transmitidos. A la hora de

comprobar si un fichero está actualizado será esa hora la que compararemos.

Como vemos esta hora se guarda en disco y no simplemente en un array de la memoria,

dado que, de hacerlo así, el sistema no nos serviría entre distintas sesiones de trabajo ya que

los datos desaparecerían.

9.7 Abstracción mediante fachadas Como ya hemos visto, el patrón ECS es muy flexible y modular, lo cual nos da un código

escalable, extensible y fácil de mantener. Como contraprestación, estas ventajas vienen a

cambio de una mayor complejidad en el uso del código.

El siguiente extracto de script Lua sería el código necesario para crear una entidad

utilizando puramente el interfaz que nos proporciona nuestro sistema ECS:

-- Instanciar una nueva entidad entity = Entity.new('caja') -- Crear un volumen que representa el objeto a dibujar (en este caso una caja) box = Box.new(2,2,2) -- Definir y añadir componente de transformación con posición y rotación transform = TransformComponent.new() transform.position = Vector3.new(0,4,0) transform:setAxisAngle (Vector3.new(0.1,2.5,2.5), 2) entity:addComponent (transform) -- Crear un material indicando shader y color difuso shader = AssetMapper:getShader ('basic.glsl') material = Material.new() material.shader = shader material.setVector4('diffuseColor', Vector4.new(0,1,1,1) ) -- Definir y añadir componente de gráficos drawable = DrawableComponent.new() drawable.shape = box drawable.material = material entity:addComponent(drawable) -- Definir y añadir componente de física physics = PhysicsComponent.new() physics.shape = box physics.mass = 1.0 physics.flags = DYNAMIC | TRIGGER entity:addComponent(physics) -- Añadir entidad a la escena ion:addEntity(entity)

Page 118: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

118

Todo este código lo que hace es crear una caja de 2x2x2 con propiedades de física que

veremos cayendo en pantalla por efecto de la gravedad. Como resumen, lo único que hace es

instanciar una entidad en blanco a la que luego añade los componentes pertinentes con las

opciones elegidas. Finalmente, añade la entidad a la escena.

Este código que parece engorroso, en realidad ilustra sólo una parte de la cantidad de

parámetros y ajustes que están a nuestro alcance, pero no todos. Por ejemplo, aquí no se ha

añadido ningún componente de script para añadirle un comportamiento de lógica a la entidad.

Sin embargo, no siempre vamos a necesitar tanto nivel de detalle. Por este motivo, se ha

decidido añadir una capa de abstracción para la creación y manejo de entidades que nos sirva

como fachada para facilitar el trabajo. Así pues, por ejemplo, podemos escribir un código

equivalente al anterior utilizando la función ion:addPhisicsEntity:

entity = ion:addPhysicsEntity("caja", BoxVolume(2,2,2), DYNAMIC | TRIGGER, PositionRotation(V4(0,4,0), V4(0.1,2.5,2.5))) entity.drawable.material.diffuse = v4(0,1,1,1)

Las factorias BoxVolume y PositionRotation ayudan a identificar el significado de los

parámetros.

El resto de opciones que no aparecen, como la masa a 1.0 estarían establecidas por defecto.

Así, se crean todos los componentes necesarios con los parámetros indicados y se añaden a la

entidad, la cual aparece en la escena tras la llamada a esta función. El único código que queda

fuera de ella el que establece color, que se añade en una línea posterior.

Si además quisiéramos que la entidad tuviera, por ejemplo, los scripts “ControlJugador”

y “Planear”, lo haríamos así:

entity = ion:physicsEntity("caja", BoxVolume(2,2,2), DYNAMIC | TRIGGER, PositionRotation(V4(0,4,0), V4(0.1,2.5,2.5))) entity.drawable.material.diffuse = v4(0,1,1,1) ion:addScript(entity,"ControlJugador") ion:addScript(entity,"Planear") ion:addEntity(entity)

En el futuro, la función addScript se reemplazará por entity:addScript. No se ha hecho

todavía porque requeriría comprobaciones, pues en este momento no se puede añadir un script

Page 119: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

119

a una entidad que ya ha sido añadida a la escena. Por eso se debe añadir antes de la llamada

a addEntity (obsérvese que al instanciar la entidad hemos usado physicsEntity en lugar de

addPhysicsEntity como antes).

9.8 Acerca del editor gráfico Por motivos de tiempo externos al desarrollo del proyecto, no ha sido posible terminar

esta parte, siendo, de todo lo que inicialmente se propuso, la única tarea que se pospone para

terminar en el futuro. No obstante, sí que se ha dejado un buen tramo del camino recorrido,

pues la aplicación base del editor está diseñada y desarrollada, dejando también el entorno de

desarrollo preparado. Se trata de una aplicación Qt de escritorio que muestra una ventana

OpenGL, la cual se ha conectado al núcleo del motor.

A partir de aquí lo siguiente sería añadir los botones, menús y otros controles imitando el

mockup propuesto en esta memoria. Todo ello está listo para realizar con el editor gráfico

QtEditor.

Page 120: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM
Page 121: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

121

10 EJEMPLOS DE USO

Esta sección pretende servir como una introducción rápida para el manejo del motor por

parte del programador de juegos que quiera empezar a realizar un nuevo proyecto con nuestro

sistema. En los siguientes apartados explicaremos cómo crear un proyecto básico y también

analizaremos el código de las demos que acompañan al motor con el fin de que todo quede

más claro.

Para ampliar la información que se proporciona aquí y tener una referencia completa de

toda la funcionalidad disponible, nos remitiremos al “Manual del API” que se incluye en el

anexo III.

10.1 Primer proyecto Para empezar un proyecto simplemente copiamos el ejecutable del reproductor

“player.exe” a la carpeta que queramos. Ese será nuestro directorio de trabajo. También

copiaremos ahí el directorio “shaders” de ejemplo que luego podremos modificar.

Ahora, creamos ahí mismo una carpeta “scripts” donde añadiremos un archivo con el

nombre “main.lua”. Debe llamarse así para que actúe como punto de entrada y se ejecute

automáticamente al abrir el juego.

El proyecto está listo para ejecutarse, pero aún no hace nada porque no hemos añadido

ninguna entidad a la escena. Si lo arrancamos ahora veremos una pantalla vacía.

Nuestra escena de ejemplo contendrá un suelo, una caja y una cámara. Veamos cómo

crearlas.

Page 122: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

122

Para crear el suelo añadimos el siguiente código a “main.lua”:

ground = ion:addPhysicsEntity("ground", BoxVolume(20,1,20), KINEMATIC) ground.physics.mass = 0.0

Como vemos, lo creamos como KINEMATIC y luego establecemos la masa a 0. Esto es

para que podamos colocarlo en una posición fija sin que reaccione a las fuerzas físicas con

otros objetos.

Ahora crearemos una caja que caerá al suelo por efecto de la gravedad. El código es muy

similar:

box = ion:physicsEntity("box", BoxVolume(2,2,2), DYNAMIC, PositionRotation(V3(4,8,0),V3(1,0.5,1.2))

Este objeto se ha creado con las opciones físicas por defecto (DYNAMIC con mass=1)

Por último, haremos que la cámara mire a la posición donde caerá la caja:

cam = ion:getEntity("camera").camera

cam:lookAt(V3(0,25,50),V3(0,0,0),V3(0,1,0))

Con esto ya tenemos nuestro primer proyecto listo para ejecutarse. Si todo ha ido bien,

veremos una caja caer por efecto de la gravedad tal como se muestra en la siguiente captura:

Figura 10.1. Captura del primer proyecto de ejemplo

10.2 Uso de la consola Antes de pasar a explicar las demos, vamos a introducir el uso de la consola para poder

ejecutar scripts.

Page 123: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

123

En esta ocasión, vamos a ejecutar player.exe desde el mismo directorio de trabajo original

del motor para así tener acceso a todos los assets que vamos a necesitar. Una vez abierto,

veremos la ventana vacía ya que no tenemos un main asociado, pero esto no importa porque

lo que queremos es ejecutar una orden de consola.

Para ello, abrimos una ventana de terminal y ejecutamos console.exe utilizando como

servidor nuestro host local (127.0.0.1) con el siguiente comando:

console 127.0.0.1

Esto conectará con el player (siempre que esté abierto) para poder enviar órdenes con

mediante Lua. Si todo ha ido bien debería mostrar los parámetros de inicio seguidos de un

mensaje de bienvenida:

IP: 127.0.0.1 Port: 8080 Root: d:/tfg/player/player/bin Welcome to ION Engine Console. Copyright (C) 2019. Daniel Ponsoda Montiel. Ready >

Ahora podemos escribir la orden que queramos. Por ejemplo, podemos probar el uso del

entorno lua haciendo algun a operación matemática utilizando variables. Veamos un ejemplo:

Ready > a = 1 Ready > b = 2 Ready > print(a + b) 3

En los siguientes apartados veremos cómo usar la consola para ejecutar las demos, pero el

verdadero potencial de esta herramienta se demuestra sabiendo que tenemos a nuestro alcance

todo el API del motor desde la consola, con lo que podemos hacer cambios en vivo en el código

mientras está en ejecución para hacer todas las pruebas que sean necesarias para pulir nuestro

proyecto de forma rápida y eficaz.

Page 124: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

124

10.3 Demostración acelerómetro: “DemoBall.lua” Esta demo incluida en el directorio scripts del motor nos enseña como obtener el estado

del acelerómetro de Android al tiempo que también nos demuestra cómo añadir un

componente de script a una entidad para alterar su comportamiento.

Para ejecutarla, accedemos a la consola como hemos visto en el apartado anterior y

escribimos la siguiente orden:

> ion:run("DemoBall")

Esto ejecutará el programa “DemoBall.lua” que se encuentra en el directorio “scripts” así

como los scripts asociados que necesita. En este momento deberíamos ver la siguiente imagen:

Figura 10.2. Demostración acelerómetro

Veremos una bola blanca sobre un escenario donde tiene un suelo y cuatro paredes para

que no se salga. Si el reproductor estamos ejecutándolo sobre un dispositivo Android,

podremos controlar la gravedad con el acelerómetro y ver cómo se mueve la bola.

Si lo hacemos sobre Windows, podemos probar el mismo comportamiento con el emulador

de acelerómetro (Las teclas J,K,L,I funcionan como los cursores para los ejes X,Z, y las teclas

Y,H hacen lo propio para el eje Y)

Veamos ahora con un poco más de detalle el funcionamiento de esta demo. En primer

lugar, vemos algo nuevo al principio del código:

AssetMapper.forceReload = true

Page 125: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

125

ion:reset()

El flag forceReload le dice al gestor de assets que acceda siempre a disco para obtener los

recursos sin utilizar caché. Esto es útil en modo desarrollo si vamos a estar haciendo cambios

en los assets. Así podemos ver los cambios sin tener que reiniciar el player.

Luego, ion:reset() lo que hace es reiniciar la escena (elimina todo lo que haya antes de

cargar este script). Como veremos en la siguiente demo, la ejecución de un script no limpia la

escena previamente. Esto nos sirve para poder ejecutar y combinar scripts entre distintos

archivos. Por otro lado, nos puede interesar reiniciar, por ejemplo, para un cambio de fase o

escena en nuestro juego. Para nuestro ejemplo, reiniciamos al comienzo.

Seguido a estas dos instrucciones, se añaden a la escena el suelo y las paredes tal como ya

se ha explicado y, por último, vemos cómo se crea la entidad “ball”. Esta vez, añadiendo un

componente de script:

ball = ion:physicsEntity("ball", SphereVolume(1), 0, Position(0,5,0)) ball.drawable.material.diffuse = V4(1,1.4,1,1) ion:addScript(ball,"AccelController") ion:addEntity(ball)

El script “AccelController.lua” contiene el siguiente código:

function AccelController:init() print('init AccelController\n') end function AccelController:update(deltaTime) end function AccelController:onAccelUpdate(x,y,z) g = 9.8*4 v = V3(x,-z,-y) v = v:normalize() v = V3(v.x*g, v.y*g, v.z*g) self.physics.gravity = v; end

Como vemos, simplemente tiene 3 funciones de objeto que responden a los eventos init,

update y onAccelUpdate. Los dos primeros no hacen nada, mientras que el último, que es el

que nos interesa, recibe los ángulos del acelerómetro desde Android y calcula la nueva

inclinación para modificar la gravedad sobre el propio objeto (self).

Page 126: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

126

Como vemos, los scripts que podemos usar como componentes de una entidad deben

implementar funciones dentro del objeto del mismo nombre que el archivo. Los distintos

eventos que se pueden utilizar están descritos en el “Manual del API” del anexo III.

10.4 Demostración rendimiento: “DemoRandom.lua” Esta demo instancia una cantidad previamente elegida de objetos con tamaños, formas y

colores aleatorios con propiedades de física que colisionarán entre sí en la escena. Esto nos

sirve para demostrar el rendimiento de nuestro motor.

Como escena base utilizaremos la demo anterior que nos proporciona un recipiente para

los objetos. Por este motivo, en el script esta vez no ponemos ion:reset() al comienzo. De esta

manera, tras ejecutar DemoBall, si lanzamos DemoRandom no se borrará la escena anterior

y los objetos caerán sobre ese suelo.

Una última cosa que haremos antes de ejecutar DemoRandom será indicarle el número de

entidades que vamos a crear por medio de la variable global maxEntities. Las órdenes a

ejecutar serán, por tanto, las siguientes:

> ion:run("DemoBall") > maxEntities = 200 > ion:run("DemoRandom")

Al hacerlo nos aparecerán 200 entidades aleatorias en escena que se mueven de manera

fluida en cualquier dispositivo de gama baja-media moderno:

Figura 10.3. Demostración física

Page 127: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

127

10.5 Demostración minijuego: “DemoMarble.lua” Esta demo reúne una gran parte de la funcionalidad del motor. En ella se implementa un

minijuego basado en el mítico Marble Madness de la época de los 8 bits en el que controlaremos

una bola por un escenario con obstáculos y enemigos utilizando el acelerómetro del móvil.

En esta ocasión, las características nuevas del API que pondremos a prueba serán:

Carga de modelos 3D creados en Blender (exportados a .obj)

Combinación de varios componentes de script sobre una misma entidad

Movimiento cinemático de elementos de la escena

Respuesta a eventos de colisión por parte de la lógica

Colisiones en objetos cóncavos

Como venimos haciendo hasta ahora, la demo la ejecutaremos por medio de la consola,

pero debemos recordar que, si este fuera un juego para distribuir, renombraríamos el script

“DemoMarble.lua” a “main.lua” para que se ejecutase automáticamente al abrir la aplicación.

La orden para lanzar la demo por ahora sería:

> ion:run("DemoMarble")

Tras una breve carga (no debería demorar más de 1 o 2 segundos), veremos en pantalla el

juego. Nuestro personaje que es la bola puede ser controlado con el acelerómetro. Deberemos

evitar caer desde demasiado alto o la caída nos matará. También debemos evitar que nos

toquen los enemigos, los cuales son unas bolas negras con pinchos que deambulan por la

escena. El objetivo es llegar a la zona cuadriculada que sería la meta (cuidado con el último

tramo, ya que es un puente muy estrecho). Al llegar al final no hará nada, puesto que sólo es

una demostración y no tiene más fases, pero seguro que puede servir de base e inspiración

para un juego más grande.

A continuación, mostramos algunas capturas. Como puede verse, el escenario tiene los

bordes redondeados. Esto es porque no ha sido creado usando las formas básicas como hasta

Page 128: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

128

ahora, sino que todo son modelos 3D diseñados en Blender. En la carpeta “meshes” están

todos los modelos que intervienen en la escena.

Figura 10.4. Juego de demostración

Page 129: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

129

11 PRUEBAS DE RENDIMIENTO

Las siguientes pruebas han sido realizadas utilizando la demo de rendimiento

“DemoRandom.lua” sobre la escena del tutorial de iniciación (Ver apartado de “Ejemplos de

uso”). La versión compilada del motor está optimizada para modo release con Visual Studio

Community 2017.

El equipo de escritorio utilizado para las pruebas tiene las siguientes características:

Procesador Intel Core i7 2600K Arquitectura 64 bits

Número de núcleos 4 Número de subprocesos 8

Velocidad de reloj 3800 Mhz Caché primaria 4x32Kb

Caché secundaria 4x256Kb Caché L3 8Mb SmartCache

RAM 2x4Gb DDR3 a 1333Mhz Velocidad bus DMI 5 GT/s

Ancho banda memoria 21 GB/s Disco duro 240Gb SSD Sata 3

Tarjeta gráfica nVidia Geforce GTX 750 Sistema operativo Windows 10 64 bits

Figura 11.1 DemoRandom.lua

Page 130: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

130

El dispositivo Android de pruebas es el siguiente:

Modelo Samsung Galaxy S4 GT-I9505 Procesador Krait 300

Número de núcleos 4 Velocidad de reloj 1900 Mhz

RAM 2Gb a 600Mhz GPU Qualcomm Adreno 320, 400Mhz

Sistema operativo Android 5.0.1

Para la medición se ha desactivado la sincronización vertical de la pantalla y se ha obtenido

el resultado más bajo registrado tras la ejecución de la demo. Dicho resultado coincide con el

momento donde aparecen todos los objetos en pantalla ya que, más tarde, a medida que van

cayendo éstos, se salen por debajo y ya no se renderizan con lo cual el rendimiento es mayor.

Los siguientes gráficos comparan la versión de escritorio con la versión móvil. La versión

PC aguanta sin bajar de 90 fps con 550 entidades simultáneas.

Figura 11.2. Gráfica de rendimiento en la versión PC

Por otro lado, como vemos, en la gráfica de rendimiento de Android, hemos tenido que

utilizar otra escala de entidades para poder apreciar mejor los datos. Si en PC hemos probado

entro 0 y 1000 entidades, en Android medimos de 0 a 100.

3566

1881

1449

1098824

604445

285 157 142 121 90 57 48 43 37 33 29 23 22 190

500

1000

1500

2000

2500

3000

3500

4000

FOTO

GRA

MAS

PO

R SE

GU

ND

O

NÚMERO DE ENTIDADES

Rendimiento versión PC

Page 131: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

131

Este sistema genera menos fps ya que en general la GPU y la CPU tienen mucha menos

potencia. No obstante, un juego normal con 30 entidades de física simultáneas es más de lo

necesario para casi cualquier propósito, y esa cifra se alcanza a 44 fps, lo cual compite sin

problemas con otros juegos comerciales de esta plataforma.

Figura 11.3. Gráfica de rendimiento en la versión Android

773

163

69 44 33 27 23 20 17 15 13

0

100

200

300

400

500

600

700

800

900

0 10 20 30 40 50 60 70 80 90 100

FOTO

GRA

MAS

PO

R SE

GU

ND

O

NÚMERO DE ENTIDADES

Rendimiento versión Android

Page 132: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM
Page 133: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

133

12 POSIBLES MEJORAS

El game engine desarrollado en este trabajo ha quedado funcionalmente muy completo,

aunque por más características implementadas que pueda llegar a tener, siempre podrán

añadirse más. En los siguientes apartados se proponen algunas características extra que sería

interesante incluir a corto/medio plazo.

12.1 Mejoras en el sistema de gráficos Lo primero que tal vez mejoraríamos al ver las imágenes del motor en comparación a otros

disponibles en el mercado es el aspecto gráfico. Sin embargo, esto se puede personalizar por

parte del programador de juegos añadiendo shaders o creando los suyos propios. Un

desarrollador gráfico experimentado dominará el lenguaje GLSL y podrá crear todo tipo de

efectos para sus juegos. De hecho, lo más probable es que ya tenga su propia biblioteca de

shaders que podrá emplear en este sistema. Pero algo que no estaría de más, no obstante, es

incluir una pequeña biblioteca más extensa que cubra al menos los efectos más fundamentales,

como materiales tipo metal, transparencia, plástico, dibujo animado, etc.

Dichos shaders también servirían a programadores menos experimentados que podrían

usarlos como punto de partida para modificarlos y crear los suyos propios.

12.2 Compilación del script El hecho de que la lógica del juego pueda ser desarrollada mediante un lenguaje de script

tiene todas las ventajas que ya se han comentado en el apartado de introducción de esta

memoria. Principalmente nos da mucha más agilidad a la hora de programar al no tener que

Page 134: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

134

compilar el código y, además, al usar un intérprete sobre una máquina virtual con garbage

collector, no tenemos que preocuparnos de problemas de pérdida de punteros, memoria, etc.

Sin embargo, tiene una desventaja y es que el rendimiento no puede compararse al de un

programa realizado en código nativo (por ejemplo, el que genera un compilador de C++).

Una mejora que se podría hacer sin demasiada dificultad a medio plazo sería la inclusión

de un sistema que permitiera compilar los scripts Lua a código máquina para la plataforma

objetivo. De esta forma, durante la etapa de desarrollo de un juego podríamos desarrollar con

Lua ya que nos da mayor soltura a la hora de programar y, cuando por fin hemos terminado

el proyecto y lo tenemos todo como queremos, hacer una compilación en binario para, además,

que funcione al máximo rendimiento posible.

Pero ¿Cómo conseguiríamos esto? Podríamos usar LuaJIT, pero este sistema no está

disponible en todas las plataformas y además tampoco ofrece el 100% del rendimiento de un

código escrito en C++. La idea sería crear, utilizando Bison y Flex, un traductor de Lua a

C++. Con los estándares modernos de C++ tenemos todo tipo de características en este

lenguaje como funciones lambda, smart pointers, etc. con lo que una traducción de Lua a

C++ se podría hacer prácticamente con un símple cambio en la gramática (sin apenas tocar

nada a nivel semántico), con lo que el proceso se simplifica enormemente.

Una vez tenemos el código traducido a C++ se compilaría utilizando un compilador

maduro como gcc o clang, el cual nos daría acceso a todo el potencial que ofrece su optimizador

de código. Además, al ser de código abierto, podría incluirse directamente en el motor.

12.3 Prescindir de algunas dependencias La unión en un solo sistema de OSG (para gráficos) y Bullet (para física) ha quedado

funcional, pero no es todo lo óptimo que desearíamos, ya que como ya hemos visto en

apartados anteriores, cada una de estas librerías utiliza su propio sistema de matemáticas y,

para poder comunicarlas entre sí, ha hecho falta recurrir a código adaptador, introduciendo

un nivel de indirección que produce pérdida de rendimiento.

Page 135: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

135

Por otra parte, OSG es un motor gráfico que no hace uso de la última tecnología disponible

como Vulkan o DirectX12.

Lo ideal sería desarrollar una librería de matemáticas propia partiendo de los

conocimientos que ya tenemos (ver Anexo I: Librería de matemáticas) y basarnos en dicha

librería para crear un motor gráfico que utilice Vulkan para la versión de escritorio, OpenGL

para Android (ya que la mayoría de dispositivos actuales todavía no soportan Vulkan) y

DirectX 12 para un posible port a Xbox.

Por último, teniendo también un motor de física propio tendríamos todo bajo el mismo

sistema de matemáticas y podríamos optimizarlo adecuadamente.

12.4 Cifrado del código y assets En juegos comerciales esto es imprescindible, ya que la gran mayoría de desarrolladores lo

demandan. Los assets y el código deben estar cifrados para dificultar la ingeniería inversa.

Sobre todo, para tener una mínima protección frente a hackers que puedan hacer trampas en

juegos de red y también, por supuesto, para proteger la propiedad intelectual de los creadores.

Page 136: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM
Page 137: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

137

13 CONCLUSIONES

Tras completar con éxito el desarrollo del motor, se ha conseguido realizar de manera

satisfactoria un tipo de proyecto que alberga un alto grado de complejidad y se ha demostrado

que un game engine básico puede ser desarrollado por una sola persona en un plazo razonable.

Por supuesto, cualquier programador cuyo objetivo sea realizar un videojuego podrá elegir

entre una gran cantidad de herramientas disponibles en el mercado, muchas de ellas excelentes,

de código abierto y con un inmejorable soporte técnico. No obstante, para hacer uso de ellas,

también es necesario saber lo que estamos haciendo y, sobre todo, sortear la curva de

aprendizaje que impone el motor que vayamos a usar. Pero al final, esto no será más que otro

tipo de esfuerzo que estamos invirtiendo en aprender a usar esa herramienta concreta.

Por otro lado, si desarrollamos nuestro propio motor, llegamos por fin a comprender qué

hay debajo de todos esos subsistemas que, para el resto de programadores, seguirán siendo

cajas negras cuyo funcionamiento es un misterio. Esto sin duda nos hace mejores ingenieros,

puesto que seremos capaces de extrapolar lo aprendido para plantear mejores soluciones en

caso de trabajar con cualquiera de los motores existentes, ya que los principios en los que se

basan son los mismos en su gran mayoría. Además, estaremos mejor predispuestos a realizar

mejoras sobre un motor existente de código abierto.

Por último, quisiera destacar la experiencia adquirida durante el desarrollo en cuanto a

diseño de código. Se trata de un sistema complejo donde hay que encajar piezas procedentes

de disciplinas muy diferentes y esto me ha proporcionado conocimientos transversales

inestimables en cuanto a ingeniería del software. Además, todo lo aprendido ha sido plasmado

en esta memoria explicando por qué se han tomado las decisiones de diseño de determinada

forma y no de otra, lo cual puede ser de gran ayuda a otros desarrolladores.

Page 138: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM
Page 139: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

139

14 REFERENCIAS

Amber, Scott W. 2006. The Agile Unified Process. 2006.

Crytek. 2018. CryEngine V Manual. [En línea] 2018.

http://docs.cryengine.com/display/CEMANUAL/CRYENGINE+V+Manual.

Epic Games. 2018. Unreal Engine 4 for Unity developers. [En línea] 2018.

https://docs.unrealengine.com/en-us/GettingStarted/FromUnity.

Epic Games. 2018. Unreal Engine Documentation. [En línea] 2018.

https://docs.unrealengine.com/en-us/.

Forsyth, Tom. 2006. Tom Forsyth Wiki. [En línea] 2006.

http://tomforsyth1000.github.io/blog.wiki.html#%5B%5BScene%20Graphs%20-

%20just%20say%20no%5D%5D.

Khronos Group. 2018. OpenGL wiki. [En línea] 2018.

https://www.khronos.org/opengl/wiki.

Newzoo. 2018. Global games market revenues. [En línea] 2018.

https://newzoo.com/insights/articles/global-games-market-reaches-137-9-billion-in-2018-

mobile-games-take-half/.

Texterity. 2011. Game developer salary survey. [En línea] 2011.

https://www.neogaf.com/threads/game-developer-salary-survey-2011.456723/.

Unity Technologies. 2018. Unity Manual. [En línea] 2018.

https://docs.unity3d.com/Manual/index.html.

Page 140: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

140

Unity. 2018. Unity3D Official Blog. [En línea] 2018.

https://blogs.unity3d.com/es/2018/03/26/releasing-the-unity-c-source-code/.

Van Verth, James M. y Bishop, Lars M. 2008. Essential Mathematics for Games and

Interactive Applications. 2008.

West, Mick. 2007. Evolve Your Hierarchy. [En línea] 2007.

http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/.

Wikipedia. 2018. List of most expensive video games to develop. [En línea] 2018.

https://en.wikipedia.org/wiki/List_of_most_expensive_video_games_to_develop.

Page 141: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

141

15 GLOSARIO

Asset: Término usado para referirnos a un recurso en forma de archivo utilizado por un juego.

Por ejemplo, texturas, materiales, modelos 3D, sprites, secuencias de animaciones, sonidos,

música, scripts, etc.

Acelerómetro: Sensor que mide la aceleración que se le aplica. En los dispositivos móviles

se instala un acelerómetro de 3 ejes y se utiliza aprovechando la aceleración producida por la

gravedad terrestre para determinar su orientación con respecto al horizonte. El problema es

que su precisión no es muy exacta y produce mucho ruido en las lecturas. Esto se corrige en

combinación con un giroscopio.

Billboard: En un espacio 3D, es un plano orientado hacia la cámara, al cual se le aplica una

textura que normalmente representa un sprite, un texto o en general cualquier elemento 2D

que queramos que se muestre sobre la escena 3D.

Blueprints: Sistema visual de programación creado por Epic para su motor Unreal Engine

que permite desarrollar la lógica del juego sin necesidad de escribir código.

Cuaternión: Se trata de un número complejo de cuatro dimensiones. Es decir, es una

estructura matemática con una parte real y tres imaginarias. Se utiliza en los motores gráficos

y de física para aplicar rotaciones a los objetos evitando el indeseado efecto “cardán”, que

aparece al utilizar una matriz de transformación y por el cual perdemos un grado de libertad

cuando varios ejes de rotación están alineados.

Dinámica de cuerpos rígidos: La dinámica es la parte de la ciencia dentro de la física que

estudia el movimiento traslacional y rotacional de los objetos en respuesta a las fuerzas a las

Page 142: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

142

que están sometidos, como la aceleración producida por la gravedad, o un impacto con otro

objeto. Cuando decimos dinámica de cuerpos rígidos nos referimos a la dinámica de cuerpos

que nunca se van a deformar, con lo cual serán más sencillos de simular.

FlowGraph: Sistema visual de programación creado por CryTek para su motor CryEngine

que permite desarrollar la lógica del juego sin necesidad de escribir código.

Game engine: Conjunto de librerías, herramientas y otros elementos software reutilizables

encaminados a facilitar la tarea de creación y representación de un videojuego. Consta a su

vez de varios subsistemas como el de gráficos, física, colisiones, sonido, matemáticas,

inteligencia artificial, scripting, red, interfaz de usuario, etc.

Giroscopio: Sensor que mide la velocidad angular. Se utiliza en dispositivos móviles para

ayudar a detectar su orientación, pero debido a que sus lecturas son relativas, no podemos

obtener una orientación absoluta a causa del error acumulado. Este error se corrige con la

combinación de un acelerómetro.

Indie: Desarrollador de videojuegos (u otros productos) de cuyos proyectos se sabe que no

dependen ni están respaldados económicamente por una gran empresa del sector. También es

el nombre que reciben los juegos creados por éstos.

Kinemática: Mecánica que determina el movimiento de los cuerpos sin tener en consideración

su masa o las fuerzas a las que está sometido.

LOD: Siglas de Level of Detail. Un objeto 3D puede tener definidos varias mallas que

representan distintos niveles de detalle. La elección de un determinado nivel se hace en tiempo

de ejecución y depende de la proximidad del objeto con el punto de observación. Si el objeto

está muy cerca necesitará un nivel de detalle máximo, mientras que si se encuentra alejado,

bastará con el mínimo nivel de detalle.

Magnetómetro: Sensor que detecta la presencia y dirección de un campo magnético. Se

utiliza en los móviles para detectar la orientación del dispositivo con respecto al eje vertical

aprovechando el campo geomagnético de la tierra.

Page 143: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

143

Máquina virtual: Software que simula la estructura de un sistema físico, quedando éste

aislado de la máquina real donde se ejecuta. El sistema simulado puede comportarse como la

CPU de un ordenador real, con registros, pila, unidad de control, memoria, etc. y todos ellos

implementados de forma virtual mediante software. En un game engine lo utilizamos para

embeber scripts en el sistema para manejar la lógica del juego.

Matriz de transformación: Se trata de una estructura matemática, habitualmente de 4x4

elementos y utilizada para aplicar transformaciones de traslación, escala y rotación de forma

simultánea sobre los vectores que definen a los objetos en un espacio 3D.

Motor de juegos: Ver game engine.

Pantalla multitáctil: Se trata de una pantalla que permite detectar la posición de varios

dedos simultáneamente. La entrada es por tanto una lista de vectores 2D con la que se puede

determinar la intención del usuario por medio de gestos, como ampliar o reducir la pantalla

encogiendo dos dedos, o simular el click contextual del ratón haciendo un toque con dos dedos

a la vez.

PhysX: API desarrollado por Nvidia que permite utilizar hardware de aceleración gráfico que

sea compatible con éste para realizar cálculos de física en tiempo real para videojuegos.

Script: En el ámbito de los motores de los videojuegos, lo definiremos como un pequeño

programa escrito en un lenguaje de alto nivel y ejecutado por una máquina virtual que lo

interpreta sin necesidad de compilarlo previamente.

Shader: Fragmento de código que se ejecuta sobre hardware especializado de aceleración de

gráficos con el fin de personalizar la forma en que se muestran los objetos, posibilitando la

aplicación en tiempo real de todo tipo de efectos como sombras, texturas, iluminación, etc.

Sprite: mapa de bits que representa los colores de cada pixel de un elemento gráfico 2D. Se

dibuja en pantalla para representar un personaje o cualquier otro elemento gráfico de un

videojuego.

Textura: Imagen que se aplica sobre la superficie de un objeto 2D o 3D

Page 144: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

144

Triple A: Rango y término por el cual se conoce a los juegos de última generación que

emplean los últimos avances en tecnología y que han sido desarrollados por grandes empresas

de desarrollo invirtiendo una cantidad considerable de tiempo y recursos humanos.

Page 145: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

145

ANEXO I: LIBRERÍA DE MATEMÁTICAS

Para realizar los cálculos que requiere la representación gráfica de un objeto en un espacio

3D, o para el cálculo de las leyes de dinámica de cuerpos rígidos, si disponemos de la suficiente

paciencia, podríamos emplear simplemente álgebra lineal básica, aunque afortunadamente,

con los años han aparecido métodos de cálculo más avanzados que resuelven este tipo de

cuestiones mucho más fácilmente, como son las operaciones algebraicas sobre estructuras de

tipo vector, matrices o cuaterniones.

Este combo de estructuras es la base para cualquier librería de matemáticas de un game

engine. De hecho, el hardware de aceleración de gráficos se ha ido optimizando con el tiempo

para trabajar precisamente con este tipo de cálculos. Incluso, las CPU modernas están también

construidas para optimizar el cálculo vectorial por medio de las extensiones SIMD.

La mayor parte del tiempo de cómputo dentro de los subsistemas de gráficos y física, está

dedicado a cálculos sobre vectores, matrices y cuaterniones. De ahí la importancia de disponer

de una librería perfectamente optimizada y un buen conocimiento sobre la materia para poder

aprovecharla al máximo.

En los siguientes subapartados hablaremos de estas estructuras, su propósito y las

operaciones básicas que se pueden realizar con ellas.

Vectores Un vector es una entidad en un espacio 2D o 3D que posee valores de longitud (o magnitud)

dirección y sentido, pero sin definir una posición. En computación gráfica los vectores se

Page 146: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

146

utilizan con dos fines: para representar la dirección de un objeto o para determinar un cambio,

por ejemplo, de velocidad o aceleración de este objeto.

Gráficamente lo representamos con una flecha, tal como

se muestra en la figura Figura 0.1. Vectores. Lo que nos

interesa es su dirección, representada por la orientación de

la flecha, el sentido que lo indica la punta de ésta y la

magnitud, ilustrada mediante su longitud. En otras

palabras, dos vectores son iguales si lo son su dirección y

longitud. El origen es importante, ya que es su punto de

aplicación, pero el vector en sí no contiene esta información,

y para poder almacenarla necesitaremos el apoyo de una segunda estructura con las

coordenadas. Dicho de otra forma: dos vectores son iguales si lo son su dirección, sentido y

longitud.

Un vector se representa algebraicamente mediante varios elementos entre paréntesis

separados por coma. Utilizaremos al menos un elemento por dimensión, de forma que, por

ejemplo, para tres dimensiones escribiríamos el vector v así: 𝑣 = (𝑥, 𝑦, 𝑧). No obstante, en la

práctica veremos que se suele usar un cuarto elemento, la w, que se utiliza para facilitar los

cálculos en combinación con matrices. Esto se entenderá mejor en el siguiente apartado. Ahora

pasamos a explicar algunas de las operaciones que pueden realizarse con los vectores.

Propiedades de los vectores

Al igual que los cálculos con cualquier número que conocemos, los vectores tienen ciertas

propiedades algebraicas de las cuales debemos conocer al menos las principales:

1. Conmutativa de la suma: 𝑣 + 𝑤 = 𝑤 + 𝑣

2. Asociativa de la suma: 𝑢 + (𝑣 + 𝑤) = (𝑢 + 𝑣) + 𝑤

3. Elemento neutro de la suma: 𝑣 + 0 = 𝑣

4. Elemento opuesto de la suma: Por cada v, hay un vector -v tal que v+(-v) = 0.

5. Asociativa del producto: (𝑎𝑏)𝑣 = 𝑎(𝑏𝑣)

6. Distributiva: (𝑎 + 𝑏)𝑣 = 𝑎𝑣 + 𝑏𝑣

Figura 0.1. Vectores

Page 147: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

147

7. Elemento neutro del producto: 𝑣 · 1 = 𝑣

Magnitud de un vector

La magnitud de un vector v se representa con ||v|| y gráficamente, indica la longitud de

la flecha que representa el vector. Se calcula utilizando el teorema de Pitágoras para la

hipotenusa. Por ejemplo, si tenemos un vector (4,3), consideremos por un momento que el

inicio de la flecha está en el origen de coordenadas. Ahora usando los ejes de coordenadas

podemos formar un triángulo rectángulo con el que podemos calcular la hipotenusa m que

forma el vector tal como se ve en la figura.

Por tanto, en este ejemplo, el cuadrado de la magnitud

del vector según el teorema de Pitágoras sería: 𝑚 = 𝑥 +

𝑦 . De igual modo, para tres dimensiones sería 𝑚 = 𝑥 +

𝑦 + 𝑧 . Y más genéricamente, para los elementos v

pertenecientes al vector en un número d de dimensiones hallaríamos la magnitud con la

siguiente ecuación:

‖𝑣‖ = 𝑣

Normalización de un vector

Normalizar un vector implica convertirlo a unitario, es decir un vector cuya magnitud es

la unidad (1). Esta normalización descarta la información de longitud, pero mantiene su

dirección y sentido. Es muy utilizado para multitud de cálculos que lo requieren de esta forma.

Sabiendo que 𝑥 · = 1 , para normalizar un

vector multiplicaremos cada uno de sus elementos

por la inversa de su magnitud. En el ejemplo

anterior, ‖𝑣‖ = √4 + 3 = 5, por tanto, dividiendo

cada elemento por 5, el vector resultante tendría

Figura 0.2. Magnitud de vector

Page 148: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

148

longitud unitaria: = 1 y se representaría tal como se ve en la figura marcado en rojo.

Producto escalar

Representado con el operador punto (·), y también conocido como producto Euclídeo

interno o en inglés como dot product, el producto escalar entre dos vectores 𝑣 y 𝑤 que forman

un ángulo 𝜃 entre ellos, da como resultado un escalar y se define por la expresión:

𝑣 · 𝑤 = ‖𝑣‖‖𝑤‖ cos(𝜃)

Si el ángulo que forman los vectores está completamente cerrado, el producto escalar es 1

ya que cos(0) = 1. Si está completamente abierto (180º) será 0, ya que cos ( )=0. Por tanto,

si los vectores están normalizados, el escalar resultante será un número comprendido entre 0

y 1 que indicará el factor de alineación entre los dos vectores. Es decir, si los vectores apuntan

a la misma dirección y sentido, el producto escalar será el valor máximo (1), mientras que si

miran en dirección y sentido completamente opuesto, será el mínimo (0).

Computacionalmente, el cálculo de este producto escalar puede simplificarse utilizando

una propiedad de la Ley de Cosenos, la cual nos permite en este caso evitar calcularlo, ya que

podemos utilizar la siguiente expresión que es equivalente a la anterior:

𝑣 · 𝑤 = 𝑣 𝑤 | 𝑑 = 𝑛ú𝑚𝑒𝑟𝑜 𝑑𝑒 𝑑𝑖𝑚𝑒𝑛𝑠𝑖𝑜𝑛𝑒𝑠

𝑃𝑎𝑟𝑎 𝑡𝑟𝑒𝑠 𝑑𝑖𝑚𝑒𝑛𝑠𝑖𝑜𝑛𝑒𝑠: 𝑣 · 𝑤 = 𝑣 𝑤 + 𝑣 𝑤 + 𝑣 𝑤

Producto vectorial

Si tenemos dos vectores v y w, podemos calcular un vector u que es ortogonal al plano

formado por los dos primeros. Es decir, un vector cuya dirección corresponde a la orientación

de dicho plano. El producto vectorial entre dos vectores se representa con el operador aspa

(×), y obedece a la siguiente expresión:

‖𝑣 × 𝑤‖ = ‖𝑣‖‖𝑤‖ sen(𝜃)

Figura 0.3. Vector normalizado

Page 149: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

149

Como se puede deducir este producto no tiene razón de ser en 2 dimensiones, ya que entre

dos vectores 2D no puede haber un nuevo vector ortogonal, pues esto implicaría una nueva

dimensión.

Otro aspecto importante es que puede haber dos resultados

válidos, y es que la misma dirección puede tener dos sentidos con

respecto a un plano en un espacio tridimensional. La elección de

uno u otro dependerá de si estamos utilizando un sistema de

mano izquierda o de mano derecha. Para aplicar la regla de la

mano derecha, colocando esta mano de forma que el dedo índice

apunte en la dirección del vector v, y el dedo corazón apunte en

la dirección de w, la dirección del vector u será la de nuestro dedo

pulgar. Del mismo modo, la regla de mano izquierda funciona

igual pero utilizando esta mano (el vector resultante tendrá sentido opuesto).

Al igual que con el producto vectorial, aquí también podemos evitar el cálculo de la función

trigonométrica (en este caso el seno). Para vectores en espacio tridimensional resolveríamos

cada componente con las siguientes operaciones:

𝑢 = 𝑣 × 𝑤

𝑢 = 𝑣 𝑤 − 𝑣 𝑤

𝑢 = 𝑣 𝑤 − 𝑣 𝑤

𝑢 = 𝑣 𝑤 − 𝑣 𝑤

Dados los vectores u, v, w y el escalar a, se aplican las siguientes reglas algebraicas (nótese

que el producto vectorial no es conmutativo):

1. 𝑣 × 𝑤 = −𝑤 × 𝑣

2. 𝑢 × (𝑣 + 𝑤) = (𝑢 × 𝑣) + (𝑢 × 𝑤)

3. (𝑢 + 𝑣) × 𝑤 = (𝑢 × 𝑤) + (𝑣 × 𝑤)

4. 𝑎(𝑣 × 𝑤) = (𝑎𝑣) × 𝑤 = 𝑣 × (𝑎𝑤)

5. 𝑣 × 0 = 0 × 𝑣 = 0

6. 𝑣 × 𝑣 = 0

Figura 0.4. Regla de mano derecha

Page 150: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

150

Puntos Un punto se define con el mismo tipo de estructura que un vector. Sin embargo, entre

estos dos hay ciertas diferencias de concepto que van a determinar el tipo de operaciones que

podemos hacer con ellos.

Un punto representa una localización en el espacio, mientras que un vector, como ya hemos

visto contiene sólo la información sobre dirección, sentido y magnitud. Por tanto, mientras

que este último puede servirnos para determinar el factor de cambio de un objeto con respecto

a un estado anterior, con el punto podemos determinar dicho estado, por ejemplo, su punto

de origen desde el que se desplaza.

Para el ejemplo del cambio de localización, la magnitud del vector podría representar la

velocidad. O bien, para un cambio de velocidad, dicha magnitud representaría la aceleración.

De forma análoga un punto puede localizar un instante concreto en el tiempo, mientras que

un vector puede determinar una duración.

A continuación, citamos las distintas operaciones que pueden realizarse entre puntos y

vectores:

1. La suma de un punto (por ejemplo, una localización) y un vector da como resultado

un punto. Es decir, una nueva localización que depende del punto de origen y el

desplazamiento definido en el vector.

2. La resta entre dos puntos determina:

a. En espacio: una distancia

b. En tiempo: una duración

3. La suma de un vector con otro vector:

a. En espacio: un desplazamiento compuesto

b. En tiempo: una duración compuesta

4. La suma entre dos puntos no tiene sentido ya que sumar dos orígenes tanto en tiempo

como en espacio no da como resultado ninguna información útil.

Page 151: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

151

Por último, existe otra diferencia que aparece a la hora de almacenar la información. Tal

como veremos en el apartado de transformaciones con matrices, cuando trabajamos con

vectores o puntos 3D utilizamos 4 elementos (y no 3). Además de las coordenadas

𝑥, 𝑦, 𝑧 cartesianas, añadimos un nuevo componente al que llamamos 𝑤.

Este componente será 0, por ejemplo (x,y,z,0) si la estructura es un vector, o será 1 si se

trata de un punto. Por ejemplo (x,y,z,1). Esto es así para evitar que pueda afectar a un vector

cualquier operación de traslación, ya que como hemos visto esto sería erróneo (un punto más

sumado a un vector no puede dar un vector como resultado). Por ejemplo, no podemos

trasladar un eje de rotación, o una normal. El resultado simplemente no tendría sentido.

Poniendo el componente w a 0 o 1 en función de si se trata de un vector o de un punto

respectivamente, podemos tratar a todos los miembros de un objeto 3D por igual utilizando

la misma matriz de transformación. Todo esto se verá más claramente en el correspondiente

apartado de transformaciones con matrices.

Matrices Una matriz es una estructura bidimensional de números ordenados en m filas y n columnas,

de la cual se dice que es una matriz de 𝑚 × 𝑛 elementos. La matríz se representa con una letra

mayúscula (Por ejemplo, A, B, C). Por notación, un elemento de la matriz se denota con la

misma letra de la matriz en minúscula y se identifica unívocamente por sus coordenadas en el

subíndice, por ejemplo 𝑒 , , donde i es el número de fila y j es el número de columna numeradas

comenzando por 0.

Los siguientes, son ejemplos de matrices:

𝐴 =1 0 00 1 00 0 1

; 𝐵 =

5 24 1

1 45 3

3 08 3

7 21 9

En computación gráfica se hace un uso intensivo de las matrices para aplicar

transformaciones lineales de forma eficiente ya que permiten aplicar varias transformaciones

en una sola operación.

Page 152: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

152

Matriz identidad

Una matriz con todos sus elementos a 0 excepto en su diagonal donde son 1 se conoce

como matriz identidad (representada con la letra mayúscula I). Se trata del elemento neutro

con respecto a la multiplicación. Es decir, toda matriz M multiplicada por la identidad da

como resultado la propia matriz M.

𝐼 =

1 00 1

⋯ 0… 0

⋮ ⋮0 0

⋱ ⋮… 1

Matriz traspuesta

La traspuesta de una matriz 𝐴 se denota por 𝐴 y es aquella cuyos elementos de A por

columnas están intercambiados por los elementos de A por filas. Por ejemplo:

𝐴 =1 2 34 5 6

; 𝐴 =1 42 53 6

Suma de matrices y multiplicación escalar

Podemos sumar dos matrices que tengan el mismo número de filas m y columnas n. El

resultado será también una matriz de 𝑚 × 𝑛 . Para realizar la operación 𝑆 = 𝐴 + 𝐵 ,

simplemente sumamos cada elemento de la matriz A con el correspondiente elemento de B de

la misma posición. Es decir:

𝑠 , = 𝑎 , + 𝑏 ,

Para multiplicar una matriz A por un escalar s, (operación 𝑃 = 𝑠𝐴) sólo hay que

multiplicar cada elemento de A por dicho escalar:

𝑝 , = 𝑠 · 𝑎 ,

Las propiedades de la suma y la multiplicación escalar son:

1. 𝐴 + 𝐵 = 𝐵 + 𝐴

2. 𝐴 + (𝐵 + 𝐶) = (𝐴 + 𝐵) + 𝐶

3. 𝐴 + 0 = 𝐴

Page 153: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

153

4. 𝐴 + (−𝐴) = 0

5. 𝑎(𝐴 + 𝐵) = 𝑎𝐴 + 𝑎𝐵

6. 𝑎(𝑏𝐴) = (𝑎𝑏)𝐴

7. (𝑎 + 𝑏)𝐴 = 𝑎𝐴 + 𝑏𝐴

8. 1𝐴 = 𝐴

Producto de matrices

Es posible multiplicar una matriz A de 𝑚 × 𝑛 elementos por otra B de 𝑝 × 𝑞 elementos si

y sólo si 𝑛 es igual a 𝑝. La matriz resultante será de 𝑚 × 𝑞 elementos.

Si queremos calcular 𝑃 = 𝐴 𝐵, por cada elemento 𝑝 , de la matriz resultado sólo tenemos

que hacer un producto escalar de la siguiente forma:

𝑝 , = 𝑐𝑜𝑙 (𝐴) · 𝑓𝑖𝑙 (𝐵)

Donde 𝑐𝑜𝑙 (𝐴) sería un vector formado por los elementos de la columna i de A, y 𝑓𝑖𝑙 (𝐵)

es un vector formado por los elementos de la fila j de B. En la siguiente figura se muestra

visualmente cómo se seleccionan los pares de vectores que participan en estos productos

escalares para formar la matriz resultado.

También podemos implementarlo de forma algorítmica. Sean 𝐴 y 𝐵 dos matrices con el

tamaño adecuado para poder calcular su producto. Un algoritmo general para resolver 𝑃 =

𝐴 𝐵 sería el que mostramos a continuación:

para i = 0 hasta m-1 para j = 0 hasta p-1 suma = 0

Figura 0.5. Multiplicación de matrices

Page 154: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

154

para k = 0 hasta n-1 suma = suma + Aik * Bkj fin para Pij = suma fin para fin para

El producto de matrices tiene las siguientes propiedades algebráicas (Nótese que no es

conmutativo):

1. 𝐴𝐵 ≠ 𝐵𝐴

2. 𝐴(𝐵𝐶) = (𝐴𝐵)𝐶

3. 𝑎(𝐵𝐶) = (𝑎𝐵)𝐶

4. 𝐴(𝐵 + 𝐶) = 𝐴𝐵 + 𝐴𝐶

5. (𝐴 + 𝐵)𝐶 = 𝐴𝐶 + 𝐵𝐶

6. (𝐴𝐵) = 𝐵 𝐴

Producto de una matriz por un vector

Como sabemos que para multiplicar dos matrices el número de columnas de la primera

debe ser igual al número de filas de la segunda, podemos considerar un vector como una matriz

de 1 × 𝑛 elementos (vector fila) o bien de 𝑛 × 1 elementos (vector columna). Dependiendo de

nuestra elección la multiplicación será por la izquierda o por la derecha respectivamente.

Producto por la izquierda (vector fila):

𝑣 = [0 1 4]6 2 13 5 90 7 8

Producto por la derecha (vector columna):

𝑣 =6 2 13 5 90 7 8

014

Transformaciones con matrices

Como ya hemos mencionado, una matriz se utiliza principalmente para aplicar

transformaciones lineales sobre los vectores. Ejemplos de estas transformaciones son la escala,

rotación o traslación, que veremos en los siguientes apartados. Una vez tenemos definida la

Page 155: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

155

transformación en una matriz, si multiplicamos ésta por un vector, nos dará como resultado

un nuevo vector con la transformación aplicada.

En computación gráfica es muy frecuente aplicar las mismas transformaciones a varios

vectores. Esto se utiliza por ejemplo para aplicar traslación, rotación y escala sobre cada uno

de los vértices de un objeto 3D, los cuales necesitarán las mismas transformaciones para no

deformar el objeto.

Lo que le da la potencia al cálculo con matrices es la posibilidad de acumular varias

transformaciones en una sola para aplicarlas a un vector en una sola operación. Por ejemplo,

si tenemos que aplicar 10 transformaciones 𝑇 , 𝑇 … 𝑇 a un vector v, podemos multiplicar

todas las matrices de transformación entre sí obteniendo una matriz M, de forma que,

multiplicando v por M obtendremos un vector resultado que tendrá aplicadas todas las

transformaciones.

Para transformaciones en un espacio 2D necesitaremos matrices de 3 × 3 elementos,

mientras que para 3D deberán ser de 4 × 4. Esto se verá más claramente en los siguientes

apartados.

Matriz de traslación

La traslación es una de las operaciones más sencillas. Si no utilizáramos matrices,

simplemente consistiría en sumar a cada elemento del vector la distancia que queremos

trasladarlo en sus correspondientes ejes cartesianos.

Para obtener este mismo efecto en un espacio 3D usando matrices, partiendo de la matriz

identidad, y un vector de traslación t, pondremos en la última columna la distancia a trasladar

las coordenadas x,y,z de este vector:

𝑇 =

1 00 1

0 𝑡0 𝑡

0 00 0

1 𝑡0 1

Con esto, para trasladar un vector v utilizando la matriz T, resolvemos el producto 𝑇 · 𝑣:

Page 156: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

156

1 00 1

0 𝑡0 𝑡

0 00 0

1 𝑡0 1

·

𝑣𝑣𝑣𝑣

=

1𝑣 + 0 + 0 + 𝑡 𝑣0 + 1𝑣 + 0 + 𝑡 𝑣

0 + 0 + 1𝑣 + 𝑡 𝑣0 + 0 + 0 + 1𝑣

=

𝑣 + 𝑡 𝑣𝑣 + 𝑡 𝑣

𝑣 + 𝑡 𝑣𝑣

El componente 𝑣 en este caso estará determinando el factor de influencia de la

transformación. Para aplicar la transformación exactamente como la da el vector de traslación,

el 𝑣 debería ser 1. Como se puede ver, si utilizáramos matrices de 3x3 y vectores de 3

elementos para un espacio 3D no podríamos aplicar traslaciones, por tanto este cuarto

elemento, la 𝑤, es necesario.

Matriz de escala

La escala funciona de forma similar a la traslación. Aplicada sobre los vértices de un objeto

3D lo que hace es expandir sus posiciones con respecto al origen, haciendo efectivamente un

escalado de este objeto.

Para aplicar una escala definida por el vector 𝑠 a nuestro vector 𝑣 construimos la siguiente

matriz:

𝑆 =

𝑠 00 𝑠

0 00 0

0 00 0

𝑠 00 1

Ahora podemos aplicarlo resolviendo el producto 𝑆 · 𝑣 tal como hemos hecho antes para la

traslación:

𝑠 00 𝑠

0 00 0

0 00 0

𝑠 00 1

·

𝑣𝑣𝑣𝑣

=

𝑠 𝑣 + 0 + 0 + 00 + 𝑠 𝑣 + 0 + 0

0 + 0 + 𝑠 𝑣 + 00 + 0 + 0 + 1𝑣

=

𝑠 𝑣𝑠 𝑣𝑠 𝑣𝑣

Vemos que siguiendo la regla de multiplicación de matrices obtenemos un resultado que

es la multiplicación de los respectivos elementos de los vectores 𝑠 y 𝑣 tal como esperábamos.

Matriz de rotación

Con la ayuda de las funciones trigonométricas seno y coseno, podemos crear matrices de

rotación para cada uno de los ejes cartesianos dado un ángulo 𝜃. La rotación se hará en sentido

Page 157: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

157

antihorario en un sistema de coordenadas de mano derecha. Estas matrices son las que

mostramos a continuación. Nótese que la cuarta columna y fila las mantenemos para poder

combinar estas matrices con otras matrices de transformación, pero en realidad no se utilizan

para la rotación:

𝑅 (𝜃) =

1 00 𝑐𝑜𝑠(𝜃)

0 0−𝑠𝑒𝑛(𝜃) 0

0 𝑠𝑒𝑛(𝜃)0 0

cos(𝜃) 00 1

𝑅 (𝜃) =

cos (𝜃) 0 0 1

𝑠𝑒𝑛(𝜃) 00 0

−𝑠𝑒𝑛(𝜃) 00 0

cos(𝜃) 00 1

𝑅 (𝜃) =

cos (𝜃) −𝑠𝑒𝑛(𝜃)

𝑠𝑒𝑛(𝜃) cos(𝜃)0 00 0

0 00 0

1 00 1

Cada una de las rotaciones sobre los

ejes cartesianos x,y,z recibe el nombre en

inglés de roll, pitch y yaw respectivamente.

O bien en español, rotación longitudinal,

trasversal y vertical, tal como se muestra

en la figura.

Como ya se ha comentado, al igual que

cualquier otra transformación, podemos

combinar varias de estas rotaciones

multiplicando estas matrices para formar

una matriz de rotación compuesta. Y también, como sucede al combinar con otras

transformaciones, el orden de la multiplicación determina distintos resultados. Por ejemplo,

como puede comprobarse experimentalmente, no es lo mismo girar un objeto 30 grados sobre

el eje x, y luego 40 grados sobre el eje y, que hacerlo en orden inverso. La orientación resultante

no será la misma. Del mismo modo obtendremos distintos resultados, por ejemplo, entre

Figura 0.6. Ejes de rotación

Page 158: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

158

traslación y rotación según su orden de aplicación. No significa que un determinado orden sea

incorrecto, sino que irá en función de nuestras necesidades.

También conocemos una matriz de rotación especial que nos permite rotar un objeto en

un ángulo 𝜃 sobre un eje arbitrario determinado por el vector u y que definimos así:

𝑅(𝑢, 𝜃) =

=

⎣⎢⎢⎢⎡

cos 𝜃 + 𝑢 (1 − cos 𝜃) 𝑢 𝑢 (1 − cos 𝜃) − 𝑢 sen 𝜃

𝑢 𝑢 (1 − cos 𝜃) + 𝑢 sen 𝜃 cos 𝜃 + 𝑢 (1 − cos 𝜃)

𝑢 𝑢 (1 − cos 𝜃) + 𝑢 sen 𝜃 0

𝑢 𝑢 (1 − cos 𝜃) − 𝑢 sen 𝜃 0

𝑢 𝑢 (1 − cos 𝜃) − 𝑢 sen 𝜃 𝑢 𝑢 (1 − cos 𝜃) + 𝑢 sen 𝜃

0 0 cos 𝜃 + 𝑢 (1 − cos 𝜃) 0

0 1⎦⎥⎥⎥⎤

Cofactor de un elemento de la matriz

El cofactor del elemento 𝑎 de la matriz A se calcula con la siguiente expresión:

𝐶 , = (−1) 𝐴 ,

Donde 𝐴 , es la submatriz resultante de eliminar la fila i y la columna j de la matriz A.

Determinante de una matriz

El determinante de una matriz cuadrada A de cualquier tamaño es un escalar denotado

por |𝐴| y podemos calcularlo eligiendo cualquier fila i de la matriz y evaluando la siguiente

expresión:

|𝐴| = 𝑎 , 𝐶 ,

Donde 𝐶 , es el cofactor 𝑎 , . Por tanto, esta expresión es recursiva, ya que el método para

hallar el cofactor que hemos visto requiere a su vez del determinante. Pero afortunadamente,

si tenemos una matriz de tamaño conocido (lo más habitual son matrices de 3x3 o 4x4),

podemos escribir todo el código necesario sin tener que hacer uso de la recursividad.

Matriz adjunta

La matriz adjunta de A se denota por adj(𝐴) y es el resultado de sustituir cada uno de los

elementos 𝑎 de A por su correspondiente cofactor.

Page 159: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

159

Matriz inversa

Una matriz inversa de A se denota por 𝐴 y cumple que 𝐴 · 𝐴 = 𝐴 · 𝐴 = 𝐼. Es decir,

toda matriz multiplicada por su inversa da como resultado la matriz identidad.

La operación de división no existe para las matrices. Por tanto, si queremos dividir una

matriz A por otra matriz B lo haremos multiplicando por la inversa de B, que denotamos

como 𝐵 .

Para calcular la inversa de una matriz, multiplicamos la traspuesta de su adjunta por el

inverso de su determinante tal como muestra la siguiente expresión:

𝐴 =1

|𝐴|adj(𝐴)

Uno de los principales usos que se le da a esta matriz en computación gráfica es para

calcular la matriz transformación para la “vista” a partir de la cámara (punto de observación)

de una escena 3D.

Propiedades de la matriz inversa:

1. 𝐴 · 𝐴 = 𝐼

2. 𝐴 · 𝐴 = 𝐼

3. (𝐴 ) = 𝐴

4. No siempre existe una inversa para una matriz

Cuaterniones Desarrollado por Sir William Hamilton, e introducido posteriormente por Ken Shoemake

en 1980 para la computación gráfica, un cuaternión es un número complejo de cuatro

dimensiones donde tenemos una parte real y tres imaginarias. Por ejemplo:

𝑞 = 𝑤 + 𝑥𝑖 + 𝑦𝑗 + 𝑧𝑘

También se suele representar como un vector, obviando los coeficientes imaginarios i,j,k:

Page 160: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

160

𝑞 = (𝑤, 𝑥, 𝑦, 𝑧)

El cuaternión, utilizado como alternativa a las matrices de rotación, permite evitar el

indeseado efecto “gimbal lock”, por el cual perdemos un grado de libertad cuando varios ejes

de rotación están alineados. Este efecto se puede apreciar mejor en la figura

El gimbal es un conjunto de aros concéntricos que permiten sostener una figura en el

centro de forma que podemos orientarla con respecto a tres ejes. Estando en la posición A,

tenemos 100% de libertad para cualquier giro. Sin embargo, en la posición B, con los 3 ejes

alineados, si giramos el aro exterior (verde) o el interior (rojo) el efecto es el mismo, con lo

cual hemos perdido un grado de libertad. Este es precisamente el problema que resuelve el

cuaternión.

Cuaternión a partir de un eje de rotación y un ángulo

Dado un eje de rotación definido por el vector 𝑣 , y un ángulo 𝜃 , el cuaternión que

representa esta transformación se calcula de la siguiente forma:

𝑞 = (cos , 𝑣 sen , 𝑣 sen , 𝑣 sen )

Además, podemos rotar una orientación inicial representada en un cuaternión,

multiplicándolo por otro que contenga la variación a aplicar (ver Operaciones con

cuaterniones).

Figura 0.7. Demostración del efecto gimbal lock

Page 161: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

161

Operaciones con cuaterniones

Podemos hacer la suma de dos cuaterniones término a término, igual que lo haríamos con

un vector o con un número complejo cualquiera.

𝑎 + 𝑏 = (𝑎 + 𝑏 ) + (𝑎 + 𝑏 )𝑖 + 𝑎 + 𝑏 𝑗 + (𝑎 + 𝑏 )𝑘

El producto se realiza de la siguiente forma (Nótese que al igual que las matrices, no es

conmutativo):

𝑎𝑏 = 𝑎 𝑏 − 𝑎 𝑏 − 𝑎 𝑏 − 𝑎 𝑏 + 𝑎 𝑏 + 𝑎 𝑏 + 𝑎 𝑏 − 𝑎 𝑏 𝑖

+ 𝑎 𝑏 − 𝑎 𝑏 + 𝑎 𝑏 + 𝑎 𝑏 𝑗 + 𝑎 𝑏 + 𝑎 𝑏 − 𝑎 𝑏 + 𝑎 𝑏 𝑘

Page 162: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM
Page 163: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

163

ANEXO II. SISTEMA DE GRÁFICOS

El sistema de gráficos se encarga de dibujar en pantalla los elementos de una escena que

se encuentra en un espacio 2D o 3D.

Debe estar perfectamente optimizado ya que es una parte crítica del programa que se

ejecuta varias de veces por segundo. La mayor parte de la eficiencia dependerá de lo

optimizada que esté la librería de matemáticas de la que depende (ver apartado ¡Error! No

se encuentra el origen de la referencia.). En este apartado haremos una introducción a

los conceptos fundamentales para poder entender el funcionamiento de un motor de gráficos

3D.

Pipeline de procesamiento de gráficos En cualquier aplicación que necesite dibujar gráficos en tiempo real, como puede ser un

videojuego, vamos a realizar una serie de pasos para cada fotograma cuya salida dependerá

del paso anterior. Por tanto, se deben realizar en un orden concreto. De ahí su nombre de

pipeline (tubería) de renderizado.

Los pasos más importantes del API de OpenGL se muestran en el siguiente diagrama

(Khronos Group, 2018), donde los marcados en naranja son opcionales:

Page 164: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

164

Todos los pasos mostrados (excepto el de rasterización) son programables mediante

shaders. Los shaders son programas que se ejecutan en un hardware de aceleración de gráficos

compatible.

Vertex shader

La lista de vértices que se pasa al vertex shader puede contener listas con información

sobre posición, color, normales, etc. Cuando se obtiene la lista de vértices, el shader

típicamente se programa para aplicar las transformaciones para cada uno utilizando la matriz

MVP (ver apartado 0 Matriz Modelo-Vista-Proyección) y también puede aplicar efectos de

iluminación de tipo vértice, como el que se consigue con la técnica Gouraud.

Teselado

La salida de vértices se pasa opcionalmente al shader de teselado que puede, a partir de

parches definidos de las primitivas que forman estos vértices, crear nuevas primitivas más

pequeñas subdividiendo las originales.

Lista ordenada de vértices (VAO, VBO) con sus atributos

Vertex shader

Teselado

Geometry shader

Rasterización

Fragment shader

Vértices transformados

Vértices + primitivas adicionales

Secuencia de primitivas

Info color + coordenadas destino

Píxeles finales en pantalla

Page 165: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

165

Geometry shader

Un geometry shader toma como entrada una primitiva y puede dar como salida cero o

varias primitivas. Se suele utilizar para definir algoritmos de LOD (Level-of-detail) que dan

como salida geometrías cuya complejidad depende de la proximidad del observador.

Rasterización

En este paso, teniendo toda la información vectorial de la escena, se hace corresponder

cada fragmento de esta a pixeles discretos donde se situarán éstos.

Vertex shader

Por último, por cada pixel, aplicaremos el algoritmo deseado para determinar el color final

en función del efecto que queramos aplicar. En esta fase se puede aplicar cualquier efecto que

necesite ser procesado pixel a pixel, como sombreado Phong o distintos efectos de post-

procesado como motion-blur, DOF (Depth-of-field), scan-lines, etc.

La cámara La cámara es el punto de observación para una escena 3D y se define por medio de una

matriz C de transformación que contiene simplemente un punto (para la localización) y tres

vectores (para la orientación).

Una operación típica que se realiza

para calcular la orientación de la cámara

es la función lookAt, que consiste en hacer

que se oriente hacia un punto arbitrario de

la escena. Al punto de origen de la cámara

lo llamamos p, y al punto al que queremos

mirar lo llamamos t.

Lo que queremos construir es una

matriz C que indique la posición y

orientación de la cámara. Para la Figura 0.1. Matriz cámara

Page 166: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

166

orientación necesitamos los vectores arriba, derecha y frente, y para la posición p, un punto

de 3 coordenadas. La distribución en la matriz de esta información será la siguiente:

𝐶 =

𝑑𝑒𝑟𝑒𝑐ℎ𝑎 𝑎𝑟𝑟𝑖𝑏𝑎𝑑𝑒𝑟𝑒𝑐ℎ𝑎 𝑎𝑟𝑟𝑖𝑏𝑎

𝑓𝑟𝑒𝑛𝑡𝑒 𝑝𝑓𝑟𝑒𝑛𝑡𝑒 𝑝

𝑑𝑒𝑟𝑒𝑐ℎ𝑎 𝑎𝑟𝑟𝑖𝑏𝑎0 0

𝑓𝑟𝑒𝑛𝑡𝑒 𝑝0 1

El vector frente es sencillo de calcular. Simplemente hallamos la normal de la diferencia

entre t y p:

𝑓𝑟𝑒𝑛𝑡𝑒 = 𝑛𝑜𝑟𝑚𝑎𝑙(𝑡 − 𝑝)

Ahora ya tenemos la dirección hacia la que apuntar, pero sólo con esto aún no sabemos lo

que es arriba y abajo. Sin ninguna referencia todavía, tendremos que definir temporalmente

un vector que nos permita crear un plano con el de frente de tal forma que su normal apunte

justo a la derecha. Esto nos lo garantiza el vector unitario 𝑦: (0,1,0), ya que podemos obtener

el vector derecha mediante el siguiente producto vectorial:

𝑑𝑒𝑟𝑒𝑐ℎ𝑎 = 𝑦 × 𝑓𝑟𝑒𝑛𝑡𝑒

Finalmente, para saber cuál es la dirección real hacia arriba de la cámara sólo nos queda

calcular el producto vectorial entre frente y derecha:

𝑎𝑟𝑟𝑖𝑏𝑎 = 𝑓𝑟𝑒𝑛𝑡𝑒 × 𝑑𝑒𝑟𝑒𝑐ℎ𝑎

Matriz Modelo-Vista-Proyección En un espacio 3D trabajamos constantemente con conjuntos de puntos (vértices) que

forman objetos más complejos en la escena. Sin embargo, la pantalla que nos permite

visualizarlos es un plano de dos dimensiones. Además, debemos poder determinar en qué

posición dibujar cada objeto en función de la posición del observador virtual en la escena.

Todos estos problemas se resuelven utilizando una matriz compuesta de 3 transformaciones

que conocemos como matriz MVP (o modelo-vista-proyección).

Page 167: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

167

Matriz modelo

Cada objeto individual que podemos representar está

definido por un conjunto de puntos cuya posición es relativa

al origen de coordenadas propio de dicho objeto. La matriz

modelo (M) determina cómo se deben situar estos puntos

desde el espacio modelo (objeto) al espacio mundo (escena).

Esta matriz está compuesta a su vez de las típicas

transformaciones traslación, rotación, escala.

Como vemos en la figura, el origen de coordenadas relativo

para el objeto seguirá siendo siempre el original, y cualquier

modificación que hagamos sobre sus puntos individuales

afectará en relación a este espacio.

Matriz vista

Una parte fundamental de la visualización de una escena 3D es saber cómo serán vistos

los objetos (su localización) relativos al punto de observación, es decir, desde la cámara de la

escena.

Para ello, lo que hacemos es aplicar una transformación a cada uno de los objetos de la

escena para que se alineen de forma adecuada con la cámara. Esto significa transformarlos

desde el espacio “mundo” al espacio “vista”.

Esta transformación se almacena en la matriz vista (V) y se calcula mediante la inversa

de la matriz de la cámara (ver el apartado 0 La cámara).

𝑉 = 𝐶

Figura 0.2. Transformación con la matriz modelo

Page 168: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

168

Matriz proyección

Para poder simular la forma en que los humanos vemos un espacio tridimensional sobre

una pantalla lo que hacemos es calcular la proyección de cada punto a dibujar sobre el plano

imaginario formado por dicha pantalla.

Siendo c el punto de origen de la cámara,

cada punto v a proyectar dará como

resultado un punto p sobre el espacio

proyectado tal como se muestra en la figura.

Además, establecemos una distancia n (near)

que indica el punto de corte más cercano de

nuestra área visible (y es donde se proyectará

todo), y una distancia f (far) que indica el

punto de corte más lejano a partir del cual no veremos nada.

Como vamos a proyectar sobre un espacio 2D que puede no ser cuadrado, sino que

típicamente podrá ser panorámico 16:9, o tal vez 4:3, deberemos calcular un factor de

corrección para esta relación de aspecto 𝑎. Esto podemos calcularlo a partir de las dimensiones

de la pantalla, siendo w el ancho y h el alto: 𝑎 =

Las coordenadas de p se calculan de la siguiente forma:

𝑝 =𝑛𝑣

−𝑎𝑣 ; 𝑝 =

𝑛𝑣

−𝑣 ; 𝑝 = −𝑑

Finalmente, podemos llevar estos cálculos a una matriz de proyección que quedará así:

𝑃 =

⎣⎢⎢⎢⎢⎡𝑛

𝑎0

0 𝑛

0 0 0 0

0 00 0

𝑛 + 𝑓

𝑛 − 𝑓

2𝑛𝑓

𝑛 − 𝑓−1 0 ⎦

⎥⎥⎥⎥⎤

Figura 0.3. Proyección de un punto sobre un plano

Page 169: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

169

ANEXO III: MANUAL DEL API

Este anexo es un manual de referencia del interfaz expuesto por el motor al entorno de

programación Lua. Las funciones descritas aparecen clasificadas por el objeto al que pertenecen

y por orden alfabético. El manual está pensado para utilizarse como libro de consulta. En

ningún caso es necesario leerlo por completo para poder empezar a utilizar el motor. Para ello,

se recomienda al lector remitirse al capítulo “Ejemplos de uso” donde encontrará una guía de

iniciación más adecuada en las primeras fases de aprendizaje

AssetMapper AssetMapper.forceReload Descripción: Propiedad que indica si se debe forzar el recargado de ficheros.

Cuando está deshabilitado los ficheros de assets se obtienen de la caché si ya han sido accedidos previamente. Sin embargo, cuando estamos desarrollando, puede interesarnos forzar la recarga si vamos a cambiar ficheros mientras hacemos nuestras pruebas.

Ver también: forceReload

AssetMapper:getMesh(name) Descripción: Obtiene una malla 3D desde disco. Si el recurso fue utilizado

anteriormente lo obtendrá a partir de la caché. El archivo de malla debe estar en formato obj. Si utilizamos Blender podemos exportar nuestra escena a ese formato utilizando la opción de Export del menú File. Téngase en cuenta que sólo se cargarán las mallas de objetos. El sistema no reconoce cámaras, luces o cualquier otro objeto no renderizable.

Parámetros: - name: Cadena con el nombre de archivo que contiene la malla

Devuelve: Instancia de un objeto tipo Mesh

Ver también: forceReload

Page 170: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

170

AssetMapper:getShader(name) Descripción: Obtiene un shader desde disco que deberá ser un archivo con el

código escrito lenguaje glsl. Si el recurso fue utilizado anteriormente lo obtendrá a partir de la caché.

Parámetros: - name: Cadena con el nombre de archivo que contiene el shader

Devuelve: Instancia de un objeto tipo Shader

Ver también: forceReload

Page 171: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

171

CameraComponent Camera.clearColor Descripción: Propiedad de tipo Vector4 que permite modificar el color de borrado

de la ventana de gráficos

Ver también: CameraComponent.lookAt(eye,center,up) Descripción: Establece una matriz de transformación para la cámara de tal forma

que la entidad esté en la posición indicada por “eye”, mirando hacia “center” y tomando “up” como vector que apunta en dirección y sentido del el eje vertical natural hacia arriba.

Parámetros: - eye: Vector3 que indica la posición de la cámara - center: Vector3 que indica la posición objetivo hacia donde

queremos que se oriente - up: Vector3 unitario que indica la dirección y sentido del eje

vertical hacia arriba Devuelve:

Ver también: CameraComponent.setPerspectiveProjection, CameraComponent.setOrthogonalProjection

CameraComponent:setOrthogonalProjection(left,right,bottom,top,near,far) Descripción: Establece la matriz de proyección de la cámara en modo ortogonal.

Es decir, de tal forma que los objetos se muestran en pantalla del mismo tamaño sin importar la distancia a la cámara.

Parámetros: - left,right: Escalares que especifican las posiciones de los planos de corte izquierdo y derecho

- bottom,top: Escalares que especifican las posiciones de los planos de corte inferior y superior

- near,far: Escalares que especifican las posiciones de los planos de corte más cercano y lejano

Devuelve:

Ver también: CameraComponent.setPerspectiveProjection, CameraComponent.LookAt

CameraComponent:setPerspectiveProjection(fov,aspect,near,far)

Page 172: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

172

Descripción: Establece la matriz de proyección de la cámara en modo perspectiva. Es decir, de tal forma que los objetos se muestran más pequeños a medida que se alejan de la cámara.

Parámetros: - fov: Escalar que indica el ángulo del campo de visión en radianes - aspect: Escalar que indica la relación de aspecto entre

ancho/alto del viewport (ventana de visión) - near,far: Escalares que especifican las posiciones de los planos

de corte más cercano y lejano Devuelve:

Ver también: CameraComponent.setOrthogonalProjection, CameraComponent.LookAt

Camera.viewPort Descripción: Propiedad de tipo Vector4 que permite modificar la posición y

tamaño del viewport. Las primeras dos coordenadas del vector hacen referencia a la posición x,y, y las dos últimas al tamaño en ancho y alto.

Ver también: CameraComponent.setOrthogonalProjection, CameraComponent.setPerspectiveProjection, CameraComponent.LookAt

Page 173: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

173

DrawableComponent DrawableComponent.material Descripción: Propiedad que da acceso a la instancia del material que utiliza el

componente de gráficos de la entidad.

Ver también: Material, Entity DrawableComponent:setMesh(mesh) Descripción: Asigna una malla 3D al componente de gráficos de la entidad. Nótese

que esta malla será diferente a la del componente de física. Por tanto podemos usar en los gráficos una versión de la malla más detallada.

Parámetros: - mesh: Malla 3D a asignar

Devuelve:

Ver también: Mesh, Shape, PhysicsComponent DrawableComponent:setShape(shape) Descripción: Asigna una forma al componente gráfico a partir de un volumen

primitivo.

Parámetros: - shape: Volumen primitivo a asignar

Devuelve:

Ver también: Mesh, Shape, PhysicsComponent, BoxVolume, ConeVolume, CylinderVolume, CapsuleVolume, SphereVolume

Page 174: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

174

Entity Entity:addComponent(component) Descripción: Añade un componente a la entidad que podrá ser cualquier objeto

derivado de la clase Component. Una entidad sólo puede contener un componente por cada tipo. Por ejemplo, no es posible añadir dos GraphicsComponent o dos PhysicsComponent a la entidad. Sin embargo, en el caso de los scripts, podemos añadir un ScriptComponent que actúa como contenedor de instancias de script, con lo cual podemos combinar, en una misma entidad, lógicas procedentes de varios archivos.

Parámetros: - component: Instancia de un objeto derivado de la clase Component que vamos a añadir a la entidad

Devuelve:

Ver también: Component

Entity.camera Descripción: Propiedad que da acceso al componente CameraComponent de la

entidad. Es la propiedad que debemos usar por ejemplo para la cámara global de la escena si queremos manipularla.

Ver también: CameraComponent, GraphicsComponent

Entity.drawable Descripción: Propiedad que da acceso al componente DrawableComponent

derivado de GraphicsComponent.

Ver también: DrawableComponent, GraphicsComponent.

Entity:getComponent(componentName) Descripción: Obtiene un componente de la entidad a partir de su nombre de tipo.

Parámetros: - componentName: Nombre del tipo de componente que vamos a obtener. Las opciones son: “graphics”, “physics”, “transform”, o “script”.

Devuelve:

Ver también: Component

Entity.id

Page 175: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

175

Descripción: Propiedad que contiene el identificador único de la entidad en la escena. Esta propiedad sólo es accesible para lectura y se establece en el constructor del objeto

Ver también: Entity.new(id)

Entity.new(id) Descripción: Constructor del objeto Entity que representa una entidad en la

escena.

Parámetros: - id: Cadena con el identificador único de la entidad

Devuelve: Instancia de un objeto tipo Entity

Ver también:

Entity.physics Descripción: Propiedad que da acceso al componente PhysicsComponent de la

entidad.

Ver también: PhysicsComponent Entity:removeComponent(componentName) Descripción: Elimina un componente de una entidad por nombre de tipo. Nótese

que si eliminamos el componente “script” se eliminarán de la entidad todas las instancias de script relacionadas.

Parámetros: - componentName: Nombre del tipo de componente que vamos a eliminar. Las opciones son: “graphics”, “physics”, “transform”, o “script”.

Devuelve: Verdadero en caso de que se haya eliminado el componente

Ver también: Component

Entity.script Descripción: Propiedad que da acceso al componente ScriptComponent de la

entidad en caso de tenerlo asociado.

Ver también: ScriptComponent

Entity.transform Descripción: Propiedad que da acceso al componente transform de la entidad en

caso de tenerlo asociado.

Ver también: TransformComponent

Page 176: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

176

ion (alias de SystemManager) ion:addEntity(id,shape,transform=nil) Descripción: Función fachada que crea y añade a la escena una entidad con los

parámetros indicados. El resto de opciones básicas se establecen por defecto.

Parámetros: - id: Cadena que contiene el identificador único de la entidad a crear.

- shape: Objeto de tipo Shape o Mesh que establece la forma de la entidad para el componente de gráficos.

- transform: Objeto de tipo TransformComponent que determina las transformaciones afines iniciales de la entidad

Devuelve: Instancia de objeto de tipo Entity

Ver también: ion:removeEntity, ion:getEntity, ion:addPhysicsEntity, Position, PositionRotation, TransoformComponent

ion:addPhysicsEntity(id,shape,flags,transform=nil) Descripción: Función fachada que crea y añade a la escena una entidad con los

parámetros indicados y con componente de física. El resto de opciones básicas se establecen por defecto.

Parámetros: - id: Cadena que contiene el identificador único de la entidad a crear.

- shape: Objeto de tipo Shape o Mesh que establece la forma de la entidad para el componente de gráficos.

- flags: Opciones combinables que indican el tipo de física a aplicar. Pueden ser: KINEMATIC, DYNAMIC, GHOST, TRIGGER.

- transform: Objeto de tipo TransformComponent que determina las transformaciones afines iniciales de la entidad

Devuelve: Instancia de objeto de tipo Entity

Ver también: ion:removeEntity, ion:getEntity, ion:addEntity, Position, PositionRotation, TransoformComponent

ion:getEntity(id) Descripción: Recupera la entidad de la escena cuyo identificador se especifica por

parámetro

Parámetros: - id: Cadena que contiene el identificador de la entidad a obtener

Page 177: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

177

Devuelve: Instancia de la entidad recuperada o nil si no existe

Ver también: ion:removeEntity, ion:addEntity

ion:removeEntity(id) Descripción: Elimina la entidad de la escena cuyo identificador se especifica por

parámetro

Parámetros: - id: Cadena que contiene el identificador de la entidad a eliminar

Devuelve:

Ver también: ion:getEntity, ion:addEntity

ion:reset() Descripción: Elimina todas las entidades de la escena y reinicia los sistemas ECS

Parámetros:

Devuelve:

Ver también:

ion:run(luaFileName) Descripción: Ejecuta el script del archivo cuyo nombre se indica por parámetro.

Esta función está pensada para utilizarse manualmente desde la consola con el fin de realizar pruebas. No se aconseja su uso dentro de otro script. Para ejecutar código dentro de nuestro código se preferirá siempre el uso de funciones.

Parámetros: - id: Cadena que contiene el identificador de la entidad a eliminar

Devuelve: El valor devuelto es nil, pero podremos ver el resultado de la ejecución en la consola de script.

Ver también:

ion.showFPS Descripción: Propiedad que establece si se deben mostrar los fotogramas por

segundo en la consola de depuración. En caso de estar habilitado, se imprimirá un log cada segundo indicando la media de fps obtenida en ese intervalo. Nótese que los FPS no aparecerán en la consola de script, sino en la ventana de depuración del entorno de desarrollo (Por ejemplo el log de AndroidStudio o la ventana de Salida de Visual Studio)

Ver también:

Page 178: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

178

Material Material.diffuse Descripción: Propiedad que nos permite leer/escribir el atributo “diffuseColor”

del shader. Por ejemplo, Material.diffuse = color, es equivalente a llamar a Entity:setVector4(“diffuseColor”, color)

Ver también: Shader, setVector4, getVector4, setFloat, getFloat

Material:getFloat(key) Descripción: Obtiene el valor del atributo uniforme de tipo float del shader con

el nombre solicitado.

Parámetros: - key: Nombre del atributo uniforme del shader a obtener.

Devuelve: Valor del atributo obtenido

Ver también: Shader, setVector4, getVector4, getFloat

Material:getVector4(key) Descripción: Obtiene el valor del atributo uniforme de tipo Vector4 del shader

con el nombre solicitado.

Parámetros: - key: Nombre del atributo uniforme del shader a obtener.

Devuelve: Valor del atributo obtenido

Ver también: Shader, setVector4, setFloat, getFloat Material:setFloat(key,value) Descripción: Establece el valor del atributo uniforme de tipo float del shader con

el nombre indicado.

Parámetros: - key: Nombre del atributo uniforme del shader a modificar. - value: Nuevo valor del atributo

Devuelve:

Ver también: Shader, setVector4, getVector4, setFloat, getFloat

Material:setShader(shader) Descripción: Establece el shader que se aplicará al material para programar el

efecto deseado.

Parámetros: - shader: Objeto de tipo Shader

Devuelve:

Ver también: Shader, AssetMapper:getShader

Page 179: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

179

Material:setVector4(key,value) Descripción: Establece el valor del atributo uniforme de tipo Vector4 del shader

con el nombre indicado.

Parámetros: - key: Nombre del atributo uniforme del shader a modificar. - value: Nuevo valor del atributo

Devuelve:

Ver también: Shader, setVector4, getVector4, setFloat, getFloat

Page 180: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

180

PhysicsComponent PhysicsComponent.angularVelocity Descripción: Propiedad de tipo Vector3 que permite obtener o modificar la

velocidad angular del objeto usando ángulos Euler.

Ver también: linearVelocity PhysicsComponent:applyCentralImpulse(impulse) Descripción: Aplica un impulso al objeto desde su centro de masas dando como

resultando que se mueva en la dirección indicada si las demás condiciones físicas lo permiten

Parámetros: - impulse: Vector3 que indica la dirección del impulso.

Devuelve:

Ver también: mass, gravity PhysicsComponent.gravity Descripción: Propiedad de tipo Vector3 que da acceso a la dirección de la

gravedad local para el objeto. En realidad, se refiere a cómo afecta la gravedad a esta entidad. Para modificar la dirección de la gravedad global que afecta a todos los objetos, usaremos Physics.setGravity

Ver también: gravity PhysicsComponent.linearVelocity Descripción: Propiedad de tipo Vector3 que permite obtener o modificar la

velocidad lineal del objeto indicando la velocidad en los 3 ejes cartesianos.

Ver también: linearVelocity PhysicsComponent.mass Descripción: Propiedad escalar que da acceso a la masa del objeto. Por defecto es

1.0. Un objeto con masa 0.0 se considera de masa infinita en el sentido de que no se moverá al colisionar. Además, tampoco será afectado por la gravedad.

Ver también: gravity

Page 181: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

181

PhysicsComponent.new(shape) Descripción: Constructor del componente tipo PhysicsComponent

Parámetros: - shape: Volumen o malla 3D a asignar al componente y que se utilizará para la detección de colisiones.

Devuelve: Instancia de objeto PhysicsComponent

Ver también:

Page 182: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

182

Random Random:float(min=0,max=RAND_MAX) Descripción: Obtiene un número aleatorio de tipo coma flotante entre un rango

Parámetros: - min: Valor mínimo del rango. Si no se indica es cero. - max: Valor máximo del rango. Si no se indica es el máximo valor

para un numero de tipo coma flotante Devuelve:

Ver también: Random.seed, Random.int Random:int(min=0,max=RAND_MAX) Descripción: Obtiene un número aleatorio de tipo entero entre un rango

Parámetros: - min: Valor mínimo del rango. Si no se indica es cero. - max: Valor máximo del rango. Si no se indica es el máximo valor

para un numero de tipo entero Devuelve:

Ver también: Random.seed, Random.float Random:seed(s) Descripción: Establece la semilla para la secuencia de números pseudo-aleatorios

Parámetros: - s: Valor para usar como semilla Devuelve:

Ver también: Random.unit, Random.float, Random.int Random:unit() Descripción: Obtiene un número aleatorio de tipo coma flotante unitario (entre

0.0 y 1.0)

Parámetros:

Devuelve:

Ver también: Random.seed, Random.float, Random.int

Page 183: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

183

Shapes BoxVolume(x,y,z) Descripción: Factoría de un objeto que define un volumen en forma de caja

Parámetros: - x: Tamaño de la caja sobre el eje local X - y: Tamaño de la caja sobre el eje local Y - z: Tamaño de la caja sobre el eje local Z

Devuelve: Instancia de un objeto Box

Ver también: CapsuleVolume(radius,height) Descripción: Factoría de un objeto que define un volumen en forma de cápsula

Parámetros: - radius: Radio de la cápsula formado sobre el eje Y - height: Altura de la cápsula sobre el eje Y

Devuelve: Instancia de un objeto Capsule

Ver también: CylinderVolume CylinderVolume(radius,height) Descripción: Factoría de un objeto que define un volumen en forma de cilindro

Parámetros: - radius: Radio del cilindro formado sobre el eje Y - height: Altura del cilindro sobre el eje Y

Devuelve: Instancia de un objeto Cylinder

Ver también: CapsuleVolume SphereVolume(radius) Descripción: Factoría de un objeto que define un volumen en forma de esfera

Parámetros: - radius: Radio de la esfera Devuelve: Instancia de un objeto Sphere

Ver también:

Page 184: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Game engine multiplataforma

184

TransformComponent TransformComponent.eulerAngles Descripción: Propiedad de tipo Vector3 que obtiene o modifica la posición en la

matriz de transformación.

Ver también: scale, position TransformComponent:getRotationAngle() Descripción: Obtiene el ángulo en el que está rotada la transformación atendiendo

al modo de rotación eje-ángulo

Parámetros:

Devuelve: Escalar que representa el ángulo de rotación

Ver también: eulerAngles, setAxisAngle, getRotationAxis TransformComponent:getRotationAxis() Descripción: Obtiene el eje de rotación sobre el que está rotada la matriz

atendiendo al modo de rotación eje-ángulo

Parámetros:

Devuelve: Escalar que representa el ángulo de rotación

Ver también: eulerAngles, setAxisAngle, getRotationAngle TransformComponent.position Descripción: Propiedad de tipo Vector3 que obtiene o modifica la posición en la

matriz de transformación.

Ver también: scale, eulerAngles TransformComponent:setAxisAngle(axis,angle) Descripción: Aplica a la matriz de transformación una rotación determinada por

un ángulo sobre el eje indicado

Parámetros: - axis: Vector3 que indica el eje sobre el que aplicar la rotación. - angle: Escalar con el ángulo de la rotación en radianes

Devuelve:

Ver también: eulerAngles, getRotationAxis, getRotationAngle TransformComponent.scale Descripción: Propiedad de tipo Vector3 que obtiene o modifica la escala en la

matriz de transformación.

Ver también: position, eulerAngles

Page 185: g g g gg g *UDGR HQ ,QJHQLHUtD ,QIRUPiWLFDrua.ua.es/dspace/bitstream/10045/93558/1/Game... · g g g g g g g g g g g g g g g g gJ Mm2H -gTQ`gbmgBM2biBK #H2g+QKT ¢ Q -gg 2gBMi2`KBM

Daniel Ponsoda Montiel

185