Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Curso Desarrollo Vide Desconhecido
Curso Desarrollo Vide Desconhecido
Creative Commons License: Usted es libre de copiar, distribuir y comunicar pblicamente la obra, bajo
las condiciones siguientes: 1. Reconocimiento. Debe reconocer los crditos de la obra de la manera
especicada por el autor o el licenciador. 2. No comercial. No puede utilizar esta obra para nes
comerciales. 3. Sin obras derivadas. No se puede alterar, transformar o generar una obra derivada a
partir de esta obra. Ms informacin en: http://creativecommons.org/licenses/by-nc-nd/3.0/
Prefacio
1. Arquitectura del Motor, donde se estudian los aspectos esenciales del diseo
de un motor de videojuegos, as como las tcnicas bsicas de programacin
y patrones de diseo. En este bloque tambin se estudian los conceptos ms
relevantes del lenguaje de programacin C++.
2. Programacin Grca, donde se presta especial atencin a los algoritmos y
tcnicas de representacin grca, junto con las optimizaciones en sistemas de
despliegue interactivo.
3. Tcnicas Avanzadas, donde se recogen ciertos aspectos avanzados, como es-
tructuras de datos especcas, tcnicas de validacin y pruebas o simulacin
fsica. As mismo, en este bloque se profundiza en el lenguaje C++.
4. Desarrollo de Componentes, donde, nalmente, se detallan ciertos componen-
tes especcos del motor, como la Inteligencia Articial, Networking, Sonido y
Multimedia o tcnicas avanzadas de Interaccin.
Sobre este libro
Este libro que tienes en tus manos es una ampliacin y revisin de los apuntes del
Curso de Experto en Desarrollo de Videojuegos, impartido en la Escuela Superior de
Informtica de Ciudad Real de la Universidad de Castilla-La Mancha. Puedes obtener
ms informacin sobre el curso, as como los resultados de los trabajos creados por
los alumnos, en la web del mismo: http://www.cedv.es. La versin electrnica de este
libro puede descargarse desde la web anterior. El libro fsico puede adquirirse desde
la pgina web de la editorial online Edlibrix en http://www.shoplibrix.com.
Requisitos previos
Este libro tiene un pblico objetivo con un perl principalmente tcnico. Al igual
que el curso del que surgi, est orientado a la capacitacin de profesionales de la
programacin de videojuegos. De esta forma, este libro no est orientado para un
pblico de perl artstico (modeladores, animadores, msicos, etc.) en el mbito de
los videojuegos.
Se asume que el lector es capaz de desarrollar programas de nivel medio en C
y C++. Aunque se describen algunos aspectos clave de C++ a modo de resumen, es
recomendable refrescar los conceptos bsicos con alguno de los libros recogidos en
la bibliografa. De igual modo, se asume que el lector tiene conocimientos de estruc-
turas de datos y algoritmia. El libro est orientado principalmente para titulados o
estudiantes de ltimos cursos de Ingeniera en Informtica.
Agradecimientos
Los autores del libro quieren agradecer, en primer lugar, a los alumnos de las tres
primeras ediciones del Curso de Experto en Desarrollo de Videojuegos por su partici-
pacin en el mismo y el excelente ambiente en las clases, las cuestiones planteadas y
la pasin demostrada en el desarrollo de todos los trabajos.
De igual modo, se quiere reejar el agradecimiento especial al personal de admi-
nistracin y servicios de la Escuela Superior de Informtica, por su soporte, predis-
posicin y ayuda en todos los caprichosos requisitos que plantebamos a lo largo del
curso.
Por otra parte, este agradecimiento tambin se hace extensivo a la Escuela de In-
formatica de Ciudad Real y al Departamento de Tecnologas y Sistema de Informacin
de la Universidad de Castilla-La Mancha.
Finalmente, los autores desean agradecer su participacin a los colaboradores de
las tres primeras ediciones: Indra Software Labs, la asociacin de desarrolladores de
videojuegos Stratos, Libro Virtual, Devilish Games, Dolores Entertainment, From the
Bench, Iberlynx, KitMaker, Playspace, Totemcat/Materia Works y ZuinqStudio.
Autores
1. Introduccin 3
1.1. El desarrollo de videojuegos . . . . . . . . . . . . . . . . . . . . . . 3
1.1.1. La industria del videojuego. Presente y futuro . . . . . . . . . 3
1.1.2. Estructura tpica de un equipo de desarrollo . . . . . . . . . . 5
1.1.3. El concepto de juego . . . . . . . . . . . . . . . . . . . . . . 7
1.1.4. Motor de juego . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.1.5. Gneros de juegos . . . . . . . . . . . . . . . . . . . . . . . 10
1.2. Arquitectura del motor. Visin general . . . . . . . . . . . . . . . . . 15
1.2.1. Hardware, drivers y sistema operativo . . . . . . . . . . . . . 15
1.2.2. SDKs y middlewares . . . . . . . . . . . . . . . . . . . . . . 16
1.2.3. Capa independiente de la plataforma . . . . . . . . . . . . . . 17
1.2.4. Subsistemas principales . . . . . . . . . . . . . . . . . . . . 18
1.2.5. Gestor de recursos . . . . . . . . . . . . . . . . . . . . . . . 18
1.2.6. Motor de rendering . . . . . . . . . . . . . . . . . . . . . . . 19
1.2.7. Herramientas de depuracin . . . . . . . . . . . . . . . . . . 22
1.2.8. Motor de fsica . . . . . . . . . . . . . . . . . . . . . . . . . 22
1.2.9. Interfaces de usuario . . . . . . . . . . . . . . . . . . . . . . 23
1.2.10. Networking y multijugador . . . . . . . . . . . . . . . . . . . 23
1.2.11. Subsistema de juego . . . . . . . . . . . . . . . . . . . . . . 24
1.2.12. Audio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
1.2.13. Subsistemas especcos de juego . . . . . . . . . . . . . . . . 26
2. Herramientas de Desarrollo 27
I
[II] NDICE GENERAL
2.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.2. Compilacin, enlazado y depuracin . . . . . . . . . . . . . . . . . . 28
2.2.1. Conceptos bsicos . . . . . . . . . . . . . . . . . . . . . . . 28
2.2.2. Compilando con GCC . . . . . . . . . . . . . . . . . . . . . 31
2.2.3. Cmo funciona GCC? . . . . . . . . . . . . . . . . . . . . . 31
2.2.4. Ejemplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
2.2.5. Otras herramientas . . . . . . . . . . . . . . . . . . . . . . . 39
2.2.6. Depurando con GDB . . . . . . . . . . . . . . . . . . . . . . 39
2.2.7. Construccin automtica con GNU Make . . . . . . . . . . . 45
2.3. Gestin de proyectos y documentacin . . . . . . . . . . . . . . . . . 50
2.3.1. Sistemas de control de versiones . . . . . . . . . . . . . . . . 50
2.3.2. Documentacin . . . . . . . . . . . . . . . . . . . . . . . . . 59
2.3.3. Forjas de desarrollo . . . . . . . . . . . . . . . . . . . . . . . 62
Anexos 1079
XXI
[XXII] ACRNIMOS
DD Debian Developer
DEB Deep Estimation Buffer
DEHS Debian External Health Status
DFSG Debian Free Software Guidelines
DIP Dependency Inversion Principle
DLL Dynamic Link Library
DOD Diamond Of Death
DOM Document Object Model
DTD Documento Tcnico de Diseo
DVB Digital Video Broadcasting
DVD Digital Video Disc
E/S Entrada/Salida
EASTL Electronic Arts Standard Template Library
EA Electronic Arts
ELF Executable and Linkable Format
FIFO First In, First Out
FLAC Free Lossless Audio Codec
FPS First Person Shooter
FSM Finite State Machine
GCC GNU Compiler Collection
GDB GNU Debugger
GDD Game Design Document
GIMP GNU Image Manipulation Program
GLUT OpenGL Utility Toolkit
GLU OpenGL Utility
GNOME GNU Object Model Environment
GNU GNU is Not Unix
GPL General Public License
GPS Global Positioning System
GPU Graphic Processing Unit
GPV grafos de puntos visibles
GTK GIMP ToolKit
GUI Graphical User Interface
GUP Game Unied Process
HDTV High Denition Television
HFSM Hierarchical Finite State Machine
HOM Hierarchical Occlusion Maps
HSV Hue, Saturation, Value
HTML HyperText Markup Language
I/O Input/Output
IANA Internet Assigned Numbers Authority
IA Inteligencia Articial
IBM International Business Machines
IDL Interface Denition Language
[XXIII]
Introduccin
1
David Vallejo Fernndez
A
ctualmente, la industria del videojuego goza de una muy buena salud a nivel
mundial, rivalizando en presupuesto con las industrias cinematogrca y mu-
sical. En este captulo se discute, desde una perspectiva general, el desarrollo
de videojuegos, haciendo especial hincapi en su evolucin y en los distintos elemen-
tos involucrados en este complejo proceso de desarrollo. En la segunda parte del cap-
tulo se introduce el concepto de arquitectura del motor, como eje fundamental para
el diseo y desarrollo de videojuegos comerciales.
El primer videojuego Lejos han quedado los das desde el desarrollo de los primeros videojuegos, ca-
racterizados principalmente por su simplicidad y por el hecho de estar desarrollados
El videojuego Pong se considera completamente sobre hardware. Debido a los distintos avances en el campo de la in-
como unos de los primeros video- formtica, no slo a nivel de desarrollo software y capacidad hardware sino tambin
juegos de la historia. Desarrollado
por Atari en 1975, el juego iba in- en la aplicacin de mtodos, tcnicas y algoritmos, la industria del videojuego ha evo-
cluido en la consola Atari Pong. lucionado hasta llegar a cotas inimaginables, tanto a nivel de jugabilidad como de
Se calcula que se vendieron unas calidad grca, tan slo hace unos aos.
50.000 unidades.
3
[4] CAPTULO 1. INTRODUCCIN
Tiempo real El desarrollo de videojuegos comerciales es un proceso complejo debido a los dis-
tintos requisitos que ha de satisfacer y a la integracin de distintas disciplinas que
En el mbito del desarrollo de vi- intervienen en dicho proceso. Desde un punto de vista general, un videojuego es una
deojuegos, el concepto de tiempo aplicacin grca en tiempo real en la que existe una interaccin explcita mediante
real es muy importante para dotar
de realismo a los juegos, pero no el usuario y el propio videojuego. En este contexto, el concepto de tiempo real se ree-
es tan estricto como el concepto de re a la necesidad de generar una determinada tasa de frames o imgenes por segundo,
tiempo real manejado en los siste- tpicamente 30 60, para que el usuario tenga una sensacin continua de realidad.
mas crticos. Por otra parte, la interaccin se reere a la forma de comunicacin existente entre el
usuario y el videojuego. Normalmente, esta interaccin se realiza mediante joysticks o
mandos, pero tambin es posible llevarla a cabo con otros dispositivos como por ejem-
plo teclados, ratones, cascos o incluso mediante el propio cuerpo a travs de tcnicas
de visin por computador o de interaccin tctil.
A continuacin se describe la estructura tpica de un equipo de desarrollo aten-
diendo a los distintos roles que juegan los componentes de dicho equipo [42]. En
muchos casos, y en funcin del nmero de componentes del equipo, hay personas
especializadas en diversas disciplinas de manera simultnea.
Los ingenieros son los responsables de disear e implementar el software que
permite la ejecucin del juego, as como las herramientas que dan soporte a dicha
ejecucin. Normalmente, los ingenieros se suelen clasicar en dos grandes grupos:
Los programadores del ncleo del juego, es decir, las personas responsables
de desarrollar tanto el motor de juego como el juego propiamente dicho.
Los programadores de herramientas, es decir, las personas responsables de
desarrollar las herramientas que permiten que el resto del equipo de desarrollo
pueda trabajar de manera eciente.
Productor ejecutivo
Programador Networking
Equipo artstico
Herramientas Fsica
Conceptual Modelado Artista
tcnico Inteligencia Art. Motor
Animacin Texturas
Interfaces Audio
Al igual que suele ocurrir con los ingenieros, existe el rol de artista senior cuyas
responsabilidades tambin incluyen la supervisin de los numerosos aspectos vincu-
lados al componente artstico.
Los diseadores de juego son los responsables de disear el contenido del juego,
Scripting e IA destacando la evolucin del mismo desde el principio hasta el nal, la secuencia de
El uso de lenguajes de alto nivel captulos, las reglas del juego, los objetivos principales y secundarios, etc. Evidente-
es bastante comn en el desarrollo mente, todos los aspectos de diseo estn estrechamente ligados al propio gnero del
de videojuegos y permite diferen- mismo. Por ejemplo, en un juego de conduccin es tarea de los diseadores denir el
ciar claramente la lgica de la apli- comportamiento de los coches adversarios ante, por ejemplo, el adelantamiento de un
cacin y la propia implementacin.
Una parte signicativa de las desa- rival.
rrolladoras utiliza su propio lengua- Los diseadores suelen trabajar directamente con los ingenieros para afrontar di-
je de scripting, aunque existen len-
guajes ampliamente utilizados, co- versos retos, como por ejemplo el comportamiento de los enemigos en una aventura.
mo son Lua o Python. De hecho, es bastante comn que los propios diseadores programen, junto con los in-
genieros, dichos aspectos haciendo uso de lenguajes de scripting de alto nivel, como
por ejemplo Lua4 o Python5 .
Como ocurre con las otras disciplinas previamente comentadas, en algunos estu-
dios los diseadores de juego tambin juegan roles de gestin y supervisin tcnica.
Finalmente, en el desarrollo de videojuegos tambin estn presentes roles vincu-
lados a la produccin, especialmente en estudios de mayor capacidad, asociados a la
planicacin del proyecto y a la gestin de recursos humanos. En algunas ocasiones,
los productores tambin asumen roles relacionados con el diseo del juego. As mis-
mo, los responsables de marketing, de administracin y de soporte juegan un papel
relevante. Tambin resulta importante resaltar la gura de publicador como entidad
responsable del marketing y distribucin del videojuego desarrollado por un determi-
nado estudio. Mientras algunos estudios tienen contratos permanentes con un deter-
minado publicador, otros preeren mantener una relacin temporal y asociarse con el
publicador que le ofrezca mejores condiciones para gestionar el lanzamiento de un
ttulo.
En otras palabras, los juegos son aplicaciones interactivas que estn marcadas por
el tiempo, es decir, cada uno de los ciclos de ejecucin tiene un deadline que ha de
cumplirse para no perder realismo.
Aunque el componente grco representa gran parte de la complejidad compu-
tacional de los videojuegos, no es el nico. En cada ciclo de ejecucin, el videojuego
ha de tener en cuenta la evolucin del mundo en el que se desarrolla el mismo. Dicha
evolucin depender del estado de dicho mundo en un momento determinado y de c-
mo las distintas entidades dinmicas interactan con l. Obviamente, recrear el mundo
C1
1.1. El desarrollo de videojuegos [9]
real con un nivel de exactitud elevado no resulta manejable ni prctico, por lo que nor-
malmente dicho mundo se aproxima y se simplica, utilizando modelos matemticos
para tratar con su complejidad. En este contexto, destaca por ejemplo la simulacin
fsica de los propios elementos que forman parte del mundo.
Por otra parte, un juego tambin est ligado al comportamiento del personaje prin-
cipal y del resto de entidades que existen dentro del mundo virtual. En el mbito
acadmico, estas entidades se suelen denir como agentes (agents) y se encuadran
dentro de la denominada simulacin basada en agentes [64]. Bsicamente, este tipo
de aproximaciones tiene como objetivo dotar a los NPC con cierta inteligencia pa-
ra incrementar el grado de realismo de un juego estableciendo, incluso, mecanismos
de cooperacin y coordinacin entre los mismos. Respecto al personaje principal, un
videojuego ha de contemplar las distintas acciones realizadas por el mismo, consi-
derando la posibilidad de decisiones impredecibles a priori y las consecuencias que
podran desencadenar.
Figura 1.3: El motor de juego re- En resumen, y desde un punto de vista general, el desarrollo de un juego implica
presenta el ncleo de un videojuego considerar un gran nmero de factores que, inevitablemente, incrementan la comple-
y determina el comportamiento de jidad del mismo y, al mismo tiempo, garantizar una tasa de fps adecuada para que la
los distintos mdulos que lo com- inmersin del usuario no se vea afectada.
ponen.
Obviamente, la separacin entre motor de juego y juego nunca es total y, por una
circunstancia u otra, siempre existen dependencias directas que no permiten la reusa-
bilidad completa del motor para crear otro juego. La dependencia ms evidente es el
genero al que est vinculado el motor de juego. Por ejemplo, un motor de juegos dise-
ado para construir juegos de accin en primera persona, conocidos tradicionalmente
como shooters o shootem all, ser difcilmente reutilizable para desarrollar un juego
de conduccin.
Una forma posible para diferenciar un motor de juego y el software que representa
a un juego est asociada al concepto de arquitectura dirigida por datos (data-driven
architecture). Bsicamente, cuando un juego contiene parte de su lgica o funciona-
miento en el propio cdigo (hard-coded logic), entonces no resulta prctico reutilizar-
la para otro juego, ya que implicara modicar el cdigo fuente sustancialmente. Sin
embargo, si dicha lgica o comportamiento no est denido a nivel de cdigo, sino
por ejemplo mediante una serie de reglas denidas a travs de un lenguaje de script,
entonces la reutilizacin s es posible y, por lo tanto, beneciosa, ya que optimiza el
tiempo de desarrollo.
Como conclusin nal, resulta relevante destacar la evolucin relativa a la genera- Game engine tuning
lidad de los motores de juego, ya que poco a poco estn haciendo posible su utilizacin
para diversos tipos de juegos. Sin embargo, el compromiso entre generalidad y optima- Los motores de juegos se suelen
adaptar para cubrir las necesidades
lidad an est presente. En otras palabras, a la hora de desarrollar un juego utilizando especcas de un ttulo y para obte-
un determinado motor es bastante comn personalizar dicho motor para adaptarlo a ner un mejor rendimiento.
las necesidades concretas del juego a desarrollar.
Figura 1.5: Captura de pantalla del juego Tremulous R , licenciado bajo GPL y desarrollado sobre el motor
de Quake III.
Otro gnero importante est representado por los juegos de lucha, en los que,
normalmente, dos jugadores compiten para ganar un determinado nmero de com-
bates minando la vida o stamina del jugador contrario. Ejemplos representativos de
juegos de lucha son Virtua Fighter, Street Fighter, Tekken, o Soul Calibur, entre otros.
Actualmente, los juegos de lucha se desarrollan normalmente en escenarios tridimen-
sionales donde los luchadores tienen una gran libertad de movimiento. Sin embargo,
ltimamente se han desarrollado diversos juegos en los que tanto el escenario como
los personajes son en 3D, pero donde el movimiento de los mismos est limitado a dos
dimensiones, enfoque comnmente conocido como juegos de lucha de scroll lateral.
Debido a que en los juegos de lucha la accin se centra generalmente en dos per-
sonajes, stos han de tener una gran calidad grca y han de contar con una gran
variedad de movimientos y animaciones para dotar al juego del mayor realismo posi-
ble. As mismo, el escenario de lucha suele estar bastante acotado y, por lo tanto, es
posible simplicar su tratamiento y, en general, no es necesario utilizar tcnicas de op-
timizacin como las comentadas en el gnero de los FPS. Por otra parte, el tratamiento
de sonido no resulta tan complejo como lo puede ser en otros gneros de accin.
Los juegos del gnero de la lucha han de prestar atencin a la deteccin y gestin
de colisiones entre los propios luchadores, o entre las armas que utilicen, para dar una
sensacin de mayor realismo. Adems, el mdulo responsable del tratamiento de la
entrada al usuario ha de ser lo sucientemente sosticado para gestionar de manera
adecuada las distintas combinaciones de botones necesarias para realizar complejos
movimientos. Por ejemplo, juegos como Street Fighter IV incorporan un sistema de
timing entre los distintos movimientos de un combo. El objetivo perseguido consiste
Simuladores F1 en que dominar completamente a un personaje no sea una tarea sencilla y requiera que
Los simuladores de juegos de con- el usuario de videojuegos dedique tiempo al entrenaiento del mismo.
duccin no slo se utilizan para Los juegos de lucha, en general, han estado ligados a la evolucin de tcnicas
el entretenimiento domstico sino
tambin para que, por ejemplo, los complejas de sntesis de imagen aplicadas sobre los propios personajes con el objetivo
pilotos de Frmula-1 conozcan to- de mejorar al mximo su calidad y, de este modo, incrementar su realismo. Un ejemplo
dos los entresijos de los circuitos y representativo es el uso de shaders [76] sobre la armadura o la propia piel de los
puedan conocerlos al detalle antes
de embarcarse en los entrenamien-
personajes que permitan implementar tcnicas como el bump mapping [6], planteada
tos reales. para dotar a estos elementos de un aspecto ms rugoso.
[14] CAPTULO 1. INTRODUCCIN
Al igual que ocurre en los juegos de estrategia, los MMOG suelen utilizar persona-
jes virtuales en baja resolucin para permitir la aparicin de un gran nmero de ellos
en pantalla de manera simultnea.
Adems de los distintos gneros mencionados en esta seccin, existen algunos
ms como por ejemplo los juegos deportivos, los juegos de rol o RPG (Role-Playing
Games) o los juegos de puzzles.
Antes de pasar a la siguiente seccin en la que se discutir la arquitectura general
de un motor de juego, resulta interesante destacar la existencia de algunas herramien-
tas libres que se pueden utilizar para la construccin de un motor de juegos. Una de
las ms populares, y que se utilizar en el presente curso, es OGRE 3D9 . Bsicamente,
OGRE es un motor de renderizado 3D bien estructurado y con una curva de aprendi-
zaje adecuada. Aunque OGRE no se puede denir como un motor de juegos completo,
s que proporciona un gran nmero de mdulos que permiten integrar funcionalidades
no triviales, como iluminacin avanzada o sistemas de animacin de caracteres.
La arquitectura Cell La capa relativa al hardware est vinculada a la plataforma en la que se ejecutar
el motor de juego. Por ejemplo, un tipo de plataforma especca podra ser una consola
En arquitecturas ms novedosas, de juegos de sobremesa. Muchos de los principios de diseo y desarrollo son comu-
como por ejemplo la arquitectura nes a cualquier videojuego, de manera independiente a la plataforma de despliegue
Cell usada en Playstation 3 y desa-
rrollada por Sony, Toshiba e IBM, nal. Sin embargo, en la prctica los desarrolladores de videojuegos siempre llevan
las optimizaciones aplicadas suelen a cabo optimizaciones en el motor de juegos para mejorar la eciencia del mismo,
ser ms dependientes de la platafor- considerando aquellas cuestiones que son especcas de una determinada plataforma.
ma nal.
La capa de drivers soporta aquellos componentes software de bajo nivel que per-
miten la correcta gestin de determinados dispositivos, como por ejemplo las tarjetas
de aceleracin grca o las tarjetas de sonido.
La capa del sistema operativo representa la capa de comunicacin entre los pro-
cesos que se ejecutan en el mismo y los recursos hardware asociados a la plataforma
en cuestin. Tradicionalmente, en el mundo de los videojuegos los sistemas opera-
tivos se compilan con el propio juego para producir un ejecutable. Sin embargo, las
9 http://www.ogre3d.org/
[16] CAPTULO 1. INTRODUCCIN
Subsistema
Networking Audio
de juego
Gestor de recursos
Subsistemas principales
Sistema operativo
Drivers
Hardware
Figura 1.9: Visin conceptual de la arquitectura general de un motor de juegos. Esquema adaptado de la
arquitectura propuesta en [42].
Abstraccin funcional Gran parte de los juegos se desarrollan teniendo en cuenta su potencial lanzamien-
to en diversas plataformas. Por ejemplo, un ttulo se puede desarrollar para diversas
Aunque en teora las herramien- consolas de sobremesa y para PC al mismo tiempo. En este contexto, es bastante co-
tas multiplataforma deberan abs-
traer de los aspectos subyacentes
mn encontrar una capa software que aisle al resto de capas superiores de cualquier
a las mismas, como por ejemplo aspecto que sea dependiente de la plataforma. Dicha capa se suele denominar capa
el sistema operativo, en la prctica independiente de la plataforma.
suele ser necesario realizar algunos
ajustos en funcin de la plataforma Aunque sera bastante lgico suponer que la capa inmediatamente inferior, es de-
existente en capas de nivel inferior. cir, la capa de SDKs y middleware, ya posibilita la independencia respecto a las pla-
taformas subyacentes debido al uso de mdulos estandarizados, como por ejemplo
bibliotecas asociadas a C/C++, la realidad es que existen diferencias incluso en bi-
bliotecas estandarizadas para distintas plataformas.
Algunos ejemplos representativos de mdulos incluidos en esta capa son las bi-
bliotecas de manejo de hijos o los wrappers o envolturas sobre alguno de los mdulos
de la capa superior, como el mdulo de deteccin de colisiones o el responsable de la
parte grca.
10 http://www.sgi.com/tech/stl/
11 http://http://www.opengl.org/
12 http://www.havok.com
13 http://www.ode.org
[18] CAPTULO 1. INTRODUCCIN
Recurso Recurso
Mundo Etc
esqueleto colisin
Gestor de recursos
Figura 1.10: Visin conceptual del gestor de recursos y sus entidades asociadas. Esquema adaptado de la
arquitectura propuesta en [42].
Ogre::ScriptLoader
ResourceAlloc Ogre::TextureManager
Ogre::SkeletonManager
Ogre::MeshManager
Ogre::ResourceManager Ogre::MaterialManager
Ogre::GPUProgramManager
Ogre::FontManager
Ogre::CompositeManager
Figura 1.11: Diagrama de clases asociado al gestor de recursos de Ogre 3D, representado por la clase
Ogre::ResourceManager.
Front end
Efectos visuales
Interfaz con el
dispositivo grfico
Motor de rendering
Figura 1.12: Visin conceptual de la arquitectura general de un motor de rendering. Esquema simplicado
de la arquitectura discutida en [42].
Por otra parte, dicha capa tambin gestiona el estado del hardware grco y los
shaders asociados. Bsicamente, cada primitiva recibida por esta capa tiene asociado
un material y se ve afectada por diversas fuentes de luz. As mismo, el material descri-
be la textura o texturas utilizadas por la primitiva y otras cuestiones como por ejemplo
qu pixel y vertex shaders se utilizarn para renderizarla.
La capa superior a la de renderizado de bajo nivel se denomina scene graph/-
culling y optimizaciones y, desde un punto de vista general, es la responsable de
seleccionar qu parte o partes de la escena se enviarn a la capa de rendering. Esta se-
leccin, u optimizacin, permite incrementar el rendimiento del motor de rendering,
debido a que se limita el nmero de primitivas geomtricas enviadas a la capa de nivel
inferior.
Aunque en la capa de rendering slo se dibujan las primitivas que estn dentro del
campo de visin de la cmara, es decir, dentro del viewport, es posible aplicar ms
optimizaciones que simpliquen la complejidad de la escena a renderizar, obviando
aquellas partes de la misma que no son visibles desde la cmara. Este tipo de optimi-
zaciones son crticas en juegos que tenga una complejidad signicativa con el objetivo
de obtener tasas de frames por segundo aceptables.
Una de las optimizaciones tpicas consiste en hacer uso de estructuras de datos de
subdivisin espacial para hacer ms eciente el renderizado, gracias a que es posible
determinar de una manera rpida el conjunto de objetos potencialmente visibles. Di-
chas estructuras de datos suelen ser rboles, aunque tambin es posible utilizar otras
alternativas. Tradicionalmente, las subdivisiones espaciales se conocen como scene
graph (grafo de escena), aunque en realidad representan un caso particular de estruc-
tura de datos.
Por otra parte, en esta capa tambin es comn integrar mtodos de culling, como
por ejemplo aquellos basados en utilizar informacin relevante de las oclusiones para
determinar qu objetos estn siendo solapados por otros, evitando que los primeros se
tengan que enviar a la capa de rendering y optimizando as este proceso.
Idealmente, esta capa debera ser independiente de la capa de renderizado, permi-
tiendo as aplicar distintas optimizaciones y abstrayndose de la funcionalidad rela-
tiva al dibujado de primitivas. Un ejemplo representativo de esta independencia est
representado por OGRE (Object-Oriented Graphics Rendering Engine) y el uso de la
losofa plug & play, de manera que el desarrollador puede elegir distintos diseos de
grafos de escenas ya implementados y utilizarlos en su desarrollo.
Filosofa Plug & Play Sobre la capa relativa a las optimizaciones se sita la capa de efectos visuales, la
cual proporciona soporte a distintos efectos que, posteriormente, se puedan integrar en
Esta losofa se basa en hacer uso los juegos desarrollados haciendo uso del motor. Ejemplos representativos de mdulos
de un componente funcional, hard-
ware o software, sin necesidad de
que se incluyen en esta capa son aqullos responsables de gestionar los sistemas de
congurar ni de modicar el fun- partculos (humo, agua, etc), los mapeados de entorno o las sombras dinmicas.
cionamiento de otros componentes
asociados al primero. Finalmente, la capa de front-end suele estar vinculada a funcionalidad relativa
a la superposicin de contenido 2D sobre el escenario 3D. Por ejemplo, es bastante
comn utilizar algn tipo de mdulo que permita visualizar el men de un juego o la
interfaz grca que permite conocer el estado del personaje principal del videojuego
(inventario, armas, herramientas, etc). En esta capa tambin se incluyen componentes
para reproducir vdeos previamente grabados y para integrar secuencias cinemticas,
a veces interactivas, en el propio videojuego. Este ltimo componente se conoce como
IGC (In-Game Cinematics) system.
[22] CAPTULO 1. INTRODUCCIN
Por otra parte, algunos juegos incluyen sistemas realistas o semi-realistas de simu-
lacin dinmica. En el mbito de la industria del videojuego, estos sistemas se suelen
denominar sistema de fsica y estn directamente ligados al sistema de gestin de
colisiones.
Actualmente, la mayora de compaas utilizan motores de colisin/fsica desarro-
llados por terceras partes, integrando estos kits de desarrollo en el propio motor. Los
ms conocidos en el mbito comercial son Havok, el cual representa el estndar de
facto en la industria debido a su potencia y rendimiento, y PhysX, desarrollado por
NVIDIA e integrado en motores como por ejemplo el Unreal Engine 3.
En el mbito del open source, uno de los ms utilizados es ODE. Sin embargo,
en este curso se har uso del motor de simulacin fsica Bullet14 , el cual se utiliza
actualmente en proyectos tan ambiciosos como la suite 3D Blender.
Sistema de scripting
Subsistema de juego
Figura 1.13: Visin conceptual de la arquitectura general del subsistema de juego. Esquema simplicado
de la arquitectura discutida en [42].
Este subsistema sirve tambin como capa de aislamiento entre las capas de ms
bajo nivel, como por ejemplo la de rendering, y el propio funcionamiento del juego. Diseando juegos
Es decir, uno de los principales objetivos de diseo que se persiguen consiste en inde-
pendizar la lgica del juego de la implementacin subyacente. Por ello, en esta capa es Los diseadores de los niveles de un
juego, e incluso del comportamien-
bastante comn encontrar algn tipo de sistema de scripting o lenguaje de alto nivel to de los personajes y los NPCs,
para denir, por ejemplo, el comportamiento de los personajes que participan en el suelen dominar perfectamente los
juego. lenguajes de script, ya que son su
principal herramienta para llevar a
cabo su tarea.
C1
1.2. Arquitectura del motor. Visin general [25]
El modelo de objetos del juego est intimamente ligado al modelo de objetos soft-
ware y se puede entender como el conjunto de propiedades del lenguaje, polticas y
convenciones utilizadas para implementar cdigo utilizando una losofa de orienta-
cin a objetos. As mismo, este modelo est vinculado a cuestiones como el lenguaje
de programacin empleado o a la adopcin de una poltica basada en el uso de patrones
de diseo, entre otras.
En la capa de subsistema de juego se integra el sistema de eventos, cuya principal
responsabilidad es la de dar soporte a la comunicacin entre objetos, independiente-
mente de su naturaleza y tipo. Un enfoque tpico en el mundo de los videojuegos con-
siste en utilizar una arquitectura dirigida por eventos, en la que la principal entidad es
el evento. Dicho evento consiste en una estructura de datos que contiene informacin
relevante de manera que la comunicacin est precisamente guiada por el contenido
del evento, y no por el emisor o el receptor del mismo. Los objetos suelen implementar
manejadores de eventos (event handlers) para tratarlos y actuar en consecuencia.
Por otra parte, el sistema de scripting permite modelar fcilmente la lgica del
juego, como por ejemplo el comportamiento de los enemigos o NPCs, sin necesidad
de volver a compilar para comprobar si dicho comportamiento es correcto o no. En
algunos casos, los motores de juego pueden seguir en funcionamiento al mismo tiempo
que se carga un nuevo script.
Finalmente, en la capa del subsistema de juego es posible encontrar algn mdulo
que proporcione funcionalidad aadida respecto al tratamiento de la IA, normalmente
de los NPCs. Este tipo de mdulos, cuya funcionalidad se suele incluir en la propia
capa de software especca del juego en lugar de integrarla en el propio motor, son
cada vez ms populares y permiten asignar comportamientos preestablecidos sin nece-
sidad de programarlos. En este contexto, la simulacin basada en agentes [102] cobra
especial relevancia.
Este tipo de mdulos pueden incluir aspectos relativos a problemas clsicos de la
IA, como por ejemplo la bsqueda de caminos ptimos entre dos puntos, conocida
como pathnding, y tpicamente vinculada al uso de algoritmos A* [79]. As mismo,
tambin es posible hacer uso de informacin privilegiada para optimizar ciertas tareas,
Im all ears!
como por ejemplo la localizacin de entidades de inters para agilizar el clculo de
El apartado sonoro de un juego es aspectos como la deteccin de colisiones.
especialmente importante para que
el usuario se sienta inmerso en el
mismo y es crtico para acompaar
de manera adecuada el desarrollo de
dicho juego.
[26] CAPTULO 1. INTRODUCCIN
1.2.12. Audio
Tradicionalmente, el mundo del desarrollo de videojuegos siempre ha prestado
ms atencin al componente grco. Sin embargo, el apartado sonoro tambin tiene
una gran importancia para conseguir una inmersin total del usuario en el juego. Por
ello, el motor de audio ha ido cobrando ms y ms relevancia.
Asimismo, la aparicin de nuevos formatos de audio de alta denicin y la popu-
laridad de los sistemas de cine en casa han contribuido a esta evolucin en el cada vez
ms relevante apartado sonoro.
Actualmente, al igual que ocurre con otros componentes de la arquitectura del mo-
tor de juego, es bastante comn encontrar desarrollos listos para utilizarse e integrarse
en el motor de juego, los cuales han sido realizados por compaas externas a la del
propio motor. No obstante, el apartado sonoro tambin requiere modicaciones que
son especcas para el juego en cuestin, con el objetivo de obtener un alto de grado
de delidad y garantizar una buena experiencia desde el punto de visto auditivo.
A
ctualmente, existen un gran nmero de aplicaciones y herramientas que permi-
ten a los desarrolladores de videojuegos, y de aplicaciones en general, aumen-
tar su productividad a la hora de construir software, gestionar los proyectos y
recursos, as como automatizar procesos de construccin.
En este captulo, se pone de maniesto la importancia de la gestin en un proyec-
to software y se muestran algunas de las herramientas de desarrollo ms conocidas
en sistemas GNU/Linux. La eleccin de este tipo de sistema no es casual. Por un la-
do, se trata de Software Libre, lo que permite a desarrolladores estudiar, aprender y
entender lo que hace el cdigo que se ejecuta. Por otro lado, probablemente sea el
mejor sistema operativo para construir y desarrollar aplicaciones debido al gran n-
mero de herramientas que proporciona. Es un sistema hecho por programadores para
programadores.
2.1. Introduccin
En la construccin de software no trivial, las herramientas de gestin de proyec-
tos y de desarrollo facilitan la labor de las personas que lo construyen. Conforme el
software se va haciendo ms complejo y se espera ms funcionalidad de l, se hace
necesario el uso de herramientas que permitan automatizar los procesos del desarrollo,
as como la gestin del proyecto y su documentacin.
Adems, dependiendo del contexto, es posible que existan otros integrantes del
proyecto que no tengan formacin tcnica y que necesiten realizar labores sobre el
producto como traducciones, pruebas, diseo grco, etc.
27
[28] CAPTULO 2. HERRAMIENTAS DE DESARROLLO
C2
Cdigo fuente, cdigo objeto y cdigo ejecutable
Compilador
Enlazador
Un programa puede estar compuesto por varios mdulos, lo cual permite que un
proyecto pueda ser ms mantenible y manejable. Los mdulos pueden tener indepen-
dencias entre s y la comprobacin y resolucin de estas dependencias corren a cargo
del enlazador. El enlazador toma como entrada el cdigo objeto.
Bibliotecas
Una de las principales ventajas del software es la reutilizacin del cdigo. Normal-
mente, los problemas pueden resolverse utilizando cdigo ya escrito anteriormente y
la reutilizacin del mismo se vuelve un aspecto clave para el tiempo de desarrollo del
producto. Las bibliotecas ofrecen una determinada funcionalidad ya implementada
para que sea utilizada por programas. Las bibliotecas se incorporan a los programas
durante el proceso de enlazado.
2.2. Compilacin, enlazado y depuracin [31]
C2
Las bibliotecas pueden enlazarse contra el programa de dos formas:
Compilacin
C2
Comprobacin y resolucin de smbolos y dependencias a nivel de declaracin.
Realizar optimizaciones.
Ensamblador
Enlazador
GNU Linker Con todos los archivos objetos el enlazador (linker) es capaz de generar el eje-
cutable o cdigo binario nal. Algunas de las tareas que se realizan en el proceso de
GNU Linker tambin forma parte de enlazado son las siguientes:
la distribucin GNU Binutils y se
corresponde con el programa ld.
Seleccin y ltrado de los objetos necesarios para la generacin del binario.
Comprobacin y resolucin de smbolos y dependencias a nivel de denicin.
Realizacin del enlazado (esttico y dinmico) de las bibliotecas.
2.2.4. Ejemplos
Como se ha mostrado, el proceso de compilacin est compuesto por varias fases
bien diferenciadas. Sin embargo, con GCC se integra todo este proceso de forma que,
a partir del cdigo fuente se genere el binario nal.
En esta seccin se mostrarn ejemplos en los que se crea un ejecutable al que,
posteriormente, se enlaza con una biblioteca esttica y otra dinmica.
[34] CAPTULO 2. HERRAMIENTAS DE DESARROLLO
Compilacin de un ejecutable
1 #include <iostream>
2
3 using namespace std;
4
5 class Square {
6 private:
7 int side_;
8
9 public:
10 Square(int side_length) : side_(side_length) { };
11 int getArea() const { return side_*side_; };
12 };
13
14 int main () {
15 Square square(5);
16 cout << "Area: " << square.getArea() << endl;
17 return 0;
18 }
En C++, los programas que generan un ejecutable deben tener denida la fun-
cin main, que ser el punto de entrada de la ejecucin. El programa es trivial: se
dene una clase Square que representa a un cuadrado. sta implementa un mtodo
getArea() que devuelve el rea del cuadrado.
Suponiendo que el archivo que contiene el cdigo fuente se llama main.cpp,
para construir el binario utilizaremos g++, el compilador de C++ que se incluye en
GCC. Se podra utilizar gcc y que se seleccionara automticamente el compilador.
Sin embargo, es una buena prctica utilizar el compilador correcto:
C2
Compilacin de un ejecutable (modular)
Para construir el programa, se debe primero construir el cdigo objeto del mdulo
y aadirlo a la compilacin de la funcin principal main. Suponiendo que el archivo
de cabecera se encuentra en un directorio llamado headers, la compilacin puede
realizarse de la siguiente manera:
Con la opcin -I, que puede aparecer tantas veces como sea necesario, se puede
aadir rutas donde se buscarn las cabeceras. Ntese que, por ejemplo, en main.cpp
se incluyen las cabeceras usando los smbolos <> y . Se recomienda utilizar los pri-
meros para el caso en que las cabeceras forman parte de una API pblica (si existe) y
deban ser utilizadas por otros programas. Por su parte, las comillas se suelen utilizar
para cabeceras internas al proyecto. Las rutas por defecto son el directorio actual . pa-
ra las cabeceras incluidas con y para el resto el directorio del sistema (normalmente,
/usr/include).
Como norma general, una buena costumbre es generar todos los archivos de
cdigo objeto de un mdulo y aadirlos a la compilacin con el programa
principal.
Para este ejemplo se supone que se pretende construir una biblioteca con la que se
pueda enlazar estticamente y que contiene una jerarqua de clases correspondientes
a 3 tipos de guras (Figure): Square, Triangle y Circle. Cada gura est
implementada como un mdulo (cabecera + implementacin):
C2
Listado 2.9: Triangle.h
1 #include <Figure.h>
2
3 class Triangle : public Figure {
4 private:
5 float base_;
6 float height_;
7
8 public:
9 Triangle(float base_, float height_);
10 float getArea() const;
11 };
Como se puede ver, se utiliza GCC directamente para generar la biblioteca dinmi-
ca. La compilacin y enlazado con el programa principal se realiza de la misma forma
que en el caso del enlazado esttico. Sin embargo, la ejecucin del programa principal
es diferente. Al tratarse de cdigo objeto que se cargar en tiempo de ejecucin, exis-
ten una serie de rutas predenadas donde se buscarn las bibliotecas. Por defecto, son
las mismas que para el proceso de enlazado.
Tambin es posible aadir rutas modicando la variable LD_LIBRARY_PATH:
$ LD_LIBRARY_PATH=. ./main
2.2. Compilacin, enlazado y depuracin [39]
C2
2.2.5. Otras herramientas
La gran mayora de las herramientas utilizadas hasta el momento forman parte
de la distribucin GNU Binutils1 que se proporcionan en la mayora de los sistemas
GNU/Linux. Existen otras herramientas que se ofrecen en este misma distribucin y
que pueden ser de utilidad a lo largo del proceso de desarrollo:
GDB necesita informacin extra que, por defecto, GCC no proporciona para po-
der realizar las tareas de depuracin. Para ello, el cdigo fuente debe ser compilado
con la opcin -ggdb. Todo el cdigo objeto debe ser compilado con esta opcin de
compilacin, por ejemplo:
Para depurar no se debe hacer uso de las optimizaciones. stas pueden gene-
rar cdigo que nada tenga que ver con el original.
C2
$ ./main
Main start
Function A: 24
Segmentation fault
Una violacin de segmento (segmentation fault) es uno de los errores lgicos tpi-
cos de los lenguajes como C++. El problema es que se est accediendo a una zona de
la memoria que no ha sido reservada para el programa, por lo que el sistema operativo
interviene denegando ese acceso indebido.
A continuacin, se muestra cmo iniciar una sesin de depuracin con GDB para
encontrar el origen del problema:
$ gdb main
...
Reading symbols from ./main done.
(gdb)
Como se puede ver, GDB ha cargado el programa, junto con los smbolos de depu-
racin necesarios, y ahora se ha abierto una lnea de rdenes donde el usuario puede
especicar sus acciones.
Examinando el contenido
Abreviatura Para comenzar la ejecucin del programa se puede utilizar la orden start:
Todas las rdenes de GDB pueden (gdb) start
escribirse utilizando su abreviatura. Temporary breakpoint 1 at 0x400d31: file main.cpp, line 26.
Ej: run = r. Starting program: main
(gdb) list
21 cout << "Return B: " << functionB("Hi", test) << endl;
22 return 5;
23 }
24
25 int main() {
26 cout << "Main start" << endl;
27 cout << "Return A: " << functionA(24) << endl;
28 return 0;
29 }
(gdb)
[42] CAPTULO 2. HERRAMIENTAS DE DESARROLLO
Como el resto de rdenes, list acepta parmetros que permiten ajustar su com-
portamiento.
Las rdenes que permiten realizar una ejecucin controlada son las siguientes:
(gdb) s
Main start
27 cout << "Return A: " << functionA(24) << endl;
(gdb)
functionA (a=24) at main.cpp:18
18 cout << "Function A: " << a << endl;
(gdb)
En este punto se puede hacer uso de las rdenes para mostrar el contenido del
parmetro a de la funcin functionA():
(gdb) print a
$1 = 24
(gdb) print &a
$2 = (int *) 0x7fffffffe1bc
La ejecucin est detenida en la lnea 19 donde un comentario nos avisa del error.
Se est creando un puntero con el valor NULL. Posteriormente, se invoca un mtodo
sobre un objeto que no est convenientemente inicializado, lo que provoca la violacin
de segmento:
(gdb) next
20 test->setValue(15);
(gdb)
2.2. Compilacin, enlazado y depuracin [43]
C2
Program received signal SIGSEGV, Segmentation fault.
0x0000000000400df2 in Test::setValue (this=0x0, a=15) at main.cpp:8
8 void setValue(int a) { _value = a; }
Breakpoints
La ejecucin paso a paso es una herramienta til para una depuracin de grano
no. Sin embargo, si el programa realiza grandes iteraciones en bucles o es demasiado
grande, puede ser un poco incmodo (o inviable). Si se tiene la sospecha sobre el lugar
donde est el problema se pueden utilizar puntos de ruptura o breakpoints que permite
detener el ujo del programa en un punto determinado por el usuario.
Con el ejemplo ya arreglado, se congura un breakpoint en la funcin functionB()
y otro en la lnea 28 con la orden break. A continuacin, se ejecuta el programa hasta
que se alcance el breakpoint con la orden run (r):
No hace falta escribir todo!. Utiliza TAB para completar los argumentos de
una orden.
Con la orden continue (c) la ejecucin avanza hasta el siguiente punto de rup-
tura (o n del programa):
(gdb) continue
Continuing.
Function B: Hi, 15
Return B: 3.14
Return A: 5
(gdb)
Stack y frames
En muchas ocasiones, los errores vienen debidos a que las llamadas a funciones no
se realizan con los parmetros adecuados. Es comn pasar punteros no inicializados o
valores incorrectos a una funcin/mtodo y, por tanto, obtener un error lgico.
Para gestionar las llamadas a funciones y procedimientos, en C/C++ se utiliza la
pila (stack ). En la pila se almacenan frames, estructuras de datos que registran las
variables creadas dentro de una funcin as como otra informacin de contexto. GDB
permite manipular la pila y los frames de forma que sea posible identicar un uso
indebido de las funciones.
Con la ejecucin parada en functionB(), se puede mostrar el contenido de la
pila con la orden backtrace (bt):
(gdb) backtrace
#0 functionB (str1=..., t=0x602010) at main.cpp:13
#1 0x0000000000400d07 in functionA (a=24) at main.cpp:21
#2 0x0000000000400db3 in main () at main.cpp:27
(gdb)
Con up y down se puede navegar por los frames de la pila, y con frame se puede
seleccionar uno en concreto:
(gdb) up
#1 0x0000000000400d07 in functionA (a=24) at gdb-fix.cpp:21
21 cout << "Return B: " << functionB("Hi", test) << endl;
(gdb)
#2 0x0000000000400db3 in main () at gdb-fix.cpp:27
27 cout << "Return A: " << functionA(24) << endl;
(gdb) frame 0
#0 functionB (str1=..., t=0x602010) at gdb-fix.cpp:13
13 cout << "Function B: " << str1 << ", " << t->getValue() << endl;
(gdb)
Una vez seleccionado un frame, se puede obtener toda la informacin del mismo, Invocar funciones
adems de modicar las variables y argumentos:
La orden call se puede utilizar pa-
ra invocar funciones y mtodos .
(gdb) print *t
$1 = {_value = 15}
(gdb) call t->setValue(1000)
(gdb) print *t
$2 = {_value = 1000}
(gdb)
2.2. Compilacin, enlazado y depuracin [45]
C2
Entornos grcos para GDB
GDB TUI: normalmente, la distribucin GDB
de
incorpora una interfaz basada
en modo texto accesible pulsando Ctrl + x y, a continuacin, a .
ddd y xxgdb: las libreras grcas utilizadas son algo anticuadas, pero facilitan
el uso de GDB.
gdb-mode: modo de Emacs para GDB. Dentro del modo se puede activar la
opcin M-x many-windows para obtener buffers con toda la informacin
disponible.
kdbg: ms atractivo grcamente (para escritorios KDE).
Estructura
Existen algunos objetivos especiales como all, install y clean que sirven
como regla de partida inicial, para instalar el software construido y para limpiar del
proyecto los archivos generados, respectivamente.
Tomando como ejemplo la aplicacin que hace uso de la biblioteca dinmica, el
siguiente listado muestra el Makele que generara tanto el programa ejecutable como
la biblioteca esttica:
C2
$ make
$ make clean
Como ejercicio se plantean las siguientes cuestiones: qu ocurre si una vez cons-
truido el proyecto se modica algn chero .cpp? Y si se modica una cabecera
.h? Se podra construir una regla con patrn genrica para construir la biblioteca
esttica? Cmo lo haras?.
[48] CAPTULO 2. HERRAMIENTAS DE DESARROLLO
Reglas implcitas
Como se puede ver, Make puede generar automticamente los archivos objeto .o
a partir de la coincidencia con el nombre del chero fuente (que es lo habitual). Por
ello, no es necesario especicar cmo construir los archivos .o de la biblioteca, ni
siquiera la regla para generar main ya que asume de que se trata del ejecutable (al
existir un chero llamado main.cpp).
Las variables de usuario que se han denido permiten congurar los ags de com-
pilacin que se van a utilizar en las reglas explcitas. As:
C2
Funciones
GNU Make proporciona un conjunto de funciones que pueden ser de gran ayuda
a la hora de construir los Makeles. Muchas de las funciones estn diseadas para
el tratamiento de cadenas, ya que se suelen utilizar para transformar los nombres de
archivos. Sin embargo, existen muchas otras como para realizar ejecucin condicional,
bucles y ejecutar rdenes de consola. En general, las funciones tienen el siguiente
formato:
$(nombre arg1,arg2,arg3,...)
Las funciones se pueden utilizar en cualquier punto del Makele, desde las ac-
ciones de una regla hasta en la denicin de un variable. En el siguiente listado se
muestra el uso de algunas de estas funciones:
$ DEBUG=yes make
Ms informacin
GNU Make es una herramienta que est en continuo crecimiento y esta seccin s-
lo ha sido una pequea presentacin de sus posibilidades. Para obtener ms informa-
cin sobre las funciones disponibles, otras variables automticas y objetivos predeni-
dos se recomiendo utilizar el manual en lnea de Make2 , el cual siempre se encuentra
actualizado.
C2
Por otro lado, sera interesante tener la posibilidad de realizar desarrollos en para-
lelo de forma que exista una versin estable de todo el proyecto y otra ms experi-
mental del mismo donde se probaran diferentes algoritmos y diseos. De esta forma,
probar el impacto que tendra nuevas implementaciones sobre el proyecto no afec-
tara a una versin ms ocial. Tambin es comn que se desee aadir una nueva
funcionalidad y sta se realiza en paralelo junto con otros desarrollos.
Los sistemas de control de versiones o Version Control System (VCS) permiten
gestionar los archivos de un proyecto (y sus versiones) y que sus integrantes puedan
acceder remotamente a ellos para descargarlos, modicarlos y publicar los cambios.
Tambin se encargan de detectar posibles conictos cuando varios usuarios modican
los mismos archivos y de proporcionar un sistema bsico de registro de cambios.
Como norma general, al VCS debe subirse el archivo fuente y nunca el archivo
generado. No se deben subir binarios ya que no es fcil seguir la pista a sus
modicaciones.
Existen diferentes criterios para clasicar los diferentes VCS existentes. Uno de
los que ms inuye tanto en la organizacin y uso del repositorio es si se trata de VCS
centralizado o distribuido. En la gura 2.6 se muestra un esquema de ambas losofas.
Los VCS centralizados como CVS o Subversion se basan en que existe un nodo
servidor con el que todos los clientes conectan para obtener los archivos, subir modi-
caciones, etc. La principal ventaja de este esquema reside en su sencillez: las diferentes
versiones del proyecto estn nicamente en el servidor central, por lo que los posibles
conictos entre las modicaciones de los clientes pueden detectarse y gestionarse ms
fcilmente. Sin embargo, el servidor es un nico punto de fallo y en caso de cada, los
clientes quedan aislados.
Por su parte, en los VCS distribuidos como Mercurial o Git, cada cliente tiene un
repositorio local al nodo en el que se suben los diferentes cambios. Los cambios pue-
den agruparse en changesets, lo que permite una gestin ms ordenada. Los clientes
actan de servidores para el resto de los componentes del sistema, es decir, un cliente
puede descargarse una versin concreta de otro cliente.
Esta arquitectura es tolerante a fallos y permite a los clientes realizar cambios sin
necesidad de conexin. Posteriormente, pueden sincronizarse con el resto. An as,
un VCS distribuido puede utilizarse como uno centralizado si se ja un nodo como
servidor, pero se perderan algunas posibilidades que este esquema ofrece.
[52] CAPTULO 2. HERRAMIENTAS DE DESARROLLO
Subversion
Inicialmente, los clientes pueden descargarse el repositorio por primera vez utili-
zando la orden checkout:
$ mkdir doc
$ echo "This is a new file" > doc/new_file
$ echo "Other file" > other_file
$ svn add doc other_file
A doc
A doc/new_file
A other_file
$ svn commit
C2
$ svn update -r REVISION
$ svn update
C other_file
At revision X+1.
Ntese que este commit slo aade los cambios hechos en new_file, aceptando
los cambios en other_file que hizo user2.
Mercurial
$ hg init /home/user1/myproyect
Al igual que ocurre con Subversion, este directorio debe ser accesible median-
te algn mecanismo (preferiblemente, que sea seguro) para que el resto de usuarios
Figura 2.8: Logotipo del proyecto pueda acceder. Sin embargo, el usuario user1 puede trabajar directamente sobre ese
Mercurial. directorio.
[54] CAPTULO 2. HERRAMIENTAS DE DESARROLLO
Para obtener una versin inicial, otro usuario (user2) debe clonar el repositorio.
Basta con ejecutar lo siguiente:
$ hg clone ssh://user2@host//home/user1/myproyect
A partir de este instante, user2 tiene una versin inicial del proyecto extrada a
partir de la del usuario user1. De forma muy similar a Subversion, con la orden add
se pueden aadir archivos y directorios.
Mientras que en el modelo de Subversion, los clientes hacen commit y update
para subir cambios y obtener la ltima versin, respectivamente; en Mercurial es algo
ms complejo, ya que existe un repositorio local. Como se muestra en la gura 2.9, la
operacin commit (3) sube los cambios a un repositorio local que cada cliente tiene.
Cada commit se considera un changeset, es decir, un conjunto de cambios agrupa-
dos por un mismo ID de revisin. Como en el caso de Subversion, en cada commit se
pedir una breve descripcin de lo que se ha modicado.
Una vez hecho todos commits, para llevar estos cambios a un servidor remoto se
debe ejecutar la orden de push (4). Siguiendo con el ejemplo, el cliente user2 lo
enviar por defecto al repositorio del que hizo la operacin clone.
El sentido inverso, es decir, traerse los cambios del servidor remoto a la copia
local, se realiza tambin en 2 pasos: pull (1) que trae los cambios del repositorio
remoto al repositorio local; y update (2), que aplica dichos cambios del repositorio
local al directorio de trabajo. Para hacer los dos pasos al mismo tiempo, se puede hacer
lo siguiente:
$ hg pull -u
Para evitar conictos con otros usuarios, una buena costumbre antes de reali-
zar un push es conveniente obtener los posibles cambios en el servidor con
pull y update.
2.3. Gestin de proyectos y documentacin [55]
C2
Para ver cmo se gestionan los conictos en Mercurial, supngase que user1
realiza lo siguiente:
Git
Diseado y desarrollado por Linus Torvalds para el proyecto del kernel Linux, Git
es un VCS distribuido que cada vez es ms utilizado por la comunidad de desarrolla-
dores. En trminos generales, tiene una estructura similar a Mercurial: independencia
entre repositorio remotos y locales, gestin local de cambios, etc.
Sin embargo, Git es en ocasiones preferido sobre Mercurial por algunas de sus
caractersticas propias:
[56] CAPTULO 2. HERRAMIENTAS DE DESARROLLO
Cada head apunta a un commit y tiene un nombre simblico para poder ser re-
ferenciado. Por defecto, todos los respositorios Git tienen un head llamado master.
HEAD (ntese todas las letras en mayscula) es una referencia al head usado en cada
instante. Por lo tanto, en un repositorio Git, en un estado sin cambios, HEAD apuntar
al master del respositorio.
Para crear un repositorio donde sea posible que otros usuarios puedan subir cam-
bios se utiliza la orden init:
C2
Figura 2.11: Esquema del ujo de trabajo bsico en Git
$ git status
# On branch master
nothing to commit, working directory clean
Ntese como la ltima llamada a status no muestra ningn cambio por subir al
repositorio local. Esto signica que la copia de trabajo del usuario est sincronizada
con el repositorio local (todava no se han realizado operaciones con el remoto). Se
puede utilizar reflog para ver la historia:
[58] CAPTULO 2. HERRAMIENTAS DE DESARROLLO
$ git reflog
2f81676 HEAD@{0}: commit: Test example: initial version
...
diff se utiliza para ver cambios entre commits, ramas, etc. Por ejemplo, la si-
guiente orden muestra las diferencias entre master del repositorio local y master del
remoto:
Para modicar cdigo y subirlo al repositorio local se sigue el mismo procedi- gitk
miento: (1) realizar la modicacin, (2) usar add para aadir el archivo cambiado,
(3) hacer commit para subir el cambio al repositorio local. Sin embargo, como se ha Para entender mejor estos concep-
dicho anteriormente, una buena caracterstica de Git es la creacin y gestin de ramas tos y visualizarlos durante el proce-
(branches) locales que permiten hacer un desarrollo en paralelo. Esto es muy til ya so de desarrollo, existen herramien-
tas grcas como gitk que permi-
que, normalmente, en el ciclo de vida de desarrollo de un programa se debe simulta- ten ver todos los commits y heads
near tanto la creacin de nuevas caractersticas como arreglos de errores cometidos. en cada instante.
En Git, estas ramas no son ms que referencias a commits, por lo que son muy ligeras
y pueden llevarse de un sitio a otro de forma sencilla.
Como ejemplo, la siguiente secuencia de rdenes crea una rama local a partir del
HEAD, modica un archivo en esa rama y nalmente realiza un merge con master:
$ git branch
* NEW-BRANCH
master
Ntese cmo la orden branch muestra la rama actual marcada con el smbolo
*. Utilizando las rdenes log y show se pueden listar los commits recientes. Estas
rdenes aceptan, adems de identicadores de commits, ramas y rangos temporales
de forma que pueden obtenerse gran cantidad de informacin de ellos.
Finalmente, para desplegar los cambios en el repositorio remoto slo hay que uti-
lizar:
$ git push
2.3. Gestin de proyectos y documentacin [59]
C2
Git utiliza cheros como .gitconfig y .gitignore para cargar con-
guraciones personalizadas e ignorar cheros a la hora de hacer los commits,
respectivamente. Son muy tiles. Revisa la documentacin de git-config
y gitignore para ms informacin.
2.3.2. Documentacin
Uno de los elementos ms importantes que se generan en un proyecto es la do-
cumentacin: cualquier elemento que permita entender mejor tanto el proyecto en su
totalidad como sus partes, de forma que facilite el proceso de mantenimiento en el
futuro. Adems, una buena documentacin har ms sencilla la reutilizacin de com-
ponentes.
Existen muchos formatos de documentacin que pueden servir para un proyecto
software. Sin embargo, muchos de ellos, tales como PDF, ODT, DOC, etc., son for-
matos binarios por lo que no son aconsejables para utilizarlos en un VCS. Adems,
utilizando texto plano es ms sencillo crear programas que automaticen la generacin
de documentacin, de forma que se ahorre tiempo en este proceso.
Por ello, aqu se describen algunas formas de crear documentacin basadas en
texto plano. Obviamente, existen muchas otras y, seguramente, sean tan vlidas como
las que se proponen aqu.
Doxygen
8 /**
9 \param s the name of the Test.
10 */
11 Test(string s);
12
13 /// Start running the test.
14 /**
15 \param max maximum time of test delay.
16 \param silent if true, do not provide output.
17 \sa Test()
18 */
19 int run(int max, bool silent);
20 };
$ doxygen .
reStructuredText
C2
30 | row 2 | | | |
31 +--------------+----------+-----------+-----------+
32
33 Images
34 ------
35
36 .. image:: gnu.png
37 :scale: 80
38 :alt: A title text
Como se puede ver, aunque RST aade una sintaxis especial, el texto es completa-
mente legible. sta es una de las ventajas de RST, el uso de etiquetas de formato que
no ensucian demasiado el texto.
YAML
Las forjas de desarrollo suelen ser accesibles via web, de forma que slo sea ne-
cesario un navegador para poder utilizar los diferentes servicios que ofrece. Depen-
diendo de la forja de desarrollo, se ofrecern ms o menos servicios. Sin embargo, los
expuestos hasta ahora son los que se proporcionan habitualmente. Existen forjas gra-
tuitas en Internet que pueden ser utilizadas para la creacin de un proyecto. Algunas
de ellas:
C2
SourceForge9 : probablemente, una de las forjas gratuitas ms conocidas. Pro-
piedad de la empresa GeekNet Inc., soporta Subversion, Git, Mercurial, Bazaar
y CVS.
Google Code10 : la forja de desarrollo de Google que soporta Git, Mercurial y
Subversion.
Redmine
9 http://sourceforge.net
10 http://code.google.com
C++. Aspectos Esenciales
Captulo 3
David Vallejo Fernndez
E
l lenguaje ms utilizado para el desarrollo de videojuegos comerciales es C++,
debido especialmente a su potencia, eciencia y portabilidad. En este captulo
se hace un recorrido por C++ desde los aspectos ms bsicos hasta las he-
rramientas que soportan la POO (Programacin Orientada a Objetos) y que permiten
disear y desarrollar cdigo que sea reutilizable y mantenible, como por ejemplo las
plantillas y las excepciones.
POO En esta seccin se realiza un recorrido por los aspectos bsicos de C++, hacien-
do especial hincapi en aquellos elementos que lo diferencian de otros lenguajes de
La programacin orientada a obje- programacin y que, en ocasiones, pueden resultar ms complicados de dominar por
tos tiene como objetivo la organi-
zacin ecaz de programas. Bsi- aquellos programadores inexpertos en C++.
camente, cada componente es un
objeto autocontenido que tiene una
serie de operaciones y de datos o
estado. Este planteamiento permi-
3.1.1. Introduccin a C++
te reducir la complejidad y gestio-
nar grandes proyectos de programa- C++ se puede considerar como el lenguaje de programacin ms importante en la
cin. actualidad. De hecho, algunas autores relevantes [81] consideran que si un programa-
dor tuviera que aprender un nico lenguaje, ste debera ser C++. Aspectos como su
sintaxis y su losofa de diseo denen elementos clave de programacin, como por
ejemplo la orientacin a objetos. C++ no slo es importante por sus propias caracters-
ticas, sino tambin porque ha sentado las bases para el desarrollo de futuros lenguajes
de programacin. Por ejemplo, Java o C# son descendientes directos de C++. Desde el
punto de vista profesional, C++ es sumamente importante para cualquier programador.
65
[66] CAPTULO 3. C++. ASPECTOS ESENCIALES
El origen de C++ est ligado al origen de C, ya que C++ est construido sobre
C. De hecho, C++ es un superconjunto de C y se puede entender como una versin
extendida y mejorada del mismo que integra la losofa de la POO y otras mejoras,
como por ejemplo la inclusin de un conjunto amplio de bibliotecas. Algunos autores
consideran que C++ surgi debido a la necesidad de tratar con programas de mayor
complejidad, siendo impulsado en gran parte por la POO.
C++ fue diseado por Bjarne Stroustrup1 en 1979. La idea de Stroustrup fue
aadir nuevos aspectos y mejoras a C, especialmente en relacin a la POO, de manera
que un programador de C slo que tuviera que aprender aquellos aspectos relativos a
la OO.
En el caso particular de la industria del videojuego, C++ se puede considerar co-
mo el estndar de facto debido principalmente a su eciencia y portabilidad. C++ es
una de los pocos lenguajes que posibilitan la programacin de alto nivel y, de manera
simultnea, el acceso a los recursos de bajo nivel de la plataforma subyacente. Por lo
tanto, C++ es una mezcla perfecta para la programacin de sistemas y para el desarro-
llo de videojuegos. Una de las principales claves a la hora de manejarlo ecientemente
en la industria del videojuego consiste en encontrar el equilibrio adecuado entre e-
ciencia, abilidad y mantenibilidad [27].
Figura 3.1: Bjarne Stroustrup,
creador del lenguaje de programa-
3.1.2. Hola Mundo! en C++ cin C++ y personaje relevante en
el mbito de la programacin.
A continuacin se muestra el clsico Hola Mundo! implementado en C++. En
este primer ejemplo, se pueden apreciar ciertas diferencias con respecto a un programa Dominar C++
escrito en C. Una de las mayores ventajas de
C++ es que es extremadamente po-
tente. Sin embargo, utilizarlo e-
Listado 3.1: Hola Mundo en C++ cientemente es dcil y su curva de
aprendizaje no es gradual.
1 /* Mi primer programa con C++. */
2
3 #include <iostream>
4 using namespace std;
5
6 int main () {
7
8 string nombre;
9
10 cout << "Por favor, introduzca su nombre... ";
11 cin >> nombre;
12 cout << "Hola " << nombre << "!"<< endl;
13
14 return 0;
15
16 }
La directiva include de la lnea 3 incluye la biblioteca
<iostream>, la cual soporta
el sistema de E/S de C++. A continuacin, en la 4 el programa le indica al compila-
dor que utilice el espacio de nombres std, en el que se declara la biblioteca estndar de
C++. Un espacio de nombres delimita una zona de declaracin en la que incluir dife-
rentes elementos de un programa. Los espacios de nombres siguen la misma losofa
que los paquetes en Java y tienen como objetivo organizar los programas. El hecho de
utilizar un espacio de nombres permite acceder a sus elementos y funcionalidades sin
tener que especicar a qu espacio pertenecen.
1 http://www2.research.att.com/~bs/
3.1. Utilidades bsicas [67]
En la lnea 10 de hace uso de cout (console output), la sentencia de salida por
C3
consola junto con el operador <<, redireccionando lo que queda a su derecha, es
decir, Por favor, introduzca su nombre..., hacia la salida por consola. A continuacin,
en la siguiente lnea se hace uso de cin (console input) junto con el operador >>
para redirigir la entrada proporcionado por teclado a la variable nombre. Note que
dicha variable es de tipo cadena, un tipo de datos de C++ que se dene como
un array
de caracteres nalizado con el carcter null. Finalmente, en la lnea 12 se saluda al
lector, utilizando adems la sentencia endl para aadir un retorno de carro a la salida
y limpiar el buffer.
int *ip;
edadptr = &edad;
miEdad = *edadptr
En C++ existe una relacin muy estrecha entre los punteros y los arrays, siendo
posible intercambiarlos en la mayora de casos. El siguiente listado de cdigo muestra
un sencillo ejemplo de indexacin de un array mediante aritmtica de punteros.
En la inicializacin del bucle for de la lnea 10 se aprecia cmo se asigna la direc-
cin de inicio del array s al puntero p para, posteriormente, indexar el array mediante
p para resaltar en maysculas el contenido del array una vez nalizada la ejecucin
del bucle. Note que C++ permite la inicializacin mltiple.
3.1. Utilidades bsicas [69]
C3
Los punteros son enormemente tiles y potentes. Sin embargo, cuando un puntero
almacena, de manera accidental, valores incorrectos, el proceso de depuracin puede
resultar un autntico quebradero de cabeza. Esto se debe a la propia naturaleza del
puntero y al hecho de que indirectamente afecte a otros elementos de un programa, lo
cual puede complicar la localizacin de errores.
El caso tpico tiene lugar cuando un puntero apunta a algn lugar de memoria que
no debe, modicando datos a los que no debera apuntar, de manera que el programa
muestra resultados indeseables posteriormente a su ejecucin inicial. En estos casos,
cuando se detecta el problema, encontrar la evidencia del fallo no es una tarea trivial,
ya que inicialmente puede que no exista evidencia del puntero que provoc dicho
error. A continuacin se muestran algunos de los errores tpicos a la hora de manejar
punteros [81].
1. No inicializar punteros. En el listado que se muestra a continuacin, p contiene
una direccin desconocida debido a que nunca fue denida. En otras palabras, no es
posible conocer dnde se ha escrito el valor contenido en edad.
No olvide que una de las claves para garantizar un uso seguro de los punteros
consiste en conocer en todo momento hacia dnde estn apuntando.
C3
2 using namespace std;
3
4 struct persona {
5 string nombre;
6 int edad;
7 };
8
9 void modificar_nombre (persona *p, const string& nuevo_nombre);
10
11 int main () {
12 persona p;
13 persona *q;
14
15 p.nombre = "Luis";
16 p.edad = 23;
17 q = &p;
18
19 cout << q->nombre << endl;
20 modificar_nombre(q, "Sergio");
21 cout << q->nombre << endl;
22
23 return 0;
24 }
25
26 void modificar_nombre (persona *p, const string& nuevo_nombre) {
27 p->nombre = nuevo_nombre;
28 }
C3
Las funciones tambin pueden devolver referencias. En C++, una de las mayores
utilidades de esta posibilidad es la sobrecarga de operadores. Sin embargo, en el lis-
tado que se muestra a continuacin se reeja otro uso potencial, debido a que cuando
se devuelve una referencia, en realidad se est devolviendo un puntero implcito al
valor de retorno. Por lo tanto, es posible utilizar la funcin en la parte izquierda de
una asignacin.
Funciones y referencias Como se puede apreciar en el siguiente listado, la funcin f devuelve una referen-
cia a un valor en punto otante de doble precisin, en concreto
a la variable global
En una funcin, las referencias se valor. La parte importante del cdigo est en la lnea 15 , en la que valor se actualiza
pueden utilizar como parmetros de
entrada o como valores de retorno. a 7,5, debido a que la funcin devuelve dicha referencia.
Las ventajas de las referencias sobre los punteros se pueden resumir en que utili-
zar referencias es una forma de ms alto nivel de manipular objetos, ya que permite al
desarrollador olvidarse de los detalles de gestin de memoria y centrarse en la lgica
del problema a resolver. Aunque pueden darse situaciones en las que los punteros son
ms adecuados, una buena regla consiste en utilizar referencias siempre que sea posi-
ble, ya que su sintaxis es ms limpia que la de los punteros y su uso es menos proclive
a errores.
3.2. Clases
C3
Listado 3.10: Clase Figura
1 class Figura
2 {
3 public:
4 Figura (double i, double j);
5 ~Figura ();
6
7 void setDim (double i, double j);
8 double getX () const;
9 double getY () const;
10
11 protected:
12 double _x, _y;
13 };
Note cmo las variables de clase se denen como protegidas, es decir, con una vi-
sibilidad privada fuera de dicha clase a excepcin de las clases que hereden de Figura,
tal y como se discutir en la seccin 3.3.1. El constructor y el destructor comparten el
nombre con la clase, pero el destructor tiene delante el smbolo ~.
Paso por referencia El resto de funciones sirven para modicar y acceder al estado de los objetos ins-
tanciados a partir de dicha clase. Note el uso del modicador const en las funciones
Recuerde utilizar parmetros por de acceso getX() y getY(), con el objetivo de informar de manera explcita al compila-
referencia const para minimizar el
nmero de copias de los mismos. dor de que dichas funciones no modican el estado de los objetos. A continuacin, se
muestra la implementacin de las funciones denidas en la clase Figura.
Uso de inline Antes de continuar discutiendo ms aspectos de las clases, resulta interesante in-
El modicador inline se suele in- troducir brevemente el concepto de funciones en lnea (inlining), una tcnica que
cluir despus de la declaracin de la puede reducir la sobrecarga implcita en las llamadas a funciones. Para ello, slo es
funcin para evitar lneas de cdigo necesario incluir el modicador inline delante de la declaracin de una funcin. Es-
demasiado largas (siempre dentro
del archivo de cabecera). Sin em-
ta tcnica permite obtener exactamente el mismo rendimiento que el acceso directo
bargo, algunos compiladores obli- a una variable sin tener que desperdiciar tiempo en ejecutar la llamada a la funcin,
gan a incluirlo en ambos lugares. interactuar con la pila del programa y volver de dicha funcin.
Las funciones en lnea no se pueden usar indiscriminadamente, ya que pueden
degradar el rendimiento de la aplicacin fcilmente. En primer lugar, el tamao del
ejecutable nal se puede disparar debido a la duplicidad de cdigo. As mismo, la
cach de cdigo tambin puede hacer que dicho rendimiento disminuya debido a las
continuas penalizaciones asociadas a incluir tantas funciones en lnea. Finalmente, los
tiempos de compilacin se pueden incrementar en grandes proyectos.
[76] CAPTULO 3. C++. ASPECTOS ESENCIALES
Una buena regla para usar de manera adecuada el modicador inline consiste
en evitar su uso hasta prcticamente completar el desarrollo de un proyecto. A
continuacin, se puede utilizar alguna herramienta de proling para detectar
si alguna funcin sencilla est entre las ms utilidas. Este tipo de funciones
son candidatas potenciales para modicarlas con inline y, en consecuencia,
elementos para mejorar el rendimiento del programa.
Al igual que ocurre con otros tipos de datos, los objetos tambin se pueden mani-
pular mediante punteros. Simplemente se ha de utilizar la misma notacin y recordar
que la aritmtica de punteros tambin se puede usar con objetos que, por ejemplo,
formen parte de un array.
Para los objetos creados en memoria dinmica, el operador new invoca al cons-
tructor de la clase de manera que dichos objetos existen hasta que explcitamente
se eliminen con el operador delete sobre los punteros asociados. A continuacin se
muestra un listado de cdigo que hace uso de la clase Figura previamente introducida.
C3
2 #include "Figura.h"
3 using namespace std;
4
5 int main () {
6 Figura *f1;
7 f1 = new Figura (1.0, 0.5);
8
9 cout << "[" << f1->getX() << ", " << f1->getY() << "]" << endl;
10
11 delete f1;
12 return 0;
13 }
Construyendo...
7
Destruyendo...
Destruyendo...
[78] CAPTULO 3. C++. ASPECTOS ESENCIALES
Como se puede apreciar, existe una llamada al constructor al crear a (lnea 24 ) y
dos llamadas al destructor. Como se ha comentado antes, cuando un objeto se pasa a
una funcin, entonces se crea una copia del mismo, la cual se destruye cuando naliza
la ejecucin de la funcin. Ante esta situacin surgen dos preguntas: i) se realiza una
llamada al constructor? y ii) se realiza una llamada al destructor?
En realidad, lo que ocurre cuando se pasa un objeto a una funcin es que se llama
al constructor de copia, cuya responsabilidad consiste en denir cmo se copia un
objeto. Si una clase no tiene un constructor de copia, entonces C++ proporciona uno
por defecto, el cual crea una copia bit a bit del objeto. En realidad, esta decisin es
bastante lgica, ya que el uso del constructor normal para copiar un objeto no generara
el mismo resultado que el estado que mantiene el objeto actual (generara una copia
con el estado inicial).
Sin embargo, cuando una funcin naliza y se ha de eliminar la copia del objeto,
entonces se hace uso del destructor debido a que la copia se encuentra fuera de su
mbito local. Por lo tanto, en el ejemplo anterior se llama al destructor tanto para la
copia como para el argumento inicial.
C3
Listado 3.14: Paso de objetos por valor
1 #include <iostream>
2 using namespace std;
3
4 class A {
5 int _valor;
6 public:
7 A(int valor): _valor(valor) {
8 cout << "Construyendo..." << endl;
9 }
10 ~A() {
11 cout << "Destruyendo..." << endl;
12 }
13
14 int getValor () const {
15 return _valor;
16 }
17 };
18
19 void mostrar (A a) {
20 cout << a.getValor() << endl;
21 }
22
23 int main () {
24 A a(7);
25 mostrar(a);
26 return 0;
27 }
C3
7
Destruyendo...
Destruyendo...
El constructor de copia se dene entre las lneas 18 y 21 , reservando una nueva
C3
regin de memoria para el contenido
de _valor y copiando el mismo en la variable
miembro. Por otra parte, las lneas 23-26 muestran la implementacin de la funcin
set, que modica el contenido de dicha variable miembro.
El siguiente listado muestra la implementacin de la funcin entrada, que pide
una cadena por teclado y devuelve un objeto que alberga la entrada proporcionada por
el usuario.
En primer lugar, el programa se comporta adecuadamente cuando se llama a entra-
da, particularmente cuando se devuelve la copia del objeto a, utilizando el constructor
de copia previamente denido. Sin embargo, el programar abortar abruptamente
cuando el objeto devuelto por entrada se asigna a obj en la funcin principal. Recuer-
de que en este caso se efectua una copia idntica. El problema reside en que obj.valor
apunta a la misma direccin de memoria que el objeto temporal, y ste ltimo se des-
truye despus de volver desde entrada, por lo que obj.valor apunta a memoria que
acaba de ser liberada. Adems, obj.valor se vuelve a liberar al nalizar el programa.
3.3.1. Herencia
El siguiente listado de cdigo muestra la clase base Vehculo que, desde un punto
de vista general, dene un medio de transporte por carretera. De hecho, sus variables
miembro son el nmero de ruedas y el nmero de pasajeros.
Herencia y acceso
La clase base anterior se puede extender para denir coches con una nueva carac-
terstica propia de los mismos, como se puede apreciar en el siguiente listado. El modicador de acceso cuando se
usa herencia es opcional. Sin em-
En este ejemplo no se han denido los constructores de manera intencionada para
discutir el acceso a los miembros de la clase. Como se puede apreciar en la lnea 5
bargo, si ste se especica ha de ser
public, protected o private. Por
del siguiente listado, la clase Coche hereda de la clase Vehculo, utilizando el operador defecto, su valor es private si la
clase derivada es efectivamente una
:. La palabra reservada public delante de Vehculo determina el tipo de acceso. En clase. Si la clase derivada es una es-
este caso concreto, el uso de public implica que todos los miembros pblicos de la tructura, entonces su valor por de-
fecto es public.
3.3. Herencia y polimorsmo [85]
C3
Listado 3.21: Clase derivada Coche
1 #include <iostream>
2 #include "Vehiculo.cpp"
3 using namespace std;
4
5 class Coche : public Vehiculo {
6 int _PMA;
7
8 public:
9 void setPMA (int PMA) {_PMA = PMA;}
10 int getPMA () const {return _PMA;}
11
12 void mostrar () const {
13 cout << "Ruedas: " << getRuedas() << endl;
14 cout << "Pasajeros: " << getPasajeros() << endl;
15 cout << "PMA: " << _PMA << endl;
16 }
17 };
clase base sern tambin miembros pblicos de la clase derivada. En otras palabras, el
efecto que se produce equivale a que los miembros pblicos de Vehculo se hubieran
declarado dentro de Coche. Sin embargo, desde Coche no es posible acceder a los
miembros privados de Vehculo, como por ejemplo a la variable _ruedas.
El caso contrario a la herencia pblica es la herencia privada. En este caso, cuando
la clase base se hereda con private, entonces todos los miembros pblicos de la clase
base se convierten en privados en la clase derivada.
Adems de ser pblico o privado, un miembro de clase se puede denir como
protegido. Del mismo modo, una clase base se puede heredar como protegida. Si un
miembro se declara como protegido, dicho miembro no es accesible por elementos que
no sean miembros de la clase salvo en una excepcin. Dicha excepcin consiste en he-
redar un miembro protegido, hecho que marca la diferencia entre private y protected.
En esencia, los miembros protegidos de la clase base se convierten en miembros pro-
tegidos de la clase derivada. Desde otro punto de vista, los miembros protegidos son
miembros privados de una clase base pero con la posibilidad de heredarlos y acceder
a ellos por parte de una clase derivada. El siguiente listado de cdigo muestra el uso
de protected.
Otro caso particular que resulta relevante comentar se da cuando una clase ba-
se se hereda como privada. En este caso, los miembros protegidos se heredan como
miembros privados en la clase protegida.
Si una clase base se hereda como protegida mediante el modicador de acceso pro-
tected, entonces todos los miembros pblicos y protegidos de dicha clase se heredan
como miembros protegidos en la clase derivada.
Constructores y destructores
Cuando se hace uso de herencia y se denen constructores y/o destructores de Inicializacin de objetos
clase, es importante conocer el orden en el que se ejecutan en el caso de la clase
base y de la clase derivada, respectivamente. Bsicamente, a la hora de construir un El constructor de una clase debera
inicializar idealmente todo el esta-
objeto de una clase derivada, primero se ejecuta el constructor de la clase base y, a do de los objetos instanciados. Uti-
continuacin, el constructor de la derivada. En el caso de la destruccin de objetos, lice el constructor de la clase base
el orden se invierte, es decir, primero se ejecuta el destructor de la clase derivada y, a cuando as sea necesario.
continuacin, el de la clase base.
Otro aspecto relevante est vinculado al paso de parmetros al constructor de la
clase base desde el constructor de la clase derivada. Para ello, simplemente se realiza
una llamada al constructor de la clase base, pasando los argumentos que sean nece-
sarios. Este planteamiento es similar al utilizado en Java mediante super(). Note que
aunque una clase derivada no tenga variables miembro, en su constructor han de es-
pecicarse aquellos parmetros que se deseen utilizar para llamar al constructor de la
clase base.
C3
El enfoque todo en uno
Una primera opcin de diseo podra consistir en aplicar un enfoque todo en uno,
es decir, implementar los requisitos previamente comentados en la propia clase Ob-
jetoJuego, aadiendo la funcionalidad de recepcin de mensajes y la posibilidad de
enlazar el objeto en cualquier parte del rbol a la propia clase.
Aunque la simplicidad de esta aproximacin es su principal ventaja, en general
aadir todo lo que se necesita en una nica clase no es la mejor decisin de diseo. Si
se utiliza este enfoque para aadir ms funcionalidad, la clase crecer en tamao y en
complejidad cada vez que se integre un nuevo requisito funcional. As, una clase base
que resulta fundamental en el diseo de un juego se convertir en un elemento difcil
de utilizar y de mantener. En otras palabras, la simplicidad a corto plazo se transforma
en complejidad a largo plazo.
Otro problema concreto con este enfoque es la duplicidad de cdigo, ya que la
clase ObjetoJuego puede no ser la nica en recibir mensajes, por ejemplo. La clase
Jugador podra necesitar recibir mensajes sin ser un tipo particular de la primera clase.
En el caso de enlazar con una estructura de rbol se podra dar el mismo problema, ya
que otros elementos del juego, como por ejemplo los nodos de una escena se podra
organizar del mismo modo y haciendo uso del mismo tipo de estructura de rbol.
En este contexto, copiar el cdigo all donde sea necesario no es una solucin viable
debido a que complica enormemente el mantenimiento y afecta de manera directa a la
arquitectura del diseo.
Contenedores La conclusin directa que se obtiene al reexionar sobre el anterior enfoque es que
resulta necesario disear sendas clases, ReceptorMensajes y NodoArbol, para repre-
La aplicacin de un esquema basa-
do en agregacin, de manera que sentar la funcionalidad previamente discutida. La cuestin reside en cmo relacionar
una clase contiene elementos rele- dichas clases con la clase ObjetoJuego.
vantes vinculados a su funcionali-
dad, es en general un buen diseo. Una opcin inmediata podra ser la agregacin, de manera que un objeto de la cla-
se ObjetoJuego contuviera un objeto de la clase ReceptorMensajes y otro de la clase
NodoArbol, respectivamente. As, la clase ObjetoJuego sera responsable de propor-
cionar la funcionalidad necesaria para manejarlos en su propia interfaz. En trminos
generales, esta solucin proporciona un gran nivel de reutilizacin sin incrementar de
manera signicativa la complejidad de las clases que se extienden de esta forma.
El siguiente listado de cdigo muestra una posible implementacin de este diseo.
La desventaja directa de este enfoque es la generacin de un gran nmero de fun-
ciones que simplemente llaman a la funcin de una variable miembro, las cuales han
de crearse y mantenerse. Si unimos este hecho a un cambio en su interfaz, el man-
tenimiento se complica an ms. As mismo, se puede producir una sobrecarga en el
nmero de llamadas a funcin, hecho que puede reducir el rendimiento de la aplica-
cin.
Una posible solucin a este problema consiste en exponer los propios objetos en
lugar de envolverlos con llamadas a funciones miembro. Este planteamiento simpli-
ca el mantenimiento pero tiene la desventaja de que proporciona ms informacin
de la realmente necesaria en la clase ObjetoJuego. Si adems, posteriormente, es ne-
cesario modicar la implementacin de dicha clase con propsitos de incrementar la
Uso de la herencia eciencia, entonces habra que modicar todo el cdigo que haga uso de la misma.
Recuerde utilizar la herencia con
prudencia. Un buen truco consiste
en preguntarse si la clase derivada
es un tipo particular de la clase ba-
se.
[88] CAPTULO 3. C++. ASPECTOS ESENCIALES
Nodo
rbol
Receptor Nodo Objeto
Mensajes rbol Juego
Objeto
Juego
Figura 3.2: Distintas soluciones de diseo para el problema de la clase ObjetoJuego. (a) Uso de agregacin.
(b) Herencia simple. (c) Herencia mltiple.
Otra posible solucin de diseo consiste en usar herencia simple, es decir, Ob-
jetoJuego se podra declarar como una clase derivada de ReceptorMensajes, aunque
NodoArbol quedara aislado. Si se utiliza herencia simple, entonces una alternativa se-
ra aplicar una cadena de herencia, de manera que, por ejemplo, ArbolNodo hereda de
ReceptorMensajes y, a su vez, ObjetoJuego hereda de ArbolNodo (ver gura 3.2.b).
3.3. Herencia y polimorsmo [89]
C3
Aunque este planteamiento es perfectamente funcional, el diseo no es adecua-
do ya que resulta bastante lgico pensar que ArbolNodo no es un tipo especial de
ReceptorMensajes. Si no es as, entonces no debera utilizarse herencia. Simple y lla-
namente. Del mismo modo, la relacin inversa tampoco es lgica.
Desde un punto de vista general, la herencia mltiple puede introducir una serie
de complicaciones y desventajas, entre las que destacan las siguientes:
Ambigedad, debido a que las clases base de las que hereda una clase derivada
pueden mantener el mismo nombre para una funcin. Para solucionar este pro-
blema, se puede explicitar el nombre de la clase base antes de hacer uso de la
funcin, es decir, ClaseBase::Funcion.
Topografa, debido a que se puede dar la situacin en la que una clase derivada
herede de dos clases base, que a su vez heredan de otra clase, compartiendo
todas ellas la misma clase. Este tipo de rboles de herencia puede generar con-
secuencias inesperadas, como duplicidad de variables y ambigedad. Este tipo
de problemas se puede solventar mediante herencia virtual, concepto distinto al
que se estudiar en el siguiente apartado relativo al uso de funciones virtuales.
Arquitectura del programa, debido a que el uso de la herencia, simple o mlti-
ple, puede contribuir a degradar el diseo del programa y crear un fuerte acopla-
miento entre las distintas clases que la componen. En general, es recomendable
utilizar alternativas como la composicin y relegar el uso de la herencia mltiple
slo cuando sea la mejor alternativa real.
C3
Listado 3.26: Manejo de punteros a clase base (cont.)
1 #include "Coche.cpp"
2
3 int main () {
4 Vehiculo *v; // Puntero a objeto de tipo vehculo.
5 Coche c; // Objeto de tipo coche.
6
7 c.setRuedas(4); // Se establece el estado de c.
8 c.setPasajeros(7);
9 c.setPMA(1885);
10
11 v = &c; // v apunta a un objeto de tipo coche.
12
13 cout << v->getPMA() << endl; // ERROR en tiempo de compilacin.
14
15 cout << ((Coche*)v)->getPMA() << endl; // NO recomendable.
16
17 cout << static_cast<Coche*>(v)->getPMA() << endl; // Estilo C++.
18
19 return 0;
20 }
La palabra clave virtual Una funcin virtual es una funcin declarada como virtual en la clase base y rede-
nida en una o ms clases derivadas. De este modo, cada clase derivada puede tener
Una clase que incluya una funcin su propia versin de dicha funcin. El aspecto interesante es lo que ocurre cuando se
virtual se denomina clase polimr-
ca. llama a esta funcin con un puntero o referencia a la clase base. En este contexto,
C++ determina en tiempo de ejecucin qu versin de la funcin se ha de ejecutar en
funcin del tipo de objeto al que apunta el puntero.
Soy Base!
Soy Derivada1!
Soy Derivada2!
Las funciones virtuales han de ser miembros de la clase en la que se denen, Sobrecarga/sobreescrit.
es decir, no pueden ser funciones amigas. Sin embargo, una funcin virtual puede
ser amiga de otra clase. Adems, los destructores se pueden denir como funciones Cuando una funcin virtual se rede-
ne en una clase derivada, la fun-
virtuales, mientras que en los constructores no es posible. cin se sobreescribe. Para sobrecar-
Las funciones virtuales se heredan de manera independiente del nmero de niveles gar una funcin, recuerde que el n-
mero de parmetros y/o sus tipos
que tenga la jerarqua de clases. Suponga que en el ejemplo anterior Derivada2 hereda han de ser diferentes.
de Derivada1 en lugar de heredar de Base. En este caso, la funcin imprimir seguira
siendo virtual y C++ sera capaz de seleccionar la versin adecuada al llamar a dicha
funcin. Si una clase derivada no sobreescribe una funcin virtual denida en la clase
base, entonces se utiliza la versin de la clase base.
El polimorsmo permite manejar la complejidad de los programas, garantizando
la escalabilidad de los mismos, debido a que se basa en el principio de una interfaz,
mltiples mtodos. Por ejemplo, si un programa est bien diseado, entonces se puede
suponer que todos los objetos que derivan de una clase base se acceden de la misma
forma, incluso si las acciones especcas varan de una clase derivada a la siguiente.
Esto implica que slo es necesario recordar una interfaz. Sin embargo, la clase deri-
vada es libre de aadir uno o todos los aspectos funcionales especicados en la clase
base.
C3
Funciones virtuales puras y clases abstractas
33 }
3.4. Plantillas
En el desarrollo de software es bastante comn encontrarse con situaciones en las
que los programas implementados se parecen enormemente a otros implementados
con anterioridad, salvo por la necesidad de tratar con distintos tipos de datos o de
clases. Por ejemplo, un mismo algoritmo puede mantener el mismo comportamiento
de manera que ste no se ve afectado por el tipo de datos a manejar.
En esta seccin se discute el uso de las plantillas en C++, un mecanismo que
permite escribir cdigo genrico sin tener dependencias explcitas respecto a tipos de
datos especcos.
C3
<MiClase> <MiClase> <MiClase> <MiClase>
(a)
<MiClase> <MiClase>
MiClase
(b) (c)
Figura 3.3: Distintos enfoques para la implementacin de una lista con elementos gnericos. (a) Integracin
en la propia clase de dominio. (b) Uso de herencia. (c) Contenedor con elementos de tipo nulo.
Otra posible solucin consiste en hacer uso de la herencia para denir una clase
base que represente a cualquier elemento de una lista (ver gura 3.3.b). De este modo,
cualquier clase que desee incluir la funcionalidad asociada a la lista simplemente ha
de extenderla. Este planteamiento permite tratar a los elementos de la lista mediante
polimorsmo. Sin embargo, la mayor desventaja que presenta este enfoque est en el
diseo, ya que no es posible separar la funcionalidad de la lista de la clase propiamente
dicha, de manera que no es posible tener el objeto en mltiples listas o en alguna otra
estructura de datos. Por lo tanto, es importante separar la propia lista de los elementos
que realmente contiene.
Una alternativa para proporcionar esta separacin consiste en hacer uso de una
lista que maneja punteros de tipo nulo para albergar distintos tipos de datos (ver -
gura 3.3.c). De este modo, y mediante los moldes correspondientes, es posible tener
una lista con elementos de distinto tipo y, al mismo tiempo, la funcionalidad de la mis-
ma est separada del contenido. La principal desventaja de esta aproximacin es que
no es type-safe, es decir, depende del programador incluir la funcionalidad necesaria
para convertir tipos, ya que el compilador no los detectar.
Otra desventaja de esta propuesta es que son necesarias dos reservas de memoria
para cada uno de los nodos de la lista: una para el objeto y otra para el siguiente
nodo de la lista. Este tipo de cuestiones han de considerarse de manera especial en
el desarrollo de videojuegos, ya que la plataforma hardware nal puede tener ciertas
restricciones de recursos.
[96] CAPTULO 3. C++. ASPECTOS ESENCIALES
La gura 3.3 muestra de manera grca las distintas opciones discutidas hasta
ahora en lo relativo a la implementacin de una lista que permita el tratamiento de
datos genricos.
C3
Las plantillas de funciones siguen la misma idea que las plantillas de clases apli-
cndolas a las funciones. Obviamente, la principal diferencia con respecto a las plan-
tillas a clases es que no necesitan instanciarse. El siguiente listado de cdigo muestra
un ejemplo sencillo de la clsica funcin swap para intercambiar el contenido de dos
variables. Dicha funcin puede utilizarse con enteros, valores en punto otante, ca-
denas o cualquier clase con un constructor de copia y un constructor de asignacin.
Adems, la funcin se instancia dependiendo del tipo de datos utilizado. Recuerde que
no es posible utilizar dos tipos de datos distintos, es decir, por ejemplo un entero y un
valor en punto otante, ya que se producir un error en tiempo de compilacin.
Uso de plantillas El uso de plantillas en C++ solventa todas las necesidades planteadas para manejar
las listas introducidas en la seccin 3.4.1, principalmente las siguientes:
Las plantillas son una herramienta
excelente para escribir cdigo que
no dependa de un tipo de datos es- Flexibilidad, para poder utilizar las listas con distintos tipos de datos.
pecco.
Simplicidad, para evitar la copia de cdigo cada vez que se utilice una estruc-
tura de lista.
Uniformidad, ya que se maneja una nica interfaz para la lista.
Independencia, entre el cdigo asociado a la funcionalidad de la lista y el c-
digo asociado al tipo de datos que contendr la lista.
7
8 private:
9 T _datos;
10 };
11
12 template<class T>
13 class Lista {
14 public:
15 NodoLista<T> getCabeza ();
16 void insertarFinal (T datos);
17 // Resto funcionalidad...
18
19 private:
20 NodoLista<T> *_cabeza;
21 };
Desde una perspectiva general, no debe olvidar que las plantillas representan una
herramienta adecuada para un determinado uso, por lo que su uso indiscriminado es un
error. Recuerde tambin que las plantillas introducen una dependencia de uso respecto
a otras clases y, por lo tanto, su diseo debera ser simple y mantenible.
Una de las situaciones en las que el uso de plantillas resulta adecuado est aso-
ciada al uso de contenedores, es decir, estructuras de datos que contienen objetos de
distintas clases. En este contexto, es importante destacar que la biblioteca STL de C++
ya proporciona una implementacin de listas, adems de otras estructuras de datos y
de algoritmos listos para utilizarse. Por lo tanto, es bastante probable que el desarro-
llador haga un uso directo de las mismas en lugar de tener que desarrollar desde cero
su propia implementacin. En el captulo 5 se estudia el uso de la biblioteca STL y se
discute su uso en el mbito del desarrollo de videojuegos.
3.5. Manejo de excepciones [99]
C3
Freezing issues A la hora de afrontar cualquier desarrollo software, un programador siempre tiene
que tratar con los errores que dicho software puede generar. Existen diversas estra-
Aunque el desarrollo de videojue- tegias para afrontar este problema, desde simplemente ignorarlos hasta hacer uso de
gos comerciales madura ao a ao,
an hoy en da es bastante comn tcnicas que los controlen de manera que sea posible recuperarse de los mismos. En
encontrar errores y bugs en los mis- esta seccin se discute el manejo de excepciones en C++ con el objetivo de escribir
mos. Algunos de ellos obligan in- programas robustos que permitan gestionar de manera adecuada el tratamiento de
cluso a resetear la estacin de jue- errores y situaciones inesperadas. Sin embargo, antes de profundizar en este aspecto
gos por completo.
se introducirn brevemente las distintas alternativas ms relevantes a la hora de tratar
con errores.
Hasta ahora, los enfoques comentados tienen una serie de desventajas importantes.
En este contexto, las excepciones se posicionan como una alternativa ms adecuada
y prctica. En esencia, el uso de excepciones permite que cuando un programa se
tope con una situacin inesperada se arroje una excepcin. Este hecho tiene como
consecuencia que el ujo de ejecucin salte al bloque de captura de excepciones ms
cercano. Si dicho bloque no existe en la funcin en la que se arroj la excepcin,
entonces el programa gestionar de manera adecuada la salida de dicha funcin (des-
truyendo los objetos vinculados) y saltar a la funcin padre con el objetivo de buscar
un bloque de captura de excepciones.
Este proceso se realiza recursivamente hasta encontrar dicho bloque o llegar a
la funcin principal delegando en el cdigo de manejo de errores por defecto, que
tpicamente nalizar la ejecucin del programa y mostrar informacin por la salida
estndar o generar un chero de log.
Los bloques de tratamiento de excepciones ofrecen al desarrollador la exibili-
dad de hacer lo que desee con el error, ya sea ignorarlo, tratar de recuperarse del
mismo o simplemente informar sobre lo que ha ocurrido. Este planteamiento facili-
ta enormemente la distincin entre distintos tipos de errores y, consecuentemente, su
tratamiento.
C3
4
5 int main () {
6 try {
7 int *array = new int[1000000];
8 }
9 catch (bad_alloc &e) {
10 cerr << "Error al reservar memoria." << endl;
11 }
12
13 return 0;
14 }
MiExcepcin
C3
desea que el programa capture cualquier tipo de excepcin, entonces se puede aadir
una captura genrica (ver cuarto bloque catch). En resumen, si se lanza una excepcin
no contemplada en un bloque catch, entonces el programa seguir buscando el bloque
catch ms cercano.
Es importante resaltar que el orden de las sentencias catch es relevante, ya que
dichas sentencias siempre se procesan de arriba a abajo. Adems, cuando el programa
encuentra un bloque que trata con la excepcin lanzada, el resto de bloques se ignoran
automticamente.
Exception handlers Otro aspecto que permite C++ relativo al manejo de excepciones es la posibilidad
de re-lanzar una excepcin, con el objetivo de delegar en una capa superior el trata-
El tratamiento de excepciones se
puede enfocar con un esquema pa- miento de la misma. El siguiente listado de cdigo muestra un ejemplo en el que se
recido al del tratamiento de eventos, delega el tratamiento del error de entrada/salida.
es decir, mediante un planteamien-
to basado en capas y que delege las
excepciones para su posterior ges- Listado 3.36: Re-lanzando una excepcin
tin.
1 void Mesh::cargar (const char *archivo) {
2
3 try {
4 Stream stream(archivo); // Puede generar un error de I/O.
5 cargar(stream);
6 }
7
8 catch (MiExcepcionIO &e) {
9 if (e.datosCorruptos()) {
10 // Tratar error I/O.
11 }
12 else {
13 throw; // Se re-lanza la excepcin.
14 }
15 }
16
17 }
C3
se producir el clsico memory leak, es decir, la situacin en la que no se libera una
porcin de memoria que fue previamente reservada. En estos casos, el destructor no se
ejecuta ya que, despus de todo, el constructor no naliz correctamente su ejecucin.
La solucin pasa por hacer uso de este tipo de punteros.
El caso de los destructores es menos problemtico, ya que es lgico suponer
que nadie har uso de un objeto que se va a destruir, incluso cuando se genere una
excepcin dentro del propio destructor. Sin embargo, es importante recordar que el
destructor no puede lanzar una excepcin si el mismo fue llamado como consecuencia
de otra excepcin.
C
uando nos enfrentamos al diseo de un programa informtico como un video-
juego, no es posible abordarlo por completo. El proceso de diseo de una
aplicacin suele ser iterativo y en diferentes etapas de forma que se vaya re-
nando con el tiempo. El diseo perfecto y a la primera es muy difcil de conseguir.
La tarea de disear aplicaciones es compleja y, con seguridad, una de las ms
importantes y que ms impacto tiene no slo sobre el producto nal, sino tambin
sobre su vida futura. En el diseo de la aplicacin es donde se denen las estructuras
y entidades que se van a encargar de resolver el problema modelado, as como sus
relaciones y sus dependencias. Cmo de bien denamos estas entidades y relaciones
inuir, en gran medida, en el xito o fracaso del proyecto y en la viabilidad de su
mantenimiento.
El diseo, por tanto, es una tarea capital en el ciclo de vida del software. Sin
embargo, no existe un procedimiento sistemtico y claro sobre cmo crear el mejor
diseo para un problema dado. Podemos utilizar metodologas, tcnicas y herramien-
tas que nos permitan renar nuestro diseo. La experiencia tambin juega un papel
importante. Sin embargo, el contexto de la aplicacin es crucial y los requisitos, que
pueden cambiar durante el proceso de desarrollo, tambin.
Un videojuego es un programa con una componente muy creativa. Adems, el
mercado de videojuegos se mueve muy deprisa y la adaptacin a ese medio cam-
biante es un factor determinante. En general, los diseos de los programas deben ser
escalables, extensibles y que permitan crear componentes reutilizables.
Esta ltima caracterstica es muy importante ya que la experiencia nos hace ver que
al construir una aplicacin se nos presentan situaciones recurrentes y que se asemejan
a situaciones pasadas. Es el deja v en el diseo: cmo solucion esto?. En esencia,
muchos componentes pueden modelarse de formas similares.
107
[108] CAPTULO 4. PATRONES DE DISEO
En este captulo, se describen algunos patrones de diseo que almacenan este co-
nocimiento experimental de diseo procedente del estudio de aplicaciones y de los
xitos y fracasos de casos reales. Bien utilizados, permiten obtener un mejor diseo
ms temprano.
4.1. Introduccin
El diseo de una aplicacin es un proceso iterativo y de continuo renamiento.
Normalmente, una aplicacin es lo sucientemente compleja como para que su di-
seo tenga que ser realizado por etapas, de forma que al principio se identican los
mdulos ms abstractos y, progresivamente, se concreta cada mdulo con un diseo
en particular.
En el camino, es comn encontrar problemas y situaciones que conceptualmente
pueden parecerse entre s, por lo menos a priori. Quizs un estudio ms exhaustivo de
los requisitos permitan determinar si realmente se trata de problemas equivalentes.
Por ejemplo, supongamos que para resolver un determinado problema se llega
a la conclusin de que varios tipos de objetos deben esperar a un evento producido
por otro. Esta situacin puede darse en la creacin de una interfaz grca donde la
pulsacin de un botn dispara la ejecucin de otras acciones. Pero tambin es similar
a la implementacin de un manejador del teclado, cuyas pulsaciones son recogidas por
los procesos interesados, o la de un gestor de colisiones, que notica choques entre
elementos del juego. Incluso se parece a la forma en que muchos programas de chat
envan mensajes a un grupo de usuarios.
Ciertamente, cada uno de los ejemplos anteriores tiene su contexto y no es posible Denicin
(ni a veces deseable) aplicar exactamente la misma solucin a cada uno de ellos. Sin En [38], un patrn de diseo es una
embargo, s que es cierto que existe semejanza en la esencia del problema. En nuestro descripcin de la comunicacin en-
ejemplo, en ambos casos existen entidades que necesitan ser noticadas cuando ocurre tre objetos y clases personalizadas
un cierto evento. para solucionar un problema gen-
rico de diseo bajo un contexto de-
Los patrones de diseo son formas bien conocidas y probadas de resolver proble- terminado.
mas de diseo que son recurrentes en el tiempo. Los patrones de diseo son amplia-
mente utilizados en las disciplinas creativas y tcnicas. As, de la misma forma que
un guionista de cine crea guiones a partir de patrones argumentales como comedia
o ciencia-ccin, un ingeniero se basa en la experiencia de otros proyectos para
identicar patrones comunes que le ayuden a disear nuevos procesos. De esta for-
ma, reutilizando soluciones bien probadas y conocidas se ayuda a reducir el tiempo
necesario para el diseo.
Los patrones sintetizan la tradicin y experiencia profesional de diseadores de
software experimentados que han evaluado y demostrado que la solucin proporciona-
da es una buena solucin bajo un determinado contexto. El diseador o desarrollador
que conozca diferentes patrones de diseo podr reutilizar estas soluciones, pudiendo
alcanzar un mejor diseo ms rpidamente.
4.1. Introduccin [109]
C4
Cuando se describe un patrn de diseo se pueden citar ms o menos propiedades
del mismo: el problema que resuelve, sus ventajas, si proporciona escalabilidad en el
diseo o no, etc. Nosotros vamos a seguir las directrices marcadas por los autores del
famoso libro de Design Patterns [38] 1 , por lo que para denir un patrn de diseo es
necesario describir, como mnimo, cuatro componentes fundamentales:
1 Tambin conocido como The Gang of Four (GoF) (la Banda de los Cuatro) en referencia a los autores
del mismo. Sin duda se trata de un famoso libro en este rea, al que no le faltan detractores.
[110] CAPTULO 4. PATRONES DE DISEO
4.2.1. Singleton
El patrn singleton se suele utilizar cuando se requiere tener una nica instancia
de un determinado tipo de objeto.
Problema
C4
En C++, utilizando el operador new es posible crear una instancia de un objeto.
Sin embargo, es posible que necesitemos que slo exista una instancia de una clase
determinada por diferentes motivos (prevencin de errores, seguridad, etc.).
El baln en un juego de ftbol o la entidad que representa al mundo 3D son ejem-
plos donde podra ser conveniente mantener una nica instancia de este tipo de objetos.
Solucin
Para garantizar que slo existe una instancia de una clase es necesario que los
clientes no puedan acceder directamente al constructor. Por ello, en un singleton el
constructor es, por lo menos, protected. A cambio se debe proporcionar un nico
punto (controlado) por el cual se pide la instancia nica. El diagrama de clases de este
patrn se muestra en la gura 4.1.
Implementacin
Como se puede ver, la caracterstica ms importante es que los mtodos que pue-
den crear una instancia de Ball son todos privados para los clientes externos. Todos
ellos deben utilizar el mtodo esttico getTheBall() para obtener la nica instan-
cia.
Consideraciones
Problema
En ella, se muestra jerarquas de clases que modelan los diferentes tipos de per-
sonajes de un juego y algunas de sus armas. Para construir cada tipo de personaje es
necesario saber cmo construirlo y con qu otro tipo de objetos tiene relacin. Por
ejemplo, restricciones del tipo la gente del pueblo no puede llevar armas o los ar-
queros slo pueden puede tener un arco, es conocimiento especco de la clase que
se est construyendo.
Supongamos que en nuestro juego, queremos obtener razas de personajes: hom-
bres y orcos. Cada raza tiene una serie de caractersticas propias que hacen que pueda
moverse ms rpido, trabajar ms o tener ms resistencia a los ataques.
4.2. Patrones de creacin [113]
El patrn Abstract Factory puede ser de ayuda en este tipo de situaciones en las que
es necesario crear diferentes tipos de objetos utilizando una jerarqua de componentes.
C4
Dada la complejidad que puede llegar a tener la creacin de una instancia es deseable
aislar la forma en que se construye cada clase de objeto.
Solucin
En la gura 4.3 se muestra la aplicacin del patrn para crear las diferentes ra-
zas de soldados. Por simplicidad, slo se ha aplicado a esta parte de la jerarqua de
personajes.
En primer lugar se dene una factora abstracta que ser la que utilice el cliente
(Game) para crear los diferentes objetos. CharFactory es una factora que slo
dene mtodos abstractos y que sern implementados por sus clases hijas. stas son
factoras concretas a cada tipo de raza (ManFactory y OrcFactory) y ellas son
las que crean las instancias concretas de objetos Archer y Rider para cada una de
las razas.
En denitiva, el patrn Abstract Factory recomienda crear las siguientes entidades:
Factora abstracta que dena una interfaz para que los clientes puedan crear los
distintos tipos de objetos.
Factoras concretas que realmente crean las instancias nales.
Implementacin
Ntese como las factoras concretas ocultan las particularidades de cada tipo. Una
implementacin similar tendra el mtodo makeRider().
Consideraciones
C4
cuando la creacin de las instancias implican la imposicin de restricciones y
otras particularidades propias de los objetos que se construyen.
los productos que se deben fabricar en las factoras no cambian excesivamente
en el tiempo. Aadir nuevos productos implica aadir mtodos a todas las fac-
toras ya creadas, por lo que es un poco problemtico. En nuestro ejemplo, si
quisiramos aadir un nuevo tipo de soldado deberamos modicar la factora
abstracta y las concretas. Por ello, es recomendable que se aplique este patrn
sobre diseos con un cierto grado de estabilidad.
Un patrn muy similar a ste es el patrn Builder. Con una estructura similar,
el patrn Builder se centra en el proceso de cmo se crean las instancias y no en
la jerarqua de factoras que lo hacen posible. Como ejercicio se plantea estudiar el
patrn Builder y encontrar las diferencias.
Problema
Al igual que ocurre con el patrn Abstract Factory, el problema que se pretende
resolver es la creacin de diferentes instancias de objetos abstrayendo la forma en que
realmente se crean.
Solucin
La gura 4.4 muestra un diagrama de clases para nuestro ejemplo que emplea el
patrn Factory Method para crear ciudades en las que habitan personajes de diferentes
razas.
Como puede verse, los objetos de tipo Village tienen un mtodo populate()
que es implementado por las subclases. Este mtodo es el que crea las instancias de
Villager correspondientes a cada raza. Este mtodo es el mtodo factora. Adems
de este mtodo, tambin se proporcionan otros como population() que devuelve
la poblacin total, o location() que devuelve la posicin de la cuidad en el mapa.
Todos estos mtodos son comunes y heredados por las ciudades de hombres y orcos.
Finalmente, objetos Game podran crear ciudades y, consecuentemente, crear ciu-
dadanos de distintos tipos de una forma transparente.
Consideraciones
Ntese que el patrn Factory Method se utiliza para implementar el patrn Abs-
tract Factory ya que la factora abstracta dene una interfaz con mtodos de construc-
cin de objetos que son implementados por las subclases.
4.2.4. Prototype
El patrn Prototype proporciona abstraccin a la hora de crear diferentes objetos
en un contexto donde se desconoce cuntos y cules deben ser creados a priori. La
idea principal es que los objetos deben poder clonarse en tiempo de ejecucin.
Problema
Solucin
C4
Para atender a las nuevas necesidades dinmicas en la creacin de los distintos
tipo de armas, sin perder la abstraccin sobre la creacin misma, se puede utilizar el
patrn Prototype como se muestra en la gura 4.5.
Consideraciones
Puede parecer que entra en conicto con Abstract Factory debido a que intenta
eliminar, precisamente, factoras intermedias. Sin embargo, es posible utilizar
ambas aproximaciones en una Prototype Abstract Factory de forma que la fac-
tora se congura con los prototipos concretos que puede crear y sta slo invoca
a clone().
Tambin es posible utilizar un gestor de prototipos que permita cargar y descar-
gar los prototipos disponibles en tiempo de ejecucin. Este gestor es interesante
para tener diseos ampliables en tiempo de ejecucin (plugins).
Para que los objetos puedan devolver una copia de s mismo es necesario que en
su implementacin est el constructor de copia (copy constructor) que en C++
viene por defecto implementado.
4.3.1. Composite
El patrn Composite se utiliza para crear una organizacin arbrea y homognea
de instancias de objetos.
Problema
Solucin
Por otro lado, hay objetos hoja que no contienen a ms objetos, como es el caso
de Clock.
Consideraciones
Una buena estrategia para identicar la situacin en la que aplicar este patrn
es cuando tengo un X y tiene varios objetos X.
4.3. Patrones estructurales [119]
C4
prohibir la composicin de un tipo de objeto con otro. Por ejemplo, un jarrn
grande dentro de una pequea bolsa. La comprobacin debe hacerse en tiempo
de ejecucin y no es posible utilizar el sistema de tipos del compilador. En este
sentido, usando Composite se relajan las restricciones de composicin entre
objetos.
Los usuarios de la jerarqua se hacen ms sencillos, ya que slo tratan con un
tipo abstracto de objeto, dndole homogeneidad a la forma en que se maneja la
estructura.
4.3.2. Decorator
Tambin conocido como Wrapper, el patrn Decorator sirve para aadir y/o mo-
dicar la responsabilidad, funcionalidad o propiedades de un objeto en tiempo de
ejecucin.
Problema
Supongamos que el personaje de nuestro videojuego porta un arma que utiliza para
eliminar a sus enemigos. Dicha arma, por ser de un tipo determinado, tiene una serie
de propiedades como el radio de accin, nivel de ruido, nmero de balas que puede
almacenar, etc. Sin embargo, es posible que el personaje incorpore elementos al arma
que puedan cambiar estas propiedades como un silenciador o un cargador extra.
El patrn Decorator permite organizar el diseo de forma que la incorporacin
de nueva funcionalidad en tiempo de ejecucin a un objeto sea transparente desde el
punto de vista del usuario de la clase decorada.
Solucin
Implementacin
8 public:
9 float noise () const { return 150.0; }
10 int bullets () const { return 5; }
11 };
12
13 /* Decorators */
14
15 class FirearmDecorator : public Firearm {
16 protected:
17 Firearm* _gun;
18 public:
19 FirearmDecorator(Firearm* gun): _gun(gun) {};
20 virtual float noise () const { return _gun->noise(); }
21 virtual int bullets () const { return _gun->bullets(); }
22 };
23
24 class Silencer : public FirearmDecorator {
25 public:
26 Silencer(Firearm* gun) : FirearmDecorator(gun) {};
27 float noise () const { return _gun->noise() - 55; }
28 int bullets () const { return _gun->bullets(); }
29 };
30
31 class Magazine : public FirearmDecorator {
32 public:
33 Magazine(Firearm* gun) : FirearmDecorator(gun) {};
34 float noise () const { return _gun->noise(); }
35 int bullets () const { return _gun->bullets() + 5; }
36 };
37
38 /* Using decorators */
39
40 ...
41 Firearm* gun = new Rifle();
42 cout << "Noise: " << gun->noise() << endl;
43 cout << "Bullets: " << gun->bullets() << endl;
44 ...
45 // char gets a silencer
46 gun = new Silencer(gun);
47 cout << "Noise: " << gun->noise() << endl;
48 cout << "Bullets: " << gun->bullets() << endl;
49 ...
50 // char gets a new magazine
51 gun = new Magazine(gun);
52 cout << "Noise: " << gun->noise() << endl;
53 cout << "Bullets: " << gun->bullets() << endl;
4.3. Patrones estructurales [121]
C4
rador a la instancia?
Consideraciones
Este patrn permite tener una jerarqua de clases compuestas, formando una es-
tructura ms dinmica y exible que la herencia esttica. El diseo equivalente
utilizando mecanismos de herencia debera considerar todos los posibles casos
en las clases hijas. En nuestro ejemplo, habra 4 clases: rie, rie con silencia-
dor, rie con cargador extra y rie con silenciador y cargador. Sin duda, este
esquema es muy poco exible.
4.3.3. Facade
El patrn Facade eleva el nivel de abstraccin de un determinado sistema para
ocultar ciertos detalles de implementacin y hacer ms sencillo su uso.
Problema
Solucin
Como se puede ver, el usuario ya no tiene que conocer las relaciones que exis-
ten entre los diferentes mdulos para crear este tipo de animaciones. Esto aumenta,
levemente, el nivel de abstraccin y hace ms sencillo su uso.
En denitiva, el uso del patrn Facade proporciona una estructura de diseo como
la mostrada en la gura 4.8.
Consideraciones
C4
Los sistemas deben ser independientes y portables.
Controlar el acceso y la forma en que se utiliza un sistema determinado.
Los clientes pueden seguir utilizando los subsistemas directamente, sin pasar
por la fachada, lo que da la exibilidad de elegir entre una implementacin de
bajo nivel o no.
Sin embargo, utilizando el patrn Facade es posible caer en los siguientes errores:
Crear clases con un tamao desproporcionado. Las clases fachada pueden con-
tener demasiada funcionalidad si no se divide bien las responsabilidades y se
tiene claro el objetivo para el cual se creo la fachada. Para evitarlo, es necesario
ser crtico/a con el nivel de abstraccin que se proporciona.
Obtener diseos poco exibles y con mucha contencin. A veces, es posible
crear fachadas que obliguen a los usuarios a un uso demasiado rgido de la fun-
cionalidad que proporciona y que puede hacer que sea ms cmodo, a la larga,
utilizar los subsistemas directamente. Adems, una fachada puede convertirse
en un nico punto de fallo, sobre todo en sistemas distribuidos en red.
Exponer demasiados elementos y, en denitiva, no proporcionar un nivel de
abstraccin adecuado.
4.3.4. MVC
El patrn MVC (Model View Controller) se utiliza para aislar el dominio de apli-
cacin, es decir, la lgica, de la parte de presentacin (interfaz de usuario).
Problema
Solucin
En el patrn MVC, mostrado en la gura 4.9, existen tres entidades bien denidas:
Vista: se trata de la interfaz de usuario que interacta con el usuario y recibe sus
rdenes (pulsar un botn, introducir texto, etc.). Tambin recibe rdenes desde
el controlador, para mostrar informacin o realizar un cambio en la interfaz.
Figura 4.9: Estructura del patrn
MVC.
[124] CAPTULO 4. PATRONES DE DISEO
Consideraciones
4.3.5. Adapter
El patrn Adapter se utiliza para proporcionar una interfaz que, por un lado, cum-
pla con las demandas de los clientes y, por otra, haga compatible otra interfaz que, a
priori, no lo es.
Problema
Solucin
C4
Usando el patrn Adapter es posible crear una nueva interfaz de acceso a un deter-
minado objeto, por lo que proporciona un mecanismo de adaptacin entre las deman-
das del objeto cliente y el objeto servidor que proporciona la funcionalidad.
Consideraciones
Tener sistemas muy reutilizables puede hacer que sus interfaces no puedan ser
compatibles con una comn. El patrn Adapter es una buena opcin en este
caso.
Un mismo adaptador puede utilizarse con varios sistemas.
Otra versin del patrn es que la clase Adapter sea una subclase del sistema
adaptado. En este caso, la clase Adapter y la adaptada tienen una relacin ms
estrecha que si se realiza por composicin.
Este patrn se parece mucho al Decorator. Sin embargo, dieren en que la nali-
dad de ste es proporcionar una interfaz completa del objeto adaptador, mientras
que el decorador puede centrarse slo en una parte.
4.3.6. Proxy
El patrn Proxy proporciona mecanismos de abstraccin y control para acceder a
un determinado objeto simulando que se trata del objeto real.
[126] CAPTULO 4. PATRONES DE DISEO
Problema
Muchos de los objetos de los que puede constar una aplicacin pueden presentar
diferentes problemas a la hora de ser utilizados por clientes:
Coste computacional: es posible que un objeto, como una imagen, sea costoso
de manipular y cargar.
Acceso remoto: el acceso por red es una componente cada vez ms comn entre
las aplicaciones actuales. Para acceder a servidores remotos, los clientes deben
conocer las interioridades y pormenores de la red (sockets, protocolos, etc.).
Acceso seguro: es posible que muchos objetos necesiten diferentes privilegios
para poder ser utilizados. Por ejemplo, los clientes deben estar autorizados para
poder acceder a ciertos mtodos.
Dobles de prueba: a la hora de disear y probar el cdigo, puede ser til uti-
lizar objetos dobles que reemplacen instancias reales que pueden hacer que las
pruebas sea pesadas y/o lentas.
Solucin
Implementacin
C4
8
9 ...
10 /* perform a hard file load */
11 ...
12 }
13
14 void display() {
15 ...
16 /* perform display operation */
17 ...
18 }
19 };
20
21 class ImageProxy : public Graphic {
22 private:
23 Image* _image;
24 public:
25 void display() {
26 if (not _image) {
27 _image = new Image();
28 _image.load();
29 }
30 _image->display();
31 }
32 };
33
34 /* Client */
35 ...
36 Graphic image = new ImageProxy();
37 image->display(); // loading and display
38 image->display(); // just display
39 image->display(); // just display
40 ...
Consideraciones
Existen muchos ejemplos donde se hace un uso intensivo del patrn proxy en
diferentes sistemas:
4.4.1. Observer
El patrn Observer se utiliza para denir relaciones 1 a n de forma que un objeto
pueda noticar y/o actualizar el estado de otros automticamente.
Problema
Solucin
El patrn Observer proporciona un diseo con poco acoplamiento entre los obser-
vadores y el objeto observado. Siguiendo la losofa de publicacin/suscripcin, los
objetos observadores se deben registrar en el objeto observado, tambin conocido co-
mo subject. As, cuando ocurra el evento oportuno, el subject recibir una invocacin
a travs de notify() y ser el encargado de noticar a todos los elementos suscri-
tos a l a travs del mtodo update(). Los observadores que reciben la invocacin
pueden realizar las acciones pertinentes como consultar el estado del dominio para
obtener nuevos valores. En la gura 4.12 se muestra un esquema general del patrn
Observer.
Consideraciones
C4
Figura 4.13: Diagrama de secuencia de ejemplo utilizando un Observer
Los canales de eventos es un patrn ms genrico que el Observer pero que sigue
respetando el modelo push/pull. Consiste en denir estructuras que permiten la comu-
nicacin n a n a travs de un medio de comunicacin (canal) que se puede multiplexar
en diferentes temas (topics). Un objeto puede establecer un rol suscriptor de un tema
dentro de un canal y slo recibir las noticaciones del mismo. Adems, tambin pue-
de congurarse como publicador, por lo que podra enviar actualizaciones al mismo
canal.
[130] CAPTULO 4. PATRONES DE DISEO
4.4.2. State
El patrn State es til para realizar transiciones de estado e implementar autmatas
respetando el principio de encapsulacin.
Problema
Es muy comn que en cualquier aplicacin, includo los videojuegos, existan es-
tructuras que pueden ser modeladas directamente como un autmata, es decir, una
coleccin de estados y unas transiciones dependientes de una entrada. En este caso, la
entrada pueden ser invocaciones y/o eventos recibidos.
Por ejemplo, los estados de un personaje de un videojuego podran ser: de pie,
tumbado, andando y saltando. Dependiendo del estado en el que se encuentre y de la
invocacin recibida, el siguiente estado ser uno u otro. Por ejemplo, si est de pie y
recibe la orden de tumbarse, sta se podr realizar. Sin embargo, si ya est tumbado
no tiene sentido volver a tumbarse, por lo que debe permanecer en ese estado.
Solucin
Por cada estado en el que puede encontrarse el personaje, se crea una clase que
hereda de la clase abstracta anterior, de forma que en cada una de ellas se implementen
los mtodos que producen cambio de estado.
Por ejemplo, segn el diagrama, en el estado de pie se puede recibir la orden
de caminar, tumbarse y saltar, pero no de levantarse. En caso de recibir esta ltima, se
ejecutar la implementacin por defecto, es decir, no hacer nada.
En denitiva, la idea es que las clases que representan a los estados sean las encar-
gadas de cambiar el estado del personaje, de forma que los cambios de estados quedan
encapsulados y delegados al estado correspondiente.
4.4. Patrones de comportamiento [131]
Consideraciones
C4
Los componentes del diseo que se comporten como autmatas son buenos
candidatos a ser modelados con el patrn State. Una conexin TCP (Transport
Control Protocol) o un carrito en una tienda web son ejemplos de este tipo de
problemas.
Es posible que una entrada provoque una situacin de error estando en un deter-
minado estado. Para ello, es posible utilizar las excepciones para noticar dicho
error.
Las clases que representan a los estados no deben mantener un estado intrn-
seco, es decir, no se debe hacer uso de variables que dependan de un contexto.
De esta forma, el estado puede compartirse entre varias instancias. La idea de
compartir un estado que no depende del contexto es la base fundamental del pa-
trn Flyweight, que sirve para las situaciones en las que crear muchas instancias
puede ser un problema de rendimiento.
4.4.3. Iterator
El patrn Iterator se utiliza para ofrecer una interfaz de acceso secuencial a una
determinada estructura ocultando la representacin interna y la forma en que realmen-
te se accede.
Problema
Solucin
Con ayuda del patrn Iterator es posible obtener acceso secuencial, desde el punto
de vista del usuario, a cualquier estructura de datos, independientemente de su imple-
mentacin interna. En la gura 4.15 se muestra un diagrama de clases genrico del
patrn. Como puede verse, la estructura de datos es la encargada de crear el iterador
adecuado para ser accedida a travs del mtodo iterator(). Una vez que el cliente
ha obtenido el iterador, puede utilizar los mtodos de acceso que ofrecen tales como
next() (para obtener el siguiente elemento) o isDone() para comprobar si no
existen ms elementos.
[132] CAPTULO 4. PATRONES DE DISEO
Implementacin
35 bool isDone() {
36 return _currentIndex > _list->length();
};
C4
37
38 };
39
40 /* client using iterator */
41
42 List list = new List();
43 ListIterator it = list.iterator();
44
45 for (Object ob = it.first(); not it.isDone(); it.next()) {
46 // do the loop using ob
47 };
Consideraciones
Problema
Por ejemplo, supongamos que tenemos dos tipos de jugadores de juegos de mesa:
ajedrez y damas. En esencia, ambos juegan igual; lo que cambia son las reglas del
juego que, obviamente, condiciona su estrategia y su forma de jugar concreta. Sin
embargo, en ambos juegos, los jugadores mueven en su turno, esperan al rival y esto
se repite hasta que acaba la partida.
El patrn Template Method consiste extraer este comportamiento comn en una
clase padre y denir en las clases hijas la funcionalidad concreta.
Solucin
Siguiendo con el ejemplo de los jugadores de ajedrez y damas, la gura 4.16 mues-
tra una posible aplicacin del patrn Template Method a modo de ejemplo. Ntese que
la clase GamePlayer es la que implementa el mtodo play() que es el que invoca
a los otros mtodos que son implementados por las clases hijas. Este mtodo es el
mtodo plantilla.
Cada tipo de jugador dene los mtodos en base a las reglas y heursticas de su
juego. Por ejemplo, el mtodo isOver() indica si el jugador ya no puede seguir
jugando porque se ha terminado el juego. En caso de las damas, el juego se acaba para
el jugador si se ha quedado sin chas; mientras que en el caso ajedrez puede ocurrir
por jaque mate (adems de otros motivos).
Consideraciones
4.4.5. Strategy
C4
El patrn Strategy se utiliza para encapsular el funcionamiento de una familia de
algoritmos, de forma que se pueda intercambiar su uso sin necesidad de modicar a
los clientes.
Problema
Solucin
La idea es extraer los mtodos que conforman el comportamiento que puede ser
intercambiado y encapsularlo en una familia de algoritmos. En este caso, el movi-
miento del jugador se extrae para formar una jerarqua de diferentes movimientos
(Movement). Todos ellos implementan el mtodo move() que recibe un contexto
que incluye toda la informacin necesaria para llevar a cabo el algoritmo.
El siguiente fragmento de cdigo indica cmo se usa este esquema por parte de
un cliente. Ntese que al congurarse cada jugador, ambos son del mismo tipo de
cara al cliente aunque ambos se comportarn de forma diferente al invocar al mtodo
doBestMove().
Consideraciones
El patrn Strategy es una buena alternativa a realizar subclases en las entidades que
deben comportarse de forma diferente en funcin del algoritmo utilizado. Al extraer la
heurstica a una familia de algoritmos externos, obtenemos los siguientes benecios:
4.4.6. Reactor
El patrn Reactor es un patrn arquitectural para resolver el problema de cmo
atender peticiones concurrentes a travs de seales y manejadores de seales.
Problema
Solucin
En el patrn Reactor se denen una serie de actores con las siguientes responsabi-
lidades (vase gura 4.18):
Eventos: los eventos externos que puedan ocurrir sobre los recursos (Handles).
Normalmente su ocurrencia es asncrona y siempre est relaciona a un recurso
C4
determinado.
Recursos (Handles): se reere a los objetos sobre los que ocurren los eventos.
La pulsacin de una tecla, la expiracin de un temporizador o una conexin en-
trante en un socket son ejemplos de eventos que ocurren sobre ciertos recursos.
La representacin de los recursos en sistemas tipo GNU/Linux es el descriptor
de chero.
Manejadores de Eventos: Asociados a los recursos y a los eventos que se pro-
ducen en ellos, se encuentran los manejadores de eventos (EventHandler)
que reciben una invocacin a travs del mtodo handle() con la informacin
del evento que se ha producido.
Reactor: se trata de la clase que encapsula todo el comportamiento relativo a
la desmultiplexacin de los eventos en manejadores de eventos (dispatching).
Cuando ocurre un cierto evento, se busca los manejadores asociados y se les
invoca el mtodo handle().
Ntese que aunque los eventos ocurran concurrentemente el Reactor serializa las
llamadas a los manejadores. Por lo tanto, la ejecucin de los manejadores de eventos
ocurre de forma secuencial.
Consideraciones
4.4.7. Visitor
El patrn Visitor proporciona un mecanismo para realizar diferentes operaciones
sobre una jerarqua de objetos de forma que aadir nuevas operaciones no haga nece-
sario cambiar las clases de los objetos sobre los que se realizan las operaciones.
Problema
Solucin
Implementacin
C4
Figura 4.19: Diagrama de clases del patrn Visitor
Consideraciones
C4
Hasta el momento, se han descrito algunos de los patrones de diseo ms impor-
tantes que proporcionan una buena solucin a determinados problemas a nivel de dise-
o. Todos ellos son aplicables a cualquier lenguaje de programacin en mayo o menor
medida: algunos patrones de diseo son ms o menos difciles de implementar en un
lenguaje de programacin determinado. Depender de las estructuras de abstraccin
que ste proporcione.
Sin embargo, a nivel de lenguaje de programacin, existen patrones que hacen
que un programador comprenda mejor dicho lenguaje y aplique soluciones mejores,
e incluso ptimas, a la hora de resolver un problema de codicacin. Los expresiones
idiomticas (o simplemente, idioms) son un conjunto de buenas soluciones de progra-
macin que permiten:
Al igual que los patrones, los idioms tienen un nombre asociado (adems de sus
alias), la denicin del problema que resuelven y bajo qu contexto, as como algunas
consideraciones (eciencia, etc.). A continuacin, se muestran algunos de los ms
relevantes. En la seccin de Patrones de Diseo Avanzados se exploran ms de
ellos.
Para que no aparezcan sorpresas una clase no trivial debe tener como mnimo:
C4
1 class Vehicle
2 {
3 public:
4 virtual ~Vehicle();
5 virtual std::string name() = 0;
6 virtual void run() = 0;
7 };
8
9 class Car: public Vehicle
10 {
11 public:
12 virtual ~Car();
13 std::string name() { return "Car"; }
14 void run() { /* ... */ }
15 };
16
17 class Motorbike: public Vehicle
18 {
19 public:
20 virtual ~Motorbike();
21 std::string name() { return "Motorbike"; }
22 void run() { /* ... */ }
23 };
Este idiom muestra cmo crear una clase que se comporta como una interfaz,
utilizando mtodosvirtuales puros.
El compilador fuerza que los mtodos virtuales puros sean implementados por
las clases derivadas, por lo que fallar en tiempo de compilacin si hay alguna que
no lo hace. Como resultado, tenemos que Vehicle acta como una clase interfaz.
Ntese que el destructor se ha declarado como virtual. Como ya se cit en la forma
cannica ortodoxa (seccin 4.5.1), esto es una buena prctica para evitar posibles leaks
de memoria en tiempo de destruccin de un objeto Vehicle usado polimrcamente
(por ejemplo, en un contenedor). Con esto, se consigue que se llame al destructor de
la clase ms derivada.
Desarrollo de una librera o una API que va ser utilizada por terceros.
Clases externas de mdulos internos de un programa de tamao medio o grande.
4.5.4. pImpl
Pointer To Implementation (pImpl), tambin conocido como Handle Body u Opa-
que Pointer, es un famoso idiom (utilizado en otros muchos) para ocultar la implemen-
tacin de una clase en C++. Este mecanismo puede ser muy til sobre todo cuando se
tienen componentes reutilizables cuya declaracin o interfaz puede cambiar, lo cual
implicara recompilar a todos sus usuarios. El objetivo es minimizar el impacto de un
cambio en la declaracin de la clase a sus usuarios.
En C++, un cambio en las variables miembro de una clase o en los mtodos
inline puede suponer que los usuarios de dicha clase tengan que recompilar. Para
resolverlo, la idea de pImpl es que la clase ofrezca una interfaz pblica bien denida
y que sta contenga un puntero a su implementacin, descrita de forma privada.
Por ejemplo, la clase Vehicle podra ser de la siguiente forma:
Como se puede ver, es una clase muy sencilla ya que ofrece slo un mtodo pbli-
co. Sin embargo, si queremos modicarla aadiendo ms atributos o nuevos mtodos
privados, se obligar a los usuarios de la clase a recompilar por algo que realmente no
utilizan directamente. Usando pImpl, quedara el siguiente esquema:
C4
9 class VehicleImpl;
10 VehicleImpl* _pimpl;
11 };
capitalEn este captulo se proporciona una visin general de STL (Standard Tem-
plate Library), la biblioteca estndar proporcionada por C++, en el contexto del desa-
rrollo de videojuegos, discutiendo su utilizacin en dicho mbito de programacin.
Asimismo, tambin se realiza un recorrido exhaustivo por los principales tipos de
contenedores, estudiando aspectos relevantes de su implementacin, rendimiento y
uso en memoria. El objetivo principal que se pretende alcanzar es que el lector sea ca-
paz de utilizar la estructura de datos ms adecuada para solucionar un problema,
justicando el porqu y el impacto que tiene dicha decisin sobre el proyecto en su
conjunto.
Usando STL Desde un punto de vista abstracto, la biblioteca estndar de C++, STL, es un
STL es sin duda una de las bibliote-
conjunto de clases que proporciona la siguiente funcionalidad [94]:
cas ms utilizadas en el desarrollo
de aplicaciones de C++. Adems,
est muy optimizada para el mane-
Soporte a caractersticas del lenguaje, como por ejemplo la gestin de memoria
jo de estructuras de datos y de algo- e informacin relativa a los tipos de datos manejados en tiempo de ejecucin.
ritmos bsicos, aunque su comple-
jidad es elevada y el cdigo fuente Soporte relativo a aspectos del lenguaje denidos por la implementacin, como
es poco legible para desarrolladores por ejemplo el valor en punto otante con mayor precisin.
poco experimentados.
Soporte para funciones que no se pueden implementar de manera ptima con
las herramientas del propio lenguaje, como por ejemplo ciertas funciones mate-
mticas o asignacin de memoria dinmica.
147
[148] CAPTULO 5. LA BIBLIOTECA STL
La gura 5.2 muestra una perspectiva global de la organizacin de STL y los di-
ferentes elementos que la denen. Como se puede apreciar, STL proporciona una gran
variedad de elementos que se pueden utilizar como herramientas para la resolucin de
problemas dependientes de un dominio en particular.
Las utilidades de la biblioteca estndar se denen en el espacio de nombres std y
se encuentran a su vez en una serie de bibliotecas, que identican las partes fundamen-
tales de STL. Note que no se permite la modicacin de la biblioteca estndar y no es
aceptable modicar su contenido mediante macros, ya que afectara a la portabilidad
del cdigo desarrollado.
En el mbito del desarrollo de videojuegos, los contenedores juegan un papel fun- Abstraccin STL
damental como herramienta para el almacenamiento de informacin en memoria. En El uso de los iteradores en STL re-
este contexto, la realizacin un estudio de los mismos, en trminos de operaciones, presenta un mecanismo de abstrac-
gestin de memoria y rendimiento, es especialmente importante para utilizarlos ade- cin fundamental para realizar el re-
cuadamente. Los contenedores estn representados por dos tipos principales. Por una corrido, el acceso y la modicacin
sobre los distintos elementos alma-
parte, las secuencias permiten almacenar elementos en un determinado orden. Por otra cenados en un contenedor.
parte, los contenedores asociativos no tienen vinculado ningn tipo de restriccin de
orden.
La herramienta para recorrer los contenedores est representada por el iterador.
Todos los contenedores mantienen dos funciones relevantes que permiten obtener dos
iteradores:
Iteradores Algoritmos
C5
<iterator> Iteradores y soporte a iteradores
<algorithm> algoritmos generales
<cstdlib> bsearch() y qsort()
Cadenas
Figura 5.2: Visin general de la organizacin de STL [94]. El asterisco referencia a elementos con el estilo
del lenguaje C.
[150] CAPTULO 5. LA BIBLIOTECA STL
en concreto la funcin push_back para aadir un elemento al nal del vector. tas estructuras de datos incluidas en
Finalmente, en la lnea 13 se declara un iterador de tipo vector<int> que se utili-
la biblioteca estndar.
zar como base para el recorrido del vector. La inicializacin del mismo se encuentra
en la lnea 15 , es decir, en la parte de inicializacin del bucle for, de manera que
originalmente el iterador apunta al primer elemento del vector. La iteracin sobre el
vector
se estar realizando hasta que no se llegue al iterador devuelto por end() (lnea
16 ), es decir, al elemento posterior al ltimo.
(lnea 17 ), al igual que el acceso
Note cmo el incremento del iterador es trivial
del contenido al que apunta el iterador (lnea 18 ), mediante una nomenclatura similar
a la utilizada para manejar punteros.
Por otra parte, STL proporciona un conjunto estndar de algoritmos que se pueden
aplicar a los contenedores e iteradores. Aunque dichos algoritmos son bsicos, stos se
pueden combinar para obtener el resultado deseado por el propio programador. Algu-
nos ejemplos de algoritmos son la bsqueda de elementos, la copia, la ordenacin, etc.
Al igual que ocurre con todo el cdigo de STL, los algoritmos estn muy optimizados
y suelen sacricar la simplicidad para obtener mejores resultados que proporcionen
una mayor eciencia.
5.2. STL y el desarrollo de videojuegos [151]
C5
desarrollo, herramientas transversales y middlewares con el objetivo de dar soporte al
proceso de desarrollo de un videojuego (ver seccin 1.2.2). STL estara incluido en
dicha capa como biblioteca estndar de C++, posibilitando el uso de diversos conte-
nedores como los mencionados anteriormente.
Desde un punto de vista general, a la hora de abordar el desarrollo de un videojue-
go, el programador o ingeniero tendra que decidir si utilizar STL o, por el contrario,
utilizar alguna otra biblioteca que se adapte mejor a los requisitos impuestos por el
juego a implementar.
5.2.2. Rendimiento
Uno de los aspectos crticos en el mbito del desarrollo de videojuegos es el ren-
dimiento, ya que es fundamental para lograr un sensacin de interactividad y para
dotar de sensacin de realismo el usuario de videojuegos. Recuerde que un videojue-
go es una aplicacin grca de renderizado en tiempo real y, por lo tanto, es necesario
asegurar una tasa de frames por segundo adecuada y en todo momento. Para ello, el
rendimiento de la aplicacin es crtico y ste viene determinado en gran medida por
las herramientas utilizadas.
En general, STL proporciona un muy buen rendimiento debido principalmente a
que ha sido mejorado y optimizado por cientos de desarrolladores en estos ltimos
aos, considerando las propiedades intrnsecas de cada uno de sus elementos y de las
plataformas sobre las que se ejecutar.
STL ser normalmente ms eciente que otra implementacin y, por lo tanto, es La herramienta ideal
el candidato ideal para manejar las estructuras de datos de un videojuego. Mejorar el Benjamin Franklin arm que si
rendimiento de STL implica tener un conocimiento muy profundo de las estructuras dispusiera de 8 horas para derribar
de datos a manejar y puede ser una alternativa en casos extremos y con plataformas un rbol, empleara 6 horas para a-
hardware especcas. Si ste no es el caso, entonces STL es la alternativa directa. lar su hacha. Esta reexin se puede
aplicar perfectamente a la hora de
No obstante, algunas compaas tan relevantes en el mbito del desarrollo de vi- decidir qu estructura de datos uti-
deojuegos, como EA (Electronic Arts), han liberado su propia adaptacin de STL de- lizar para solventar un problema.
nominada EASTL (Electronic Arts Standard Template Library) 1 , justicando esta
decisin en base a la deteccin de ciertas debilidades, como el modelo de asignacin
de memoria, o el hecho de garantizar la consistencia desde el punto de vista de la
portabilidad.
Adems del rendimiento de STL, es importante considerar que su uso permite
que el desarrollador se centre principalmente en el manejo de elementos propios, es
decir, a nivel de nodos en una lista o claves en un diccionario, en lugar de prestar ms
importancia a elementos de ms bajo nivel, como punteros o buffers de memoria.
Uno de los aspectos claves a la hora de manejar de manera eciente STL se basa en
utilizar la herramienta adecuada para un problema concreto. Si no es as, entonces es
fcil reducir enormemente el rendimiento de la aplicacin debido a que no se utiliz,
por ejemplo, la estructura de datos ms adecuada.
5.2.3. Inconvenientes
El uso de STL tambin puede presentar ciertos inconvenientes; algunos de ellos
directamente vinculados a su propia complejidad [27]. En este contexto, uno de los
principales inconvenientes al utilizar STL es la depuracin de programas, debido a
aspectos como el uso extensivo de plantillas por parte de STL. Este planteamiento
complica bastante la inclusin de puntos de ruptura y la depuracin interactiva. Sin
embargo, este problema no es tan relevante si se supone que STL ha sido extensamente
probado y depurado, por lo que en teora no sera necesario llegar a dicho nivel.
Por otra parte, visualizar de manera directa el contenido de los contenedores o
estructuras de datos utilizadas puede resultar complejo. A veces, es muy complicado
conocer exactamente a qu elemento est apuntando un iterador o simplemente ver
todos los elementos de un vector.
La ltima desventaja es la asignacin de memoria, debido a que se pretende que
STL se utilice en cualquier entorno de cmputo de propsito general. En este contexto,
es lgico suponer que dicho entorno tenga suciente memoria y la penalizacin por
asignar memoria no es crtica. Aunque esta situacin es la ms comn a la hora de
1 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2271.html
5.3. Secuencias [153]
C5
En general, hacer uso de STL para desarrollar un videojuego es una de las
mejores alternativas posibles. En el mbito comercial, un gran nmero de
juegos de ordenador y de consola han hecho uso de STL. Recuerde que siem-
pre es posible personalizar algunos aspectos de STL, como la asignacin de
memoria, en funcin de las restricciones existentes.
Tamao elemento La principal caracterstica de los contenedores de secuencia es que los elementos
almacenados mantienen un orden determinado. La insercin y eliminacin de ele-
Inicio mentos se puede realizar en cualquier posicin debido a que los elementos residen
en una secuencia concreta. A continuacin se lleva a cabo una discusin de tres de
los contenedores de secuencia ms utilizados: el vector (vector), la cola de doble n
(deque) y la lista (list).
Implementacin
Rendimiento
C5
y libera el bloque de memoria inicial. Este planteamiento evita que el vector tenga
que reasignar memoria con frecuencia. En este contexto, es importante resaltar que un
vector no garantiza la validez de un puntero o un iterador despus de una insercin, ya
que se podra dar esta situacin. Por lo tanto, si es necesario acceder a un elemento en
concreto, la solucin ideal pasa por utilizar el ndice junto con el operador [].
Vectores y reserve() Los vectores permiten la preasignacin de un nmero de entradas con el objetivo
de evitar la reasignacin de memoria y la copia de elementos a un nuevo bloque.
La reserva de memoria explcita Para ello, se puede utilizar la operacin reserve de vector que permite especicar la
puede contribuir a mejorar el rendi-
miento de los vectores y evitar un cantidad de memoria reservada para el vector y sus futuros elementos.
alto nmero de operaciones de re-
serva.
El caso contrario a esta situacin est representado por vectores que contienen
datos que se usan durante un clculo en concreto pero que despus se descartan. Si
esta situacin se repite de manera continuada, entonces se est asignando y liberando
el vector constantemente. Una posible solucin consiste en mantener el vector como
esttico y limpiar todos los elementos despus de utilizarlos. Este planteamiento ha-
ce que el vector quede vaco y se llame al destructor de cada uno de los elementos
previamente contenidos.
Finalmente, es posible aprovecharse del hecho de que los elementos de un vector
se almacenan de manera contigua en memoria. Por ejemplo, sera posible pasar el
contenido de un vector a una funcin que acepte un array, siempre y cuando dicha
funcin no modique su contenido [27], ya que el contenido del vector no estara
sincronizado.
[156] CAPTULO 5. LA BIBLIOTECA STL
5.3.2. Deque
Deque proviene del trmino ingls double-ended queue y representa una cola de
doble n. Este tipo de contenedor es muy parecido al vector, ya que proporciona
acceso directo a cualquier elemento y permite la insercin y eliminacin en cualquier
posicin, aunque con distinto impacto en el rendimiento. La principal diferencia reside
en que tanto la insercin como eliminacin del primer y ltimo elemento de la cola
son muy rpidas. En concreto, tienen una complejidad constante, es decir, O(1).
La colas de doble n incluyen la funcionalidad bsica de los vectores pero tam- Deque y punteros
bin considera operaciones para insertar y eliminar elementos, de manera explcita, al
principio del contenedor3 . La cola de doble n no garanti-
za que todos los elementos almace-
nados residan en direcciones conti-
guas de memoria. Por lo tanto, no es
Implementacin posible realizar un acceso seguro a
los mismos mediante aritmtica de
punteros.
Este contenedor de secuencia, a diferencia de los vectores, mantiene varios blo-
ques de memoria, en lugar de uno solo, de forma que se reservan nuevos bloques
conforme el contenedor va creciendo en tamao. Al contrario que ocurra con los vec-
tores, no es necesario hacer copias de datos cuando se reserva un nuevo bloque de
memoria, ya que los reservados anteriormente siguen siendo utilizados.
La cabecera de la cola de doble n almacena una serie de punteros a cada uno de
los bloques de memoria, por lo que su tamao aumentar si se aaden nuevos bloques
que contengan nuevos elementos.
3 http://www.cplusplus.com/reference/stl/deque/
5.3. Secuencias [157]
Cabecera Bloque 1
Principio
Vaco
C5
Final
Elemento 1
#elementos
Elemento 2
Tamao elemento
Bloque 1 Bloque 2
Bloque 2 Elemento 3
Bloque n Elemento 4
Elemento 5
Vaco
Bloque n Elemento 6
Elemento j
Elemento k
Vaco
Rendimiento
5.3.3. List
La lista es otro contenedor de secuencia que diere de los dos anteriores. En pri-
mer lugar, la lista proporciona iteradores bidireccionales, es decir, iteradores que
permiten navegar en los dos sentidos posibles: hacia adelante y hacia atrs. En se-
gundo lugar, la lista no proporciona un acceso aleatorio a los elementos que contiene,
como s hacen tanto los vectores como las colas de doble n. Por lo tanto, cualquier
algoritmo que haga uso de este tipo de acceso no se puede aplicar directamente sobre
listas.
La principal ventaja de la lista es el rendimiento y la conveniencia para determina-
das operaciones, suponiendo que se dispone del iterador necesario para apuntar a una
determinada posicin.
La especicacin de listas de STL ofrece una funcionalidad bsica muy similar Algoritmos en listas
a la de cola de doble n, pero tambin incluye funcionalidad relativa a aspectos ms
complejos como la fusin de listas o la bsqueda de elementos4 . La propia naturaleza de la lista per-
mite que se pueda utilizar con un
buen rendimiento para implementar
algoritmos o utilizar los ya existen-
Implementacin tes en la propia biblioteca.
Rendimiento
Cabecera
Principio Siguiente Siguiente Siguiente
C5
Final Anterior Anterior Anterior
#elementos
Elemento 1 Elemento 2 Elemento 3
Tamao elemento
...
El planteamiento basado en manejar punteros hace que las listas sean ms ecien-
tes a la hora de, por ejemplo, reordenar sus elementos, ya que no es necesario realizar
copias de datos. En su lugar, simplemente hay que modicar el valor de los punteros
de acuerdo al resultado esperado o planteado por un determinado algoritmo. En este
contexto, a mayor nmero de elementos en una lista, mayor benecio en comparacin
con otro tipo de contenedores.
El anterior listado de cdigo mostrar el siguiente resultado por la salida estndar:
0: 23 1: 28 2: 10 3: 17 4: 20 5: 22 6: 11
2: 10 6: 11 3: 17 4: 20 5: 22 0: 23 1: 28
C5
Recorrido Rpido Rpido Menos rpido
Asignacin memoria Raramente Peridicamente Con cada I/E
Acceso memoria sec. S Casi siempre No
Invalidacin iterador Tras I/E Tras I/E Nunca
Cuadro 5.1: Resumen de las principales propiedades de los contenedores de secuencia previamente estu-
diados (I/E = insercin/eliminacin).
7 9 5
Cabecera
Principio
C5
Final
Tamao elemento
...
Hijoi Elemento Hijodi Hijodi Elemento Hijod
Implementacin
Rendimiento
Implementacin
Rendimiento
C5
modo, es posible obtener cierta ventaja de manejar claves simples sobre una gran
cantidad de elementos complejos, mejorando el rendimiento de la aplicacin. Sin em-
bargo, nunca hay que olvidar que si se manejan claves ms complejas como cadenas
o tipos de datos denidos por el usuario, el rendimiento puede verse afectado negati-
vamente.
El operador [] Por otra parte, el uso del operador [] no tiene el mismo rendimiento que en vecto-
res, ya que implica realizar la bsqueda del elemento a partir de la clave y, por lo tanto,
El operador [] se puede utilizar pa-
ra acceder, escribir o actualizar in- no sera de un orden de complejidad constante sino logartmico. Al usar este operador,
formacin sobre algunos contene- tambin hay que tener en cuenta que si se escribe sobre un elemento que no existe,
dores. Sin embargo, es importante entonces ste se aade al contenedor. Sin embargo, si se intenta leer un elemento que
considerar el impacto en el rendi- no existe, entonces el resultado es que el elemento por defecto se aade para la clave
miento al utilizar dicho operador, el
cual vendr determinado por el con- en concreto y, al mismo tiempo, se devuelve dicho valor. El siguiente listado de cdigo
tenedor usado. muestra un ejemplo.
Como se puede apreciar, el listado de cdigo muestra caractersticas propias de
STL para manejar los elementos del contenedor:
En la lnea 7 se hace uso de pair paradeclarar
una pareja de valores: i) un itera-
dor al contenedor denido en la lnea 6 y ii) un valor booleano. Esta
estructura
se utilizar para recoger el valor de retorno de una insercin (lnea 14 ).
Las lneas 10-11 muestran inserciones de elementos.
En la lnea 15 se recoge el valor de retorno al intentar insertar un elemento
con una clave ya existente. Note cmo se accede al iterador en la lnea 17 para
obtener el valor previamente almacenado en la entrada con clave 2.
La lnea 20 muestra un ejemplo de insercin con el operador [], ya que la clave
3 no se haba utilizado previamente.
La lnea 23 ejemplica la insercin de un elemento por defecto. Al tratarse de
cadenas de texto, el valor por defecto es la cadena vaca.
Cuadro 5.2: Resumen de las principales propiedades de los contenedores asociativos previamente estudia-
dos (I/E = insercin/eliminacin).
Ante esta situacin, resulta deseable hacer uso de la operacin nd para determi- Usando referencias
nar si un elemento pertenece o no al contenedor, accediendo al mismo con el iterador Recuerde consultar informacin so-
devuelto por dicha operacin. As mismo, la insercin de elementos es ms eciente bre las operaciones de cada uno
con insert en lugar de con el operador [], debido a que este ltimo implica un ma- de los contenedores estudiados para
yor nmero de copias del elemento a insertar. Por el contrario, [] es ligeramente ms conocer exactamente su signatura y
cmo invocarlas de manera ecien-
eciente a la hora de actualizar los contenidos del contenedor en lugar de insert. te.
Respecto al uso de memoria, los mapas tienen un comportamiento idntico al de
los conjuntos, por lo que se puede suponer los mismos criterios de aplicacin.
C5
desarrollador tenga que especicarla. Este enfoque se basa en que, por ejemplo, los
contenederos de secuencia existentes tienen la exibilidad necesaria para comportar-
se como otras estructuras de datos bien conocidas, como por ejemplo las pilas o las
colas.
LIFO Un pila es una estructura de datos que solamente permite aadir y eliminar ele-
La pila est disea para operar en un mentos por un extremo, la cima. En este contexto, cualquier conteneder de secuencia
contexto LIFO (Last In, First Out) discutido anteriormente se puede utilizar para proporcionar dicha funcionalidad. En
(last-in rst-out), es decir, el ele- el caso particular de STL, es posible manejar pilas e incluso especicar la imple-
mento que se apil ms reciente- mentacin subyacente que se utilizar, especicando de manera explcita cul ser el
mente ser el primero en salir de la
pila. contenedor de secuencia usado.
Aunque sera perfectamente posible delegar en el programador el manejo del con-
tenedor de secuencia para que se comporte como, por ejemplo, una pila, en la prctica
existen dos razones importantes para la denicin de adaptadores:
1. La declaracin explcita de un adaptador, como una pila o stack, hace que sea el
Cima cdigo sea mucho ms claro y legible en trminos de funcionalidad. Por ejem-
plo, declarar una pila proporciona ms informacin que declarar un vector que
se comporte como una pila. Esta aproximacin facilita la interoperabilidad con
otros programadores.
2. El compilador tiene ms informacin sobre la estructura de datos y, por lo tanto,
puede contribuir a la deteccin de errores con ms ecacia. Si se utiliza un vec-
tor para modelar una pila, es posible utilizar alguna operacin que no pertenezca
Elemento 1 a la interfaz de la pila.
5.5.1. Stack
Elemento 2
El adaptador ms sencillo en STL es la pila o stack9 . Las principales operaciones
sobre una pila son la insercin y eliminacin de elementos por uno de sus extremos:
Elemento 3 la cima. En la literatura, estas operaciones se conocen como push y pop, respectiva-
mente.
8 vector<int>::iterator it;
9 stack<int> pila;
10
11 for (int i = 0; i < 10; i++) // Rellenar el vector
12 fichas.push_back(i);
13
14 for (it = fichas.begin(); it != fichas.end(); ++it)
15 pila.push(*it); // Apilar elementos para invertir
16
17 fichas.clear(); // Limpiar el vector
18
19 while (!pila.empty()) { // Rellenar el vector
20 fichas.push_back(pila.top());
21 pila.pop();
22 }
23
24 return 0;
25 }
5.5.2. Queue
Al igual que ocurre con la pila, la cola o queue10 es otra estructura de datos de
uso muy comn que est representada en STL mediante un adaptador de secuencia.
Bsicamente, la cola mantiene una interfaz que permite la insercin de elementos al Insercin
nal y la extraccin de elementos slo por el principio. En este contexto, no es posible
acceder, insertar y eliminar elementos que estn en otra posicin.
Por defecto, la cola utiliza la implementacin de la cola de doble n, siendo posible
utilizar la implementacin de la lista. Sin embargo, no es posible utilizar el vector
debido a que este contenedor no proporciona la operacin push_front, requerida por
el propio adaptador. De cualquier modo, el rendimiento de un vector para eliminar
elementos al principio no es particularmente bueno, ya que tiene una complejidad Elemento 1
lineal.
Elemento 2
5.5.3. Cola de prioridad Elemento 3
STL tambin proporciona el adaptador cola de prioridad o priority queue11 como ...
caso especial de cola previamente discutido. La diferencia entre los dos adaptadores
reside en que el elemento listo para ser eliminado de la cola es aqul que tiene una
mayor prioridad, no el elemento que se aadi en primer lugar.
Elemento i
As mismo, la cola con prioridad incluye cierta funcionalidad especca, a diferen-
cia del resto de adaptadores estudiados. Bsicamente, cuando un elemento se aade a ...
la cola, entonces ste se ordena de acuerdo a una funcin de prioridad.
Por defecto, la cola con prioridad se apoya en la implementacin de vector, pero es Elemento n
posible utilizarla tambin con deque. Sin embargo, no se puede utilizar una lista debi-
do a que la cola con prioridad requiere un acceso aleatorio para insertar ecientemente
elementos ordenados.
La comparacin de elementos sigue el mismo esquema que el estudiado en la sec-
cin 5.4.1 y que permita denir el criterio de inclusin de elementos en un conjunto,
el cual estaba basado en el uso del operador menor que.
Extraccin
10 http://www.cplusplus.com/reference/stl/queue/
11 http://www.cplusplus.com/reference/stl/priority_queue/ Figura 5.12: Visin abstracta de
una cola o queue. Los elementos so-
lamente se aaden por el nal y se
eliminan por el principio.
5.5. Adaptadores de secuencia [169]
C5
Gestin de Recursos
Captulo 6
David Vallejo Fernndez
E
n este captulo se cubren aspectos esenciales a la hora de afrontar el diseo
de un juego. En particular, se discutirn las arquitecturas tpicas del bucle de
juego, haciendo especial hincapi en un esquema basado en la gestin de los
estados de un juego. Como caso de estudio concreto, en este captulo se propone una
posible implementacin del bucle principal mediante este esquema haciendo uso de
las bibliotecas que Ogre3D proporciona.
As mismo, este captulo discute la gestin de recursos y, en concreto, la gestin
de sonido y de efectos especiales. La gestin de recursos es especialmente importante
en el mbito del desarrollo de videojuegos, ya que el rendimiento de un juego depende
en gran medida de la eciencia del subsistema de gestin de recursos.
Finalmente, la gestin del sistema de archivos, junto con aspectos bsicos de en-
trada/salida, tambin se estudia en este captulo. Adems, se plantea el diseo e imple-
mentacin de un importador de datos que se puede utilizar para integrar informacin
multimedia a nivel de cdigo fuente.
171
[172] CAPTULO 6. GESTIN DE RECURSOS
C6
De cualquier modo, es necesario un planteamiento que permita actualizar el es-
tado de cada uno de los subsistemas y que considere las restricciones temporales de
los mismos. Tpicamente, este planteamiento se suele abordar mediante el bucle de
juego, cuya principal responsabilidad consiste en actualizar el estado de los distintos
componentes del motor tanto desde el punto de vista interno (ej. coordinacin en-
tre subsistemas) como desde el punto de vista externo (ej. tratamiento de eventos de
teclado o ratn).
En las plataformas WindowsTM , los juegos han de atender los mensajes recibidos
por el propio sistema operativo y dar soporte a los distintos componentes del propio
motor de juego. Tpicamente, en estas plataformas se implementan los denominados
message pumps [42], como responsables del tratamiento de este tipo de mensajes.
Desde un punto de vista general, el planteamiento de este esquema consiste en
atender los mensajes del propio sistema operativo cuando llegan, interactuando con el
motor de juegos cuando no existan mensajes del sistema operativo por procesar. En
ese caso se ejecuta una iteracin del bucle de juego y se repite el mismo proceso.
La principal consecuencia de este enfoque es que los mensajes del sistema ope-
message dispatching
rativo tienen prioridad con respecto a aspectos crticos como el bucle de renderizado.
Por ejemplo, si la propia ventana en la que se est ejecutando el juego se arrastra o su
tamao cambia, entonces el juego se congelar a la espera de nalizar el tratamiento
de eventos recibidos por el propio sistema operativo.
17 glutInitWindowSize(640, 480);
18 glutCreateWindow("Session #04 - Solar System");
19
20 // Definicin de las funciones de retrollamada.
21 glutDisplayFunc(display);
22 glutReshapeFunc(resize);
23 // Eg. update se ejecutar cuando el sistema
24 // capture un evento de teclado.
C6
25 // Signatura de glutKeyboardFunc:
26 // void glutKeyboardFunc(void (*func)
27 // (unsigned char key, int x, int y));
28 glutKeyboardFunc(update);
29
30 glutMainLoop();
31
32 return 0;
33 }
Men Game
Intro Juego
ppal over
Pausa
Figura 6.5: Visin general de una mquina de estados nita que representa los estados ms comunes en
cualquier juego.
Desde un punto de vista general, los juegos se pueden dividir en una serie de
etapas o estados que se caracterizan no slo por su funcionamiento sino tambin por
la interaccin con el usuario o jugador. Tpicamente, en la mayor parte de los juegos
es posible diferenciar los siguientes estados:
C6
1 1 1 1..*
InputManager GameManager GameState
1 1
OIS:: OIS::
IntroState PlayState PauseState
Keyboard Mouse
Ogre::
Singleton
Figura 6.6: Diagrama de clases del esquema de gestin de estados de juego con Ogre3D. En un tono ms
oscuro se reejan las clases especcas de dominio.
www.ogre3d.org/tikiwiki/Managing+Game+States+with+OGRE
[178] CAPTULO 6. GESTIN DE RECURSOS
C6
44 void pushState (GameState* state) {
45 GameManager::getSingletonPtr()->pushState(state);
46 }
47 void popState () {
48 GameManager::getSingletonPtr()->popState();
49 }
50
51 };
52
53 #endif
Sin embargo, antes de pasar a discutir esta clase, en el diseo discutido se con-
templa la denicin explcita de la clase InputManager, como punto central para la
gestin de eventos de entrada, como por ejemplo los de teclado o de ratn.
Ogre::Singleton? El InputManager sirve como interfaz para aquellas entidades que estn interesa-
das en procesar eventos de entrada (como se discutir ms adelante), ya que mantiene
La clase InputManager implementa operaciones para aadir y eliminar listeners de dos tipos: i) OIS::KeyListener y ii)
el patrn Singleton mediante las uti-
lidades de Ogre3D. Es posible utili- OIS::MouseListener. De hecho, esta clase hereda de ambas clases. Adems, imple-
zar otros esquemas para que su im- menta el patrn Singleton con el objetivo de que slo exista una nica instancia de la
plementacin no depende de Ogre misma.
y se pueda utilizar con otros frame-
works.
Listado 6.5: Clase InputManager.
1 // SE OMITE PARTE DEL CDIGO FUENTE.
2 // Gestor para los eventos de entrada (teclado y ratn).
3 class InputManager : public Ogre::Singleton<InputManager>,
4 public OIS::KeyListener, public OIS::MouseListener {
5 public:
6 InputManager ();
7 virtual ~InputManager ();
8
9 void initialise (Ogre::RenderWindow *renderWindow);
10 void capture ();
11
12 // Gestin de listeners.
13 void addKeyListener (OIS::KeyListener *keyListener,
14 const std::string& instanceName);
15 void addMouseListener (OIS::MouseListener *mouseListener,
16 const std::string& instanceName );
17 void removeKeyListener (const std::string& instanceName);
18 void removeMouseListener (const std::string& instanceName);
19 void removeKeyListener (OIS::KeyListener *keyListener);
20 void removeMouseListener (OIS::MouseListener *mouseListener);
21
22 OIS::Keyboard* getKeyboard ();
23 OIS::Mouse* getMouse ();
24
25 // Heredados de Ogre::Singleton.
26 static InputManager& getSingleton ();
27 static InputManager* getSingletonPtr ();
28
29 private:
30 // Tratamiento de eventos.
31 // Delegar en los listeners.
32 bool keyPressed (const OIS::KeyEvent &e);
33 bool keyReleased (const OIS::KeyEvent &e);
34
35 bool mouseMoved (const OIS::MouseEvent &e);
[180] CAPTULO 6. GESTIN DE RECURSOS
C6
39
40 bool mouseMoved (const OIS::MouseEvent &e);
41 bool mousePressed (const OIS::MouseEvent &e, OIS::MouseButtonID
id);
42 bool mouseReleased (const OIS::MouseEvent &e, OIS::MouseButtonID
id);
43
44 // Gestor de eventos de entrada.
45 InputManager *_inputMgr;
46 // Estados del juego.
47 std::stack<GameState*> _states;
48 };
Note que esta clase contiene una funcin miembro start() (lnea 12 ), denida de
manera explcita para inicializar el gestor de juego, establecer el estado inicial (pasado
como parmetro) y arrancar el bucle de renderizado.
PlayState
Listado 6.8: Clase GameManager. Funcin changeState().
1 void
2 GameManager::changeState
3 (GameState* state) IntroState
4 {
5 // Limpieza del estado actual.
6 if (!_states.empty()) {
7 // exit() sobre el ltimo estado.
8 _states.top()->exit();
tecla 'p'
9 // Elimina el ltimo estado.
10 _states.pop();
11 }
12 // Transicin al nuevo estado.
13 _states.push(state);
14 // enter() sobre el nuevo estado.
15 _states.top()->enter();
16 }
PauseState
Otro aspecto relevante del diseo de esta clase es la delegacin de eventos de en-
trada asociados a la interaccin por parte del usuario con el teclado y el ratn. El
diseo discutido permite delegar directamente el tratamiento del evento al estado ac-
tivo, es decir, al estado que ocupe la cima de la pila. Del mismo modo, se traslada
a dicho estado la implementacin de las funciones frameStarted() y frameEnded().
El siguiente listado de cdigo muestra cmo la implementacin de, por ejemplo, la
funcin keyPressed() es trivial.
PlayState
Listado 6.9: Clase GameManager. Funcin keyPressed().
1 bool
2 GameManager::keyPressed
3 (const OIS::KeyEvent &e)
IntroState
4 {
5 _states.top()->keyPressed(e);
6 return true;
7 }
Figura 6.7: Actualizacin de la pi-
la de estados para reanudar el juego
(evento teclado p).
6.1.5. Denicin de estados concretos
Este esquema de gestin de estados general, el cual contiene una clase genrica
GameState, permite la denicin de estados especcos vinculados a un juego en par-
ticular. En la gura 6.6 se muestra grcamente cmo la clase GameState se extiende
para denir tres estados:
C6
A la hora de llevar a cabo dicha implementacin, se ha optado por utilizar el pa-
trn Singleton, mediante la clase Ogre::Singleton, para garantizar que slo existe una
instacia de un objeto por estado.
El siguiente listado de cdigo muestra una posible declaracin de la clase IntroS-
tate. Como se puede apreciar, esta clase hace uso de las funciones tpicas para el
tratamiento de eventos de teclado y ratn.
4 }
5
6 void PauseState::keyPressed (const OIS::KeyEvent &e) {
7 if (e.key == OIS::KC_P) // Tecla p ->Estado anterior.
8 popState();
9 }
C6
1 0..*
NuevoGestor NuevoRecursoPtr NuevoRecurso
Figura 6.11: Diagrama de clases de las principales clases utilizadas en Ogre3D para la gestin de recursos.
En un tono ms oscuro se reejan las clases especcas de dominio.
Carga de niveles Debido a las restricciones hardware que una determinada plataforma de juegos
puede imponer, resulta fundamental hacer uso de un mecanismo de gestin de recur-
Una de las tcnicas bastantes comu-
nes a la hora de cargar niveles de sos que sea exible y escalable para garantizar el diseo y gestin de cualquier tipo
juego consiste en realizarla de ma- de recurso, junto con la posibilidad de cargarlo y liberarlo en tiempo de ejecucin,
nera previa al acceso a dicho nivel. respectivamente. Por ejemplo, si piensa en un juego de plataformas estructurado en
Por ejemplo, se pueden utilizar es- diversos niveles, entonces no sera lgico hacer una carga inicial de todos los nive-
tructuras de datos para representar
los puntos de acceso de un nivel al les al iniciar el juego. Por el contrario, sera ms adecuado cargar un nivel cuando el
siguiente para adelantar el proceso jugador vaya a acceder al mismo.
de carga. El mismo planteamiento
se puede utilizar para la carga de En Ogre, la gestin de recursos se lleva a cabo utilizando principalmente cuatro
texturas en escenarios. clases (ver gura 6.11):
Bsicamente, el desarrollador que haga uso del planteamiento que Ogre ofrece
para la gestin de recursos tendr que implementar algunas funciones heredadas de
las clases anteriormente mencionadas, completando as la implementacin especca
del dominio, es decir, especca de los recursos a gestionar. Ms adelante se discute
en detalle un ejemplo relativo a los recursos de sonido.
[186] CAPTULO 6. GESTIN DE RECURSOS
C6
7 // Inicializacin de SDL.
8 if (SDL_Init(SDL_INIT_VIDEO) != 0) {
9 fprintf(stderr, "Unable to initialize SDL: %s\n",
10 SDL_GetError());
11 return -1;
12 }
13
14 // Cuando termine el programa, llamada a SQLQuit().
15 atexit(SDL_Quit);
16 // Activacin del double buffering.
17 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
18
19 // Establecimiento del modo de vdeo con soporte para OpenGL.
20 screen = SDL_SetVideoMode(640, 480, 16, SDL_OPENGL);
21 if (screen == NULL) {
22 fprintf(stderr, "Unable to set video mode: %s\n",
23 SDL_GetError());
24 return -1;
25 }
26
27 SDL_WM_SetCaption("OpenGL with SDL!", "OpenGL");
28 // Ya es posible utilizar comandos OpenGL!
29 glViewport(80, 0, 480, 480);
30
31 glMatrixMode(GL_PROJECTION);
32 glLoadIdentity();
33 glFrustum(-1.0, 1.0, -1.0, 1.0, 1.0, 100.0);
34 glClearColor(1, 1, 1, 0);
35
36 glMatrixMode(GL_MODELVIEW); glLoadIdentity();
37 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
38
39 // Renderizado de un tringulo.
40 glBegin(GL_TRIANGLES);
41 glColor3f(1.0, 0.0, 0.0); glVertex3f(0.0, 1.0, -2.0);
42 glColor3f(0.0, 1.0, 0.0); glVertex3f(1.0, -1.0, -2.0);
43 glColor3f(0.0, 0.0, 1.0); glVertex3f(-1.0, -1.0, -2.0);
44 glEnd();
45
46 glFlush();
47 SDL_GL_SwapBuffers(); // Intercambio de buffers.
48 SDL_Delay(5000); // Espera de 5 seg.
49
50 return 0;
51 }
Como se puede apreciar en el listado anterior, la lnea 20 establece el modo de v-
deo con soporte para
OpenGL para, posteriormente, hacer uso de comandos OpenGL
a partir de la lnea 29 . Sin embargo, note cmo el
intercambio del contenido entre el
front buffer y el back buffer se realiza en la lnea 47 mediante una primitiva de SDL.
8 basic_sdl_opengl: basic_sdl_opengl.o
9 $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
10
11 basic_sdl_opengl.o: basic_sdl_opengl.c
12 $(CC) $(CFLAGS) $^ -o $@
13
14 clean:
15 @echo Cleaning up...
16 rm -f *~ *.o basic_sdl_opengl
17 @echo Done.
18
19 vclean: clean
Reproduccin de msica
C6
constructor de la clase padre para poder instanciar un recurso, adems de inicializar
las propias variables miembro.
Por otra parte, las funciones de manejo bsico del track (lneas 16-22 ) son en
realidad una interfaz para manejar de una manera adecuada la biblioteca SDL_mixer.
Por ejemplo, la funcin miembro play simplemente interacta con SDL para com-
probar si la cancin estaba pausada y, en ese caso, reanudarla. Si la cancin no estaba
pausada, entonces dicha funcin la reproduce desde el principio. Note cmo se hace
uso del gestor de logs de Ogre3D para almacenar en un archivo la posible ocurrencia
de algn tipo de error.
Figura 6.15: Es posible incluir Listado 6.15: Clase Track. Funcin play().
efectos de sonidos ms avanzados, 1 void
como por ejemplo reducir el nivel 2 Track::play
de volumen a la hora de nalizar la 3 (int loop)
reproduccin de una cancin. 4 {
5 Ogre::LogManager* pLogManager =
6 Ogre::LogManager::getSingletonPtr();
7
8 if(Mix_PausedMusic()) // Estaba pausada?
9 Mix_ResumeMusic(); // Reanudacin.
10
11 // Si no, se reproduce desde el principio.
12 else {
13 if (Mix_PlayMusic(_pTrack, loop) == -1) {
14 pLogManager->logMessage("Track::play() Error al....");
15 throw (Ogre::Exception(Ogre::Exception::ERR_FILE_NOT_FOUND,
16 "Imposible reproducir...",
17 "Track::play()"));
18 }
19 }
20 }
7 findResourceFileInfo(mGroup, mName);
8
9 for (Ogre::FileInfoList::const_iterator i = info->begin();
10 i != info->end(); ++i) {
11 _path = i->archive->getName() + "/" + i->filename;
12 }
13
14 if (_path == "") { // Archivo no encontrado...
15 // Volcar en el log y lanzar excepcin.
16 }
17
18 // Cargar el recurso de sonido.
19 if ((_pTrack = Mix_LoadMUS(_path.c_str())) == NULL) {
20 // Si se produce un error al cargar el recurso,
21 // volcar en el log y lanzar excepcin.
22 }
23
24 // Clculo del tamao del recurso de sonido.
25 _size = ...
26 }
27
28 void
29 Track::unloadImpl()
30 {
31 if (_pTrack) {
32 // Liberar el recurso de sonido.
33 Mix_FreeMusic(_pTrack);
34 }
35 }
Ogre3D permite el uso de punteros inteligentes compartidos, denidos en la clase resource 2 references
Ogre::SharedPtr, con el objetivo de parametrizar el recurso denido por el desarro-
llador, por ejemplo Track, y almacenar, internamente, un contador de referencias a
dicho recurso. Bsicamente, cuando el recurso se copia, este contador se incrementa.
Si se destruye alguna referencia al recurso, entonces el contador se decrementa. Este
esquema permite liberar recursos cuando no se estn utilizando en un determinado ptr refs ptr refs
momento y compartir un mismo recurso entre varias entidades.
El listado de cdigo 6.17 muestra la implementacin de la clase TrackPtr, la cual Figura 6.16: El uso de smart poin-
incluye una serie de funciones (bsicamente constructores y asignador de copia) here- ters optimiza la gestin de recursos
dadas de Ogre::SharedPtr. A modo de ejemplo, tambin se incluye el cdigo asociado y facilita su liberacin.
al constructor de copia. Como se puede apreciar, en l se incrementa el contador de
referencias al recurso.
Una vez implementada la lgica necesaria para instanciar y manejar recursos de
sonido, el siguiente paso consiste en denir un gestor o manager especco para cen-
tralizar la administracin del nuevo tipo de recurso. Ogre3D facilita enormemente esta
tarea gracias a la clase Ogre::ResourceManager. En el caso particular de los recursos
de sonido se dene la clase TrackManager, cuyo esqueleto se muestra en el listado
de cdigo 6.18.
Esta clase no slo hereda del gestor de recursos de Ogre, sino que tambin lo hace
de la clase Ogre::Singleton con el objetivo de manejar una nica instancia del gestor
de recursos de sonido. Las funciones ms relevantes son las siguientes:
load() (lneas 10-11 ), que permite la carga de canciones por parte del desarro-
llador. Si el recurso a cargar no existe, entonces lo crear internamente utilizan-
do la funcin que se comenta a continuacin.
createImpl() (lneas 18-23 ), funcin que posibilita la creacin de un nuevo
recurso, es decir, una nueva instancia de la clase Track. El desarrollador es res-
ponsable de realizar la carga del recurso una vez que ha sido creado.
6.2. Gestin bsica de recursos [191]
C6
5 // y operador de asignacin.
6 TrackPtr(): Ogre::SharedPtr<Track>() {}
7 explicit TrackPtr(Track* m): Ogre::SharedPtr<Track>(m) {}
8 TrackPtr(const TrackPtr &m): Ogre::SharedPtr<Track>(m) {}
9 TrackPtr(const Ogre::ResourcePtr &r);
10 TrackPtr& operator= (const Ogre::ResourcePtr& r);
11 };
12
13 TrackPtr::TrackPtr
14 (const Ogre::ResourcePtr &resource):
15 Ogre::SharedPtr<Track>()
16 {
17 // Comprobar la validez del recurso.
18 if (resource.isNull())
19 return;
20
21 // Para garantizar la exclusin mutua...
22 OGRE_LOCK_MUTEX(*resource.OGRE_AUTO_MUTEX_NAME)
23 OGRE_COPY_AUTO_SHARED_MUTEX(resource.OGRE_AUTO_MUTEX_NAME)
24
25 pRep = static_cast<Track*>(resource.getPointer());
26 pUseCount = resource.useCountPointer();
27 useFreeMethod = resource.freeMethod();
28
29 // Incremento del contador de referencias.
30 if (pUseCount)
31 ++(*pUseCount);
32 }
Con las tres clases que se han discutido en esta seccin ya es posible realizar la
carga de recursos de sonido, delegando en la biblioteca SDL_mixer, junto con su ges-
tin y administracin bsicas. Este esquema encapsula la complejidad del tratamiento
del sonido, por lo que en cualquier momento se podra sustituir dicha biblioteca por
otra.
Ms adelante se muestra un ejemplo concreto en el que se hace uso de este tipo
de recursos sonoros. Sin embargo, antes de discutir este ejemplo de integracin se
plantear el soporte de efectos de sonido, los cuales se podrn mezclar con el tema o
track principal a la hora de desarrollar un juego. Como se plantear a continuacin, la
losofa de diseo es exactamente igual que la planteada en esta seccin.
Adems de llevar a cabo la reproduccin del algn tema musical durante la eje-
cucin de un juego, la incorporacin de efectos de sonido es esencial para alcanzar
un buen grado de inmersin y que, de esta forma, el jugador se sienta como parte del
Figura 6.17: La integracin de propio juego. Desde un punto de vista tcnico, este esquema implica que la biblioteca
efectos de sonido es esencial para de desarrollo permita la mezcla de sonidos. En el caso de SDL_mixer es posible llevar
dotar de realismo la parte sonora del a cabo dicha integracin.
videojuego y complementar la parte
grca.
[192] CAPTULO 6. GESTIN DE RECURSOS
C6
La diferencia ms sustancial con respecto al gestor de sonido reside en que el tipo de
recurso mantiene un identicador textual distinto y que, en el caso de los efectos de
sonido, se lleva a cabo una reserva explcita de 32 canales de audio. Para ello, se hace
uso de una funcin especca de la biblioteca SDL_mixer.
Para llevar a cabo la integracin de los aspectos bsicos previamente discutidos so-
bre la gestin de msica y efectos de sonido se ha tomado como base el cdigo fuente
de la sesin de iluminacin del mdulo 2, Programacin Grca. En este ejemplo se
haca uso de una clase MyFrameListener para llevar a cabo la gestin de los eventos
de teclado.
Por una parte, este ejemplo se ha extendido para incluir la reproduccin ininte-
rrumpida de un tema musical, es decir, un tema que se estar reproduciendo desde que
se inicia la aplicacin hasta que sta naliza su ejecucin. Por otra parte, la reproduc-
cin de efectos de sonido adicionales est vinculada a la generacin de ciertos eventos
de teclado. En concreto, cada vez que el usuario pulsa las teclas 1 2, las cuales
estn asociadas a dos esquemas diferentes de clculo del sombreado, la aplicacin re-
producir un efecto de sonido puntual. Este efecto de sonido se mezclar de manera
adecuada con el track principal.
El siguiente listado de cdigo muestra la nueva funcin miembro introducida en la
clase MyApp para realizar la carga de recursos asociada a la biblioteca SDL_mixer.
Ogre::
TrackManager
FrameListener
1
1
1 1 1 1
SoundFXManager MyApp MyFrameListener
Figura 6.18: Diagrama simplicado de clases de las principales entidades utilizadas para llevar a cabo la
integracin de msica y efectos de sonido.
5 if (SDL_Init(SDL_INIT_AUDIO) < 0)
6 return false;
7 // Llamar a SDL_Quit al terminar.
8 atexit(SDL_Quit);
9
10 // Inicializando SDL mixer...
11 if (Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT,
12 MIX_DEFAULT_CHANNELS, 4096) < 0)
13 return false;
14
15 // Llamar a Mix_CloseAudio al terminar.
16 atexit(Mix_CloseAudio);
17
18 return true;
19
20 }
Finalmente, slo hay que reproducir los eventos de sonido cuando as sea necesa-
rio. En este ejemplo, dichos eventos se reproducirn cuando se indique, por parte del
usuario, el esquema de clculo de sombreado mediante las teclas 1 2. Dicha ac-
tivacin se realiza, por simplicacin, en la propia clase FrameListener; en concreto,
en la funcin miembro frameStarted a la hora de capturar los eventos de teclado.
El esquema planteado para la gestin de sonido mantiene la losofa de delegar el
C6
tratamiento de eventos de teclado, al menos los relativos a la parte sonora, en la clase
principal (MyApp). Idealmente, si la gestin del bucle de juego se plantea en base a
un esquema basado en estados, la reproduccin de sonido estara condicionada por el
estado actual. Este planteamiento tambin es escalable a la hora de integrar nuevos
estados de juegos y sus eventos de sonido asociados. La activacin de dichos eventos
depender no slo del estado actual, sino tambin de la propia interaccin por parte
del usuario.
Figura 6.20: Generalmente, los sis- En el desarrollo multiplataforma, esta API especca proporciona el nivel de
temas de archivos mantienen es- abstraccin necesario para no depender del sistema de archivos y de la platafor-
tructuras de rbol o de grafo. ma subyacente.
[196] CAPTULO 6. GESTIN DE RECURSOS
/home/david/apps/firefox/firefox-bin
En el caso de las consolas, las rutas suelen seguir un convenio muy similar inclu- Encapsulacin
so para referirse a distintos volmenes. Por ejemplo, PlayStation3TM utiliza el prejo
/dev_bdvd para referirse al lector de blu-ray, mientras que el prejo /dev_hddx per- Una vez ms, el principio de encap-
sulacin resulta fundamental para
mite el acceso a un determinado disco duro. abstraerse de la complejidad asocia-
Las rutas o paths pueden ser absolutas o relativas, en funcin de si estn denidas da al tratamiento del sistema de ar-
chivos y del sistema operativo sub-
teniendo como referencia el directorio raz del sistema de archivos u otro directorio, yacente.
respectivamente. En sistemas UNIX, dos ejemplos tpicos seran los siguientes:
/usr/share/doc/ogre-doc/api/html/index.html
apps/firefox/firefox-bin
El primero de ellos sera una ruta absoluta, ya que se dene en base al directorio
raz. Por otra parte, la segunda sera una ruta relativa, ya que se dene en relacin al
directorio /home/david.
6.3. El sistema de archivos [197]
C6
bin boot dev etc home lib media usr var
5 www.boost.org/libs/lesystem/
[198] CAPTULO 6. GESTIN DE RECURSOS
El lenguaje de programacin C permite manejar dos APIs para gestionar las opera-
ciones ms relevantes en relacin al contenido de un archivo, como la apertura, lectura,
escritura o el cierre. La primera de ellas proporciona E/S con buffers mientras que la
segunda no.
La diferencia entre ambas reside en que la API con E/S mediante buffer gestiona de
manera automtica el uso de los propios buffers sin necesidad de que el programador
C6
asuma dicha responsabilidad. Por el contrario, la API de C que no proporciona E/S con
buffers tiene como consecuencia directa que el programador ha de asignar memoria
para dichos buffers y gestionarlos. La tabla 6.1 muestra las principales operaciones de
dichas APIs.
Finalmente, es muy importante tener en cuenta que las bibliotecas de E/S estndar
de C son sncronas. En otras palabras, ante una invocacin de E/S, el programa se
queda bloqueado hasta que se atiende por completo la peticin. El listado de cdigo
anterior muestra un ejemplo de llamada bloqueante mediante la operacin fread().
Evidentemente, el esquema sncrono presenta un inconveniente muy importante,
ya que bloquea al programa mientras la operacin de E/S se efecta, degradando as
el rendimiento global de la aplicacin. Un posible solucin a este problema consiste
en adoptar un enfoque asncrono, tal y como se discute en la siguiente seccin.
C6
Desplazar int fseek(FILE *stream, long offset, int whence);
Obtener offset long ftell(FILE *stream);
Lectura lnea char *fgets(char *s, int size, FILE *stream);
Escritura lnea int fputs(const char *s, FILE *stream);
Lectura cadena int fscanf(FILE *stream, const char *format, ...);
Escritura cadena int fprintf(FILE *stream, const char *format, ...);
Obtener estado int fstat(int fd, struct stat *buf);
Cuadro 6.1: Resumen de las principales operaciones de las APIs de C para la gestin de E/S (con y sin buffers).
La gura 6.24 muestra de manera grca cmo dos agentes se comunican de ma-
nera asncrona mediante un objeto de retrollamada. Bsicamente, el agente noticador
enva un mensaje al agente receptor de manera asncrona. Esta invocacin tiene dos
parmetros: i) el propio mensaje m y ii) un objeto de retrollamada cb. Dicho objeto
ser usado por el agente receptor cuando ste termine de procesar el mensaje. Mien-
tras tanto, el agente noticador continuar su ujo normal de ejecucin, sin necesidad
de bloquearse por el envo del mensaje.
Callbacks La mayora de las bibliotecas de E/S asncrona existentes permiten que el progra-
Las funciones de retrollamada se
ma principal espera una cierta cantidad de tiempo antes de que una operacin de E/S
suelen denominar comnmente se complete. Este planteamiento puede ser til en situaciones en las que los datos de
callbacks. Este concepto no slo dicha operacin se necesitan para continuar con el ujo normal de trabajo. Otras APIs
se usa en el mbito de la E/S asn- pueden proporcionar incluso una estimacin del tiempo que tardar en completarse
crona, sino tambin en el campo
de los sistemas distribuidos y los
una operacin de E/S, con el objetivo de que el programador cuente con la mayor
middlewares de comunicaciones. cantidad posible de informacin. As mismo, tambin es posible encontrarse con ope-
raciones que asignen deadlines sobre las propias peticiones, con el objetivo de plantear
un esquema basado en prioridades. Si el deadline se cumple, entonces tambin suele
ser posible asignar el cdigo a ejecutar para contemplar ese caso en particular.
[202] CAPTULO 6. GESTIN DE RECURSOS
Agente 1
: Agente n
Notificador Receptor
1
<<crear ()>> : Objeto
callback
recibir_mensaje (cb, m)
Procesar
mensaje
responder ()
responder ()
Figura 6.24: Esquema grco representativo de una comunicacin asncrona basada en objetos de retrolla-
mada.
C6
El desarrollo de esta biblioteca se justica por la necesidad de interaccin en-
tre programas, ya sea mediante cheros, redes o mediante la propia consola, que no
pueden quedarse a la espera ante operaciones de E/S cuyo tiempo de ejecucin sea
elevado. En el caso del desarrollo de videojuegos, una posible aplicacin de este en-
foque, tal y como se ha introducido anteriormente, sera la carga en segundo plano
de recursos que sern utilizados en el futuro inmediato. La situacin tpica en este
contexto sera la carga de los datos del siguiente nivel de un determinado juego.
Segn el propio desarrollador de la biblioteca7 , Asio proporciona las herramientas
necesarias para manejar este tipo de problemtica sin la necesidad de utilizar, por parte
del programador, modelos de concurrencia basados en el uso de hilos y mecanismos
de exclusin mutua. Aunque la biblioteca fue inicialmente concebida para la proble-
mtica asociada a las redes de comunicaciones, sta es perfectamente aplicable para
wait llevar a cabo una E/S asncrona asociada a descriptores de cheros o incluso a puertos
serie.
Los principales objetivos de diseo de Boost.Asio son los siguientes:
En determinados contextos resulta muy deseable llevar a cabo una espera asncro- In the meantime...
na, es decir, continuar ejecutando instrucciones mientras en otro nivel de ejecucin se
Recuerde que mientras se lleva a
realizan otras tareas. Si se utiliza la biblioteca Boost.Asio, es posible denir mane- cabo una espera asncrona es posi-
jadores de cdigo asociados a funciones de retrollamada que se ejecuten mientras el ble continuar ejecutando operacio-
programa contina la ejecucin de su ujo principal. Asio tambin proporciona meca- nes en el hilo principal. Aproveche
nismos para que el programa no termine mientras haya tareas por nalizar. este esquema para obtener un mejor
rendimiento en sistemas con ms de
El siguiente listado de cdigo muestra cmo el ejemplo anterior se puede modicar un procesador.
para llevar a cabo una espera asncrona, asociando en este caso un temporizador que
controla la ejecucin de una funcin de retrollamada.
25
26 return 0;
27 }
C6
$ Esperando a print()...
$ Hola Mundo!
Sin embargo, pasarn casi 3 segundos entre la impresin de una secuenciay otra,
ya que inmediatamente despus de la llamada a la
espera asncrona (lnea 15 ) se
ejecutar la primera secuencia de impresin (lnea
17 ), mientras que la ejecucin de
la funcin de retrollamada print() (lneas 6-8 ) se demorar 3 segundos.
Otra opcin imprescindible a la hora de gestionar estos manejadores reside en la
posibilidad de realizar un paso de parmetros, en funcin del dominio de la aplica-
cin que se est desarrollando. El siguiente listado de cdigo muestra cmo llevar a
cabo dicha tarea.
Como se puede apreciar, las llamadas a la funcin async_wait() varan con res-
pecto a otros ejemplos, ya que se indica de manera explcita los argumentos relevantes
para la funcin de retrollamada. En este caso, dichos argumentos son la propia funcin
de retrollamada, para realizar llamadas recursivas, el timer para poder prolongar en un
segundo su duracin en cada llamada, y una variable entera que se ir incrementando
en cada llamada.
Flexibilidad en Asio La biblioteca Boost.Asio tambin permite encapsular las funciones de retrollamada
que se ejecutan de manera asncrona como funciones miembro de una clase. Este
La biblioteca Boost.Asio es muy esquema mejora el diseo de la aplicacin y permite que el desarrollador se abstraiga
exible y posibilita la llamada asn-
crona a una funcin que acepta un de la implementacin interna de la clase. El siguiente listado de cdigo muestra una
nmero arbitrario de parmetros. posible modicacin del ejemplo anterior mediante la denicin de una clase Counter,
stos pueden ser variables, punte- de manera que la funcin count pasa a ser una funcin miembro de dicha clase.
ros, o funciones, entre otros.
[206] CAPTULO 6. GESTIN DE RECURSOS
20 _timer1.async_wait(_strand.wrap
21 (boost::bind(&Counter::count1, this)));
22 }
23 }
24
25 // IDEM que count1 pero sobre timer2 y count2
26 void count2() { /* src */ }
27
C6
28 private:
29 boost::asio::strand _strand;
30 boost::asio::deadline_timer _timer1, _timer2;
31 int _count;
32 };
33
34 int main() {
35 boost::asio::io_service io;
36 // run() se llamar desde dos threads (principal y boost)
37 Counter c(io);
38 boost::thread t(boost::bind(&boost::asio::io_service::run, &io));
39 io.run();
40 t.join();
41
42 return 0;
43 }
XML mantiene un alto nivel semntico, est bien soportado y estandarizado y permite
asociar estructuras de rbol bien denidas para encapsular la informacin relevante.
Adems, posibilita que el contenido a importar sea legible por los propios programa-
dores.
Es importante resaltar que este captulo est centrado en la parte de importacin
de datos hacia el motor de juegos, por lo que el proceso de exportacin no se discutir
a continuacin (si se har, no obstante, en el mdulo 2 de Programacin Grca).
Ogre Exporter
1. Exportacin, con el objetivo de obtener una representacin de los datos 3D.
2. Importacin, con el objetivo de acceder a dichos datos desde el motor de juegos
o desde el propio juego.
Importer
Archivos binarios
C6
En el caso de utilizar un formato no estandarizado, es necesario explicitar los
separadores o tags existentes entre los distintos campos del archivo en texto plano.
Por ejemplo, se podra pensar en separadores como los dos puntos, el punto y coma y
el retorno de carro para delimitar los campos de una determinada entidad y una entidad
de la siguiente, respectivamente.
XML
Figura 6.29: Visin conceptual de Sin embargo, no todo son ventajas. El proceso de parseado o parsing es rela-
la relacin de XML con otros len-
guajes.
tivamente lento. Esto implica que algunos motores hagan uso de formatos binarios
propietarios, los cuales son ms rpidos de parsear y mucho ms compactos que los
archivos XML, reduciendo as los tiempos de importacin y de carga.
El parser Xerces-C++
Como se ha comentado anteriormente, una de los motivos por los que XML est
tan extendido es su amplio soporte a nivel de programacin. En otras palabras, prc-
ticamente la mayora de lenguajes de programacin proporcionan bibliotecas, APIs y
herramientas para procesar y generar contenidos en formato XML.
En el caso del lenguaje C++, estndar de facto en la industria del videojuego, el
parser Xerces-C++11 es una de las alternativas ms completas para llevar a cabo el
procesamiento de cheros XML. En concreto, esta herramienta forma parte del pro-
Figura 6.30: Logo principal del yecto Apache XML12 , el cual gestiona un nmero relevante de subproyectos vincula-
proyecto Apache. dos al estndar XML.
11 http://xerces.apache.org/xerces-c/
12 http://xml.apache.org/
[210] CAPTULO 6. GESTIN DE RECURSOS
C6
S
Figura 6.32: Representacin interna bidimensional en forma de grafo del escenario de NoEscapeDemo.
Los nodos de tipo S (spawn) representan puntos de nacimiento, mientras que los nodos de tipo D (drain)
representan sumideros, es decir, puntos en los que los fantasmas desaparecen.
El nodo raz En principio, la informacin del escenario y de las cmaras virtuales ser impor-
La etiqueta <data> es el nodo raz tada al juego, haciendo uso del importador cuyo diseo se discute a continuacin. Sin
del documento XML. Sus posibles embargo, antes se muestra un posible ejemplo de la representacin de estos mediante
nodos hijo estn asociados con las el formato denido para la demo en cuestin.
etiquetas <graph> y <camera>.
En concreto, el siguiente listado muestra la parte de informacin asociada a la
estructura de grafo que conforma el escenario. Como se puede apreciar, la estructura
graph est compuesta por una serie de vrtices (vertex) y de arcos (edge).
Por una parte, en los vrtices del grafo se incluye su posicin en el espacio 3D
mediante las etiquetas <x>, <y> y <z>. Adems, cada vrtice tiene como atributo
un ndice, que lo identica unvocamente, y un tipo, el cual puede ser spawn (punto
de generacin) o drain (punto de desaparicin), respectivamente. Por otra parte, los
arcos permiten denir la estructura concreta del grafo a importar mediante la etiqueta
<vertex>.
Vrtices y arcos En caso de que sea necesario incluir ms contenido, simplemente habr que ex-
tender el formato denido. XML facilita enormemente la escalabilidad gracias a su
En el formato denido, los vrtices
se identican a travs del atributo esquema basado en el uso de etiquetas y a su estructura jerrquica.
index. Estos IDs se utilizan en la eti-
queta <edge> para especicar los
dos vrtices que conforman un ar-
co.
[212] CAPTULO 6. GESTIN DE RECURSOS
Listado 6.30: Ejemplo de chero XML usado para exportar e importar contenido asociado al
grafo del escenario.
1 <?xml version=1.0 encoding=UTF-8?>
2 <data>
3
4 <graph>
5
6 <vertex index="1" type="spawn">
7 <x>1.5</x> <y>2.5</y> <z>-3</z>
8 </vertex>
9 <!-- More vertexes... -->
10 <vertex index="4" type="drain">
11 <x>1.5</x> <y>2.5</y> <z>-3</z>
12 </vertex>
13
14 <edge>
15 <vertex>1</vertex> <vertex>2</vertex>
16 </edge>
17 <edge>
18 <vertex>2</vertex> <vertex>4</vertex>
19 </edge>
20 <!-- More edges... -->
21
22 </graph>
23
24 <!-- Definition of virtual cameras -->
25 </data>
Lgica de dominio
1 #include <vector>
2 #include <Camera.h>
6.4. Importador de datos de intercambio [213]
Listado 6.31: Ejemplo de chero XML usado para exportar e importar contenido asociado a
las cmaras virtuales.
1 <?xml version=1.0 encoding=UTF-8?>
2 <data>
3 <!-- Graph definition here-->
C6
4
5 <camera index="1" fps="25">
6
7 <path>
8
9 <frame index="1">
10 <position>
11 <x>1.5</x> <y>2.5</y> <z>-3</z>
12 </position>
13 <rotation>
14 <x>0.17</x> <y>0.33</y> <z>0.33</z> <w>0.92</w>
15 </rotation>
16 </frame>
17
18 <frame index="2">
19 <position>
20 <x>2.5</x> <y>2.5</y> <z>-3</z>
21 </position>
22 <rotation>
23 <x>0.17</x> <y>0.33</y> <z>0.33</z> <w>0.92</w>
24 </rotation>
25 </frame>
26
27 <!-- More frames here... -->
28
29 </path>
30
31 </camera>
32
33 <!-- More cameras here... -->
34 </data>
3 #include <Node.h>
4 #include <Graph.h>
5
6 class Scene
7 {
8 public:
9 Scene ();
10 ~Scene ();
11
12 void addCamera (Camera* camera);
13 Graph* getGraph () { return _graph;}
14 std::vector<Camera*> getCameras () { return _cameras; }
15
16 private:
17 Graph *_graph;
18 std::vector<Camera*> _cameras;
19 };
Por otra parte, la clase Graph mantiene la lgica de gestin bsica para imple-
mentar una estructura de tipo grafo mediante listas de adyacencia. El siguiente listado
de cdigo muestra dicha clase e integra una funcin miembro para obtener la lista
de
vrtices o nodos adyacentes a partir del identicador de uno de ellos (lnea 17 ).
[214] CAPTULO 6. GESTIN DE RECURSOS
1 1
Scene Importer Ogre::Singleton
1
1
1..* 1
1 1..*
Camera Graph GraphVertex
1 2 1
1
Figura 6.33: Diagrama de clases de las entidades ms relevantes del importador de datos.
26 std::vector<GraphEdge*> _edges;
27 };
C6
2 class Node
3 {
4 public:
5 Node ();
6 Node (const int& index, const string& type,
7 const Ogre::Vector3& pos);
8 ~Node ();
9
10 int getIndex () const { return _index; }
11 string getType () const { return _type; }
12 Ogre::Vector3 getPosition () const { return _position; }
13
14 private:
15 int _index; // ndice del nodo (id nico)
16 string _type; // Tipo: generador (spawn), sumidero
(drain)
17 Ogre::Vector3 _position; // Posicin del nodo en el espacio 3D
18 };
GraphVertex y Node Respecto al diseo de las cmaras virtuales, las clases Camera y Frame son las
utilizadas para encapsular la informacin y funcionalidad de las mismas. En esencia,
La clase GraphVertex mantiene co- una cmara consiste en un identicador, un atributo que determina la tasa de frames
mo variable de clase un objeto de ti-
po Node, el cual alberga la informa- por segundo a la que se mueve y una secuencia de puntos clave que conforman el
cin de un nodo en el espacio 3D. camino asociado a la cmara.
La clase Importer
API DOM y memoria El punto de interaccin entre los datos contenidos en el documento XML y la
lgica de dominio previamente discutida est representado por la clase Importer. Esta
El rbol generado por el API DOM clase proporciona la funcionalidad necesaria para parsear documentos XML con la
a la hora de parsear un documento
XML puede ser costoso en memo-
estructura planteada anteriormente y rellenar las estructuras de datos diseadas en la
ria. En ese caso, se podra plantear anterior seccin.
otras opciones, como por ejemplo el
uso del API SAX. El siguiente listado de cdigo muestra la declaracin de la clase Importer. Como se
puede apreciar, dicha clase hereda de Ogre::Singleton para garantizar que solamente
existe una instancia de dicha clase, accesible con las funciones miembro getSingleton()
y getSingletonPtr()13 .
Note cmo, adems de estas dos funciones miembro, la nica funcin miembro
pblica es parseScene() (lnea 9 ), la cual se puede utilizar para parsear un docu-
mento XML cuya ruta se especica en el primer parmetro. El efecto de realizar una
llamada a esta funcin tendr como resultado el segundo parmetro de la misma, de ti-
po puntero a objeto de clase Scene, con la informacin obtenida a partir del documento
XML (siempre y cuando no se produzca ningn error).
6 public:
7 // nica funcin miembro pblica para parsear.
8 void parseScene (const char* path, Scene *scn);
9
10 static Importer& getSingleton (); // Ogre::Singleton.
11 static Importer* getSingletonPtr (); // Ogre::Singleton.
12
13 private:
14 // Funcionalidad oculta al exterior.
15 // Facilita el parseo de las diversas estructuras
16 // del documento XML.
17 void parseCamera (xercesc::DOMNode* cameraNode, Scene* scn);
18
19 void addPathToCamera (xercesc::DOMNode* pathNode, Camera *cam);
20 void getFramePosition (xercesc::DOMNode* node,
21 Ogre::Vector3* position);
22 void getFrameRotation (xercesc::DOMNode* node,
23 Ogre::Vector4* rotation);
24
25 void parseGraph (xercesc::DOMNode* graphNode, Scene* scn);
26 void addVertexToScene (xercesc::DOMNode* vertexNode, Scene* scn);
27 void addEdgeToScene (xercesc::DOMNode* edgeNode, Scene* scn);
28
29 // Funcin auxiliar para recuperar valores en punto flotante
30 // asociados a una determinada etiqueta (tag).
31 float getValueFromTag (xercesc::DOMNode* node, const XMLCh *tag);
32 };
C6
20 for (XMLSize_t i = 0;
21 i < elementRoot->getChildNodes()->getLength(); ++i ) {
22 DOMNode* node = elementRoot->getChildNodes()->item(i);
23
24 if (node->getNodeType() == DOMNode::ELEMENT_NODE) {
25 // Nodo <camera>?
26 if (XMLString::equals(node->getNodeName(), camera_ch))
27 parseCamera(node, scene);
28 else
29 // Nodo <graph>?
30 if (XMLString::equals(node->getNodeName(), graph_ch))
31 parseGraph(node, scene);
32 }
33 }// Fin for
34 // Liberar recursos.
35 }
Bajo Nivel y Concurrencia
Captulo 7
David Vallejo Fernndez
E
ste captulo realiza un recorrido por los sistemas de soporte de bajo nivel ne-
cesarios para efectuar diversas tareas crticas para cualquier motor de juegos.
Algunos ejemplos representativos de dichos subsistemas estn vinculados al
arranque y la parada del motor, su conguracin y a la gestin de cuestiones de ms
bajo nivel.
El objetivo principal del presente captulo consiste en profundizar en dichas tareas
y en proporcionar al lector una visin ms detallada de los subsistemas bsicos sobre
los que se apoyan el resto de elementos del motor de juegos.
From the ground up! En concreto, en este captulo se discutirn los siguientes subsistemas:
Los subsistemas de bajo nivel del Subsistema de arranque y parada.
motor de juegos resultan esenciales
para la adecuada integracin de ele- Subsistema de gestin de contenedores.
mentos de ms alto nivel. Algunos
de ellos son simples, pero la funcio- Subsistema de gestin de cadenas.
nalidad que proporcionan es crtica
para el correcto funcionamiento de
otros subsistemas. El subsistema de gestin relativo a la gestin de contenedores de datos se discuti
en el captulo 5. En este captulo se llev a cabo un estudio de la biblioteca STL y
se plantearon unos criterios bsicos para la utilizacin de contenedores de datos en
el mbito del desarrollo de videojuegos con C++. Sin embargo, este captulo dedica
una breve seccin a algunos aspectos relacionados con los contenedores que no se
discutieron anteriormente.
Por otra parte, el subsistema de gestin de memoria se estudiar en el mdulo 3,
titulado Tcnicas Avanzadas de Desarrollo, del presente curso de desarrollo de video-
juegos. Respecto a esta cuestin particular, se har especial hincapi en las tcnicas y
las posibilidades que ofrece C++ en relacin a la adecuada gestin de la memoria del
sistema.
219
[220] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA
Con el objetivo de abordar esta problemtica desde un punto de vista prctico en El arranque y la parada de subsiste-
mas representa una tarea bsica y, al
el mbito del desarrollo de videojuegos, en este captulo se plantean distintos meca- mismo tiempo, esencial para la co-
nismos de sincronizacin de hilos haciendo uso de los mecanismos nativos ofrecidos rrecta gestin de los distintos com-
en C++11 y, por otra parte, de la biblioteca de hilos de ZeroC I CE, un middleware de ponentes de la arquitectura de un
comunicaciones que proporciona una biblioteca de hilos que abstrae al desarrollador motor de juegos.
de la plataforma y el sistema operativo subyacentes. Subsistema S
Sistema de Manejo de
Motor de rendering, motor de fsica,
arranque y parada cadenas
herramientas de desarrollo, networking,
Gestin de Gestin de audio, subsistema de juego...
memoria archivos
Gestor de recursos
Biblioteca
C7
...
matemtica
Subsistemas principales
SDKs y middlewares
Figura 7.2: Interaccin del subsistema de arranque y parada con el resto de subsistemas de la arquitectura
general del motor de juegos [42].
No obstante, aunque existe cierto control sobre el arranque de los subsistemas de-
pendientes de uno en concreto, esta alternativa no proporciona control sobre la parada
de los mismos. As, es posible que C++ destruya uno de los subsistemas dependientes
del gestor de memoria de manera previa a la destruccin de dicho subsistema.
Adems, este enfoque presenta el inconveniente asociado al momento de la cons-
truccin de la instancia global del subsistema de memoria. Evidentemente, la construc-
cin se efectuar a partir de la primera llamada a la funcin get, pero es complicado
controlar cundo se efectuar dicha llamada.
C7
Por otra parte, no es correcto realizar suposiciones sobre la complejidad vinculada
a la obtencin del singleton, ya que los distintos subsistemas tendrn necesidades muy
distintas entre s a la hora de inicializar su estado, considerando las interdependencias
con otros subsistemas. En general, este diseo puede ser problemtico y es necesa-
rio proporcionar un esquema sobre el que se pueda ejercer un mayor control y que
simplique los procesos de arranque y parada.
Ogre::Singleton<Root> RootAlloc
C7
Ogre::Root
Figura 7.6: Clase Ogre::Root y su relacin con las clases Ogre::Singleton y RootAlloc.
C7
En esta seccin se estudiar desde un punto de vista general el sistema de arranque
y de parada de Quake III. Al igual que ocurre en el caso de Ogre, el esquema planteado
consiste en hacer uso de funciones especcas para el arranque, inicializacin y parada
de las distintas entidades o subsistemas involucrados.
El siguiente listado de cdigo muestra el punto de entrada de Quake III para sis-
temas WindowsTM . Como se puede apreciar, la funcin principal consiste en una serie
de casos que determinan el ujo de control del juego. Es importante destacar los ca-
sos GAME_INIT y GAME_SHUTDOWN que, como su nombre indica, estn vin-
culados al arranque y la parada del juego mediante las funciones G_InitGame() y
G_ShutdownGame(), respectivamente.
20 de Agosto
Listado 7.8: Quake 3. Funcin vmMain().
La fecha de liberacin del cdigo de
Quake III no es casual. En realidad, 1 int vmMain( int command, int arg0, int arg1, ..., int arg11 ) {
coincide con el cumpleaos de John 2 switch ( command ) {
Carmack, nacido el 20 de Agosto de 3 // Se omiten algunos casos.
1970. Actualmente, John Carmack 4 case GAME_INIT:
es una de las guras ms reconoci- 5 G_InitGame( arg0, arg1, arg2 );
das dentro del mbito del desarrollo 6 return 0;
de videojuegos. 7 case GAME_SHUTDOWN:
8 G_ShutdownGame( arg0 );
9 return 0;
10 case GAME_CLIENT_CONNECT:
11 return (int)ClientConnect( arg0, arg1, arg2 );
12 case GAME_CLIENT_DISCONNECT:
13 ClientDisconnect( arg0 );
14 return 0;
15 case GAME_CLIENT_BEGIN:
16 ClientBegin( arg0 );
17 return 0;
18 case GAME_RUN_FRAME:
19 G_RunFrame( arg0 );
20 return 0;
21 case BOTAI_START_FRAME:
22 return BotAIStartFrame( arg0 );
23 }
24
25 return -1;
26 }
1 https://github.com/id-Software/Quake-III-Arena
[228] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA
13 BotAIShutdown( restart );
14 }
15 }
C7
la entidad afectada.
Programacin estructura A su vez, la funcin BotAIShutdown() delega en otra funcin que es la encargada,
nalmente, de liberar la memoria y los recursos previamente reservados para el caso
El cdigo de Quake es un buen
ejemplo de programacin estructu- particular de los jugadores que participan en el juego, utilizando para ello la funcin
rada que gira en torno a una adecua- BotAIShutdownClient().
da denicin de funciones y a un
equilibrio respecto a la complejidad
de las mismas. Listado 7.11: Quake 3. Funciones BotAIShutdown y BotAIShutdownClient.
1 int BotAIShutdown( int restart ) {
2 // Si el juego se resetea para torneo...
3 if ( restart ) {
4 // Parar todos los bots en botlib.
5 for (i = 0; i < MAX_CLIENTS; i++)
6 if (botstates[i] && botstates[i]->inuse)
7 BotAIShutdownClient(botstates[i]->client, restart);
8 }
9 // ...
10 }
11
12 int BotAIShutdownClient(int client, qboolean restart) {
13 // Se omite parte del cdigo.
14 // Liberar armas...
15 trap_BotFreeWeaponState(bs->ws);
16 // Liberar el bot...
17 trap_BotFreeCharacter(bs->character);
18 // ..
19 // Liberar el estado del bot...
20 memset(bs, 0, sizeof(bot_state_t));
21 // Un bot menos...
22 numbots--;
23 // ...
24 // Todo OK.
25 return qtrue;
26 }
7.2. Contenedores
Como ya se introdujo en el captulo 5, los contenedores son simplemente objetos
que contienen otros objetos. En el mundo del desarrollo de videojuegos, y en el de
las aplicaciones software en general, los contenedores se utilizan extensivamente para
almacenar las estructuras de datos que conforman la base del diseo que soluciona un
determinado problema.
Algunos de los contenedores ms conocidos ya se comentaron en las seccio-
nes 5.3, 5.4 y 5.5. En dichas secciones se hizo un especial hincapi en relacin a la
utilizacin de estos contenedores, atendiendo a sus principales caracteristicas, como
la denicin subyacente en la biblioteca STL.
[230] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA
Acceso
Bidireccional Unidireccional Entrada
directo
Salida
Figura 7.9: Esquema grca de las relaciones entre las distintas categoras de iteradores en STL. Cada
categora implementa la funcionalidad de todas las categoras que se encuentran a su derecha.
7.2.1. Iteradores
Desde un punto de vista abstracto, un iterador se puede denir como una clase Patrn Iterator
que permite acceder de manera eciente a los elementos de un contenedor especco.
Normalmente, los iteradores se im-
Segn [94], un iterador es una abstraccin pura, es decir, cualquier elemento que se plementan haciendo uso del pa-
comporte como un iterador se dene como un iterador. En otras palabras, un itera- trn que lleva su mismo nombre,
dor es una abstraccin del concepto de puntero a un elemento de una secuencia. Los tal y como se discuti en la sec-
principales elementos clave de un iterador son los siguientes: cin 4.4.3.
De este modo, un elemento primitivo int* se puede denir como un iterador sobre
un array de enteros, es decir, sobre int[]. As mismo, std::list<std::string>::iterator Rasgos de iterador
es un iterador sobre la clase list. En STL, los tipos relacionados con
Un iterador representa la abstraccin de un puntero dentro de un array, de manera un iterador se describen a partir
de una serie de declaraciones en
que no existe el concepto de iterador nulo. Como ya se introdujo anteriormente, la la plantilla de clase iterator_traits.
condicin para determinar si un iterador apunta o no a un determinado elemento se Por ejemplo, es posible acceder al
evala mediante una comparacin al elemento nal de una secuencia (end). tipo de elemento manejado o el tipo
de las operaciones soportadas.
Las principales ventajas de utilizar un iterador respecto a intentar el acceso sobre
un elemento de un contenedor son las siguientes [42]:
C7
Cuadro 7.1: Resumen de las principales categoras de iteradores en STL junto con sus operaciones [94].
Los iteradores tambin se pueden aplicar sobre ujos de E/S gracias a que la
biblioteca estndar proporciona cuatro tipos de iteradores enmarcados en el esquema
general de contenedores y algoritmos:
Listado 7.12: Ejemplo de uso de iteradores para llevar a cabo operaciones de E/S.
1 #include <iostream>
2 #include <iterator>
3
4 using namespace std;
5
6 int main () {
7 // Escritura de enteros en cout.
8 ostream_iterator<int> fs(cout);
9
10 *fs = 7; // Escribe 7 (usa cout).
11 ++fs; // Preparado para siguiente salida.
12 *fs = 6; // Escribe 6.
13
14 return 0;
15 }
De manera independiente al hecho de considerar la opcin de usar STL, existen Estandarizando Boost
otras bibliotecas relacionadas que pueden resultar tiles para el desarrollador de vi-
deojuegos. Una de ellas es STLPort, una implementacin de STL especcamente Gran parte de las bibliotecas del
proyecto Boost estn en proceso de
ideada para ser portable a un amplio rango de compiladores y plataformas. Esta im- estandarizacin con el objetivo de
plementacin proporciona una funcionalidad ms rica que la que se puede encontrar incluirse en el propio estndar de
en otras implementaciones de STL. C++.
7
A B A C
1 2 B A D
2 12 4
C7
E
C D
3
3
D B E
C D
2
E A B C
4
Figura 7.10: Representacin grca del grafo de ejemplo utilizado para el clculo de caminos mnimos. La
parte derecha muestra la implementacin mediante listas de adyacencia planteada en la biblioteca Boost.
La gura 7.10 muestra un grafo dirigido que servir como base para la discusin
del siguiente fragmento de cdigo, en el cual se hace uso de la biblioteca Boost para
llevar a cabo el clculo de los caminos mnimos desde un vrtice al resto mediante el
algoritmo de Dijkstra.
Edsger W. Dijkstra Recuerde que un grafo es un conjunto no vaco de nodos o vrtices y un conjunto
de pares de vrtices o aristas que unen dichos nodos. Un grafo puede ser dirigido o
Una de las personas ms relevan-
tes en el mbito de la computacin no dirigido, en funcin de si las aristas son unidireccionales o bidireccionales, y es
es Dijkstra. Entre sus aportaciones, posible asignar pesos a las aristas, conformando as un grafo valorado.
destacan la solucin al camino ms
corto, la notacin polaca inversa, el 3 http://www.stlport.org
algoritmo del banquero, la deni- 4 http://www.boost.org
cin de semforo como mecanismo
de sincronizacin e incluso aspec-
tos de computacin distribuida, en-
tre otras muchas contribuciones.
[234] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA
dijkstra-example.cpp
7.3. Subsistema de gestin de cadenas [235]
C7
El ltimo bloque de cdigo (lneas 44-52 ) permite obtener la informacin rele-
vante a partir del clculo de los caminos mnimos desde A, es decir, la distancia que
existe desde el propio nodo A hasta cualquier otro y el nodo a partir del cual se accede
al destino nal (partiendo de A).
Para generar un ejecutable, simplemente es necesario enlazar con la biblioteca
boost_graph:
Al ejecutar, la salida del programa mostrar todos los caminos mnimos desde el
vrtice especicado; en este caso, desde el vrtice A.
cad
Distancias y nodos padre:
'H' Distancia(A) = 0, Padre(A) = A
Distancia(B) = 9, Padre(B) = E
p Distancia(C) = 2, Padre(C) = A
'o' Distancia(D) = 4, Padre(D) = C
Distancia(E) = 7, Padre(E) = D
'l'
7.3. Subsistema de gestin de cadenas
'a'
Al igual que ocurre con otros elementos transversales a la arquitectura de un motor
de juegos, como por ejemplos los contenedores de datos (ver captulo 5), las cadenas
'm' de texto se utilizan extensivamente en cualquier proyecto software vinculado al desa-
rrollo de videojuegos. Aunque en un principio pueda parecer que la utilizacin y la
gestin de cadenas de texto es una cuestin trivial, en realidad existe una gran varie-
dad de tcnicas y restricciones vinculadas a este tipo de datos. Su consideracin puede
'u'
ser muy relevante a la hora de mejorar el rendimiento de la aplicacin.
C7
Modicadores, que proporcionan la funcionalidad necesaria para alterar el con-
tenido de la cadena de texto. Un ejemplo es la funcin swap(), que intercambia
el contenido de la cadena por otra especicada como parmetro.
Operadores de cadena, que dene operaciones auxiliares para tratar con cade-
nas. Un ejemplo es la funcin compare(), que permite comparar cadenas.
Como regla general, pase los objetos de tipo string como referencia y no
como valor, ya que esto incurrira en una penalizacin debido al uso de cons-
tructores de copia.
7 Se recomienda la visualizacin del curso Introduction to Algorithms del MIT, en concreto las clases 7
y 8, disponible en la web.
7.4. Conguracin del motor [239]
5
6 void funcion (StringId id) {
7 // Ms eficiente que...
8 // if (id == internString ("hola"))
9 if (id == sid_hola)
10 // ...
11 else if (id == sid_mundo)
12 // ...
13 }
C7
7.4. Conguracin del motor
Debido a la complejidad inherente a un motor de juegos, existe una gran variedad
de variables de conguracin que se utilizan para especicar el comportamiento de
determinadas partes o ajustar cuestiones especcas. Desde un punto de vista general,
en la conguracin del motor se pueden distinguir dos grandes bloques:
Tricks
En muchos juegos es bastante co- 7.4.1. Esquemas tpicos de conguracin
mn encontrar trucos que permi-
ten modicar signicativamente al-
gunos aspectos internos del juego, Las variables de conguracin se pueden denir de manera trivial mediante el
como por ejemplo la capacidad de uso de variables globales o variables miembro de una clase que implemente el patrn
desplazamiento del personaje prin- singleton. Sin embargo, idealmente debera ser posible modicar dichas variables de
cipal. Tradicionalmente, la activa- conguracin sin necesidad de modicar el cdigo fuente y, por lo tanto, volver a
cin de trucos se ha realizado me-
diante combinaciones de teclas. compilar para generar un ejecutable.
Normalmente, las variables o parmetros de conguracin residen en algn tipo de
dispositivo de almacenamiento externo, como por ejemplo un disco duro o una tarjeta
de memoria. De este modo, es posible almacenar y recuperar la informacin asociada
a la conguracin de una manera prctica y directa. A continuacin se discute breve-
mente los distintas aproximaciones ms utilizadas en el desarrollo de videojuegos.
En primer lugar, uno de los esquems tpicos de recuperacin y almacenamiento de
informacin est representado por los cheros de conguracin. La principal ventaja
de este enfoque es que son perfectamente legibles ya que suelen especicar de manera
explcita la variable de conguracin a modicar y el valor asociada a la misma. En
general, cada motor de juegos mantiene su propio convenio aunque, normalmente,
todos se basan en una secuencia de pares clave-valor. Por ejemplo, Ogre3D utiliza la
siguiente nomenclatura:
Dentro del mbito de los cheros de conguracin, los archivos XML representan
otra posibilidad para establecer los parmetros de un motor de juegos. En este caso, es
necesario llevar a cabo un proceso de parsing para obtener la informacin asociada a
dichos parmetros.
Tradicionalmente, las plataformas de juego que sufran restricciones de memoria, El lenguaje XML
debido a limitaciones en su capacidad, hacan uso de un formato binario para alma-
cenar la informacin asociada a la conguracin. Tpicamente, dicha informacin se eXtensible Markup Language es un
metalenguaje extensible basado en
almacenaba en tarjetas de memoria externas que permitan salvar las partidas guarda- etiquetas que permite la denicin
das y la conguracin proporcionada por el usuario de un determinado juego. de lenguajes especcos de un de-
terminado dominio. Debido a su po-
Este planteamiento tiene como principal ventaja la eciencia en el almacenamien- pularidad, existen multitud de he-
to de informacin. Actualmente, y debido en parte a la convergencia de la consola de rramientas que permiten el trata-
sobremesa a estaciones de juegos con servicios ms generales, la limitacin de alma- miento y la generacin de archivos
bajo este formato.
cenamiento en memoria permanente no es un problema, normalmente. De hecho, la
mayora de consolas de nueva generacin vienen con discos duros integrados.
Los propios registros del sistema operativo tambin se pueden utilizar como so-
porte al almacenamiento de parmetros de conguracin. Por ejemplo, los sistemas
operativos de la familia de Microsoft WindowsTM proporcionan una base de datos glo-
bal implementada mediante una estructura de rbol. Los nodos internos de dicha es-
tructura actan como directorios mientras que los nodos hoja representan pares clave-
valor.
Tambin es posible utilizar la lnea de rdenes o incluso la denicin de variables
de entorno para llevar a cabo la conguracin de un motor de juegos.
Finalmente, es importante reexionar sobre el futuro de los esquemas de almace-
namiento. Actualmente, es bastante comn encontrar servicios que permitan el alma-
cenamiento de partidas e incluso de preferencias del usuario en la red, haciendo uso de
servidores en Internet normalmente gestionados por la propia compaa de juegos.
Este tipo de aproximaciones tienen la ventaja de la redundancia de datos, sacricando
el control de los datos por parte del usuario.
C7
7 (fade-out-seconds float :default 0.25)
8 )
9 )
10
11 ;; Instancia especfica para andar.
12 (define-export anim-walk
13 (new simple-animation
14 :name "walk"
15 :speed 1.0
16 )
17 )
18 ;; Instancia especfica para andar rpido.
19 (define-export anim-walk-fast
20 (new simple-animation
21 :name "walk-fast"
22 :speed 2.0
23 )
24 )
C7
hilo
(a) (b)
Figura 7.12: Esquema grco de los modelos de programacin monohilo (a) y multihilo (b).
SECCIN_ENTRADA
SECCIN_CRTICA
SECCIN_SALIDA
SECCIN_RESTANTE
Esqueleto
C7
Proxy API ICE API ICE Adaptador de objetos
Red de
comunicaciones ICE runtime (servidor)
ICE runtime (cliente)
Figura 7.14: Arquitectura general de una aplicacin distribuida desarrollada con el middleware ZeroC I CE.
id(), que devuelve el identicador nico asociado al hilo. Este valor depender
del soporte de hilos nativo (por ejemplo, POSIX pthreads).
isAlive(), que permite conocer si un hilo est en ejecucin, es decir, si ya se
llam a start() y la funcin run() no termin de ejecutarse.
La sobrecarga de los operadores de comparacin permiten hacer uso de hilos
en contenedores de STL que mantienen relaciones de orden.
C7
Sincronizacin Cuando un lsofo piensa, entonces se abstrae del mundo y no se relaciona con
El problema de los lsofos comen- ningn otro lsofo. Cuando tiene hambre, entonces intenta coger a los palillos que
sales es uno de los problemas clsi- tiene a su izquierda y a su derecha (necesita ambos). Naturalmente, un lsofo no
cos de sincronizacin y existen mu- puede quitarle un palillo a otro lsofo y slo puede comer cuando ha cogido los dos
chas modicaciones del mismo que palillos. Cuando un lsofo termina de comer, deja los palillos y se pone a pensar.
permiten asimilar mejor los con-
ceptos tericos de la programacin La solucin que se discutir en esta seccin se basa en implementar el lsofo
concurrente. como un hilo independiente. Para ello, se crea la clase FilosofoThread que se expone
en el anterior listado de cdigo.
La implementacin de la funcin run() es trivial a partir de la descripcin del
enunciado del problema.
I CE proporciona la clase Mutex para modelar esta problemtica de una forma sen-
cilla y directa.
Las funciones miembro ms importantes de esta clase son las que permiten adqui-
rir y liberar el cerrojo:
lock(), que intenta adquirir el cerrojo. Si ste ya estaba cerrado, entonces el hilo
que invoc a la funcin se suspende hasta que el cerrojo quede libre. La llamada
a dicha funcin retorna cuando el hilo ha adquirido el cerrojo.
tryLock(), que intenta adquirir el cerrojo. A diferencia de lock(), si el cerrojo
est cerrado, la funcin devuelve false. En caso contrario, devuelve true con el
cerrojo cerrado.
unlock(), que libera el cerrojo.
C7
sincronizacin que mantiene una semntica recursiva.
La clase Mutex se puede utilizar para gestionar el acceso concurrente a los palillos.
En la solucin planteada a continuacin, un palillo es simplemente una especializacin
de IceUtil::Mutex con el objetivo de incrementar la semntica de dicha solucin.
Riesgo de interbloqueo
Listado 7.22: La clase Palillo
Si todos los lsofos cogen al mis-
mo tiempo el palillo que est a su 1 #ifndef __PALILLO__
izquierda se producir un interblo- 2 #define __PALILLO__
queo ya que la solucin planteada 3
no podra avanzar hasta que un l- 4 #include <IceUtil/Mutex.h>
sofo coja ambos palillos. 5
6 class Palillo : public IceUtil::Mutex {
7 };
8
9 #endif
Para que los lsofos puedan utilizar los palillos, habr que utilizar la funcionali-
dad previamente discutida, es decir, las funciones lock() y unlock(), en las funciones
coger_palillos() y dejar_palillos(). La solucin planteada garantiza que no se ejecuta-
rn dos llamadas a lock() sobre un palillo por parte de un mismo hilo, ni tampoco una
llamada sobre unlock() si previamente no se adquiri el palillo.
La solucin planteada es poco exible debido a que los lsofos estn inactivos
durante el periodo de tiempo que pasa desde que dejan de pensar hasta que cogen
los dos palillos. Una posible variacin a la solucin planteada hubiera sido continuar
pensando (al menos hasta un nmero mximo de ocasiones) si los palillos estn ocu-
Gestin del deadlock pados. En esta variacin se podra utilizar la funcin tryLock() para modelar dicha
Aunque existen diversos esquemas problemtica.
para evitar y recuperarse de un in-
terbloqueo, los sistemas operativos
modernos suelen optar por asumir
que nunca se producirn, delegan-
do en el programador la implemen-
tacin de soluciones seguras.
[250] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA
Evitando interbloqueos
El uso de las funciones lock() y unlock() puede generar problemas importantes si,
por alguna situacin no controlada, un cerrojo previamente adquirido con lock() no
se libera posteriormente con una llamada a unlock(). El siguiente listado de cdigo
muestra esta problemtica.
En la lnea 8 , la sentencia return implica abandonar
mi_funcion() sin haber li-
berado el cerrojo previamente adquirido en la lnea 6 . En este contexto, es bastante
probable que se genere una potencial situacin de interbloqueo que paralizara la eje-
cucin del programa. La generacin de excepciones no controladas representa otro
caso representativo de esta problemtica.
Para evitar este tipo de problemas, la clase Mutex proporciona las deniciones
de tipo Lock y TryLock, que representan plantillas muy sencillas compuestas de un
constructor en el que se llama a lock() y tryLock(), respectivamente. En el destructor se
llama a unlock() si el cerrojo fue previamente adquirido cuando la plantilla quede fuera
de mbito. En el ejemplo anterior, sera posible garantizar la liberacin del cerrojo al
ejecutar return, ya que quedara fuera del alcance de la funcin. El siguiente listado
de cdigo muestra la modicacin realizada para evitar interbloqueos.
C7
No olvides... Adems de proporcionar cerrojos con una semntica no recursiva, I CE tambin
proporciona la clase IceUtil::RecMutex con el objetivo de que el desarrollador pueda
Liberar un cerrojo slo si fue pre-
viamente adquirido y llamar a un- manejar cerrojos recursivos. La interfaz de esta nueva clase es exactamente igual que
lock() tantas veces como a lock() la clase IceUtil::Mutex.
para que el cerrojo quede disponi-
ble para otro hilo. Sin embargo, existe una diferencia fundamental entre ambas. Internamente, el ce-
rrojo recursivo est implementado con un contador inicializado a cero. Cada llamada
a lock() incrementa el contador, mientras que cada llamada a unlock() lo decrementa.
El cerrojo estar disponible para otro hilo cuando el contador alcance el valor de cero.
Datos
compartidos
x
Condiciones
y
Operaciones
Cdigo
inicializacin
lock(), que intenta adquirir el monitor. Si ste ya estaba cerrado, entonces el hilo
que la invoca se suspende hasta que el monitor quede disponible. La llamada
retorna con el monitor cerrado.
tryLock(), que intenta adquirir el monitor. Si est disponible, la llamada de- No olvides...
vuelve true con el monitor cerrado. Si ste ya estaba cerrado antes de relizar la
Comprobar la condicin asociada al
llamada, la funcin devuelve false. uso de wait siempre que se retorne
de una llamada a la misma.
unlock(), que libera el monitor. Si existen hilos bloqueados por el mismo, en-
tonces uno de ellos se despertar y cerrar de nuevo el monitor.
7.6. La biblioteca de hilos de I CE [253]
C7
decir, si el timeout expira, timedWait() devuelve false.
notify(), que despierta a un nico hilo suspendido debido a una invocacin so-
bre wait() o timedWait(). Si no existiera ningn hilo suspendido, entonces la
invocacin sobre notify() se pierde. Llevar a cabo una noticacin no implica
que otro hilo reanude su ejecucin inmediatamente. En realidad, esto ocurrira
cuando el hilo que invoca a wait() o timedWait() libera el monitor.
notifyAll(), que despierta a todos los hilos suspendidos por wait() o timedWait().
El resto del comportamiento derivado de invocar a esta funcin es idntico a
notify().
get ()
wait ()
notify put ()
return item
no contiene
2. Si la estructura elementos, entonces el hilo se suspende mediante
wait() (lneas 18-19 ) hasta
que otro hilo que ejecute put() realice la invocacin
sobre notify() (lnea 13 ).
7.6. La biblioteca de hilos de I CE [255]
Recuerde que para que un hilo bloqueado por wait() reanude su ejecucin, otro
hilo ha de ejecutar notify() y liberar el monitor mediante unlock(). Sin embargo, en
el anterior listado de cdigo no existe ninguna llamada explcita a unlock(). Es inco-
rrecta la solucin? La respuesta es no, ya que la liberacin del monitor se delega en
Lock cuando la funcin put() naliza, es decir, justo despus de ejecutar la operacin
notify() en este caso particular.
No olvides... Volviendo al ejemplo anterior, considere dos hilos distintos que interactan con
Usar Lock y TryLock para evitar po-
la estructura creada, de manera genrica, para almacenar los slots que permitirn la
C7
sibles interbloqueos causados por la activacin de habilidades especiales por parte del jugador virtual. Por ejemplo, el hilo
generacin de alguna excepcin o asociado al productor podra implementarse como se muestra en el siguiente listado.
una terminacin de la funcin no
prevista inicialmente.
Suponiendo que el cdigo del consumidor sigue la misma estructura, pero extra-
yendo elementos de la estructura de datos compartida, entonces sera posible lanzar
distintos hilos para comprobar que el acceso concurrente sobre los distintos slots se
realiza de manera adecuada. Adems, sera sencillo visualizar, mediante mensajes por
la salida estndar, que efectivamente los hilos consumidores se suspenden en wait()
hasta que hay al menos algn elemento en la estructura de datos compartida con los
productores.
Antes de pasar a discutir en la seccin 7.9 un caso de estudio para llevar a cabo
el procesamiento de alguna tarea en segundo plano, resulta importante volver a dis-
cutir la solucin planteada inicialmente para el manejo de monitores. En particular,
la implementacin de las funciones miembro put() y get() puede generar sobrecarga
debido a que, cada vez que se aade un nuevo elemento a la estructura de datos, se
realiza una invocacin. Si no existen hilos esperando, la noticacin se pierde. Aun-
que este hecho no conlleva ningn efecto no deseado, puede generar una reduccin
del rendimiento si el nmero de noticaciones se dispara.
Una posible solucin a este problema consiste en llevar un control explcito de
Informacin adicional la existencia de consumidores de informacin, es decir, de hilos que invoquen a la
funcin get(). Para ello, se puede incluir una variable miembro en la clase Queue,
Una vez el ms, el uso de alguna planteando un esquema mucho ms eciente.
estructura de datos adicional puede
facilitar el diseo de una solucin. Como se puede apreciar en el siguiente listado de cdigo, el hilo productor slo
Este tipo de planteamientos puede
incrementar la eciencia de la so-
llevar a cabo una noticacin en el caso de que haya algn hilo consumidor en espera.
lucin planteada aunque haya una Para ello, consulta el valor de la variable miembro _consumidoresEsperando.
mnima sobrecarga debido al uso y
procesamiento de datos extra.
[256] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA
Por otra parte, los hilos consumidores, es decir, los que invoquen a la funcin get()
incrementan dicha variable antes de realizar un wait(), decrementndola cuando se
despierten.
Note que el acceso a la variable miembro _consumidoresEsperando es exclusivo
y se garantiza gracias a la adquisicin del monitor justo al ejecutar la operacin de
generacin o consumo de informacin por parte de algn hilo.
C7
7 }
8
9 int main() {
10 thread th(&func, 100);
11 th.join();
12 cout << "Fuera del hilo" << endl;
13 return 0;
14 }
9 http://es.cppreference.com/w/cpp/thread/mutex
[258] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA
C7
24
25 std::chrono::milliseconds espera(1250);
26 std::this_thread::sleep_for(espera);
27
28 /* Los mutex se desbloquean al salir de la funcin */
29 };
El listado que se muestra a continuacin lleva a cabo la instanciacin de los pali-
llos (ver lnea 9 ). stos se almacenan en un contenedor
vector que inicialmente tiene
una capacidad igual al nmero de lsofos (lnea 1 ). Resulta interesante destacar el
uso de unique_ptr en la lnea 4 para que el programador se olvide de la liberacin de
los punteros asociados, la cual tendr lugar, gracias a este enfoque, cuando queden
fuera
del mbito de declaracin. Tambin se utiliza la funcin std::move en la lnea
, novedad en C++11, para copiar el puntero a p1 en el vector palillos, anulndolo
12
tras su uso.
2 vector<thread> filosofos(numero_filosofos);
3
4 /* Primer filsofo */
5 /* A su derecha el palillo 1 y a su izquierda el palillo 5 */
6 filosofos[0] = thread(comer,
7 /* Palillo derecho (1) */
8 palillos[0].get(),
9 /* Palillo izquierdo (5) */
10 palillos[numero_filosofos - 1].get(),
11 /* Id filsofo */
12 1,
13 /* Id palillo derecho */
14 1,
15 /* Id palillo izquierdo */
16 numero_filosofos
17 );
18
19 /* Restos de filsofos */
20 for (int i = 1; i < numero_filosofos; i++) {
21 filosofos[i] = (thread(comer,
22 palillos[i - 1].get(),
23 palillos[i].get(),
24 i + 1,
25 i,
26 i + 1
27 )
28 );
29 }
30
31 /* A comer... */
32 for_each(filosofos.begin(),
33 filosofos.end(),
34 mem_fn(&thread::join));
El siguiente paso consiste en ejecutar la interfaz grca que nos ofrece cmake para
hacer explcito el soporte multi-hilo de Ogre. Para ello, simplemente hay que ejecutar
el comando cmake-gui y, a continuacin, especicar la ruta en la que se encuentra el
C7
cdigo fuente de Ogre y la ruta en la que se generar el resultado de la compilacin,
como se muestra en la gura 7.21. Despus, hay que realizar las siguientes acciones:
$ cd ogre_build_v1-8-1
$ make -j 2
11 http://www.ogre3d.org/tikiwiki/tiki-index.php?page=Building+Ogre+With+CMake
12 Segn los desarrolladores de Ogre, slo se recomienda utilizar un valor de 0 o de 2 en sistemas GNU/-
Linux
13 http://www.ogre3d.org/docs/api/html/classOgre_1_1Resource_1_
1Listener.html
[262] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA
Figura 7.21: Generando el chero Makele mediante cmake para compilar Ogre 1.8.1.
Tambin es muy importante considerar la naturaleza del juego, es decir, las ne-
cesidades reales de actualizar el mdulo de IA. En el caso del juego Simon, est nece-
sidad no sera especialmente relevante considerando el intervalo de tiempo existente
entre la generacin de una secuencia y la generacin del siguiente elemento que exten-
der la misma. Sin embargo, en juegos en los que intervienen un gran nmero de bots
con una gran interactividad, la actualizacin del mdulo de IA se debera producir con
mayor frecuencia.
: MyApp
start ()
update ()
Figura 7.23: Diagrama de interaccin entre la clase principal del juego y AIThread.
7 }
8
9 _AI = new AIThread(IceUtil::Time::seconds(1));
10 IceUtil::ThreadControl tc = _AI->start();
11 // Se desliga del hilo principal.
12 tc.detach();
13
14 // ...
15 }
Programacin Grfica
El objetivo de este mdulo titulado Programacin Grfica del Cur
so de Experto en Desarrollo de Videojuegos es cubrir los aspectos
esenciales relativos al desarrollo de un motor grfico interactivo. En
este contexto, el presente mdulo cubre aspectos esenciales y bsicos
relativos a los fundamentos del desarrollo de la parte grfica, como
por ejemplo el pipeline grfico, como elemento fundamental de la
arquitectura de un motor de juegos, las bases matemticas, las APIs
de programacin grfica, el uso de materiales y texturas, la
iluminacin o los sistemas de partculas. As mismo, el presente
mdulo tambin discute aspectos relativos a la exportacin e
importacin de datos, haciendo especial hincapi en los formatos
existentes para tratar con informacin multimedia. Finalmente, se
pone de manifiesto la importancia del uso de elementos directamente
conectados con el motor grfico, como la simulacin fsica, con el
objetivo de dotar de ms realismo en el comportamiento de los
objetos y actores que intervienen en el juego.
Fundamentos de Grcos
Captulo 8
Tridimensionales
Carlos Gonzlez Morcillo
U
no de los aspectos que ms llaman la atencin en los videojuegos actuales son
sus impactantes grcos 3D. En este primer captulo introduciremos los con-
ceptos bsicos asociados al pipeline en grcos 3D. Se estudiarn las diferen-
tes etapas de transformacin de la geometra hasta su despliegue nal en coordenadas
de pantalla. En la segunda parte del captulo estudiaremos algunas de las capacidades
bsicas del motor grco de videojuegos y veremos los primeros ejemplos bsicos de
uso de Ogre.
8.1. Introduccin
An ms rpido!! Desde el punto de vista del usuario, un videojuego puede denirse como una apli-
cacin software que responde a una serie de eventos, redibujando la escena y ge-
Si cada frame tarda en desplegarse nerando una serie de respuestas adicionales (sonido en los altavoces, vibraciones en
ms de 40ms, no conseguiremos el
mnimo de 25 fps (frames per se- dispositivos de control, etc...).
cond) establecido como estndar en El redibujado de esta escena debe realizarse lo ms rpidamente posible. La capa
cine. En videojuegos, la frecuencia
recomendable mnima es de unos de aplicacin en el despliegue grco es una de las actividades que ms ciclos de CPU
50 fps. consumen (incluso, como veremos en la seccin 8.3, con el apoyo de las modernas
GPUs Graphics Processing Unit existentes en el mercado).
269
[270] CAPTULO 8. FUNDAMENTOS DE GRFICOS TRIDIMENSIONALES
Supercies. La geometra de los objetos que forman la escena debe ser denida
empleando alguna representacin matemtica, para su posterior procesamiento
por parte del ordenador.
Cmara. La situacin del visor debe ser denida mediante un par (posicin,
rotacin) en el espacio 3D. El plano de imagen de esta cmara virtual denir
el resultado del proceso de rendering. Como se muestra en la Figura 8.1, para
imgenes generadas en perspectiva, el volumen de visualizacin dene una pir-
mide truncada que selecciona los objetos que sern representados en la escena.
Esta pirmide se denomina Frustum.
Figura 8.1: Descripcin general
Fuentes de luz. Las fuentes de luz emiten rayos que interactan con las super- del proceso de rendering.
cies e impactarn en el plano de imagen. Dependiendo del modo de simulacin
de estos impactos de luz (de la resolucin de la denominada ecuacin de ren- Tiempo de cmputo
der), tendremos diferentes mtodos de rendering.
En sntesis de imagen realista (co-
Propiedades de las supercies. En este apartado se incluyen las propiedades mo por ejemplo, las tcnicas de ren-
dering empleadas en pelculas de
de materiales y texturas que describen el modelo de rebote de los fotones sobre animacin) el clculo de un solo fo-
las supercies. tograma de la animacin puede re-
querir desde varias horas hasta das,
empleando computadores de altas
Uno de los principales objetivos en sntesis de imagen es el realismo. En gene- prestaciones.
ral, segn el mtodo empleado para la resolucin de la ecuacin de render tendremos
diferentes niveles de realismo (y diferentes tiempos de cmputo asociados). El prin-
cipal problema en grcos en tiempo real es que las imgenes deben ser generadas
muy rpidamente. Eso signica, como hemos visto anteriormente, que el motor gr-
co dispone de menos de 40 ms para generar cada imagen. Habitualmente este tiempo
es incluso menor, ya que es necesario reservar tiempo de CPU para otras tareas como
el clculo de la Inteligencia Articial, simulacin fsica, sonido...
8.1. Introduccin [271]
Pipeline
Raster
Punto
Punto de
de Vista
Vista
C8
Pipeline Hardware (Interactivo) RayCasting
Mapeado hacia delante Mapeado Inverso
Figura 8.2: Diferencias entre las tcnicas de despliegue interactivas (mtodos basados en ScanLine) y
realistas (mtodos basados en RayCasting). En el Pipeline Hardware, la seleccin del pxel ms cercano
relativo a cada tringulo se realiza directamente en Hardware, empleando informacin de los fragmentos
(ver Seccin 8.2).
App
El Pipeline est dividido en etapas funcionales. Al igual que ocurre en los pipe- Aplicacin
line de fabricacin industrial, algunas de estas etapas se realizan en paralelo y otras
secuencialmente. Idealmente, si dividimos un proceso en n etapas se incrementar la
Coord. Modelo (Locales)
velocidad del proceso en ese factor n. As, la velocidad de la cadena viene determinada
por el tiempo requerido por la etapa ms lenta. Transf Modelado
Como seala Akenine-Mler [8], el pipeline interactivo se divide en tres etapas Coord. Universales
conceptuales de Aplicacin, Geometra y Rasterizacin (ver Figura 8.3). A continua-
Etapa de Geometra
cin estudiaremos estas etapas. Transf Visualizacin
Coord. Visualizacin
8.2.1. Etapa de Aplicacin Vertex Shader
La etapa de aplicacin se ejecuta en la CPU. Actualmente la mayora de las CPUs Transf Proyeccin
son multincleo, por lo que el diseo de esta aplicacin se realiza mediante diferentes
hilos de ejecucin en paralelo. Habitualmente en esta etapa se ejecutan tareas aso- Coord. Normalizadas
ciadas al clculo de la posicin de los modelos 3D mediante simulaciones fsicas, Transf Recorte
deteccin de colisiones, gestin de la entrada del usuario (teclado, ratn, joystick...).
De igual modo, el uso de estructuras de datos de alto nivel para la aceleracin del des- Coord. Recortadas
pliegue (reduciendo el nmero de polgonos que se envan a la GPU) se implementan
Transf Pantalla
en la etapa de aplicacin.
Coord. Pantalla
Sistema de
Coordenadas Sistema de
Y Universal (SRU) Coordenadas de
Visualizacin
Z
Sistema de X
Coordenadas Local Y
(Objeto)
Z Z
X
C8
Y Plano de
X visualizacin
Figura 8.4: Sistema de coordenadas de visualizacin y su relacin con otros sistemas de coordenadas de la
escena.
Y
X
Z
Y
X
Transformacin
de Visualizacin
Z
X X
Transformacin
de Visualizacin
Z Z
Figura 8.5: El usuario especica la posicin de la cmara (izquierda) que se transforma, junto con los
objetos de la escena, para posicionarlos a partir del origen del SRU y mirando en la direccin negativa del
eje Z. El rea sombreada de la cmara se corresponde con el volumen de visualizacin de la misma (slo
los objetos que estn contenidos en esa pirmide sern nalmente representados).
nicamente los objetos que estn dentro del volumen de visualizacin deben ser
generados en la imagen nal. Los objetos que estn totalmente dentro del volumen de
visualizacin sern copiados ntegramente a la siguiente etapa del pipeline. Sin em-
bargo, aquellos que estn parcialmente incluidas necesitan ser recortadas, generando
nuevos vrtices en el lmite del recorte. Esta operacin de Transformacin de Recor-
te se realiza automticamente por el hardware de la tarjeta grca. En la Figura 8.6 se
muestra un ejemplo simplicado de recorte.
X
Finalmente la Transformacin de Pantalla toma como entrada las coordenadas
de la etapa anterior y produce las denominadas Coordenadas de Pantalla, que ajustan
Z las coordenadas x e y del cubo unitario a las dimensiones de ventana nales.
C8
Vrtices
Nuevos
8.2.3. Etapa Rasterizacin
A partir de los vrtices proyectados (en Coordenadas de Pantalla) y la informacin
asociada a su sombreado obtenidas de la etapa anterior, la etapa de rasterizacin se
encarga de calcular los colores nales que se asignarn a los pxeles de los objetos.
X Esta etapa de rasterizacin se divide normalmente en las siguientes etapas funciones
para lograr mayor paralelismo.
En la primera etapa del pipeline llamada Conguracin de Tringulos (Triangle
Z Setup), se calculan las coordenadas 2D que denen el contorno de cada tringulo (el
primer y ltimo punto de cada vrtice). Esta informacin es utilizada en la siguiente
Figura 8.6: Los objetos que inter- etapa (y en la interpolacin), y normalmente se implementa directamente en hardware
secan con los lmites del cubo uni- dedicado.
tario (arriba) son recortados, aa-
diendo nuevos vrtices. Los objetos A continuacin, en la etapa del Recorrido de Tringulo (Triangle Traversal) se
que estn totalmente dentro del cu- generan fragmentos para la parte de cada pxel que pertenece al tringulo. El recorrido
bo unitario se pasan directamente a del tringulo se basa por tanto en encontrar los pxeles que forman parte del tringulo,
la siguiente etapa. Los objetos que y se denomina Triangle Traversal (o Scan Conversion). El fragmento se calcula inter-
estn totalmente fuera del cubo uni- polando la informacin de los tres vrtices denidos en la etapa de Conguracin de
tario son descartados. Tringulos y contiene informacin calculada sobre la profundidad desde la cmara y
el sombreado (obtenida en la etapa de geometra a nivel de todo el tringulo).
La informacin interpolada de la etapa anterior se utiliza en el Pixel Shader (Som-
breado de Pxel) para aplicar el sombreado a nivel de pxel. Esta etapa habitualmente
se ejecuta en ncleos de la GPU programables, y permite implementaciones propias
por parte del usuario. En esta etapa se aplican las texturas empleando diversos mtodos
de proyeccin (ver Figura 8.7).
Finalmente en la etapa de Fusin (Merging) se almacena la informacin del color
de cada pxel en un array de colores denominado Color Buffer. Para ello, se combina
el resultado de los fragmentos que son visibles de la etapa de Sombreado de Pxel.
La visibilidad se suele resolver en la mayora de los casos mediante un buffer de
profundidad Z-Buffer, empleando la informacin que almacenan los fragmentos.
p'
Figura 8.9: Modelo de proyeccin en perspectiva simple. El plano de proyeccin innito est denido en
z = d, de forma que el punto p se proyecta sobre p .
Empleando tringulos semejantes (ver Figura 8.9 derecha), obtenemos las siguien-
tes coordenadas:
px d d px
= px = (8.1)
px pz pz
C8
8.3. Implementacin del Pipeline en GPU
El hardware de aceleracin grca ha sufrido una importante transformacin en la
P Vertex Shader ltima dcada. Como se muestra en la Figura 8.11, en los ltimos aos el potencial de
las GPUs ha superado con creces al de la CPU.
P Geometry Shader
GK110
F Transf. Recorte 7
C Transf. Pantalla
Itanium
Poulson
C
Nmero de Transistores ( Miles de Millones)
U
GP
zEC12
C Recorrido Tring.
P Pixel Shader 2
F Fusin (Merger)
GT200 IBM z196
U
CP
Figura 8.10: Implementacin tpi- 1 Opteron 2400
ca del pipeline en GPU con so-
porte Hbrido (OpenGL 2). La le- AMD K10
G80
tra asociada a cada etapa indica el
nivel de modicacin permitida al Core 2 Duo
usuario; P indica totalmente progra- Pentium IV Itanium 2
Pentium III GF4
mable, F indica jo (no programa-
ble), y C indica congurable pero Ao 1999 2001 2003 2005 2007 2009 2011 2013
no programable.
Figura 8.11: Evolucin del nmero de transistores en GPU y CPU (1999-2013)
Resulta de especial inters conocer aquellas partes del Pipeline de la GPU que
puede ser programable por el usuario mediante el desarrollo de shaders. Este cdigo
se ejecuta directamente en la GPU, y permite realizar operaciones a diferentes niveles
con alta eciencia.
[278] CAPTULO 8. FUNDAMENTOS DE GRFICOS TRIDIMENSIONALES
En GPU, las etapas del Pipeline grco (estudiadas en la seccin 8.2) se imple-
mentan en un conjunto de etapas diferente, que adems pueden o no ser programables
por parte del usuario (ver Figura 8.10). Por cuestiones de eciencia, algunas partes
del Pipeline en GPU no son programables, aunque se prevee que la tendencia en los
prximos aos sea permitir su modicacin.
La etapa asociada al Vertex Shader es totalmente programable, y encapsula las
cuatro primeras etapas del pipeline grco que estudiamos en la seccin 8.2: Trans-
formacin de Modelado, Transformacin de Visualizacin, Sombreado de Vrtices
(Vertex Shader) y Transformacin de Proyeccin.
La etapa referente al Geometry Shader es otra etapa programable que permite El trmino GPU
realizar operaciones sobre las primitivas geomtricas. Desde el 1999, se utiliza el trmino
GPU (Grpahics Processing Unit)
acuado por NVIDIA para diferen-
ciar la primera tarjeta grca que
La evolucin natural de las dos plataformas principales de grcos 3D en permita al programador implemen-
tiempo real (tanto OpenGL como Direct3D) ha sido el soporte nico de etapas tar sus propios algoritmos (GeForce
totalmente programables. Desde 2009, OpenGL en su versin 3.2 incluye un 256).
modo Compatibility Prole manteniendo el soporte de llamadas a las etapas
no programables. De forma similar, en 2009 Direct 3D versin 10 elimin las
llamadas a las etapas jas del pipeline.
C8
primitivas de entrada.
Este tipo de shaders se emplean para la simulacin de pelo, para encontrar los
bordes de los objetos, o para implementar algunas tcnicas de visualizacin avanzadas
como metabolas o simulacin de telas.
Figura 8.13: Capturas de algunos videojuegos desarrollados con motores libres. La imagen de la izquierda
se corresponde con Planeshift, realizado con Crystal Space. La captura de la derecha es del videojuego
H-Craft Championship, desarrollado con Irrlicht.
C8
OGRE. OGRE (http://www.ogre3d.org/) es un motor para grcos 3D
libre multiplataforma. Sus caractersticas sern estudiadas en detalle en la sec-
cin 8.6.
De entre los motores estudiados, OGRE 3D ofrece una calidad de diseo superior,
con caractersticas tcnicas avanzadas que han permitido el desarrollo de varios vi-
deojuegos comerciales. Adems, el hecho de que se centre exclusivamente en el capa
grca permite utilizar gran variedad de bibliotecas externas (habitualmente accedidas
mediante plugins) para proporcionar funcionalidad adicional. En la siguiente seccin
daremos una primera introduccin y toma de contacto al motor libre OGRE.
C8
que contiene. Veremos ms detalles sobre el trabajo con el grafo de escena a lo
largo de este mdulo.
Aceleracin Hardware. OGRE necesita una tarjeta aceleradora grca para
poder ejecutarse (con soporte de direct rendering mode). OGRE permite denir
el comportamiento de la parte programable de la GPU mediante la denicin de
Shaders, estando al mismo nivel de otros motores como Unreal o CryEngine.
Materiales. Otro aspecto realmente potente en OGRE es la gestin de materia-
les. Es posible crear materiales sin modicar ni una lnea de cdigo a compilar.
El sistema de scripts para la denicin de materiales de OGRE es uno de los
ms potentes existentes en motores de rendering interactivo. Los materiales de
OGRE se denen mediante una o ms Tcnicas, que se componen a su vez de
una o ms Pasadas de rendering (el ejemplo de la Figura 8.16 utiliza una ni-
ca pasada para simular el sombreado a lpiz mediante hatching). OGRE busca
automticamente la mejor tcnica disponible en un material que est soportada
por el hardware de la mquina de forma transparente al programador. Adems,
es posible denir diferentes Esquemas asociados a modos de calidad en el des-
pliegue.
Figura 8.16: Ejemplo de Shader
desarrollado por Assaf Raman para Animacin. OGRE soporta tres tipos de animacin ampliamente utilizados en
simular trazos a Lpiz empleando el la construccin de videojuegos: basada en esqueletos (skeletal), basada en vr-
sistema de materiales de OGRE. tices (morph y pose). En la animacin mediante esqueletos, OGRE permite el
uso de esqueletos con animacin basada en cinemtica directa. Existen multi-
tud de exportadores para los principales paquetes de edicin 3D. En este mdulo
utilizaremos el exportador de Blender.
El sistema de animacin de OGRE se basa en el uso de controladores, que
modican el valor de una propiedad en funcin de otro valor. En el caso de
animaciones se utiliza el tiempo como valor para modicar otras propiedades
(como por ejemplo la posicin del objeto). El motor de OGRE soporta dos mo-
delos bsicos de interpolacin: lineal y basada en splines cbicas.
La animacin y la geometra asociada a los modelos se almacena en un nico
Optimizacin ofine formato binario optimizado. El proceso ms empleado se basa en la exportacin
desde la aplicacin de modelado y animacin 3D a un formato XML (Ogre
El proceso de optimizacin de al
formato binario de OGRE calcula XML) para convertirlo posteriormente al formato binario optimizado mediante
el orden adecuado de los vrtices la herramienta de lnea de rdenes OgreXMLConverter.
de la malla, calcula las normales de
las caras poligonales, as como ver- Composicin y Postprocesado. El framework de composicin facilita al pro-
siones de diferentes niveles de de- gramador incluir efectos de postprocesado en tiempo de ejecucin (siendo una
talle de la malla. Este proceso evi- extensin del pixel shader del pipeline estudiado en la seccin 8.3.3). La apro-
ta el clculo de estos parmetros en
tiempo de ejecucin. ximacin basada en pasadas y diferentes tcnicas es similar a la explicada para
el gestor de materiales.
Plugins. El diseo de OGRE facilita el diseo de Plugins como componentes
que cooperan y se comunican mediante un interfaz conocido. La gestin de
archivos, sistemas de rendering y el sistema de partculas estn implementados
basados en el diseo de Plugins.
[284] CAPTULO 8. FUNDAMENTOS DE GRFICOS TRIDIMENSIONALES
Gestin de Recursos. Para OGRE los recursos son los elementos necesarios pa-
ra representar un objetivo. Estos elementos son la geometra, materiales, esque-
letos, fuentes, scripts, texturas, etc. Cada uno de estos elementos tienen asociado
un gestor de recurso especco. Este gestor se encarga de controlar la cantidad
de memoria empleada por el recurso, y facilitar la carga, descarga, creacin e
inicializacin del recurso. OGRE organiza los recursos en niveles de gestin
superiores denominados grupos. Veremos en la seccin 8.6.1 los recursos ges-
tionados por OGRE.
Caractersticas especcas avanzadas. El motor soporta gran cantidad de ca-
ractersticas de visualizacin avanzadas, que estudiaremos a lo largo del m-
dulo, tales como sombras dinmicas (basadas en diversas tcnicas de clculo),
sistemas de partculas, animacin basada en esqueletos y de vrtices, y un largo
etctera. OGRE soporta adems el uso de otras bibliotecas auxiliares mediante
plugins y conectores. Entre los ms utilizados cabe destacar las bibliotecas de
simulacin fsica ODE, el soporte del metaformato Collada, o la reproduccin
de streaming de vdeo con Theora. Algunos de estos mdulos los utilizaremos
en el mdulo 3 del presente curso.
Plugin Plugin
CustomArchiveFactory GLTexture GLRenderSystem
Uno de los objetos principales del sistema es el denominado Root. Root propor-
ciona mecanismos para la creacin de los objetos de alto nivel que nos permitirn
gestionar las escenas, ventanas, carga de plugins, etc. Gran parte de la funcionalidad
de Ogre se proporciona a travs del objeto Root. Como se muestra en el diagrama 8.17,
existen otros tres grupos de clases principales en OGRE:
C8
Gestor de Escena (clase SceneManager) es el que se encarga de implementar el
grafo de escena. Los nodos de escena SceneNode son elementos relacionados
jerrquicamente, que pueden adjuntarse o desligarse de una escena en tiempo de
ejecucin. El contenido de estos SceneNode se adjunta en la forma de instancias
de Entidades (Entity), que son implementaciones de la clase MovableObject.
Gestin de Recursos: Este grupo de objetos se encarga de gestionar los recursos
necesarios para la representacin de la escena (geometra, texturas, tipografas,
etc...). Esta gestin permite su carga, descarga y reutilizacin (mediante cachs
de recursos). En la siguiente subseccin veremos en detalle los principales ges-
tores de recursos denidos en OGRE.
Entidades Procedurales Rendering: Este grupo de objetos sirve de intermediario entre los objetos de
La mayor parte de las entidades que Gestin de Escena y el pipeline grco de bajo nivel (con llamadas espec-
incluyas en el SceneNode sern car- cas a APIs grcas, trabajo con buffers, estados internos de despliegue, etc.).
gadas de disco (como por ejem-
plo, una malla binaria en formato La clase RenderSystem es un interfaz general entre OGRE y las APIs de bajo
.mesh). Sin embargo, OGRE per- nivel (OpenGL o Direct3D). La forma ms habitual de crear la RenderWindow
mite la dencin en cdigo de otras es a travs del objeto Root o mediante el RenderSystem (ver Figura 8.18). La
entidades, como una textura proce- creacin manual permite el establecimiento de mayor cantidad de parmetros,
dural, o un plano.
aunque para la mayora de las aplicaciones la creacin automtica es ms que
suciente.
Por ser la gestin de recursos uno de los ejes principales en la creacin de una
aplicacin grca interactiva, estudiaremos a continuacin los grupos de gestin de
recursos y las principales clases relacionadas en OGRE.
Gestin de Recursos
ResourceManager
RenderSystem
ExternalTextureSourceManager
SceneManager
ResourceGroupManager
OverlayManager
PlatformManager
Figura 8.18: Diagrama de clases simplicado de los Gestores de alto nivel que dan acceso a los diferentes
subsistemas denidos en OGRE
Un gestor (Manager) en OGRE es una clase que gestiona el acceso a otros tipos
de objetos (ver Figura 8.18). Los Managers de OGRE se implementan mediante el
patrn Singleton que garantiza que nicamente hay una istancia de esa clase en toda
la aplicacin. Este patrn permite el acceso a la instancia nica de la clase Manager
desde cualquier lugar del cdigo de la aplicacin.
8.6. Introduccin a OGRE [287]
C8
ControllerManager. Es el gestor de los controladores que, como hemos in-
dicado anteriormente, se encargan de producir cambios en el estado de otras
clases dependiendo del valor de ciertas entradas.
DynLibManager. Esta clase es una de las principales en el diseo del sistema
de Plugins de OGRE. Se encarga de gestionar las bibliotecas de enlace dinmico
(DLLs en Windows y objetos compartidos en GNU/Linux).
ExternalTextureSourceManager. Gestiona las fuentes de textura externas (co-
mo por ejemplo, en el uso de video streaming).
FontManager. Gestiona las fuentes disponibles para su representacin en su-
perposiciones 2D (ver OverlayManager).
GpuProgramManager. Carga los programas de alto nivel de la GPU, denidos
en ensamblador. Se encarga igualmente de cargar los programas compilados por
el HighLevelGpuProgramManager.
HighLevelGpuProgramManager. Gestiona la carga y compilacin de los pro-
gramas de alto nivel en la GPU (shaders) utilizados en la aplicacin en HLSL,
GLSL o Cg.
LogManager. Se encarga de enviar mensajes de Log a la salida denida por
OGRE. Puede ser utilizado igualmente por cualquier cdigo de usuario que
quiera enviar eventos de Log.
MaterialManager. Esta clase mantiene las instancias de Material cargadas en
la aplicacin, permitiendo reutilizar los objetos de este tipo en diferentes obje-
tos.
MeshManager. De forma anloga al MaterialManager, esta clase mantiene las
instancias de Mesh permitiendo su reutilizacin entre diferentes objetos.
OverlayManager. Esta clase gestiona la carga y creacin de instancias de su-
perposiciones 2D, que permiten dibujar el interfaz de usuario (botones, iconos,
nmeros, radar...). En general, los elementos denidos como HUDs (Head Up
Display).
ParticleSystemManager. Gestiona los sistemas de partculas, permitiendo aa-
dir gran cantidad de efectos especiales en aplicaciones 3D. Esta clase gestiona
los emisores, los lmites de simulacin, etc.
PlatformManager. Esta clase abstrae de las particularidades del sistema opera-
tivo y del hardware subyacente de ejecucin, proporcionando rutinas indepen-
dientes del sistema de ventanas, temporizadores, etc.
[288] CAPTULO 8. FUNDAMENTOS DE GRFICOS TRIDIMENSIONALES
GNU/Linux (Debian)
Tanto OGRE como OIS puede ser igualmente instalado en cualquier distribucin
compilando directamente los fuentes. En el caso de OGRE, es necesario CMake para
generar el makele especco para el sistema donde va a ser instalado. En el caso de
OIS, es necesario autotools.
Microsoft Windows
C8
los desarrollos realizados con OGRE en plataformas Windows.
Como entorno de compilacin utilizaremos MinGW (Minimalist GNU for Win-
dows), que contiene las herramientas bsicas de compilacin de GNU para Windows.
El instalador de MinGW mingw-get-inst puede obtenerse de la pgina web
http://www.mingw.org/. Puedes instalar las herramientas en C:\MinGW\. Una
vez instaladas, debers aadir al path el directorio C:\MinGW\bin. Para ello, podrs
usar la siguiente orden en un terminal del sistema.
De igual modo, hay que descargar el SDK de DirectX4 . Este paso es opcional
siempre que no queramos ejecutar ningn ejemplo de OGRE que utilice Direct3D.
Sobre Boost... A continuacin instalaremos la biblioteca OGRE3D para MinGW5 . Cuando acabe,
al igual que hicimos con el directorio bin de MinGW, hay que aadir los directorios
Boost es un conjunto de bibliotecas
libres que aaden multitud de fun-
boost_1_44\lib\ y bin\Release al path.
cionalidad a la biblioteca de C++
(estn en proceso de aceptacin por path = %PATH %;C:\Ogre3D\boost_1_44\lib\; C:\Ogre3D\bin\
el comit de estandarizacin del
lenguaje).
make mode=release
28
29 all: info $(EXEC)
30
31 info:
32 @echo ---------------------------------------------------
33 @echo >>> Using mode $(mode)
34 @echo (Please, call "make" with [mode=debug|release])
35 @echo ---------------------------------------------------
36
37 # Enlazado -------------------------------------
38 $(EXEC): $(OBJS)
39 $(CXX) $(LDFLAGS) -o $@ $^ $(LDLIBS)
40
C8
41 # Compilacion -----------------------------------
42 $(DIROBJ) %.o: $(DIRSRC) %.cpp
43 $(CXX) $(CXXFLAGS) -c $< -o $@ $(LDLIBS)
44
45 # Limpieza de temporales ----------------------------
46 clean:
47 rm -f *.log $(EXEC) *~ $(DIROBJ)* $(DIRSRC)*~ $(DIRHEA)*~
Como se muestra en la Figura 8.19, adems de los archivos para make, en el direc-
torio raiz se encuentran tres archivos de conguracin. Estudiaremos a continuacin
su contenido:
ogre.cfg. Este archivo, si existe, intentar ser cargado por el objeto Root. Si el
archivo no existe, o est creado de forma incorrecta, se le mostrar al usuario
una ventana para congurar los parmetros (resolucin, mtodo de anti-aliasing,
profundidad de color, etc...). En el ejemplo que vamos a realizar, se fuerza a que
siempre se abra el dilogo (ver Figura 8.20), recuperando los parmetros que el
usuario especic en la ltima ejecucin.
Plugins.cfg en Windows plugins.cfg. Como hemos comentado anteriormente, un plugin es cualquier m-
dulo que implementa alguno de los interfaces de plugins de Ogre (como Sce-
En sistemas Windows, como no es
posible crear enlaces simblicos, se neManager o RenderSystem). En este archivo se indican los plugins que de-
deberan copiar los archivos .dll be cargar OGRE, as como su localizacin. En el ejemplo del holaMundo, el
al directorio plugins del proyecto e contenido del archivo indica la ruta al lugar donde se encuentran los plugins
indicar la ruta relativa a ese directo- (PluginFolder), as como el Plugin que emplearemos (pueden especicarse
rio en el archivo plugins.cfg.
Esta aproximacin tambin puede varios en una lista) para el rendering con OpenGL.
realizarse en GNU/Linux copiando
los archivos .so a dicho directo- resources.cfg. En esta primera versin del archivo de recursos, se especica el
rio. directorio general desde donde OGRE deber cargar los recursos asociados a los
nodos de la escena. A lo largo del curso veremos cmo especicar manualmente
ms propiedades a este archivo.
Nombres en entidades Una vez denida la estructura de directorios y los archivos que forman el ejemplo,
estudiaremos la docena de lneas de cdigo que tiene nuestro Hola Mundo.
Si no indicamos ningn nombre,
OGRE elegir automticamente un
nombre para la misma. El nombre Listado 8.2: El main.cpp del Hola Mundo en OGRE
debe ser nico. Si el nombre existe,
OGRE devolver una excepcin. 1 #include <ExampleApplication.h>
2
3 class SimpleExample : public ExampleApplication {
[292] CAPTULO 8. FUNDAMENTOS DE GRFICOS TRIDIMENSIONALES
C8
a b c
d e f
g h i
j k l m
Figura 8.22: Trabajos nales de los alumnos del Curso de Experto en Desarrollo de Videojuegos en las
Ediciones 2011/2012 y 2012/2013, que utilizaban OGRE como motor de representacin grca. a) Vault
Defense (D. Frutos, J. Lpez, D. Palomares), b) Robocorps (E. Aguilar, J. Alczar, R. Pozuelo, M. Romero),
c) Shadow City (L.M. Garca-Muoz, D. Martn-Lara, E. Monroy), d) Kartoon (R. Garca-Reina, E. Prieto,
J.M. Snchez), e) Ransom (M. Blanco, D. Moreno), f) Crazy Tennis (J. Angulo), g) Camel Race (F.J. Corts),
h) 4 Season Mini Golf (A. Blanco, J. Solana), i) Blood & Metal (J. Garca, F. Gaspar, F. Trivio), j) God
Mode On (J.M. Romero), k) Another Far West (R.C. Daz, J.M. Garca, A. Suarez), l) Impulse (I. Cuesta,
S. Fernndez) y m) Msica Maestro (P. Santos, C. de la Torre).
Matemticas para Videojuegos
Captulo 9
Carlos Gonzlez Morcillo
E
n este captulo comenzaremos a estudiar las transformaciones anes bsicas
que sern necesarias para en el desarrollo de Videojuegos. Las transformacio-
nes son herramientas imprescindibles para cualquier aplicacin que manipule
geometra o, en general, objetos descritos en el espacio 3D. La mayora de APIs y
motores grcos que trabajan con grcos 3D (como OGRE) implementan clases au-
xiliares para facilitar el trabajo con estos tipos de datos.
295
[296] CAPTULO 9. MATEMTICAS PARA VIDEOJUEGOS
9.1.2. Puntos
Un punto puede denirse como una localizacin en un espacio n-dimensional. Pa- SRU
ra identicar claramente p como un punto, decimos que p E2 que signica que un
punto 2D vive en el espacio eucldeo E2 . En el caso de los videojuegos, este espa- Figura 9.2: Sistemas de referencia
cio suele ser bidimensional o tridimensional. El tratamiento discreto de los valores Global (Sistema de Referencia Uni-
asociados a la posicin de los puntos en el espacio exige elegir el tamao mnimo y versal o SRU) y Local. Si describi-
mximo que tendrn los objetos en nuestro videojuego. Es crtico denir el convenio mos la posicin del tesoro con res-
pecto del sistema de coordenadas
empleado relativo al sistema de coordenadas y las unidades entre programadores y local del barco, el tesoro siempre se
artistas. Existen diferentes sistemas de coordenadas en los que pueden especicarse mover con el barco y ser imposi-
estas posiciones, como se puede ver en la Figura 9.4: ble volver a localizarlo.
Mano
Coordenadas en OGRE. OGRE utiliza el convenio de la mano derecha. El Derecha
pulgar (eje positivo X) y el ndice (eje positivo Y ) denen el plano de la
pantalla. El pulgar apunta hacia la derecha de la pantalla, y el ndice hacia el
techo. El anular dene el eje positivo de las Z, saliendo de la pantalla hacia z x
el observador.
Figura 9.3: Convenios para esta-
blecer los sistemas de coordenadas
cartesianas.
9.1. Puntos, Vectores y Coordenadas [297]
y h
py
ph
p
p pr p p
pr p
pz p
px
z x
r
r
Coordenadas Coordenadas Coordenadas
Cartesianas Cilndricas Esfricas
C9
Figura 9.4: Representacin de un punto empleando diferentes sistemas de coordenadas.
9.1.3. Vectores
Un vector es una tupla n-dimensional que tiene una longitud (denominada m-
dulo), una direccin y un sentido. Puede ser representado mediante una echa. Dos
vectores son iguales si tienen la misma longitud, direccin y sentido. Los vectores son
entidades libres que no estn ancladas a ninguna posicin en el espacio. Para dife-
renciar un vector v bidimensional de un punto p, decimos que v R2 ; es decir, que
vive en un espacio lineal R2 .
Convenio en OGRE Como en algn momento es necesario representar los vectores como tuplas de
En OGRE se utilizan las clases
nmeros en nuestros programas, en muchas bibliotecas se emplea la misma clase
Vector2 y Vector3 para tra- Vector para representar puntos y vectores en el espacio. Es imprescindible que el
bajar indistintamente con puntos y programador distinga en cada momento con qu tipo de entidad est trabajando.
vectores en 2 y 3 dimensiones.
A continuacin describiremos brevemente algunas de las operaciones habituales
realizadas con vectores.
Tambin puede ser calculado mediante la siguiente expresin que relaciona el n- Figura 9.6: La proyeccin de a so-
gulo que forman ambos vectores. bre b obtiene como resultado un es-
calar.
a b = |a| |b| cos() (9.2)
Combinando las expresiones 9.1 y 9.2, puede calcularse el ngulo que forman dos
vectores (operacin muy utilizada en informtica grca).
9.1. Puntos, Vectores y Coordenadas [299]
Otra operacin muy til es el clculo de la proyeccin de un vector sobre otro, que
se dene a b como la longitud del vector a que se proyecta mediante un ngulo
recto sobre el vector b (ver Figura 9.6).
ab
a b = |a| cos() =
|b|
El producto escalar se emplea adems para detectar si dos vectores tienen la mis-
ma direccin, o si son perpendiculares, o si tienen la misma direccin o direcciones
opuestas (para, por ejemplo, eliminar geometra que no debe ser representada).
a
Colineal. Dos vectores son colineales si se encuentran denidos en la misma
C9
ab>0 lnea recta. Si normalizamos ambos vectores, y aplicamos el producto escalar
podemos saber si son colineales con el mismo sentido (a b = 1) o sentido
opuesto (a b = 1).
b
a Perpendicular. Dos vectores son perpendiculares si el ngulo que forman entre
ellos es 90 (o 270), por lo que a b = 0.
Misma direccin. Si el ngulo que forman entre ambos vectores est entre 270
ab=0 y 90 grados, por lo que a b > 0 (ver Figura 9.7).
b Direccin Opuesta. Si el ngulo que forman entre ambos vectores es mayor que
a 90 grados y menor que 270, por lo que a b < 0.
El sentido del vector resultado del producto escalar sigue la regla de la mano de-
recha (ver Figura 9.8): si agarramos a b con la palma de la mano, el pulgar apunta
en el sentido positivo del vector producto si el giro del resto de los dedos va de a a b.
En caso contrario, el sentido del vector es el inverso. Por tanto, el producto vectorial
axb no es conmutativo.
El mdulo del producto vectorial est directamente relacionado con el ngulo que
forman ambos vectores . Adems, el mdulo del producto vectorial de dos vectores
a es igual al rea del paralelogramo formado por ambos vectores (ver Figura 9.8).
b
|a b| = |a| |b| sin
De igual modo podemos expresar una rotacin de un punto p = (x, y) a una nueva x
posicin rotando un ngulo respecto del origen de coordenadas, especicando el eje
de rotacin y un ngulo . Las coordenadas iniciales del punto se pueden expresar Figura 9.9: Arriba. Traslacin de
un punto p a p empleando el vec-
como (ver Figura 9.10): tor t. Abajo. Es posible trasladar un
objeto poligonal completo aplican-
px = d cos py = d sen (9.4) do la traslacin a todos sus vrtices.
C9
9.2.1. Representacin Matricial
Figura 9.11: Conversin de un cua- En muchas aplicaciones grcas los objetos deben transformarse geomtricamente
drado a un rectngulo emplean-
do los factores de escala Sx =
de forma constante (por ejemplo, en el caso de una animacin, en la que en cada frame
2, Sy = 1,5. el objeto debe cambiar de posicin. En el mbito de los videojuegos, es habitual que
aunque un objeto permanezca inmvil, es necesario cambiar la posicin de la cmara
virtual para que se ajuste a la interaccin con el jugador.
Homogenezate!!
De este modo resulta crtica la eciencia en la realizacin de estas transforma-
Gracias al uso de coordenadas ho- ciones. Como hemos visto en la seccin anterior, las ecuaciones 9.3, 9.5 y 9.6 nos
mogneas es posible representar las
ecuaciones de transformacin geo- describan las operaciones de traslacin, rotacin y escalado. Para la primera es nece-
mtricas como multiplicacin de sario realizar una suma, mientras que las dos ltimas requieren multiplicaciones. Sera
matrices, que es el mtodo estn- conveniente poder combinar las transformaciones de forma que la posicin nal de
dar en grcos por computador (so-
portado en hardware por las tarjetas
las coordenadas de cada punto se obtenga de forma directa a partir de las coordenadas
aceleradoras grcas). iniciales.
Si reformulamos la escritura de estas ecuaciones para que todas las operaciones se
realicen multiplicando, podramos conseguir homogeneizar estas transformaciones.
Si aadimos un trmino extra (parmetro homogneo h) a la representacin del
punto en el espacio (x, y), obtendremos la representacin homognea de la posicin
descrita como (xh , yh , h). Este parmetro homogneo h es un valor distinto de cero tal
que x = xh /h, y = yh /h. Existen, por tanto innitas representaciones homogneas
equivalentes de cada par de coordenadas, aunque se utiliza normalmente h = 1. Como
veremos en la seccin 9.3, no siempre el parmetro h es igual a uno.
Puntos y Vectores De este modo, la operacin de traslacin, que hemos visto anteriormente, puede
expresarse de forma matricial como:
En el caso de puntos, la componen-
te homognea h = 1. Los vectores
emplean como parmetro h = 0. x 1 0 Tx x
y = 0 1 Ty y (9.7)
1 0 0 1 1
y
Al resolver la multiplicacin matricial se obtienen un conjunto de ecuaciones equi-
valentes a las enunciadas en 9.3. De forma anloga, las operaciones de rotacin Tr y
escalado Ts tienen su equivalente matricial homogneo.
x cos sen 0 Sx 0 0
z Tr = sen cos 0 Ts = 0 Sy 0 (9.8)
0 0 1 0 0 1
Figura 9.12: Sentido de las rotacio- Las transformaciones inversas pueden realizarse sencillamente cambiando el signo en
nes positivas respecto de cada eje de el caso de la traslacin y rotacin (distancias y ngulos negativos), y en el caso de la
coordenadas. escala, utilizando los valores 1/Sx y 1/Sy .
[302] CAPTULO 9. MATEMTICAS PARA VIDEOJUEGOS
Las rotaciones requieren distinguir el eje sobre el que se realizar la rotacin. Las
rotaciones positivas alrededor de un eje se realizan en sentido opuesto a las agujas
del reloj, cuando se est mirando a lo largo de la mitad positiva del eje hacia el origen
del sistema de coordenadas (ver Figura 9.12).
Las expresiones matriciales de las rotaciones son las siguientes:
1 0 0 0 cos 0 sen 0 cos sen 0 0
0 cos sen 0 0 1 0 0 sen cos 0 0
Rx = 0 sen cos 0 Ry = sen 0 cos 0 Rz = 0 0 1 0 (9.10)
0 0 0 1 0 0 0 1 0 0 0 1
Cuadro 9.1: Propiedades geomtricas preservadas segn la clase de transformacin: Transformaciones Esca-
C. Rg.
anes, Lineales y de Cuerpo Rgido. lado Traslacin
Rotacin
Propiedad T. Anes T. Lineales T. Cuerpo Rgido
ngulos No No S
Figura 9.13: Esquema de subclases
Distancias No No S de transformaciones anes y opera-
Ratios de distancias S S S ciones asociadas.
Lneas paralelas S S S
Lneas rectas S S S
9.2. Transformaciones Geomtricas [303]
x x x x
C9
Figura 9.14: Secuencia de operaciones necesarias para rotar una gura respecto de un origen arbitrario.
Matrices Inversas Tanto la matriz A como su inversa deben ser cuadradas y del mismo tamao. Otra
No todas las matrices tienen inversa
propiedad interesante de la inversa es que la inversa de la inversa de una matriz es
(incluso siendo cuadradas). Un caso igual a la matriz original (A1 )1 = A.
muy simple es una matriz cuadrada
cuyos elementos son cero. En la ecuacin inicial, podemos resolver el sistema utilizando la matriz inversa.
Si partimos de A = B x, podemos multiplicar ambos trminos a la izquierda por la
inversa de B, teniendo B 1 A = B 1 B x, de forma que obtenemos la matriz
identidad B 1 A = I x, con el resultado nal de B 1 A = x.
En algunos casos el clculo de la matriz inversa es directo, y puede obtenerse de
forma intuitiva. Por ejemplo, en el caso de una traslacin pura (ver ecuacin 9.9),
basta con emplear como factor de traslacin el mismo valor en negativo. En el caso de
escalado, como hemos visto bastar con utilizar 1/S como factor de escala.
Cuando se trabaja con matrices compuestas, el clculo de la inversa tiene que
realizarse con mtodos generales, como por ejemplo el mtodo de eliminacin de
Gauss o la traspuesta de la matriz adjunta.
9.2.3. Composicin
Como hemos visto en la seccin anterior, una de las principales ventajas derivadas
del trabajo con sistemas homogneos es la composicin de matrices. Matemtica-
mente esta composicin se realiza multiplicando las matrices en un orden determina-
do, de forma que es posible obtener la denominada matriz de transformacin neta
MN resultante de realizar sucesivas transformaciones a los puntos. De este modo,
bastar con multiplicar la MN a cada punto del modelo para obtener directamente su
posicin nal. Por ejemplo, si P es el punto original y P es el punto transformado, y
T1 Tn son transformaciones (rotaciones, escalados, traslaciones) que se aplican al
[304] CAPTULO 9. MATEMTICAS PARA VIDEOJUEGOS
y y y
Rz(/4) Sx(2)
x x x
C9
y y y
Sx(2) Rz(/4)
x x x
Figura 9.17: La multiplicacin de matrices no es conmutativa, por lo que el orden de aplicacin de las
transformaciones es relevante para el resultado nal. Por ejemplo, la gura de arriba primero aplica una
rotacin y luego el escalado, mientras que la secuencia inferior aplica las transformaciones en orden inverso.
1 0 0 0 px px d px /pz
0 1 0 0 py py d py /pz
p = Mp p = 0 0 1 0 pz = pz = d (9.13)
0 0 1/d 0 1 pz /d 1
Denicin near y far El ltimo paso de la ecuacin 9.13 corresponde con la normalizacin de los com-
ponentes dividiendo por el parmetro homogneo h = pz /d. De esta forma tenemos
Un error comn suele ser que la de- la matriz de proyeccin que nos aplasta los vrtices de la geometra sobre el plano de
nicin correcta de los planos near
y far nicamente sirve para limitar proyeccin. Desafortunadamente esta operacin no puede deshacerse (no tiene inver-
la geometra que ser representada sa). La geometra una vez aplastada ha perdido la informacin sobre su componente
en el Frustum. La denicin correc- de profundidad. Es interesante obtener una trasnformacin en perspectiva que proyec-
ta de estos parmetros es impres- te los vrtices sobre el cubo unitario descrito previamente (y que s puede deshacerse).
cindible para evitar errores de pre-
cisin en el Z-Buffer. De esta forma, denimos la pirmide de visualizacin o frustum, como la pirmide
truncada por un plano paralelo a la base que dene los objetos de la escena que sern
representados. Esta pirmide de visualizacin queda denida por cuatro vrtices que
denen el plano de proyeccin (left l, right r, top t y bottom b), y dos distancias a los
planos de recorte (near n y far f ), como se representa en la Figura 9.18. El ngulo de
visin de la cmara viene determinado por el ngulo que forman l y r (en horizontal)
y entre t y b (en vertical).
[306] CAPTULO 9. MATEMTICAS PARA VIDEOJUEGOS
Y Y
top
left
right
botto Mp
m
near
far
X Z X
Z
La matriz que transforma el frustum en el cubo unitario viene dada por la expresin
de la ecuacin 9.14.
2n r+l
rl 0 rl 0
0 2n t+b
tb 0
Mp =
0
tb
f +n n
(9.14)
0 f n f2fn
0 0 1 0
9.4. Cuaternios
Los cuaternios (quaternion) fueron propuestos en 1843 por William R. Hamilton
como extensin de los nmeros complejos. Los cuaternios se representan mediante
una 4-tupla q = [qx , qy , qz , qw ]. El trmino qw puede verse como un trmino escalar
que se aade a los tres trminos que denen un vector (qx , qy , qz ). As, es comn
representar el cuaternio como q = [qv , qs ] siendo qv = (qx , qy , qz ) y qs = qw en la
tupla de cuatro elementos inicial.
Los cuaternios unitarios, que cumplen la restriccin de que (qx2 +qy2 +qz2 +qw
2
) = 1,
se emplean ampliamente para representar rotaciones en el espacio 3D. El conjunto de
todos los cuaternios unitarios denen la hiperesfera unitaria en R4 .
Si denimos como a al vector unitario que dene el eje de rotacin, como el
ngulo de rotacin sobre ese eje empleando la regla de la mano derecha (si el pulgar
apunta en la direccin de a, las rotaciones positivas se denen siguiendo la direccin
Figura 9.19: Representacin de un
de los dedos curvados de la mano), podemos describir el cuaternio como (ver Figu-
cuaternio unitario.
ra 9.19):
Teorema de Euler
q = [qv , qs ] = [a sin(/2), cos(/2)]
Segn el Teorema de Rotacin de
Leonhard Euler (1776), cualquier
De forma que la parte vectorial se calcula escalando el vector de direccin uni- composicin de rotaciones sobre un
tario por el seno de /2, y la parte escalar como el coseno de /2. Obviamente, esta slido rgido es equivalente a una
multiplicacin en la parte vectorial se realiza componente a componente. sola rotacin sobre un eje, llamado
Polo de Euler.
9.4. Cuaternios [307]
z z z z
a) b) c) d)
x x x x
y y y y
Figura 9.20: El problema del bloqueo de ejes (Gimbal Lock). En a) se muestra el convenio de sistema de
C9
coordenadas que utilizaremos, junto con las elipses de rotacin asociadas a cada eje. En b) aplicamos una
rotacin respecto de x, y se muestra el resultado en el objeto. El resultado de aplicar una rotacin de 90o
en y se muestra en c). En esta nueva posicin cabe destacar que, por el orden elegido de aplicacin de las
rotaciones, ahora el eje x y el eje z estn alineados, perdiendo un grado de libertad en la rotacin. En d) se
muestra cmo una rotacin en z tiene el mismo efecto que una rotacin en x, debido al efecto de Gimbal
Lock.
Como vimos en la seccin 9.2.1, se puede usar una matriz para resumir cualquier
conjunto de rotaciones en 3D. Sin embargo, las matrices no son la mejor forma de
representar rotaciones, debido a:
q 1 = q = [qv , qs ]
v = q2 q1 v q11 q21
9.5. Interpolacin Lineal y Esfrica [309]
C9
polacin. Un claro ejemplo de su uso es para calcular la posicin intermedia en una
animacin de un objeto que se traslada desde un punto A, hasta un punto B.
6 B La interpolacin lineal es un mtodo para calcular valores intermedios mediante
5 polinomios lineales. Es una de las formas ms simples de interpolacin. Habitual-
4 v mente se emplea el acrnimo LERP para referirse a este tipo de interpolacin. Para
3 A obtener valores intermedios entre dos puntos A y B, habitualmente se calcula el vec-
2 tor v = B A con origen en A y destino en B.
1
1 2 3 4 5 6 7 z SLERP (p,q, t=0.5) cuaternio q
(0.816, 0, 0.408, 0.408) (0.707, 0, 0.707, 0)
Figura 9.22: Ejemplo de interpola-
cin lineal entre dos puntos. Se han
z
marcado sobre el vector v los pun- z
tos correspondientes a t = 0, 25, y
t = 0,5 y t = 0,75.
y
y
x
cuaternio p x x
(0.707, 0, 0, 0.707)
Figura 9.23: Ejemplo de aplicacin de interpolacin esfrica SLERP en la rotacin de dos cuaternios p y q
sobre un objeto.
Como podemos ver en el ejemplo de la Figura 9.22, para t = 0,25 tenemos que
v = (6, 4), por lo que v 0,25 = (1,5, 1). As, LERP (A, B, 0,25) = (1, 2) +
(1,5, 1) = (2,5, 3).
Como se enunci en la seccin 9.4, una de las ventajas del uso de cuaternios es
la relativa a la facilidad de realizar interpolacin entre ellos. La Interpolacin Lineal
Esfrica (tambin llamada slerp de Spherical Linear Interpolation) es una operacin
que, dados dos cuaternios y un parmetro t (0, 1), calcula un cuaternio interpolado
entre ambos, mediante la siguiente expresin1 :
sin((1 t)) sin(t)
SLERP (p, q, t) = p+ q
sin() sin()
1 Siendo el ngulo que forman los dos cuaternios unitarios.
[310] CAPTULO 9. MATEMTICAS PARA VIDEOJUEGOS
Modulo: |V1| = 1
Normalizar: V2n = Vector3(0, 1, 0)
Angulo entre V1 y V2 = 90
--- Algunos operadores de Cuaternios ----
Suma: P + Q = Quaternion(1.41421, 0, 0.707107, 0.707107)
Producto: P * Q = Quaternion(0.5, -0.5, 0.5, 0.5)
Producto escalar: P * Q = 0.5
SLERP(p,q,0.5) = Quaternion(0.816497, 0, 0.408248, 0.408248)
C9
A' y despus una rotacin de /2 radianes respecto del eje Z. Las transformaciones
C' se realizan en ese orden y sobre el SRU.
B 2. Dado el cuadrado denido por los puntos A=(1,0,3), B=(3,0,3), C=(3,0,1) y
A D=(1,0,1), realizar un escalado del doble en el eje X respecto a su centro geo-
mtrico. Compruebe que el resultado no es el mismo que si realiza el escalado
C directamente con respecto del eje X del SRU.
Figura 9.24: En el ejercicio 4, tras 3. Cules seran las expresiones matriciales correspondientes a la operacin de
aplicar el desplazamiento de 2 uni- reexin (espejo) sobre los planos ortogonales x=0, y=0 y z=0? Calcule una
dades en la direccin del vector nor- matriz por cada plano.
mal sobre los vrtices originales del
tringulo, obtenemos A B C . 4. Dado el tringulo ABC, siendo A=(1, -1, 1), B=(-1, -1, -1) y C=(1, 1, -1), despla-
ce la cara poligonal 2 unidades en la direccin de su vector normal (ver Figura
9.24). Suponga que el vector normal sale segn la regla de la mano derecha.
Captulo
Grafos de Escena
10
Carlos Gonzlez Morcillo
C
omo vimos en el captulo 8, uno de los pilares clave de cualquier motor grco
es la organizacin de los elementos que se representarn en la escena. Esta
gestin se realiza mediante el denominado Grafo de Escena que debe permitir
inserciones, bsquedas y mtodos de ordenacin ecientes. OGRE proporciona una
potente aproximacin a la gestin de los Grafos de Escena. En este captulo trabaja-
remos con los aspectos fundamentales de esta estructura de datos jerrquica.
10.1. Justicacin
Como seala D. Eberly [30], el motor de despliegue grco debe dar soporte a
cuatro caractersticas fundamentales, que se apoyan en el Grafo de Escena:
Test de Visibilidad
1. Gestin Datos Eciente. Es necesario eliminar toda la geometra posible antes
La gestin de la visibilidad de los
elementos de la escena es una de las de enviarla a la GPU. Aunque la denicin del Frustum y la etapa de recor-
tareas tpicas que se encarga de rea- te eliminan la geometra que no es visible, ambas etapas requieren tiempo de
lizar el motor grco empleando el procesamiento. Es posible utilizar el conocimiento sobre el tipo de escena a re-
Grafo de Escena. presentar para optimizar el envo de estas entidades a la GPU. Las relaciones
entre los objetos y sus atributos se modelan empleando el Grafo de Escena.
2. Interfaz de Alto Nivel. El Grafo de Escena puede igualmente verse como un
interfaz de alto nivel que se encarga de alimentar al motor grco de bajo nivel
empleando alguna API especica. El diseo de este interfaz de alto nivel per-
mite abstraernos de futuros cambios necesarios en las capas dependientes del
dispositivo de visualizacin.
313
[314] CAPTULO 10. GRAFOS DE ESCENA
Las estructuras de datos de Grafos de Escena suelen ser grafos que representen
las relaciones jerrquicas en el despliegue de objetos compuestos. Esta dependencia
espacial se modela empleando nodos que representan agregaciones de elementos (2D
o 3D), as como transformaciones, procesos y otras entidades representables (sonidos,
vdeos, etc...).
El Grafo de Escena puede denirse como un grafo dirigido sin ciclos. Los arcos
del grafo denen dependencias del nodo hijo respecto del padre, de modo que la apli-
cacin de una transformacin en un nodo padre hace que se aplique a todos los nodos
hijo del grafo.
El nodo raz habitualmente es un nodo abstracto que proporciona un punto de
inicio conocido para acceder a la escena. Gracias al grafo, es posible especicar f-
cilmente el movimiento de escenas complejas de forma relativa a los elementos padre Cabina
de la jerarqua. En la Figura 10.1, el movimento que se aplique a la Tierra (rotacin, Base Llantas
traslacin, escalado) se aplicar a todos los objetos que dependan de ella (como por
ejemplo a las Tiritas, o las Llantas de la exacavadora). A su vez, las modicaciones Codo
aplicadas sobre la Cabina se aplicarn a todos los nodos que herenden de la jerarqua,
pero no al nodo Llantas o al nodo Tierra.
Pala
Tierra
10.1.1. Operaciones a Nivel de Nodo
Como seala Theoharis et al. [98], en trminos funcionales, la principal ventaja
asociada a la construccin de un Grafo de Escena es que cada operacin se propaga de
forma jerrquica al resto de entidades mediante el recorrido del grafo. Las principales Escena
operaciones que se realizan en cada nodo son la inicializacin, simulacin, culling y
dibujado. A continuacin estudiaremos qu realiza cada operacin.
Tierra Cielo
Inicializacin. Este operador establece los valores iniciales asociados a cada
entidad de la jerarqua. Es habitual que existan diferentes instancias referencia- Tiritas Llantas
das de las entidades asociadas a un nodo. De este modo se separan los datos
estticos asociados a las entidades (denicin de la geometra, por ejemplo) y
las transformaciones aplicados sobre ellos que se incluyen a nivel de nodo. Cabina
C10
Figura 10.2: Ejemplo de estructura de datos para la gestin del culling jerrquico. En el ejemplo, el nodo
referente a las Llantas (ver Figura 10.1) debe mantener la informacin de la caja lmite (Bounding Box) de
todos los hijos de la jerarqua, remarcada en la imagen de la derecha.
Visibilidad
10.2.1. Creacin de Objetos Si quieres que un nodo no se dibuje,
simplemente ejecutas la operacin
de detach y no ser renderizado.
La tarea ms ampliamente utilizada del Gestor de Escenas es la creacin de los
objetos que formarn la escena: luces, cmaras, sistemas de partculas, etc. Cualquier Destruccin de nodos
elemento que forme parte de la escena ser gestionado por el Gestor de Escenas, y
La destruccin de un nodo de la es-
formar parte del Grafo de Escena. Esta gestin est directamente relacionada con el cena no implica la liberacin de la
ciclo de vida completa de los objetos, desde su creacin hasta su destruccin. memoria asignada a los objetos ad-
juntos al nodo. Es responsabilidad
Los Nodos del Grafo de Escena se crean empleando el Gestor de Escena. Los del programador liberar la memoria
nodos del grafo en OGRE tienen asignado un nico nodo padre. Cada nodo padre de esos objetos.
puede tener cero o ms hijos. Los nodos pueden adjuntarse (attach) o separarse (de-
tach) del grafo en tiempo de ejecucin. El nodo no se destruir hasta que se le indique
explcitamente al Gestor de Escenas.
En la inicializacin, el Gestor de Escena se encarga de crear al menos un nodo:
el Root Node. Este nodo no tiene padre, y es el padre de toda la jerarqua. Aunque
es posible aplicar transformaciones a cualquier nodo de la escena (como veremos en
la seccin 10.2.2), al nodo Root no se le suele aplicar ninguna transformacin y es
un buen punto en el que adjuntar toda la geometra esttica de la escena. Dado el
objeto SceneManager, una llamada al mtodo getRootSceneNode() nos devuelve
un puntero al Root SceneNode. Este nodo es, en realidad, una variable miembro de la
clase SceneManager.
Los objetos de la escena se pueden adjuntar a cualquier nodo de la misma. En
un nodo pueden existir varios objetos. Sin embargo, no es posible adjuntar la misma
instancia de un objeto a varios nodos de escena al mismo tiempo. Para realizar es-
ta operacin primero hay que separar (detach) el objeto del nodo, y posteriormente
adjuntarlo (attach) a otro nodo distinto.
Para crear un nodo en OGRE, se utiliza el mtodo createSceneNode, que puede
recibir como parmetro el nombre del nodo. Si no se especica, OGRE internamente
le asigna un nombre nico que podr ser accedido posteriormente.
Root
MovableObject
Hijo1 Hijo2
SimpleRenderable Entity Light AnimableObject ParticleSystem
Hijo11 Hijo12
Rectangle2D WireBoundingBox Frustum ManualObject
Hijo121
Camera
Figura 10.3: Ejemplo de grafo de
Figura 10.4: Algunos de los tipos de objetos que pueden ser aadidos a un nodo en OGRE. La clase escena vlido en OGRE. Todos los
abstracta MovableObject dene pequeos objetos que sern aadidos (attach) al nodo de la escena. nodos (salvo el Root) tienen un no-
do padre. Cada nodo padre tiene ce-
ro o ms hijos.
10.2. El Gestor de Escenas de OGRE [317]
C10
de entidades se realiza empleando el mtodo createEntity del Gestor de Escena,
indicando el nombre del modelo y el nombre que quiere asignarse a la entidad.
Mundo
El cdigo del siguiente listado (modicacin del Hola del captulo 8) crea
Root
un nodo hijo myNode de RootSceneNode en las lneas , y aade la entidad myEnt
al nodo myChild en la lnea 6 (ver Figura 10.5). A partir de ese punto del cdi-
3-4
myNode go, cualquier transformacin que se aplique sobre el nodo myNode se aplicarn a la
entidad myEnt.
myEnt
Listado 10.1: Creacin de nodos
1 class SimpleExample : public ExampleApplication {
Figura 10.5: Jerarqua obtenida en 2 public : void createScene() {
el grafo de escena asociado al lista- 3 SceneNode* node = mSceneMgr->createSceneNode("myNode");
do de ejemplo. 4 mSceneMgr->getRootSceneNode()->addChild(node);
5 Entity *myEnt = mSceneMgr->createEntity("cuboejes", "cuboejes.
mesh");
6 node->attachObject(myEnt);
7 }
8 };
10.2.2. Transformaciones 3D
Las transformaciones 3D se realizan a nivel de nodo de escena, no a nivel de
objetos. En realidad en OGRE, los elementos que se mueven son los nodos, no los
objetos individuales que han sido aadidos a los nodos.
Como vimos en la seccin 9.1, OGRE utiliza el convenio de la mano derecha para
especicar su sistema de coordenadas. Las rotaciones positivas se realizan en contra
del sentido de giro de las agujas del reloj (ver Figura 9.12).
Las transformaciones en el Grafo de Escena de OGRE siguen el convenio general
explicado anteriormente en el captulo, de forma que se especican de forma relativa
al nodo padre.
Traslacin La traslacin absoluta se realiza mediante la llamada al mtodo setPosition,
La traslacin de un nodo que ya
que admite un Vector3 o tres argumentos de tipo Real. Esta traslacin se realiza de
ha sido posicionado anteriormen- forma relativa al nodo padre de la jerarqua. Para recuperar la traslacin relativa de un
te se realiza mediante la llamada a nodo con respecto a su nodo padre se puede emplear la llamada getPosition.
translate.
[318] CAPTULO 10. GRAFOS DE ESCENA
y y
y Root
x
z x x node1 node2
node2 z node1
z node3 ent1 ent2
y
node3 node4
ent3 ent4
x nod
a) e4
b) c)
Figura 10.6: Ejemplo de transformaciones empleando el Grafo de Escena de OGRE. a) Resultado de eje-
cucin del ejemplo. b) Representacin de los nodos asociados a la escena. c) Jerarqua del grafo de escena.
a radianes
Enellistado anterior se utilizan las funciones de utilidad para convertir
(lnea 7 ), as como algunas constantes matemticas (como en la lnea 8 ).
Posicin de Root Como se observa en la Figura 10.6, se han creado 4 nodos. La transformacin in-
cial aplicada al node1 es heredada por los nodos 3 y 4. De este modo, basta con aplicar
Aunque es posible aplicar transfor-
maciones al nodo Root, se desacon- una traslacin de 5 unidades en el eje x al nodo 3 para obtener el resultado mostrado
seja su uso. El nodo Root debe man- en la Figura 10.6.b). Esta traslacin es relativa al sistema de referencia transformado
tenerse esttico, como punto de re- del nodo padre. No ocurre lo mismo con el nodo 2, cuya posicin se especica de
ferencia del SRU. nuevo desde el origen del sistema de referencia universal (del nodo Root).
C10
10.2.3. Espacios de transformacin
Las transformaciones estudiadas anteriormente pueden denirse relativas a dife-
rentes espacios de transformacin. Muchos de los mtodos explicados en la seccin
anterior admiten un parmetro opcional de tipo TransformSpace que indica el es-
pacio de transformacin relativo de la operacin. OGRE dene tres espacios de trasn-
formacin como un tipo enumerado Node::TransformSpace:
a) b) y y
x
z
node1 x node2
y
x no d
e3
Figura 10.7: Ejemplo de trabajo con diferentes espacios de transformacin. a) Resultado de ejecucin del
ejemplo. b) Distribucin de los nodos del Grafo de Escena.
E
n este captulo se realizar un anlisis de los recursos que son necesarios en
los grcos 3D, centrndose sobre todo en los formatos de especicacin y
requisitos de almacenamiento. Adems, se analizarn diversos casos de estudio
de formatos populares como MD2, MD3, MD5, Collada y se har especial hincapi
en el formato de Ogre 3D.
11.1.1. Introduccin
En la actualidad, el desarrollo de videojuegos de ltima generacin implica la ma-
nipulacin simultnea de mltiples cheros de diversa naturaleza, como son imgenes
estticas (BMP (BitMaP), JPG, TGA (Truevision Graphics Adapter), PNG, ...), che-
ros de sonido (WAV (WAVeform), OGG, MP3 (MPEG-2 Audio Layer III)), mallas que
representan la geometra de los objetos virtuales, o secuencias de vdeo (AVI (Audio
Video Interleave), BIK (BINK Video), etc). Una cuestin relevante es cmo se cargan
estos cheros y qu recursos son necesarios para su reproduccin, sobre todo teniendo
en cuenta que la reproduccin debe realizarse en tiempo real, sin producir ningn tipo
de demora que acabara con la paciencia del usuario.
321
[322] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS
Figura 11.1: Flujo de datos desde los archivos de recursos hasta los subsistemas que se encargan de la
reproduccin de los contenidos [62].
11.1. Formatos de Especicacin [323]
Normalmente estos archivos estn asociados a un nivel del juego. En cada uno de
estos niveles se denen una serie de entornos, objetos, personajes, objetivos, eventos,
etc. Al cambiar de nivel, suele aparecer en la pantalla un mensaje de carga y el usuario
debe esperar; el sistema lo que est haciendo en realidad es cargar el contenido de
estos cheros que empaquetan diversos recursos.
Cada uno de los archivos empaquetados se debe convertir en un formato adecuado
que ocupe el menor nmero de recursos posible. Estas conversiones dependen en la
mayora de los casos de la plataforma hardware en la que se ejecuta el juego. Por
ejemplo, las plataformas PS3 y Xbox360 presentan formatos distintos para el sonido
y las texturas de los objetos 3D.
En la siguiente seccin se ver con mayor grado de detalle los recursos grcos 3D
que son necesarios, centrndonos en los formatos y los requisitos de almacenamiento.
C11
Los juegos actuales con al menos una complejidad media-alta suelen ocupar varios
GigaBytes de memoria. La mayora de estos juegos constan de un conjunto de cheros
cerrados con formato privado que encapsulan mltiples contenidos, los cuales estn
distribuidos en varios DVDs (4.7 GB por DVD) o en un simple Blue-Ray ( 25GB)
como es en el caso de los juegos vendidos para la plataforma de Sony - PS3. Si tene-
mos algn juego instalado en un PC, tenemos acceso a los directorios de instalacin y
podemos hacernos una idea del gran tamao que ocupan una vez que los juegos han
sido instalados.
Lo que vamos a intentar en las siguientes secciones es hacernos una idea de cmo
estos datos se almacenan, qu formatos utilizan y cmo se pueden comprimir los datos
para obtener el producto nal. Por tanto, lo primero que debemos distinguir son los
tipos de cheros de datos que normalmente se emplean. La siguiente clasicacin,
ofrece una visin general [62]:
En las siguientes secciones se describir con mayor detalle cada uno de los puntos
anteriores.
Objetos y Mallas 3D
Listado 11.1: Representacin de los vrtices de un cubo con respecto al origen de coordenadas
1 Vec3 TestObject::g_SquashedCubeVerts[] =
2 {
3 Vec3( 0.5,0.5,-0.25), // Vertex 0.
4 Vec3(-0.5,0.5,-0.25), // Vertex 1.
5 Vec3(-0.5,0.5,0.5), // And so on.
6 Vec3(0.75,0.5,0.5),
7 Vec3(0.75,-0.5,-0.5),
8 Vec3(-0.5,-0.5,-0.5),
9 Vec3(-0.5,-0.3,0.5),
10 Vec3(0.5,-0.3,0.5)
11 };
Con un simple cubo es complicado apreciar los benecios que se pueden obtener
con esta tcnica, pero si nos paramos a pensar que un modelo 3D puede tener miles
y miles de tringulos, la optimizacin es clara. De esta forma es posible almacenar n
tringulos con n+2 ndices, en lugar de n*3 vrtices como sucede en el primer caso.
11.1. Formatos de Especicacin [325]
Datos de Animacin
C11
Una animacin es en realidad la variacin de la posicin y la orientacin de los
vrtices que forman un objeto a lo largo del tiempo. Como se coment anteriormente,
una forma de representar una posicin o vrtice en el espacio es mediante tres va-
lores reales asociados a las tres coordenadas espaciales X, Y y Z. Estos nmeros se
representan siguiendo el estndar IEEE (Institute of Electrical and Electronics Engi-
neers)-754 para la representacin de nmeros reales en coma otante mediante 32 bits
(4 bytes). Por tanto, para representar una posicin 3D ser necesario emplear 12 bytes.
Adems de la posicin es necesario guardar informacin relativa a la orientacin,
y el tamao de las estructuras de datos empleadas para ello suele variar entre 12 y 16
bytes, dependiendo del motor de rendering. Existen diferentes formas de representar la
orientacin, el mtodo elegido inuir directamente en la cantidad de bytes necesarios.
Dos de los mtodos ms comunes en el desarrollo de videojuegos son los ngulos de
Euler y el uso de cuaterniones o tambin llamados cuaternios.
Para hacernos una idea del tamao que sera necesario en una sencilla animacin,
supongamos que la frecuencia de reproduccin de frames por segundo es de 25. Por
otro lado, si tenemos en cuenta que son necesarios 12 bytes por vrtice ms otros
12 (como mnimo) para almacenar la orientacin de cada vrtice, necesitaramos por
cada vrtice 12 + 12 = 24 bytes por cada frame. Si en un segundo se reproducen 25
frames, 25 x 24 = 600 bytes por cada vrtice y cada segundo. Ahora supongamos
que un objeto consta de 40 partes movibles (normalmente las partes movibles de un
personaje las determina el esqueleto y los huesos que lo forman). Si cada parte movible
necesita 600 bytes y existen 40 de ellas, se necesitara por cada segundo un total de
24.000 bytes.
Naturalmente 24.000 bytes puede ser un tamao excesivo para un solo segundo
y existen diferentes formas de optimizar el almacenamiento de los datos de anima-
cin, sobre todo teniendo en cuenta que no todas las partes del objeto se mueven en
cada momento y, por tanto, no sera necesario almacenarlas de nuevo. Tampoco es
necesario almacenar la posicin de cada parte movible en cada uno de los 25 frames
que se reproducen en un segundo. Una solucin elegante es establecer frames claves
y calcular las posiciones intermedias de un vrtice desde un frame clave al siguiente
mediante interpolacin lineal. Es decir, no es necesario almacenar todas las posiciones
(nicamente la de los frames claves) ya que el resto pueden ser calculadas en tiempo
de ejecucin.
[326] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS
Otra posible mejora se podra obtener empleando un menor nmero de bytes para
representar la posicin de un vrtice. En muchas ocasiones, el desplazamiento o el
cambio de orientacin no son exageradamente elevados y no es necesario emplear 12
bytes. Si los valores no son excesivamente elevados se pueden emplear, por ejemplo,
nmeros enteros de 2 bytes. Estas tcnicas de compresin pueden reducir considera-
blemente el tamao en memoria necesario para almacenar los datos de animacin.
Mapas/Datos de Nivel
Cada uno de los niveles que forman parte de un juego tiene asociado diferentes
elementos como objetos estticos 3D, sonido de ambientacin, dilogos, entornos,
etc. Normalmente, todos estos contenidos son empaquetados en un chero binario
que suele ser de formato propietario. Una vez que el juego es instalado en nuestro
equipo es complicado acceder a estos contenidos de manera individual. En cambio,
durante el proceso de desarrollo estos datos se suelen almacenar en otros formatos
que s son accesibles por los miembros del equipo como, por ejemplo, en formato
XML. El formato XML es un formato accesible, interpretable y permite a personas que
trabajan con diferentes herramientas y en diferentes mbitos, disponer de un medio
para intercambiar informacin de forma sencilla.
Texturas
32-bits (8888 RGBA (Red Green Blue Alpha)). Las imgenes se representan
mediante cuatro canales, R(red), G(green), B(blue), A(alpha), y por cada uno de
estos canales se emplean 8 bits. Es la forma menos compacta de representar un
mapa de bits y la que proporciona un mayor abanico de colores. Las imgenes
representadas de esta forma poseen una calidad alta, pero en muchas ocasiones
es innecesaria y otros formatos podran ser ms apropiados considerando que Figura 11.2: Ejemplo de una ima-
hay que ahorrar el mayor nmero de recursos posibles. gen RGBA con porciones transpa-
rentes (canal alpha).
11.1. Formatos de Especicacin [327]
24-bits (888 RGB (Red Green Blue)). Este formato es similar al anterior pero
sin canal alpha, de esta forma se ahorran 8 bits y los 24 restantes se dividen en
partes iguales para los canales R(red), G(green) y B(blue). Este formato se suele
emplear en imgenes de fondo que contienen una gran cantidad de colores que
no podran ser representados con 16 u 8 bits.
24-bits (565 RGB, 8A). Este formato busca un equilibrio entre los dos anterio-
res. Permite almacenar imgenes con una profundidad de color aceptable y un
rango amplio de colores y, adems, proporciona un canal alfa que permite in-
cluir porciones de imgenes traslcidas. El canal para el color verde tiene un bit
extra debido a que el ojo humano es ms sensible a los cambios con este color.
16-bits (565 RGB). Se trata de un formato compacto que permite almacenar
imgenes con diferentes variedades de colores sin canal alfa. El canal para el
color verde tambin emplea un bit extra al igual que en el formato anterior.
C11
lores rojo, verde y azul.
8-bits indexado. Este formato se suele emplear para representar iconos o im-
genes que no necesitan alta calidad, debido a que la paleta o el rango de colores
empleado no es demasiado amplio. Sin embargo, son imgenes muy compactas
que ahorran mucho espacio en memoria y se transmiten o procesan rpidamente.
En este caso, al emplearse 8 bits, la paleta sera de 256 colores y se represen-
ta de forma matricial, donde cada color tiene asociado un ndice. Precisamente
los ndices son los elementos que permiten asociar el color de cada pxel en la
imagen a los colores representados en la paleta de colores.
Figura 11.4: Paleta de 256 colores
con 8 bits
11.1.3. Casos de Estudio
Formato MD2/MD3
Figura 11.5: Captura del vdeojuego Quake II, desarrollado por la empresa id Software, donde se emple
por primera vez el formato MD2
10 int m_iNumTriangles;
11 int m_iNumGLCommands;
12 int m_iOffsetSkins;
13 int m_iOffsetTexCoords;
14 int m_iOffsetTriangles;
15 int m_iOffsetFrames;
16 int m_iOffsetGlCommands;
17 int m_iFileSize;
18 int m_iNumFrames;
19
20 };
El tamao total es de 68 bytes, debido a que cada uno de los enteros se repre-
senta haciendo uso de 4 bytes. A continuacin, en el siguiente listado se describir
brevemente el signicado de cada uno de estos campos [72].
C11
Figura 11.6: Jerarqua de capas en un chero con formato MD2
Las cinco variables siguientes se emplean para denir el offset o direccin relativa
(el nmero de posiciones de memoria que se suman a una direccin base para obtener
la direccin absoluta) en bytes de cada una de las partes del chero. Esta informacin
es fundamental para facilitar los saltos en las distintas partes o secciones del chero
durante el proceso de carga. Por ltimo, la variable m_iFileSize representa el tamao
en bytes desde el inicio de la cabecera hasta el nal del chero.
Frames y Vrtices
A continuacin se muestra la estructura que representa la informacin que se ma-
neja por frame en el formato MD2 [72]:
Como se puede apreciar en la estructura anterior, cada frame comienza con seis
nmeros representados en punto otante que representan la escala y traslacin de los
vrtices en los ejes X, Y y Z. Posteriormente, se dene una cadena de 16 caracteres
que determinan el nombre del frame, que puede resultar de gran utilidad si se quiere
hacer referencia a ste en algn momento. Por ltimo se indica la posicin de todos los
vrtices en el frame (necesario para animar el modelo 3D). En cada uno de los frames
habr tantos vrtices como se indique en la variable de la cabecera m_iNumVerts. En la
ltima parte de la estructura se puede diferenciar un constructor y destructor, que son
necesarios para reservar y liberar memoria una vez que se ha reproducido el frame, ya
que el nmero mximo de vrtices que permite almacenar el formato MD2 por frame
es 2048.
Si se renderizaran nicamente los vrtices de un objeto, en pantalla tan slo se
veran un conjunto de puntos distribuidos en el espacio. El siguiente paso consistir
en unir estos puntos para denir los tringulos y dar forma al modelo.
Triangularizacin
En el formato MD2 los tringulos se representan siguiendo la tcnica descrita en
la Seccin 11.1.2. Cada tringulo tiene tres vrtices y varios tringulos tienen ndices
en comn [72]. No es necesario representar un vrtice ms de una vez si se representa
la relacin que existe entre ellos mediante el uso de ndices en una matriz.
Adems, cada tringulo debe tener asociado una textura, o sera ms correcto de-
cir, una parte o fragmento de una imagen que representa una textura. Por tanto, es Figura 11.7: Modelado de un ob-
jeto tridimensional mediante el uso
necesario indicar de algn modo las coordenadas de la textura que corresponden con de tringulos.
cada tringulo. Para asociar las coordenadas de una textura a cada tringulo se em-
plean el mismo nmero de ndices, es decir, cada ndice de un vrtice en el tringulo
tiene asociado un ndice en el array de coordenadas de una textura. De esta forma se
puede texturizar cualquier objeto fcilmente.
11.1. Formatos de Especicacin [331]
Listado 11.5: Estructura de datos para representar los tringulos y las coordenadas de la tex-
tura
1 struct SMD2Tri {
2 unsigned short m_sVertIndices[3];
3 unsigned short m_sTexIndices[3];
4 };
Inclusin de Texturas
En el formato MD2 existen dos formas de asociar texturas a los objetos. La prime-
ra de ellas consiste en incluir el nombre de las texturas embebidos en el chero MD2.
El nmero de cheros de texturas y la localizacin, se encuentran en las variables de
la cabecera m_iNumSkins y m_iOffsetSkins. Cada nombre de textura ocupa 64 carac-
C11
teres alfanumricos como mximo. A continuacin se muestra la estructura de datos
empleada para denir una textura [72]:
Listado 11.6: Estructura de datos para denir una textura embebida en un chero MD2
1 struct SMD2Skin
2 {
3 char m_caSkin[64];
4 CImage m_Image;
5 };
Formato MD5
COLLADA
Ser un formato de cdigo libre, representado en XML para liberar los recursos
digitales de formatos binarios propietarios.
Proporcionar un formato estndar que pueda ser utilizado por una amplia ma-
yora de herramientas de edicin de modelos tridimensionales.
Intentar que sea adoptado por una amplia comunidad de usuarios de contenidos
digitales.
Proveer un mecanismo sencillo de integracin, tal que toda la informacin po-
sible se encuentre disponible en este formato.
Ser la base comn de todas las transferencias de datos entre aplicaciones 3D.
C11
COLLADA Core Elements. Donde se dene la geometra de los objetos, in-
formacin sobre la animacin, cmaras, luces, etc.
COLLADA Physics. En este apartado es posible asociar propiedades fsicas
a los objetos, con el objetivo de reproducir comportamientos lo ms realistas
posibles.
COLLADA FX. Se establecen las propiedades de los materiales asociados a
los objetos e informacin valiosa para el renderizado de la escena.
No es nuestro objetivo ver de forma detallada cada uno de estos tres bloques, pero
s se realizar una breve descripcin de los principales elementos del ncleo ya que
stos componen los elementos bsicos de una escena, los cuales son comunes en la
mayora de formatos.
COLLADA Core Elements
En este bloque se dene la escena (<scene>) donde transcurre una historia y los
objetos que participan en ella, mediante la denicin de la geometra y la animacin
de los mismos. Adems, se incluye informacin sobre las cmaras empleadas en la
escena y la iluminacin. Un documento con formato COLLADA slo puede contener
un nodo <scene>, por tanto, ser necesario elaborar varios documentos COLLADA
para denir escenas diferentes. La informacin de la escena se representa mediante un
grafo acclico y debe estar estructurado de la mejor manera posible para que el pro-
cesamiento sea ptimo. A continuacin se muestran los nodos/etiquetas relacionados
con la denicin de escenas [68].
Listado 11.10: Nodos utilizados para denir la geometra de un objeto en el formato COLLADA
1 <control_vertices>
2 <geometry>
3 <instance_geometry>
4 <library_geometries>
5 <lines>
6 <linestrips>
7 <mesh>
8 <polygons>
9 <polylist>
10 <spline>
11 <triangles>
12 <trifans>
Luces ambientales
Luces puntuales
C11
Luces direccionales
Puntos de luz
Por otro lado, COLLADA tambin permite la denicin de cmaras. Una cmara
declara una vista de la jerarqua del grafo de la escena y contiene informacin sobre la
ptica (perspectiva u ortogrca). Los nodos que se utilizan para denir una cmara
son los siguientes [68]:
7 </aspect_ratio>
8 <znear>1.0</znear>
9 <zfar>1000.0</zfar>
10 </perspective>
11 </technique_common>
12 </optics>
13 </camera>
Los tres elementos esenciales en el formato de Ogre son los siguientes [45]:
Para la asignacin de los huesos se indican los huesos y sus pesos. Como se puede
observar, el ndice utilizado para los huesos empieza por el 0.
C11
Listado 11.17: Asignacin de huesos a una malla en OgreXML
1 <boneassignments>
2 <vertexboneassignment vertexindex="0" boneindex="26" weight="
1.000000"/>
3 .............................
4 </boneassignments>
5 </submesh>
Por ltimo, si se han creado animaciones asociadas al esqueleto y han sido exporta-
das, se mostrar cada una de ellas identicndolas con el nombre denido previamente
en Blender y la longitud de la accin medida en segundos. Cada animacin contendr
informacin sobre los huesos que intervienen en el movimiento (traslacin, rotacin y
escalado) y el instante de tiempo en el que se ha denido el frame clave:
Otros Formatos
A continuacin se incluye una tabla con alguno de los formatos ms comunes para
la representacin de objetos 3D, su extensin, editores que lo utilizan y un enlace
donde se puede obtener ms informacin sobre el formato (ver Tabla 3.1) [72].
C11
DMO Duke Nukem 3D http://www.3drealms.com
DXF Autodesk Autocad http://www.autodesk.com
HRC Softimage 3D http://www.softimage.com
INC POV-RAY http://www.povray.org
KF2 Animaciones y poses en Max Payne http://www.maxpayne.com
KFS Max Payne: informacin de la ma- http://www.maxpayne.com
lla y los materiales
LWO Lightwave http://www.newtek.com
MB Maya http://www.aliaswavefront.com
MAX 3D Studio Max http://www.discreet.com
MS3D Milkshape 3D http://www.swissquake.ch
OBJ Alias|Wavefront http://aliaswavefront.com
PZ3 Poser http://www.curioslab.com
RAW Tringulos RAW http://
RDS Ray Dream Studio http://www.metacreations.com
RIB Renderman File http://www.renderman.com
VRLM Lenguaje para el modelado de reali- http://www.web3d.org
dad virtual
X Formato Microsoft Direct X http://www.microsoft.com
XGL Formato que utilizan varios progra- http://www.xglspec.com
mas CAD
Cada uno de los recuadros corresponde a la textura que se aplicar a una de las
caras del cubo en el eje indicado. En la Figura 11.11 se muestra la textura real que se
aplicar al modelo; tal como se puede apreciar est organizada de la misma forma que
se presenta en la Figura 11.10 (cargar la imagen llamada textura512.jpg que se aporta
con el material del curso). Todas las texturas se encuentran en una misma imagen
cuya resolucin es 512x512. Los mdulos de memoria de las tarjetas grcas estn
11.2. Exportacin y Adaptacin de Contenidos [341]
C11
Figura 11.11: Imagen con resolucin 512x512 que contiene las texturas que sern aplicadas a un cubo
Una vez conocida la textura a aplicar sobre el cubo y cmo est estructurada, se
describirn los pasos necesarios para aplicar las texturas en las caras del cubo mediante
la tcnica UV Mapping. En primer lugar ejecutamos Blender y dividimos la pantalla
en dos partes. Para ello situamos el puntero del ratn sobre el marco superior hasta
que el cursor cambie de forma a una echa doblemente punteada, pulsamos el botn
derecho y elegimos en el men otante la opcin Split Area.
En el marco de la derecha pulsamos el men desplegable situado en la esquina
inferior izquierda y elegimos la opcin UV/Image Editor. A continuacin cargamos la
imagen de la textura (textura512.jpg); para ello pulsamos en el men Image >Open
y elegimos la imagen que contiene las texturas. Despus de realizar estos datos la
apariencia del editor de Blender debe ser similar a la que aparece en la Figura 11.12
En el marco de la izquierda se pueden visualizar las lneas que delimitan el cubo,
coloreadas de color rosa, donde el modo de vista por defecto es Wireframe. Para vi-
sualizar por pantalla las texturas en todo momento elegimos el modo de vista Textured
en el men situado en el marco
inferior
Viewport Shading. Otra posibilidad consiste
en pulsar las teclas SHIFT + Z .
[342] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS
Figura 11.12: Editor de Blender con una divisin en dos marcos. En la parte izquierda aparece la gu-
ra geomtrica a texturizar y en la parte derecha las texturas que sern aplicadas mediante la tcnica de
UV/Mapping
Por otro lado, el cubo situado en la escena tiene asociado un material por defecto,
pero ste no tiene asignada ninguna textura (en el caso de que no tenga un material
asociado, ser necesario crear uno). Para ello, nos dirigimos al men de materiales
situado en la barra de herramientas de la derecha (ver Figura 11.13).
Pulsamos A para que ninguno de los vrtices quede seleccionado y, de forma
manual, seleccionamos los vrtices de la cara superior (z+). Para seleccionar los vrti-
existen
ces dos alternativas; la primera es seleccionar uno a uno manteniendo la tecla
SHIFT pulsada y presionar el botn derecho del ratn en cada uno de los vrtices.
Una segunda opcin es dibujar un rea que encuadre los vrtices, para ello hay que
pulsar primero la tecla B (Pulsa el botn intermedio del ratn y muvelo para cambiar
la perspectiva y poder asegurar as que se han elegido los vrtices correctos).
En la parte derecha, en el editor UV se puede observar un cuadrado con los bordes
de color amarillo y la supercie morada. Este recuadro corresponde con la cara del
cubo seleccionada y con el que se pueden establecer correspondencias con coordena-
das de la textura. El siguiente paso consistir en adaptar la supercie del recuadro a la
imagen que debe aparecer en la parte superior del cubo (z+), tal como indica la Figu-
ra 11.10. Para adaptar la supercie
una de ellas es escalar
existen varias alternativas;
el cuadrado con la tecla S y para desplazarlo la tecla G (para hacer zoom sobre la
textura se gira la rueda central del ratn). Un segunda opcin es ajustar cada vrtice
para ello se selecciona un vrtice con el botn derecho del ra-
de forma individual,
tn, pulsamos G y desplazamos el vrtice a la posicin deseada. Las dos alternativas
C11
descritas anteriormente no son excluyentes y se pueden combinar, es decir, se podra
escalar el recuadro en primer lugar y luego ajustar los vrtices, e incluso escalar de
nuevo si fuera necesario.
Los pasos que se han realizo para establecer la textura de la parte superior del cubo
deben ser repetidos para el resto de caras del cubo. Si renderizamos la escena pulsando
F12 podemos apreciar el resultado nal.
Por ltimo vamos a empaquetar el archivo .blend para que sea autocontenido. La
textura es un recurso externo; si cargramos el chero .blend en otro ordenador posi-
blemente no se visualizara debido a que las rutas no coinciden. Si empaquetamos el
chero eliminamos este tipo de problemas. Para ello, seleccionamos File >External
Data >Pack into .blend le.
Directorios:
C11
Include
Figura 11.16: Sistema de coorde-
nadas utilizado en Ogre. Media
Obj
Plugins
src
Ficheros en el directorio raz del proyecto Ogre:
ogre.cfg
plugins.cfg
resources.cfg
Figura 11.17: Sistema de coorde- Una vez que se ha exportado el cubo con las texturas asociadas y se han gene-
nadas utilizado en Blender. rado los cheros Cube.mesh, Cube.mesh.xml y Scene.material, los incluimos en el
directorio media (tambin incluimos la textura "textura512.jpg"). Los cheros deben
incluirse en este directorio porque as est congurado en el chero resources.cfg, si
deseramos incluir los recursos en otro directorio deberamos variar la conguracin
en dicho chero.
En segundo lugar, cambiamos el nombre del ejecutable; para ello, abrimos el ar-
chivo makele y en lugar de EXEC := helloWorld, ponemos EXEC:= cubo. Despus
es necesario cambiar el cdigo de ejemplo para que cargue nuestro modelo. Abrimos
el archivo /src/main.cpp que se encuentra dentro en el directorio /src. El cdigo es el
siguiente:
En nuestro caso la entidad a crear es aquella cuya malla se llama Cube.Mesh, sta
ya contiene informacin sobre la geometra de la malla y las coordenadas UV en el
caso de utilizacin de texturas.
Por lo tanto el cdigo tras la modicacin de la funcin createScene, sera el si-
guiente:
Listado 11.22: Modicacin del chero main.cpp para cargar el cubo modelado en Blender
1 public : void createScene() {
2 Ogre::Entity *ent = mSceneMgr->createEntity("Caja", "cubo.mesh"
);
3 mSceneMgr->getRootSceneNode()->attachObject(ent);
4 }
Al compilar (make) y ejecutar (./Cubo) aceptamos las opciones que vienen por
defecto y se puede observar el cubo pero muy lejano. A partir de ahora todas las
operaciones que queramos hacer (translacin, escalado, rotacin, ...) tendr que ser
a travs de cdigo. A continuacin se realiza una operacin de traslacin para acercar
el cubo a la cmara:
C11
este sistema local. posicin.
Con el objeto seleccionado en Modo de Objeto se puede indicar a Blender que
recalcule el centro geomtrico
del objeto en el panel de Object Tools (accesible con
la tecla T ), en el botn Origin (ver Figura 11.20). Una vez pulsado Origin, podemos
elegir entre Geometry to Origin, que desplaza los vrtices del objeto de modo que
se ajustan a la posicin jada del centro geomtrico, Origin to Geometry que realiza
la misma operacin de alineacin, pero desplazando el centro en lugar de mover los
vrtices, o Origin to 3D Cursor que cambia la posicin del centro del objeto a la
localizacin actual del cursor 3D.
En muchas ocasiones, es necesario situar el centro y los vrtices de un modelo
con absoluta precisin. Para realizar esta operacin, basta con pulsar la tecla N , y
Figura 11.20: Botn Origin para aparecer el panel de Transformacin Transform a la derecha de la vista 3D (ver Fi-
elegir el centro del objeto, en el pa- gura 11.21), en el que podremos especicar las coordenadas especcas del centro
de
nel Object Tools. del objeto. Si estamos en modo edicin, podremos indicar las coordenadas a nivel
vrtice. Es interesante que esas coordenadas se especiquen localmente (botn Local
activado), porque el exportador de Ogre trabajar con coordenadas locales relativas a
ese centro.
El centro del objeto puede igualmente situarse de forma precisa, empleando la po-
sicin del puntero 3D. El puntero 3D puedes situarse en cualquier posicin del espacio
con precisin. En el panel Transform comentado anteriormente (accesible mediante la
tecla T ) pueden especicarse numricamente
las coordenadas 3D (ver Figura 11.23).
Posteriormente, utilizando el botn Origin (ver Figura 11.20), y eligiendo la opcin
Figura 11.21: Propiedades del pa-
Origin to 3D Cursor podremos situar el centro del objeto en la posicin del puntero
nel Transform en modo edicin (te- 3D.
cla T). Permiten indicar las coorde- Es muy importante que las transformaciones de modelado se apliquen nalmente
nadas de cada vrtice con respecto
a las coordenadas locales del objeto, para que se apliquen de forma efectiva a las
de los vrtices. Es decir, si despus de trabajar con el modelo, pulsando
del Sistema de Referencia Local y
coordenadas
la tecla N en modo Objeto, el valor Scale es distinto de 1 en alguno de los ejes,
del Sistema de Referencia Univer-
sal (Global).
implica que esa transformacin se ha realizado en modo objeto, por lo que los vrtices
del mismo no tendrn esas coordenadas asociadas. De igual modo, tambin hay que
prestar atencin a la rotacin del objeto (estudiaremos cmo resolver este caso ms
adelante).
La Figura 11.22 muestra un ejemplo de este problema; el cubo de la izquierda se
ha escalado en modo edicin, mientras que el de la derecha se ha escalado en modo
objeto. El modelo de la izquierda se exportar correctamente, porque los vrtices tie-
Figura 11.23: Posicionamiento nu- nen asociadas sus coordenadas locales. El modelo de la derecha sin embargo aplica
mrico del cursor 3D. una transformacin a nivel de objeto, y ser exportado como un cubo.
[348] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS
b)
a) c) d)
Figura 11.22: Aplicacin de transformaciones geomtricas en modo Edicin. a) Situacin inicial del mo-
delo al que le aplicaremos un escalado de 0.5 respecto del eje Z. b) El escalado se aplica en modo Edicin.
El campo Vertex Z muestra el valor correcto. c) Si el escalado se aplic en modo objeto, el campo Vertex Z
no reeja la geometra real del objeto. Esto puede comprobarse fcilmente si alguno de los campos Scale
no son igual a uno, como en d).
Otra operacin muy interesante consiste en posicionar un objeto con total preci-
sin. Para realizar esta operacin puede ser suciente con situarlo numricamente,
comohemos
visto anteriormente, accediendo al panel de transformacin mediante la
tecla N . Sin embargo, otras veces necesitamos situarlo en una posicin relativa a otro
objeto; necesitaremos situarlo empleando el cursor 3D.
El centro de un objeto puede situarse en la posicin del puntero 3D, y el puntero
3D puede situarse en cualquier posicin del espacio. Podemos, por ejemplo, en modo
edicin situar el puntero 3D en la posicin de un vrtice de un modelo, y situar ah
el centro del objeto. Desde ese momento, los desplazamientos del modelo se harn
tomando como referencia ese punto.
Mediante el atajo Shift S Cursor to Selected podemos situar el puntero 3D en la
posicin de un elemento seleccionado (por ejemplo, un vrtice, oen elcentro
de otro
objeto que haya sido seleccionado en modo objeto) y mediante Shift S Selection to
Cursor moveremos el objeto seleccionado a la posicin del puntero 3D (ver Figura
11.24). Mediante este sencillo mecanismo de 2 pasos podemos situar los objetos con
precisin, y modicar el centro del objeto a cualquier punto de inters.
a) b) c) d)
C11
e) f)
Figura 11.24: Ejemplo de uso del operador Cursor to Selected y Selection to Cursor. a) Posicin inicial de
los dos modelos. Queremos llegar a la posicin f). En modo edicin, elegimos un vrtice superior del cubo
y posicionamos el puntero 3D ah (empleando Shift S Cursor to Selected). c) A continuacin modicamos
el valor de X e Y de la posicin del cursor numricamente, para que se site en el centro de la cara. d)
Elegimos Origin to 3D Cursor entre las opciones disponibles en el botn Origin del panel Object Tools.
Ahora el cubo tiene su centro en el punto medio de la cara superior. e) Elegimos el vrtice sealado en la
cabeza del mono y posicionamos ah el puntero 3D. Finalmente en f) cambiamos la posicin del cubo de
forma que interseca exactamente en ese vrtice (Shift S/ Selection to Cursor).
El problema viene asociado a que el modelo ha sido construido empleando trans-
formaciones a nivel de objeto. Si accedemos a las propiedades de transformacin N
para cada objeto, obtenemos la informacin mostrada en la Figura 11.26. Como ve-
mos, la escala a nivel de objeto de ambos modelos es distinta de 1.0 para alguno de
los ejes, lo que indica que la transformacin se realiz a nivel de objeto.
Aplicaremos la escala (y la rotacin, aunque en este caso el objeto no fue rotado)
realizada
a los vrtices del modelo, eliminando cualquier operacin en modo objeto.
Para ello, con cada objeto seleccionado, pulsaremos Control A Apply / Rotation &
Scale. Ahora la escala (ver Figura 11.26) debe mostrar un valor de 1.0 en cada eje.
Como hemos visto, la eleccin correcta del centro del objeto facilita el cdigo en
etapas posteriores. Si el modelo est normalizado (con escala 1.0 en todos los ejes),
las coordenadas del espacio 3D de Blender pueden ser fcilmente transformadas a
coordenadas de Ogre. En este caso, vamos a situar el centro de cada objeto de la
exactamente
escena de forma que est situado en el Z = 0. Para ello, con cada objeto
seleccionado, pulsaremos en Origin Origin to 3D Cursor (del panel Object Tools).
Figura 11.26: Propiedades de Ahora posicionaremos el cursor 3D en esa posicin Shift S Cursor to Selected
del cursor 3D, para que se site en
transformacin de los dos objetos y modicaremos numricamente la coordenada
del ejemplo.
Z = 0 (accediendo al panel de Propiedades N ). El resultado de posicionar el cursor
3D se muestra en la Figura 11.27.
[350] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS
Figura 11.27: Posicionamiento del puntero 3D en relacin al centro del objeto. En muchas situaciones
puede ser conveniente especicar que varios objetos tengan el centro en una misma posicin del espacio
(como veremos en la siguiente seccin). Esto puede facilitar la construccin de la aplicacin.
C11
Obviamente, el proceso de exporta- Listado 11.27: Fragmento de MyApp.c
cin de los datos relativos al posi- 1 Ogre::Entity* ent1 = _sceneManager->createEntity("MS.mesh");
cionamiento de objetos en la esce- 2 Ogre::SceneNode* node1 = _sceneManager->createSceneNode("MS");
na (junto con otros datos de inte- 3 node1->attachObject(ent1);
rs) debern ser automatizados de- 4 _sceneManager->getRootSceneNode()->addChild(node1);
niendo un formato de escena pa- 5
ra Ogre. Este formato tendr la in- 6 Ogre::Entity* ent2 = _sceneManager->createEntity("Mando.mesh");
formacin necesaria para cada jue- 7 Ogre::SceneNode* node2 = _sceneManager->createSceneNode("Mando");
go particular. 8 node2->attachObject(ent2);
9 node2->translate(-1.8,0,2.8);
10 node1->addChild(node2);
Por ltimo, sera deseable poder exportar la posicin de las cmara para percibir
la escena con la misma conguracin que en Blender. Existen varias formas de con-
gurar el camera pose. Una de las ms cmodas para el diseador es trabajar con la
posicin de la cmara y el punto al que sta mira. Esta especicacin es equivalente a
yel punto look at (en el primer fragmento de cdigo de la seccin,
indicar la posicin
en las lneas 4 y 5 .
Para que la cmara apunte hacia un objeto de la escena, puede crearse una restric-
cin de tipo Track To. Para ello,
aadimos un objeto vaco a la escena (que no tiene
Shift A Add / Empty. Ahora seleccionamos primero la c-
representacin), mediante
mara, y luego con Shift pulsado seleccionamos el Empty y pulsamos Control T Track
To Constraint. De este modo, si desplazamos la cmara, obtendremos que siempre
est mirando hacia el objeto Empty creado (ver Figura 11.29).
Cuando tengamos la fotografa de la escena montada, bastar con consultar el
valor de posicin de la cmara y el Empty, y asignar esos valores al cdigo de posi-
cionamiento y look at de la misma (teniendo en cuenta las diferencias entre sistemas
de coordenadas de Blender y Ogre). El resultado se encuentra resumido en la Figura
11.30.
cam->setPosition (Vector3(5.7,6.3,7.1));
cam->lookAt (Vector3(0.9,0.24,1.1));
cam->setFOVy (Ogre::Degree(52));
Figura 11.30: Resultado de aplicar la misma cmara en Blender y Ogre. El campo de visin de la lente
puede consultarse en las propiedades especcas de la cmara, indicando que el valor quiere representarse
en grados (ver lista desplegable de la derecha)
C11
Figura 11.33: Ejemplo de aplicacin que desarrollaremos en esta seccin. El interfaz permite seleccionar
objetos en el espacio 3D y modicar algunas de sus propiedades (escala y rotacin).
El desplazamiento de la rueda del ratn se obtiene en la coordenada Z con getMou-
seState (lnea 5 ). Utilizamos esta informacin para desplazar la cmara relativamente
en su eje local Z.
11.4.4. Queries
El gestor de escena permite resolver preguntas relativas a la relacin espacial entre
los objetos de la escena. Estas preguntas pueden planterase relativas a los objetos
mviles MovableObject y a la geometra del mundo WorldFragment.
Ogre no gestiona las preguntas relativas a la geometra esttica. Una posible so-
lucin pasa por crear objetos mviles invisibles de baja poligonalizacin que servirn
para realizar estas preguntas. Estos objetos estn exportados conservando las mismas
dimensiones y rotaciones que el bloque de geometra esttica (ver Figura 11.35). Ade-
ms, para evitar tener que cargar manualmente los centros de cada objeto, todos los
bloques han redenido su centro en el origen del SRU (que coincide con el centro de
la geometra esttica). El siguiente listado muestra la carga de estos elementos en el
grafo de escena.
C11
23 sauxnode.str(""); sauxmesh.str(""); // Limpiamos el stream
24 }
Cabe destacar el establecimiento de ags propios (en las lneas 5 y 19 ) que
estudiaremos ascomo la propiedad de visibilidad del nodo (en
en la seccin 11.4.5,
7 y 21 ). El bucle denido en 13-23 sirve para cargar las cajas lmite mostradas en
la Figura 11.35.
Las preguntas que pueden realizarse al gestor de escena se dividen en cuatro cate-
goras principales: 1. Caja lmite, denida mediante dos vrtices opuestos, 2. Esfera,
denida por una posicin y un radio, 3. Volumen, denido por un conjunto de tres o
ms planos y 4. Rayo, denido por un punto (origen) y una direccin.
Otras intersecciones En este ejemplo utilizaremos las intersecciones Rayo-Plano. Una vez creado el
Ogre soporta igualmente realizar
objeto de tipo RaySceneQuery en el constructor (mediante una llamada a createRay-
consultas sobre cualquier tipo de in- Query del SceneManager), que posteriormente ser eliminado en el destructor (me-
tersecciones arbitrarias entre obje- diante una llamada a destroyQuery del SceneManager), podemos utilizar el objeto
tos de la escena. para realizar consultas a la escena.
En la lnea 20 se utiliza un mtodo auxiliar para crear la Query, indicando las
coordenadas del ratn. Empleando la funcin de utilidad de la lnea 2 , Ogre crea un
rayo con origen en la cmara y la direccin indicada por las dos coordendas X, Y
normalizadas (entre 0.0 y 1.0) que recibe como argumento (ver Figura 11.36). En la
lnea 4 se establece ese rayo para la consulta y se indica en la lnea 5 que ordene los
resultados por distancia de interseccin. La consulta de tipo rayo devuelve una lista
con todos los objetos que han intersecado con el rayo1 . Ordenando el resultado por
distancia, tendremos como primer elemento el primer objeto con el que ha chocado el
rayo. La ejecucin de la consulta se realiza en la lnea 21 .
Para recuperar los datos de la consulta, se emplea un iterador. En el caso de esta
aplicacin, slo nos interesa el primer elemento devuelto por la consulta (el primer
Primera
rayMouse
punto de interseccin), por lo que en el if de la lnea 25 preguntamos si hay algn
inter- elemento devuelto por la consulta.
seccin origen
1 El rayo se describe por un punto de origen y una direccin. Desde ese origen, y siguiendo esa direccin,
el rayo describe una lnea innita que podr intersecar con innitos objetos
Figura 11.36: Empleando la llama-
da a getCameraToViewportRay, el
usuario puede obtener un rayo con
origen en la posicin de la cma-
ra, y con la direccin denida por
la posicin del ratn.
[356] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS
ox4
Col_B Col_
Box5
Col_ Col_
Box2 Box3
Box1 Col_
o
Suel
Col_
Figura 11.35: A la izquierda la geometra utilizada para las preguntas al gestor de escena. A la derecha la
geometra esttica. El centro de todas las entidades de la izquierda coinciden con el centro del modelo de
la derecha. Aunque en la imagen los modelos estn desplazados con respecto del eje X, en realidad ambos
estn exactamente en la misma posicin del mundo.
C11
16 if (_selectedNode != NULL) { // Si hay alguno seleccionado...
17 _selectedNode->showBoundingBox(false); _selectedNode = NULL;
18 }
19
20 Ray r = setRayQuery(posx, posy, mask);
21 RaySceneQueryResult &result = _raySceneQuery->execute();
22 RaySceneQueryResult::iterator it;
23 it = result.begin();
24
25 if (it != result.end()) {
26 if (mbleft) {
27 if (it->movable->getParentSceneNode()->getName() ==
28 "Col_Suelo") {
29 SceneNode *nodeaux = _sceneManager->createSceneNode();
30 int i = rand() %2; stringstream saux;
31 saux << "Cube" << i+1 << ".mesh";
32 Entity *entaux=_sceneManager->createEntity(saux.str());
33 entaux->setQueryFlags(i?CUBE1:CUBE2);
34 nodeaux->attachObject(entaux);
35 nodeaux->translate(r.getPoint(it->distance));
36 _sceneManager->getRootSceneNode()->addChild(nodeaux);
37 }
38 }
39 _selectedNode = it->movable->getParentSceneNode();
40 _selectedNode->showBoundingBox(true);
41 }
42 }
11.4.5. Mscaras
Las mscaras permiten restringir el mbito de las consultas realizadas al gestor de
13 y 14 especicamos la mscara binaria
escena. En el listado anterior, en las lneas
que utilizaremos en la consulta (lnea 6 ). Para que una mscara sea vlida (y permita
realizar operaciones lgicas con ella), debe contener un nico uno.
As, la forma ms sencilla de denirlas es desplazando el 1 tantas posiciones como
se indica tras el operador << como se indica en la Figura 11.38. Como el campo
de mscara es de 32 bits, podemos denir 32 mscaras personalizadas para nuestra
aplicacin.
[358] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS
la llamada a setQueryFlags
En el listado de la seccin 11.4.4, vimos que mediante
se podan asociar mscaras a entidades (lneas 5 y 19 ). Si no se especica ninguna
mscara en la creacin de la entidad, sta responder a todas las Queries, planteando
as un esquema bastante exible.
Los operadores lgicos pueden emplearse para combinar varias
mscaras, como el
uso del AND &&, el OR || o la negacin (ver lneas 13 y 14 del pasado cdigo
fuente). En este cdigo fuente, la consulta a STAGE sera equivalente a consultar por
CUBE1 | CUBE2. La consulta de la lnea 13 sera equivalente a preguntar por todos
los objetos de la escena (no especicar ninguna mscara).
E
n este captulo se introducirn los aspectos ms relevantes de OpenGL. Como
hemos sealado en el captulo de introudccin, muchas bibliotecas de visuali-
zacin (como OGRE) nos abstraen de los detalles de bajo nivel. Sin embargo,
resulta interesante conocer el modelo de estados de este ipo de APIs, por compartir
multitud de convenios con las bibliotecas de alto nivel. Entre otros aspectos, en este
captulo se estudiar la gestin de pilas de matrices y los diferentes modos de trans-
formacin de la API.
12.1. Introduccin
Actualmente existen en el mercado dos alternativas principales como bibliotecas
de representacin 3D a bajo nivel: Direct3D y OpenGL. Estas dos bibliotecas son so-
portadas por la mayora de los dispositivos hardware de aceleracin grca. Direct3D
forma parte del framework DirectX de Microsoft. Es una biblioteca ampliamente ex-
tendida, y multitud de motores 3D comerciales estn basados en ella. La principal
desventaja del uso de DirectX es la asociacin exclusiva con el sistema operativo Mi-
crosoft Windows. Aunque existen formas de ejecutar un programa compilado para esta
plataforma en otros sistemas, no es posible crear aplicaciones nativas utilizando Di-
rectX en otros entornos. De este modo, en este primer estudio de las APIs de bajo nivel
nos centraremos en la API multiplataforma OpenGL.
OpenGL es, probablemente, la biblioteca de programacin grca ms utilizada
del mundo; desde videojuegos, simulacin, CAD, visualizacin cientca, y un largo
etectera de mbitos de aplicacin la conguran como la mejor alternativa en multitud
de ocasiones. En esta seccin se resumirn los aspectos bsicos ms relevantes para
comenzar a utilizar OpenGL.
359
[360] CAPTULO 12. APIS DE GRFICOS 3D
En este breve resumen de OpenGL se dejarn gran cantidad de aspectos sin men-
cionar. Se recomienda el estudio de la gua ocial de OpenGL [86] (tambin conocido
como El Libro Rojo de OpenGL para profundizar en esta potente biblioteca grca.
Otra fantstica fuente de informacin, con ediciones anteriores del Libro Rojo es la
pgina ocial de la biblioteca1 .
El propio nombre OpenGL indica que es una Biblioteca para Grcos Abierta2 .
Una de las caractersticas que ha hecho de OpenGL una biblioteca tan famosa es que es
independiente de la plataforma sobre la que se est ejecutando (en trminos software
y hardware). Esto implica que alguien tendr que encargarse de abrir una ventana
grca sobre la que OpenGL pueda dibujar los preciosos grcos 3D.
Para facilitar el desarrollo de aplicaciones con OpenGL sin preocuparse de los
detalles especcos de cada sistema operativo (tales como la creacin de ventanas,
gestin de eventos, etc...) M. Kilgard cre GLUT, una biblioteca independiente de
OpenGL (no forma parte de la distribucin ocial de la API) que facilita el desarro-
llo de pequeos prototipos. Esta biblioteca auxiliar es igualmente multiplataforma. En
este captulo trabajaremos con FreeGLUT, la alternativa con licencia GPL totalmente
compatible con GLUT. Si se desea mayor control sobre los eventos, y la posibilidad
de extender las capacidades de la aplicacin, se recomienda el estudio de otras APIs Programa de Usuario
multiplataforma compatibles con OpenGL, como por ejemplo SDL3 o la que emplea-
remos con OGRE (OIS). Existen alternativas especcas para sistemas de ventanas
GLUT (FreeGLUT)
GLX, AGL, WGL...
WGL para sistemas Windows.
Widget Opengl
De este modo, el ncleo principal de las biblioteca se encuentra en el mdulo GL
(ver Figura 12.2). En el mdulo GLU (OpenGL Utility) se encuentran funciones de
uso comn para el dibujo de diversos tipos de supercies (esferas, conos, cilindros,
curvas...). Este mdulo es parte ocial de la biblioteca.
Uno de los objetivos principales de OpenGL es la representacin de imgenes de
alta calidad a alta velocidad. OpenGL est diseado para la realizacin de aplicacio-
nes interactivas, como videojuegos. Gran cantidad de plataformas actuales se basan GLU
en esta especicacin para denir sus interfaces de dibujado en grcos 3D.
GL
Otras Bibliotecas
12.2. Modelo Conceptual Software
glVertex4dv (v);
C12
bujado de elementos.
Cambio de Estado La operacin de Cambiar el Estado se encarga de inicializar las variables internas
de OpenGL que denen cmo se dibujarn las primitivas. Este cambio de estado puede
A modo de curiosidad: OpenGL
cuenta con ms de 400 llamadas a ser de mtiples tipos; desde cambiar el color de los vrtices de la primitiva, establecer
funcin que tienen que ver con el la posicin de las luces, etc. Por ejemplo, cuando queremos dibujar el vrtice de un
cambio del estado interno de la bi- polgono de color rojo, primero cambiamos el color del vrtice con glColor() y
blioteca. despus dibujamos la primitiva en ese nuevo estado con glVertex().
Algunas de las formas ms utilizadas para cambiar el estado de OpenGL son:
entre 0 y 1. Las primeras tres componentes se corresponden con los canales RGB, y la cuarta es el valor de
transparencia Alpha.
[362] CAPTULO 12. APIS DE GRFICOS 3D
-Y
12.3.1. Transformacin de Visualizacin
C12
Figura 12.6: Descripcin del siste- La transformacin de Visualizacin debe especicarse antes que ninguna otra
ma de coordenadas de la cmara de- transformacin de Modelado. Esto es debido a que OpenGL aplica las transforma-
nida en OpenGL.
ciones en orden inverso. De este modo, aplicando en el cdigo las transformaciones
de Visualizacin antes que las de Modelado nos aseguramos que ocurrirn despus
que las de Modelado.
Para comenzar con la denicin de la Transformacin de Visualizacin, es nece-
sario limpiar la matriz de trabajo actual. OpenGL cuenta con una funcin que carga la
matriz identidad como matriz actual glLoadIdentity().
Una vez hecho esto, podemos posicionar la cmara virtual de varias formas:
Matriz Identidad
La Matriz Identidad es una matriz 1. glulookat. La primera opcin, y la ms comunmente utilizada es mediante la
4x4 con valor 1 en la diagonal prin-
cipal, y 0 en el resto de elementos. funcin gluLookAt. Esta funcin recibe como parmetros un punto (eye) y dos
La multiplicacin de esta matriz I vectores (center y up). El punto eye es el punto donde se encuentra la cmara
por una matriz M cualquiera siem- en el espacio y mediante los vectores libres center y up orientamos hacia dnde
pre obtiene como resultado M . Esto mira la cmara (ver Figura 12.7).
es necesario ya que OpenGL siem-
pre multiplica las matrices que se le 2. Traslacin y Rotacin. Otra opcin es especicar manualmente una secuencia
indican para modicar su estado in-
terno. de traslaciones y rotaciones para posicionar la cmara (mediante las funciones
glTraslate() y glRotate(), que sern estudiadas ms adelante.
3. Carga de Matriz. La ltima opcin, que es la utilizada en aplicaciones de
Realidad Aumentada, es la carga de la matriz de Visualizacin calculada exter-
namente. Esta opcin se emplea habitualmente cuando el programador quiere
calcular la posicin de la cmara virtual empleando mtodos externos (como
por ejemplo, mediante una biblioteca de tracking para aplicaciones de Realidad
Aumentada).
center up
(cx,cy,cz) (ux,uy,uz)
12.3.2. Transformacin de Modelado
Las transformaciones de modelado nos permiten modicar los objetos de la es-
cena. Existen tres operaciones bsicas de modelado que implementa OpenGL con
eye llamadas a funciones. No obstante, puede especicarse cualquier operacin aplicando
(ex,ey,ez) una matriz denida por el usuario.
Figura 12.7: Parmetros de la fun- Traslacin. El objeto se mueve a lo largo de un vector. Esta operacin se realiza
cin glulookat. mediante la llamada a glTranslate(x,y,z).
[364] CAPTULO 12. APIS DE GRFICOS 3D
Posicin
final
Z Z Z
X X X
Posicin
inicial
b) Visto como Sistema de Referencia Local Y' X'
Sis
Re tema d Y Y Y
Unifverenciae Z'
ersal
Z
Y' X'
X Y'
C12
decidir el orden de las transfor- Z Z Z
maciones en OpenGL: SRU o SRL.
X X X
El cdigo anterior dibuja el objeto en la posicin deseada, pero cmo hemos
llegado a ese cdigo y no hemos intercambiado las instrucciones de las lneas 3 y
4 ?. Existen dos formas de imaginarnos cmo se realiza el dibujado que nos puede
ayudar a plantear el cdigo fuente. Ambas formas son nicamente aproximaciones
conceptuales, ya que el resultado en cdigo debe ser exactamente el mismo.
Como puede verse en el listado anterior, se ha incluido una variable global hours
que se incrementa cada vez que se llama a la funcin display. En este ejemplo, esta
funcin se llama cada vez que se pulsa cualquier tecla. Esa variable modela el paso
de
de las horas, de forma que la traslacin y rotacin la Tierra se calcular a partir del
nmero de horas que han pasado (lneas 11 y 12 ). En la simulacin se ha acelerado
10 veces el movimiento de traslacin para que se vea ms claramente.
Empleando la Idea de Sistema de Referencia Local podemos
pensar que despla-
zamos el sistema de referencia para dibujar el sol. En la lnea 15 , para dibujar una
esfera almbrica especicamos como primer argumento el radio, y a continuacin el
nmero de rebanadas (horizontales y verticales) en que se dibujar la esfera.
En ese punto dibujamos la primera esfera correspondiente al Sol. Hecho esto, ro-
(lnea 16 )
C12
tamos los grados correspondientes al movimiento de traslacin de la tierra
y nos desplazamos 3 unidades respecto del eje X local del objeto (lnea 17 ). Antes de
la Tierra tendremos que realizar el movimiento
dibujar de rotacin de la tierra (lnea
18 ). Finalmente dibujamos la esfera en la lnea 20 .
Veamos ahora un segundo ejemplo que utiliza glPushMatrix() y glPopMatrix()
Figura 12.9: Salida por pantalla del para dibujar un brazo robtico sencillo. El ejemplo simplemente emplea dos cubos
ejemplo del planetario. (convenientemente escalados) para dibujar la estructura jerrquica de la gura 12.10.
En este ejemplo, se asocian manejadores de callback para los eventos de teclado,
que se corresponden con la funcin keyboard (en 8-16 ). Esta funcin simplemente
Ejemplo Brazo Robot
de las
incrementa o decrementa los ngulos de rotacin dos articulaciones del robot,
que estn denidas como variables globales en 2 y 3 .
Un segundo ejemplo que utiliza las
funciones de aadir y quitar ele-
interesante
mentos de la pila de matrices.
La parte ms del ejemplo se encuentra denido en la funcin dibujar
en las lneas 19-42 .
1. Modique el ejemplo del listado del planetario para que dibuje una esfera de
color blanco que representar a la Luna, de radio 0.2 y separada 1 unidad del
centro de la Tierra (ver Figura 12.11). Este objeto tendr una rotacin completa
alrededor de la Tierra cada 2.7 das (10 veces ms rpido que la rotacin real
de la Luna sobre la Tierra. Supondremos que la luna no cuenta con rotacin
interna6 .
2. Modique el ejemplo del listado del brazo robtico para que una base (aadida Figura 12.11: Ejemplo de salida
con un toroide glutSolidTorus) permita rotar el brazo robtico respecto de del ejercicio propuesto.
la base (eje Y ) mediante las teclas 1 y 2. Adems, aada el cdigo para que el
extremo cuente con unas pinzas (creadas con glutSolidCone) que se abran y
cierren empleando las teclas z y x, tal y como se muestra en la Figura 12.12.
E
n las sesiones anteriores hemos delegado la gestin del arranque, incializacin y
parada de Ogre en una clase SimpleExample proporcionada junto con el motor
grco para facilitar el desarrollo de los primeros ejemplos. En este captulo
estudiaremos la gestin semi-automtica de la funcionalidad empleada en los ejemplos
anteriores, as como introduciremos nuevas caractersticas, como la creacin manual
de entidades, y el uso de Overlays, luces y sombras dinmicas.
Inicio Casi-Manual En esta seccin introduciremos un esqueleto de cdigo fuente que emplearemos
en el resto del captulo, modicndolo de manera incremental. Trabajaremos con
En este captulo no describiremos el
inicio totalmente manual de Ogre; dos clases; MyApp que proporciona el ncleo principal de la aplicacin, y la clase
emplearemos las llamadas de alto MyFrameListener que es una isntancia de clase que hereda de Ogre::FrameListener,
nivel para cargar plugins, inicializar basada en el patrn Observador.
ventana, etc...
Como puede verse en el primer listado, en el chero de cabecera de MyApp.h se
declara la clase que contiene tres miembros privados; la instancia de root (que hemos
estudiado en captulos anteriores), el gestor de escenas y un puntero a un objeto de la
clase propia MySceneManager que deniremos a continuacin.
369
[370] CAPTULO 13. GESTIN MANUAL OGRE 3D
6 Ogre::SceneManager* _sceneManager;
7 Ogre::Root* _root;
8 MyFrameListener* _framelistener;
9
10 public:
11 MyApp();
12 ~MyApp();
13 int start();
14 void loadResources();
15 void createScene();
16 };
El programa principal nicamente tendr que crear una instancia de la clase MyApp
y ejecutar su mtodo start. La denicin de este mtodo puede verse en el siguiente
listado, en las lneas 13-44 .
13.1.1. Inicializacin
En la lnea 14 se crea el objeto Root. Esta es la primera operacin que se debe
realizar antes de ejecutar cualquier otra operacin con Ogre. El constructor de Root
est sobrecargado, y permite que se le pase como parmetros el nombre del archivo
de conguracin de plugins (por defecto, buscar plugins.cfg en el mismo directorio
donde se encuentra el ejecutable), el de conguracin de vdeo (por defecto ogre.cfg),
y de log (por
defecto ogre.log). Si no le indicamos ningn parmetro (como en el caso
de la lnea 14 ), tratar de cargar los cheros con el nombre por defecto.
En la lnea 16 se intenta cargar la conguracin de vdeo existente en el archi-
vo ogre.cfg. Si el archivo no existe, o no es correcto, podemos
abrir el dilogo que
empleamos en los ejemplos de las sesiones anteriores (lnea 17 ), y guardar a con-
tinuacion los valores elegidos por el usuario (lnea 18 ). Con
el objeto Root creado,
podemos pasar a crear la ventana. Para ello, en la lnea 21 solicitamos al objeto Root
que nalice su inicializacin creando una ventana que utilice la conguracin que
eligi el usuario, con el ttulo especicado como segundo parmetro (MyApp en este
caso).
El primer parmetro booleano del mtodo indica a Ogre que se encarge de crear
automticamente la ventana. La llamada a este mtodo nos devuelve un puntero a un
objeto RenderWindow, que guardaremos en la variable window.
Lo que ser representado en la ventana vendr denido por el volumen de visua-
lizacin de la cmara virtual. Asociado a esta cmara, crearemos una supercie (algo
que conceptualmente puede verse como el lienzo, o el plano de imagen) sobre la que se
dibujarn los objetos 3D. Esta supercie se denomina viewport. El Gestor de Escena
admite diversos modos de gestin, empleando el patrn de Factora, que son cargados
como plugins (y especicados en el archivo plugins.cfg). El parmetro indicado en la
13.1. Inicializacin Manual [371]
llamada de 22 especica el tipo de gestor de escena que utilizaremos ST_GENERIC1 .
Esta gestin de escena mnima no est optimizada para ningn tipo de aplicacin en
particular, y resulta especialmente adecuada para pequeas aplicaciones, mens de
introduccin, etc.
C13
18 _root->saveConfig(); // Guardamos la configuracion
19 }
20
21 Ogre::RenderWindow* window = _root->initialise(true,"MyApp");
22 _sceneManager = _root->createSceneManager(Ogre::ST_GENERIC);
23
24 Ogre::Camera* cam = _sceneManager->createCamera("MainCamera");
25 cam->setPosition(Ogre::Vector3(5,20,20));
26 cam->lookAt(Ogre::Vector3(0,0,0));
27 cam->setNearClipDistance(5); // Establecemos distancia de
28 cam->setFarClipDistance(10000); // planos de recorte
29
30 Ogre::Viewport* viewport = window->addViewport(cam);
31 viewport->setBackgroundColour(Ogre::ColourValue(0.0,0.0,0.0));
32 double width = viewport->getActualWidth();
33 double height = viewport->getActualHeight();
34 cam->setAspectRatio(width / height);
35
36 loadResources(); // Metodo propio de carga de recursos
37 createScene(); // Metodo propio de creacion de la escena
38
39 _framelistener = new MyFrameListener(); // Clase propia
40 _root->addFrameListener(_framelistener); // Lo anadimos!
41
42 _root->startRendering(); // Gestion del bucle principal
43 return 0; // delegada en OGRE
44 }
45
46 void MyApp::loadResources() {
47 Ogre::ConfigFile cf;
48 cf.load("resources.cfg");
49
50 Ogre::ConfigFile::SectionIterator sI = cf.getSectionIterator();
51 Ogre::String sectionstr, typestr, datastr;
52 while (sI.hasMoreElements()) { // Mientras tenga elementos...
53 sectionstr = sI.peekNextKey();
54 Ogre::ConfigFile::SettingsMultiMap *settings = sI.getNext();
55 Ogre::ConfigFile::SettingsMultiMap::iterator i;
56 for (i = settings->begin(); i != settings->end(); ++i) {
57 typestr = i->first; datastr = i->second;
58 Ogre::ResourceGroupManager::getSingleton().
addResourceLocation(datastr, typestr, sectionstr);
1 Existen otros mtodos de gestin, como ST_INTERIOR, ST_EXTERIOR... Estudiaremos en
detalle las opciones de estos gestores de escena a lo largo de este mdulo.
[372] CAPTULO 13. GESTIN MANUAL OGRE 3D
59 }
60 }
61 Ogre::ResourceGroupManager::getSingleton().
initialiseAllResourceGroups();
62 }
63
64 void MyApp::createScene() {
65 Ogre::Entity* ent = _sceneManager->createEntity("Sinbad.mesh");
66 _sceneManager->getRootSceneNode()->attachObject(ent);
67 }
52-60 ), ob-
resources.cfg del ejemplo.
existan elementos que procesar por el iterador (bucle while de las lneas
tiene la clave del primer elemento de la coleccin (lnea 53 ) sin avanzar al siguiente
(en el caso del ejemplo, obtiene la seccin General). A continuacin, obtiene en set-
tings un puntero al valor del elemento actual de la coleccin, avanzando al siguiente.
Este valor en realidad otro mapa. Emplearemos un segundo iterador (lneas 56-69 )
para recuperar los nombres y valores de cada entrada de la seccin. En el chero de
ejemplo, nicamente iteraremos una vez dentro del segundo iterador, para obtener en
typestr la entrada a FileSystem, y en datastr el valor del directorio media.
Finalmente la llamada al ResourceGroupManager (lnea 61 ) solicita que se ini-
cialicen todos
los grupos de recursos que han sido aadidos en el iterador anterior, en
C13
la lnea 58 .
13.1.3. FrameListener
Como hemos comentado anteriormente, el uso de FrameListener se basa en el
patrn Observador. Aadimos instancias de esta clase al mtodo Root de forma que
ser noticada cuando ocurran ciertos eventos. Antes de representar un frame, Ogre
itera sobre todos los FrameListener aadidos, ejecutando el mtodo frameStarted de
cada uno de ellos. Cuando el frame ha sido dibujado, Ogre ejecutar igualmente el
mtodo frameEnded asociado a cada FrameListener. En el siguiente listado se declara
la clase MyFrameListener.
FrameStarted
Si delegamos la gestin de bucle de Listado 13.3: MyFrameListener.h
dibujado a Ogre, la gestin de los 1 #include <OgreFrameListener.h>
eventos de teclado, ratn y joystick 2
se realiza en FrameStarted, de mo- 3 class MyFrameListener : public Ogre::FrameListener {
do que se atienden las peticiones 4 public:
antes del dibujado del frame. 5 bool frameStarted(const Ogre::FrameEvent& evt);
6 bool frameEnded(const Ogre::FrameEvent& evt);
7 };
false en el mtodo frameStarted del FrameListener cuando esto ocurra. Como vemos
Recordemos que OIS no forma par-
en el siguiente listado, es necesario aadir al constructor de la clase (lnea 11 ) un
te de la distribucin de Ogre. Es
posible utilizar cualquier biblioteca
puntero a la RenderWindow. para la gestin de eventos.
En las lneas 8-9 convertimos el manejador a cadena, y aadimos el par de objetos
(empleando la plantilla pair de std) como parmetros de OIS. Como puede verse el
convenio de Ogre y de OIS es similar a la hora de nombrar el manejador de la ventana
(salvo por el hecho de que OIS requiere que se especique como cadena, y Ogre lo
devuelve como un entero).
Con este parmetro, creamos un InputManager de OIS en 11 , que nos permitir
interfaces para trabajar con eventos de teclado, ratn, etc. As, en las
crear diferentes
lneas 12-13 creamos un objeto de tipo OIS::Keyboard, que nos permitir obtener
las pulsaciones de tecla del usuario. El segundo parmetro de la lnea 13 permite
indicarle a OIS si queremos que almacene en un buffer los eventos de ese objeto. En
este caso, la entrada por teclado se consume directamente sin almacenarla en un buffer
intermedio.
Tanto el InputManager como el ojbeto de teclado deben ser eliminados explci-
tamente en el destructor de nuestro FrameListener. Ogre se encarga de liberar los
recursos asociados a todos sus Gestores. Como Ogre es independiente de OIS, no tie-
ne constancia de su uso y es responsabilidad del programador liberar los objetos de
entrada de eventos, as como el propio gestor InputManager.
C13
1 #include "MyFrameListener.h"
2
3 MyFrameListener::MyFrameListener(Ogre::RenderWindow* win) {
4 OIS::ParamList param;
5 unsigned int windowHandle; std::ostringstream wHandleStr;
6
7 win->getCustomAttribute("WINDOW", &windowHandle);
8 wHandleStr << windowHandle;
9 param.insert(std::make_pair("WINDOW", wHandleStr.str()));
10
11 _inputManager = OIS::InputManager::createInputSystem(param);
12 _keyboard = static_cast<OIS::Keyboard*>
13 (_inputManager->createInputObject(OIS::OISKeyboard, false));
14 }
15
16 MyFrameListener::~MyFrameListener() {
17 _inputManager->destroyInputObject(_keyboard);
18 OIS::InputManager::destroyInputSystem(_inputManager);
19 }
20
21 bool MyFrameListener::frameStarted(const Ogre::FrameEvent& evt) {
22 _keyboard->capture();
23 if(_keyboard->isKeyDown(OIS::KC_ESCAPE)) return false;
24 return true;
25 }
La implementacin del mtodo frameStarted es muy sencilla. En la lnea 22 se
obtiene si hubo alguna pulsacin de tecla. Si se presion la tecla Escape (lnea 23 ), se
devuelve false de modo que Ogre nalizar el bucle principal de dibujado y liberar
todos los recursos que se estn empleando.
muestra
Hasta ahora, la escena es totalmente esttica. La Figura 13.2 el resultado
de ejecutar el ejemplo (hasta que el usuario presiona la tecla ESC ). A continuacin
veremos cmo modicar la posicin de los elementos de la escena, deniendo un
interfaz para el manejo del ratn.
a) 43 _mouse->capture();
44 float rotx = _mouse->getMouseState().X.rel * deltaT * -1;
45 float roty = _mouse->getMouseState().Y.rel * deltaT * -1;
46 _camera->yaw(Ogre::Radian(rotx));
47 _camera->pitch(Ogre::Radian(roty));
48
49 return true;
50 }
Espacio
Tiempo (Frames)
C13
anterior. En esta ocasin, se denen en las lneas 28-30 una serie de variables que
Espacio comentaremos a continuacin. En vt almacenaremos el vector de traslacin relativo
que aplicaremos a la cmara, dependiendo de la pulsacin de teclas del usuario. La
Tiempo (Segundos) Tiempo (Segundos)
c)
variable r almacenar la rotacin que aplicaremos al nodo de la escena (pasado como
tercer argumento al constructor). En deltaT guardaremos el nmero de segundos
transcurridos desde el despliegue del ltimo frame. Finalmente la variable tSpeed
servir para indicar la traslacin (distancia en unidades del mundo) que queremos
recorrer con la cmara en un segundo.
Espacio La necesidad de medir el tiempo transcurrido desde el despliegue del ltimo frame
es imprescindible si queremos que las unidades del espacio recorridas por el modelo
d) (o la cmara en este caso) sean dependientes del tiempo e independientes de las capa-
cidades de representacin grcas de la mquina sobre la que se estn ejecutando. Si
aplicamos directamente un incremento del espacio sin tener en cuenta el tiempo, como
se muestra en la Figura 13.3, el resultado del espacio recorrido depender de la velo-
cidad de despliegue (ordenadores ms potentes avanzarn ms espacio). La solucin
es aplicar incrementos en la distancia dependientes del tiempo.
Espacio De este modo, aplicamos un movimiento relativo a la cmara (en funcin de sus
ejes locales), multiplicando el vector de traslacin (que ha sido denido dependien-
do de la pulsacin de teclas del usuario en las lneas 34-37 ), por deltaT y por la
Figura 13.3: Comparacin entre
animacin basada en frames y ani-
macin basada en tiempo. El eje de velocidad de traslacin (de modo que avanzar 20 unidades por segundo).
abcisas representa el espacio reco- De forma similar, cuando el usuario pulse la tecla R , se rotar
el modelo respecto
rrido en una trayectoria, mientras
de su eje Y local a una velocidad de 180 por segundo (lneas 40-41 ).
o
que el eje de ordenadas representa
el tiempo. En las grcas a) y b) el Finalmente, se aplicar una rotacin a la cmara respecto de sus ejes Y y Z locales
el movimiento relativo del ratn. Para ello, se obtiene el estado del ratn
empelando
tiempo se especica en frames. En
las grcas c) y d) el tiempo se es- (lneas 44-45 ), obteniendo el incremento relativo en pxeles desde la ltima captura
pecica en segundos. Las grcas (mediante el atributo abs se puede obtener el valor absoluto del incremento).
a) y c) se corresponden con los rsul-
tados obtenidos en un computador
con bajo rendimiento grco (en el
mismo tiempo presenta una baja ta- 13.3. Creacin manual de Entidades
sa de frames por segundo). Las gr-
cas b) y d) se corresponden a un
computador con el doble de frames Ogre soporta la creacin manual de multitud de entidades y objetos. Veremos a
por segundo. continuacin cmo se pueden aadir planos y fuentes de luz a la escena.
[378] CAPTULO 13. GESTIN MANUAL OGRE 3D
manual del plano se realiza en las lneas 8-11 del siguiente listado.
La creacin
En la lnea 8 se especica que el plano tendr como vector normal, el vector unitario
en Y , y que estar situado a -5 unidades respecto del vector normal. Esta denicin
se corresponde con un plano innito (descripcin matemtica abstracta). Si queremos
representar el plano, tendremos que indicar a Ogre el tamao (nito) del mismo, as
como la resolucin que queremos aplicarle (nmero de divisiones
horizontales y ver-
ticales). Estas operaciones se indican en las lneas 9-11 .
Finalmente las lneas 18-22 se encargan de denir una fuente de luz dinmica
direccional 2 y habilitar el clculo de sombras. Ogre soporta 3 tipos bsicos de fuentes
de luz y 11 modos de clculo de sombras dinmicas, que sern estudiados en detalle
en prximos captulos.
C13
necesario especicar la carga de una fuente de texto. Ogre soporta texturas basadas
en imagen o truetype. La denicin de las fuentes se realiza empleando un chero de
extensin .fontdef (ver Figura 13.7).
Figura 13.6: Valores de repeti- En el listado de la Figura 13.8 se muestra la denicin de los elementos y conten-
cin en la textura. La imagen supe- dores del Overlay, as como los cheros de material (extensin .material) asociados
rior se corresponde con valores de al mismo. En el siguiente captulo estudiaremos en detalle la denicin de materiales
uT ile = 1, vT ile = 1. La ima- en Ogre, aunque un simple vistazo al chero de materiales nos adelanta que estamos
gen inferior implica un valor de 2 creando dos materiales, uno llamado panelInfoM, y otro matUCLM. Ambos utilizan
en ambos parmetros (repitiendo la texturas de imagen. El primero de ellos utiliza una tcnica de mezclado que tiene en
textura 2x2 veces en el plano). cuenta la componente de opacidad de la pasada.
El primer contenedor de tipo Panel (llamado PanelInfo), contiene cuatro TextArea
en su interior. El posicionamiento de estos elementos se realiza de forma relativa al
posicionamiento del contenedor. De este modo, se establece una relacin de jerarqua
implcita entre el contenedor y los objetos contenidos. El segundo elemento contene-
dor (llamado logoUCLM) no tiene ningn elemento asociado en su interior.
La Tabla 13.1 describe los principales atributos de los contenedores y elementos
de los overlays. Estas propiedades pueden ser igualmente modicadas en tiempo de
Figura 13.7: Fichero de denicin ejecucin, por lo que son animables. Por ejemplo, es posible modicar la rotacin de
de la fuente Blue.fontdef. un elemento del Overlay consiguiendo bonitos efectos en los mens del juego.
El sistema de scripting de Overlays de Ogre permite denir plantillas de estilo
que pueden utilizar los elementos y contenedores. En este ejemplo se han denido
dos plantillas, una para texto genrico (de nombre MyTemplates/Text), y otra para tex-
to pequeo (MyTemplates/SmallText). Para aplicar estas plantillas a un elemento del
Overlay, basta con indicar seguido de dos puntos el nombre de la plantilla a continua-
cin del elemento (ver Figura 13.8).
En el listado del Overlay denido, se ha trabajado con el modo de especicacin
del tamao en pxeles. La alineacin de los elementos se realiza a nivel de pxel (en
lugar de ser relativo a la resolucin de la pantalla), obteniendo un resultado como se
muestra en la Figura 13.8. El uso de valores negativos en los campos de alineacin
permite posicionar con exactitud los paneles alineados a la derecha y en el borde infe-
2 Este tipo de fuentes de luz nicamente tienen direccin. Denen rayos de luz paralelos de una fuente
Figura 13.8: Denicin de los Overlays empleando cheros externos. En este chero se describen dos
paneles contenedores; uno para la zona inferior izquierda que contiene cuatro reas de texto, y uno para la
zona superior derecha que representar el logotipo de la UCLM.
rior. Por ejemplo, en el caso del panel logoUCLM, de tamao (150x120), la alineacin
horizontal se realiza a la derecha. El valor de -180 en el campo left indica que que-
remos que quede un margen de 30 pxeles entre el lado derecho de la ventana y el
extremo del logo.
Los Overlays tienen asociada una profundidad, denida en el campo zorder (ver
Figura 13.8), en el rango de 0 a 650. Overlays con menor valor de este campo sern
dibujados encima del resto. Esto nos permite denir diferentes niveles de despliegue
de elementos 2D.
Para nalizar estudiaremos la modicacin en el cdigo de MyApp y MyFrame-
Listener.
En el siguiente listado se muestra que el constructor del FrameListener (lnea
7 ) necesita conocer el OverlayManager, cuya referencia se obtiene en la lnea 14 .
En realidad no sera necesario pasar el puntero al constructor, pero evitamos de esta
forma que el FrameListener tenga que solicitar la referencia.
13.4. Uso de Overlays [381]
Atributo Descripcin
metrics_mode Puede ser relative (valor entre 0.0 y 1.0), o pixels (valor en pxeles). Por defecto es relative. En
coordenadas relativas, la coordenada superior izquierda es la (0,0) y la inferior derecha la (1,1).
horz_align Puede ser left (por defecto), center o right.
vert_align Puede ser top (por defecto), center o bottom.
left Posicin con respecto al extremo izquierdo. Por defecto 0. Si el valor es negativo, se interpreta
como espacio desde el extremo derecho (con alineacin right).
top Posicin con respecto al extremo superior. Por defecto 0. Si el valor es negativo, se interpreta como
espacio desde el extremo inferior (con alineacin vertical bottom).
width Ancho. Por defecto 1 (en relative).
height Alto. Por defecto 1 (en relative).
material Nombre del material asociado (por defecto Ninguno).
caption Etiqueta de texto asociada al elemento (por defecto Ninguna).
rotation ngulo de rotacin del elemento (por defecto sin rotacin).
C13
1024x768
1280x800 800x600
Figura 13.9: Resultado de ejecucin del ejemplo de uso de Overlays, combinado con los resultados parcia-
les de las secciones anteriores. La imagen ha sido generada con 3 conguraciones de resolucin diferentes
(incluso con diferente relacin de aspecto): 1280x800, 1024x768 y 800x600.
En las lneas 15-16 , obtenemos el Overlay llamado Info (denido en el listado de
la Figura 13.8), y lo mostramos.
Para nalizar, el cdigo del FrameListener denido en las lneas 6-16 del siguien-
te listado parcial, modica el valor del texto asociado a los elementos de tipo TextArea.
Hacemos uso de la clase auxiliar Ogre::StringConverter que facilita la conversin a
String de ciertos tipos de datos (vectores, cuaternios...).
3 loadResources();
4 createScene();
5 createOverlay(); // Metodo propio para crear el overlay
6 ...
7 _framelistener = new MyFrameListener(window, cam, node,
8 _overlayManager);
9 _root->addFrameListener(_framelistener);
10 ...
11 }
12
13 void MyApp::createOverlay() {
14 _overlayManager = Ogre::OverlayManager::getSingletonPtr();
15 Ogre::Overlay *overlay = _overlayManager->getByName("Info");
16 overlay->show();
17 }
Interaccin y Widgets
14
Csar Mora Castro
C
omo se ha visto a lo largo del curso, el desarrollo de un videojuego requiere
tener en cuenta una gran variedad de disciplinas y aspectos: grcos 3D o 2D,
msica, simulacin fsica, efectos de sonido, jugabilidad, eciencia, etc. Uno
de los aspectos a los que se les suele dar menos importancia, pero que juegan un papel
fundamental a la hora de que un juego tenga xito o fracase, es la interfaz de usuario.
Sin embargo, el mayor inconveniente es que la mayora de los motores grcos no dan
soporte para la gestin de Widgets, y realizarlos desde cero es un trabajo ms costoso
de lo que pueda parecer.
En este captulo se describe de forma general la estructura y las guas que hay que
tener en cuenta a la hora de disear y desarrollar una interfaz de usuario. Adems, se
explicar el uso de CEGUI, una biblioteca de gestin de Widgets para integrarlos en
motores grcos.
Eciencia y diseo
Para desarrollar un videojuego, tan
importante es cuidar la eciencia y
14.1. Interfaces de usuario en videojuegos
optimizarlo, como que tenga un di-
seo visualmente atractivo.
Las interfaces de usuario especcas de los videojuegos deben tener el objetivo
de crear una sensacin positiva, que consiga la mayor inmersin del usuario posible.
Estas interfaces deben tener lo que se denomina ow. El ow[50] es la capacidad de
atraer la atencin del usuario, manteniendo su concentracin, la inmersin dentro de
la trama del videojuego, y que consiga producir una experiencia satisfactoria.
383
[384] CAPTULO 14. INTERACCIN Y WIDGETS
Cada vez se realizan estudios ms serios sobre cmo desarrollar interfaces que
tengan ow, y sean capaces de brindar una mejor experiencia al usuario. La principal
diferencia con las interfaces de usuario de aplicaciones no orientadas al ocio, es que
estas centran su diseo en la usabilidad y la eciencia, no en el impacto sensorial.
Si un videojuego no consigue atraer y provocar sensaciones positivas al usuario, este
posiblemente no triunfar, por muy eciente que sea, o por muy original o interesante
que sea su trama.
A continuacin se detalla una lista de aspectos a tener en cuenta que son reco-
mendables para aumentar el ow de un videojuego, aunque tradicionalmente se han
considerado perjudiciales a la hora de disear interfaces tradicionales segn las reglas
de interaccin persona-computador:
Se ha visto cmo el caso particular de las interfaces de los videojuegos puede con-
tradecir reglas que se aplican en el diseo de interfaces de usuario clsicas en el campo
de la interaccin persona-computador. Sin embargo, existen muchas recomendaciones
que son aplicables a ambos tipos de interfaces. A continuacin se explican algunas:
C14
2. Eciencia: cun rpido se puede realizar una tarea, sobretodo si es muy repeti-
tiva.
3. Simplicidad: mantener los controles y la informacin lo ms minimalista posi-
ble.
4. Esttica: cun sensorialmente atractiva es la interfaz.
Una vez vistas algunas guas para desarrollar interfaces de usuario de videojuegos
atractivas y con ow, se va a describir la estructura bsica que deben tener.
Existe una estructura bsica que un videojuego debe seguir. No es buena prctica
comenzar el juego directamente en el terreno de juego o campo de batalla. La
estructura tpica que debe seguir la interfaz de un videojuego es la siguiente:
En primer lugar, es buena prctica mostrar una splash screen. Este tipo de pantallas
se muestran al ejecutar el juego, o mientras se carga algn recurso que puede durar un
tiempo considerable, y pueden ser usadas para mostrar informacin sobre el juego o
sobre sus desarrolladores. Suelen mostrarse a pantalla completa, o de menor tamao
pero centradas. La Figura 14.1 muestra la splash screen del juego free orion.
Las otros dos elementos de la esctructura de un videojuego son el Men y el HUD.
Suponen una parte muy importante de la interfaz de un juego, y es muy comn utilizar
Widgets en ellos. A continuacion se analizarn ms en detalle y se mostrarn algunos
ejemplos.
Figura 14.2: Extracto del men de conguracin de Nexuiz (izquierda), e interfaz de ScummVM (derecha).
14.1.1. Men
Todos los videojuegos deben tener un Men desde el cual poder elegir los modos
de juego, congurar opciones, mostrar informacin adicional y otras caractersticas.
Dentro de estos mens, es muy frecuente el uso de Widgets como botones, barras des-
lizantes (por ejemplo, para congurar la resolucin de pantalla), listas desplegables
(para elegir idioma), o check buttons (para activar o desactivar opciones). Por eso es
importante disponer de un buen repertorio de Widgets, y que sea altamente personali-
zable para poder adaptarlos al estilo visual del videojuego.
En la Figura 14.2 se puede apreciar ejemplos de interfaces de dos conocidos jue-
gos open-source. La interfaz de la izquierda, correspondiente al juego Nexuiz, muestra
un trozo de su dialogo de conguracin, mientras que la de la derecha corresponde a
la interfaz de ScummVM. En estos pequeos ejemplos se muestra un nmero conside-
rable de Widgets, cuyo uso es muy comn:
14.1.2. HUD
En relacin a las interfaces de los videojuegos, concretamente se denomina HUD
(del ingls, Head-Up Display), a la informacin y elementos de interaccin mostrados
durante el propio transcurso de la partida. La informacin que suelen proporcionar son
la vida de los personajes, mapas, velocidad de los vehculos, etc.
En la Figura 14.3 se muestra una parte de la interfaz del juego de estrategia am-
bientada en el espacio FreeOrion. La interfaz utiliza elementos para mostrar los par-
metros del juego, y tambin utiliza Widgets para interactuar con l.
Como se puede intuir, el uso de estos Widgets es muy comn en cualquier vi-
deojuego, independientemente de su complejidad. Sin embargo, crear desde cero un
conjunto medianamente funcional de estos es una tarea nada trivial, y que roba mucho
tiempo de la lnea principal de trabajo, que es el desarrollo del propio videojuego.
14.2. Introduccin CEGUI [387]
Una vez que se ha dado unas guas de estilo y la estructura bsicas para cualquier
videojuego, y se ha mostrado la importancia de los Widgets en ejemplos reales, se va
a estudiar el uso de una potente biblioteca que proporciona estos mismos elementos
para distintos motores grcos, para que el desarrollo de Widgets para videojuegos sea
lo menos problemtico posible.
C14
Figura 14.3: Screenshot del HUD del juego FreeOrion.
CEGUIs mission
Como dice en su pgina web, CE-
GUI est dirigido a desarrolladores 14.2. Introduccin CEGUI
de videojuegos que deben invertir
su tiempo en desarrollar buenos jue-
gos, no creando subsistemas de in- CEGUI (Crazy Eddies GUI)[2] (Crazy Eddies GUI) es una biblioteca open sour-
terfaces de usuario. ce multiplataforma que proporciona entorno de ventanas y Widgets para motores gr-
cos, en los cuales no se da soporte nativo, o es muy deciente. Es orientada a objetos
y est escrita en C++.
CEGUI es una biblioteca muy potente en pleno desarrollo, por lo que est
sujeta a continuos cambios. Todas las caractersticas y ejemplos descritos a lo
largo de este captulo se han creado utilizando la versin actualmente estable,
la 0.7.x. No se asegura el correcto funcionamiento en versiones anteriores o
posteriores.
CEGUI y Ogre3D CEGUI es muy potente y exible. Es compatible con los motores grcos OpenGL,
Direct3D, Irrlicht y Ogre3D.
A lo largo de estos captulos,
se utilizar la frmula CEGUI/O-
gre3D/OIS para proveer interfaz
grca, motor de rendering y ges-
De la misma forma que Ogre3D es nicamente un motor de rendering, CEGUI
tin de eventos a los videojuegos, es slo un motor de de gestin de Widgets, por lo que el renderizado y la gestin de
aunque tambin es posible utilizarlo eventos de entrada deben ser realizadas por bibliotecas externas.
con otras bibliotecas (ver documen-
tacin de CEGUI).
En sucesivas secciones se explicar cmo integrar CEGUI con las aplicaciones de
este curso que hacen uso de Ogre3D y OIS. Adems se mostrarn ejemplos prcticos
de las caractersticas ms importantes que ofrece.
[388] CAPTULO 14. INTERACCIN Y WIDGETS
14.2.1. Instalacin
En las distribuciones actuales ms comunes de GNU/Linux (Debian, Ubuntu), est
disponible los paquetes de CEGUI para descargar, sin embargo estos dependen de la
versin 1.7 de Ogre, por lo que de momento no es posible utilizar la combinacin CE-
GUI+OGRE 1.8 desde los respositorios. Para ello, es necesario descargarse el cdigo
fuente de la ltima versin de CEGUI y compilarla. A continuacin se muestran los
pasos.
./configure cegui_enable_ogre=yes
make && sudo make install
Estos pasos instalarn CEGUI bajo el directorio /usr/local/, por lo que es im-
portante indicar en el Makele que las cabeceras las busque en el directorio
/usr/local/include/CEGUI.
Adems, algunos sistemas operativos no buscan las bibliotecas de enlazado
dinmico en /usr/local/lib por defecto. Esto produce un error al ejecutar la
aplicacin indicando que no puede encontrar las bibliotecas libCEGUI*. Pa-
ra solucionarlo, se puede editar como superusuario el chero /etc/ld.so.conf,
y aadir la lnea include /usr/local/lib. Para que los cambios surtan efecto,
ejecutar sudo ldconfig.
14.2.2. Inicializacin
La arquitectura de CEGUI es muy parecida a la de Ogre3D, por lo que su uso es
similar. Est muy orientado al uso de scripts, y hace uso del patrn Singleton para
implementar los diferentes subsistemas. Los ms importantes son:
Estos subsistemas ofrecen funciones para poder gestionar los diferentes recursos
que utiliza CEGUI. A continuacin se listan estos tipos de recursos:
14.2. Introduccin CEGUI [389]
C14
Listado 14.1: Inicializacn de CEGUI para su uso con Ogre3D
1 #include <CEGUI.h>
2 #include <RendererModules/Ogre/CEGUIOgreRenderer.h>
3
4 CEGUI::OgreRenderer* renderer = &CEGUI::OgreRenderer::
bootstrapSystem();
5
6 CEGUI::Scheme::setDefaultResourceGroup("Schemes");
7 CEGUI::Imageset::setDefaultResourceGroup("Imagesets");
8 CEGUI::Font::setDefaultResourceGroup("Fonts");
9 CEGUI::WindowManager::setDefaultResourceGroup("Layouts");
10 CEGUI::WidgetLookManager::setDefaultResourceGroup("LookNFeel");
Y las opciones que hay que aadir al Makele para compilar son:
Es importante incluir los ags de compilado, en la lnea 2, para que encuentre las
cabeceras segn se han indicado en el ejemplo de inicializacin.
Esto es slo el cdigo que inicializa la biblioteca en la aplicacin para que pueda
comenzar a utilizar CEGUI como interfaz grca, todava no tiene ninguna funciona-
lidad. Pero antes de empezar a aadir Widgets, es necesario conocer otros conceptos
primordiales.
CEGUI::UDim(scale, offset)
Indica la posicin en una dimensin. El primer parmetro indica la posicin rela- (left, top)
tiva, que toma un valor entre 0 y 1, mientras que el segundo indica un desplazamiento
absoluto en pxeles.
Por ejemplo, supongamos que posicionamos un Widget con UDim 0.5,20 en la di- Widget
mensin x. Si el ancho de la pantalla fuese 640, la posicin sera 0.5*640+20 = 340,
mientras que si el ancho fuese 800, la posicin seri 0.5*800+20 = 420. En la Figu-
ra 14.5 se aprecian los dos ejemplos de forma grca. De esta forma, si la resolucin
de la pantalla cambia, por ejemplo, el Widget se reposicionar y se redimensionar de
forma automtica. (right, bottom)
Teniendo en cuenta cmo expresar el posicionamiento en una nica dimensin Figura 14.4: rea rectangular de-
utilizando UDim, se denen dos elementoss ms. nida por URect.
14.2. Introduccin CEGUI [391]
0.5 20 px
0.5 20 px
640x480 800x600
Coordenada x
CEGUI::UVector2(UDim x, UDim y)
C14
que est compuesto por dos UDim, uno para la coordenada x, y otro para la y, o
para el ancho y el alto, dependiendo de su uso.
El segundo elemento, se utiliza para denir un rea rectangular:
Como muestra la Figura 14.4, los dos primeros denen la esquina superior izquier-
da, y los dos ltimos la esquina inferior derecha.
14 }
15
16 bool MyFrameListener::mousePressed(const OIS::MouseEvent& evt, OIS
::MouseButtonID id)
17 {
18 CEGUI::System::getSingleton().injectMouseButtonDown(
convertMouseButton(id));
19 return true;
20 }
21
22 bool MyFrameListener::mouseReleased(const OIS::MouseEvent& evt, OIS
::MouseButtonID id)
23 {
24 CEGUI::System::getSingleton().injectMouseButtonUp(
convertMouseButton(id));
25 return true;
26 }
Adems, es necesario convertir la forma en que identica OIS los botones del
ratn, a la que utiliza CEGUI, puesto que no es la misma, al contrario que sucede con
las teclas del teclado. Para ello se ha escrito la funcin convertMouseButton():
Listado 14.5: Funcin de conversin entre identicador de botones de ratn de OIS y CEGUI.
1 CEGUI::MouseButton MyFrameListener::convertMouseButton(OIS::
MouseButtonID id)
2 {
3 CEGUI::MouseButton ceguiId;
4 switch(id)
5 {
6 case OIS::MB_Left:
7 ceguiId = CEGUI::LeftButton;
8 break;
9 case OIS::MB_Right:
10 ceguiId = CEGUI::RightButton;
11 break;
12 case OIS::MB_Middle:
13 ceguiId = CEGUI::MiddleButton;
14 break;
15 default:
16 ceguiId = CEGUI::LeftButton;
17 }
18 return ceguiId;
19 }
Por otro lado, tambin es necesario decir a CEGUI cunto tiempo ha pasado desde
la deteccin del ltimo evento, por lo que hay que aadir la siguiente lnea a la funcin
frameStarted():
Listado 14.6: Orden que indica a CEGUI el tiempo transcurrido entre eventos.
1 CEGUI::System::getSingleton().injectTimePulse(evt.
timeSinceLastFrame)
Es importante tener en cuenta que en CEGUI, todos los elementos son Win-
dows. Cada uno de los Windows puede contener a su vez otros Windows. De
este modo, pueden darse situaciones raras como que un botn contenga a otro
botn, pero que en la prctica no suceden.
C14
8 CEGUI::WidgetLookManager::setDefaultResourceGroup("LookNFeel");
9
10 CEGUI::SchemeManager::getSingleton().create("TaharezLook.scheme")
;
11 CEGUI::System::getSingleton().setDefaultFont("DejaVuSans-10");
12 CEGUI::System::getSingleton().setDefaultMouseCursor("TaharezLook"
,"MouseArrow");
13
14 //Creating GUI Sheet
15 CEGUI::Window* sheet = CEGUI::WindowManager::getSingleton().
createWindow("DefaultWindow","Ex1/Sheet");
16
17 //Creating quit button
18 CEGUI::Window* quitButton = CEGUI::WindowManager::getSingleton().
createWindow("TaharezLook/Button","Ex1/QuitButton");
19 quitButton->setText("Quit");
20 quitButton->setSize(CEGUI::UVector2(CEGUI::UDim(0.15,0),CEGUI::
UDim(0.05,0)));
21 quitButton->setPosition(CEGUI::UVector2(CEGUI::UDim(0.5-0.15/2,0)
,CEGUI::UDim(0.2,0)));
22 quitButton->subscribeEvent(CEGUI::PushButton::EventClicked,
23 CEGUI::Event::Subscriber(&MyFrameListener::quit,
24 _framelistener));
25 sheet->addChildWindow(quitButton);
26 CEGUI::System::getSingleton().setGUISheet(sheet);
27 }
Los Widgets de la interfaz grca se organizan de forma jerrquica, de forma Convenio de nombrado
anloga al grafo de escena Ogre. Cada Widget (o Window) debe pertenecer a otro CEGUI no exige que se siga ningn
que lo contenga. De este modo, debe de haber un Widget padre o raz. Este Wid- convenio de nombrado para sus ele-
get se conoce en CEGUI como Sheet (del ingls, hoja, rerindose a la hoja en blanco mentos. Sin embargo, es altamen-
te recomendable utilizar un conve-
que contiene todos los elementos). Esta se crea en la lnea 15. El primer parmetro nio jerrquico, utilizando la barra
indica el tipo del Window, que ser un tipo genrico, DefaultWindow. El segundo es / como separador.
el nombre que se le da a ese elemento. Con Ex1 se hace referencia a que es el primer
ejemplo (Example1), y el segundo es el nombre del elemento.
MouseClicked
MouseEnters
MouseLeaves
EventActivated
Figura 14.6: Screenshot de la pri-
EventTextChanged mera aplicacin de ejemplo.
14.4. Tipos de Widgets [395]
EventAlphaChanged
EventSized
Listado 14.8: Funcin que implementa el comportamiento del botn al ser pulsado.
1 bool MyFrameListener::quit(const CEGUI::EventArgs &e)
2 {
3 _quit = true;
4 return true;
5 }
C14
En la Figura 14.6 se puede ver una captura de esta primera aplicacin.
Button
Check Box
Combo Box
Frame Window
[396] CAPTULO 14. INTERACCIN Y WIDGETS
List Box
Progress Bar
Slider
Static Text
etc
El siguiente paso en el aprendizaje de CEGUI es crear una interfaz que bien podra
servir para un juego, aprovechando las ventajas que proporcionan los scripts.
14.5. Layouts
Los scripts de layouts especican qu Widgets habr y su organizacin para una
ventana especca. Por ejemplo, un layout llamado chatBox.layout puede contener la
estructura de una ventana con un editBox para insertar texto, un textBox para mostrar
la conversacin, y un button para enviar el mensaje. De cada layout se pueden crear
tantas instancias como se desee.
No hay que olvidar que estos cheros son xml, por lo que deben seguir su estruc-
tura. La siguiente es la organizacin genrica de un recurso layout:
En la lnea 1 se escribe la cabecera del archivo xml, lo cual no tiene nada que ver
con CEGUI. En la lnea 2 se abre la etiqueta GUILayout, para indicar el tipo de script
y se cierra en la lnea 13.
A partir de aqu, se denen los Windows que contendr la interfaz (en CEGUI todo
es un Window!). Para declarar uno, se indica el tipo de Window y el nombre, como se
puede ver en la lnea 3. Dentro de l, se especican sus propiedades (lneas 4 y 5).
Estas propiedades pueden indicar el tamao, la posicin, el texto, la transparencia,
etc. Los tipos de Widgets y sus propiedades se denan en el esquema escogido, por
lo que es necesario consultar su documentacin especca. En la Seccin 14.6 se ver
un ejemplo concreto.
En la lnea 6 se muestra un comentario en xml.
Despus de la denicin de las propiedades de un Window, se pueden denir ms
Windows que pertenecern al primero, ya que los Widgets siguen una estructura jerr-
quica.
14.6. Ejemplo de interfaz [397]
C14
16 <Property Name="CurrentValue" Value="75" />
17 <Property Name="MaximumValue" Value="100" />
18 <Property Name="MinimumValue" Value="0" />
19 <Property Name="UnifiedAreaRect" Value="
{{0.0598,0},{0.046,0},{0.355,0},{0.166,0}}" />
20 </Window>
21 <!-- Fullscreen parameter -->
22 <Window Type="TaharezLook/StaticText" Name="Cfg/FullScrText" >
23 <Property Name="Text" Value="Fullscreen" />
24 <Property Name="UnifiedAreaRect" Value="
{{0.385,0},{0.226,0},{0.965,0},{0.367,0}}" />
25 </Window>
26 <Window Type="TaharezLook/Checkbox" Name="Cfg/FullscrCheckbox"
>
27 <Property Name="UnifiedAreaRect" Value="
{{0.179,0},{0.244,0},{0.231,0},{0.370,0}}" />
28 </Window>
29 <!-- Port parameter -->
30 <Window Type="TaharezLook/StaticText" Name="Cfg/PortText" >
31 <Property Name="Text" Value="Port" />
32 <Property Name="UnifiedAreaRect" Value="
{{0.385,0},{0.420,0},{0.9656,0},{0.551,0}}" />
33 </Window>
34 <Window Type="TaharezLook/Editbox" Name="Cfg/PortEditbox" >
35 <Property Name="Text" Value="1234" />
36 <Property Name="MaxTextLength" Value="1073741823" />
37 <Property Name="UnifiedAreaRect" Value="
{{0.0541,0},{0.417,0},{0.341,0},{0.548,0}}" />
38 <Property Name="TextParsingEnabled" Value="False" />
39 </Window>
40 <!-- Resolution parameter -->
41 <Window Type="TaharezLook/StaticText" Name="Cfg/ResText" >
42 <Property Name="Text" Value="Resolution" />
43 <Property Name="UnifiedAreaRect" Value="
{{0.385,0},{0.60,0},{0.965,0},{0.750,0}}" />
44 </Window>
45 <Window Type="TaharezLook/ItemListbox" Name="Cfg/ResListbox" >
46 <Property Name="UnifiedAreaRect" Value="
{{0.0530,0},{0.613,0},{0.341,0},{0.7904,0}}" />
47 <Window Type="TaharezLook/ListboxItem" Name="Cfg/Res/Item1">
48 <Property Name="Text" Value="1024x768"/>
49 </Window>
[398] CAPTULO 14. INTERACCIN Y WIDGETS
Cada uno de los Windows tiene unas propiedades para personalizarlos. En la do-
cumentacin se explican todas y cada una de las opciones de los Window en funcin
del esquema que se utilice.
Text: indica el texto del Widget. Por ejemplo, la etiqueta de un botn o el ttulo
de una ventana.
14.6. Ejemplo de interfaz [399]
C14
del rectngulo (los dos primeros UDims), y la inferior derecha.
Es importante tener en cuenta que si al Widget hijo se le indique que ocupe
todo el espacio (con el valor para la propiedad de {{0,0},{0,0},{1,0},{1,0}}),
ocupar todo el espacio del Window al que pertenezca, no necesariamente toda
la pantalla.
TitlebarFont: indica el tipo de fuente utilizado en el ttulo de la barra de una
FrameWindow.
TitlebarEnabled: activa o desactiva la barra de ttulo de una FrameWindow.
CurrentValue: valor actual de un Widget al que haya que indicrselo, como el
Spinner.
MaximumValue y MinimumValue: acota el rango de valores que puede tomar un
Widget.
Cada Widget tiene sus propiedades y uso especial. Por ejemplo, el Widget utilizado
para escoger la resolucin (un ItemListBox), contiene a su vez otros dos Window del
tipo ListBoxItem, que representan cada una de las opciones (lneas 47 y 50).
Para poder aadir funcionalidad a estos Widgets (ya sea asociar una accin o uti-
lizar valores, por ejemplo), se deben recuperar desde cdigo mediante el WindowMa-
nager, con el mismo nombre que se ha especicado en el layout. Este script por tanto
se utiliza nicamente para cambiar la organizacin y apariencia de la interfaz, pero no
su funcionalidad.
En este ejemplo concreto, slo se ha aadido una accin al botn con la etiqueta
Exit para cerrar la aplicacin.
[400] CAPTULO 14. INTERACCIN Y WIDGETS
www.cegui.org.uk/wiki/index.php/CEED
Al tratarse de una versin inestable, es mejor consultar este tutorial para mantener
los pasos actualizados de acuerdo a la ltima versin en desarrollo.
14.8.1. Scheme
Los esquemas contienen toda la informacin para ofrecer Widgets a una interfaz,
su apariencia, su comportamiento visual o su tipo de letra.
Al igual que suceda con los layout, comienzan con una etiqueta que identican el
tipo de script. Esta etiqueta es GUIScheme, en la lnea 2.
De las lneas 3-6 se indican los scripts que utilizar el esquema de los otros tipos.
Qu conjunto de imgenes para los botones, cursores y otro tipo de Widgets mediante
el Imageset (lnea 3), qu fuentes utilizar mediante un Font (lnea 4), y el comporta-
miento visual de los Widgets a travs del LookNFeel (lnea 5). Adems se indica qu
sistema de renderizado de skin utilizar (lnea 6). CEGUI utiliza Falagard.
14.8. Scripts en detalle [401]
14.8.2. Font
Describen los tipos de fuente que se utilizarn en la interfaz.
C14
14.8.3. Imageset
Contiene las verdaderas imgenes que compondrn la interfaz. Este recurso es,
junto al de las fuentes, los que ms sujetos a cambio estn para poder adaptar la apa-
riencia de la interfaz a la del videojuego.
14.8.4. LookNFeel
Estos tipos de scripts son mucho ms complejos, y los que CEGUI proporciona
suelen ser ms que suciente para cualquier interfaz. An as, en la documentacin se
puede consultar su estructura y contenido.
C14
27 CEGUI::Imageset& imageSet = CEGUI::ImagesetManager::getSingleton().
create("RTTImageset", guiTex);
28 imageSet.defineImage("RTTImage",
29 CEGUI::Point(0.0f,0.0f),
30 CEGUI::Size(guiTex.getSize().d_width, guiTex.getSize()
.d_height),
31 CEGUI::Point(0.0f,0.0f));
32
33 CEGUI::Window* ex1 = CEGUI::WindowManager::getSingleton().
loadWindowLayout("render.layout");
34
35 CEGUI::Window* RTTWindow = CEGUI::WindowManager::getSingleton().
getWindow("CamWin/RTTWindow");
36
37 RTTWindow->setProperty("Image",CEGUI::PropertyHelper::imageToString
(&imageSet.getImage("RTTImage")));
38
39 //Exit button
40 CEGUI::Window* exitButton = CEGUI::WindowManager::getSingleton().
getWindow("ExitButton");
41 exitButton->subscribeEvent(CEGUI::PushButton::EventClicked,
42 CEGUI::Event::Subscriber(&MyFrameListener::quit,
43 _framelistener));
44 //Attaching layout
45 sheet->addChildWindow(ex1);
46 CEGUI::System::getSingleton().setGUISheet(sheet);
14.10.1. Introduccin
El formato de las etiquetas utilizadas son el siguiente:
14.10. Formateo de texto [405]
[tag-name=value]
Si se quiere mostrar como texto la cadena [Texto] sin que lo interprete como
una etiqueta, es necesario utilizar un carcter de escape. En el caso concreto
de C++ se debe anteponer \\ nicamente a la primera llave, de la forma
\\[Texto]
14.10.2. Color
Para cambiar el color del texto, se utiliza la etiqueta colour, usada de la siguiente
forma:
C14
[colour=FFFF0000]
Esta etiqueta colorea el texto que la siguiese de color rojo. El formato en el que
se expresa el color es ARGB de 8 bits, es decir AARRGGBB. El primer parmetro
expresa la componente de transparencia alpha, y el resto la componente RGB.
14.10.3. Formato
Para cambiar el formato de la fuente (tipo de letra, negrita o cursiva, por ejemplo)
es ms complejo ya que se necesitan los propios cheros que denan ese tipo de
fuente, y adems deben estar denidos en el script Font.
Estando seguro de tener los archivos .font y de tenerlos incluidos dentro del chero
del esquema, se puede cambiar el formato utilizando la etiqueta:
[font=Arial-Bold-10]
Esta en concreto corresponde al formato en negrita del tipo de letra Arial, de ta-
mao 10. El nombre que se le indica a la etiqueta es el que se especic en el .scheme.
[imageset=set:<imageset> image:<image>]
Una vez ms, CEGUI trata las imgenes individuales como parte de un Imageset,
por lo que hay que indicarle el conjunto, y la imagen.
[406] CAPTULO 14. INTERACCIN Y WIDGETS
[image=set:TaharezLook image=CloseButtonNormal]
Adems, existe otra etiqueta poder cambiar el tamao de las imgenes insertadas.
El formato de la etiqueta es el siguiente:
[image-size=w:<width_value> h:<height_value>]
El valor del ancho y del alto se da en pxeles, y debe ponerse antes de la etiqueta
que inserta la imagen. Para mostrar las imgenes en su tamao original, se deben poner
los valores de width y height a cero.
[vert-alignment=<tipo_de_alineamiento>]
14.10.6. Padding
El padding consiste en reservar un espacio alrededor del texto que se desee. Para
denirlo, se indican los pxeles para el padding izquierdo, derecho, superior e inferior.
As, adems de el espacio que ocupe una determinada cadena, se reservar como un
margen el espacio indicado en el padding. Viendo la aplicacin de ejemplo se puede
apreciar mejor este concepto.
El formato de la etiqueta es:
C14
14.10.7. Ejemplo de texto formateado
El siguiente es un ejemplo que muestra algunas de las caractersticas que se han
descrito. En la Figura 14.11 se aprecia el acabado nal. El siguiente es el layout utili-
zado:
Y el siguiente listado muestra cada una de las cadenas que se han utilizado, junto
a las etiquetas:
2
3 "El tipo de letra puede [font=Batang-26]cambiar de un momento a
otro, [font=fkp-16]y sin previo aviso!"
4
5 "Si pulsas aqui [image-size=w:40 h:55][image=set:TaharezLook
image:CloseButtonNormal] no pasara nada :("
6
7 "[font=Batang-26] Soy GRANDE, [font=DejaVuSans-10][vert-
alignment=top] puedo ir arriba, [vert-alignment=bottom]o
abajo, [vert-alignment=centre]al centro..."
8
9 "En un lugar de la [padding=l:20 t:15 r:20 b:15]Mancha[padding=l
:0 t:0 r:0 b:0], de cuyo nombre no quiero acordarme, no ha
mucho..."
La primera cadena (lnea 1) utiliza las etiquetas del tipo colour para cambiar el
color del texto escrito a partir de ella. Se utilizan los colores rojo, verde y azul, en ese
orden.
La segunda (lnea 3) muestra cmo se pueden utilizar las etiquetas para cambiar
totalmente el tipo de fuente, siempre y cuando estn denidos los recursos .font y estos
estn reejados dentro el .scheme.
La tercera (lnea 5) muestra una imagen insertada en medio del texto, y adems
redimensionada. Para ello se utiliza la etiqueta de redimensionado para cambiar el ta-
mao a 40x55, y despus inserta la imagen CloseButtonNormal, del conjunto Taha-
rezLook
La cuarta (lnea 7) muestra una cadena con un texto (Soy GRANDE) de un tipo
de fuente con un tamao 30, y el resto con un tamao 10. Para el resto del texto, sobra
espacio vertical, por lo que se utiliza la etiqueta vertical-alignment para indicar dnde
posicionarlo.
Por ltimo, la quinta cadena (lnea 9), utiliza padding para la palabra Mancha. A
esta palabra se le reserva un margen izquierdo y derecho de 20 pxeles, y un superior
e inferior de 15.
Materiales y Texturas
15
Carlos Gonzlez Morcillo
Iluminacin
E
Local n este captulo estudiaremos los conceptos fundamentales con la denicin de
materiales y texturas. Introduciremos la relacin entre los modos de sombrea-
L do y la interaccin con las fuentes de luz, describiendo los modelos bsicos
Punto
Vista
soportados en aplicaciones interactivas. Para nalizar, estudiaremos la potente apro-
ximacin de Ogre para la denicin de materiales, basndose en los conceptos de
tcnicas y pasadas.
15.1. Introduccin
Los materiales describen las propiedades fsicas de los objetos relativas a cmo
Iluminacin reejan la luz incidente. Obviamente, el aspecto nal obtenido ser dependiente tanto
Global de las propiedades del material, como de la propia denicin de las fuentes de luz. De
este modo, materiales e iluminacin estn ntimamente relacionados.
L
Desde los inicios del estudio de la ptica, investigadores del campo de la fsica
Punto
Vista han desarrollado modelos matemticos para estudiar la interaccin de la luz en las
supercies. Con la aparicin del microprocesador, los ordenadores tuvieron suciente
potencia como para poder simular estas complejas interacciones.
As, usando un ordenador y partiendo de las propiedades geomtricas y de mate-
riales especicadas numricamente es posible simular la reexin y propagacin de la
luz en una escena. A mayor precisin, mayor nivel de realismo en la imagen resultado.
409
[410] CAPTULO 15. MATERIALES Y TEXTURAS
a) b)
Figura 15.2: Ejemplo de resultado utilizando un modelo de iluminacin local y un modelo de iluminacin
global con la misma conguracin de fuentes de luz y propiedades de materiales. a) En el modelo de ilumi-
nacin local, si no existen fuentes de luz en el interior de la habitacin, los objetos aparecern totalmente a
oscuras, ya que no se calculan los rebotes de luz indirectos. b) Los modelos de iluminacin global tienen
en cuenta esas contribuciones relativas a los rebotes de luz indirectos.
+ + + =
Sombreado difuso. Muchos objetos del mundo real tienen una acabado emi-
nentemente mate (sin brillo). Por ejemplo, el papel o una tiza pueden ser su-
percies con sombreado principalmente difuso. Este tipo de materiales reejan
la luz en todas las direcciones, debido principalmente a las rugosidades mi-
C15
croscpicas del material. Como efecto visual, el resultado de la iluminacin es
mayor cuando la luz incide perpendicularmente en la supercie. La intensidad
nal viene determinada por el ngulo que forma la luz y la supercie (es inde-
pendiente del punto de vista del observador). En su expresin ms simple, el
modelo de reexin difuso de Lambert dice que el color de una supercie es
proporcional al coseno del ngulo formado entre la normal de la supercie y el
vector de direccin de la fuente de luz (ver Figura 15.3).
Sombreado especular. Es el empleado para simular los brillos de algunas su-
percies, como materiales pulidos, pintura plstica, etc. Una caracterstica prin-
cipal del sombreado especular es que el brillo se mueve con el observador. Esto
implica que es necesario tener en cuenta el vector del observador. La dureza del
brillo (la cantidad de reejo del mismo) viene determinada por un parmetro h.
A mayor valor del parmetro h, ms concentrado ser el brillo. El comporta-
miento de este modelo de sombreado est representado en la Figura 15.5, donde
r es el vector reejado del l (forma el mismo ngulo con n) y e es el vector
que se dirige del punto de sombreado al observador. De esta forma, el color nal
de la supercie es proporcional al ngulo .
L
Sombreado ambiental. Esta componente bsica permite aadir una aproxima-
n cin a la iluminacin global de la escena. Simplemente aade un color base
r independiente de la posicin del observador y de la fuente de luz. De esta for-
ma se evitan los tonos absolutamente negros debidos a la falta de iluminacin
l e global, aadiendo este trmino constante. En la Figura 15.4 se puede ver la
componente ambiental de un objeto sencillo.
Sombreado de emisin. Finalmente este trmino permite aadir una simula-
Figura 15.5: En el modelo de som-
breado especular, el color c se ob-
cin de la iluminacin propia del objeto. No obstante, debido a la falta de inter-
tiene como c (r e)h . Los vecto- accin con otras supercies (incluso con las caras poligonales del propio obje-
res r y e deben estar normalizados. to), suele emplearse como una alternativa al sombreado ambiental a nivel local.
El efecto es como tener un objeto que emite luz pero cuyos rayos no interactan
con ninguna supercie de la escena (ver Figura 15.4).
[412] CAPTULO 15. MATERIALES Y TEXTURAS
Una de las caractersticas interesantes de los modelos de mapeado de texturas Figura 15.6: Denicin de una sen-
(tanto ortogonales como mediante mapas paramtricos UV) es la indepen- cilla textura procedural que dene
dencia de la resolucin de la imagen. De este modo es posible tener textu- bandas de color dependiendo del
ras de diferentes tamaos y emplearlas aplicando tcnicas de nivel de detalle valor de coordenada Z del punto
(LOD). As, si un objeto se muestra a una gran distancia de la cmara es po- 3D.
sible cargar texturas de menor resolucin que requieran menor cantidad de
memoria de la GPU.
u=0.35 Asociacin de
coordendas de textura
UV a cada vrtice de
cada cara del modelo.
Figura 15.7: Asignacin de coordenadas UV a un modelo poligonal. Esta operacin suele realizarse con el
soporte de alguna herramienta de edicin 3D.
C15
Proyeccin Proyeccin Proyeccin Proyeccin
Textura a Mapear Plana Cbica Cilndrica Esfrica
Figura 15.8: Mtodos bsicos de mapeado ortogonal sobre una esfera. Los bordes marcados con lneas de
colores en la textura a mapear en la izquierda se proyectan de forma distinta empleado diversos mtodos de
proyeccin.
Material
Technique 1 Technique 2 Technique N
Pass 2 Pass 2
... Pass 2
Figura 15.10: Descripcin de un material en base a tcnicas y pasadas. El nmero de tcnicas y pasadas
asociadas a cada tcnica puede ser diferente.
Con la idea de realizar estas optimizaciones, Ogre dene que por defecto los mate-
riales son compartidos entre objetos. Esto implica que el mismo puntero que referencia
a un material es compartido por todos los objetos que utilizan ese material. De este
modo, si queremos cambiar la propiedad de un material de modo que nicamente afec-
te a un objeto es necesario clonar este material para que los cambios no se propaguen
al resto de objetos.
Los materiales de Ogre se denen empleando tcnicas y esquemas. Una Tcnica
puede denirse como cada uno de los modos alternativos en los que puede renderi-
zarse un material. De este modo, es posible tener, por ejemplo, diferentes niveles de
detalle asociados a un material. Los esquemas agrupan tcnicas, permitiendo denir
nombres y grupos que identiquen esas tcnicas como alto nivel de detalle, medio
rendimiento, etc.
15.4.1. Composicin
Un material en Ogre est formado por una o varias tcnicas, que contienen a su
vez una o varias Pasadas (ver Figura 15.10). En cada momento slo puede existir
una tcnica activa, de modo que el resto de tcnicas no se emplearn en esa etapa de
render.
Una vez que Ogre ha decidido qu tcnica emplear, generar tantas pasadas (en el
mismo orden de denicin) como indique el material. Cada pasada dene una opera-
cin de despliegue en la GPU, por lo que si una tcnica tiene cuatro pasadas asociadas
a un material tendr que desplegar el objeto tres veces en cada frame.
Las Unidades de Textura (Texture Unit) contienen referencias a una nica textura.
Esta textura puede ser generada en cdigo (procedural), puede ser obtenida mediante
un archivo o mediante un ujo de vdeo. Cada pasada puede utilizar tantas unidades
de textura como sean necesarias. Ogre optimiza el envo de texturas a la GPU, de
modo que nicamente se descargar de la memoria de la tarjeta cuando no se vaya a
utilizar ms.
Atributo Descripcin
lod_values Lista de distancias para aplicar diferentes niveles de detalle. Est relacionado
con el campo lod_strategy (ver API de Ogre).
receive_shadows Admite valores on (por defecto) y off. Indica si el objeto slido puede recibir
sombras. Los objetos transparentes nunca pueden recibir sombras (aunque s
arrojarlas, ver el siguiente campo).
transparency_casts_shadows Indica si el material transparente puede arrojar sombras. Admite valores on y
off (por defecto).
set_texture_alias Permite crear alias de un nombre de textura. Primero se indica el nombre del
alias y despus del nombre de la textura original.
C15
depth_check on | off Por defecto on. Indica si se utilizar el depth-buffer para comprobar la profun-
didad.
lighting on | off Por defecto on. Indica si se emplear iluminacin dinmica en la pasada.
shading (Ver valores) Indica el tipo de mtodo de interpolacin de iluminacin a nivel de vrtice. Se
especican los valores de interpolacin de sombreado at o gouraud o phong.
Por defecto se emplea el mtodo de gouraud.
polygon_mode (Ver valores) Indica cmo se representarn los polgonos. Admite tres valores: solid (por
defecto), wireframe o points.
Cuadro 15.2: Atributos generales de las Pasadas. Ver API de Ogre para una descripcin completa de todos los atributos soportados.
Figura 15.13: Un material ms complejo que incluye la denicin de una texture_unit con diversas com-
ponentes de sombreado.
Los mapas de entorno se utilizan para simular la reexin del mundo (representado
en el mapa) dependiendo de la relacin entre las normales de las caras poligionales y la
posicin del observador. En este caso, se indica a Ogre que el tipo de mapa de entorno
es esfrico (tipo ojo de pez, ver Figura 15.12). El operador de colour_op empleado
indica cmo se combinar el color de la textura con el color base del objeto. Existen
diversas alternativas; mediante modulate se indica a Ogre que multiplique el color base
(en este caso, un color amarillo indicado en la componente difusa del color) y el color
del mapa.
A continuacin se describen algunas de las propiedades generales de los mate-
riales. En la tabla 15.1 se resumen los atributos generales ms relevantes empleados
en la denicin de materiales, mientras que las tablas 15.2 y 15.3 resumen los atri-
butos globales ms utilizados en la denicin de las pasadas y unidades de textura
resepectivamente. Cada pasada tiene asociada cero o ms unidades de textura. En los
siguientes ejemplos veremos cmo combinar varias pasadas y unidades de textura para
denir materiales complejos en Ogre.
15.5. Mapeado UV en Blender [417]
Atributo Descripcin
texture Especica el nombre de la textura (esttica) para esta texture_unit. Permite especicar el tipo, y el
uso o no de canal alpha separado.
anim_texture Permite utilizar un conjunto de imgenes como textura animada. Se especica el nmero de frames
y el tiempo (en segundos).
ltering Filtro empleado para ampliar o reducir la textura. Admite valores entre none, bilinear (por defec-
to), trilinear o anisotropic.
colour_op Permite determinar cmo se mezcla la unidad de textura. Admite valores entre replace, add, mo-
dulate (por defecto) o alpha_blend.
colour_op_ex Versin extendida del atributo anterior, que permite especicar con mucho mayor detalle el tipo
de mezclado.
env_map Uso de mapa de entorno. Si se especica (por defecto est en off, puede tomar valores entre sphe-
rical, planar, cubic_reection y cubic_normal.
rotate Permite ajustar la rotacin de la textura. Requiere como parmetro el ngulo de rotacin (en contra
de las agujas del reloj).
scale Ajusta la escala de la textura, especicando dos factores (en X e Y).
Cuadro 15.3: Atributos generales de las Unidades de Textura. Ver API de Ogre para una descripcin completa de todos los atributos soportados
C15
es el mapeado paramtrico UV. Como hemos visto, a cada vrtice del modelo (con
El mapeado UV es el estndar en coordenadas X,Y,Z) se le asocian dos coordenadas paramtricas 2D (U,V).
desarrollo de videojuegos, por lo
que prestaremos especial atencin Los modelos de mapeado ortogonales no se ajustan bien en objetos complejos.
en este documento. Por ejemplo, la textura asociada a una cabeza de un personaje no se podra mapear
adecuadamente empleando modelos de proyeccin ortogonal. Para tener el realismo
necesario en modelos pintados manualmente (o proyectando texturas basadas en foto-
grafas) es necesario tener control sobre la posicin nal de cada pxel sobre la textura.
El mapa UV describe qu parte de la textura se asociar a cada polgono del modelo,
de forma que, mediante una operacin de despliegue (unwrap) del modelo obtenemos
la equivalencia de la supercie en el plano 2D.
En esta seccin estudiaremos con ms detalle las opciones de Blender para trabajar
con este tipo de coordenadas.
Como vimos en el captulo 11, para comenzar es necesario aplicar una operacin
de despliegue del modelo para obtener una o varias regiones en la ventana de UV/Ima-
ge Editor . Esta operacin de despliegue se realiza siempre en modo edicin. Con
el objeto en modo edicin,
en los botones de edicin , y tras aplicar el despliegue
(Unwrap) (tecla U ), aparece un nuevo
subpanel en el panel de herramientas (Tool
Shelf ), accesible mediante la tecla T (ver Figura 15.15) que controla el modo en el
que se realiza el despliegue del modelo. Este panel aparece mientras el objeto est en
Figura 15.14: El problema del des- modo de edicin, y es dependiente del modo de despliegue elegido (por ejemplo, el de
pliegue se ha afrontado desde los la Figura 15.15 contiene las opciones de Unwrap, pero otros modos de despliegue ten-
orgenes de la cartografa. En la drn otras opciones asociadas). A continuacin describimos las principales opciones
imagen, un mapa de 1482 muestra que pueden encontrarse en este tipo de subpaneles:
una proyeccin del mundo conoci-
do hasta el momento.
Angle Based | Conformal. Dene el mtodo de clculo de la proyeccin. El
Isla UV basado en ngulo crea una nueva isla cuando el ngulo entre caras vecinas sea
relevante. En el modo conformal se realiza dependiendo del mtodo de pro-
Se dene una isla en un mapa UV
como cada regin que contiene vr-
yeccin seleccionado. El mtodo basado en ngulo ofrece, en general, mejores
tices no conectados con el resto. resultados.
[418] CAPTULO 15. MATERIALES Y TEXTURAS
Fill Holes. Intenta ordenar todas las islas para evitar que queden huecos en la
textura sin ninguna cara UV.
Correct Aspect. Permite el escalado de los vrtices desplegados en UV para
ajustarse a la relacin de aspecto de la textura a utilizar.
Margin. Dene la separacin mnima entre islas.
Use Subsurf Data. Utiliza el nivel de subdivisin especicado en Subsurf Tar-
get para el clculo de las coordenadas UV en lugar de la malla original (Red de
Control).
Transform Correction. Corrige la distorsin del mapa UV mientras se est
editando.
Cube Size. Cuando se emplea una proyeccin cbica, este parmetro indica el
porcentaje de la imagen que se emlpear por el mapa UV (por defecto el 100 %).
Figura 15.15: Opciones del panel
Radius. Cuando se emplea el mtodo de proyeccin Cylinder projection, este Unwrap.
parmetro dene el radio del cilindro de proyeccin. A menores valores, las
coordenadas del objeto aparecern estiradas en el eje Y.
Align: Polar ZX | Polar ZY. Determina el plano frontal de proyeccin en los
mtodos cilndrico y esfrico.
a b c d
e f g h
Figura 15.19: Resultado obtenido tras aplicar diferentes mtodos de despliegue (unwrap) al modelo de
Suzanne con todas las caras seleccionadas: a) Unwrap, b) Cube, c) Cylinder, d) Sphere, e) Project from
C15
view, f) Reset, g) Lightmap y h) Smart UV.
Figura 15.23: Utilizacin de costuras y despliegue del modelo utilizando diferentes tcnicas de desplie-
gue. En la imagen de la izquerda se marcan perfectamente los seams denidos para recortar el modelo
adecuadamente.
15.5.1. Costuras
En el despliegue de mallas complejas, es necesario ayudar a los mtodos de des-
pliegue deniendo costuras (seams). Estas costuras servirn para denir las zonas de
recorte, guiando as al mtodo de desplegado.
Para denir una costura basta con elegir las aristas que la denen en modo edicin,
tal y como se muestra en la Figura 15.23 . La seleccin de bucles de aristas (como en
el caso del bote de spray del modelo) puede realizarse cmodamente seleccionando
una de las aristas del bucle y empleando la combinacin de teclas Control E
Edge Loop. Cuando tenemos la zona de recorte seleccionada, deniremos la costura
mediante la combinacin Control E Mark Seam. En este men aparece igualmente
la opcin para eliminar una costura Clear Seam.
15.6. Ejemplos de Materiales en Ogre [421]
C15
proyectar la misma textura que en el suelo de la escena.
En la denicin del Material4 se utiliza una textura de tipo cebra donde las bandas
Figura 15.22: Subpanel de opcio- negras son totalmente transparentes. En la pasada de este material se utiliza el atributo
nes de Texture Paint en el que puede scene_blend que permite especicar el tipo de mezclado de esta pasada con el resto de
elegirse el color de pintado, tamao contenido de la escena. Como veremos en el ejemplo del Material6, la pricipal dife-
del pincel, opacidad, etc... rencia entre las operaciones de mezclado a nivel de pasada o a nivel de textura es que
las primeras denen la mezcla con el contenido global de la escena, mientras que las
segundas estn limitadas entre capas de textura. Mediante el modicador alpha_blend
se indica que utilizaremos el valor alpha de la textura. A continuacin estudiaremos
las opciones permitidas por el atributo scene_blend en sus dos versiones.
El formato de scene_blend permite elegir cuatro parmetros en su forma bsica:
Figura 15.24: Ejemplos de denicin de algunos materiales empleando diversos modos de composicin en
Ogre.
C15
utilizar su valor directamente sin modicacin. La operacin blend_current_alpha
(como veremos en el siguiente ejemplo) utiliza la informacin de alpha de las capas
anteriores. La operacin modulate multiplica los valores de src1 y src2.
El Material6 dene tres capas de textura. La primera carga una textura de csped.
La segunda capa carga la textura de cebra con transparencia, e indica que la combinar
con la anterior empleando la informacin de alpha de esta segunda textura. Finalmen-
te la tercera capa utiliza el operador extendido de colour_op_ex, donde dene que
combinar el color de la capa actual (indicado como primer parmetro en src_texture
con el obtenido en las capas anteriores src_current. El parmetro blend_current_alpha
multiplica el alpha de src2 por (1-alpha(src1)).
Los ejemplos de la Figura 15.27 utilizan algunas de las opciones de animacin
existentes en las unidades de textura. El Material7 por ejemplo dene dos capas de
textura. La primera aplica la textura del suelo al objeto, utilizando el atributo rota-
te_anim. Este atributo requiere un parmetro que indica el nmero de revoluciones
por segundo (empleando velocidad constante) de la textura.
La composicin de la segunda capa de textura (que emplea un mapeado de en-
torno esfrico) se realiza utilizando el atributo colour_op_ex estudiado anteriormente.
La versin de la operacin modulate_x2 multiplica el resultado de modulate (multi-
plicacin) por dos, para obtener un resultado ms brillante.
En el ejemplo del Material8 se denen dos capas de textura, ambas con anima-
cin. La segunda capa utiliza una operacin de suma sobre la textura de cebra sin
opacidad, de modo que las bandas negras se ignoran. A esta textura se aplica una rota-
cin de 0.15 revoluciones por segundo. La primera capa utiliza el atributo scroll_anim
que permite denir un desplazamiento constante de la textura en X (primer parmetro)
y en Y (segundo parmetro). En este ejemplo la textura nicamente se desplaza en el
eje X. De igual modo, la primera capa emplea wave_xform para denir una animacin
basado en una funcin de onda. En este caso se utiliza una funcin senoidal indicando
los parmetros requeridos de base, frecuencia, fase y amplitud (ver el manual de Ogre
para ms detalles sobre el uso de la funcin).
[424] CAPTULO 15. MATERIALES Y TEXTURAS
a)
b) c) d)
Figura 15.28: Construccin del ejemplo de RTT. a) Modelo empleado para desplegar la textura de RTT.
El modelo se ha denido con tres materiales; uno para la carcasa, otro para las letras del logotipo (ambos
C15
desplegados en c), y uno para la pantalla llamado pantallaTV, desplegado en b). En d) se muestra el
resultado de la ejecucin del programa de ejemplo.
33 }
En la lnea 5 se obtiene un puntero a un RenderTexture que es una especializacin
de la clase RenderTarget especca para renderizar sobre una textura.
Puede haber varios RenderTargets que generen resultados sobre la misma textura.
A este objeto de tipo RenderTexture le asociamos una cmara en la lnea 13 (que ha
sido creada en las lneas 7-11 ), y conguramos las propiedades especdas del view-
port asociado (como que
se limpie en cada frame en la lnea 14 , y que no represente
los overlays en la lnea 16 ).
En la lnea 17 indicamos que la textura se actualice automticamente (gestionada
por el bucle principal de Ogre). En otro caso, ser necesario ejecutar manualmente el
mtodo update del RenderTarget.
Las lneas 19-25 declaran manualmente el material sobre el que emplearemos la
Texture Unit con la textura anteriormente denida.
En creamos un material
21 (lnea 22 ). Esa
19-20
llamado RttMat, con una nica tcnica (lnea ) y una pasada
pasada dene sus propiedades en las lneas 23-25 , y aade como TextureUnit a la
textura RttT.
El ltimo bucle recorre todas las SubEntities que forman a la entidad de la tele-
visin. En el caso de que el material asociado a la subentidad sea el llamado pan-
tallaTV (lnea ), le asignamos el material que hemos creado anteriormente (lnea
32 ).
31
C15
Listado 15.4: Utilizacin en MyApp.cpp
1 _textureListener = new MyTextureListener(entTV);
2 rtex->addListener(_textureListener);
Para concluir, en las lneas 42-43 se conguran los aspectos relativos a la cmara
Mirror en relacin al plano de reexin. La llamada a enableReection hace que se
modique el Frustum de la cmara de modo que renderice empleando la reexin con
respecto del plano que se le pasa como argumento.
Finalmente, la llamada a enableCustomNearClipPlane permite recortar la geome-
tra situada debajo del plano pasado como argumento, de modo que nicamente la
geometra que est situada sobre el plano ser nalmente desplegada en el reejo,
evitando as errores de visualizacin.
E
n el captulo anterior hemos estudiado algunas de las caractersticas relativas a
la denicin de materiales bsicos. Estas propiedades estn directamente rela-
cionadas con el modelo de iluminacin y la simulacin de las fuentes de luz
que realicemos. Este captulo introduce algunos conceptos generales sobre ilumina-
cin en videojuegos, as como tcnicas ampliamente utilizadas para la simulacin de
la iluminacin global.
16.1. Introduccin
Una pequea parte de los rayos de luz son visibles al ojo humano. Aquellos rayos
que estn denidos por una onda con longitud de onda entre 700 y 400nm. Variando
las longitudes de onda obtenemos diversos colores.
En grcos por computador es habitual emplear los denominados colores-luz, don-
de el Rojo, Verde y Azul son los colores primarios y el resto se obtienen de su combi-
nacin. En los colores-luz el color blanco se obtiene como la suma de los tres colores
bsicos.
Figura 16.1: Gracias a la proyec- El RGB es un modelo clsico de este tipo. En el mundo fsico real se trabaja
cin de sombras es posible cono- con colores-pigmento, donde el Cyan, el Magenta y el Amarillo forman los colores
cer la posicin relativa entre obje- primerarios. La combinacin de igual cantidad de los tres colores primarios obtiene el
tos. En la imagen superior no es po- color negro1 . As, el CMYK (Cyan Magenta Yellow Key) empleado por las impresoras
sible determinar si el modelo de Su- es un clsico modelo de este tipo. De un modo simplicado, podemos denir los pasos
zanne reposa sobre algn escaln o
ms relevantes para realizar la representacin de una escena sinttica:
est otando en el aire. La imagen
inferior, gracias al uso de sombras 1 En realidad se obtiene un tono parduzco, por lo que habitualmente es necesario incorporar el negro
elimina esa ambigedad visual. como color primario.
429
[430] CAPTULO 16. ILUMINACIN
1. La luz es emitida por las fuentes de luz de la escena (como el sol, una lmpara de
luz situada encima de la mesa, o un panel luminoso en el techo de la habitacin).
2. Los rayos de luz interactan con los objetos de la escena. Dependiendo de las
propiedades del material de estos objetos, parte de la luz ser absorbida y otra
parte reejada y propagada en diversas direcciones. Todos los rayos que no son
totalmente absorbidos continuarn rebotando en el entorno.
3. Finalmente algunos rayos de luz sern capturados por un sensor (como un ojo
humano, el sensor CCD (Charge-Coupled Device) de una cmara digital o una
pelcula fotogrca).
Las fuentes puntuales (point lights) irradian energa en todas las direcciones
a partir de un punto que dene su posicin en el espacio. Este tipo de fuentes Fuente
permite variar su posicin pero no su direccin. En realidad, las fuentes de luz Direccional
puntuales no existen como tal en el mundo fsico (cualquier fuente de luz tiene
asociada un rea, por lo que para realizar simulaciones realistas de la ilumina-
cin tendremos que trabajar con fuentes de rea. Ogre dene este tipo de fuente
como LT_POINT.
Uno de los tipos de fuentes ms sencillo de simular son las denominadas fuentes
direccionales (directional lights), que pueden considerarse fuentes situadas a
una distancia muy grande, por lo que los rayos viajan en una nica direccin
Fuente Foco
en la escena (son paralelos entre s). El sol podra ser modelado mediante una
fuente de luz direccional. De este modo, la direccin viene determinada por un
Figura 16.2: Tipos de fuentes de
vector l (especicado en coordenadas universales). Este tipo de fuentes de luz luz utilizadas en aplicaciones in-
no tienen por tanto una posicin asociada (nicamente direccin). Este tipo de teractivas. Ogre soporta nicamente
fuente est descrito como LT_DIRECTIONAL en Ogre. estos tres tipos de fuentes bsicos.
16.3. Sombras Estticas Vs Dinmicas [431]
Finalmente los focos (spot lights) son en cierto modo similares a las fuentes
de luz puntuales, pero aadiendo una direccin de emisin. Los focos arrojan
luz en forma cnica o piramidal en una direccin especca. De este modo,
requieren un parmetro de direccin, adems de dos ngulos para denir los
conos de emisin interno y externo. Ogre las dene como LT_SPOTLIGHT.
C16
Ogre soporta dos tcnicas bsicas de sombreado dinmico: sombreado empleando
el Stencil Buffer y mediante Mapas de Texturas (ver Figura 16.4). Ambas aproxima-
ciones admiten la especicacin como Additive o Modulative.
a Las tcnicas de tipo Modulative nicamente oscurecen las zonas que quedan en
sombra, sin importar el nmero de fuentes de luz de la escena. Por su parte, si la
tcnica se especica de tipo Additive es necesario realizar el clculo por cada fuente
de luz de modo acumulativo, obteniendo un resultado ms preciso (pero ms costoso
computacionalmente).
Cada tcnica tiene sus ventajas e inconvenientes (como por ejemplo, el relativo a
la resolucin en el caso de los Mapas de Textura, como se muestra en la Figura 16.3).
b No es posible utilizar varias tcnicas a la vez, por lo que deberemos elegir la tcnica
que mejor se ajuste a las caractersticas de la escena que queremos representar. En
Figura 16.3: Comparativa entre trminos generales, los mtodos basados en mapas de textura suelen ser ms precisos,
sombras calculadas por a) Mapas pero requieren el uso de tarjetas aceleradoras ms potentes. Veamos las caractersticas
de Texturas y b) Stencil Buffer. Las generales de las tcnicas de sombreado para estudiar a continuacin las caractersticas
basadas en mapas de texturas son
claramente dependientes de la re-
particulares de cada mtodo.
solucin del mapa, por lo que de-
ben evitarse con reas de proyec-
cin muy extensas.
[432] CAPTULO 16. ILUMINACIN
1. Para cada fuente de luz, obtener la lista de aristas de cada objeto que comparten
polgonos cuyo vector normal apunta hacia la fuente de luz y las que estn
opuestas a la misma. Estas aristas denen la silueta del objeto desde el punto
de vista de la fuente de luz.
2. Proyectar estas aristas de silueta desde la fuente de luz hacia la escena. Obtener
el volumen de sombra proyectado sobre los objetos de la escena.
3. Finalmente, utilizar la informacin de profundidad de la escena (desde el punto
de vista de la cmara virtual) para denir en el Stencil Buffer la zona que debe
recortarse de la sombra (aquella cuya profundidad desde la cmara sea mayor
que la denida en el ZBuffer). La Figura 16.5 muestra cmo la proyeccin del
volumen de sombra es recortado para aquellos puntos cuya profundidad es me-
nor que la relativa a la proyeccin del volumen de sombra.
a b c
d e f
Figura 16.4: Utilizacin de diversos modos de clculo de sombras y materiales asociados. a) Sombras
mediante Mapas de Texturas y Material simple. b) Sombras por Stencil Buffer y Material simple. c) Som-
bras por Stencil Buffer y Material con Ambient Occlusion precalculado. d) Sombras mediante Mapas de
C16
Texturas y Material basado en textura de mrmol procedural precalculada. e) Sombras por Stencil Buffer
y Material basado en textura de mrmol procedural precalculada. f) Sombras por Stencil Buffer y Material
combinado de c) + e) (Ambient Occlusion y Textura de Mrmol precalculados).
Fuente
Foco
+
Volumen de
Cmara sombra
proyectada
Volumen de Stencil
sombra buffer
proyectada
Figura 16.5: Proceso de utilizacin del Stencil Buffer para el recorte de la proyeccin del volumen de
sombra.
[434] CAPTULO 16. ILUMINACIN
b
a
Mapa Profundidad
desde la fuente de luz Pb
Pa
Resultado final
C16
10 light->setPosition(-5,12,2);
11 light->setType(Light::LT_SPOTLIGHT);
12 light->setDirection(Vector3(1,-1,0));
13 light->setSpotlightInnerAngle(Degree(25.0f));
14 light->setSpotlightOuterAngle(Degree(60.0f));
15 light->setSpotlightFalloff(0.0f);
16 light->setCastShadows(true);
17
18 Light* light2 = _sceneManager->createLight("Light2");
19 light2->setPosition(3,12,3);
20 light2->setDiffuseColour(0.2,0.2,0.2);
21 light2->setType(Light::LT_SPOTLIGHT);
22 light2->setDirection(Vector3(-0.3,-1,0));
23 light2->setSpotlightInnerAngle(Degree(25.0f));
24 light2->setSpotlightOuterAngle(Degree(60.0f));
25 light2->setSpotlightFalloff(5.0f);
26 light2->setCastShadows(true);
27
28 Entity* ent1 = _sceneManager->createEntity("Neptuno.mesh");
29 SceneNode* node1 = _sceneManager->createSceneNode("Neptuno");
30 ent1->setCastShadows(true);
31 node1->attachObject(ent1);
32 _sceneManager->getRootSceneNode()->addChild(node1);
33
34 // ... Creamos plano1 manualmente (codigo eliminado)
35
36 SceneNode* node2 = _sceneManager->createSceneNode("ground");
37 Entity* groundEnt = _sceneManager->createEntity("p", "plane1");
38 groundEnt->setMaterialName("Ground");
39 groundEnt->setCastShadows(false);
40 node2->attachObject(groundEnt);
41 _sceneManager->getRootSceneNode()->addChild(node2);
42 }
[436] CAPTULO 16. ILUMINACIN
En la lnea 2 se dene la tcnica de clculo de sombras que se utilizar por de-
se cambiar en tiempo de ejecucin empleando las
en el FrameListener
fecto, aunque
teclas 1 y 2 . Las lneas 2-3 denen propiedades generales de la escena, como el
color con el que se representarn las sombras y el color de la luz ambiental (que en
este ejemplo, debido a la denicin de los materiales, tendr especial importancia).
Las lneas 6-9 nicamente tienen relevancia en el caso del uso del mtodo de
clculo basado en mapas de textura. Si el mtodo utilizado es el de Stencil Buffer
simplemente sern ignoradas. La lnea 6 congura el nmero de texturas que se em-
plearn para calcular las sombras. Si se dispone de ms de una fuente de luz (como en
este ejemplo), habr que especicar el nmero de texturas a utilizar.
El tamao de la textura (cuadrada) se indica en la lnea 7 . A mayor tamao,
mejor resolucin en la sombra (pero mayor cantidad de memoria gastada). El tamao
por defecto es 512, y debe ser potencia de dos.
A continuacin en las lneas 9-26 se denen dos fuente de luz de tipo SPOTLIGHT.
Cada luz dene su posicin y rotacin.
En el caso de la segunda fuente luz se dene
de
adems un color difuso (lnea 20 ). El ngulo interno y externo 13-14 dene el cono
de iluminacin de la fuente. En ambas fuentes se ha activado la propiedad de que la
fuente de luz permita el clculo de sombras.
principal de la escena. En el caso del ltimo material denido para el objeto (tecla
0 ), se compone en dos capas de textura el color base (calculado mediante una textura
procedural) y una capa de iluminacin basada en Ambient Occlusion. La denicin
del material compuesto se muestra en la Figura 16.8. Gracias a la separacin de la
iluminacin y del color en diferentes mapas, es posible cambiar la resolucin indi-
vidualmente de cada uno de ellos, o reutilizar el mapa de iluminacin en diferentes
modelos que compartan el mismo mapa de iluminacin pero tengan diferente mapa de
color.
Multitud de juegos utilizan el preclculo de la iluminacin. Quake II y III utili-
zaron modelos de Radiosidad para crear mapas de iluminacin para simular de una
forma mucho ms realista el comportamiento fsico de la luz en la escena. Aunque
actualmente poco a poco se implantan las tcnicas de iluminacin dinmicas a nivel
de pxel, los mapas de iluminacin siguen siendo una opcin ampliamente utilizada.
Figura 16.8: Denicin del mate-
rial multicapa para componer una
textura con iluminacin de tipo Ab-
mientOcclusion con la textura de
color. Ambas texturas emplean un
despliegue automtico (tipo Light-
Map en Blender).
16.5. Mapas de Iluminacin [437]
Figura 16.9: Ejemplo de uso de un mapa de iluminacin para denir sombras suaves en un objeto. El mapa
de color y el mapa de iluminacin son texturas independiente que se combinan (multiplicando su valor) en
la etapa nal.
C16
minacin por pxel (per pixel lighting), de modo que por cada pxel de la escena se
calcula la contribucin real de la iluminacin. El clculo preciso de las sombras es
posible, aunque a da de hoy todava es muy costoso para videojuegos.
Los mapas de luz permiten precalcular la iluminacin por pxel de forma estti-
ca. Esta iluminacin precalculada puede ser combinada sin ningn problema con los
mtodos de iluminacin dinmicos estudiados hasta el momento. La calidad nal de
la iluminacin es nicamente dependiente del tiempo invertido en el preclculo de la
misma y la resolucin de las texturas.
De este modo, para cada cara poligonal del modelo se denen una o varias capas de
textura. La capa del mapa de iluminacin es nalmente multiplicada con el resultado
de las capas anteriores (como se puede ver en la Figura 16.9).
A diferencia de las texturas de color donde cada vrtice del modelo puede com-
partir las mismas coordenadas UV con otros vrtices, en mapas de iluminacin cada
vrtice de cada cara debe tener una coordenada nica. Los mapas de iluminacin se
cargan de la misma forma que el resto de texturas de la escena. En la Figura 16.8
hemos estudiado un modo sencillo de composicin de estos mapas de iluminacin,
[438] CAPTULO 16. ILUMINACIN
aunque pueden denirse otros mtodos que utilicen varias pasadas y realicen otros
modos de composicin. A continuacin estudiaremos dos tcnicas ampliamente utili-
zadas en el preclculo de la iluminacin empleando mapas de iluminacin: Ambient
Occlusion y Radiosidad.
C16
Estas tcnicas de ocultacin (obscurances) se desacoplan totalmente de la fase de
iluminacin local, teniendo lugar en una segunda fase de iluminacin secundaria difu-
sa. Se emplea un valor de distancia para limitar la ocultacin nicamente a polgonos
b c cercanos a la zona a sombrear mediante una funcin. Si la funcin toma valor de ocul-
tacin igual a cero en aquellos puntos que no superan un umbral y un valor de uno si
Figura 16.12: Descripcin esque- estn por encima del umbral, la tcnica de ocultacin se denomina Ambient Occlusion.
mtica del clculo de Ambient Oc- Existen multitud de funciones exponenciales que se utilizan para lograr estos efectos.
clusion. a) Esquema de clculo del
valor de ocultacin W (P ). b) Ren- La principal ventaja de esta tcnica es que es bastante ms rpida que las tcnicas
der obtenido con iluminacin lobal que realizan un clculo correcto de la iluminacin indirecta. Adems, debido a la
(dos fuentes puntuales). c) La mis- sencillez de los clculos, pueden realizarse aproximaciones muy rpidas empleando
ma conguracin que en b) pero la GPU, pudiendo utilizarse en aplicaciones interactivas. El principal inconveniente
con Ambient Occlusion de 10 mue-
es que no es un mtodo de iluminacin global y no puede simular efectos complejos
tras por pxel.
como casticas o contribuciones de luz entre supercies con reexin difusa.
Para activar el uso de Ambient Occlusion (AO) en Blender, en el grupo de botones
del mundo , en la pestaa Ambient Occlusion activamos el checkbox (ver Figura
16.13). La pestaa Gather permite elegir el tipo de recoleccin de muestras entre
trazado de rayos Raytrace o Aproximada Approximate (ver Figura 16.13).
El clculo de AO mediante Trazado de Rayos ofrece resultados ms precisos a
costa de un tiempo de render mucho mayor. El efecto del ruido blanco debido al n-
mero de rayos por pxel puede disminuirse a costa de aumentar el tiempo de cmputo
aumentando el nmero de muestras (Samples).
Falloff: Esta opcin controla el tamao de las sombras calculadas por AO. Si
est activa, aparece un nuevo control Strength que permite variar el factor de
atenuacin. Con valores mayores de Strength, la sombra aparece ms enfocada
(es ms pequea).
Add (Por defecto): El punto recibe luz segn los rayos que no se han chocado
con ningn objeto. La escena esta ms luminosa que la original sin AO.
Multiply: El punto recibe sombra segn los rayos que han chocado con algn
objeto. La escena es ms oscura que la original sin AO.
16.7. Radiosidad
En esta tcnica se calcula el intercambio de luz entre supercies. Esto se consigue
subdividiendo el modelo en pequeas unidades denominadas parches, que sern la
base de la distribucin de luz nal. Inicialmente los modelos de radiosidad calculaban
las interacciones de luz entre supercies difusas (aquellas que reejan la luz igual en
todas las direcciones), aunque existen modelos ms avanzados que tienen en cuenta
modelos de reexin ms complejos.
El modelo bsico de radiosidad calcula una solucin independiente del punto de
vista. Sin embargo, el clculo de la solucin es muy costoso en tiempo y en espacio
de almacenamiento. No obstante, cuando la iluminacin ha sido calculada, puede uti-
lizarse para renderizar la escena desde diferentes ngulos, lo que hace que este tipo
de soluciones se utilicen en visitas interactivas y videojuegos en primera persona ac-
tuales. En el ejemplo de la gura 16.15, en el techo de la habitacin se encuentran las
caras poligonales con propiedades de emisin de luz. Tras aplicar el proceso del clcu-
lo de radiosidad obtenemos la malla de radiosidad que contiene informacin sobre la
distribucin de iluminacin entre supercies difusas.
En el modelo de radiosidad, cada supercie tiene asociados dos valores: la intensi-
dad luminosa que recibe, y la cantidad de energa que emite (energa radiante). En este
algoritmo se calcula la interaccin de energa desde cada supercie hacia el resto. Si
Figura 16.14: El modelo de Radio-
tenemos n supercies, la complejidad del algoritmo ser O(n2 ). El valor matemtico sidad permite calcular el intercam-
que calcula la relacin geomtrica entre supercies se denomina Factor de Forma, y bio de luz entre supercies difusas,
se dene como: mostrando un resultado de render
correcto en el problema planteado
cosi cosj en la histrica Cornell Box.
Fij = Hij dAj (16.2)
r2
Siendo Fij el factor de forma de la supercie i a la supercie j, en el numerador de
la fraccin denimos el ngulo que forman las normales de las supercies, r2 mide
la distancia entre las supercies, Hij es el parmetro de visibilidad, que valdr uno si
la superce j es totalmente visible desde i, cero si no es visible y un valor entre uno
y cero segn el nivel de oclusin. Finalmente, dAj indica el rea de la superce j (no
tendremos el mismo resultado con una pequea supercie emisora de luz sobre una
superce grande que al contrario). Este factor de forma se suele calcular empleando
un hemi-cubo.
16.7. Radiosidad [441]
Figura 16.15: Ejemplo de aplicacin del mtodo de radiosidad sobre una escena simple. En el techo de
la habitacin se ha denido una fuente de luz. a) Malla original. b) Malla de radiosidad. c) Resultado del
proceso de renderizado.
C16
Ser necesario calcular n2 factores de forma (no cumple la propiedad conmutativa)
debido a que se tiene en cuenta la relacin de rea entre supercies. La matriz que
contiene los factores de forma relacionando todas las supercies se denomina Matriz
de Radiosidad. Cada elemento de esta matriz contiene un factor de forma para la
interaccin desde la supercie indexada por la columna hacia la supercie indexada
por la la (ver gura 16.16).
como una nueva malla poligonal que tiende a desenfocar los lmites de las sombras.
Como en videojuegos suelen emplearse modelos en baja poligonalizacin, y es po-
sible mapear esta iluminacin precalculada en texturas, el modelo de Radiosidad se
ha empleado en diveros ttulos de gran xito comercial. Actualmente incluso existen
motores grcos que calculan soluciones de radiosidad en tiempo real.
2
1 4
Figura 16.16: Esquema de la matriz de radiosidad. En cada posicin de la matriz se calcula el Factor de
Forma. La diagonal principal de la matriz no se calcula.
Necesario Blender 2.49b. Aunque la radiosidad es un mtodo muy empleado Figura 16.17: Escena de ejemplo
en videojuegos, fue eliminado de la implementacin de Blender a partir de la para el clculo de la Radiosidad.
versin 2.5. Probablemente vuelva a incorporarse en futuras versiones, pero
de momento es necesario utilizar versiones anteriores del programa. La ltima
versin que la incorpor (Blender 2.49b) puede ser descargada de la pgina
ocial http://download.blender.org/release/.
Figura 16.19: Opciones generales de Radiosidad (Paneles Radio Render, Radio Tool y Calculation.
Los elementos que emiten luz tendrn el campo Emit del material con un valor
mayor que 0. Los emisores de este ejemplo tienen un valor de emisin de 0.4.
Accedemos al men de radiosidad
dentro de los botones de sombreado .
Seleccionamos todas las mallas que forman nuestra escena A y pinchamos en el botn
Collect Meshes (ver Figura 16.19). La escena aparecer con colores slidos.
Hecho esto, pinchamos en Go . De esta forma comienza el proceso de clculo de
la
solucin de radiosidad. Podemos parar el proceso en cualquier momento pulsando
Escape , quedndonos con la aproximacin que se ha conseguido hasta ese instante.
Si no estamos satisfechos con la solucin de Radiosidad calculada, podemos eli-
minarla pinchando en Free Radio Data.
C16
En la Figura 16.19 se muestran algunas opciones relativas al clculo de la solucin
de radiosidad. El tamao de los Parches (PaMax - PaMin) determina el detalle nal
(cuanto ms pequeo sea, ms detallado ser el resultado nal), pero incrementamos el
tiempo de clculo. De forma similar ocurre con el tamao de los Elementos2 (ElMax
- ElMin). En Max Iterations indicamos el nmero de pasadas que Blender har en
el bucle de Radiosidad. Un valor 0 indica que haga las que estime necesarias para
minimizar el error (lo que es conveniente si queremos generar la malla de radiosidad
nal). MaxEl indica el nmero mximo de elementos para la escena. Hemires es el
tamao del hemicubo para el clculo del factor de forma.
Una vez terminado el proceso de clculo (podemos pararlo cuando la calidad sea
aceptable con Esc ), podemos aadir la nueva malla calculada reemplazando las crea-
das anteriormente (Replace Meshes) o aadirla como nueva a la escena (Add new
Meshes). Elegiremos esta segunda opcin, para aplicar posteriormente el resultado a
informacin
la malla en baja poligonalizacin. Antes de deseleccionar la malla con la
de radiosidad calculada, la moveremos a otra capa (mediante la tecla M ) para tener
los objetos mejor organizados. Hecho esto, podemos liberar la memoria ocupada por
estos datos pinchando en Free Radio Data.
El resultado de la malla de radiosidad se muestra en la Figura 16.20. Como se pue-
de comprobar, es una malla extremadamente densa, que no es directamente utilizable
en aplicaciones de tiempo real. Vamos a utilizar el Baking de Blender para utilizar esta
informacin de texturas sobre la escena original en baja poligonalizacin.
Por simplicidad, uniremos todos dela escena original en una nica
los objetos
malla (seleccionndolos todos A y pulsando Control J Join Selected Meshes). A con-
tinuacin, desplegaremos el objeto empleando el modo de despliegue de Light Map, y
Figura 16.20: Resultado de la ma- crearemos una nueva imagen que recibir el resultado del render Baking.
lla de radiosidad.
2 En Blender, cada Parche est formado por un conjunto de Elementos, que describen supercies de
Figura 16.21: Resultado de aplicacin del mapa de radiosidad al modelo en baja resolucin.
Ahora seleccionamos primero la malla de radiosidad, y despus con Shift pulsado
to Active
seleccionamos la malla en baja resolucin. Pinchamos en el botn Selected
de la pestaa Bake, activamos Full Render y pinchamos en el botn Bake . Con esto
debemos haber asignado el mapa de iluminacin de la malla de radiosidad al objeto
en baja poligonalizacin (ver Figura 16.21).
Exportacin y Uso de Datos de
Captulo 17
Intercambio
Carlos Gonzlez Morcillo
H
asta ahora hemos empleado exportadores genricos de contenido de anima-
cin, mallas poligonales y denicin de texturas. Estas herramientas facilitan
enormemente la tarea de construccin de contenido genrico para nuestros
videojuegos. En este captulo veremos cmo crear nuestros propios scripts de expor-
tacin y su posterior utilizacin en una sencilla demo de ejemplo.
17.1. Introduccin
En captulos anteriores hemos utilizado exportadores genricos de contenido para
su posterior importacin. En multitud de ocasiones en necesario desarrollar herra-
mientas de exportacin de elementos desde las herramientas de diseo 3D a formatos
de intercambio.
La comunidad de Ogre ha desarrollado algunos exportadores de geometra y ani-
maciones para las principales suites de animacin y modelado 3D. Sin embargo, estos
elementos almacenan su posicin relativa a su sistema de coordenadas, y no tienen en
cuenta otros elementos (como cmaras, fuentes de luz, etc...).
En esta seccin utilizaremos las clases denidas en el Captulo 6 del Mdulo 1
para desarrollar un potencial juego llamado NoEscapeDemo. Estas clases importan
contenido denido en formato XML, que fue descrito en dicho captulo.
A continuacin enumeraremos las caractersticas esenciales que debe soportar el
Figura 17.1: El Wumpus ser el exportador a desarrollar:
personaje principal del videojuego
demostrador de este captulo NoEs-
cape Demo.
445
[446] CAPTULO 17. EXPORTACIN Y USO DE DATOS DE INTERCAMBIO
Mltiples nodos. El mapa del escenario (ver Figura 17.4) describe un grafo.
Utilizaremos un objeto de tipo malla poligonal para modelarlo en Blender. Cada
vrtice de la malla representar un nodo del grafo. Los nodos del grafo podrn
ser de tres tipos: Nodo productor de Wumpus (tipo spawn), Nodo destructor de
Wumpus (tipo drain) o un Nodo genrico del grafo (que servir para modelar
las intersecciones donde el Wumpus puede cambiar de direccin).
Mltiples aristas. Cada nodo del grafo estar conectado por uno o ms nodos,
deniendo mltiples aristas. Cada arista conectar una pareja de nodos. En este
grafo, las aristas no tienen asociada ninguna direccin.
Mltiples cmaras animadas. El exportador almacenar igualmente las ani-
maciones denidas en las cmaras de la escena. En el caso de querer almacenar
cmaras estticas, se exportar nicamente un fotograma de la animacin. Para
cada frame de la animacin de cada cmara, el exportador almacenar la posi-
cin y rotacin (mediante un cuaternio) de la cmara en el XML.
La versin de Blender 2.65 utiliza Python 3.3, por lo que ser necesaria su insta-
lacin para ejecutar los scripts desarrollados. En una ventana de tipo Text Editor
bastar con situar el puntero del ratn
podemos editar los scripts. Para su ejecucin,
dentro de la ventana de texto y pulsar Alt P .
1 Resulta especialmente interesante la comparativa emprica de Python con otros len-
Aunque la versin 2.65 de Blender permite aadir nuevas propiedades a los obje-
tos de la escena, por mantener la compatibilidad con versiones anteriores lo haremos
codicndolo como parte del nombre. En la pestaa Object, es posible especicar el
nombre del objeto, como se muestra en la Figura 17.3. En nuestro ejemplo, se ha
utilizado el siguiente convenio de nombrado:
Cmaras. Las cmaras codican en el nombre (separadas por guin bajo), co-
mo primer parmetro el identicador de la misma (un ndice nico), y como
segundo parmetro el nmero de frames asociado a su animacin. De este mo-
do, la cmara llamada GameCamera_1_350 indica que es la primera cmara
Figura 17.3: Especicacin del
(que ser la utilizada en el estado de Juego), y que tiene una animacin denida
nombre del objeto en la pestaa Ob- de 350 frames.
ject.
Empty. Como no es posible asignar tipos a los vrtices de la malla poligonal
con la que se denir el grafo, se han aadido objetos de tipo Empty que ser-
virn para asociar los generadores (spawn) y destructores (drain) de Wumpus.
Estos objetos Empty tendrn como nombre el literal spawn o drain y a con-
tinuacin un nmero del 1 al 9.
(como hemos indicado anteriormente ser una malla poligonal), y 10 un valor que
utilizaremos como distancia mxima de separacin entre un vrtice del grafo y cada
Empty de la escena (para considerar que estn asociados).
C17
2
3 import bpy, os, sys
4 import mathutils
5 from math import *
6 from bpy import *
7
8 FILENAME = "output.xml" # Archivo XML de salida
9 GRAPHNAME = "Graph" # Nombre del objeto Mesh del grafo
10 EPSILON = 0.01 # Valor de distancia Epsilon
11
12 # --- isclose --------------------------------------------------
13 # Decide si un empty coincide con un vertice del grafo
14 # --------------------------------------------------------------
15 def isclose(empty, coord):
16 xo, yo, zo = coord
17 xd, yd, zd = empty.location
18 v = mathutils.Vector((xo-xd, yo-yd, zo-zd))
19 if (v.length < EPSILON):
20 return True
21 else:
22 return False
23
24 # --- gettype --------------------------------------------------
25 # Devuelve una cadena con el tipo del nodo del grafo
26 # --------------------------------------------------------------
27 def gettype (dv, key):
28 obs = [ob for ob in bpy.data.objects if ob.type == EMPTY]
29 for empty in obs:
30 empName = empty.name
31 if ((empName.find("spawn") != -1) or
32 (empName.find("drain") != -1)):
33 if (isclose(empty, dv[key])):
34 return type ="+ empName[:-1] +"
35 return type=""
[448] CAPTULO 17. EXPORTACIN Y USO DE DATOS DE INTERCAMBIO
36
37 ID1 = *2 # Identadores para el xml
38 ID2 = *4 # Solo con proposito de obtener un xml "bonito"
39 ID3 = *6
40 ID4 = *8
41
42 graph = bpy.data.objects[GRAPHNAME]
43
44 dv = {} # Diccionario de vertices
45 for vertex in graph.data.vertices:
46 dv[vertex.index+1] = vertex.co
47
48 de = {} # Diccionario de aristas
49 for edge in graph.data.edges: # Diccionario de aristas
50 de[edge.index+1] = (edge.vertices[0], edge.vertices[1])
51
52 file = open(FILENAME, "w")
53 std=sys.stdout
54 sys.stdout=file
55
56 print ("<?xml version=1.0 encoding=UTF-8?>\n")
57 print ("<data>\n")
58
59 # --------- Exportacion del grafo ---------------------
60 print ("<graph>")
61 for key in dv.keys():
62 print (ID1 + <vertex index=" + str(key) + " +
63 gettype(dv,key) +>)
64 x,y,z = dv[key]
65 print (ID2 + <x> %f</x> <y> %f</y> <z> %f</z> % (x,y,z))
66 print (ID1 + </vertex>)
67 for key in de.keys():
68 print (ID1 + <edge>)
69 v1,v2 = de[key]
70 print (ID2 + <vertex> %i</vertex> <vertex> %i</vertex>
71 % (v1,v2))
72 print (ID1 + </edge>)
73 print ("</graph>\n")
74
75 # --------- Exportacion de la camara -------------------
76 obs = [ob for ob in bpy.data.objects if ob.type == CAMERA]
77 for camera in obs:
78 camId = camera.name
79 camName = camId.split("_")[0]
80 camIndex = int(camId.split("_")[1])
81 camFrames = int (camId.split("_")[2])
82 print (<camera index=" %i" fps=" %i"> %
83 (camIndex, bpy.data.scenes[Scene].render.fps))
84 print (ID1 + <path>)
85 for i in range (camFrames):
86 cFrame = bpy.data.scenes[Scene].frame_current
87 bpy.data.scenes[Scene].frame_set(cFrame+1)
88 x,y,z = camera.matrix_world.translation
89 qx,qy,qz,qw = camera.matrix_world.to_quaternion()
90 print (ID2 + <frame index=" %i"> % (i+1))
91 print (ID3 + <position>)
92 print (ID4 + <x> %f</x> <y> %f</y> <z> %f</z> % (x,y,z))
93 print (ID3 + </position>)
94 print (ID3 + <rotation>)
95 print (ID4 + <x> %f</x> <y> %f</y> <z> %f</z> <w> %f</w> %
96 (qx,qy,qz,qw))
97 print (ID3 + </rotation>)
98 print (ID2 + </frame>)
99 print (ID1 + </path>)
100 print (</camera>)
101
102 print ("</data>")
103
104 file.close()
105 sys.stdout = std
17.1. Introduccin [449]
C17
la vaca para tipo (que indica que no es un nodo especial del grafo) en la lnea
35 .
La funcin auxiliar isclose (denida en las lneas 15-22 devolver True si el empty
est a una distancia menor que respecto del nodo del grafo. En otro caso, devolver
False. Para realizar esta comparacin, simplemente creamos un vector restando las
coordenadas del vrtice con la posicin del Empty y utilizamos directamente el atri-
buto de longitud (lnea 19 ) de la clase Vector de la biblioteca mathutils.
Trayectorias genricas La segunda parte del cdigo (denido en las lneas 76-100 ) se encarga de exportar
las animaciones asociadas a las cmaras. Segn el convenio explicado anteriormente,
el propio nombre de la cmara codica el ndice de la cmara (ver lnea 80 ) y el n-
Aunque en este ejemplo exportare-
mero de frames que contiene su animacin (ver lnea 81 ). En el caso de este ejemplo
mos trayectorias sencillas, el expor-
tar la posicin de la cmara en ca-
da frame nos permite tener un con- se han denido dos cmaras (ver Figura 17.4). La cmara del men inicial es esttica,
trol absoluto sobre la pose en cada por lo que dene en la parte relativa a los frames un valor de 1 (no hay animacin). La
instante. En la implementacin en
OGRE tendremos que utilizar algn cmara del juego describir rotaciones siguiendo una trayectoria circular.
mecanismo de interpolacin entre
cada pose clave.
[450] CAPTULO 17. EXPORTACIN Y USO DE DATOS DE INTERCAMBIO
spawn4
spawn1
drain2
spawn2
drain1
spawn3
Figura 17.4: Denicin del grafo asociado al juego NoEscapeDemo en Blender. En la imagen de la izquier-
da se han destacado la posicin de los Emptys auxiliares para describir el tipo especial de algunos nodos
(vrtices) del grafo. La imagen de la derecha muestra las dos cmaras de la escena y la ruta descrita para la
cmara principal del juego.
El nmero de frame se
modica directamente en el atributo frame_current del la
escena actual (ver lnea 86 ). Para cada frame se exporta la posicin de la cmara
(obtenida en la ltima columna de la matriz de transformacin del objeto, en la lnea
88 ), y el cuaternio de rotacin (mediante una conversin de la matriz en 89 ). La
Figura 17.5 muestra un fragmento del XML exportado relativo a la escena base del
ejemplo. Esta exportacin puede realizarse ejecutando el chero .py desde lnea de
rdenes, llamando a Blender con el parmetro -P <script.py> (siendo script.py el
nombre del script de exportacin).
Figura 17.5: Fragmento del resultado de la exportacin del XML del ejemplo.
Captulo 18
Animacin
Carlos Gonzlez Morcillo
E
n este captulo estudiaremos los fundamentos de la animacin por computador,
analizando los diversos mtodos y tcnicas de construccin, realizando ejem-
plos de composicin bsicos con Ogre. Se estudiar el concepto de Animation
State, exportando animaciones denidas previamente en Blender.
18.1. Introduccin
estroboscpico.
2 tambin, aunque menos extendido en el mbito de la animacin por computador se utiliza el trmino
cuadro o fotograma
451
[452] CAPTULO 18. ANIMACIN
Dependiendo del tipo de mtodo que se utilice para el clculo de los fotogramas
intermedios estaremos ante una tcnica de interpolacin u otra. En general, sue-
len emplearse curvas de interpolacin del tipo Spline, que dependiendo de sus
propiedades de continuidad y el tipo de clculo de los ajustes de tangente obten-
dremos diferentes resultados. En el ejemplo de la Figura 18.2, se han aadido
claves en los frames 1 y 10 con respecto a la posicin (localizacin) y rotacin
en el eje Z del objeto. El ordenador calcula automticamente la posicin y rota-
cin de las posiciones intermedias. La gura muestra las posiciones intermedias
calculadas para los frames 4 y 7 de la animacin.
Animacin Procedural. En esta categora, el control sobre el movimiento se
realiza empleando procedimientos que denen explcitamente el movimiento
como funcin del tiempo. La generacin de la animacin se realiza mediante
descripciones fsicas, como por ejemplo en visualizaciones cientcas, simula-
ciones de uidos, tejidos, etc... Estas tcnicas de animacin procedural sern
igualmente estudiadas a continuacin este mdulo curso. La simulacin din-
mica, basada en descripciones matemticas y fsicas suele emplearse en anima-
cin de acciones secundarias. Es habitual contar con animaciones almacenadas
a nivel de vrtice simulando comportamiento fsico en videojuegos. Sera el
equivalente al Baking de animaciones complejas precalculadas.
C18
ria.
Una vez realizada esta introduccin general a las tcnicas de animacin por compu-
tador, estudiaremos en la siguiente seccin los tipos de animacin y las caractersticas
generales de cada uno de ellos en el motor grco Ogre.
[454] CAPTULO 18. ANIMACIN
Posicin X
Posicin Y
Posicin Z
1 2 3 4 5 6 7 8 9 10 Frame
Figura 18.2: Ejemplo de uso de Frames Clave y Splines asociadas a la interpolacin realizada.
Ogre utiliza el trmino pista Track para referirse a un conjunto de datos alma-
cenados en funcin del tiempo. Cada muestra realizada en un determinado instante
de tiempo es un Keyframe. Dependiendo del tipo de Keyframes y el tipo de pista, se
denen diferentes tipos de animaciones.
Las animaciones asociadas a una Entidad se representan en un AnimationState.
Este objeto tiene una serie de propiedades que pueden ser consultadas:
18.2. Animacin en Ogre [455]
Hombro
Codo
Posicin
del Efector Base Mueca
Posicin
del Efector
Figura 18.3: Ejemplo de uso de Frames Clave y Splines asociadas a la interpolacin realizada.
C18
Activa. Es posible activar y consultar el estado de una animacin mediante las
llamadas a getEnabled y setEnabled.
Longitud. Obtiene en punto otante el nmero de segundos de la animacin,
mediante la llamada a getLength.
Posicin. Es posible obtener y establecer el punto de reproduccin actual de la
animacin mediante llamadas a getTimePosition y setTimePosition. El tiempo
se establece en punto otante en segundos.
Bucle. La animacin puede reproducirse en modo bucle (cuando llega al nal
continua reproduciendo el primer frame). Las llamadas a mtodos son getLoop
y setLoop.
Peso. Este parmetro controla la mezcla de animaciones, que ser descrita en la
seccin 18.4.
Figura 18.4: Principales ventanas de Blender empleadas en la animacin de objetos. En el centro se en-
cuentran las ventanas del NLA Editor y del Dope Sheet, que se utilizarn para denir acciones que sern
exportadas empleando el script de Exportacin a Ogre.
18.2.2. Controladores
La animacin basada en frames clave permite animar mallas poligionales. Me-
diante el uso de controladores se posicionan los nodos, deniendo una funcin. El
controlador permite modicar las propiedades de los nodos empleando un valor que
se calcula en tiempo de ejecucin.
Las acciones aparecern en el interfaz del exportador de Ogre, siempre que hayan
sido correctamente creadas y estn asociadas a un hueso de un esqueleto. En la Figu-
ra 18.4 se han denido dos acciones llamadas Saltar y Rotar (la accin Rotar est
seleccionada y aparece resaltada en la gura). A continuacin estudiaremos el proceso
de exportacin de animaciones.
Como se ha comentado anteriormente, las animaciones deben exportarse emplean-
do animacin basada en esqueletos. En el caso ms sencillo de animaciones de cuerpo
rgido, bastar con crear un esqueleto de un nico hueso y emparentarlo con el objeto
que queremos animar.
un esqueleto con un nico hueso al objeto que queremos aadir median-
Aadimos
te Shift A Add/Armature/Single Bone. Ajustamos el extremo superior del hueso para
que tenga un tamao similar al objeto a animar (como se muestra en la Figura 18.5).
A continuacin crearemos una relacin de parentesco entre el objeto y el hueso del
esqueleto, de forma que el objeto sea hijo de el esqueleto.
Este parentesco debe denirse en modo Pose . Con el hueso del esqueleto selec-
cionado, elegiremos el modo
Pose en la cabecera de la ventana 3D (o bien empleando
el atajo de teclado Control TAB ). El hueso deber representarse en color azul en el inter-
faz de Blender. Con el hueso en modo pose, ahora seleccionamos primero al caballo,
y a continuacin con Shift pulsado seleccionamos el hueso (se deber elegir en color
azul), y pulsamos Control P , eligiendo Set Parent to ->Bone. Si ahora desplazamos el
hueso del esqueleto en modo pose, el objeto deber seguirle.
Antes de crear las animaciones, debemos aadir un Modicador de objeto sobre
Figura 18.5: Ajuste del tamao del la malla poligional. Con el objeto Horse seleccionado, en el panel Object Modiers
hueso para que sea similar al objeto aadimos un modicador de tipo Armature. Este modicador se utiliza para animar
a animar. las poses de personajes y objetos asociados a esqueletos. Especicando el esqueleto
que utilizaremos con cada objeto podremos incluso deformar los vrtices del propio
modelo.
Como se muestra en la Figura 18.6, en el campo Object especicaremos el nom-
bre del esqueleto que utilizaremos con el modicador (Armature en este caso). En
el mtodo de Bind to indicamos qu elementos se asociarn al esqueleto. Si quere-
C18
mos que los vrtices del modelo se deformen en funcin de los huesos del esqueleto,
activaremos Vertex Groups (en este ejemplo deber estar desactivado). Mediante Bo-
ne Envelopes podemos indicar a Blender que utilice los lmites de cada hueso para
denir la deformacin que se aplicar a cada hueso.
A continuacin creamos un par de animaciones asociadas a este hueso. Recorde-
Figura 18.6: Opciones del Modi-
cador Armature.
mos que debemos trabajar en modo Pose , por lo que siempre el hueso debe estar
seleccionado en color azul en el interfaz de Blender. Comenzaremos deniendo una
accin que se llamar Saltar. Aadiremos los frames clave de la animacin (en este
caso, hemos denido frames clave de LocRot en 1, 21 y 35).
Tras realizar esta operacin, las ventanas de DopeSheet y NLA Editor mostrarn
un aspecto como el de la Figura 18.7. La ventana DopeSheet permite modicar
fcilmente las posiciones de los frames clave asociados al hueso. Pinchando y des-
plazando los rombos asociados a cada clave, podemos modicar el timing de la ani-
Figura 18.7: Ventanas de DopeS- macin. En la ventana NLA podemos crear una accin (Action Strip), pinchando
heet y NLA Editor.
sobre el icono del copo de nieve . Tras esta operacin, se habr creado una accin
denida mediante una barra amarilla.
Es posible indicar el nombre de la accin en el campo Active Strip, accesible pul-
sando la tecla N . Aparecer un nuevo subpanel, como se muestra en la Figura 18.8,
en la zona derecha de la ventana, donde se podrn congurar los parmetros asociados
a la accin (nombre, frames, mtodo de mezclado con otras acciones, etc...).
Figura 18.8: Especicacin del
nombre de la accin en el Panel de
Propiedades.
[458] CAPTULO 18. ANIMACIN
Mediante el atajo de teclado Alt A podemos reproducir la animacin relativa a la
accin que tenemos seleccionada. Resulta muy cmodo emplear una ventana de tipo
Timeline para denir el intervalo sobre el que se crearn las animaciones.
A la hora de exportar las animaciones, procedemos como vimos en el Captulo
11. En File/ Export/ Ogre3D, accederemos al exportador de Blender a Ogre3D. Es
interesante activar las siguientes opciones de exportacin:
El uso de las animaciones exportadas en Ogre es relativamente sencillo. En si- Huesos y vrtices
guiente cdigo muestra un ejemplode aplicacin
de las animaciones previamente ex-
portadas. Cuando se pulsa la tecla o se reproduce la animacin Saltar o Rotar En este captulo trabajaremos con
hasta que naliza. En la lnea 18 se actualiza el tiempo transcurrido desde la ltima
A Z
huesos que tienen una inuencia to-
teclas
tal sobre el objeto (es decir, inuen-
actualizacin. Cuando se pulsa alguna de las anteriores, la animacin seleccio-
nada se reproduce desde el principio (lnea 10 ).
cia de 1.0 sobre todos los vrtices
del modelo).
19 }
C18
25 if (_animState->getEnabled() && !_animState->hasEnded())
26 _animState->addTime(deltaT);
27 if (_animState2->getEnabled() && !_animState2->hasEnded())
28 _animState2->addTime(deltaT);
29 // ...
Obviamente, ser necesario contar con alguna clase de nivel superior que nos ges-
tione las animaciones, y se encarge de gestionar los AnimationState, actualizndolos
adecuadamente.
en http://www.gamasutra.com/view/feature/3456/
18.4. Mezclado de animaciones [461]
Veamos a continuacin los principales mtodos de la clase, declarados en las lneas
20-23 del listado anterior.
En el constructor de la clase inicializamos todas las animaciones asociadas a la en-
tidad (recibida como nico parmetro). Mediante un iterador (lnea 6
) desactivamos
todos los AnimationState asociados (lnea 7 ), y reseteamos la posicin
de reproduc-
cin y su peso asociado para su posterior composicin (lneas 8-9 ).
La clase AnimationBlender dispone de un mtodo principal para cargar animacio-
nes, indicando (como segundo parmetro) el tipo de mezcla que quiere realiarse con
la animacin que est reproducindose. La implementacin actual de la clase admite
dos modos de mezclado; un efecto de mezclado suave tipo cross fade y una transicin
bsica de intercambio de canales.
La transicin de tipo Blend implementa una transicin simple lineal de tipo Cross
Fade. En la Figura 18.10 representa este tipo de transicin, donde el primer canal de
animacin se mezclar de forma suave con el segundo, empleando una combinacin
lineal de pesos.
C18
Saltar
Listado 18.5: AminationBlender.cpp (Blend)
27 }
28 }
C18
Figura 18.13: Ejemplo de aplicacin que utiliza la clase AnimationBlender. Mediante las teclas indicadas
en el interfaz es posible componer las animaciones exportadas empleandos los dos modos de transicin
implementados.
Simulacin Fsica
19
Carlos Gonzlez Morcillo
E
n prcticamente cualquier videojuego (tanto 2D como 3D) es necesaria la de-
teccin de colisiones y, en muchos casos, la simulacin realista de dinmica
de cuerpo rgido. En este captulo estudiaremos la relacin existente entre sis-
temas de deteccin de colisiones y sistemas de simulacin fsica, y veremos algunos
ejemplos de uso del motor de simulacin fsica libre Bullet.
19.1. Introduccin
La mayora de los videojuegos requieren en mayor o menor medida el uso de tc-
nicas de deteccin de colisiones y simulacin fsica. Desde un videojuego clsico co-
mo Arkanoid, hasta modernos juegos automovilsticos como Gran Turismo requieren
denir la interaccin de los elementos en el mundo fsico.
El motor de simulacin fsica puede abarcar una amplia gama de caractersticas y
funcionalidades, aunque la mayor parte de las veces el trmino se reere a un tipo con-
creto de simulacin de la dinmica de cuerpos rgidos. Esta dinmica se encarga de
Figura 19.1: Anarkanoid, el ma-
chacaladrillos sin reglas es un jue-
determinar el movimiento de estos cuerpos rgidos y su interaccin ante la inuencia
go tipo Breakout donde la simula- de fuerzas.
cin fsica se reduce a una detec- En el mundo real, los objetos no pueden pasar a travs de otros objetos (salvo ca-
cin de colisiones 2D.
sos especcos convenientemente documentados en la revista Ms All). En nuestro
videojuego, a menos que tengamos en cuenta las colisiones de los cuerpos, tendre-
Cuerpo Rgido mos el mismo efecto. El sistema de deteccin de colisiones, que habitualmente es un
Denimos un cuerpo rgido como mdulo del motor de simulacin fsica, se encarga de calcular estas relaciones, deter-
un objeto slido ideal, innitamente minando la relacin espacial existente entre cuerpos rgidos.
duro y no deformable.
465
[466] CAPTULO 19. SIMULACIN FSICA
PhysX. Este motor privativo, es actualmente mantenido por NVidia con ace-
leracin basada en hardware (mediante unidades especcas de procesamiento
fsico PPUs Physics Processing Units o mediante ncleos CUDA. Las tarjetas
grcas con soporte de CUDA (siempre que tengan al menos 32 ncleos CU-
DA) pueden realizar la simulacin fsica en GPU. Este motor puede ejecutarse
en multitud de plataformas como PC (GNU/Linux, Windows y Mac), PlaySta-
tion 3, Xbox y Wii. El SDK es gratuito, tanto para proyectos comerciales como
no comerciales. Existen multitud de videojuegos comerciales que utilizan este
motor de simulacin. Gracias al wrapper NxOgre se puede utilizar este motor
en Ogre.
Figura 19.4: Logotipo del motor de
simulacin fsico estrella en el mun- Havok. El motor Havok se ha convertido en el estndar de facto en el mun-
do del software privativo. do del software privativo, con una amplia gama de caractersticas soportadas y
plataformas de publicacin (PC, Videoconsolas y Smartphones). Desde que en
Fsica 2007 Intel comprara la compaa que originalmente lo desarroll, Havok ha si-
do el sistema elegido por ms de 150 videojuegos comerciales de primera lnea.
Ttulos como Age of Empires, Killzone 2 & 3, Portal 2 o Uncharted 3 avalan la
calidad del motor.
... Grficos
Existen algunas bibliotecas especcas para el clculo de colisiones (la mayora
Lgica del distribuidas bajo licencias libres). Por ejemplo, I-Collide, desarrollada en la Universi-
Juego
dad de Carolina del Norte permite calcular intersecciones entre volmenes convexos.
Existen versiones menos ecientes para el tratamiento de formas no convexas, llama-
GUI Networking das V-Collide y RAPID. Estas bibliotecas pueden utilizarse como base para la cons-
truccin de nuestro propio conjunto de funciones de colisin para videojuegos que no
IA requieran funcionalidades fsicas complejas.
C19
Predictibilidad. El uso de un motor de simulacin fsica afecta a la predictibili-
dad del comportamiento de sus elementos. Adems, el ajuste de los parmetros
relativos a la denicin de las caractersticas fsicas de los objetos (coecientes,
constantes, etc...) son difciles de visualizar.
Realizacin de pruebas. La propia naturaleza catica de las simulaciones (en
muchos casos no determinista) diculta la realizacin de pruebas en el video-
juego.
Integracin. La integracin con otros mdulos del juego puede ser compleja.
Por ejemplo, qu impacto tendr en la bsqueda de caminos el uso de simula-
ciones fsicas? cmo garantizar el determinismo en un videojuego multijuga-
dor?.
Realismo grco. El uso de un motor de simulacin puede dicultar el uso
Figura 19.7: La primera incursin de ciertas tcnicas de representacin realista (como por ejemplo el preclculo
de Steven Spielberg en el mundo de la iluminacin con objetos que pueden ser destruidos). Adems, el uso de
de los videojuegos fue en 2008 con cajas lmite puede producir ciertos resultados poco realistas en el clculo de
Boom Blox, un ttulo de Wii desa- colisiones.
rrollado por Electronic Arts con
una componente de simulacin f-
sica crucial para la experiencia del
jugador.
[468] CAPTULO 19. SIMULACIN FSICA
Figura 19.6: Gracias al uso de PhysX, las baldosas del suelo en Batman Arkham Asylum pueden ser des-
truidas (derecha). En la imagen de la izquierda, sin usar PhysX el suelo permanece inalterado, restando
realismo y espectacularidad a la dinmica del juego.
donde F es la fuerza resultante que acta sobre un cuerpo de masa m, y con una
aceleracin lineal a aplicada sobre el centro de gravedad del cuerpo.
19.2. Sistema de Deteccin de Colisiones [469]
C19
e i-2
Enl
ac
i-1i 19.2. Sistema de Deteccin de Colisiones
xx
i-1i
La responsabilidad principal del Sistema de Deteccin de Colisiones (SDC) es
calcular cundo colisionan los objetos de la escena. Para calcular esta colisin, los
Figura 19.9: Un controlador puede objetos se representan internamente por una forma geomtrica sencilla (como esferas,
intentar que se mantenga constante cajas, cilindros...).
el ngulo i formado entre dos es-
labones de un brazo robtico. Adems de comprobar si hubo colisin entre los objetos, el SDC se encarga de
proporcionar informacin relevante al resto de mdulos del simulador fsico sobre las
Test de Interseccin propiedades de la colisin. Esta informacin se utiliza para evitar efectos indeseables,
como la penetracin de un objeto en otro, y consegir la estabilidad en la simulacin
En realidad, el Sistema de Detec-
cin de Colisiones puede entender- cuando el objeto llega a la posicin de equilibrio.
se como un mdulo para realizar
pruebas de interseccin complejas.
[470] CAPTULO 19. SIMULACIN FSICA
Como se muestra en la Figura 19.10, las entidades del juego pueden tener dife- (a)
rentes formas de colisin, o incluso pueden compartir varias primitivas bsicas (para
representar por ejemplo cada parte de la articulacin de un brazo robtico).
El Mundo Fsico sobre el que se ejecuta el SDC mantiene una lista de todas las
entidades que pueden colisionar empleando habitualmente una estructura global Sin-
gleton. Este Mundo Fsico es una representacin del mundo del juego que mantiene
la informacin necesaria para la deteccin de las colisiones. Esta separacin evita que (b)
el SDC tenga que acceder a estructuras de datos que no son necesarias para el clculo
de la colisin.
Los SDC mantienen estructuras de datos especcas para manejar las colisiones,
proporcionando informacin sobre la naturaleza del contacto, que contiene la lista de
las formas que estn intersectando, su velocidad, etc...
Para gestionar de un modo ms eciente las colisiones, las formas que suelen uti- (c)
lizarse con convexas. Una forma convexa es aquella en la que un rayo que surja desde
su interior atravesar la superce una nica vez. Las supercies convexas son mucho
Figura 19.10: Diferentes formas de
ms simples y requieren menor capacidad computacional para calcular colisiones que
colisin para el objeto de la imagen.
las formas cncavas. Algunas de las primitivas soportadas habitualmente en SDC son: (a) Aproximacin mediante una ca-
ja. (b) Aproximacin mediante un
Esferas. Son las primitivas ms simples y ecientes; basta con denir su centro volumen convexo. (c) Aproxima-
y radio (uso de un vector de 4 elementos). cin basada en la combinacin de
varias primitivas de tipo cilndrico.
Cajas. Por cuestiones de eciencia, se suelen emplear cajas lmite alineadas con
los ejes del sistema de coordenadas (AABB o Axis Aligned Bounding Box). Las Formas de colisin
cajas AABB se denen mediante las coordenadas de dos extremos opuestos. El A cada cuerpo dinmico se le aso-
principal problema de las cajas AABB es que, para resultar ecientes, requie- cia habitualmente una nica forma
ren estar alineadas con los ejes del sistema de coordenas global. Esto implica de colisin en el SDC.
19.2. Sistema de Deteccin de Colisiones [471]
Figura 19.11: Gestin de la forma de un objeto empleando cajas lmite alineadas con el sistema de referen-
cia universal AABBs. Como se muestra en la gura, la caja central realiza una aproximacin de la forma
del objeto muy pobre.
C19
formas que son aproximadamente cilndricas (como las extremidades del cuerpo
humano).
Volmenes convexos. La mayora de los SDC permiten trabajar con volmenes
convexos (ver Figura 19.10). La forma del objeto suele representarse interna-
mente mediante un conjunto de n planos. Aunque este tipo de formas es menos
eciente que las primitivas estudiadas anteriormente, existen ciertos algoritmos
como el GJK que permiten optimizar los clculos en este tipo de formas.
Malla poligional. En ciertas ocasiones puede ser interesante utilizar mallas ar-
bitrarias. Este tipo de supercies pueden ser abiertas (no es necesario que de-
nan un volumen), y se construyen como mallas de tringulos. Las mallas poli-
gonales se suelen emplear en elementos de geometra esttica, como elementos
del escenario, terrenos, etc. (ver Figura 19.13) Este tipo de formas de colisin
son las ms complejas computacionalmente, ya que el SDC debe probar con ca-
da tringulo. As, muchos juegos tratan de limitar el uso de este tipo de formas
de colisin para evitar que el rendimiento se desplome.
Formas compuestas. Este tipo de formas se utilizan cuando la descripcin de
Figura 19.12: Algunas primitivas un objeto se aproxima ms convenientemente con una coleccin de formas. Este
de colisin soportadas en ODE: Ca- tipo de formas es la aproximacin deseable en el caso de que tengamos que uti-
jas, cilindros y cpsulas. La imagen lizar objetos cncavos, que no se adaptan adecuadamente a volmenes convexos
inferior muestra los AABBs asocia-
dos a los objetos.
(ver Figura 19.14).
[472] CAPTULO 19. SIMULACIN FSICA
19.2.2. Optimizaciones
La deteccin de colisiones es, en general, una tarea que requiere el uso intensivo de
la CPU. Por un lado, los clculos necesarios para determianr si dos formas intersecan
no son triviales. Por otro lado, muchos juegos requiren un alto nmero de objetos en
la escena, de modo que el nmero de test de interseccin a calcular rpidamente crece.
En el caso de n objetos, si empleamos un algoritmo de fuerza bruta tendramos una
complejidad O(n2 ). Es posible utilizar ciertos tipos de optimizaciones que mejoran
esta complejidad inicial:
En muchos motores, como en Bullet, se utilizan varias capas o pasadas para de-
tectar las colisiones. Primero suelen emplearse cajas AABB para comprobar si los
objetos pueden estar potencialmente en colisin (deteccin de la colisin amplia). A
continuacin, en una segunda capa se hacen pruebas con volmenes generales que
engloban los objetos (por ejemplo, en un objeto compuesto por varios subobjetos, se a) b)
calcula una esfera que agrupe a todos los subobjetos). Si esta segunda capa de colisin
da un resultado positivo, en una tercera pasada se calculan las colisiones empleando
las formas nales.
Ray Casting. Este tipo de query requiere que se especique un rayo, y un ori-
gen. Si el rayo interseca con algn objeto, se devolver un punto o una lista de
puntos. Como vimos, el rayo se especica habitualmente mediante una ecua-
cin paramtrica de modo que p(t) = po + td, siendo t el parmetro que toma
19.3. Dinmica del Cuerpo Rgido [473]
d d
d d
Po
Po Po Po
a) b) c) d)
Figura 19.15: Clculo de los puntos de colisin empleando Shape Casting. a) La forma inicialmente se
encuentra colisionando con algn objeto de la escena. b) El SDC obtiene un punto de colisin. c) La forma
interseca con dos objetos a la vez. d) En este caso el sistema calcula todos los puntos de colisin a lo largo
de la trayectoria en la direccin de d.
valores entre 0 y 1. d nos dene el vector direccin del rayo, que determinar la
Uso de Rayos distancia mxima de clculo de la colisin. Po nos dene el punto de origen del
rayo. Este valor de t que nos devuelve la query puede ser fcilmente convertido
El Ray Casting se utiliza amplia- al punto de colisin en el espacio 3D.
mente en videojuegos. Por ejemplo,
para comprobar si un personaje es- Shape Casting. El Shape Casting permite determinar los puntos de colisin de
t dentro del rea de visin de otro una forma que viaja en la direccin de un vector determinado. Es similar al Ray
personaje, para detectar si un dispa-
ro alcanza a un enemigo, para que Casting, pero en este caso es necesario tener en cuenta dos posibles situaciones
los vehculos permanezcan en con- que pueden ocurrir:
tacto con el suelo, etc.
1. La forma sobre la que aplicamos Shape Casting est inicialmente interse-
Uso de Shape Casting cando con al menos un objeto que evita que se desplace desde su posicin
Un uso habitual de estas queries inicial. En este caso el SDC devolver los puntos de contacto que pueden
permite determinar si la cmara en- estar situados sobre la supercie o en el interior del volumen.
tra en colisin con los objetos de la
escena, para ajustar la posicin de 2. La forma no interseca sobre ningn objeto, por lo que puede desplazarse
los personajes en terrenos irregula- libremente por la escena. En este caso, el resultado de la colisin suele ser
res, etc. un punto de colisin situado a una determinada distancia del origen, aun-
que puede darse el caso de varias colisiones simultneas (como se muestra
Lista de colisiones en la Figura 19.15). En muchos casos, los SDC nicamente devuelven el
C19
Para ciertas aplicaciones puede ser resultado de la primera colisin (una lista de estructuras que contienen el
igualmente conveniente obtener la valor de t, el identicador del objeto con el que han colisionado, el punto
lista de todos los objetos con los que de contacto, el vector normal de la supercie en ese punto de contacto, y
se interseca a lo largo de la trayec-
toria, como se muestra en la Figu- algunos campos extra de informacin adicional).
ra 19.15.d).
a b c
Figura 19.16: Ejemplo de simulacin de cuerpos blandos con Bullet. a) Uso de softbodies con formas
convexas. b) Simulacin de una tela sostenida por cuatro puntos. c) Simulacin de cuerdas.
19.4. Restricciones
Las restricciones sirven para limitar el movimiento de un objeto. Un objeto sin
ninguna restriccin tiene 6 grados de libertad. Las restricciones se usan en multitud de
situaciones en desarrollo de videojuegos, como en puertas, suspensiones de vehculos,
cadenas, cuerdas, etc. A continuacin enumeraremos brevemente los principales tipos
de restricciones soportadas por los motores de simulacin fsica.
Restriccin Restriccin
Punto a Muelle
Punto Rgido
Articulacin Articulacin
Bola Articulacin Prismtica
Bisagra Pistn
Figura 19.17: Representacin de algunas de las principales restricciones que pueden encontrarse en los
motores de simulacin fsica.
Muelle rgido. Un muelle rgido (Stiff Spring) funciona como una restriccin
de tipo punto a punto salvo por el hecho de que los objetos estn separados por
una determinada distancia. As, puede verse como unidos por una barra rgida
que los separa una distancia ja.
Bisagras. Este tipo de restriccin limitan el movimiento de rotacin en un deter-
minado eje (ver Figura 19.17). Este tipo de restricciones pueden denir adems
un lmite de rotacin angular permitido entre los objetos (para evitar, por ejem-
plo, que el codo de un brazo robtico adopte una posicin imposible).
Pistones. Este tipo de restricciones, denominadas en general restricciones pris-
mticas, permiten limitar el movimiento de traslacin a un nico eje. Estas res-
tricciones podran permitir opcionalmente rotacin sobre ese eje.
Bolas. Estas restricciones permiten denir lmites de rotacin exibles, esta-
bleciendo puntos de anclaje. Sirven por ejemplo para modelar la rotacin que
C19
ocurre en el hombro de un personaje.
Otras restricciones. Cada motor permite una serie de restricciones especcas,
como planares (que restringen el movimiento en un plano 2D), cuerdas, cadenas
de objetos, rag dolls (denidas como una coleccin de cuerpos rgidos conecta-
dos utilizando una estructura jerrquica), etc...
Existen multitud de videojuegos comerciales que han utilizado Bullet como motor
de simulacin fsica. Entre otros se pueden destacar Grand Theft Auto IV (de Red
Dead Redemption), Free Realms (de Sony), HotWheels (de BattleForce), Blood Drive
(de Activision) o Toy Story 3 (The Game) (de Disney).
De igual forma, el motor se ha utilizado en pelculas profesionales, como Hancock
(de Sony Pictures), Bolt de Walt Disney, Sherlock Holmes (de Framestore) o Shrek 4
(de DreamWorks).
Muchos motores grcos y de videojuegos permiten utilizar Bullet. La comunidad
ha desarrollado multitud de wrappers para facilitar la integracin en Ogre, Crystal
Space, Irrlich y Blitz3D entre otros.
En el diseo de Bullet se prest especial atencin en conseguir un motor fcilmente
adaptable y modular. Tal y como se comenta en su manual de usuario, el desarrollador
puede utilizar nicamente aquellos mdulos que necesite (ver Figura 19.19):
Dinmica Bullet
.dea, .bsp, .obj...
SoftBody MultiHilo
ra 19.20. El pipeline se ejecuta desde la izquierda a la derecha, comenzando por la
etapa de clculo de la gravedad, y nalizando con la integracin de las posiciones Dinmica
(actualizando la transformacin del mundo). Cada vez que se calcula un paso de la Cuerpo Rgido
simulacin stepSimulation en el mundo, se ejecutan las 7 etapas denidas en la
imagen anterior. Deteccin de Colisiones
Contenedores, Gestin de
El Pipeline comienza aplicando la fuerza gravedad a los objetos de la escena. Memoria, Bib. Matemtica...
Posteriormente se realiza una prediccin de las transformaciones calculando la po-
sicin actual de los objetos. Hecho esto se calculan las cajas AABBs, que darn una Figura 19.19: Esquema de los prin-
estimacin rpida de la posicin de los objetos. Con estas cajas se determinan los cipales mdulos funcionales de Bu-
pares, que consiste en calcular si las cajas AABBs se solapan en algn eje. Si no hay llet.
19.5. Introduccin a Bullet [477]
Inicio Time Step Time Step Time Step Time Step Time Step Fin
Figura 19.20: Pipeline de Bullet. En la parte superior de la imagen se describen las principales etapas
computacionales, y en la parte inferior las estructuras de datos ms importantes.
ningn solape, podemos armar que no hay colisin. De otra forma, hay que realizar
un estudio ms detallado del caso. Si hay posibilidad de contacto, se pasa a la siguien-
te etapa de calcular los contactos, que calcula utilizando la forma de colisin real del
objeto el punto de contacto exacto. Estos puntos de contacto se pasan a la ltima etapa
de clculo de la dinmica, que comienza con la resolucin de las restricciones, don-
de se determina la respuesta a la colisin empleando las restricciones de movimientos
que se han denido en la escena. Finalmente se integran las posiciones de los objetos,
obteniendo las posiciones y orientaciones nuevas para este paso de simulacin.
Tipos bsicos Las estructuras de datos bsicas que dene Bullet se dividen en dos grupos: los da-
tos de colisin, que dependen nicamente de la forma del objeto (no se tiene en cuenta
Bullet cuenta con un subconjunto las propiedades fscas, como la masa o la velocidad asociada al objeto), y los datos
de utilidades matemticas bsicas
con tipos de datos y operadores de- de propiedades dinmicas que almacenan las propiedades fsicas como la masa, la
C19
nidos como btScalar (escalar en inercia, las restricciones y las articulaciones.
punto otante), btVector3 (vector en
el espacio 3D), btTransform (trans- En los datos de colisin se distinguen las formas de colisin, las cajas AABBs, los
formacin afn 3D), btQuaternion, pares superpuestos que mantiene una lista de parejas de cajas AABB que se solapan
btMatrix3x3... en algn eje, y los puntos de contacto que han sido calculados como resultado de la
colisin.
Altura: 49.9972 Una vez compilado el programa de ejemplo, al ejecutarlo obtenemos un resultado
Altura: 49.9917 puramente textual, como el mostrado en la Figura 19.21. Como hemos comentado
Altura: 49.9833 anteriormente, los Bullet est diseado para permitir un uso modular. En este primer
Altura: 49.9722 ejemplo no se ha hecho uso de ninguna biblioteca para la representacin grca de la
Altura: 49.9583
Altura: 49.9417 escena.
Altura: 49.9222 Si representamos los 300 valores de la altura de la esfera, obtenemos una grca
Altura: 49.9 como muestra la Figura 19.22. Como vemos, cuando la esfera (de 1 metro de radio)
Altura: 49.875 llega a un metro del suelo (medido desde el centro), rebota levemente y a partir del
Altura: 49.8472 frame 230 aproximadamente se estabiliza.
El primer include denido en la lnea 2 del programa anterior se encarga de
Altura: 49.8167
Altura: 49.7833
Altura: 49.7472 incluir todos los archivos de cabecera necesarios para crear una aplicacin que haga
Altura: 49.7083 uso del mdulo de dinmicas (cuerpo rgido, restricciones, etc...). Necesitaremos ins-
Altura: 49.6667 tanciar un mundo sobre el que realizar la simulacin. En nuestro caso crearemos un
... mundo discreto, que es el adecuado salvo que tengamos objetos de movimiento muy
Figura 19.21: Salida por pantalla rpido sobre el que tengamos que hacer una deteccin de su movimiento (en cuyo caso
de la ejecucin del Hola Mundo en podramos utilizar la clase an experimental btContinuousDynamicsWorld).
Bullet.
60
50
Creacin del mundo
40
30 Como vimos en la seccin 19.2.2, los motores de simulacin fsica utilizan varias
20 capas para detectar las colisiones entre los objetos del mundo. Bullet permite utilizar
10 algoritmos en una primera etapa (broadphase) que utilizan las cajas lmite de los ob-
0 jetos del mundo para obtener una lista de pares de cajas que pueden estar en colisin.
50 100 150 200 250
Esta lista es una lista exhaustiva de todas las cajas lmite que intersecan en alguno
C19
Figura 19.22: Representacin de de los ejes (aunque, en etapas posteriores lleguen a descartarse porque en realidad no
los valores obtenidos en el Hola colisionan).
Mundo.
Muchos sistemas de simulacin fsica se basan en el Teorema de los Ejes Separa-
dos. Este teorema dice que si existe un eje sobre el que la proyeccin de dos formas
convexas no se solapan, entonces podemos asegurar que las dos formas no colisio-
nan. Si no existe dicho eje y las dos formas son convexas, podemos asegurar que las
formas colisionan. Si las formas son cncavas, podra ocurrir que no colisionaran (de-
pendiendo de la suerte que tengamos con la forma de los objetos). Este teorema se
puede visualizar fcilmente en 2 dimensiones, como se muestra en la Figura 19.23.a.
El mismo teorema puede aplicarse para cajas AABB. Adems, el hecho de que las
cajas AABB estn perfectamente alineadas con los ejes del sistema de referencia, hace
que el clculo de la proyeccin de estas cajas sea muy rpida (simplemente podemos
utilizar las coordenadas mnimas y mximas que denen las cajas).
La Figura 19.23 representa la aplicacin de este teorema sobre cajas AABB. En
el caso c) de dicha gura puede comprobarse que la proyeccin de las cajas se solapa
en todos los ejes, por lo que tendramos un caso potencial de colisin. En realidad,
como vemos en la gura las formas que contienen dichas cajas AABB no colisionan,
pero este caso deber ser resuelto por algoritmos de deteccin de colisin de menor
granularidad.
[480] CAPTULO 19. SIMULACIN FSICA
Y Y Y
a) b) c)
B B B
Pr. B
A A A
Pr. A
Proyeccin Proyeccin
de A en X de B en X X Ejemplo de AABBs que X Ejemplo de AABBs que X
no intersecan intersecan
Figura 19.23: Aplicacin del Teorema de los Ejes Separados y uso con cajas AABB. a) La proyeccin
de las formas convexas de los objetos A y B estn separadas en el eje X, pero no en el eje Y. Como
podemos encontrar un eje sobre el que ambas proyecciones no intersecan, podemos asegurar que las formas
no colisionan. b) El mismo principio aplicados sobre cajas AABB denidas en objetos cncavos, que no
intersecan. c) La proyeccin de las cajas AABB se solapa en todos los ejes. Hay un posible caso de colisin
entre formas.
En la lnea 5 creamos un objeto que implementa un algoritmo de optimizacin
en una primera etapa broadphase. En posteriores etapas Bullet calcular las colisiones
exactas. Existen dos algoritmos bsicos que implementa Bullet para mejorar al apro-
ximacin a ciegas de complejidad O(n2 ) que comprobara toda la lista de pares. Estos
algoritmos aaden nuevas parejas de cajas que en realidad no colisionan, aunque en
general mejoran el tiempo de ejecucin.
cuando los objetos se encuentren cerca). encargar de resolver la interaccin
El objeto solver (lnea 9 ) se encarga de que los objetos interacten adecuada-
entre objetos.
que hacen uso de paralelismo empleando hilos. de colisin. Si varios objetos de la
En la lnea 10 se instancia el mundo. Este objeto nos permitir aadir los objetos
escena pueden compartir la misma
forma de colisin (por ejemplo, to-
Formas de Colisin
C19
nar formas de cualquier tipo (tanto primitivas como formas de colisin de tipo
malla que veremos a continuacin). Permite obtener formas compuestas, como
la que se estudi en la Figura 19.14.
En la lnea 16-17 creamos una forma de colisin de tipo plano, pasando como
parmetro el vector normal del plano (vector unitario en Y), y una distancia respecto
del origen. As, el plano de colisin queda denido por la ecuacin y = 1.
De igual modo, la forma de colisin del cuerpo que dejaremos caer sobre el suelo
ser una esfera de radio 1 metro (lnea 18 ).
Una vez denidas las formas de colisin, las posicionaremos asocindolas a ins-
tancias de cuerpos rgidos. En la siguiente subseccin aadiremos los cuerpos rgidos
al mundo.
Cuerpos Rgidos
la forma de colisin del plano. El resultado sera el mismo si en ambos parmetros se hubiera puesto 0.
19.5. Introduccin a Bullet [483]
En las lneas 22-23 se emplea la estructura btRigidBodyConstructionInfo para
establecer la informacin para crear un cuerpo rgido.
El primer parmetro es la masa del objeto. Estableciendo una masa igual a cero
(primer parmetro), se crea un objeto esttico (equivale a establecer una masa innita,
de modo que el objeto no se puede mover). El ltimo parmetro es la inercia del suelo
(que se establece igualmente a 0, por ser un objeto esttico).
En la lnea 24 creamos el objeto rgido a partir de la informacin
almacenada en
la estructura anterior, y lo aadimos al mundo en la lnea 25 .
La creacin de la esfera sigue un patrn de cdigo similar. En la lnea 27 se crea
el MotionState
para el objeto que dejaremos caer, situado a 50 metros del suelo (lnea
28 ).
En las lneas 29-31 se establecen las propieades del cuerpo; una masa de 1Kg y
se llama a un mtodo de btCollisionShape que nos calcula la inercia de una esfera a
partir de su masa.
Bucle Principal
Para nalizar, el bucle principal se ejecuta en las lneas 36-41 . El bucle se ejecuta
300 veces, llamando al paso de simulacin con un intervalo de 60hz. En cada paso de
la simulacin se imprime la altura de la esfera sobre el suelo.
Como puede verse, la posicin y la orientacin del objeto dinmico se encapsulan
en un objeto de tipo btTransform. Como se coment anteriormente, esta informacin
C19
puede obtenerse a partir del MotionState asociado al btRigidBody a travs de la es-
tructura de inicializacin btRigidBodyConstructInfo.
Tiempos! El mtodo para avanzar un paso en la simulacin (lnea 38 ) requiere dos parme-
tros. El primero describe la cantidad de tiempo que queremos avanzar la simulacin.
Cuidado, ya que las funciones de Bullet tiene un reloj interno que permite mantener constante esta actualizacin, de for-
clculo de tiempo habitualmente
devuelven los resultados en milise- ma que sea independiente de la tasa de frames de la aplicacin. El segundo parmetro
gundos. Bullet trabaja en segundos, es el nmero de subpasos que debe realizar bullet cada vez que se llama stepSimula-
por lo que sta es una fuente habi- tion. Los tiempos se miden en segundos.
tual de errores.
El primer parmetro debe ser siempre menor que el nmero de subpasos multipli-
cado por el tiempo jo de cada paso tStep < maxSubStep tF ixedStep .
No olvides... Decrementando el tamao de cada paso de simulacin se est aumentado la reso-
lucin de la simulacin fsica. De este modo, si en el juego hay objetos que atravie-
Cuando cambies el valor de los
tiempos de simulacin, recuerda san objetos (como paredes), es posible decrementar el xedTimeStep para aumentar
calcular el nmero de subpasos de la resolucin. Obviamente, cuando se aumenta la resolucin al doble, se necesitar
simulacin para que la ecuacin si- aumentar el nmero de maxSubSteps al doble, lo que requerir aproximadamente el
ga siendo correcta. doble de tiempo de CPU para el mismo tiempo de simulacin fsica.
[484] CAPTULO 19. SIMULACIN FSICA
En las lneas 15-19 se dene el mtodo principal, que actualiza la posicin y
rotacin del SceneNode en Ogre. Dado que Bullet y Ogre denen clases distintas
para trabajar con Vectores y Cuaternios, es necesario obtener la rotacin y posicin
y asignarlo
del objeto por separado al nodo mediante las llamadas a setOrientation y
setPosition (lneas 17 y 19 ).
La llamada a setWorldTransform puede retornar en la lnea 15 si no se ha estable-
cido nodo en el constructor. Se habilita un mtodo especco para establecer el nodo
ms adelante. Esto es interesante si se quieren aadir objetos a la simulacin que no
tengan representacin grca.
Una vez creada la clase que utilizaremos para denir el MotionState, la utilizare-
mos en el Hola Mundo construido en la seccin anterior para representar la simula-
cin con el plano y la esfera. El resultado que tendremos se muestra en la Figura 19.25.
Para la construccin del ejemplo emplearemos como esqueleto base el FrameListener
C19
del Mdulo 2 del curso.
24 void MyFrameListener::CreateInitialWorld() {
25 // Creacion de la entidad y del SceneNode -----------------------
26 Plane plane1(Vector3::Vector3(0,1,0), 0);
27 MeshManager::getSingleton().createPlane("p1",
28 ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane1,
29 200, 200, 1, 1, true, 1, 20, 20, Vector3::UNIT_Z);
30 SceneNode* node = _sceneManager->createSceneNode("ground");
31 Entity* groundEnt = _sceneManager->createEntity("planeEnt","p1");
32 groundEnt->setMaterialName("Ground");
33 node->attachObject(groundEnt);
34 _sceneManager->getRootSceneNode()->addChild(node);
35
36 // Creamos las formas de colision -------------------------------
37 _groundShape = new btStaticPlaneShape(btVector3(0,1,0),1);
38 _fallShape = new btSphereShape(1);
39
40 // Creamos el plano ---------------------------------------------
41 MyMotionState* groundMotionState = new MyMotionState(
42 btTransform(btQuaternion(0,0,0,1),btVector3(0,-1,0)), node);
43 btRigidBody::btRigidBodyConstructionInfo groundRigidBodyCI
44 (0,groundMotionState,_groundShape,btVector3(0,0,0));
45 _groundRigidBody = new btRigidBody(groundRigidBodyCI);
46 _world->addRigidBody(_groundRigidBody);
47
48 // Creamos la esfera --------------------------------------------
49 Entity *entity2= _sceneManager->createEntity("ball","ball.mesh");
50 SceneNode *node2= _sceneManager->getRootSceneNode()->
createChildSceneNode();
51 node2->attachObject(entity2);
52 MyMotionState* fallMotionState = new MyMotionState(
53 btTransform(btQuaternion(0,0,0,1),btVector3(0,50,0)), node2);
54 btScalar mass = 1; btVector3 fallInertia(0,0,0);
55 _fallShape->calculateLocalInertia(mass,fallInertia);
56 btRigidBody::btRigidBodyConstructionInfo fallRigidBodyCI(
57 mass,fallMotionState,_fallShape,fallInertia);
58 _fallRigidBody = new btRigidBody(fallRigidBodyCI);
59 _world->addRigidBody(_fallRigidBody);
60 }
61
62 bool MyFrameListener::frameStarted(const Ogre::FrameEvent& evt) {
63 Real deltaT = evt.timeSinceLastFrame;
64 int fps = 1.0 / deltaT;
65
66 _world->stepSimulation(deltaT, 5); // Actualizar fisica
67
68 _keyboard->capture();
69 if (_keyboard->isKeyDown(OIS::KC_ESCAPE)) return false;
70
71 btVector3 impulse;
72 if (_keyboard->isKeyDown(OIS::KC_I)) impulse=btVector3(0,0,-.1);
73 if (_keyboard->isKeyDown(OIS::KC_J)) impulse=btVector3(-.1,0,0);
74 if (_keyboard->isKeyDown(OIS::KC_K)) impulse=btVector3(0,0,.1);
75 if (_keyboard->isKeyDown(OIS::KC_L)) impulse=btVector3(.1,0,0);
76 _fallRigidBody->applyCentralImpulse(impulse);
77
78 // Omitida parte del codigo fuente (manejo del raton, etc...)
79 return true;
80 }
81
82 bool MyFrameListener::frameEnded(const Ogre::FrameEvent& evt) {
83 Real deltaT = evt.timeSinceLastFrame;
84 _world->stepSimulation(deltaT, 5); // Actualizar fisica
85 return true;
86 }
19.7. Hola Mundo en OgreBullet [487]
C19
mina la llamada a stepSimulation, el 1 # Flags de compilacion -----------------------------
resultado de la simulacin es mucho 2 CXXFLAGS := -I $(DIRHEA) -Wall pkg-config --cflags OGRE pkg-
ms brusco. config --cflags bullet
3
4 # Flags del linker -------------------------------
5 LDFLAGS := pkg-config --libs-only-L OGRE pkg-config --libs-only-
l bullet
6 LDLIBS := pkg-config --libs-only-l OGRE pkg-config --libs-only-l
bullet -lOIS -lGL -lstdc++
Para aadir un objeto de simulacin de OgreBullet debemos crear dos elementos
bsicos; por un
lado la CollisionShape (lneas ), y por otro lado el RigidBody
(lneas 17-18 ).
14-16
C19
colisin a un RigidBody; setShape y setStaticShape. La segunda cuenta con varias
versiones; una de ellas no requiere especicar el SceneNode, y se corresponde con la
utilizada en la lnea 21 para aadir la forma de colisin al plano.
Aadir a las colas
Una vez aadido el objeto, deben Listado 19.9: CreateInitialWorld
aadirse las referencias a la forma 1 void MyFrameListener::CreateInitialWorld() {
de colisin y al cuerpo rgido en las 2 // Creacion de la entidad y del SceneNode -----------------------
colas de la clase (lnea 24). 3 Plane plane1(Vector3::Vector3(0,1,0), 0);
4 MeshManager::getSingleton().createPlane("p1",
5 ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane1,
6 200, 200, 1, 1, true, 1, 20, 20, Vector3::UNIT_Z);
7 SceneNode* node= _sceneManager->createSceneNode("ground");
8 Entity* groundEnt= _sceneManager->createEntity("planeEnt", "p1");
9 groundEnt->setMaterialName("Ground");
10 node->attachObject(groundEnt);
11 _sceneManager->getRootSceneNode()->addChild(node);
12
13 // Creamos forma de colision para el plano ----------------------
14 OgreBulletCollisions::CollisionShape *Shape;
15 Shape = new OgreBulletCollisions::StaticPlaneCollisionShape
16 (Ogre::Vector3(0,1,0), 0); // Vector normal y distancia
17 OgreBulletDynamics::RigidBody *rigidBodyPlane = new
18 OgreBulletDynamics::RigidBody("rigidBodyPlane", _world);
19
20 // Creamos la forma estatica (forma, Restitucion, Friccion) ----
[490] CAPTULO 19. SIMULACIN FSICA
33 }
En el listado
anterior, el nodo asociado a cada caja se aade en la llamada a setSha-
pe (lneas 25-27 ). Pese a que Bullet soporta multitud de propiedades en la estructura
btRigidBodyConstructionInfo, el wrapper se centra exclusivamente en la denicin de
la masa, y los coecientes de friccin y restitucin. La posicin inicial y el cuaternio
se indican igualmente en la llamada al mtodo, que nos abstrae de la necesidad de
denir el MotionState.
a laescena con una velocidad lineal relativa a la rotacin de
Las cajas se aaden
la cmara (ver lneas 28-29 ).
19.8. RayQueries
Al inicio del captulo estudiamos algunos tipos de preguntas que podan realizarse
al motor de simulacin fsica. Uno de ellos eran los RayQueries que permitan obtener
las formas de colisin que intersecaban con un determinado rayo.
Ogre? Bullet? Utilizaremos esta funcionalidad del SDC para aplicar un determinado impulso
al primer objeto que sea tocado por el puntero del ratn. De igual forma, en este
Recordemos que los objetos de si- ejemplo se aadirn objetos deniendo una forma de colisin convexa. El resultado
mulacin fsica no son conocidos
por Ogre. Aunque en el mdulo 2 de la simulacin (activando la representacin de las formas de colisin) se muestra en
del curso estudiamos los RayQue- la Figura 19.27.
ries en Ogre, es necesario realizar la
pregunta en Bullet para obtener las La llamada al mtodo AddDynamicObject recibe como parmetro un tipo enume-
referencias a los RigidBody. rado, que indica si queremos aadir una oveja o una caja. La forma de colisin de
la cajase calcula automticamente empleando la clase StaticMeshToShapeConverter
(lnea 17 ).
C19
(para las ovejas y para las cajas), y comprobar la diferencia de rendimiento
en frames por segundo cuando el nmero de objetos de la escena crece.
23
24 if (body) {
25 if (!body->isStaticObject()) {
26 body->enableActiveState ();
27 Vector3 relPos(p - body->getCenterOfMassPosition());
28 Vector3 impulse (r.getDirection ());
29 body->applyImpulse (impulse * F, relPos);
30 }
31 }
32 }
33 // Omitido resto de codigo del metodo ---------------------------
34 }
Para nalizar, si hubo colisin con algn cuerpo que no sea esttico (lneas 24-25 ),
se aplicar un impulso. La llamada a enableActiveState permite activar un cuerpo. Por
defecto, Bullet automticamente desactiva objetos dinmicos cuando la velocidad es
menor que un determinado umbral.
Los cuerpos desactivados en realidad estn se encuentran en un estado de dormi-
dos, y no consumen tiempo de ejecucin salvo por la etapa de deteccin de colisin
broadphase. Esta etapa automticamente despierta a los objetos que estuvieran dor-
midos si se encuentra colisin con otros elementos de la escena.
Impulso En las lneas 27-29 se aplica un impulso sobre el objeto en la direccin del rayo
que se calcul desde la cmara, con una fuerza proporcional a F . El impulso es una
El impulso puede denirse como
F dt = (dp/dt)dt, siendo p el fuerza que acta en un cuerpo en un determinado intervalo de tiempo. El impulso
momento. implica un cambio en el momento, siendo la Fuerza denida como el cambio en el
momento. As, el impulso aplicado sobre un objeto puede ser denido como la integral
de la fuerza con respecto del tiempo.
El wrapper de OgreBullet permite denir un pequeo subconjunto de propiedades
de los RigidBody de las soportadas en Bullet. Algunas de las principales propiedades
son la Velocidad Lineal, Impulsos y Fuerzas. Si se requieren otras propiedades, ser
necesario acceder al objeto de la clase btRigidBody (mediante la llamada a getBulle-
tRigidBody) y especicar manualmente las propiedades de simulacin.
C19
19.9. TriangleMeshCollisionShape
En este ejemplo se cargan dos objetos como mallas triangulares estticas. El re-
sultado de la ejecucin puede verse en la Figura 19.28. Al igual que en el ejemplo
anterior, se utiliza la funcionalidad proporcionada por el conversor de mallas, pero
generando una TriangleMeshCollisionShape (lnea 11-12 ).
Figura 19.29: Conguracin de la malla esttica utilizada en el ejemplo. Es importante aplicar la escala y
rotacin a los objetos antes de su exportacin, as como las dimensiones del objeto ball para aplicar los
mismos lmites a la collision shape.
15 OgreBulletCollisions::Object *obDrain =
16 _world->findObject(drain);
17 OgreBulletCollisions::Object *obOB_A = _world->findObject(obA);
18 OgreBulletCollisions::Object *obOB_B = _world->findObject(obB);
19
20 if ((obOB_A == obDrain) || (obOB_B == obDrain)) {
21 Ogre::SceneNode* node = NULL;
22 if ((obOB_A != obDrain) && (obOB_A)) {
23 node = obOB_A->getRootNode(); delete obOB_A;
24 }
25 else if ((obOB_B != obDrain) && (obOB_B)) {
26 node = obOB_B->getRootNode(); delete obOB_B;
27 }
28 if (node) {
29 std::cout << node->getName() << std::endl;
30 _sceneManager->getRootSceneNode()->
31 removeAndDestroyChild (node->getName());
32 }
33 }
34 }
35 }
OgreBullet... En la lnea 2 se obtiene el puntero directamente a la clase btCollisionWorld, que
se encuentra oculta en la implementacin de OgreBullet. Con este puntero se acceder
El listado anterior muestra adems
cmo acceder al objeto del mundo
directamente a la funcionalidad de Bullet sin emplear la clase de recubrimieneto de
de bullet, que permite utilizar gran OgreBullet. La clase btCollisionWorld sirve a la vez como interfaz y como contenedor
cantidad de mtodos que no estn de las funcionalidades relativas a la deteccin de colisiones.
Mediante la llamada a getDispatcher (lnea 3 ) se obtiene un puntero a la clase
implementados en OgreBullet.
C19
Los puntos de contacto se crean en la etapa de deteccin de colisiones na
(narrow phase). La cache de btPersistentManifold puede estar vaca o conte-
ner hasta un mximo de 4 puntos de colisin. Los algoritmos de deteccin de
la colisin aaden y eliminan puntos de esta cach empleando ciertas heurs-
ticas que limitan el mximo de puntos a 4. Es posible obtener el nmero de
puntos de contacto asociados a la cache en cada instante mediante el mtodo
getNumContacts().
La cache de colisin mantiene punteros a los dos objetos que estn colisionando.
Estos objetos pueden obtenerse mediante la llamada a mtodos get (lneas 8-11 ).
La clase CollisionsWorld de OgreBullet proporciona un mtodo ndObject que
permite obtener un puntero aobjeto genrico a partir de un SceneNode o un btColli-
sionObject (ver lneas 15-18 ).
La ltima parte del cdigo (lneas 20-32 ) comprueba si alguno de los dos objetos
de la colisin son el sumidero. En ese caso, se obtiene el puntero al otro objeto (que
se corresponder con un objeto de tipo esfera creado dinmicamente), y se elimina de
la escena. As, los objetos en esta segunda versin del ejemplo no llegan a aadirse en
la caja de la parte inferior del circuito.
[496] CAPTULO 19. SIMULACIN FSICA
C19
1 OgreBulletDynamics::WheeledRigidBody *mCarChassis;
2 OgreBulletDynamics::VehicleTuning *mTuning;
3 OgreBulletDynamics::VehicleRayCaster *mVehicleRayCaster;
4 OgreBulletDynamics::RaycastVehicle *mVehicle;
5 Ogre::Entity *mChassis;
6 Ogre::Entity *mWheels[4];
7 Ogre::SceneNode *mWheelNodes[4];
8 float mSteering;
A continuacin estudiaremos ladenicin del vehculo en el mtodo CreateInitial-
World del FrameListener. La lnea 1 del siguiente listado dene el vector de altura del
chasis (elevacin sobre el suelo), y la altura de conexin de las ruedas en l (lnea 2 )
que ser utilizado ms adelante. En la construccin inicial del vehculo se establece la
direccin del vehculo como 0.0 (lnea 3 ).
La lneas 5-9 crean la entidad del chasis y el nodo que la contendr. La lnea 10
utiliza el vector de altura del chasis para posicionar el nodo del chasis.
El chasis tendr asociada una forma de colisin de tipo caja (lnea 1 ). Esta
caja
formar parte de una forma de colisin compuesta, que se dene en la lnea 2 , y a la
que se aade la caja anterior desplazada segn el vector chassisShift (lnea 3 ).
En la lnea 4 se dene el cuerpo rgido
del vehculo,
al que se asocia la forma de
colisin creada anteriormente (lnea 6 ). En la lnea 9 se establecen los valores
de
suspensin del vehculo, y se evita que el vehculo pueda desactivarse (lnea 8 ), de
modo que el objeto no se dormir incluso si se detiene durante un tiempo continua-
do.
En el siguiente fragmento
se comienza deniendo algunos parmetros de tuning
del vehculo (lnea 1 ). Estos parmetros son la rigidez, compresin
y amortiguacin
de la suspensin y la friccin de deslizamiento. La lnea 5 establece el sistema de
coordenadas local del vehculo mediante los ndices derecho, superior y adelante.
Las lneas 7 y 8 denen los ejes que se utilizarn como direccin del vehculo y
en la denicin de las ruedas.
El bucle de las lneas 10-16 construye los nodos de las ruedas, cargando 4 instan-
cias de la malla wheel.mesh.
19.11. Restriccin de Vehculo [499]
El ejemplo desarrollado en esta seccin trabaja nicamente con las dos ruedas
delanteras (ndices 0 y 1) como ruedas motrices. Adems, ambas ruedas giran
de forma simtrica segn la variable de direccin mSteering. Queda propuesto
como ejercicio modicar el cdigo de esta seccin para que la direccin se
pueda realizar igualmente con las ruedas traseras, as como incorporar otras
opciones de motricidad (ver Listado de FrameStarted).
C19
Listado 19.20: Fragmento de CreateInitialWorld (IV).
1 Ogre::Vector3 connectionPointCS0 (1-(0.3*gWheelWidth),
connectionHeight, 2-gWheelRadius);
2
3 mVehicle->addWheel(mWheelNodes[0], connectionPointCS0,
wheelDirectionCS0, wheelAxleCS, gSuspensionRestLength,
gWheelRadius, isFrontWheel, gWheelFriction, gRollInfluence);
Figura 19.32: La gestin del determinismo puede ser un aspecto crtico en muchos videojuegos. El error
de determinismo rpidamente se propaga haciendo que la simulacin obtenga diferentes resultados.
19.12. Determinismo
El determinismo en el mbito de la simulacin fsica puede denirse de forma
intuitiva como la posibilidad de repeticin de un mismo comportamiento. En el
caso de videojuegos esto puede ser interesante en la repeticin de una misma jugada
en un videojuego deportivo, o en la ejecucin de una misma simulacin fsica en los
diferentes ordenadores de un videojuego multijugador. Incluso aunque el videojuego
siga un enfoque con clculos de simulacin centrados en el servidor, habitualmente es
necesario realizar ciertas interpolaciones del lado del cliente para mitigar los efectos
de la latencia, por lo que resulta imprescindible tratar con enfoques deterministas.
Para lograr determinismo es necesario lograr que la simulacin se realice exacta-
mente con los mismos datos de entrada. Debido a la precisin en aritmtica en punto
otante, es posible que v 2 dt no de el mismo resultado que v dt + v dt. As,
es necesario emplear el mismo valor de dt en todas las simulaciones. Por otro lado,
utilizar un dt jo hace que no podamos representar la simulacin de forma indepen-
diente de las capacidades de la mquina o la carga de representacin grca concreta
en cada momento. As, nos interesa tener lo mejor de ambas aproximaciones; por un
lado un tiempo jo para conseguir el determinismo en la simulacin, y por otro lado
la gestin con diferentes tiempos asociados al framerate para lograr independencia de
la mquina.
19.12. Determinismo [501]
Puede ayudar pensar que el motor de render produce chunks de tiempo dis-
creto, mientras que el motor de simulacin fsica los consume.
A continuacin se muestra un sencillo game loop que puede emplearse para con-
seguir determinismo de una forma sencilla.
Los tiempos mostrados en este pseudocdigo se especican en milisegundos, y se
obtienen a partir de una hipottica funcin getMillisecons().
La lnea 1 dene TickMs, una variable que nos dene la velocidad del reloj in-
terno de nuestro juego (por ejemplo, 32ms). Esta variable no tiene que ver con el reloj
el comporta-
de Bullet. Las variables relativas al reloj de simulacin fsica describen
miento independiente y asncrono del reloj interno de Bullet (lnea 2 ) y el reloj del
motor de juego (lnea 3 ).
C19
Listado 19.22: Pseudocdigo fsica determinista.
1 const unsigned int TickMs 32
2 unsigned long time_physics_prev, time_physics_curr;
3 unsigned long time_gameclock;
4
5 // Inicialmente reseteamos los temporizadores
6 time_physics_prev = time_physics_curr = getMilliseconds();
7 time_gameclock = getMilliseconds();
8
9 while (1) {
10 video->renderOneFrame();
11 time_physics_curr = getMilliseconds();
12 mWorld->stepSimulation(((float)(time_physics_curr -
13 time_physics_prev))/1000.0, 10);
14 time_physics_prev = time_physics_curr;
15 long long dt = getMilliseconds() - time_gameclock;
16
17 while(dt >= TickMs) {
18 dt -= TickMs;
19 time_gameclock += TickMs;
20 input->do_all_your_input_processing();
21 }
22 }
[502] CAPTULO 19. SIMULACIN FSICA
Como se indica en las lneas 6-7 , inicialmente se resetean los temporizadores.
El pseudocdigo del bucle principal del juego se resume en las lneas 9-21 . Tras
representar un
frame, se obtiene el tiempo transcurrido desde la ltima simulacin
fsica (lnea 11 ), y se avanza un paso de simulacin en segundos (como la llamada al
sistema lo obtiene en milisegundos y Bullet lo requiere en segundos, hay que dividir
por 1000).
Por ltimo, se actualiza la parte relativa al reloj de juego. Se calcula en dt la
diferencia entre los milisegundos que pasaron desde la ltima vezque se acualiz el
reloj de juego, y se dejan pasar (en el bucle denido en las lneas 17-21 ) empleando
ticks discretos. En cada tick consumido se procesan los eventos de entrada.
19.14. Serializacin
La serializacin de objetos en Bullet es una caracterstica propia de la biblioteca
que no requiere de ningn plugin o soporte adicional. La serializacin de objetos pre-
senta grandes ventajas relativas al preclculo de formas de colisin complejas. Para
guardar un mundo dinmico en un archivo .bullet, puede utilizarse el siguiente frag-
mento de cdigo de ejemplo:
C19
Tcnicas Avanzadas
El objetivo de este mdulo lado Tcnicas Avanzadas dentro
del Curso de Experto en Desarrollo de Videojuegos, es profundizar
es aspectos de desarrollo ms avanzados que complementen el resto
de contenidos de dicho curso y permitan explorar soluciones ms
eficientes en el contexto del desarrollo de videojuegos. En este
mdulo se introducen aspectos bsicos de jugabilidad y se describen
algunas metodologas de desarrollo de videojuegos. As mismo,
tambin se estudian los fundamentos bsicos de la validacin y
pruebas en este proceso de desarrollo. No obstante, uno de los
componentes ms importantes del presente mdulo est relacionado
con aspectos avanzados del lenguaje de programacin C++, como
por ejemplo el estudio en profundidad de la biblioteca STL, y las
optimizaciones. Finalmente, el presente mdulo se complementa con
aspectos de representacin avanzada, como los filtros de partculas o
la programacin de shaders, y con un estudio en detalle de tcnicas
de optimizacin para escenarios interiores y exteriores. Por otra
parte, se realiza un estudio de la plataforma de desarrollo de
videojuegos Unity, especialmente ideada para el desarrollo de juegos
en plataformas mviles.
Aspectos de Jugabilidad y
Captulo 20
Metodologas de Desarrollo
Miguel ngel Redondo Duque
Jos Luis Gonzlez
E
n este captulo se introducen aspectos bsicos relativos al concepto de jugabili-
dad, como por ejemplo aquellos vinculados a su caractarizacin en el mbito
del desarrollo de videojuegos o las facetas ms relevantes de los mismos, ha-
ciendo especial hincapi en la parte relativa a su calidad.
Por otra parte, en este captulo tambin se discuten los fundamentos de las me-
todologas de desarrollo para videojuegos, estableciendo las principales fases y las
actividades desarrolladas en ellas.
20.1.1. Introduccin
En el desarrollo de sistemas interactivos es fundamental la participacin del usua-
rio. Por ello, se plantean los denominados mtodos de Diseo Centrado en el Usuario
que se aplican, al menos, al desarrollo software que soporta directamente la interac-
cin con el usuario. En otras palabras, es fundamental contar con su participacin para
que tengamos la garanta de que se le va a proporcionar buenas experiencias de uso.
El software para videojuegos se puede considerar como un caso particular de sistema
interactivo por lo que requiere de un planteamiento similar en este sentido, aunque
en este mbito, los trminos y conceptos que se emplean para este propsito, varan
ligeramente.
507
[508] CAPTULO 20. ASPECTOS DE JUGABILIDAD Y METODOLOGAS DE DESARROLLO
atributos o propiedades que lo caracterizan. Estos atributos podrn ser medidos y va-
lorados, para as comparar y extraer conclusiones objetivas. Este trabajo fue realizado
por Jos Luis Gonzlez [40] que dene la Jugabilidad como el conjunto de propieda-
des que describen la experiencia del jugador ante un sistema de juego determinado,
cuyo principal objetivo es divertir y entretener de forma satisfactoria y creble, ya
sea solo o en compaa.
Es importante remarcar los conceptos de satisfaccin y credibilidad. El primero
es comn a cualquier sistema interactivo. Sin embargo, la credibilidad depender del
grado en el que se pueda lograr que los jugadores se impliquen en el juego.
Hay que signicar que los atributos y propiedades que se utilizan para caracterizar
la Jugabilidad y la Experiencia del Jugador, en muchos casos ya se han utilizado para
caracterizar la Usabilidad, pero en los videojuegos presentan matices distintos. Por
ejemplo, el Aprendizaje en un videojuego puede ser elevado, lo que puede provocar
que el jugador se vea satisfecho ante el reto que supone aprender a jugarlo y, pos-
teriormente, desarrollar lo aprendido dentro del juego. Un ejemplo lo tenemos en el
videojuego Prince of Persia, donde es difcil aprender a controlar nuestro personaje a
travs de un mundo virtual, lo que supone un reto en los primeros compases del juego.
Sin embargo, en cualquier otro sistema interactivo podra suponer motivo suciente
de rechazo. Por otro lado, la Efectividad en un juego no busca la rapidez por com-
pletar una tarea, pues entra dentro de la naturaleza del videojuego que el usuario est
jugando el mximo tiempo posible y son muchos los ejemplos que podramos citar.
Los atributos a los que hacemos referencia para caracterizar la Jugabilidad son los
siguientes:
C20
Inmersin. Capacidad para creerse lo que se juega e integrarse en el mundo
virtual mostrado en el juego.
Motivacin. Caracterstica del videojuego que mueve a la persona a realizar
determinadas acciones y a persistir en ellas para su culminacin.
Emocin. Impulso involuntario originado como respuesta a los estmulos del
videojuego, que induce sentimientos y que desencadena conductas de reaccin
automtica.
Socializacin. Atributos que hacen apreciar el videojuego de distinta manera al
jugarlo en compaa (multijugador), ya sea de manera competitiva, colaborativa
o cooperativa.
La gura 20.1 muestra como estos atributos y algunos otros ms pueden estar rela-
cionados con el concepto de Usabilidad tal y como se recoge en las normas ISO/IEC-
9241. Hay algunos atributos que estn relacionados con el videojuego (producto) y
otros se vinculan al proceso de desarrollo del juego (desarrollo), algunos hacen refe-
rencia a su inuencia sobre el jugador/es (usuarios o grupos de usuarios).
[510] CAPTULO 20. ASPECTOS DE JUGABILIDAD Y METODOLOGAS DE DESARROLLO
En [40] incluso se relacionan, a nivel interactivo, estas facetas para ilustrar cmo
pueden ser las implicaciones e inuencias que presentan. Esta relacin se resume en
la gura 20.2.
C20
Con todo lo anterior, se puede concluir que la Jugabilidad de un juego podra con-
siderarse como el anlisis del valor de cada una de las propiedades y de los atributos
en las facetas consideradas.
C20
Figura 20.3: Clasicacin de las propiedades de la calidad del producto y del proceso en un videojuego
[514] CAPTULO 20. ASPECTOS DE JUGABILIDAD Y METODOLOGAS DE DESARROLLO
C20
Figura 20.8: Mtricas para atributos de Seguridad
habilidades y maneras de jugar del jugador. Por otro lado, en un videojuego hay metas
que debe realizar el jugador, pero sin embargo, la facilidad de consecucin de esas
metas no es el principal objetivo. De hecho, ms bien podra decirse que es justo lo
contrario, el uso del juego debe ser una motivacin y presentar cierta dicultad de
consecucin, de lo contrario el jugador perder motivacin por el uso del videojuego.
As mismo, la medida de la frecuencia de error en el software tradicional con
un valor cercano a 0 siempre es mejor, pero en videojuegos podemos encontrar tanto
valores cercanos a 0 como a 1. Si el valor es cercano a 0, nos encontramos ante un
jugador experto o que la dicultad del juego es muy baja. Cercano a 1, nos informa
que nos encontramos ante un jugador novato, o que se encuentra en los primeros com-
pases del juego, o que la dicultad es muy elevada. Es por ello que los videojuegos
ofrecen distintos niveles de dicultad para atraer a los nuevos jugadores, evitando, por
ejemplo, que una dicultad extremadamente fcil haga que el juego pierda inters y
se vuelva aburrido.
La ecacia en el caso de los videojuegos es relativa, es decir, el usuario querr ju-
gar de forma inmediata, sin perder tiempo en recibir excesiva informacin, pero, de la
misma manera que comentbamos con anterioridad, el juego debe aportar dicultad y
el usuario debera encontrar cierta resistencia y progresiva dicultad en la consecucin
de las metas que lleve asociado.
La personalizacin tambin es algo especialmente deseable en el mundo del vi-
deojuego, porque en l coexisten muchos elementos de diseo que tratan de distraer,
de acompaar y de establecer la forma de interaccin. sta ltima debera ser exible
en cuanto a poder dar soporte a diferentes formas de interactuar: teclas, mandos, soni-
dos, etc. El atributo de la accesibilidad, aunque deseable y exigible, tradicionalmente
no ha contado con mucha atencin en el desarrollo de videojuegos.
Este aspecto est cambiando y la presencia de este atributo contribuye al uso del
mismo ya sea en la interfaz de usuario o en las mecnicas del juego. En este modelo de
Jugabilidad este atributo se consider implcitamente dentro de otros. Los problemas
de la accesibilidad pueden considerarse problemas de Usabilidad/Jugabilidad para,
por ejemplo, jugadores con algn tipo de discapacidad.
Si un jugador no puede entender lo que se dice en determinadas escenas u or
si otro personaje camina detrs de l por problemas de sonido, es recomendable el
uso de subttulos. Si el jugador no puede manejar determinado control de juego, se
recomienda el uso de dispositivos alternativos para facilitar el control de juego.
La seguridad/prevencin es un factor que, en el caso de los videojuegos, toma
cada vez ms importancia. El juego, en la actualidad, no es slo esttico, mental y
de sobremesa, sino que supone, en algunos casos, exigencias fsicas, por ejemplo el
uso de un control de juego que demande un esfuerzo corporal o movimientos bruscos,
los cuales pueden ser potencialmente peligrosos o dainos para la salud si el jugador
desarrolla la actividad de juego con ellos durante un tiempo prolongado de ocio.
La satisfaccin es el atributo ms determinante al tratar con videojuegos. Muchos
aspectos: cognitivos, emocionales, fsicos, de conanza y sociales pueden considerar-
se bajo este factor de la Jugabilidad. La estimacin de la misma se realiza fundamen-
talmente con cuestionarios y observando al jugador mientras juega y viendo cules son
sus preferencias de un momento de ocio para el siguiente. Probablemente, este atribu-
to en videojuegos es el ms rico y subjetivo. Por lo tanto, es el que se ha enriquecido
ms con atributos y propiedades para mejorar su medida y estimacin.
Finalmente, en la ltima columna de la tabla se proponen diversos mtodos de
evaluacin para cada mtrica. Estos mtodos pueden enriquecerse y guiarse por las
facetas, las cuales nos pueden ayudar a identicar la calidad de elementos concretos
de un videojuego segn el uso y las acciones mostradas por el conjunto de jugadores.
20.1. Jugabilidad y Experiencia del Jugador [517]
Las principales formas de medicin son la observacin, donde podemos medir con
herramientas cmo y de qu manera acta el jugador con el videojuego, usando por
ejemplo las mtricas presentadas o usar cuestionarios o tests heursticos para preguntar
o interrogar por atributos de la Jugabilidad. Estos cuestionarios pueden ir guiados por
facetas para facilitar su anlisis.
C20
[518] CAPTULO 20. ASPECTOS DE JUGABILIDAD Y METODOLOGAS DE DESARROLLO
20.2.1. Pre-Produccin
En la fase de Pre-Produccin se lleva a cabo la concepcin de la idea del juego,
C20
identicando los elementos fundamentales que lo caracterizarn y nalizando, si es
posible, en un diseo conceptual del mismo. Esta informacin se organiza para dar
lugar a lo que puede considerarse una primera versin del documento de diseo del
juego o ms conocido como GDD (Game Design Document). En este GDD, que debe
ser elaborado por el equipo creativo del diseo de videojuegos, se debe identicar
y jar todo lo relacionado con el Diseo del Videojuego que ser necesario abordar
posteriormente (normalmente en la fase de Produccin).
Como patrn de referencia y de acuerdo a lo establecido en [12], el GDD debe
contener lo siguiente:
Como se ha indicado anteriormente, esta primera versin del GDD ser el punto
de partida para iniciar la fase de Produccin, pero cabe insistir sobre la importancia de
uno de sus elementos: se trata del Gameplay. Dado el carcter un tanto difuso de este
concepto, consideremos como ejemplo el caso particular del conocido y clsico jue-
go Space Invaders. En este juego indicaramos que se debe poder mover una nave
alrededor del cuadrante inferior de la pantalla y disparar a una serie de enemigos que
aparecen por la parte superior de la pantalla y que desaparecen cuando son alcanzados
C20
por los disparos. Estos enemigos tratan de atacarnos con sus disparos y presionn-
donos mediante la reduccin de nuestro espacio de movimientos e intentando chocar
contra nuestra nave.
El Gameplay tiene una implicacin importantsima en la calidad nal del juego
y, por lo extensin, en la Jugabilidad del mismo. Luego los esfuerzos destinados a su
anlisis y planteamiento revertirn directamente en las propiedades que caracterizan la
Juabilidad. No obstante y para profundizar en ms detalle sobre este aspecto, se reco-
mienda consultar los siguientes libros: Rules of Play: Game Design Fundamentals
[80] y Game Design: Theory and Practice [77].
20.2.2. Produccin
La fase de Produccin es la fase donde se concentra el trabajo principal, en volu-
men y en nmero de participantes, del proceso de desarrollo del videojuego, especial-
mente en lo que se denomina Diseo del Juego y Diseo Tcnico. Hay que signicar
que este curso est orientado, fundamentalmente, a las tareas y tcnicas relacionadas
con el Diseo Tcnico, pero no queremos dejar de situarlo en el contexto del proceso
global que requiere llevarse a cabo para concebir y desarrollar un producto de estas
caractersticas.
[522] CAPTULO 20. ASPECTOS DE JUGABILIDAD Y METODOLOGAS DE DESARROLLO
C20
se publicar y que se producir. Obviamente, incluye todo el contenido artstico,
tcnico y documental (es decir, los manuales de usuario). En este momento, la
publicidad deber ser la mayor posible, incluyndose la realizacin de reportajes,
artculos, etc.
20.2.3. Post-Produccin
La fase de Post-Produccin, en la que no nos vamos a detener ya que se aleja
bastante del contenido tratado en el curso, aborda fundamentalmente la explotacin y
el mantenimiento del juego como si de cualquier otro producto software se tratase.
[524] CAPTULO 20. ASPECTOS DE JUGABILIDAD Y METODOLOGAS DE DESARROLLO
C20
perles de jugadores existentes. Adems, esto contribuye directamente a la reduccin
de la proliferacin de productos que requieren numerosos parches incluso desde los
primeros meses de vida en el mercado.
En este sentido [40] propone un mtodo inspirado directamente en la metodologa
PPIu+a propuesta en [41] para Ingeniera de la Usabilidad y que se resume en la
gura 20.12. Para facilitar su comprensin puede utilizarse la gura 20.13 en la que
se relaciona y compara esta metodologa MPIu+a.
En las fuentes citadas pueden encontrar muchos ms detalles sobre la fases ms
destacables que son las de anlisis, diseo, desarrollo y evaluacin de elementos juga-
bles. Especialmente, se plantea un patrn a seguir para la obtencin de requisitos de
Jugabilidad con ejemplos de aplicacin, se proponen una serie de guas de estilo para
llevar a cabo un diseo que fomente la Jugabilidad, se muestra cmo aplicar Scrum y
programacin extrema para la construccin de prototipos jugables y se describe cmo
evaluar la Jugabilidad de los prototipos para obtener conclusiones sobre la experiencia
del jugador.
[526] CAPTULO 20. ASPECTOS DE JUGABILIDAD Y METODOLOGAS DE DESARROLLO
Figura 20.13: Comparacin entre el mtodo de Diseo Centrado en el Jugador y el de Diseo Centrado en
el Usuario de MPIu+a
Captulo
C++ Avanzado
21
Francisco Moya Fernndez
David Villa Alises
Sergio Prez Camacho
Cleto Martn Angelina
527
[528] CAPTULO 21. C++ AVANZADO
10 1
4 17 4
1 5 16 21 10
5 17
16 21
Figura 21.1: Dos rboles de bsqueda binaria. Ambos contienen los mismos elementos pero el de la iz-
quierda es mucho ms eciente
Todos los nodos del subrbol izquierdo de un nodo tienen una clave
menor o igual a la de dicho nodo. Anlogamente, la clave de un nodo es
siempre menor o igual que la de cualquier otro nodo del subrbol derecho.
Por tratarse del primer tipo de rboles expondremos con cierto detalle su imple-
mentacin. Como en cualquier rbol necesitamos modelar los nodos del rbol, que
corresponden a una simple estructura:
C21
3 typedef Node<KeyType> NodeType;
4
5 KeyType key;
6 NodeType* parent;
7 NodeType* left;
8 NodeType* right;
Para obtener el mnimo basta recorrer todos los subrboles de la izquierda y anlo-
gamente para encontrar el mximo hay que recorrer todos los subrboles de la derecha
hasta llegar a un nodo sin subrbol derecho.
El atributo root mantiene cul es el nodo raz del rbol. Los mtodos de insercin
y borrado deben actualizarlo adecuadamente.
C21
de debe insertar el elemento, manteniendo el padre del elemento actual para poder
recuperar el punto adecuado al llegar a un nodo nulo.
El procedimiento ms complejo es el de borrado de un nodo. De acuerdo a [20] se
identican los cuatro casos que muestra la gura 21.2. Un caso no representado es el
caso trivial en el que el nodo a borrar no tenga hijos. En ese caso basta con modicar
el nodo padre para que el hijo correspondiente sea el objeto nulo. Los dos primeros
casos de la gura corresponden al borrado de un nodo con un solo hijo, en cuyo caso el
hijo pasa a ocupar el lugar del nodo a borrar. El tercer caso corresponde al caso en que
el hijo derecho no tenga hijo izquierdo o el hijo izquierdo no tenga hijo derecho, en
cuyo caso se puede realizar la misma operacin que en los casos anteriores enlazando
adecuadamente las dos ramas. El cuarto caso corresponde al caso general, con dos
hijos no nulos. En ese caso buscamos un sucesor del subrbol izquierdo que no tenga
hijo izquierdo, que pasa a reemplazar al nodo, reajustando el resto para mantener la
condicin de rbol de bsqueda binaria.
Con el objetivo de facilitar el movimiento de subrboles denimos el mtodo
transplant(). El subrbol con raz u se reemplaza con el subrbol con raz v.
[532] CAPTULO 21. C++ AVANZADO
Red-black trees
(a) q q
z r
NIL r
(b) q q
z l
l NIL
(c) q q
z y
l y l x
NIL x
C21
(d) q q q
z z y y
l r l NIL r l r
y x x
NIL x
Figura 21.2: Casos posibles segn [20] en el borrado de un nodo en un rbol de bsqueda binaria
[534] CAPTULO 21. C++ AVANZADO
10
4 17
1 5 16 21
Figura 21.3: Ejemplo de rbol rojo-negro. Los nodos hoja no se representarn en el resto del texto.
y rotate right
x
x c a y
rotate left
a b b c
C21
Figura 21.4: Operacin de rotacin a la derecha o a la izquierda en un rbol de bsqueda binaria
9 x = x->right;
10 }
11 z->parent = y;
12 if (y == NodeType::nil())
13 root = z;
14 else if (z->key < y->key)
15 y->left = z;
16 else
17 y->right = z;
18 z->left = NodeType::nil();
19 z->right = NodeType::nil();
20 z->color = Node::Color::Red;
21 insert_fixup(z);
22 }
Al asumir el color rojo podemos haber violado las reglas de los rboles rojo-negro.
Por esta razn llamamos a la funcin insert_fixup() que garantiza el cumpli-
miento de las reglas tras la insercin:
La insercin de un nodo rojo puede violar la regla 2 (el nodo raz queda como
rojo en el caso de un rbol vaco) o la regla 4 (el nodo insertado pasa a ser hijo de
un nodo rojo). Este ltimo caso es el que se contempla en el bucle de la funcin
insert_fixup(). Cada una de las dos ramas del if sigue la estrategia dual, depen-
diendo de si el padre es un hijo derecho o izquierdo. Basta estudiar el funcionamiento
de una rama, dado que la otra es idntica pero intercambiando right y left. Bsi-
camente se identican tres casos.
El primero corresponde a las lneas 6 a 9 . Es el caso en que el nodo a insertar
pasa a ser hijo de un nodo rojo cuyo hermano tambin es rojo (e.g. gura 21.5.a).
En este caso el nodo padre y el nodo to se pasan a negro mientras que el abuelo
se pasa a rojo (para mantener el nmero de nodos negros en todos los caminos).
Al cambiar a rojo el nodo abuelo es posible que se haya vuelto a violar alguna
regla, y por eso se vuelven a comprobar los casos.
Otra posibilidad es que el nodo to sea negro y adems el nodo insertado sea hijo
derecho (e.g. gura 21.5.b). En ese caso se realiza una rotacin a la izquierda
para reducirlo al caso siguiente y se aplica lo correspondiente al ltimo caso.
El ltimo caso corresponde a que el nodo to sea negro y el nodo insertado sea
hijo izquierdo (e.g. gura 21.5.c). En ese caso se colorea el padre como negro, y
el abuelo como rojo, y se rota a la derecha el abuelo. Este mtodo deja un rbol
correcto.
z z z
C21
(a) (b) (c)
Figura 21.5: Casos contemplados en la funcin insert_fixup().
El nodo y corresponde al nodo que va a eliminarse o moverse dentro del rbol. Ser
el propio z si tiene menos de dos hijos o el nodo y de los casos c y d en la gura 21.2.
Mantenemos la variable y_orig_color con el color que tena ese nodo que se ha
eliminado o movido dentro del rbol. Solo si es negro puede plantear problemas de
violacin de reglas, porque el nmero de nodos negros por cada rama puede variar.
Para arreglar los problemas potenciales se utiliza una funcin anloga a la utilizada en
la insercin de nuevos nodos.
19 w->color = Node::Color::Red;
20 rotate_right(w);
21 w = x->parent->right;
22 }
23 w->color = x->parent->color;
24 x->parent->color = Node::Color::Black;
25 w->right->color = Node::Color::Black;
26 rotate_left(x->parent);
27 x = root;
28 }
29 }
30 else {
31 NodeType* w = x->parent->left;
32 if (w->color == Node::Color::Red) {
33 w->color = Node::Color::Black;
34 x->parent->color = Node::Color::Red;
35 rotate_right(x->parent);
36 w = x->parent->left;
37 }
38 if (w->right->color == Node::Color::Black
39 && w->left->color == Node::Color::Black) {
40 w->color = Node::Color::Red;
41 x = x->parent;
42 }
43 else {
44 if (w->left->color == Node::Color::Black) {
45 w->right->color = Node::Color::Black;
46 w->color = Node::Color::Red;
47 rotate_left(w);
48 w = x->parent->left;
49 }
50 w->color = x->parent->color;
51 x->parent->color = Node::Color::Black;
52 w->left->color = Node::Color::Black;
53 rotate_right(x->parent);
54 x = root;
55 }
56 }
57 }
58 x->color = Node::Color::Black;
59 }
C21
El hermano w es rojo. En ese caso forzosamente los hijos de w deben ser negros.
Por tanto se puede intercambiar los colores del hermano y del padre y hacer una
rotacin a la izquierda sin violar nuevas reglas. De esta forma el nuevo hermano
ser forzosamente negro, por lo que este caso se transforma en alguno de los
posteriores.
El hermano w es negro y los dos hijos de w son negros. En ese caso cambiamos
el color del hermano a rojo. De esta forma se equilibra el nmero de negros por
cada rama, pero puede generar una violacin de reglas en el nodo padre, que se
tratar en la siguiente iteracin del bucle.
El hermano w es negro y el hijo izquierdo de w es rojo. En ese caso intercambia-
mos los colores de w y su hijo izquierdo y hacemos una rotacin a la derecha.
De esta forma hemos reducido este caso al siguiente.
El hermano w es negro y el hijo derecho de w es rojo. En este caso cambiando
colores en los nodos que muestra la gura 21.6.d y rotando a la izquierda se
obtiene un rbol correcto que compensa el nmero de negros en cada rama.
[540] CAPTULO 21. C++ AVANZADO
(a)
B D
x A D w B E
C E x A w C
(b)
B x B
x A D w A D
C E C E
(c)
B B
x A D w x A C w
C E D
(d)
B D
x A D w B E
C E A C
AVL trees
Los rboles AVL (Adelson-Velskii and Landis) son otra forma de rbol balanceado
en el que se utiliza la altura del rbol como criterio de balanceo. Solo puede haber una
diferencia de 1 entre la altura de dos ramas. Es por tanto un criterio ms estricto que
los red-black trees, lo que lo hace menos eciente en las inserciones y borrados pero
ms eciente en las lecturas.
rboles balanceados
Los red-black trees son ms ecien-
tes en insert() y remove(),
pero los AVL trees son ms ecien-
tes en search().
21.1. Estructuras de datos no lineales [541]
Cada nodo tiene informacin adicional con la altura del rbol en ese punto. En
realidad tan solo es necesario almacenar el factor de equilibrio que es simplemente
la diferencia entre las alturas del subrbol izquierdo y el derecho. La ventaja de esta
ltima alternativa es que es un nmero mucho ms reducido (siempre comprendido en
el rango -2 a +2) por lo que puede almacenarse en solo 3 bits.
Para insertar elementos en un rbol AVL se utiliza un procedimiento similar a
cualquier insercin en rboles de bsqueda binaria, con dos diferencias:
Radix tree
An hay otro tipo de rboles binarios que vale la pena comentar por sus implicacio-
nes con los videojuegos. Se trata de los rboles de prejos, frecuentemente llamados
tries1 .
La gura 21.8 muestra un ejemplo de rbol de prejos con un conjunto de enteros
binarios. El rbol los representa en orden lexicogrco. Para cada secuencia binaria si
empieza por 0 est en el subrbol izquierdo y si empieza por 1 en el subrbol derecho.
Conforme se recorren las ramas del rbol se obtiene la secuencia de bits del nmero
a buscar. Es decir, el tramo entre el nodo raz y cualquier nodo intermedio dene el
prejo por el que empieza el nmero a buscar. Por eso a este rbol se le llama prex
C21
tree o radix tree.
Un rbol de prejos (pero no binario) se utiliza frecuentemente en los diccionarios
predictivos de los telfonos mviles. Cada subrbol corresponde a una nueva letra de
la palabra a buscar.
Tambin se puede utilizar un rbol de prejos para indexar puntos en un segmento
de longitud arbitraria. Todos los puntos en la mitad derecha del segmento estn en
el subrbol derecho, mientras que todos los puntos de la mitad izquierda estn en
el subrbol izquierdo. Cada subrbol tiene las mismas propiedades con respecto al
subsegmento que representa. Es decir, el subrbol derecho es un rbol de prejos que
representa a medio segmento derecho, y as sucesivamente. El nmero de niveles del
rbol es ajustable dependiendo de la precisin que requerimos en el posicionamiento
de los puntos.
1 El nombre en singular es trie, que deriva de retrieve. Por tanto la pronunciacin correcta se asemeja a
(a) A A
h+2 h h h+2
B B
C C
(b) A A
h+2 h h h+2
C C
B B
(c) C C
h+1 h+1 h+1 h+1
B A B A
En los rboles de prejos la posicin de los nodos est prejada a priori por el
valor de la clave. Estos rboles no realizan ninguna funcin de equilibrado por lo que
su implementacin es trivial. Sin embargo estarn razonablemente equilibrados si los
nodos presentes estn uniformemente repartidos por el espacio de claves.
0 1
0
1 0
10
1 0 1
011 100
1
1011
Figura 21.8: Un ejemplo de trie extraido de [20]. Contiene los elementos 1011, 10, 011, 100 y 0.
C21
1 template <typename Func>
2 void preorder_tree_walk(Func f) {
3 preorder_tree_walk(root, f);
4 }
5
6 template <typename Func>
7 void preorder_tree_walk(NodeType* x, Func f) {
8 if (x == 0) return;
9 f(x);
10 preorder_tree_walk(x->left, f);
11 preorder_tree_walk(x->right, f);
12 }
3 postorder_tree_walk(root, f);
4 }
5
6 template <typename Func>
7 void postorder_tree_walk(NodeType* x, Func f) {
8 if (x == 0) return;
9 postorder_tree_walk(x->left, f);
10 postorder_tree_walk(x->right, f);
11 f(x);
12 }
Pero para el recorrido de estructuras de datos con frecuencia es mucho mejor em-
plear el patrn iterador. En ese caso puede reutilizarse cualquier algoritmo de la STL.
Incluir el orden de recorrido en el iterador implica almacenar el estado necesario.
Las funciones de recorrido anteriormente descritas son recursivas, por lo que el es-
tado se almacenaba en los sucesivos marcos de pila correspondientes a cada llamada
anidada. Por tanto necesitamos un contenedor con la ruta completa desde la raz has-
ta el nodo actual. Tambin tendremos que almacenar el estado de recorrido de dicho
nodo, puesto que el mismo nodo es visitado en tres ocasiones, una para el subrbol
izquierdo, otra para el propio nodo, y otra para el subrbol derecho.
41
42 template<typename KeyType>
43 inline bool operator== (inorder_iterator<KeyType> const& lhs,
44 inorder_iterator<KeyType> const& rhs) {
45 return lhs.equal(rhs);
46 }
C21
21.1.3. Quadtree y octree
Los rboles binarios particionan de forma eciente un espacio de claves de una
sola dimensin. Pero con pequeas extensiones es posible particionar espacios de dos
y tres dimensiones. Es decir, pueden ser usados para indexar el espacio de forma
eciente.
Los quadtrees y los octrees son la extensin natural de los rboles binarios de
prejos (tries) para dos y tres dimensiones respectivamente. Un quadtree es un rbol
cuyos nodos tienen cuatro subrboles correspondientes a los cuatro cuadrantes de un
espacio bidimensional. Los nodos de los octrees tienen ocho subrboles correspon-
dientes a los ocho octantes de un espacio tridimensional.
La implementacin y el funcionamiento es anlogo al de un rbol prejo utilizado
para indexar los puntos de un segmento. Adicionalmente, tambin se emplean para
indexar segmentos y polgonos.
[546] CAPTULO 21. C++ AVANZADO
Cuando se utilizan para indexar segmentos o polgonos puede ocurrir que un mis-
mo segmento cruce el lmite de un cuadrante o un octante. En ese caso existen dos
posibles soluciones:
Hacer un recortado (clipping) del polgono dentro de los lmites del cuadrante
u octante.
Poner el polgono en todos los cuadrantes u octantes con los que intersecta.
En este ltimo caso es preciso disponer de alguna bandera asociada a los polgonos
para no recorrerlos ms veces de las necesarias.
Simon Perreault distribuye una implementacin sencilla y eciente de octrees en
C++2 . Simplicando un poco esta implementacin los nodos son representados de esta
forma:
octree.html.
21.1. Estructuras de datos no lineales [547]
17 Node* children[2][2][2];
18 };
19
20 class Leaf : public Node {
21 public:
22 Leaf( const T& v );
23
24 const T& value() const;
25 T& value();
26 void setValue( const T& v );
27
28 private:
29 T value_;
30 };
La lnea 5 construye un octree de 4096 puntos de ancho en cada dimensin.
Esta implementacin requiere que sea una potencia de dos, siendo posible indexar
4096 4096 4096 nodos.
C21
La regularidad de los octree los hacen especialmente indicados para la parale-
lizacin con GPU y recientemente estn teniendo cierto resurgimiento con su
utilizacin en el renderizado de escenas con raycasting o incluso raytracing
en una tcnica denominada Sparse Voxel Octree.
thesis/CCrassinThesis_EN_Web.pdf
[548] CAPTULO 21. C++ AVANZADO
Bjarne Stroustrup invent una tcnica de aplicacin general para resolver este tipo
de problemas. Se llama RAII (Resource Acquisition Is Initialization) y bsicamente
consiste en encapsular las operaciones de adquisicin de recursos y liberacin de re-
cursos en el constructor y destructor de una clase normal. Esto es precisamente lo que
hace auto_ptr con respecto a la reserva de memoria dinmica. El mismo cdigo
del fragmento anterior puede reescribirse de forma segura as:
Como puede verse hemos ligado el tiempo de vida del objeto construido en memo-
ria dinmica al tiempo de vida del auto_ptr, que suele ser una variable automtica o
un miembro de clase. Se dice que el auto_ptr posee al objeto dinmico. Pero puede
ceder su posesin simplemente con una asignacin o una copia a otro auto_ptr.
Es decir, auto_ptr garantiza que solo hay un objeto que posee el objeto di-
nmico. Tambin permite desligar el objeto dinmico del auto_ptr para volver a
gestionar la memoria de forma explcita.
C21
1 unique_ptr<T> p(new T());
2 unique_ptr<T> q;
3 q = p; // error (no copiable)
4 q = std:move(p);
Listado 21.31: Funcin que reserva memoria dinmica y traspasa la propiedad al llamador.
Tambin funcionara correctamente con auto_ptr.
1 unique_ptr<T> f() {
2 unique_ptr<T> p(new T());
3 // ...
4 return p;
5 }
La funcin f() devuelve memoria dinmica. Con simples punteros eso implicaba
que el llamante se haca cargo de su destruccin, de controlar su ciclo de vida. Con esta
construccin ya no es necesario. Si el llamante ignora el valor de retorno ste se libera
automticamente al destruir la variable temporal correspondiente al valor de retorno.
Si en cambio el llamante asigna el valor de retorno a otra variable unique_ptr
entonces est asumiendo la propiedad y se liberar automticamente cuando el nuevo
unique_ptr sea destruido.
Tanto con auto_ptr como con unique_ptr se persigue que la gestin de me-
moria dinmica sea anloga a la de las variables automticas con semntica de copia.
Sin embargo no aprovechan la posibilidad de que el mismo contenido de memoria sea
utilizado desde varias variables. Es decir, para que la semntica de copia sea la natural
en los punteros, que se generen dos objetos equivalentes, pero sin copiar la memoria
dinmica. Para ese caso el nico soporte que ofreca C++ hasta ahora eran los punteros
y las referencias. Y ya sabemos que ese es un terreno pantanoso.
La biblioteca estndar de C++11 incorpora dos nuevas plantillas para la gestin
del ciclo de vida de la memoria dinmica que ya existan en la biblioteca Boost:
shared_ptr y weak_ptr. Ambos cooperan para disponer de una gestin de me-
moria muy exible. La plantilla shared_ptr implementa una tcnica conocida co-
mo conteo de referencias.
Cuando se asigna un puntero por primera vez a un shared_ptr se inicializa
un contador interno a 1. Este contador se almacena en memoria dinmica y es com-
partido por todos los shared_ptr que apunten al mismo objeto. Cuando se asigna
este shared_ptr a otro shared_ptr o se utiliza el constructor de copia, se in-
crementa el contador interno. Cuando se destruye un shared_ptr se decrementa
el contador interno. Y nalmente cuando el contador interno llega a 0, se destruye
automticamente el objeto dinmico.
7 }
8 // ...
En la lnea 1 se construye un shared_ptr que apunta a un objeto dinmi-
co. Esto pone el contador interno de referencias a 1. En la lnea 4 se asigna este
shared_ptr a otro. No se copia el objeto dinmico, sino solo su direccin y la del
que adems es automticamente incrementado (pasa a va-
contador de referencias,
ler 2). En la lnea 5 se utiliza el constructor de copia de otro shared_ptr, que
nuevamente copia solo el puntero y el puntero al contador de referencias, adems de
incrementar su valor (pasa a valer 3). En la lnea 7 se destruye automticamente r,
con lo que se decrementa el contador de referencias (vuelve a valer 2). Cuando acabe
el bloque en el que se han declarado p y q se destruirn ambas variables, y con ello se
decrementar dos veces el contador de referencias. Al llegar a 0 automticamente se
invocar el operador delete sobre el objeto dinmico.
El conteo de referencias proporciona una poderosa herramienta para simplicar
la programacin de aplicaciones con objetos dinmicos. Los shared_ptr pueden
copiarse o asignarse con total libertad y con una semntica intuitiva. Pueden emplearse
en contenedores de la STL y pasarlos por valor libremente como parmetros a funcin
o como valor de retorno de una funcin. Sin embargo no estn totalmente exentos de
problemas. Considera el caso en el que main() tiene un shared_ptr apuntando
a una clase A y sta a su vez contiene directa o indirectamente un shared_ptr que
vuelve a apuntar a A. Tendramos un ciclo de referencias y el contador de referencias
con un balor de 2. En caso de que se destruyera el shared_ptr inicial seguiramos
teniendo una referencia a A, por lo que no se destruir.
Para romper los ciclos de referencias la biblioteca estndar incluye la plantilla
weak_ptr. Un weak_ptr es otro smart pointer a un objeto que se utiliza en estas
condiciones:
C21
los asteroides vecinos para detectar colisiones. Una colisin lleva a la destruccin
de uno o ms asteroides. Cada asteroide debe almacenar una lista de los asteroides
vecinos. Pero el hecho de pertenecer a esa lista no mantiene al asteroide vivo. Por
tanto el uso de shared_ptr sera inapropiado. Por otro lado un asteroide no debe ser
destruido mientras otro asteroide lo examina (para calcular los efectos de una colisin,
por ejemplo), pero debe llamarse al destructor en algn momento para liberar los
recursos asociados. Necesitamos una lista de asteroides que podran estar vivos y una
forma de sujetarlos por un tiempo. Eso es justo lo que hace weak_ptr.
Listado 21.33: Esquema de funcionamiento del propietario de los asteroides. Usa shared_ptr
para representar propiedad.
1 vector<shared_ptr<Asteroid>> va(100);
2 for (int i=0; i<va.size(); ++i) {
3 // ... calculate neighbors for new asteroid ...
4 va[i].reset(new Asteroid(weak_ptr<Asteroid>(va[neighbor])))
;
5 launch(i);
5 http://www.research.att.com/~bs/C++0xFAQ.html#std-weak_ptr
[552] CAPTULO 21. C++ AVANZADO
6 }
7 // ...
La primera regla impide tener punteros normales coexistiendo con los smart poin-
ters. Eso solo puede generar quebraderos de cabeza, puesto que el smart pointer no es
capaz de trazar los accesos al objeto desde los punteros normales.
La segunda regla garantiza la liberacin correcta de la memoria en presencia de
excepciones6 . Veamos un ejemplo extrado de la documentacin de Boost:
Para entender por qu la linea 10 es peligrosa basta saber que el orden de eva-
luacin de los argumentos no est especicado. Podra evaluarse primero el operador
new, despus llamarse a la funcin g(), y nalmente no llamarse nunca al constructor
de shared_ptr porque g() eleva una excepcin.
6 Este caso ha sido descrito en detalle por Herb Sutter en http://www.gotw.ca/gotw/056.htm.
21.2. Patrones de diseo avanzados [553]
C21
2 public:
3 C();
4 /*...*/
5 private:
6 class CImpl;
7 auto_ptr<CImpl> pimpl_;
8 };
7 Por ejemplo, en
http://www.gotw.ca/publications/using_auto_ptr_effectively.htm.
[554] CAPTULO 21. C++ AVANZADO
21.2.2. Command
El patrn command (se traducira como orden en castellano) se utiliza frecuente-
mente en interfaces grcas para el manejo de las rdenes del usuario. Consiste en
encapsular las peticiones en objetos que permiten desacoplar la emisin de la orden
de la recepcin, tanto desde el punto de vista lgico como temporal.
Problema
Solucin
C21
Figura 21.14: Ejemplo de aplicacin del patrn command.
El interfaz de usuario crea las rdenes a realizar por el personaje o los personajes
que estn siendo controlados, as como la asociacin con su personaje. Estas acciones
se van procesando por el motor del juego, posiblemente en paralelo.
Implementacin
Entre estos dos extremos se encuentran las rdenes que realizan algunas funcio-
nes pero delegan otras en el receptor. En general a todo este tipo de rdenes se les
denomina active commands.
Desde el punto de vista de la implementacin hay poco que podamos aadir a una
orden activa. Tienen cdigo de aplicacin especco que hay que aadir en el mtodo
execute().
Sin embargo, los forwarding commands actan en cierta forma como si se tratara
de punteros a funcin. El Invoker invoca el mtodo execute() del objeto orden y
ste a su vez ejecuta un mtodo del objeto Receiver al que est asociado. En [9] se
describe una tcnica interesante para este n, los generalized functors o adaptadores
polimrcos para objetos funcin. Se trata de una plantilla que encapsula cualquier
objeto, cualquier mtodo de ese objeto, y cualquier conjunto de argumentos para dicho
mtodo. Su ejecucin se traduce en la invocacin del mtodo sobre el objeto con
los argumentos almacenados. Este tipo de functors permiten reducir sensiblemente
el trabajo que implicara una jerarqua de rdenes concretas. Boost implementa una
tcnica similar en la plantilla function, que ha sido incorporada al nuevo estndar
de C++ (en la cabecera functional). Por ejemplo:
Consideraciones
El patrn command desacopla el objeto que invoca la operacin del objeto que
sabe cmo se realiza.
Al contrario que la invocacin directa, las rdenes son objetos normales. Pueden
ser manipulados y extendidos como cualquier otro objeto.
Las rdenes pueden ser agregadas utilizando el patrn composite.
Las rdenes pueden incluir transacciones para garantizar la consistencia sin nin-
gn tipo de precaucin adicional por parte del cliente. Es el objeto Invoker el
que debe reintentar la ejecucin de rdenes que han abortado por un interblo-
queo.
Si las rdenes a realizar consisten en invocar directamente un mtodo o una fun-
cin se puede utilizar la tcnica de generalized functors para reducir el cdigo
necesario sin necesidad de implementar una jerarqua de rdenes.
Problema
Solucin
C21
1 template<typename T> class Base;
2
3 class Derived: public Base<Derived> {
4 // ...
5 };
La clase derivada hereda de una plantilla instanciada para ella misma. La clase
base cuenta en su implementacin con un tipo que deriva de ella misma. Por tanto la
propia clase base puede llamar a funciones especializadas en la clase derivada.
Implementacin
Se han propuesto multitud de casos donde puede aplicarse este patrn. Nosotros
destacaremos en primer lugar su uso para implementar visitantes alternativos a los ya
vistos en el mdulo 1.
Recordaremos brevemente la estructura del patrn visitante tal y como se cont en
el mdulo 1. Examinando la gura 21.15 podemos ver que:
[558] CAPTULO 21. C++ AVANZADO
La clase base Visitor (y por tanto todas sus clases derivadas) es tremen-
damente dependiente de la jerarqua de objetos visitables de la izquierda. Si
se implementa un nuevo tipo de elemento ElementC (una nueva subclase de
Element) tendremos que aadir un nuevo mtodo visitElementB() en
la clase Visitor y con ello tendremos que reescribir todos y cada uno de
las subclases de Visitor. Cada clase visitable tiene un mtodo especco de
visita.
La jerarqua de elementos visitables no puede ser una estructura arbitraria, debe
estar compuesta por subclases de la clase Element e implementar el mtodo
accept().
Si se requiere cambiar la estrategia de visita. Por ejemplo, unicar el mtodo
de visita de dos tipos de elementos, es preciso cambiar la jerarqua de objetos
visitables.
El orden de visita de los elementos agregados est marcado por la implementa-
cin concreta de las funciones accept() o visitX(). O bien se introduce
el orden de recorrido en los mtodos accept() de forma que no es fcil cam-
biarlo, o bien se programa a medida en los mtodos visitX() concretos. No
es fcil denir un orden de recorrido de elementos (en orden, en preorden, en
postorden) comn para todos las subclases de Visitor.
En este caso en que no sea necesario el primer despachado virtual se puede lograr
de una manera mucho ms eciente sin ni siquiera usar funciones virtuales, gracias al
patrn CRTP. Por ejemplo, el mismo ejemplo del mdulo 1 quedara as:
C21
40 << endl;
41 for (auto n : _names) cout << n << endl;
42 }
43 };
44
45 class BombVisitor : public Visitor<BombVisitor> {
46 Bomb _bomb;
47 public:
48 BombVisitor(const Bomb& bomb) : _bomb(bomb) {}
49 void visitObject(ObjectScene* o) {
50 Point new_pos = calculateNewPosition(o->position,
51 o->weight,
52 _bomb.intensity);
53 o->position = new_pos;
54 }
55 };
[560] CAPTULO 21. C++ AVANZADO
Y con ello podemos denir todos los operadores de golpe sin ms que denir
operator <.
Listado 21.43: Ejemplo de mixin con CRTP para implementacin automtica de operadores.
1 class Person : public Comparisons<Person> {
2 public:
3 Person(string name_, unsigned age_)
4 : name(name_), age(age_) {}
5
6 friend bool operator<(const Person& p1, const Person& p2);
7 private:
8 string name;
8 http://eli.thegreenplace.net/2011/05/17/the-curiously-recurring-template-pattern-in-c/
21.2. Patrones de diseo avanzados [561]
9 unsigned age;
10 };
11
12 bool operator<(const Person& p1, const Person& p2) {
13 return p1.age < p2.age;
14 }
Consideraciones
C21
21.2.4. Reactor
El patrn Reactor es un patrn arquitectural para resolver el problema de cmo
atender peticiones concurrentes a travs de seales y manejadores de seales.
Problema
En los videojuegos ocurre algo muy similar: diferentes entidades pueden lanzar
eventos que deben ser tratados en el momento en el que se producen. Por ejemplo, la
pulsacin de un botn en el joystick de un jugador es un evento que debe ejecutar el
cdigo pertinente para que la accin tenga efecto en el juego.
Solucin
En el patrn Reactor se denen una serie de actores con las siguientes responsabi-
lidades (vase gura 21.16):
Eventos: los eventos externos que puedan ocurrir sobre los recursos (Handles).
Normalmente su ocurrencia es asncrona y siempre est relaciona a un recurso
determinado.
Recursos (Handles): se reere a los objetos sobre los que ocurren los eventos.
La pulsacin de una tecla, la expiracin de un temporizador o una conexin en-
trante en un socket son ejemplos de eventos que ocurren sobre ciertos recursos.
La representacin de los recursos en sistemas tipo GNU/Linux es el descriptor
de chero.
Manejadores de Eventos: Asociados a los recursos y a los eventos que se pro-
ducen en ellos, se encuentran los manejadores de eventos (EventHandler)
que reciben una invocacin a travs del mtodo handle() con la informacin
del evento que se ha producido.
Reactor: se trata de la clase que encapsula todo el comportamiento relativo a
la desmultiplexacin de los eventos en manejadores de eventos (dispatching).
Cuando ocurre un cierto evento, se busca los manejadores asociados y se les
invoca el mtodo handle().
Ntese que aunque los eventos ocurran concurrentemente el Reactor serializa las
llamadas a los manejadores. Por lo tanto, la ejecucin de los manejadores de eventos
ocurre de forma secuencial.
Implementacin
C21
17 reactor.register_handler(ACE_STDIN,
18 &h,
19 ACE_Event_Handler::READ_MASK);
20 for(;;)
21 reactor.handle_events();
22
23 return 0;
24 }
9 http://www.cs.wustl.edu/~schmidt/ACE.html
[564] CAPTULO 21. C++ AVANZADO
Consideraciones
21.2.5. Acceptor/Connector
Acceptor-Connector es un patrn de diseo propuesto por Douglas C. Schmidt [82]
y utilizado extensivamente en ACE, su biblioteca de comunicaciones.
La mayora de los videojuegos actuales necesitan comunicar datos entre jugadores
de distintos lugares fsicos. En toda comunicacin en red intervienen dos ordenadores
con roles bien diferenciados. Uno de los ordenadores toma el rol activo en la comu-
nicacin y solicita una conexin con el otro. El otro asume un rol pasivo esperando
solicitudes de conexin. Una vez establecida la comunicacin cualquiera de los or-
denadores puede a su vez tomar el rol activo enviando datos o el pasivo, esperando
la llegada de datos. Es decir, en toda comunicacin aparece una fase de conexin e
inicializacin del servicio y un intercambio de datos segn un patrn de intercambio
de mensajes pre-establecido.
El patrn acceptor-connector se ocupa de la primera parte de la comunicacin.
Desacopla el establecimiento de conexin y la inicializacin del servicio del procesa-
miento que se realiza una vez que el servicio est inicializado. Para ello intervienen
tres componentes: acceptors, connectors y manejadores de servicio (service handlers.
Un connector representa el rol activo, y solicita una conexin a un acceptor, que re-
presenta el rol pasivo. Cuando la conexin se establece ambos crean un manejador de
servicio que procesa los datos intercambiados en la conexin.
Problema
El procesamiento de los datos que viajan por la red es en la mayora de los ca-
sos independiente de qu protocolos, interfaces de programacin de comunicaciones,
o tecnologas especcas se utilicen para transportarlos. El establecimiento de la co-
municacin es un proceso inherentemente asimtrico (uno inicia la conexin mientras
otro espera conexiones) pero una vez establecida la comunicacin el transporte de
datos es completamente ortogonal.
Desde el punto de vista prctico resuelve los siguientes problemas:
Facilita el cambio de los roles de conexin sin afectar a los roles en el intercam-
bio de datos.
Facilita la adicin de nuevos servicios y protocolos sin afectar al resto de la
arquitectura de comunicacin.
21.2. Patrones de diseo avanzados [565]
Solucin
Un Acceptor es una factora que implementa el rol pasivo para establecer co-
nexiones. Ante una conexin crea e inicializa un Transport Handle y un
Service Handler asociados. En su inicializacin, un Acceptor se asocia
a una direccin de transporte (e.g. direccin IP y puerto TCP), y se congura pa-
ra aceptar conexiones en modo pasivo. Cuando llega una solicitud de conexin
realiza tres pasos:
C21
1. Acepta la conexin creando un Transport Handle que encapsula un
extremo conectado.
2. Crea un Service Handler que se comunicar directamente con el del
otro extremo a travs del Transport Handle asociado.
3. Activa el Service Handler para terminar la inicializacin.
Un Connector es una factora que implementa el rol activo de la conexin. En
la inicializacin de la conexin connect()() crea un Transport Handle
que encapsula un extremo conectado con un Acceptor remoto, y lo asocia a
un Service Handler preexistente.
World of Warcraft Tanto Acceptor como Connector pueden tener separadas las funciones de
inicializacin de la conexin de la funcin de completado de la conexin (cuando ya
WoW es el mayor MMORPG de la se tiene garantas de que el otro extremo ha establecido la conexin). De esta forma es
actualidad con ms de 11.5 millo-
nes de suscriptores mensuales. fcil soportar conexiones asncronas y sncronas de forma completamente transparen-
te.
[566] CAPTULO 21. C++ AVANZADO
Implementacin
C21
Tngase en cuenta que estos ejemplos son simples en exceso, con el propsito de
ilustrar el uso del patrn. En un videojuego habra que tratar los errores adecuadamente
y ACE permite tambin congurar el esquema de concurrencia deseado.
Consideraciones
21.3.1. Algoritmos
Para conseguir estructuras de datos genricas, los contenedores se implementan
como plantillas como ya se discuti en captulos anteriores de modo que el tipo
de dato concreto que han de almacenar se especica en el momento de la creacin de
la instancia.
Aunque es posible implementar algoritmos sencillos del mismo modo parametrizando
el tipo de dato STL utiliza un mecanismo mucho ms potente: los iteradores. Los
iteradores permiten desacoplar tanto el tipo de dato como el modo en que se organizan
y almacenan los datos en el contenedor.
Lgicamente, para que un algoritmo pueda hacer su trabajo tiene que asumir que
tanto los elementos del contenedor como los iteradores tienen ciertas propiedades, o
siendo ms precisos, un conjunto de mtodos con un comportamiento predecible. Por
ejemplo, para poder comparar dos colecciones de elementos, deben ser comparables
dos a dos para determinar su igualdad sin entrar en qu signica eso realmente. As
10 tambin llamado polimorsmo de subclase o de inclusin, en contraposicin con el polimorsmo
paramtrico
21.3. Programacin genrica [569]
pues, el algoritmo equal() espera que los elementos soporten en modelo Equa-
lityComparable, que implica que tienen sobrecargado el mtodo operator==(),
adems de cumplir ste ciertas condiciones como reexibidad, simetra, transitividad,
etc.
algoritmos escalares
Aunque la mayora de los algorit- Escribiendo un algoritmo genrico
mos de la STL manejan secuencias
delimitadas por dos iteradores, tam- El mejor modo de comprender en qu consiste la genericidad de un algoritmo
bin hay algunos que utilizan da-
tos escalares, tales como min(), es crear uno desde cero. Escribamos nuestra propia versin del algoritmo genrico
max(), power() o swap() que count() (uno de los ms sencillos). Este algoritmo sirve para contar el nmero
pueden resultar tiles para compo- de ocurrencias de un elemento en una secuencia. Como una primera aproximacin
ner algoritmos ms complejos. veamos cmo hacerlo para un array de enteros. Podra ser algo como:
C21
modicarlos.
20 }
21
22 void test_my_count4_letters() {
23 const int size = 5;
24 const int value = a;
25 char letters[] = {a, b, c, a, b};
26 vector<char> letters_vector(letters, letters + size);
27
28 assert(my_count4(letters, letters+size, value) == 2);
29 assert(my_count4(letters_vector.begin(), letters_vector.end(),
30 value) == 2);
31 }
C21
21.3.2. Predicados
En el algoritmo count(), el criterio para contar es la igualdad con el elemento
proporcionado. Eso limita mucho sus posibilidades porque puede haber muchos otros
motivos por los que sea necesario contar elementos de una secuencia: esferas de color
rojo, enemigos con nivel mayor al del jugador, armas sin municin, etc.
Lgica de predicados Por ese motivo, muchos algoritmos de la STL tienen una versin alternativa que
permite especicar un parmetro adicional llamado predicado. El algoritmo invocar
En lgica, un predicado es una ex- el predicado para averiguar si se cumple la condicin indicada por el programador y
presin que se puede evaluar como
cierta o falsa en funcin del valor de as determinar cmo debe proceder con cada elemento de la secuencia.
sus variables de entrada. En progra-
macin, y en particular en la STL,
En C/C++, para que una funcin pueda invocar a otra (en este caso, el algoritmo
un predicado es una funcin (en el al predicado) se le debe pasar como parmetro un puntero a funcin.
sentido amplio) que acepta un va-
11 Para utilizar los algoritmos estndar se debe incluir el chero <algorithm>.
lor del mismo tipo que los elemen-
tos de la secuencia sobre la que se
usa y devuelve un valor booleano.
[572] CAPTULO 21. C++ AVANZADO
Igual que con cualquier otro tipo de dato, cuando se pasa un puntero a funcin
como argumento, el parmetro de la funcin que lo acepta debe estar declarado con
ese mismo tipo. Concretamente el tipo del predicado not_equal_2 sera algo como:
21.3.3. Functors
Existe sin embargo una solucin elegante para conseguir predicados congura-
bles. Consiste es declarar una clase que sobrecargue el operador de invocacin
mtodo operator()() que permite utilizar las instancias de esa clase como si
fueran funciones. Las clases que permiten este comportamiento se denominan fun-
ctors13 . Veamos como implementar un predicado not_equal() como un functor:
Pero los predicados no son la nica utilidad interesante de los functors. Los pre-
dicados son una particularizacin de las funciones (u operadores). Los operadores
pueden devolver cualquier tipo. no slo booleanos.
La STL clasica los operadores en 3 categoras bsicas:
C21
Unario Una funcin que acepta un argumento.
Binario Una funcin que acepta dos argumentos.
Como ejemplo, el siguiente listado multiplica los elementos del array numbers:
21.3.4. Adaptadores
Es habitual que la operacin que nos gustara que ejecute el algoritmo sea un
mtodo (una funcin miembro) en lugar de una funcin estndar. Si tratamos de pasar
al algoritmo un puntero a mtodo no funcionar, porque el algoritmo no le pasar el
parmetro implcito this que todo mtodo necesita.
Una posible solucin sera escribir un functor que invoque el mtodo deseado,
como se muestra en el siguiente listado.
4 assert(count_if(enemies.begin(), enemies.end(),
5 mem_fun_ref(&Enemy::is_alive)) == 2);
6 }
Ntese que bind2nd() espera un functor como primer parmetro. Como lo que
tenemos es una funcin normal es necesario utilizar otro adaptador llamado ptr_fun(),
que como su nombre indica adapta un puntero a funcin a functor.
bind2nd() pasa su parmetro adicional (el 2 en este caso) como segundo par-
metro en la llamada a la funcin not_equal(), es decir, la primera llamada para
la secuencia del ejemplo ser not_equal(1, 2). El primer argumento (el 1) es
el primer elemento obtenido de la secuencia. El adaptador bind1st() los pasa en
orden inverso, es decir, pasa el valor extra como primer parmetro y el elemento de la
secuencia en segunda posicin.
Hay otros adaptadores de menos uso:
C21
not1() devuelve un predicado que es la negacin lgica del predicado unario al que
se aplique.
not2() devuelve un predicado que es la negacin lgica del predicado binario al
que se aplique.
Nomenclatura compose1() devuelve un operador resultado de componer las dos funciones una-
Los nombres de los algoritmos si- rias que se le pasan como parmetros. Es decir, dadas las funciones f (x) y g(x)
guen ciertos criterios. Como ya he- devuelve una funcin f (g(x)).
mos visto, aquellos que tienen una
versin acabada en el sujo _if
aceptan un predicado en lugar de compose2() devuelve un operador resultado de componer una funcin binaria y
utilizar un criterio implcito. Los dos funciones unarias que se le pasan como parmetro del siguiente modo. Da-
que tienen el sujo _copy gene- das las funciones f (x, y), g1 (x) y g2 (x) devuelve una funcin h(x) = f (g1 (x), g2 (x)).
ran su resultado en otra secuencia,
en lugar de modicar la secuencia
original. Y por ltimo, los que aca-
ban en _n aceptan un iterador y un
entero en lugar de dos iteradores; de
ese modo se pueden utilizar para in-
sertar en cosas distintas de conte-
nedores, por ejemplo ujos.
[576] CAPTULO 21. C++ AVANZADO
for_each()
find() / find_if()
Devuelve un iterador al primer elemento del rango que coincide con el indicado
(si se usa find()) o que cumple el predicado (si se usa find_if()). Devuelve el
iterador end si no encuentra ninguna coincidencia. Un ejemplo en el que se busca el
primer entero mayor que 6 que haya en el rango:
count() / count_if()
mismatch()
Dados dos rangos, devuelve un par de iteradores a los elementos de cada rango en
el que las secuencias dieren. Veamos el siguiente ejemplo extrado de la documen-
tacin de SGI15 :
equal()
Indica si los rangos indicados son iguales. Por defecto utiliza el operator==(),
pero opcionalmente es posible indicar un predicado binario como cuarto parmetro
para determinar en qu consiste la igualdad. Veamos un ejemplo en el que se com-
C21
paran dos listas de guras que se considerarn iguales simplemente porque coincida
su color:
search()
copy()
Copia los elementos de un rango en otro. No debera utilizarse para copiar una
secuencia completa en otra ya que el operador de asignacin que tienen todos los
contenedores resulta ms eciente. S resulta interesante para copiar fragmentos de
secuencias. Veamos un uso interesante de copy() para enviar a un ujo (en este caso
cout) el contenido de una secuencia.
21.3. Programacin genrica [579]
swap_ranges()
transform()
C21
Veamos un ejemplo en el que se concatenan las cadenas de dos vectores y se
almacenan en un tercero haciendo uso del operador plus():
replace() / replace_if()
Dado un rango, un valor antiguo y un valor nuevo, substituye todas las ocurrencias
del valor antiguo por el nuevo en el rango. La versin replace_if() substituye
los valores que cumplan el predicado unario especicado por el valor nuevo. Ambos
utilizan un nica secuencia, es decir, hacen la substitucin in situ.
Existen variantes llamadas replace_copy() y replace_copy_if() res-
pectivamente en la que se copian los elementos del rango de entrada al de salida a la
vez que se hace la substitucin. En este caso la secuencia original no cambia.
fill()
generate()
En realidad es una variante de fill() salvo que los valores los obtiene de un
operador que se le da como parmetro, en concreto un generador, es decir, una
funcin/functor sin parmetros que devuelve un valor:
rotate()
Rota los elementos del rango especicado por 3 iteradores, que indican el inicio,
el punto medio y el nal del rango. Existe una modalidad rotate_copy(), que
como siempre aplica el resultado a un iterador en lugar de modicar el original.
Algoritmos aleatorios
Hay tres algoritmos que tienen que ver con operaciones aleatorias sobre una se-
cuencia:
Los tres aceptan opcionalmente una funcin que genere nmeros aleatorios.
partition()
sort()
nth_element()
Dada una secuencia y tres iteradores, ordena la secuencia de modo que todos los
elementos en el subrango por debajo del segundo iterador (nth) son menores que
los elementos que quedan por encima. Adems, el elemento apuntado por el segundo
iterador es el mismo que si se hubiera realizado una ordenacin completa.
Operaciones de bsqueda
C21
lower_bound() devuelve un iterador a la primera posicin en la que es posible
insertar el elemento indicado manteniendo el orden en la secuencia.
upper_bound() devuelve un iterador a la ltima posicin en la que es posible
insertar el elemento indicado manteniendo el orden en la secuencia.
equal_range() combina los dos algoritmos anteriores. Devuelve un par con los
iteradores a la primera y ltima posicin en la que es posible insertar el elemento
indicado manteniendo el orden de la secuencia.
merge() combina dos secuencias, dadas por dos pares de iteradores, y crea una
tercera secuencia que incluye los elementos de ambas, manteniendo el orden.
Mnimo y mximo
partial_sum()
3 vector<int> values(size);
4 fill(values.begin(), values.end(), 1);
5
6 partial_sum(values.begin(), values.end(), values.begin());
7
8 int expected[size] = {1, 2, 3, 4, 5};
9 assert(equal(values.begin(), values.end(), expected));
10 }
adjacent_difference()
C21
Listado 21.78: Inventario de armas: Clase Weapon
1 class Weapon {
2 int ammo;
3
4 protected:
5 virtual int power(void) const = 0;
6 virtual int max_ammo(void) const = 0;
7
8 public:
9 Weapon(int ammo=0) : ammo(ammo) { }
10
11 void shoot(void) {
12 if (ammo > 0) ammo--;
13 }
14
15 bool is_empty(void) {
16 return ammo == 0;
17 }
18
19 int get_ammo(void) {
20 return ammo;
[586] CAPTULO 21. C++ AVANZADO
21 }
22
23 void add_ammo(int amount) {
24 ammo = min(ammo + amount, max_ammo());
25 }
26
27 void add_ammo(Weapon* other) {
28 add_ammo(other->ammo);
29 }
30
31 int less_powerful_than(Weapon* other) const {
32 return power() < other->power();
33 }
34
35 bool same_weapon_as(Weapon* other) {
36 return typeid(*this) == typeid(*other);
37 }
38 };
C21
recorrer el contenedor.
En la lnea 4, la clase WeaponNotFound se utiliza como excepcin en las
bsquedas de armas, como veremos a continuacin.
El mtodo add() se utiliza para aadir un nuevo arma al inventario, pero con-
templa especcamente el caso habitual en los shotters en el que coger un arma
que ya tiene el jugador implica nicamente coger su municin y desechar el arma.
Para ello, utiliza el algoritmo find_if() para recorrer el propio contenedor espe-
cicando como predicado el mtodo Weapon::same_weapon_as(). Ntese el
uso de los adaptadores mem_fun() (por tratarse de un mtodo) y de bind2nd()
para pasar a dicho mtodo la instancia del arma a buscar. Si se encuentra un arma del
mismo tipo (lneas 1216) se aade su municin al arma existente usando el iterador
devuelto por find_if() y se elimina (lnea 14). En otro caso se aade la nueva arma
al inventario (lnea 18).
[588] CAPTULO 21. C++ AVANZADO
Principio de Pareto
21.4.1. Eciencia
El principio de Pareto tambin es
aplicable a la ejecucin de un pro-
La eciencia es sin duda alguna uno de los objetivos principales de la librera STL. grama. Estadsticamente el 80 %
Esto es as hasta el punto de que se obvian muchas comprobaciones que haran su uso del tiempo de ejecucin de un
ms seguro y productivo. El principio de diseo aplicado aqu es: programa es debido nicamente al
20 % de su cdigo. Eso signica
que mejorando ese 20 % se pue-
Es factible construir decoradores que aadan comprobaciones adiciona- den conseguir importantes mejoras.
Por ese motivo, la optimizacin del
les a la versin eciente. Sin embargo no es posible construir una versin programa (si se necesita) debera
eciente a partir de una segura que realiza dichas comprobaciones. ocurrir nicamente cuando se haya
identicado dicho cdigo por me-
dio de herramientas de perlado y
Algunas de estas comprobaciones incluyen la dereferencia de iteradores nulos, un adecuado anlisis de los ujos
invalidados o fuera de los lmites del contenedor, como se muestra en el siguiente de ejecucin. Preocuparse por opti-
listado. mizar una funcin lenta que solo se
invoca en el arranque de un servidor
Para subsanar esta situacin el programador puede optar entre utilizar una imple- que se ejecuta durante das es perju-
mentacin que incorpore medidas de seguridad con la consiguiente reduccin de dicial. Supone un gasto de recursos
y tiempo que probablemente produ-
eciencia o bien especializar los contenedores en clases propias y controlar espec- cir cdigo menos legible y mante-
camente las operaciones susceptibles de ocasionar problemas. nible.
21.4. Aspectos avanzados de la STL [589]
En cualquier caso el programador debera tener muy presente que este tipo de de-
cisiones ad hoc (eliminar comprobaciones) forman parte de la fase de optimizacin y
slo deberan considerarse cuando se detecten problemas de rendimiento. En general,
tal como dice Ken Beck, La optimizacin prematura es un lastre. Es costosa (en
tiempo y recursos) y produce normalmente cdigo ms sucio, difcil de leer y mante-
ner, y por tanto, de inferior calidad.
Sin embargo, existen otro tipo de decisiones que el programador puede tomar
cuando utiliza la STL, que tienen un gran impacto en la eciencia del resultado y que
no afectan en absoluto a la legibilidad y mantenimiento del cdigo. Estas decisiones
tienen que ver con la eleccin del contenedor o algoritmo adecuado para cada proble-
ma concreto. Esto requiere conocer con cierto detalle el funcionamiento y diseo de
los mismos.
C21
Tamao medio del contenedor.
En general, la eciencia en cuanto a tiempo de acceso solo es signicativa pa-
ra grandes cantidades de elementos. Para menos de 100 elementos (seguramente
muchos ms considerando las computadoras o consolas actuales) es muy pro-
bable que la diferencia entre un contenedor con tiempo de acceso lineal y uno
logartmico sea imperceptible. Si lo previsible es que el nmero de elementos
sea relativamente pequeo o no se conoce bien a priori, la opcin ms adecuada
es vector.
Insercin de elementos en los dos extremos de la secuencia.
Si necesita aadir al comienzo con cierta frecuencia (>10 %) debera elegir un
contenedor que implemente esta operacin de forma eciente como deque.
Insercin y borrado en posiciones intermedias.
El contenedor ms adecuado en este caso es list. Al estar implementado como
una lista doblemente enlazada, la operacin de insercin o borrado implica poco
ms que actualizar dos punteros.
[590] CAPTULO 21. C++ AVANZADO
Contenedores ordenados.
Algunos contenedores, como set y multiset, aceptan un operador de orde-
nacin en el momento de su instanciacin. Despus de cualquier operacin de
insercin o borrado el contenedor quedar ordenado. Esto es rdenes de mag-
nitud ms rpido que utilizar un algoritmo de ordenacin cuando se necesite
ordenarlo.
Si se obtiene un iterador a un nodo, sigue siendo vlido durante toda la vida del
elemento. Sin embargo, en los basados en bloque los iteradores pueden quedar
invalidados si se realoja el contenedor.
Ocupan ms memoria por cada elemento almacenado, debido a que se requiere
informacin adicional para mantener la estructura: rbol o lista enlazada.
Aunque los algoritmos de STL estn diseados para ser ecientes (el estndar in-
cluso determina la complejidad ciclomtica mxima permitida) ciertas operaciones
sobre grandes colecciones de elementos pueden implicar tiempos de cmputo muy
considerables. Para reducir el nmero de operaciones a ejecutar es importante consi-
derar los condicionantes especcos de cada problema.
Uno de los detalles ms simples a tener en cuenta es la forma en la que se espe-
cica la entrada al algoritmo. En la mayora de ellos la secuencia queda determinada
por el iterador de inicio y el de n. Lo interesante de esta interfaz es que darle al al-
goritmo parte del contenedor es tan sencillo como drselo completo. Se pueden dar
innumerables situaciones en las que es perfectamente vlido aplicar cualquiera de los
algoritmos genricos que hemos visto a una pequea parte del contenedor. Copiar,
buscar, reemplazar o borrar en los n primeros o ltimos elementos puede servir para
lograr el objetivo ahorrando muchas operaciones innecesarias.
Otra forma de ahorrar cmputo es utilizar algoritmos que hacen solo parte del
trabajo (pero suciente en muchos casos), en particular los de ordenacin y bsqueda
como partial_sort(), nth_element(), lower_bound(), etc.
Por ejemplo, una mejora bastante evidente que se puede hacer a nuestro inventario
de armas (ver listado 21.80) es cambiar el algoritmo sort() por max_element()
en el mtodo more_powerful_weapon().
C21
del programa.
9 void test_copy_semantics(void) {
10 vector<Counter> counters;
11 Counter c1;
12 counters.push_back(c1);
13 Counter c2 = counters[0];
14
15 counters[0].inc();
16
17 assert(c1.get() == 0);
18 assert(counters[0].get() == 1);
19 assert(c2.get() == 0);
20 }
created: foo
copy of foo
destroyed: foo
21.4. Aspectos avanzados de la STL [593]
created: bar
copy of bar
copy of copy of foo
destroyed: copy of foo
destroyed: bar
created: buzz
copy of buzz
copy of copy of copy of foo
copy of copy of bar
destroyed: copy of copy of foo
destroyed: copy of bar
destroyed: buzz
-- init ready
copy of copy of copy of bar
destroyed: copy of copy of copy of bar
copy of copy of buzz
destroyed: copy of copy of buzz
-- sort complete
copy of copy of copy of copy of bar
-- end
destroyed: copy of copy of copy of copy of bar
destroyed: copy of copy of copy of bar
destroyed: copy of copy of buzz
destroyed: copy of copy of copy of foo
C21
Para profundizar en este asunto vea Implementing Reference Semantics [51].
Creando un contenedor
Dependiendo del modo en que se puede utilizar, los contenedores se clasican por
modelos. A menudo, soportar un modelo implica la existencia de mtodos concretos.
Los siguientes son los modelos ms importantes:
[594] CAPTULO 21. C++ AVANZADO
Forward container
Son aquellos que se organizan con un orden bien denido, que no puede cambiar
en usos sucesivos. La caracterstica ms interesante es que se puede crear ms
de un iterador vlido al mismo tiempo.
Reversible container
Puede ser iterado de principio a n y viceversa.
Random-access container
Es posible acceder a cualquier elemento del contenedor empleando el mismo
tiempo independientemente de su posicin.
Front insertion sequence
Permite aadir elementos al comienzo.
Back insertion sequence
Permite aadir elementos al nal.
Associative container
Aquellos que permiten acceder a los elementos en funcin de valores clave en
lugar de posiciones.
31 T* as_array() { return v; }
32 };
El siguiente listado muestra una prueba de su uso. Como los iteradores de carray
son realmente punteros ordinarios16 , este contenedor soporta los modelos forward y
reverse container adems de random access ya que tambin dispone del operador de
indexacin.
Functor adaptables
C21
Del mismo modo que los predicados y operadores, STL considera los tipos de
functors adaptables correspondientes. As pues:
21.4.4. Allocators
Los contenedores ocultan el manejo de la memoria requerida para almacenar los
elementos que contienen. Aunque en la gran mayora de las situaciones el comporta-
miento por defecto es el ms adecuado, pueden darse situaciones en las que el progra-
mador necesita ms control sobre el modo en que se pide y libera la memoria. Algunos
de esos motivos pueden ser:
Para lograrlo la STL proporciona una nueva abstraccin: el allocator. Todos los
contenedores estndar utilizan por defecto un tipo de allocator concreto y permiten
especicar uno alternativo en el momento de su creacin, como un parmetro de la
plantilla.
Como sabemos, todos los contenedores de STL son plantillas que se instancian
con el tipo de dato que van a contener. Sin embargo, tienen un segundo parme-
tro: el allocator que debe aplicar. Veamos las primeras lneas de al denicin
de vector.
Creando un allocator
El allocator es una clase que encapsula las operaciones de peticin (a travs del
mtodo allocate()) y liberacin (a travs del mtodo deallocate()) de una
C21
cantidad de elementos de un tipo concreto. La signatura de estos mtodos es:
3 public:
4 typedef T value_type;
5 typedef T* pointer;
6 typedef const T* const_pointer;
7 typedef T& reference;
8 typedef const T& const_reference;
9 typedef size_t size_type;
10 typedef ptrdiff_t difference_type;
11
12
13 template <typename U>
14 struct rebind {
15 typedef custom_alloc<U> other;
16 };
17
18 custom_alloc() {}
19
20 custom_alloc(const custom_alloc&) {}
21
22 template <typename U>
23 custom_alloc(const custom_alloc<U>&) {}
24
25 pointer address(reference value) const {
26 return &value;
27 }
28
29 const_pointer address(const_reference value) const {
30 return &value;
31 }
32
33 size_type max_size() const {
34 return numeric_limits<size_t>::max() / sizeof(T);
35 }
36
37 pointer allocate(size_type n, const void* hint=0) {
38 return (pointer) (::operator new(n * sizeof(T)));
39 }
40
41 void deallocate(pointer p, size_type num) {
42 delete p;
43 }
44
45 void construct(pointer p, const T& value) {
46 new (p) T(value);
47 }
48
49 void destroy(pointer p) {
50 p->~T();
51 }
Las lneas 4 a 15 denen una serie de tipos anidados que todo allocator debe tener:
value_type
El tipo del dato del objeto que almacena.
reference y const_reference
El tipo de las referencia a los objetos.
pointer y const_pointer
El tipo de los punteros a los objetos.
size_type
El tipo que representa los tamaos (en bytes) de los objetos.
difference_type
El tipo que representa la diferencia entre dos objetos.
21.4. Aspectos avanzados de la STL [599]
La funcin bind()
C21
El argumento especial _1 es un placeholder (ver lnea 18) que representa al primer
argumento posicional que recibir el functor generado. Es decir, si asumimos que la
invocacin a bind() crea un hipottico bool greater_than_6(int n), la
variable _1 representa al parmetro n.
Con esta nomenclatura tan simple es posible adaptar una llamada a funcin o m-
todo a otra aunque los parmetros estn dispuestos en un orden diferente o pudiendo
aadir u omitir algunos de ellos.
Tambin substituye a los adaptadores ptr_fun(), mem_fun() y, nalmente,
mem_fun_ref() dado que acepta cualquier entidad invocable y funciona tanto con
punteros como con referencias. Veamos la versin con bind() del listado 21.60:
5
6 assert(count_if(enemies.begin(), enemies.end(),
7 bind(&Enemy::is_alive, _1)) == 2);
8 }
Pero bind() hace mucho ms. Permite construir funciones parcialmente especi- bind()
cadas. Una funcin parcialmente especicada es una utilidad tpica de los lenguajes
Es el adaptador de funciones deni-
funcionales. Como su nombre indica, es una forma de jar el valor de uno o ms tivo. Substituye a todos los adapta-
parmetros de una funcin (o algo que se comporta como tal). El resultado es una dores que ofreca la STL antes del
nueva funcin (un functor realmente) con menos parmetros. Veamos un ejemplo (lis- estndar C++11.
tado 21.97). Tenemos una funcin que imprime un texto en un color determinado
indicado por parmetro. Utilizando bind() vamos a crear otra funcin que, a par-
tiendo de la primera, permite escribir texto en verde para imprimir un mensaje que
informa de un comportamiento correcto (como en un logger).
30 bind(&Car::forward_left, &car));
31 listener.add_hook({OIS::KC_S, OIS::KC_D},
32 bind(&Car::backward_right, &car));
33 listener.add_hook({OIS::KC_S, OIS::KC_A},
34 bind(&Car::backward_left, &car));
35 listener.add_hook({OIS::KC_W},
36 bind(&Car::forward, &car));
37 listener.add_hook({OIS::KC_S},
38 bind(&Car::backward, &car));
39 listener.add_hook({OIS::KC_ESCAPE},
40 bind(&WindowEventListener::shutdown, &listener)
);
41
42 // ...
Fjese en el atributo triggers_ (lnea 9), el cual consiste en un mapa que rela-
ciona un vector de KeyCode (una combinacin de teclas) con un manejador (tipo
function<void()>). Esto permite cambiar las asignaciones de teclas segn las
preferencias del usuario, leyndolas por ejemplo de un chero de conguracin, y
sera muy sencillo tambin cambiarlas con la aplicacin en marcha si fuese necesario.
Contenedores
Con el nuevo estndar, todos los tipos se pueden inicializar de forma equivalente
a como se haca con los arrays, es decir, escribiendo los datos entre llaves:
C21
Puedes ver ms detalles y ejemplos sobre inicializacin uniforme en la seccin 21.5.2.
Se ha aadido el contenedor array. Es de tamao jo y tan eciente como un
array C nativo, y por supuesto, con una interfaz estilo STL similar a vector (ver
listado 21.101).
Iteradores
Algoritmos
all_of() Devuelve cierto slo si el predicado es cierto para todos los elementos
de la secuencia.
any_of() Devuelve cierto si el predicado es cierto para al menos uno de los ele-
mentos de la secuencia.
none_of() Devuelve cierto slo si el predicado es falso para todos los elementos
de la secuencia.
iota() Asigna valores crecientes a los elementos de una secuencia.
minmax() Devuelve los valores mnima y mximo de una secuencia.
is_sorted() Devuelve cierto si la secuencia est ordenada.
21.5. C++11: Novedades del nuevo estndar [603]
C21
Expresiones constantes
1 int a = 1 + 2;
2 cout << 3.2 - 4.5 << endl;
3
4 int miArray[4 * 2];
Inicializador de listas
1 struct miStruct {
2 int a;
3 float b;
4 };
se podra inicializar un vector como sigue (tambin se incluyen ejemplos con en-
teros).
Esto es posible gracias al uso de usa nueva sintaxis y del contenedor std::ini-
tializer_list.
Si se utiliza como parmetro en el constructor de una clase
Hay que tener en cuenta que este tipo de contenedores se utilizan en tiempo de
compilacin, que slo pueden ser construidos estticamente por el compilador y que
no podrn ser modicados en tiempo de ejecucin. Aun as, como son un tipo, pueden
ser utilizados en cualquier tipo de funciones.
Tambin se pueden utilizar las llaves junto con el operador =, para inicializar o
para asignar nuevos valores.
C21
4
5 miVS2 = {{9, 1.2}};
Inicializacin uniforme
operador de asignacin. Tampoco tendrn miembros privados o protegidos que no sean estticos, ni una
clase base o funciones virtuales.
[606] CAPTULO 21. C++ AVANZADO
1 class Vector3D {
2 public:
3 Vector3D(float x, float y, float z):
4 _x(x), _y(y), _z(z) {}
5
6 private:
7 float _x;
8 float _y;
9 float _z;
10
11 friend Vector3D normalize(const Vector3D& v);
12 };
Esta notacin no sustituye a la anterior. Cabe destacar que cuando se utiliza esta
sintaxis para inicializar un objeto, el constructor que acepta una lista de inicializacin
como las presentadas anteriormente tendr prioridad sobre otros. Debido a esto, algu-
nas veces ser necesario invocar directamente al constructor adecuado con la notacin
antigua.
Esta forma de devolver objetos es compatible con RVO (Return Value Optimi-
zation), que se ver en optimizaciones. Con lo cual una llamada como la siguiente
generar cdigo ptimo y seguramente sin ninguna copia.
1 Vector3D p2 = normalize(p);
Inferencia de tipos
Hasta ahora cada vez que se declaraba una variable en C++ haba que especicar
de qu tipo era de manera explcita. En C++11 existe la inferencia de tipos. Usando la
palabra reservada auto en una inicializacin en vez del tipo, el compilador deducir
el mismo de manera automtica.
En el ejemplo siguiente se ve cmo funciona esta caracterstica, tanto para tipos
bsicos como para la clase denida en el apartado anterior.
Existe otra palabra reservada que se usa de forma similar a sizeof(), pero que
devuelve el tipo de una variable. Esta palabra es decltype y se puede usar para
extraer el tipo de una variable y usarlo para la declaracin de otra.
En C++11 se introduce una caracterstica muy til para recorrer listas de ele-
mentos, ya sean arrays, lista de inicializacin o contenedores con las operaciones
begin() y end().
C21
Funciones Lambda
Las funciones lambda son simplemente funciones annimas. La sintaxis para de-
clarar este tipo de funciones es especial y es posible no declarar el tipo devuelto de
manera explcita sino que est denido de forma implcita mediante la construccin
decltype(<expresin_devuelta>). Las dos formas posible de declarar y
denir estas funciones son las siguientes.
[captura](parmetros)->tipo_de_retorno{cuerpo}
[captura](parmetros){cuerpo}
La primera hace explcito el tipo que se devuelve. De esto modo, las funciones que
se muestran a continuacin son equivalentes.
Las variables que se utilizan dentro de estas funciones pueden ser capturadas para
utilizarse en el exterior. Se pueden capturar por valor o por referencia, dependiendo de
la sintaxis dentro de []. Si se utiliza por ejemplo [p1, &p2], p1 ser capturada por
valor y p2 por referencia. Si se usa [=,&p1], todas las variables sern capturadas por
valor (al usar =) excepto p1 que ser capturada por referencia. Si se utiliza [&,p2],
todas se capturarn por referencia (usando &), excepto p2.
En el siguiente ejemplo, se utiliza una funcin lambda para sumar la puntacio-
nes de todos los jugadores, que han sido previamente almacenadas en una lista. Se
muestran tres formas de hacerlo.
1 class K {
2 public:
3 int operator*(const K& k) const {return 2;}
4 };
5
6 template <typename T>
7 T pow2Bad(const T& t){return t*t;}
8
9 template <typename T>
10 auto pow2(const T& t)->decltype(t*t){return t*t;}
1 K kObj;
2 cout << pow2Bad(kObj) << endl; // <- no compila
3 cout << pow2(kObj) << endl;
1 class playerInfo {
2 public:
3 playerInfo(const string& name) :
4 _name(name) {}
5
6 playerInfo() : playerInfo("default") {}
7
8 private:
9 string _name;
10 };
C21
13 return _anotherX;
14 }
15 //Fallo al compilar
16 virtual int getX(int a) override {
17 return _anotherX;
18 };
19 bool isValid() final { return false; }
20 private:
21 int _anotherX;
22 };
23
24 class MasDerivada : public Derivada {
25 public:
En el ejemplo anterior tambin se muestra (lneas 4-6 y 22 ) el uso de final.
Cuando se utiliza en la declaracin de un mtodo, indica que ninguna clase que herede
de sta podr sobrescribirlo.
[610] CAPTULO 21. C++ AVANZADO
Puntero null
1 int* c = nullptr;
Cabe destacar que es un tipo compatible con los booleanos, pero que no es com-
patible con los enteros.
Alias de plantillas
Ya que typedef no se puede utilizar con plantillas, C++11 incluye una forma de
crear alias para las mismas. Se basa en utilizar using.
Tambin se puede utilizar la nueva sintaxis para realizar las deniciones de tipo
que se hacan con typedef.
21.5. C++11: Novedades del nuevo estndar [611]
1 class Vector3D {
2 public:
3 Vector3D(float x, float y, float z) {}
4 };
5
6 union miUnion {
7 int a;
8 float b;
9 Vector3D v;
10 };
C21
se usa R"(literal)". Tambin es posible usar cadenas raw con la modicacin
Unicode.
A partir de ahora se pueden crear nuevos literales usando sujos. Los sujos po-
drn ir detrs de nmeros (los que puedan ser representados por unsigned long
long o long double) o detrs de literales de cadena. Estos sujos corresponden a
funciones con el prototipo retval operator _sufijo ( unsigned long
long ).
La funcin anterior dene el sujo _d, que podr ser usado para crear un double
usando un nmero natural como literal.
1 auto d = 30_d;
1 class Vector3D {
2 public:
3 Vector3D() :
4 x_(0), y_(0), z_(0) {} ;
5
6 Vector3D(float x, float y, float z) :
7 x_(x), y_(y), z_(z) {} ;
8
9 Vector3D operator+(const Vector3D& v) {
10 return Vector3D(x_ + v.x_,
11 y_ + v.y_,
12 z_ + v.z_ );
13 }
14
15 private:
16 float x_;
17 float y_;
18 float z_;
19
20 friend Vector3D operator"" _vx(long double x);
21 friend Vector3D operator"" _vy(long double y);
22 friend Vector3D operator"" _vz(long double z);
23 };
Se podran denir los siguientes literales de usuario, por ejemplo para construir
vectores ortogonales.
Para utilizar los sujos con los literales de cadenas, se muestra el siguiente ejem-
plo, que representa un jugador, con un nombre.
1 class Player {
2 public:
3 Player(string name):
4 name_(name) {}
5
6 private:
7 string name_;
8 };
9
10 Player operator"" _player(const char* name, size_t nChars) {
11 return Player(name);
12 };
1 auto p = "bRue"_player;
Aserciones estticas
Algo muy til que ya inclua Boost es una asercin esttica. Este tipo de aserciones
se comprobarn en tiempo de compilacin, y ser el mismo compilador el que avise
de la situacin no deseada.
En C++11 se puede usar static_assert (cont-expr, error-message)
para utilizar estas aserciones en cualquier punto del cdigo.
C21
9 int main(int argc, char *argv[])
10 {
11 equal(8.0, 8.0000001, 0.00001); // OK (double 8 bytes)
12 equal(8.0f, 8.0000001f, 0.00001f); // Error!!
13
14 return 0;
15 }
1 class NoCopiable {
2 public:
3 NoCopiable(){}
4 NoCopiable(const NoCopiable&) = delete;
5 NoCopiable& operator=(const NoCopiable&) = delete;
6 };
1 class Ex {
2 public:
3 explicit Ex(double a) :
4 a_(a) {}
5
6 Ex() = default;
7
8 void setA(double a) {
9 a_ = a;
10 }
En el mismo ejemplo se usa default con uno de los constructores, lo que pide
de forma explcita al compilador que l cree uno por defecto.
Constructores de movimiento
C21
35 if (this == &m)
36 return *this;
37
38 cout << "Asignacion de movimiento" << endl;
39 size_ = m.size_;
40 buffer_ = m.buffer_;
41 m.buffer_ = nullptr;
42
43 return *this;
44 }
45
46
47 ~Movible()
48 {
49 cout << "Destructor" << endl;
50 if (buffer_ == nullptr)
51 cout << "--> con nullptr (moviendo)" << endl;
52
53 delete [] buffer_;
54 }
55
56
57
[616] CAPTULO 21. C++ AVANZADO
58
59 private:
60 char* buffer_;
61 unsigned size_;
62 };
63
64
65 Movible getM()
66 {
67 cout << "getM()" << endl;
68 Movible nuevo_objecto(20000);
69 return nuevo_objecto;
70 }
71
72 int main(int argc, char *argv[])
73 {
74 vector<Movible> v;
75
76 Movible k(234303);
77 k = getM();
78
79 v.push_back(Movible(4000));
80
81 return 0;
82 }
$ ./move
getM()
Asignacion de movimiento
Destructor
--> con nullptr (moviendo)
Constructor de movimiento
Destructor
--> con nullptr (moviendo)
Destructor
Destructor
Cuando se usa la asignacin, el objeto temporal que se genera para devolver por
copia un objecto, es capturado por la asignacin con movimiento y no se realiza nin-
guna copia extra.
La biblioteca estndar est preparada para el uso de constructores de movimiento,
y como se ve en el ejemplo anterior, lo que en c++03 supondra una copia por cada
push_back() en c++11 supone una llamada transparente al constructor de movi-
miento, evitando as todas las copias que se realizaran de otra forma.
Ntese como en los movimientos se ejecuta el destructor del objeto,
1 #include <iostream>
2 #include <functional>
3 #include <random>
4 #include <sys/time.h>
5
6 using namespace std;
7
8 int main(int argc, char *argv[])
9 {
10 struct timeval now;
11 gettimeofday(&now, 0);
12
13 minstd_rand motor;
14 motor.seed(now.tv_usec);
15
16 uniform_int_distribution<int> dist(1,6);
17 uniform_int_distribution<int> dist_2(1,50);
18
19 int loto = dist(motor); // Uso directo
20
21 auto generador = bind(dist, motor); // Bind
22 int valor_dado = generador(); // Uso "bindeado"
23
24 cout << loto << " : " << valor_dado << endl;
25
26 return 0;
27 }
C21
Tablas Hash
1 #include <iostream>
2 #include <unordered_map>
3
4 int main(int argc, char *argv[])
5 {
6 std::unordered_map<int, float> miHash;
7 miHash[13] = 1.1;
8 std::cout << miHash[13] << std::endl;
9 return 0;
[618] CAPTULO 21. C++ AVANZADO
10 }
Expresiones regulares
1
2 std::regex eRE1("\\b((c|C)ur)([^ ]*)");
1 std::smatch match;
Es posible buscar todas las coincidencias dentro de una cadena como se muestra a
continuacin.
1 if (regex_match(entrada, eRE2))
2 cout << "[" << entrada << "] cumple la regex" << endl;
Tuplas
C++11 da soporte a la creacin de tuplas que contengan diferentes tipos. Para ello
se utiliza la plantilla std::tuple. Como se ve en el ejemplo siguiente, para obtener
los valores se utiliza la funcin templatizada std::get().
1 #include <iostream>
2 #include <tuple>
3
4 using namespace std;
5
6 typedef tuple <string, int, float> tuplaPuntos;
7
8 int main(int argc, char *argv[])
9 {
10 tuplaPuntos p1("Bilbo", 20, 35.0);
11
12 cout << "El jugador " << get<0>(p1)
13 << " ha conseguido " << get<2>(p1)
21.6. Plugins [619]
Otras caractersticas
21.6. Plugins
En trminos generales se denomina plug-in (o add-on) a cualquier componente
que aade (o modica) la funcionalidad de una aplicacin principal integrndose con
ella mediante un API proporcionando ex profeso. Los plugins son un mecanismo que
se emplea habitualmente cuando se desea que programadores ajenos al desarrollo del
proyecto matriz puedan integrarse con la aplicacin. Ofrece algunas ventajas intere-
santes respecto a una aplicacin monoltica:
C21
gins. Tambin ocurre cuando partes distintas tienen licencias diferentes.
Empotrar un interprete para un lenguaje dinmico, tal como Lua, Python o Sche-
me. Esta opcin se estudia ms adelante en el presente documento. Si la apli-
cacin matriz est escrita en un lenguaje dinmico no se requiere normalmente
ningn mecanismo especial ms all de localizar y cargar los plugins desde sus
cheros.
Proporcionar un protocolo basado en mensajes para que la aplicacin principal
se pueda comunicar con los plugins. Este es el caso de OSGi y queda fuera
el mbito de este curso. Este tipo de arquitectura es muy verstil (los plugins
pueden incluso estar escritos en distintos lenguajes) aunque resulta bastante in-
eciente.
[620] CAPTULO 21. C++ AVANZADO
Para ejecutarlo hay que indicarle al sistema operativo que tambin tiene que bus-
car bibliotecas en el directorio actual. Para eso basta denir la variable de entorno
LD_LIBRARY_PATH.
$ LD_LIBRARY_PATH=. ./main
mylib_func test 12345
Sin tocar para nada todo lo hecho hasta ahora, vamos a generar otra biblioteca
dinmica con la misma funcin denida de otra forma.
21.6. Plugins [621]
$ ldd main
linux-vdso.so.1 => (0x00007fff701ff000)
libmylib.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f13043dd000)
/lib64/ld-linux-x86-64.so.2 (0x00007f130477c000)
Todos los ejecutables dinmicos estn montados con la biblioteca ld.so o ld--
linux.so. Se trata del montador dinmico. Obsrvese cmo se incluye en el eje-
cutable la ruta completa (ltima lnea). Esta biblioteca se encarga de precargar las
bibliotecas especicadas en LD_PRELOAD, buscar el resto en las rutas del sistema o
de LD_LIBRARY_PATH, y de cargarlas. El proceso de carga en el ejecutable incluye
resolver todos los smbolos que no estuvieran ya denidos.
Cuando desde la biblioteca dinmica es preciso invocar funciones (o simplemente
utilizar smbolos) denidas en el ejecutable, ste debe ser compilado con la opcin
C21
-rdynamic. Por ejemplo:
$ LD_LIBRARY_PATH=. ./main2
mylib_func 12345 test
main_func 54321
Si todas estas actividades son realizadas por una biblioteca (ld.so) no debera
extraar que esta funcionalidad est tambin disponible mediante una API, para la
carga explcita de bibliotecas desde nuestro programa.
dlopen() abre una biblioteca dinmica (un chero .so) y devuelve un mane-
jador.
dlsym() carga y devuelve la direccin de smbolo cuyo nombre se especique
como symbol.
dlclose() le indica al sistema que ya no se va a utilizar la biblioteca y puede
ser descargada de memoria.
dlerror() devuelve una cadena de texto que describe el ltimo error produ-
cido por cualquiera de las otras funciones de la biblioteca.
21.6. Plugins [623]
C21
2
3
4 void b(int i);
5 int b2(int i);
6
7 #endif
2
3 int b2(int i) {
4 return i * i;
5 }
Carga explcita
En primer lugar veamos cmo cargar y ejecutar un smbolo (la funcin b()) de
forma explcita, es decir, el programador utiliza libdl para buscar la biblioteca y
cargar el smbolo concreto que desea:
Carga implcita
C21
12 p->key = key;
13 p->function = function;
14 p->next = plugins;
15 plugins = p;
16 printf("** Plugin %s successfully registered.\n", key);
17 }
18
19 void
20 plugin_unregister(char* key) {
21 plugin_t *prev = NULL, *p = plugins;
22
23 while (p) {
24 if (0 == strcmp(p->key, key))
25 break;
26
27 prev = p;
28 p = p->next;
29 }
30
31 if (!p)
32 return;
33
34 if (prev)
[626] CAPTULO 21. C++ AVANZADO
35 prev->next = p->next;
36 else
37 plugins = p->next;
38
39 free(p);
40 }
41
42 static plugin_t*
43 plugin_find(char* key) {
44 plugin_t* p = plugins;
45 while (p) {
46 if (0 == strcmp(p->key, key))
47 break;
48
49 p = p->next;
50 }
51 return p;
52 }
53
54 void
55 call(char* key, int i) {
56 plugin_t* p;
57
58 p = plugin_find(key);
59 if (!p) {
60 char libname[PATH_MAX];
61 sprintf(libname, "./dir %s/lib %s.so", key, key);
62 printf("Trying load %s.\n", libname);
63 dlopen(libname, RTLD_LAZY);
64 p = plugin_find(key);
65 }
66
67 if (p)
68 p->function(i);
69 else
70 fprintf(stderr, "Error: Plugin %s not available.\n", key);
71 }
Los plugins (lneas 15) se almacenan en una lista enlazada (lnea 7). Las funcio-
nes plugin_register() y plugin_unregister() se utilizan para aadir y
eliminar plugins a la lista. La funcin call() (lneas 5471) ejecuta la funcin espe-
cica (contenida en el plugin) que se le pasa el parmetro, es decir, invoca una funcin
a partir de su nombre19 . Esa invocacin se puede ver en la lnea 10 del siguiente lista-
do:
Para que los plugins (en este caso libb) se registren automticamente al ser carga-
dos se requiere un pequeo truco: el atributo constructor (lnea 9) que provoca
que la funcin que lo tiene se ejecute en el momento de cargar el objeto:
19 Este proceso se denomina enlace tardo (late binding) o name binding.
21.6. Plugins [627]
C21
GModule ofrece un API muy similar a libdl con funciones prcticamente equi-
valentes:
Carga explcita
El siguiente listado muestra cmo hacer la carga y uso de la funcin b(), equiva-
lente al listado 21.118:
Carga implcita
18 }
19
20 void
21 plugin_unregister(const char* key) {
22 if (plugins != NULL)
23 g_hash_table_remove(plugins, key);
24 }
25
26 void
27 call(const char* key, int i) {
28 void (*p)(int) = NULL;
29
30 if (plugins != NULL)
31 p = g_hash_table_lookup(plugins, key);
32
33 if (!p) {
34 char libname[PATH_MAX];
35
36 sprintf(libname, "./dir %s/lib %s.so", key, key);
37 g_message("Trying load %s.", libname);
38 if (g_module_open(libname, G_MODULE_BIND_LAZY) == NULL)
39 g_error("Plugin %s not available", libname);
40
41 if (plugins != NULL)
42 p = g_hash_table_lookup(plugins, key);
43 }
44
45 if (!p)
46 g_error("Plugin %s not available", key);
47
48 p(i);
49 }
C21
Listado 21.125: Carga de smbolos desde Python con ctypes
1 LIBB_PATH = "./dirb/libb.so.1.0.0"
2
3 import ctypes
4
5 plugin = ctypes.cdll.LoadLibrary(LIBB_PATH)
6 plugin.b(3)
Para aadir o eliminar nuevas subclases de weapon tenemos que llamar a reg()
o unreg() respectivamente. Esto es adecuado para la tcnica RAII en la que la crea-
cin y destruccin de un objeto se utiliza para el uso y liberacin de un recurso:
7 : factory_(factory), key_(key) {
8 factory_.reg(key_, new weapon_type());
9 }
10 ~weapon_reg() {
11 factory_.unreg(key_);
12 }
13 };
Tanto la factora como los objetos de registro podran ser tambin modelados con
el patrn singleton, pero para simplicar el ejemplo nos limitaremos a instanciarlos
sin ms:
C21
16 };
C21
-Wl,--enable-runtime-pseudo-reloc \
-o fmethod-bow-win.dll fmethod-bow-win.cc \
fmethod-fac-win.dll
$ wine fmethod-win.exe rifle bow 2>/dev/null
Tcnicas especcas
22
Francisco Moya Fernndez
Flix J. Villanueva Molina
Sergio Prez Camacho
David Villa Alises
635
[636] CAPTULO 22. TCNICAS ESPECFICAS
22.1.1. Streams
Un stream es como una tubera por donde uyen datos. Existen streams de en-
trada o de salida, y de ambas, de modo que un programa puede leer (entrada) de una
abstrayndose por completo de qu es lo que est llenando la misma. Esto hace que
los streams sean una forma de desacoplar las entradas de la forma de acceder a las
mismas, al igual que las salidas. No importa si quien rellena un stream es la entrada
del teclado o un archivo, la forma de utilizarla es la misma para ambos casos. De este
modo, controlar la entrada supondra conectar un stream a un chero (o al teclado)
y su salida al programa. Justo al revs (donde el teclado sera ahora la pantalla) se
controlara la salida.
Normalmente los streams tienen un buffer asociado puesto que escribir o leer en
bloques suele ser mucho ms eciente en los dispositivos de entrada y salida. El stream
se encargar (usando un streambuf1 ) de proporcionar o recoger el nmero de bytes
que se requiera leer o escribir en el mismo
En la gura 22.1 se muestra la jerarqua de streams en la biblioteca estndar de
C++. La clase ios_base representa la propiedades generales de un stream, como por
ejemplo si este es de entrada o de salida o si es de texto o binaria. La clase ios, que
hereda de la anterior, contiene un streambuf. Las clases ostream y istream,
derivan de ios y proporcionan mtodos de salida y de entrada respectivamente.
istream
La clase istream implementa mtodos que se utilizan para leer del buffer in-
terno de manera transparente. Existen dos formas de recoger la entrada: formateada
y sin formatear. La primera usa el operador >> y la segunda utiliza los siguientes
miembros de la clase:
1 streambuf es una clase que provee la memoria para dicho buffer incluyendo adems funciones para
ostream
ifstream y ofstream
C22
1 #include <iostream>
2 #include <fstream>
3 #include <string>
4
5 using namespace std;
6
7 int main(int argc, char *argv[])
8 {
9 ifstream infile("prueba.txt", ios_base::binary);
10
11 if (!infile.is_open()) {
12 cout << "Error abriendo fichero" << endl;
13 return -1;
14 }
15
16 string linea;
17 getline(infile, linea);
18 cout << linea << endl;
19
20 char buffer[300];
[638] CAPTULO 22. TCNICAS ESPECFICAS
21 infile.getline(buffer, 300);
22 cout << buffer << endl;
23
24 infile.read(buffer,3);
25 buffer[3] = \0;
26 cout << "[" << buffer << "]" << endl;
27
28 streampos p = infile.tellg();
29 infile.seekg(2, ios_base::cur);
30 infile.seekg(-4, ios_base::end);
31 infile.seekg(p);
32
33 int i;
34 while ((i = infile.get()) != -1)
35 cout << "\" << (char) i << "\=int(" << i << ")" << endl;
36
37 return 0;
38 }
En la lnea 9 de crea
el stream del chero, y se intenta abrir para lectura como
un chero binario. En 11-14 secomprueba que el archivo se abri y se termina el
programa si no es as. En 16-18 se usa una funcin global de string para rellenar
una de estas con una lnea desde elchero. Se hace lo mismo con un buffer limitado
a 300 caracteres en la lneas 20-22
. Despus
se leen 3 caracteres sueltos (sin tener en
cuenta el nal de linea) (24-26 ). En 28-31 se juega con la posicin del puntero de
lectura, y en el resto, se lee carcter a carcter hasta el nal del archivo.
Los modos de apertura son los siguientes:
in Permitir sacar datos del stream
out Permitir introducir datos en el stream
ate Al abrir el stream, situar el puntero al nal del archivo.
app Poner el puntero al nal en cada operacin de salida
trunc Trunca el archivo al abrirlo
binary El stream ser binario y no de texto
del vector,
El programa anterior imprime el valor original y espera a la entrada
de un vector con el mismo formato. Al pulsar RETURN el vector original se relle-
nar con los nuevos datos tomados de la entrada estndar. De hecho, el programa
funciona tambin con una tubera del tipo echo "(1.0, 2.912, 3.123)"|
./ejecutable.
stringstream
C22
En las lneas 6-12 se dene una funcin templatizada que se usa en 33 para
transformar un nmero en una cadena, usando streamstream e invocando luego
su mtodo str(), que devuelve la cadena asociada.
En 14-23 se dene otra que se puede
utilizar para extraer un nmero de una cade-
na. Se ve un ejemplo de uso en la lnea 34 .
Sin dependencias
De este modo, todos los objetos que deriven de esta clase tendrn que implementar
la forma de escribir y leer de un stream. Es til delegar los detalles de serializacin al
objeto.
Supngase ahora una clase muy sencilla y sin dependencias, con un double, un
int y un string para serializar.
22.1. Serializacin de objetos [641]
C22
1 class ObjetoSimple : public ISerializable
2 {
3 public:
4 ObjetoSimple(double a, int b, std::string cad);
5
6 ObjetoSimple();
7 virtual ~ObjetoSimple();
8
9 virtual void read (std::istream& in);
10 virtual void write(std::ostream& out);
11
12 private:
13
14 double a_;
15 int b_;
16 std::string cad_;
17 };
Con dependencias
Almacenar todos los objetos, teniendo en cuenta que lo primero que se almace-
nar ser la direccin de memoria que ocupa el objeto actual. Los punteros del
mismo se almacenarn como el resto de datos.
Al leer los objetos, poner en una tabla el puntero antiguo ledo, asociado a la
C22
nueva direccin de memoria.
Hacer una pasada corrigiendo el valor de los punteros, buscando la correspon-
dencia en la tabla.
Para ello necesitamos una interfaz nueva, que soporte la nueva funcin fixPtrs()
y otras dos para leer y recuperar la posicin de memoria del propio objeto.
8 protected:
9 virtual void readMemDir (std::istream& in) = 0;
10 virtual void writeMemDir(std::ostream& out) = 0;
11 };
En la lnea 15 se aade un puntero que almacenar la direccin de memoria de la
propia clase.
La implementacin de las funciones de lectura y escritura se muestra a continua-
cin.
11 private:
12 LookUpTable(){}
13
14 std::map<Serializable*, Serializable*> sMap_;
15
16 };
C22
Uno de los constructores ahora acepta un puntero a un objeto del mismo tipo. En
23 se declara un puntero a un objeto del mismo tipo, y tendr que ser serializado,
recuperado y arreglado. Con motivo de probar si la lectura ha sido correcta, se han
aadido un par de funciones, printCad, que imprime la cadena serializada del pro-
pio objeto y printOther, que imprime la cadena del objeto apuntado a travs del
primer mtodo.
De esto modo, la implementacin de la clase anterior sera la siguiente. Primero
para las funciones de impresin, que son las ms sencillas:
6 void ObjetoCompuesto::printOther()
7 {
8 if (obj_) obj_->printCad();
9 }
3 return;
4
5 LookUpTable::itMapS it;
6 it = LookUpTable::getMap().find(obj_);
7 if (it == LookUpTable::getMap().end()) {
8 std::cout << "Puntero no encontrado" << std::endl;
9 throw;
10 }
11 obj_ = (ObjetoCompuesto*) it->second;
12 std::cout << "obj_ FIXED: " << obj_ << std::endl;
13 }
C22
26
27 std::vector<Serializable*> objetosLeidos;
28
29 for (int i = 0; i < 5; ++i) {
30 ObjetoCompuesto* o = new ObjetoCompuesto();
31 o->read(fin);
32 objetosLeidos.push_back(o);
33 }
34
35 cout << "\nFix punteros" << endl;
36 cout << "------------" << endl;
37
38 for_each(objetosLeidos.begin(), objetosLeidos.end(),
39 mem_fun(&Serializable::fixPtrs));
40
41 cout << "\nProbando" << endl;
42 cout << "--------" << endl;
43
44 std::vector<Serializable*>::iterator it;
45 for (it = objetosLeidos.begin();
[648] CAPTULO 22. TCNICAS ESPECFICAS
46 it != objetosLeidos.end();
47 ++it)
48 static_cast<ObjetoCompuesto*>((*it))->printOther();
49
50 return 0;
51 }
En las lneas 5-23 se crea el archivo que se usar como un stream y algunos
objetos que se van serializando. Algunos de ellos se crean en el stack y otro en el
heap. El archivo se cerrar puesto que la variable fout sale de contexto al terminar
el bloque.
En la lnea 27 se abre el mismo archivo para proceder a su lectura.
En 29-35 se
leen los datos del archivo y se van metiendo en un vector. En 40-41 se procede a
ejecutar la funcin fixPtrs de cada uno de los objetos almacenados dentro del vec-
tor. Justo despus se ejecutan las funciones que imprimen las cadenas de los objetos
apuntados, para comprobar que se han restaurado correctamente las dependencias.
La salida al ejecutar el programa anterior se muestra a continuacin:
Serializando
------------
* obj_: 0
* obj_: 0x7fff3f6dad80
* obj_: 0x7fff3f6dadb0
* obj_: 0x7fff3f6dadb0
* obj_: 0x11b3320
Recuperando
-----------
a_: 3.1371
b_: 1337
cad_: CEDV
obj_: 0
this: 0x11b3260
--------------
a_: 9.235
b_: 31337
cad_: UCLM
obj_: 0x7fff3f6dad80
this: 0x11b3370
--------------
a_: 9.235
b_: 6233
cad_: ESI
obj_: 0x7fff3f6dadb0
this: 0x11b3440
--------------
a_: 300.2
b_: 1000
cad_: BRUE
obj_: 0x7fff3f6dadb0
this: 0x11b3520
--------------
a_: 10.2
b_: 3243
cad_: 2012
obj_: 0x11b3320
this: 0x11b35d0
--------------
22.1. Serializacin de objetos [649]
Fix punteros
------------
obj_ FIXED: 0x11b3260
obj_ FIXED: 0x11b3370
obj_ FIXED: 0x11b3370
obj_ FIXED: 0x11b3520
Probando
--------
CEDV
UCLM
UCLM
BRUE
Para serializar la clase simple expuesta en la seccin anterior, primero habra del
siguiente modo:
C22
10 virtual ~ObjetoSimple();
11
12 void print();
13
14 template<class Archive>
15 void serialize(Archive & ar, const unsigned int version) {
16 ar & a_;
17 ar & b_;
18 ar & cad_;
19 }
20
21 private:
22 double a_;
23 int b_;
24 std::string cad_;
25 };
[650] CAPTULO 22. TCNICAS ESPECFICAS
En la lnea 8 se permite el acceso a esta clase desde la funcin access de Boost,
que se usar para la invocacin de serialize 18-22 . El smbolo & utilizado dentro
de dicha funcin templatizada representa a << o >> segn sea el tipo de Archive,
que ser el envoltorio de fstreams de Boost usado para la serializacin. Es preci-
samente en esa funcin donde se lleva a cabo la serializacin, puesto que para cada
variable de la clase, se procede a su lectura o escritura.
A continuacin se muestra cmo utilizar esta clase en un programa:
dosobjetos.
En el primer bloque se crea un archivo de salida, y se crean y escriben
En el segundo se leen y se imprimen. Como se muestra en la lneas 5 y 12 , se usan
los operadores de insercin y extraccin de las clases de Boost utilizadas.
28 ObjetoSimple simple_;
29 ObjetoCompuesto* obj_;
30 };
De hecho, dos de los pocos casos donde esta forma diere se muestran en el si-
guiente apartado.
C22
Listado 22.24: Declarando un objeto base serializable con Boost
1 class Base {
2 friend class boost::serialization::access;
3 public:
4 Base(const std::string& bName) :
5 baseName_(bName) {}
6
7 virtual void print() {
8 std::cout << "Base::print(): " << baseName_;
9 };
10
11 virtual ~Base() {}
12
13 template<class Archive>
14 void serialize(Archive & ar, const unsigned int version) {
15 ar & baseName_;
16 }
17
[652] CAPTULO 22. TCNICAS ESPECFICAS
18 protected:
19 std::string baseName_;
20 };
Listado 22.25: Declarando un objeto derivado y con contenedores serializable con Boost
1 class ObjetoDerivadoCont : public Base
2 {
3 friend class boost::serialization::access;
4 public:
5 ObjetoDerivadoCont(std::string s) :
6 Base(s) { }
7
8 ObjetoDerivadoCont() : Base("default") {}
9
10 virtual ~ObjetoDerivadoCont(){}
11
12 virtual void print();
13
14 void push_int(int i) {
15 v_.push_back(i);
16 };
17
18 template<class Archive>
19 void serialize(Archive & ar, const unsigned int version) {
20 ar & boost::serialization::base_object<Base>(*this);
21 ar & v_;
22 }
23
24 private:
25 std::vector<int> v_;
26 };
La nica cosa que hay que tener en cuenta a la hora de serializar este tipo de clases
la parte relativa a la clase base. Esto
es que hay que ser explcito a la hora de serializar
se lleva a cabo como se muestra en la lnea 20 del cdigo anterior.
Para que se puedan serializar contenedores, simplemente habr que incluir la ca-
becera de Boost correspondiente:
Con todos los ejemplos anteriores se puede afrontar casi cualquier tipo de seriali-
zacin. Queda claro que el uso de Boost acelera el proceso, pero aun existen platafor-
mas donde Boost no est portada (aquellas con compiladores que no soportan todas
las caractersticas de C++, por ejemplo) y donde la STL aun lucha por parecerse al
estndar. Es en stas donde habr que realizar una serializacin ms artesana y usar
algn tipo de tcnica parecida a la vista en las primeras secciones.
C22
esta interaccin entre diversos lenguajes de programacin. En concreto vamos a coger
C++, como ya hemos comentado, un lenguaje orientado a objetos muy eciente en
su ejecucin y Python, un lenguaje interpretado (como java, php, Lua etc.) muy apro-
piado por su simpleza y portabilidad que nos permite desarrollar prototipos de forma
rpida y sencilla.
En el caso del desarrollo de juegos cuyo lenguaje principal sea de scripting (por
ejemplo, Python), una aproximacin genrica sera, desarrollar el juego por completo,
y despus, mediante tcnicas de proling se identican aquellas partes crticas pa-
ra mejorar las prestaciones, que son las que se implementan en C/C++. Obviamente
aquellas partes que, a priori, ya sabemos que sus prestaciones son crticas podemos
anticiparnos y escribirlas directamente en C/C++.
En el caso de que la aplicacin se implemente en C/C++, utilizamos un lenguaje Lua vs Python
de scripting para el uso de alguna librera concreta o para poder modicar/adapta-
r/extender/corregir el comportamiento sin tener que recompilar. En general, cuando Mientras que Lua est pensado para
extender aplicaciones y como len-
hablamos de C++ y scripting hablamos de utilizar las caractersticas de un lenguaje guaje de conguracin, Python es
de prototipado rpido desde C++, lo cual incluye, a grandes rasgos: mas completo y puede ser utilizado
para funciones mas complejas.
Crear y borrar objetos en el lenguaje de scripting e interaccionar con ellos invo-
cando mtodos .
pasar datos y obtener resultados en invocaciones a funciones
Gestionar posibles errores que pudieran suceder en el proceso de interaccin,
incluyendo excepciones.
Otro ejemplo de las posibilidades de los lenguajes de scripting son utilizar lengua-
jes especcos ampliamente usados en otros entornos como la inteligencia articial,
para implementar las partes relacionadas del juego. Ejemplos de este tipo de lenguajes
seran LISP y Prolog ampliamente usados en inteligencia articial, y por lo tanto, muy
apropiados para modelar este tipo de problemas.
En la actualidad, las decisiones de diseo en cuanto a qu lenguaje de scripting
usar viene determinado por las caractersticas de dichos lenguajes. Sin tener en cuen-
ta lenguajes muy orientados a problemas concretos como los mencionados LISP y
Prolog, y considerando slo aquellos lenguajes de scripting de propsito general, las
opciones actuales pasan por Lua y Python principalmente.
Atendiendo a sus caractersticas, Python:
Es cierto que tanto Lua como Python pueden ser utilizados para extender apli-
caciones desarrolladas en C/C++, la decisin de qu lenguaje usar depende de qu
caractersticas queremos implementar en el lenguaje de scripting. Al ser Python un
lenguaje mas genrico, y por tanto mas verstil, que Lua ser el que estudiaremos mas
en profundidad.
22.2. C++ y scripting [655]
Uso de lenguajes compilados En nomenclatura Python, hablamos de extender Python cuando usamos funciones
y objetos escritos en un lenguaje (por ejemplo C++) desde programas en Python. Por
el contrario, se habla de Python embebido cuando es Python el que se invoca desde
Aquellas partes de clculo intensivo
deben ir implementadas en los len- una aplicacin desarrollada en otro lenguaje. Desde la nomenclatura C/C++ se habla
guajes ecientes (compilados) de scripting cuando accedemos a un lenguaje de script desde C++.
El interprete Python ya incluye extensiones para empotrar Python en C/C++. Es
requisito imprescindible tener instalado en la mquina a ejecutar los ejemplos de esta
seccin, el intrprete de Python (usaremos la versin 2.7) aunque dichas extensiones
estn desde la versin 2.2.
En el primer ejemplo, vamos a ver la versin Python del intrprete y que nos sirve
para ver cmo ejecutar una cadena en dicho intrprete desde un programa en C++.
C22
tipo PyObject (concretamente punteros a este tipo) podemos obtener referencias a
cualquier mdulo e invocar funciones en ellas. En la tabla 22.1 podemos ver, de forma
muy resumida, algunas funciones que nos pueden ser muy tiles. Por supuesto no estn
todas pero nos pueden dar una referencia para los pasos principales que necesitaramos
de cara a la interaccin C++ y Python.
La gestin de errores (del mdulo sys) en la actualidad est delegada en la funcin
exc_info()() que devuelve una terna que representan el tipo de excepcin que se
ha producido, su valor y la traza (lo que hasta la versin 1.5 representaban las variables
sys.exc_type, sys.exc_value y sys.exc_traceback).
Con el ejemplo visto en esta subseccin no existe un intercambio entre nuestro
programa C++ y el entorno Python. Por supuesto, el soporte nativo de Python nos
permite realizar cualquier forma de interaccin que necesitemos. No obstante, pode-
mos beneciarnos de libreras que nos hacen esta interaccin mas natural y orientada
a objetos. Vamos a estudiar la interaccin entre ambos entornos mediante la librera
boost.
[656] CAPTULO 22. TCNICAS ESPECFICAS
Funcin Cometido
Py_Initialize() Inicializa el intrprete
PyString_FromString(cadena) Retorna un puntero a PyObject con una cadena
(E.j. nombre del mdulo a cargar).
PyImport_Import(PyObject* name) Carga un mdulo, retorna un puntero a PyOb-
ject.
PyModule_GetDict(PyObject* modu- Obtiene el diccionario con atributos y mtodos
lo) del mdulo. Retorna un puntero a PyObject.
PyDict_GetItemString(PyObject *Dic- Obtiene una referencia a una funcin. Retorna
cionario, "funcin") un puntero a PyObject
PyObject_CallObject(PyObject *fun- Llama a la funcin con los argumentos propor-
cin, argumentos) cionados.
PyCallable_Check(PyObject *funcion) Comprueba que es un objeto invocable.
PyRun_File Interpreta un archivo
PyTuple_New(items) Crea una tupla
PyTuple_SetItem(tupla, posicin, item) Almacena un Item en una tupla
PyErr_Print() Imprime error.
PyList_Check(PyObject*) Comprueba si PyObject es una lista
10
11 Py_Initialize();
12 PyRun_SimpleString("import sys; major, minor = sys.version_info
[:2]");
13 object mainobj = import("__main__");
14 object dictionary = mainobj.attr("__dict__");
15 object major = dictionary["major"];
16 int major_version = extract<int>(major);
17 object minor = dictionary["minor"];
18 int minor_version = extract<int>(minor);
19 cout<<major_version<<"."<<minor_version<<endl;
20 Py_Finalize();
21 return 0;
22 }
C22
plejas (por ejemplo, tuplas), esta extraccin de elementos se puede realizar mediante
el anidamiento de llamadas a la plantilla extract.
Modicando brevemente el ejemplo anterior podemos mostrar el caso ms bsico
de una tupla. tal y como podemos ver en este listado:
11 Py_Finalize();
12 return 0;
13 }
C22
tiva a atributos y a funciones denidas en dicho archivo. A continuacin ya podemos
obtener un objeto que representa a la funcin Python que vamos a invocar (lnea 8).
Si este objeto es vlido (lnea 9), obtenemos un puntero compartido al objeto que
vamos a compartir entre el intrprete Python y el espacio C++. En este caso, creamos
un objeto de la clase hero (lnea 11).
Ya estamos listo para invocar la funcin proporcionndole la instancia que acaba-
mos de crear. Para ello, utilizamos la instancia del puntero compartido y obtenemos
con get() la instancia en C++, con el cual podemos llamar a la funcin (lnea 13) y
por supuesto comprobar que, efectivamente, nuestro hroe se ha congurado correc-
tamente (lnea 14).
[660] CAPTULO 22. TCNICAS ESPECFICAS
Veamos ahora el caso contrario, es decir, vamos a tener una clase en C++ y vamos
a acceder a ella como si de un mdulo en Python se tratara. De hecho el trabajo duro
ya lo hemos realizado, en el ejemplo anterior, ya usbamos un objeto denido en C++
desde el interprete en Python.
Aprovechemos ese trabajo, si tenemos en un archivo el cdigo relativo a la clase
hero (listado 22.31) y la exposicin realizada de la misma (listado 22.33) lo que nos
falta es construir un mdulo dinmico que el intrprete Python pueda cargar. En este
punto nos puede ayudar el sistema de construccin del propio interprete. Efectivamen-
te podemos realizar un archivo setup.py tal y como aparece en el listado 22.35
Listado 22.35: Conguracin para generar el paquete Python a partir de los fuentes C++
1
2 from distutils.core import setup, Extension
3
4 module1 = Extension(ConfActors, sources = [hero.cc] , libraries
= [boost_python-py27])
5
6 setup (name = PackageName,
7 version = 1.0,
8 description = A C++ Package for python,
9 ext_modules = [module1])
Esto nos generar la librera especca para la mquina donde estamos y lo alo-
jar en el directorio build que crear en el mismo directorio donde est el setup.py
(build/lib.linux-i686-2.7/ en nuestro caso) y con el nombre del mdulo (ConfAc-
tors.so) que le hemos indicado. A partir de este punto, previa importacin del m-
dulo ConfActors, podemos acceder a todas sus clases y mtodos directamente desde
el interprete de python como si fuera un mdulo mas escrito de forma nativa en este
lenguaje.
Y su implementacin:
C22
Con este archivo de conguracin generamos player_wrap.cc y player.py:
22.2.5. Conclusiones
Realizar un tutorial completo y guiado de la interaccin entre C++ y los lenguajes
de scripting queda fuera del mbito de este libro. Hemos proporcionado, no obstante,
algunos ejemplos sencillos que permiten al lector hacerse una idea de los pasos bsicos
para una interaccin bsica entre C++ y un lenguaje de scripting de propsito general
como es Python.
Se inici esta seccin proporcionando los motivos por los que la integracin de
varios lenguajes de programacin en una misma aplicacin es una tcnica muy til
y ampliamente utilizada en el mundo de los videojuegos. El objetivo nal es utilizar
el lenguaje mas apropiado para la tarea que estamos desarrollando, lo cual da como
resultado una mayor productividad y juegos mas exibles y extensibles.
A continuacin hemos proporcionado algunas directivas bsicas de cmo deci-
dir entre lenguajes de scripting y compilados y qu partes son apropiadas para unos
lenguajes u otros.
La mayor parte de esta seccin se ha dedicado a mostrar cmo podemos integrar
C++ y Python de tres maneras posibles:
Optimizacin
23
Francisco Moya Fernndez
A
ntes de entrar en materia vamos a matizar algunos conceptos. Optimizar ha-
ce referencia a obtener el mejor resultado posible. Pero la bondad o maldad
del resultado depende fuertemente de los criterios que se pretenden evaluar.
Por ejemplo, si queremos hacer un programa lo ms pequeo posible el resultado se-
r bastante diferente a si lo que queremos es el programa ms rpido posible. Por
tanto cuando hablamos de optimizacin debemos acompaar la frase con el objetivo,
con la magnitud que se pretende mejorar hasta el limite de lo posible. As se habla
frecuentemente de optimizar en velocidad u optimizar en tamao.
La optimizacin normalmente es un proceso iterativo e incremental. Cada etapa
produce un resultado mejor (o por lo menos ms fcil de mejorar). A cada una de
estas etapas del proceso se les suele denominar tambin optimizaciones, aunque sera
ms correcto hablar de etapas del proceso de optimizacin. Pero adems el objetivo
de optimizacin se enmarca en un contexto:
663
[664] CAPTULO 23. OPTIMIZACIN
C23
mance counters. Estos registros son capaces de contar determinados eventos,
tales como ciclos de la CPU, ciclos de bus, instrucciones, referencias a la cache,
fallos de la memoria cach, saltos o fallos en la prediccin de saltos. Estos regis-
tros pueden ser utilizados en prolers tales como perf para realizar mediciones
muy precisas.
Es importante conocer cundo se emplea cada una de estas tcnicas para poder
interpretar con precisin los datos del perlado. As, por ejemplo, las tcnicas basadas
en muestreo de la traza de llamadas debe entenderse en un contexto estadstico. Valo-
res bajos en los contadores de llamadas no tienen signicado absoluto, sino en relacin
a otros contadores. Es muy posible que tengamos que ejecutar el mismo fragmento de
cdigo mltiples veces para eliminar cualquier sesgo estadstico.
[666] CAPTULO 23. OPTIMIZACIN
A continuacin conviene congurar el kernel para que permita a los usuarios nor-
males recabar estadsticas de todo tipo. Esto no debe hacerse con carcter general,
sino solo en las computadoras empleadas en el desarrollo, puesto que tambin facilita
la obtencin de informacin para realizar un ataque.
$ perf list
C23
En la lista de eventos podemos apreciar seis tipos diferentes.
Software event. Son simples contadores del kernel. Entre otros permite contar
cambios de contexto o fallos de pgina.
Hardware event. Este evento se reere a los contadores incluidos en las PMU (Per-
formance Monitoring Units) de los procesadores modernos. Permite contar ci-
clos, intrucciones ejecutadas, fallos de cach. Algunos de estos contadores se
ofrecen de forma unicada como contadores de 64 bits, de tal forma que oculta
los detalles de la PMU subyacente. Pero en general su nmero y tipo depender
del modelo de rocesador donde se ejecuta.
[668] CAPTULO 23. OPTIMIZACIN
Hardware cache event. Dentro del subsistema de memoria las PMU modernas2
permiten extraer estadsticas detalladas de las memorias cach de primer nivel
de ltimo nivel o del TLB (Translation Lookaside Buffer). Nuevamente se trata
de contadores que dependen fuertemente del modelo de procesador sobre el que
se ejecuta.
Hardware breakpoint. Los puntos de ruptura hardware permiten detener la eje-
cucin del programa cuando el procesador intenta leer, escribir o ejecutar el
contenido de una determinada posicin de memoria. Esto nos permite monito-
rizar detalladamente objetos de inters, o trazar la ejecucin de instrucciones
concretas.
Tracepoint event. En este caso se trata de trazas registradas con la infraestructura
ftrace de Linux. Se trata de una infraestructura extremadamente exible para
trazar todo tipo de eventos en el kernel o en cualquier mdulo del kernel. Esto
incluye eventos de la GPU, de los sistemas de archivos o del propio scheduler.
Raw hardware event. En el caso de que perf no incluya todava un nombre
simblico para un contador concreto de una PMU actual se puede emplear el
cdigo hexadecimal correspondiente, de acuerdo al manual del fabricante.
2 Los eventos de las PMU se documentan en los manuales de los fabricantes. Por ejemplo, los conta-
Y podemos dividir entre los eventos que ocurren en espacio de usuario y los que
ocurren en espacio del kernel.
Todos los eventos hardware aceptan los modicadores u para ltrar solo los que
ocurren en espacio de usuario, k para ltrar los que ocurren en espacio del kernel y
uk para contabilizar ambos de forma explcita. Hay otros modicadores disponibles,
incluso alguno dependiente del procesador en el que se ejecuta.
C23
efecto con un ejemplo. El computador sobre el que se escriben estas lneas dispone de
un procesador Intel Core i5. Estos procesadores tienen 4 contadores genricos3 .
Vamos a ver qu pasa cuando se piden 4 eventos idnticos:
Puede verse que la precisin es absoluta, los cuatro contadores han contado prcti-
camente la misma cantidad de ciclos. En cambio, veamos qu pasa cuando se solicitan
5 eventos idnticos:
Los valores son signicativamente diferentes, pero los porcentajes entre corchetes
nos previenen de que se ha realizado un escalado. Por ejemplo, el primer contador ha
estado contabilizando ciclos durante el 79,06 % del tiempo. El valor obtenido en el
contador se ha escalado dividiendo por 0,7906 para obtener el valor mostrado.
En este caso los contadores nos dan una aproximacin, no un valor completamente
able. Nos vale para evaluar mejoras en porcentajes signicativos, pero no mejoras
de un 1 %, porque como vemos el escalado ya introduce un error de esa magnitud.
Adems en algunas mediciones el resultado depender del momento concreto en que
se evalen o de la carga del sistema en el momento de la medida. Para suavizar todos
estos efectos estadsticos se puede ejecutar varias veces empleando la opcin -r.
233 faults
Ntese que utilizamos la orden sleep para no consumir ciclos y de esta forma
no inuir en la medida.
C23
$ perf record -a sleep 10
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.426 MB perf.data (~18612 samples)
]
$ perf report
Para poder utilizar las caractersticas de anotacin del cdigo de los perla-
dores es necesario compilar el programa con informacin de depuracin.
Para ilustrar la mecnica veremos un caso real. Ingo Molnar, uno de los principales
desarrolladores de la infraestructura de perlado de Linux, tiene multitud de mensajes
en diversos foros sobre optimizaciones concretas que fueron primero identicadas
mediante el uso de perf. Uno de ellos4 describe una optimizacin signicativa de
git, el sistema de control de versiones.
En primer lugar realiza una fase de anlisis de una operacin concreta que revela
un dato intranquilizador. Al utilizar la operacin de compactacin git gc descubre
un nmero elevado de ciclos de estancamiento (stalled cycles5 ):
stalled-cycles pero mantenemos el texto del caso de uso original para no confundir al lector.
23.1. Perlado de programas [673]
C23
8,821,931,740 instructions # 1.00 insns per cycle
# 0.24 stalled cycles per insn ( +- 0.04 % )
1,750,408,175 branches # 631.661 M/sec ( +- 0.04 % )
74,612,120 branch-misses # 4.26 % of all branches ( +- 0.07 % )
La opcin -sync hace que se ejecute una llamada sync() (vuelca los buffers
pendientes de escritura de los sistemas de archivos) antes de cada ejecucin para re-
ducir el ruido en el tiempo transcurrido.
Despus de la optimizacin el resultado es:
Por alguna razn el bucle abierto es ms sencillo de predecir por parte de la CPU
por lo que produce menos errores de prediccin.
No obstante es importante entender que estas optimizaciones corresponden a pro-
blemas en otros puntos (compilador que genera cdigo subptimo, y arquitectura que
privilegia un estilo frente a otro). Por tanto se trata de optimizaciones con fecha de
caducidad. Cuando se utilice una versin ms reciente de GCC u otro compilador ms
agresivo en las optimizaciones esta optimizacin no tendr sentido.
Un caso clebre similar fue la optimizacin del recorrido de listas en el kernel
Linux6 . Las listas son estructuras muy poco adecuadas para la memoria cach. Al
no tener los elementos contiguos generan innumerables fallos de cach. Mientras se
produce un fallo de cach el procesador est parcialmente parado puesto que necesita
el dato de la memoria para operar. Por esta razn en Linux se emple una optimizacin
denominada prefetching. Antes de operar con un elemento se accede al siguiente. De
esta forma mientras est operando con el elemento es posible ir transriendo los datos
de la memoria a la cach.
Desgraciadamente los procesadores modernos incorporan sus propias unidades de
prefetch que realizan un trabajo mucho mejor que el manual, puesto que no interere
con el TLB. El propio Ingo Molnar reporta que esta optimizacin estaba realmente
causando un impacto de 0,5 %.
La leccin que debemos aprender es que nunca se debe optimizar sin medir, que
las optimizaciones dependen del entorno de ejecucin, y que si el entorno de ejecucin
vara las optimizaciones deben re-evaluarse.
6 https://lwn.net/Articles/444336/
23.1. Perlado de programas [675]
C23
los programas con la opcin -pg el programa quedar instrumentado para perlado.
Todas las ejecuciones del programa generan un archivo gmon.out con la infor-
macin recolectada, que puede examinarse con gprof. GNU Proler utiliza muestreo
estadstico sin ayuda de PMU. Esto lo hace muy portable pero notablemente impreci-
so.
Google Performance Tools aporta un conjunto de bibliotecas para perlado de
memoria dinmica o del procesador con apoyo de PMU. Por ejemplo, el perlado
de programas puede realizarse con la biblioteca libprofiler.so. Esta biblioteca
puede ser cargada utilizando la variable de entorno LD_PRELOAD y activada mediante
la denicin de la variable de entorno CPUPROFILE. Por ejemplo:
$ LD_PRELOAD=/usr/lib/libprofiler.so.0 CPUPROFILE=prof.data \
./light-model-test
[676] CAPTULO 23. OPTIMIZACIN
Esto genera el archivo prof.data con los datos de perlado, que luego pueden GPU Prolers
examinarse con google-pprof. Entre otras capacidades permite representacin
grca del grafo de llamadas o compatibilidad con el formato de kcachegrind. De momento solo nVidia pro-
porciona un proler con capaci-
Una caracterstica interesante de Google Performance Tools es la capacidad de dades grcas sobre GNU/Linux.
realizar el perlado solo para una seccin concreta del cdigo. Para ello, en lugar de AMD APP Proler funciona en
GNU/Linux pero no con interfaz
denir la variable CPUPROFILE basta incluir en el cdigo llamadas a las funciones grca.
ProfilerStart() y ProfilerStop().
Para un desarrollador de videojuegos es destacable la aparicin de perladores
especcos para GPUs. Las propias GPUs tienen una PMU (Performance Monitoring
Unit) que permite recabar informacin de contadores especcos. De momento en el
mundo del software libre han emergido nVidia Visual Proler, AMD APP Proler
y extensiones de Intel a perf para utilizar los contadores de la GPU (perf gpu).
Probablemente en un futuro cercano veremos estas extensiones incorporadas en la
distribucin ocial de linux-tools.
23.2. Optimizaciones del compilador [677]
C fue diseado con el objetivo inicial de programar un sistema operativo. Por este
motivo, desde las primeras versiones incorpora caractersticas de muy bajo nivel que
permite dirigir al compilador para generar cdigo ms eciente. Variables registro,
funciones en lnea, paso por referencia, o plantillas son algunas de las caractersticas
que nos permiten indicar al compilador cundo debe esforzarse en buscar la opcin
ms rpida. Sin embargo, la mayora de las construcciones son simplemente indica-
C23
ciones o sugerencias, que el compilador puede ignorar libremente si encuentra una
solucin mejor. En la actualidad tenemos compiladores libres maduros con capacida-
des comparables a los mejores compiladores comerciales, por lo que frecuentemente
las indicaciones del programador son ignoradas.
Listado 23.1: Utilizacin arcaica de register para sumar los 1000 primeros nmeros natu-
rales.
1 register unsigned i, sum = 0;
2 for (i=1; i<1000; ++i)
3 sum += i;
Esta palabra clave est en desuso porque los algoritmos de asignacin de registros
actuales son mucho mejores que la intuicin humana. Pero adems, aunque se utiliza-
ra, sera totalmente ignorada por el compilador. La mera aparicin de register en
un programa debe ser considerada como un bug, porque engaa al lector del programa
hacindole creer que dicha variable ser asignada a un registro, cuando ese aspecto
est fuera del control del programador.
El resultado es el siguiente:
14 je .L2
15 movq %rdi, %r8
16 movq %rdi, %rcx
17 andl $15, %r8d
18 shrq $2, %r8
19 negq %r8
20 andl $3, %r8d
21 cmpl %esi, %r8d
22 cmova %esi, %r8d
23 xorl %edx, %edx
24 testl %r8d, %r8d
25 movl %r8d, %ebx
26 je .L11
27 .p2align 4,,10
28 .p2align 3
29 .L4:
30 addl $1, %edx
31 addl ( %rcx), %eax
32 addq $4, %rcx
33 cmpl %r8d, %edx
34 jb .L4
35 cmpl %r8d, %esi
36 je .L2
37 .L3:
38 movl %esi, %r11d
39 subl %r8d, %r11d
40 movl %r11d, %r9d
41 shrl $2, %r9d
42 leal 0(, %r9,4), %r10d
43 testl %r10d, %r10d
44 je .L6
45 pxor %xmm0, %xmm0
46 leaq ( %rdi, %rbx,4), %r8
47 xorl %ecx, %ecx
48 .p2align 4,,10
49 .p2align 3
50 .L7:
51 addl $1, %ecx
52 paddd ( %r8), %xmm0
53 addq $16, %r8
54 cmpl %r9d, %ecx
55 jb .L7
56 movdqa %xmm0, %xmm1
57 addl %r10d, %edx
58 psrldq $8, %xmm1
59 paddd %xmm1, %xmm0
60 movdqa %xmm0, %xmm1
61 psrldq $4, %xmm1
62 paddd %xmm1, %xmm0
63 movd %xmm0, -4( %rsp)
64 addl -4( %rsp), %eax
65 cmpl %r10d, %r11d
66 je .L2
C23
67 .L6:
68 movslq %edx, %rcx
69 leaq ( %rdi, %rcx,4), %rcx
70 .p2align 4,,10
71 .p2align 3
72 .L9:
73 addl $1, %edx
74 addl ( %rcx), %eax
75 addq $4, %rcx
76 cmpl %edx, %esi
77 ja .L9
78 .L2:
79 popq %rbx
80 .cfi_remember_state
81 .cfi_def_cfa_offset 8
82 ret
83 .L11:
84 .cfi_restore_state
[680] CAPTULO 23. OPTIMIZACIN
C23
Listado 23.6: Esta biblioteca solo exporta la funcin sum10().
1 static int sum(int* a, unsigned size)
2 {
3 int ret = 0;
4 for (int i=0; i<size; ++i) ret += a[i];
5 return ret;
6 }
7
8 int sum10(int* a)
9 {
10 return sum(a,10);
11 }
[682] CAPTULO 23. OPTIMIZACIN
Estas funciones, que eran expandidas en lnea por el compilador, exhiban un com-
portamiento anmalo con respecto a los ciclos de estancamiento y a la prediccin de
saltos. Por lo que Ingo propone la siguiente optimizacin:
Cuadro 23.3: Resultados de la optimizacin de Ingo Molnar con compiladores actuales (100 repeticiones).
C23
elisin de copia, se permite en las siguientes circunstancias (que pueden
ser combinadas para eliminar copias mltiples):
En una sentencia return de una funcin cuyo tipo de retorno sea
una clase, cuando la expresin es el nombre de un objeto automti-
co no voltil (que no sea un parmetro de funcin o un parmetro de
una clusula catch) con el mismo tipo de retorno de la funcin (que
no puede ser const ni volatile), la operacin de copia o movimiento
puede ser omitida mediante la construccin directa del objeto auto-
mtico en el propio valor de retorno de la funcin.
En una expresin throw, cuando el operando es el nombre de un ob-
jeto automtico no voltil (que no sea un parmetro de funcin o un
parmetro de una clusula catch) cuyo mbito de declaracin no se
extienda ms all del nal del bloque try ms interior que contenga a
[684] CAPTULO 23. OPTIMIZACIN
1 class Thing {
2 public:
3 Thing();
4 ~Thing();
5 Thing(const Thing&);
6 };
7
8 Thing f() {
9 Thing t;
10 return t;
11 }
12
13 Thing t2 = f();
Aqu los criterios de elisin pueden combinarse para eliminar dos lla-
madas al constructor de copia de Thing: la copia del objeto automtico
local t en el objeto temporal para el valor de retorno de la funcin f() y
la copia de ese objeto temporal al objeto t2. Por tanto la construccin del
objeto local t puede verse como la inicializacin directa del objeto t2, y
la destruccin de dicho objeto tendr lugar al terminar el programa. Aa-
dir un constructor de movimiento a Thing tiene el mismo efecto, en cuyo
caso es el constructor de movimiento del objeto temporal a t2 el que se
elide.
23.2.4. Volatile
Las optimizaciones del compilador pueden interferir con el funcionamiento del
programa, especialmente cuando necesitamos comunicarnos con perifricos. As por
ejemplo, el compilador es libre de reordenar y optimizar las operaciones mientras
mantenga una equivalencia funcional. As, por ejemplo este caso se encuentra no po-
cas veces en cdigo de videojuegos caseros para consolas.
1 _Z5resetRj:
2 movl $0, ( %rdi)
3 ret
23.3. Conclusiones
La optimizacin de programas es una tarea sistemtica, pero a la vez creativa.
Toda optimizacin parte de un anlisis cuantitativo previo, normalmente mediante
el uso de perladores. Existe un buen repertorio de herramientas que nos permite
caracterizar las mejores oportunidades, pero no todo lo que consume tiempo est en
nuestra mano cambiarlo. Las mejores oportunidades provienen de la re-estructuracin
de los algoritmos o de las estructuras de datos.
Por otro lado el programador de videojuegos deber optimizar para una platafor-
ma o conjunto de plataformas que se identican como objetivo. Algunas de las opti-
mizaciones sern especcas para estas plataformas y debern re-evaluarse cuando el
entorno cambie.
C23
Captulo
Validacin y Pruebas
24
David Villa Alises
L
a programacin, igual que cualquier otra disciplina tcnica, debera ofrecer ga-
rantas sobre los resultados. La formalizacin de algoritmos ofrece garantas
indiscutibles y se utiliza con xito en el mbito de los algoritmos numricos y
lgicos. Sin embargo, el desarrollo de software en muchos mbitos est fuertemente
ligado a requisitos que provienen directamente de necesidades de un cliente. En la
mayora de los casos, esas necesidades no se pueden formalizar dado que el cliente
expresa habitualmente requisitos ambiguos o incluso contradictorios. El desarrollo de
software no puede ser ajeno a esa realidad y debe integrar al cliente de forma que
pueda ayudar a validar, renar o recticar la funcionalidad del sistema durante todo el
proceso.
Un programador responsable comprueba que su software satisface los requisitos
del cliente, comprueba los casos tpicos y se asegura que los errores detectados (ya
sea durante el desarrollo o en produccin) se resuelven y no vuelven a aparecer. Es
imposible escribir software perfecto (a da de hoy) pero un programador realmente
profesional escribe cdigo limpio, legible, fcil de modicar y adaptar a nuevas ne-
cesidades. En este captulo veremos algunas tcnicas que pueden ayudar a escribir
cdigo ms limpio y robusto.
687
[688] CAPTULO 24. VALIDACIN Y PRUEBAS
Listado 24.1: Una funcin que dene una invariante sobre su parmetro
1 double sqrt(double x) {
2 assert(x >= 0);
3 [...]
4 }
$ ./assert-argc hello
$ ./assert-argc
assert-argc: assert-argc.cc:4: int main(int, char**): Assertion argc
== 2 failed.
Abortado
24.1.1. Sobrecarga
Las aserciones facilitan la depuracin del programa porque ayudan a localizar el
punto exacto donde se desencadena la inconsistencia. Por eso deberan incluirse desde
el comienzo de la implementacin. Sin embargo, cuando el programa es razonable-
mente estable, las aserciones siempre se cumplen (o as debera ser). En una versin
de produccin las aserciones ya no son tiles2 y suponen una sobrecarga que puede
afectar a la eciencia del programa.
Obviamente, eliminar a mano todas las aserciones no parece muy cmodo. La
mayora de los lenguajes incorporan algn mecanismo para desactivarlas durante la
compilacin. En C/C++ se utiliza el preprocesador. Si la constante simblica NDEBUG
est denida la implementacin de assert() (que en realidad es una macro de
preprocesador) se substituye por una sentencia vaca de modo que el programa que se
compila realmente no tiene absolutamente nada referente a las aserciones.
C24
En los casos en los que necesitamos hacer aserciones ms complejas, que requieran
variables auxiliares, podemos aprovechar la constante NDEBUG para eliminar tambin
ese cdigo adicional cuando no se necesite:
1 [...]
2 #ifndef NDEBUG
3 vector<int> values = get_values();
4 assert(values.size());
5 #endif
2 Por contra, algunos autores como Tony Hoare, deenden que en la versin de produccin es dnde ms
Esto es, aunque valoramos los elementos de la derecha, valoramos ms Figura 24.1: Kent Beck, uno de
los de la izquierda. los principales creadores de eXtre-
me programing, TDD y los mtodos
giles.
Las tcnicas de desarrollo gil pretenden entregar valor al cliente pronto y a me-
nudo, es decir, priorizar e implementar las necesidades expresadas por el cliente para
ofrecerle un producto que le pueda resultar til desde el comienzo. Tambin favorecen
la adopcin de cambios importantes en los requisitos, incluso en las ltimas fases del
desarrollo.
24.3. TDD
Escribe la prueba haciendo uso de las interfaces del sistema (es probable que
an no existan!) y ejectala. La prueba debera fallar y debes comprobar que es
as (rojo).
A continuacin escribe el cdigo de produccin mnimo necesario para que la
prueba pase (verde). Ese cdigo mnimo debe ser solo el imprescindible, lo
ms simple posible, hasta el extremo de escribir mtodos que simplemente re-
tornan el valor que la prueba espera3 . Eso ayuda a validar la interfaz y conrma
que la prueba est bien especicada. Pruebas posteriores probablemente obli-
garn a modicar el cdigo de produccin para que pueda considerar todas las
posibles situaciones. A esto se le llama triangulacin y es la base de TDD:
Las pruebas dirigen el diseo.
Por ltimo refactoriza si es necesario. Es decir, revisa el cdigo de produccin
y elimina cualquier duplicidad. Tambin es el momento adecuado para renom-
brar tipos, mtodos o variables si ahora se tiene ms claro cul es su objetivo
real. Por encima de cualquier otra consideracin el cdigo debe expresar clara-
mente la intencin del programador. Es importante refactorizar tanto el cdigo
de produccin como las propias pruebas.
C24
Este sencillo mtodo de trabajo (el algoritmo TDD) favorece que los programa-
dores se concentren en lo que realmente importa: satisfacer los requisitos del usuario.
Tambin ayuda al personal con poca experiencia en el proyecto a decidir cul es el
prximo paso en lugar de divagar o tratando de mejorar el programa aadiendo
funcionalidades no solicitadas.
3 Kent Beck se reere a esto con la expresin Fake until make it.
[692] CAPTULO 24. VALIDACIN Y PRUEBAS
Desde el punto de vista gil hay una pauta clara: las pruebas se escriben para eje-
cutarse, y debera ocurrir tan a menudo como sea posible. Lo ideal sera ejecutar todas
las pruebas despus de cada cambio en cualquier parte de la aplicacin. Obviamente
eso resulta prohibitivo incluso para aplicaciones pequeas. Hay que llegar a una solu-
cin de compromiso. Por este motivo, las pruebas unitarias son las ms importantes.
Si estn bien escritas, las pruebas unitarias se deberan poder ejecutar en muy pocos
segundos. Eso permite que, con los frameworks y herramientas adecuadas se pueda
lanzar la batera de pruebas unitarias completa mientras se est editando el cdigo
(sea la prueba o el cdigo de produccin).
Para que una prueba se considere unitaria no basta con que est escrita en un
framework xUnit, debe cumplir los principios FIRST [60], que es un acrnimo para:
24.5. Pruebas unitarias con google-tests [693]
Fast Las pruebas unitarias deberan ser muy rpidas. Como se ha dicho, todas las
pruebas unitarias de la aplicacin (o al menos del mdulo) deberan ejecutarse
en menos de 23 segundos.
Independent Cada prueba debe poder ejecutarse por separado o en conjunto, y en
cualquier orden, sin que eso afecte al resultado.
Repeatable La prueba debera poder ejecutarse mltiples veces dando siempre el
mismo resultado. Por este motivo no es buena idea incorporar aleatoriedad a
los tests. Tambin implica que la prueba debe poder funcionar del mismo modo
en entornos distintos.
Self-validating La prueba debe ofrecer un resultado concreto: pasa o falla, sin que el
programador tenga que leer o interpretar un valor en pantalla o en un chero.
Timely El test unitario debera escribirse justo cuando se necesite, es decir, justo antes
de escribir el cdigo de produccin relacionado, ni antes ni despus.
Se incluye el archivo de cabecera de gtest (lnea 1) donde estn denidas las ma-
cros que vamos a utilizar para denir las pruebas. La lnea 4 dene una prueba llamada
C24
Zero mediante la macro TEST para el casa de prueba (TestCase) FactorialTest.
La lnea 5 especica una expectativa: el resultado de invocar factorial(0) debe
ser igual a 1.
Adems del chero con la prueba debemos escribir el cdigo de produccin, su
chero de cabecera y un Makefile. Al escribir la expectativa ya hemos decidido el
nombre de la funcin y la cantidad de parmetros (aunque no es tipo). Veamos estos
cheros:
4 Inspirado en el primer ejemplo del tutorial de GTests.
[694] CAPTULO 24. VALIDACIN Y PRUEBAS
$ make
g++ -c -o factorial.o factorial.cc
g++ factorial-test.o factorial.o -lpthread -lgtest -lgtest_main -o
factorial-test
$ ./factorial-test
Running main() from gtest_main.cc
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from FactorialTest
[ RUN ] FactorialTest.Zero
[ OK ] FactorialTest.Zero (0 ms)
[----------] 1 test from FactorialTest (0 ms total)
Esta primera prueba no la hemos visto fallar porque sin el chero de produccin
ni siquiera podramos haberla compilado.
Aadamos ahora un segundo caso de prueba al chero:
24.6. Dobles de prueba [695]
1 $ ./factorial-test
2 Running main() from gtest_main.cc
3 [==========] Running 2 tests from 1 test case.
4 [----------] Global test environment set-up.
5 [----------] 2 tests from FactorialTest
6 [ RUN ] FactorialTest.Zero
7 [ OK ] FactorialTest.Zero (0 ms)
8 [ RUN ] FactorialTest.Positive
9 factorial-test.cc:10: Failure
10 Value of: factorial(2)
11 Actual: 1
12 Expected: 2
13 [ FAILED ] FactorialTest.Positive (0 ms)
14 [----------] 2 tests from FactorialTest (0 ms total)
15
16 [----------] Global test environment tear-down
17 [==========] 2 tests from 1 test case ran. (0 ms total)
18 [ PASSED ] 1 test.
19 [ FAILED ] 1 test, listed below:
20 [ FAILED ] FactorialTest.Positive
21
22 1 FAILED TEST
El resultado nos indica la prueba que ha fallado (lnea 8), el valor obtenido por
la llamada (lnea 11) y el esperado (lnea 12).
A continuacin se debe modicar la funcin factorial() para que cumpla la
nueva expectativa. Despus escribir nuevas pruebas que nos permitan comprobar que
la funcin cumple con su cometido para unos cuantos casos representativos.
C24
orientado a objetos porque proporciona sustituibilidad (LSP (Liskov Substitution
Principle)). Pero la ocultacin diculta la denicin de pruebas en base a aserciones
sobre de estado porque el estado del objeto est denido por el valor de sus atributos.
Si tuviramos acceso a todos los atributos del objeto (sea con getters o no) sera una
pista de un mal diseo.
[696] CAPTULO 24. VALIDACIN Y PRUEBAS
Debido a ello, suele ser ms factible denir la prueba haciendo aserciones sobre la
interaccin que el objeto que se est probando (el SUT (Subject Under Test)5 ) realiza
con sus colaboradores. El problema de usar un colaborador real es que ste tendr a
su vez otros colaboradores de modo que es probable que para probar un nico mtodo
necesitemos montar gran parte de la aplicacin. Como lo que queremos es instanciar
lo mnimo posible del sistema real podemos recurrir a los dobles6 de prueba.
Un doble de prueba es un objeto capaz de simular la interfaz que un determinado SOLID
colaborador ofrece al SUT, pero que realmente no implementa nada de lgica. El doble
SOLID (SRP, OCP, LSP, ISP, DIP)7
(dependiendo de su tipo) tiene utilidades para comprobar qu mtodos y parmetros es una serie de 5 principios esencia-
us el SUT cuando invoc al doble. les para conseguir diseos orienta-
dos a objetos de calidad. Estos son:
SRP Single Responsibility
OCP Open Closed
Una regla bsica: Nunca se deben crear dobles para clases implementadas por LSP Liskov Substitution
DIP Dependency Inversion
terceros, slo para clases de la aplicacin.
ISP Interface Segregation
Un requisito importante para poder realizar pruebas con dobles es que las clases
de nuestra aplicacin permitan inyeccin de dependencias. Consiste en pasar (in-
yectar) las instancias de los colaboradores (dependencias) que el objeto necesitar en
el momento de su creacin. Pero no estamos hablando de un requisito impuesto por
las pruebas, se trata de otro de los principios SOLID, en concreto DIP (Dependency
Inversion Principle).
Aunque hay cierta confusin con la terminologa, hay bastante consenso en distin-
guir al menos entre los siguientes tipos de dobles:
Para escribir un test que pruebe el mtodo notify() necesitamos un mock para
su colaborador (el observador). El siguiente listado muestra la interfaz que deben
implementar los observadores:
C24
Listado 24.10: Patrn observador con TDD: observer.h
1 #ifndef _OBSERVER_H_
2 #define _OBSERVER_H_
3
4 class Observer {
5 public:
6 virtual void update(void) = 0;
7 virtual ~Observer() {}
8 };
9
10 #endif
14 gmock: gmock-1.7.0.zip
15 unp $<
16 ln -s gmock-1.7.0 gmock
17
18 gmock-1.7.0.zip:
19 wget https://googlemock.googlecode.com/files/gmock-1.7.0.zip
20
21 libgmock.a: gtest-all.o gmock-all.o gmock_main.o
22 ar rv $@ $^
23
24 gtest-all.o: $(GTEST_DIR)/src/gtest-all.cc
25 g++ $(CXXFLAGS) -c $<
26
27 gmock %.o: $(GMOCK_DIR)/src/gmock %.cc
28 g++ $(CXXFLAGS) -c $<
29
30 test: $(TARGET)
31 ./$<
32
33 clean:
34 $(RM) $(TARGET) *.o *~ *.a
Ejecutemos el Makefile:
1 $ make test
2 g++ -I /usr/src/gmock -c -o observable-tests.o observable-tests.cc
3 g++ -I /usr/src/gmock -c -o observable.o observable.cc
4 g++ -I /usr/src/gmock -c -o gmock_main.o /usr/src/gmock/src/
gmock_main.cc
5 g++ -I /usr/src/gmock -c -o gmock-all.o /usr/src/gmock/src/gmock-all
.cc
6 g++ observable-tests.o observable.o gmock_main.o gmock-all.o -
lpthread -lgtest -o observable-tests
7 $ ./observable-tests
8 Running main() from gmock_main.cc
9 [==========] Running 1 test from 1 test case.
10 [----------] Global test environment set-up.
11 [----------] 1 test from ObserverTest
12 [ RUN ] ObserverTest.UpdateObserver
13 observable-tests.cc:11: Failure
14 Actual function call count doesnt match EXPECT_CALL(observer, update
())...
15 Expected: to be called once
16 Actual: never called - unsatisfied and active
17 [ FAILED ] ObserverTest.UpdateObserver (1 ms)
18 [----------] 1 test from ObserverTest (1 ms total)
19
20 [----------] Global test environment tear-down
21 [==========] 1 test from 1 test case ran. (1 ms total)
22 [ PASSED ] 0 tests.
23 [ FAILED ] 1 test, listed below:
24 [ FAILED ] ObserverTest.UpdateObserver
25
26 1 FAILED TEST
C24
Despus de la compilacin (lneas 2-6) se ejecuta el binario correspondiente al
test (lnea 7). El test falla porque se esperaba una llamada a update() (lnea 15)
y no se produjo ninguna (lnea 16) de modo que la expectativa no se ha cumplido.
Es lgico porque el cuerpo del mtodo notify() est vaco. Siguiendo la losofa
TDD escribir el cdigo mnimo para que la prueba pase:
1 $ make test
2 g++ -I /usr/src/gmock -c -o observable.o observable.cc
3 g++ observable-tests.o observable.o gmock_main.o gmock-all.o -
lpthread -lgtest -o observable-tests
4 $ ./observable-tests
5 Running main() from gmock_main.cc
6 [==========] Running 1 test from 1 test case.
7 [----------] Global test environment set-up.
8 [----------] 1 test from ObserverTest
9 [ RUN ] ObserverTest.UpdateObserver
10 [ OK ] ObserverTest.UpdateObserver (0 ms)
11 [----------] 1 test from ObserverTest (0 ms total)
12
13 [----------] Global test environment tear-down
14 [==========] 1 test from 1 test case ran. (0 ms total)
15 [ PASSED ] 1 test.
La prueba pasa. Ahora comprobemos que funciona tambin para dos observado-
res:
Y ejecutamos la prueba:
1 $ make test
2 Running main() from gmock_main.cc
3 [==========] Running 3 tests from 1 test case.
4 [----------] Global test environment set-up.
5 [----------] 3 tests from ObserverTest
6 [ RUN ] ObserverTest.UpdateObserver
7 [ OK ] ObserverTest.UpdateObserver (0 ms)
8 [ RUN ] ObserverTest.NeverUpdateObserver
9 [ OK ] ObserverTest.NeverUpdateObserver (0 ms)
10 [ RUN ] ObserverTest.TwoObserver
11 observable-tests.cc:30: Failure
24.8. Limitaciones [701]
1 $ make test
2 Running main() from gmock_main.cc
3 [==========] Running 3 tests from 1 test case.
4 [----------] Global test environment set-up.
5 [----------] 3 tests from ObserverTest
6 [ RUN ] ObserverTest.UpdateObserver
7 [ OK ] ObserverTest.UpdateObserver (0 ms)
8 [ RUN ] ObserverTest.NeverUpdateObserver
9 [ OK ] ObserverTest.NeverUpdateObserver (0 ms)
10 [ RUN ] ObserverTest.TwoObserver
11 [ OK ] ObserverTest.TwoObserver (0 ms)
12 [----------] 3 tests from ObserverTest (0 ms total)
13
14 [----------] Global test environment tear-down
15 [==========] 3 tests from 1 test case ran. (0 ms total)
16 [ PASSED ] 3 tests.
Todo correcto, aunque sera conveniente una prueba adicional para un mayor n-
mero de observadores registrados. Tambin podramos comprobar que los observado-
C24
res des-registrados (detached) efectivamente no son invocados, etc.
Aunque los ejemplos son sencillos, es fcil ver la dinmica de TDD.
24.8. Limitaciones
Hay ciertos aspectos importantes para la aplicacin en los que TDD, y el testing
en general, tienen una utilidad limitada (al menos hoy en da). Las pruebas permiten
comprobar fcilmente aspectos funcionales, pero es complejo comprobar requisitos
no funcionales.
[702] CAPTULO 24. VALIDACIN Y PRUEBAS
C
ada desarrollador de videojuegos tiene sus propios motivos para invertir su
tiempo y esfuerzo en la elaboracin de un producto tan complejo. Algunos
programan juegos de forma altruista como carta de presentacin o como
contribucin a la sociedad. Pero la mayora de los desarrolladores de videojuegos
trabajan para ver recompensado su esfuerzo de alguna forma, ya sea con donaciones,
publicidad empotrada en el juego, o directamente cobrando por su distribucin, o por
las actualizaciones, o por nuevos niveles. En todos estos casos hay un factor comn, el
desarrollador quiere que el juego sea usado por la mayor cantidad posible de usuarios.
En captulos anteriores hemos visto diferencias entre MS Windows y GNU/Linux
desde el punto de vista de programacin (plugins). En general son diferencias
relativamente pequeas, es perfectamente posible programar el videojuego para que
se compile sin problemas en cualquiera de estas plataformas.
Pero el videojuego no solo tiene que compilarse, tiene que llegar al usuario
de alguna forma que le permita instalarlo sin dicultad. En este captulo veremos
precisamente eso, la construccin de paquetes instalables para algunas plataformas
(MS Windows y GNU/Linux).
A diferencia del resto de actividades de desarrollo los sistemas de empaquetado
de software son completamente diferentes de unas plataformas a otras, e incluso hay
alternativas muy diferentes en una misma plataforma. Nos centraremos en las que
consideramos con mayor proyeccin.
703
[704] CAPTULO 25. EMPAQUETADO Y DISTRIBUCIN
Pre-requisitos
Antes de empezar el empaquetado tenemos que tener disponible una versin del
proyecto correctamente compilada en MS Windows y probada. Puede utilizarse el
conjunto de compiladores de GNU para Windows (MinGW) o Visual Studio. No es
importante para esta seccin cmo se generan los ejecutables.
En la documentacin en lnea de OGRE 1.8.1 se explica cmo construir proyec-
tos con el SDK como proyecto de Visual Studio 2010 Express (Ver http://www.
ogre3d.org/tikiwiki/tiki-index.php, apartado Installing the Ogre SDK).
Es preciso puntualizar que no es necesario copiar el ejecutable al directorio de binarios
de OGRE. Tan solo hay que garantizar que:
1 Una comparativa simple con un subconjunto de las alternativas disponibles puede verse en http:
//en.wikipedia.org/wiki/List_of_installation_software.
2 Ver http://msdn.microsoft.com/en-us/library/ee721500(v=vs.100).aspx
25.1. Empaquetado y distribucin en Windows [705]
MS Windows Installer
Windows Installation XML Toolset es el primer proyecto liberado con una licencia
libre por Microsoft en 2004. Inicialmente fue programado por Rob Mensching con el
objetivo de facilitar la creacin de archivos msi y msp sin necesidad de utilizar una
interfaz grca.
WIX utiliza una aproximacin puramente declarativa. En un archivo XML (con
extensin wxs) se describe el producto y dnde se pueden encontrar cada uno de
sus elementos. Este archivo es posteriormente compilado mediante la herramienta
candle.exe para analizar su consistencia interna y generar un archivo intermedio,
con extensin wixobj. Estos archivos intermedios siguen siendo XML aunque su
contenido no est pensado para ser editado o ledo por seres humanos.
Uno o varios archivos wixobj pueden ser procesados por otra herramienta,
denominada light.exe para generar el archivo msi denitivo.
Este ujo de trabajo, muy similar al de la compilacin de programas, es el ms
simple de cuantos permite WIX, pero tambin va a ser el ms frecuente. Adems
de esto, WIX incluye herramientas para multitud de operaciones de gran utilidad en
proyectos grandes.
WIX sigue la losofa de que el paquete de distribucin es parte del desarrollo del
programa. Por tanto el archivo wxs que describe el paquete debe escribirse de forma
incremental junto al resto del programa.
Desgraciadamente en muchas ocasiones nos encontramos con que la tarea de
empaquetado no se ha considerado durante el desarrollo del proyecto. Ya sea porque se
ha utilizado otra plataforma para el desarrollo del videojuego, o por simple desidia, lo
cierto es que frecuentemente llegamos al nal del proceso de desarrollo sin ni siquiera
habernos planteado la construccin del paquete. Tambin en esos casos WIX aporta
una solucin, mediante la herramienta heat.exe. Esta herramienta puede construir
fragmentos del archivo wxs mediante el anlisis de directorios o archivos que van a
incluirse en el paquete.
Una de las formas ms sencillas de generar un paquete instalable es instalar los
binarios y los archivos de medios necesarios durante la ejecucin en un directorio
independiente. El archivo generado no especialmente legible ni siquiera est completo,
pero puede usarse como punto de partida.
Por ejemplo, si un programa instala todos los ejecutables y archivos auxiliares en
el subdirectorio bin podemos generar el archivo wxs inicial con:
Sin embargo, para mayor legibilidad en este primer ejemplo vamos a construir
el archivo desde cero. Volvamos al ejemplo ogre-hello. Una vez generados los
ejecutables en modo Release tenemos un subdirectorio Release que contiene todos
los ejecutables, en este caso OgreHello.exe. Por s solo no puede ejecutarse,
25.1. Empaquetado y distribucin en Windows [707]
necesita las bibliotecas de OGRE, los archivos del subdirectorio media y los archivos
de conguracin resources.cfg y plugins.cfg que se incluyen con el cdigo
fuente. Es bastante habitual copiar las DLL al subdirectorio Release para poder
probar el ejemplo.
La estructura del archivo wxs es la siguiente:
La etiqueta Wix se usa para envolver toda la descripcin del instalador. Dentro
de ella debe haber un Product que describe el contenido del archivo msi. Todos los
productos tienen los atributos Id y UpgradeCode que contienen cada uno un GUID
(Globally Unique Identier). Un GUID (o UUID) es un nmero largo de 128 bits, que
puede generarse de manera que sea muy poco probable que haya otro igual en ningn
otro sitio. Se utilizan por tanto para identicar de manera unvoca. En este caso se
identica el producto y el cdigo de actualizacin.
Todas las versiones del producto tienen el mismo Id, mientras que para cambio
de major version se genera otro nuevo GUID para UpgradeCode (al pasar de
1.x a 2.x, de 2.x a 3.x, etc.). El UpgradeCode es utilizado por Windows Installer
para detectar cuando debe eliminar la versin vieja para reemplazarla por la nueva o
simplemente reemplazar determinados archivos. Para generar un GUID nuevo puede
utilizarse, por ejemplo, un generador de GUIDs en lnea, como http://www.
guidgenerator.com/.
Los cdigos numricos de Language y Codepage se corresponden a los valores in-
dicados por microsoft en http://msdn.microsoft.com/en-us/library/
Aa369771.aspx. En nuestro caso el idioma elegido es el espaol de Espaa y la
pgina de cdigos es la correspondiente al alfabeto latino.
Dentro de un producto pueden declararse multitud de aspectos sobre el instalador.
Lo primero es denir el paquete MSI que se va a generar:
1 <Package Id=*
2 Description=Instalador de OgreHello 0.1
3 Manufacturer=UCLM
4 InstallerVersion=100
5 Languages=3082
6 Compressed=yes
7 SummaryCodepage=1252 />
C25
Cada nuevo paquete generado tendr su propio GUID. Por este motivo WIX
permite simplicar la construccin con el cdigo especial * que indica que debe
ser auto-generado por el compilador. El atributo InstallerVersion indica la
versin mnima de Windows Installer requerida para poder instalar el paquete. Si no
se implementan aspectos avanzados, siempre ser 100, que corresponde a la versin
1.0.
[708] CAPTULO 25. EMPAQUETADO Y DISTRIBUCIN
En este caso hemos creado un directorio OgreHello 1.0 dentro del directorio
estndar para los archivos de programa (normalmente C:\Program Files).
Dentro de este directorio hemos hecho un subdirectorio media que contendr los
archivos de medios (recursos).
Ahora podemos aadir componentes dentro de estos directorios. Cada componente
es un conjunto de archivos muy fuertemente relacionado, hasta el punto de que no
tiene sentido actualizar uno sin actualizar los dems. En general se tiende hacia
componentes lo ms pequeos posibles (un solo archivo), con objeto de que se puedan
hacer parches ms pequeos. Por ejemplo, el ejecutable principal es un componente,
pero por simplicidad aadiremos tambin los archivos de conguracin.
Cada componente tiene tambin un GUID que lo identica. En este caso contiene
tres archivos, el ejecutable y los dos archivos de conguracin. Adems, para el
ejecutable creamos tambin un atajo en el men de inicio de Windows.
El atributo KeyPath de los archivos se pone a yes solamente para un archivo
dentro del componente. Este archivo ser utilizado por Windows Installer para
identicar si el componente est previamente instalado.
25.1. Empaquetado y distribucin en Windows [709]
Para simplicar el resto del paquete vamos a meter todas las bibliotecas de OGRE
en un nico componente. En un caso real probablemente convendra dividirlo para
permitir parches ms pequeos en caso de que no afecten a todas las bibliotecas de
OGRE.
1 <DirectoryRef Id=ProgramMenuDir>
2 <Component Id="ProgramMenuDir" Guid="b16ffa2a-d978-4832-a5f2
-01005e59853c">
3 <RemoveFolder Id=ProgramMenuDir On=uninstall />
4 <RegistryValue Root=HKCU Key=Software\[Manufacturer]\[
ProductName] Type=string Value= KeyPath=yes /> C25
5 </Component>
6 </DirectoryRef>
> cd release
> candle.exe ..\wix\ogre-hello.wxs
> light.exe ogre-hello.wixobj
Por ejemplo, para aadir un formulario que pregunta al usuario dnde se desea
instalar tan solo tenemos que cambiar la seccin Feature de este modo:
12 </Feature>
Tan solo hemos aadido dos referencias externas y algunos atributos, como
Title, Description, Display y ConfigurableDirectory. El resultado
es el que se muestra en la gura.
Figura 25.1: Ejemplo de dilogos generados por WIX.
$ wget https://bitbucket.org/arco_group/ogre-hello/downloads/ogre-hello-0.1.tar.
gz
$ tar xvfz ogre-hello-0.1.tar.gz
$ cd ogre-hello-0.1
ogre-hello-0.1$
DEBEMAIL="juan.nadie@gmail.com"
DEBFULLNAME="Juan Nadie"
export DEBEMAIL DEBFULLNAME
Estas variables son tiles para otras operaciones relacionadas con las construccin
de paquetes, por lo que es buena idea aadirlas al chero $HOME/.bashrc para
futuras sesiones. Despus de eso, ejecuta dh_make:
manpages
ogre-hello-0.1$ dh_make -f ../ogre-hello-0.1.tar.gz
Type of package: single binary, indep binary, multiple binary, library, kernel Todo chero ejecutable en el PATH
module, kernel patch? debera tener su pgina de ma-
[s/i/m/l/k/n] s
nual. Las pginas de manual es-
Maintainer name : Juan Nadie tn escritas en groff, pero es mu-
Email-Address : juan.nadie@gmail.com cho ms sencillo crearlas a par-
Date : Wed, 24 Apr 2013 12:59:40 +0200 tir de formatos ms sencillos como
Package Name : ogre-hello SGML (Standard Generalized Mar-
Version : 0.1
License : blank kup Language), XML, asciidoc o
Type of Package : Single reStructuredText y convertirlas en
Hit <enter> to confirm: el momento de la construccin del
Done. Please edit the files in the debian/ subdirectory now. You should also paquete.
check that the ogre-hello Makefiles install into $DESTDIR and not in / .
debian/control
Source
Es el nombre del paquete fuente, normalmente el mismo nombre del programa
tal como lo nombr el autor original (upstream author).
Section
La categora en la que se clasica el paquete, dentro de una lista establecida,
vea http://packages.debian.org/unstable/.
Priority
La importancia que tiene el paquete para la instalacin. Las categoras son:
required, important, standard, optional y extra. Este paquete, al ser un juego y
no causar ningn conicto, es optional. Puede ver el signicado de cada una
de estas prioridades en http://www.debian.org/doc/debian-policy/
ch-archive.html.
Maintainer
Es el nombre y la direccin de correo electrnico de la persona que mantiene el
paquete actualmente. C25
Build-Depends
Es una lista de nombres de paquetes (opcionalmente con las versiones requeri-
das) que son necesarios para compilar el presente paquete. Estos paquetes deben
estar instalados en el sistema (adems del paquete build-essential) para
poder construir los paquetes binarios.
Standards-Version
La versin ms reciente de la policy que cumple el paquete.
[716] CAPTULO 25. EMPAQUETADO Y DISTRIBUCIN
Package
El nombre del paquete binario, que dar lugar al archivo .deb. Este nombre
debe cumplir una serie de reglas que dependen del lenguaje en que est escrito
el programa, su nalidad, etc.
Architecture
Indica en qu arquitecturas hardware puede compilar el programa. Aparte de las
arquitecturas soportadas por Debian hay dos identicadores especiales:
all, indica que el mismo programa funciona en todas las plataformas.
Normalmente se trata de programas escritos en lenguajes interpretados o
bien cheros multimedia, manuales, etc, que no requieren compilacin. Se
dice que son paquetes independientes de arquitectura.
any, indica que el programa debe ser compilado pero est soportado en
todas las arquitecturas.
Depends
Es una lista de los nombres de los paquetes necesarios para instalar el paquete
y ejecutar los programas que contiene.
debian/changelog
Contiene una descripcin breve de los cambios que el mantenedor hace a los
cheros especcos del paquete (los que estamos describiendo). En este chero no
se describen los cambios que sufre el programa en s; eso corresponde al autor del
programa y normalmente estarn en un chero CHANGES (o algo parecido) dentro del
.tgz que descargamos.
El siguiente listado muestra el chero changelog generado por dh_make:
Cada vez que el mantenedor haga un cambio debe crear una nueva entrada como
esa al comienzo del chero. Fjese que la versin del paquete est formada por el
nmero de versin del programa original ms un guin y un nmero adicional que
indica la revisin del paquete debian. Si el mantenedor hace cambios sobre el paquete
manteniendo la misma versin del programa ir incrementando el nmero tras el
guin. Sin embargo, cuando el mantenedor empaqueta una nueva versin del programa
(supongamos la 0.2 en nuestro ejemplo) el nmero tras el guin vuelve a empezar
desde 1.
Como se puede apreciar, la primera versin del paquete debera cerrar (solucionar)
un bug existente. Ese bug (identicado por un nmero) corresponde al ITP que el
mantenedor debi enviar antes de comentar con el proceso de empaquetado.
Cuando una nueva versin del paquete se sube a los repositorios ociales, las
sentencias Closes son procesadas automticamente para cerrar los bugs noticados
a travs del DBTS. Puedes ver ms detalles en http://www.debian.org/doc/
debian-policy/ch-source.html#s-dpkgchangelog
debian/copyright
Este chero debe contender toda la informacin sobre el autor del programa y las
licencias utilizadas en cada una de sus partes (si es que son varias). Tambin debera
indicar quin es el autor y la licencia de los cheros del paquete debian. El chero
generado por dh_make contiene algo similar a:
El chero debera contener una seccin (que comienza con Files:) por cada
autor y licencia involucrados en el programa. La seccin Files: debian/* ya
est completa y asume que el mantenedor va a utilizar la licencia GPL-2 y superior
para los cheros del paquete.
Debemos modicar este chero para incluir las licencias especcas del programa
que estamos empaquetando. Las secciones nuevas seran:
debian/rules
Por fortuna, actualmente el programa debhelper (o dh) hace la mayor parte del
trabajo aplicando reglas por defecto para todos los objetivos. Solo es necesario sobre-
escribir (reglas override_ dichos objetivos en el chero rules si se requiere un
comportamiento especial.
El listado anterior contiene una pequea diferencia respecto al generado por
dh_make que consiste en la adicin del parmetro -with quilt. El programa
quilt es una aplicacin especializada en la gestin de parches de un modo muy
cmodo.
build-depends Una vez contamos con los cheros bsicos se puede proceder a una primera
compilacin del paquete. Para ello utilizamos el programa dpkg-buildpackage.
Para construir el paquete de- El siguiente listado muestra el comando y parte del resultado. Se omiten algunas partes
be tener instalados todos los dado que la salida es bastante verbosa:
paquetes listados en los cam-
pos Build-Depends y
Build-Depends-Indep, ogre-hello-0.1$ dpkg-buildpackage -us -us
adems del paquete dpkg-buildpackage: source package ogre-hello
build-essential. dpkg-buildpackage: source version 0.1-1
dpkg-buildpackage: source changed by Juan Nadie <Juan.Nadie@gmail.com>
dpkg-buildpackage: host architecture amd64
dpkg-source --before-build ogre-hello-0.1
dpkg-source: info: applying make-install
fakeroot debian/rules clean
dh clean --with quilt
dh_testdir
dh_auto_clean
make[1]: Entering directory /home/david/repos/ogre-hello/ogre-hello-0.1
rm -f helloWorld *.log *.o *~
make[1]: Leaving directory /home/david/repos/ogre-hello/ogre-hello-0.1 C25
dh_quilt_unpatch
dpkg-source -b ogre-hello-0.1
dpkg-source: info: using source format 3.0 (quilt)
dpkg-source: info: applying make-install
dpkg-source: info: building ogre-hello using existing ./ogre-hello_0.1.orig.tar.
gz
dpkg-source: info: building ogre-hello in ogre-hello_0.1-1.debian.tar.gz
dpkg-source: info: building ogre-hello in ogre-hello_0.1-1.dsc
debian/rules build
dh build --with quilt
[...]
dh_testdir
[720] CAPTULO 25. EMPAQUETADO Y DISTRIBUCIN
ogre-hello_0.1-1.dsc
Es una descripcin del paquete fuente, con una serie de campos extrados del -
chero debian/control adems de los checksums para los otros dos cheros
que forman el paquete fuente: el .orig.tar.gz y el .debian.tar.gz.
ogre-hello_0.1-1_amd64.changes
Este chero es utilizado por el archivo (el repositorio) de paquetes de Debian
y se utiliza en la subida (upload) de paquetes por parte de los DD (Debian
Developer). Contiene checksum para los otros cheros y, en una versin ocial,
debera estar rmado digitalmente para asegurar que el paquete no ha sido
manipulado por terceros.
ogre-hello_0.1-1.debian.tar.gz
Es un archivo que contiene todos los cambios que el mantenedor del paquete ha
hecho respecto al fuente del programa original, es decir, el directorio debian
principalmente.
ogre-hello_0.1-1_amd64.deb
. Es el paquete binario resultado de la compilacin (en concreto para la
arquitectura AMD-64) que puede ser instalado con dpkg o indirectamente con
los gestores de paquetes apt-get, aptitude u otros.
25.2. Empaquetado y distribucin en GNU/Linux [721]
Este aviso indica que el paquete no contiene nada aparte de los cheros aportados
por el propio sistema de construccin. Veamos qu contiene realmente:
*** Contents:
drwxr-xr-x root/root 0 2013-04-29 14:05 ./
drwxr-xr-x root/root 0 2013-04-29 14:05 ./usr/
drwxr-xr-x root/root 0 2013-04-29 14:05 ./usr/share/
drwxr-xr-x root/root 0 2013-04-29 14:05 ./usr/share/doc/
drwxr-xr-x root/root 0 2013-04-29 14:05 ./usr/share/doc/ogre-hello/
-rw-r--r-- root/root 2348 2013-04-29 13:15 ./usr/share/doc/ogre-hello/
copyright
-rw-r--r-- root/root 175 2013-04-29 13:15 ./usr/share/doc/ogre-hello/
changelog.Debian.gz
Se debe a que no est permitido modicar los cheros extrados del tarball del
autor del programa. Es necesario crear un parche. Un parche es un chero que contiene
los cambios que es preciso realizar en otro chero para lograr un resultado concreto,
indicando el nmero de lnea y otro contenido que ayuda a las herramientas a localizar
el lugar correcto. Por suerte, el propio error nos ofrece una forma muy sencilla que
crear este parche:
Estos parches sern aplicados por el programa quilt, que se invocar automti-
camente al usar dpkg-buildpackage. Veamos las diferencias:
Aunque ahora el paquete contiene los cheros deseados y los instalar en su ruta
correcta, pero an tiene algunos problemas ya que el programa no estaba pensado para
trabajar con esta estructura de cheros:
Con esto ltimo tendremos un total de tres parches y el programa ser funcional
tras la instalacin. Quedan an dos problemas (no tan graves) por resolver segn
informa lintian. El primero (binary-without-manpage) indica que todo chero
ejecutable en el PATH debera tener una pgina de manual. El segundo (hardening-
no-relro) indica que el programa debera estar compilado con determinadas opciones C25
que evitan problemas comunes.
[724] CAPTULO 25. EMPAQUETADO Y DISTRIBUCIN
Las cuatro ltimas lneas de la salida de uscan conrman que tenemos empaque-
tada la ltima versin y que adems tenemos en disco el tarball del autor.
Una vez que disponemos de la nueva versin del programa, debemos crear una
nueva entrada en el chero debian/changelog (que se puede automatizar en
parte con el programa dch). Para aplicar los cambios del nuevo tarball puedes
utilizar el programa uupdate aunque uscan puede encargarse tambin de esto.
A continuacin debe comprobar que la construccin de la nueva versin es correcta
poniendo especial atencin a la aplicacin de los parches sobre la nueva versin del
cdigo fuente.
6 Como ejemplo vea http://qa.debian.org/developer.php?login=
pkg-games-devel%40lists.alioth.debian.ora
25.3. Otros formatos de paquete [725]
C25
Representacin Avanzada
Captulo 26
Sergio Prez Camacho
Jorge Lpez Gonzlez
Csar Mora Castro
L
os sistemas de partculas suponen una parte muy importante del impacto
visual que produce una aplicacin 3D. Sin ellos, las escenas virtuales se
compondran bsicamente de geometra slida y texturas. Los elementos que
se modelan utilizando estos tipos de sistemas no suelen tener una forma denida,
como fuego, humo, nubes o incluso aureolas simulando ser escudos de fuerza. En las
siguientes secciones se van a introducir los fundamentos de los sistemas de partculas
y billboards, y cmo se pueden generar utilizando las mltiples caractersticas que
proporciona Ogre.
26.1. Fundamentos
Para poder comprender cmo funcionan los sistemas de partculas, es necesario
conocer primero el concepto de Billboard, ya que dependen directamente de estos. A
Figura 26.1: Ejemplo tpico de vi- continuacin se dene qu son los Billboard, sus tipos y los conceptos matemticos
deojuego que utiliza billboards para bsicos asociados a estos, para despues continuar con los sistemas de partculas.
modelar la vegetacin. Screenshot
tomado del videojuego libre Stunt
Rally. 26.1.1. Billboards
Billboard signica, literalmente, valla publicitaria, haciendo alusin a los grandes
carteles que se colocan cerca de las carreteras para anunciar un producto o servicio.
Aquellos juegos en los que aparece el nombre de un jugador encima de su personaje,
siempre visible a la cmara, utilizan billboards. Los rboles de juegos de carrera que
daban la sensacin de ser un dibujo plano, utilizan Billboards.
727
[728] CAPTULO 26. REPRESENTACIN AVANZADA
u u u' u
a) b) c) n'
n n
n
r r
Figura 26.2: En a) se puede observar un Billboard con el vector up y el vector normal. En b) y c) se muestran
ejemplos de cmo calcular uno de los vectores dependiendo del otro en el caso de no ser perpendiculares.
r =uv
u = n r
Estos son los tipos ms simples de billboard. Su vector up u siempre coincide con
el de la cmara, mientras que el vector normal n se toma como el inverso del vector
normal de la cmara (hacia donde la cmara est mirando). Estos dos vectores son
siempre perpendiculares, por lo que no es necesario recalcular ninguno con el mtodo
anterior. La matriz de rotacin de estos billboard es la misma para todos.
Por lo tanto estos billboard siempre estarn alienados con la pantalla, aun cuando
la cmara realice giros sobre el eje Z (roll), como se puede apreciar en la Figura 26.3.
Esta tcnica tambin puede utilizarse para sprites circulares como partculas.
a) b)
Cmara Cmara
Figura 26.5: Billboards con el mismo vector normal que la cmara en a), mientras que en b) son orientados
al punto de vista.
Billboard axial
Impostors
Nubes de Billboards
Billboards individuales
Tambin es posible controlar la En Ogre, no existe un elemento billboard por s slo que se pueda representar.
representacin individual de cada Estos deben pertenecer a un objeto de la clase BillboardSet. Esta clase se encarga de
Billboard dentro de un Billboard- gestionar los diferentes billboards que estn contenidos en ella.
Set, pero la penalizacin en rendi-
miento es mucho mayor. En la ma-
yora de los casos es ms eciente
crear distintos BillboardSets.
[732] CAPTULO 26. REPRESENTACIN AVANZADA
De las lneas 7-9 se crean tantos Billboard como capacidad se di al BillboardSet
en el momento de su creacin. A cada uno de ellos se le indica la posicin
relativa al
SceneNode al que pertenece el BillboardSet. Por ltimo, en las lneas 11-13 se crea
un nodo y se adjunta el BillboardSet a l.
26.2. Uso de Billboards [733]
C26
mismo sitio. Esto es interesante si se quiere dejar una estela, por ejemplo, de humo.
Si se desea que las partculas ya creadas se trasladen con el nodo al que pertenece
el sistema, se puede indicar que el posicionamiento se haga referente al sistema de
coordenadas local.
Los sistemas de partculas deben tener siempre una cantidad lmite de estas, o
quota. Una vez alcanzado esta cantidad, el sistema dejar de emitir hasta que
se eliminen algunas de las partculas antiguas. Las partculas tienen un lmite
de vida congurable para ser eliminadas. Por defecto, este valor de quota es
10, por lo que puede interesar al usuario indicar un valor mayor en la plantilla.
Eciencia ante todo Ogre necesita calcular el espacio fsico que ocupa un sistema de partculas (su
Los sistemas de partculas pueden BoundingBox) de forma regular. Esto es computacionalmente costoso, por lo que
rpidamente convertirse en una par- por defecto, deja de recalcularlo pasados 10 segundos. Este comportamiento se
te muy agresiva que requiere mucho puede congurar mediante el mtodo ParticleSystem::setBoundsAutoUpdated(), el
tiempo de cmputo. Es importante
dedicar el tiempo suciente a opti-
cual recibe como parmetro los segundos que debe recalcular la BoundingBox. Si
mizarlos, por el bien del rendimien- se conoce de antemano el tamao aproximado del espacio que ocupa un sistema de
to de la aplicacin. partculas, se puede indicar a Ogre que no realice este clculo, y se le indica el tamao
jo mediante el mtodo ParticleSystem::setBounds(). De esta forma se ahorra mucho
tiempo de procesamiento. Se puede alcanzar un compromiso indicando un tamao
inicial aproximado, y luego dejando a Ogre que lo recalcule durante poco tiempo
pasados algunos segundos desde la creacin del sistema.
A continuacin se describen los dos elementos bsicos que denen los sistemas de
partculas en Ogre: los emisores y los efectores.
26.3.1. Emisores
Los emisores denen los objetos que literalmente emiten las partculas a la escena.
Los distintos emisores que proporciona Ogre son:
Puntos: point. Todas las partculas son emitidas desde un mismo punto.
Caja: box. Las partculas son emitidas desde cualquier punto dentro de un
volumen rectangular.
Cilindro: cylinder. Las partculas son emitidas desde un volumen cilndrico
denido.
Elipsoide: ellipsoid. Las partculas se emiten desde un volumen elipsoidal.
Supercie de elipsoide: hollow elipsoid. Las partculas se emiten desde la
supercie de un volumen elipsoidal.
Anillo: ring. Las partculas son emitidas desde los bordes de un anillo.
26.3.2. Efectores
Los efectores o affectors realizan cambios sobre los sistemas de partculas. Estos
cambios pueden ser en su direccin, tiempo de vida, color, etc. A continuacin se
explican cada uno de los efectores que ofrece Ogre.
LinearForce: aplica una fuerza a las partculas del sistema. Esta fuerza
se indica mediante un vector, cuya direccin equivale a la direccin de la
fuerza, y su mdulo equivale a la magnitud de la fuerza. La aplicacin de
una fuerza puede resultar en un incremento enorme de la velocidad, por lo
que se dispone de un parmetro, force_application para controlar esto. El
valor force_application average ajusta el valor de la fuerza para estabilizar
la velocidad de las partculas a la media entre la magnitud de la fuerza y la
velocidad actual de estas. Por el contrario, force_application add deja que la
velocidad aumente o se reduzca sin control.
ColourFader: modica el color de una partcula mientras sta exista. El
valor suministrado a este modicador signica cantidad de cambio de una
componente de color en funcin del tiempo. Por lo tanto, un valor de red -0.5
decrementar la componente del color rojo en 0.5 cada segundo.
ColourFader2: es similar a ColourFader, excepto que el modicador puede ColourFader
cambiar de comportamiento pasada una determinada cantidad de tiempo. Por Un valor de -0.5 no quiere decir que
ejemplo, el color de una partcula puede decrementarse suavemente hasta el se reduzca a la mitad cada segundo,
50 % de su valor, y luego caer en picado hasta 0. y por lo tanto, nunca alcanzar el
valor de 0. Signica de al valor
de la componente (perteneciente al
ColourInterpolator: es similar a ColourFader2, slo que se pueden especicar intervalo de 0 a 1) se le restar 0.5.
hasta 6 cambios de comportamiento distintos. Se puede ver como una generali- Por lo tanto a un valor de blanco
zacin de los otros dos modicadores. (1) se reducir a negro (0) en dos
segundos.
Scaler: este modicador cambia de forma proporcional el tamao de la partcula
en funcin del tiempo.
Rotator: rota la textura de la partcula por bien un ngulo aleatorio, o a una
velocidad aleatoria. Estos dos parmetros son denidos dentro de un rango (por
defecto 0).
ColourImage: este modicador cambia el color de una partcula, pero estos
valores se toman de un chero imagen (con extensin .png, .jpg, etc.). Los
valores de los pxeles se leen de arriba a abajo y de izquierda a derecha. Por
lo tanto, el valor de la esquina de arriba a la izquierda ser el color inicial, y el
de abajo a la derecha el nal.
26.3. Uso de Sistemas de Partculas [737]
A continuacin se declaran tantos emisores y modicadores como se deseen. En
las lneas 8-19 se declara un emisor del tipo anillo. Los parmetros especicados para
C26
Y an hay ms!
este emisor son:
Existen multitud de parmetros pa-
ra congurar los sistemas de part- angle: dene el ngulo de apertura con el que las partculas salen disparadas.
culas, emisores y modicadores en
Ogre. En la API ocial se detallan direction: indica la direccin de salida d e las partculas, teniendo en cuenta
todos y cada uno de estos parme- el ngulo. En realidad, el par direccin-ngulo dene un cono por el cual las
tros.
partculas se crean.
[738] CAPTULO 26. REPRESENTACIN AVANZADA
estudio decidi contratar programadores grcos para que, comandados por Edwin
Catmull, empezaran a informatizar la industria de los efectos especiales. Casi nada.
Aquel grupo de pioneros se embarc en varios proyectos diferentes, logrando que
cada uno de ellos desembocara en cosas muy interesantes. Uno de estos proyectillos
acab siendo el germen de lo que ms adelante se conocera como Pixar Animation
Studios. Y fue en esta compaa donde, durante el desarrollo del API abierto RISpec
[740] CAPTULO 26. REPRESENTACIN AVANZADA
26.4.2. Y qu es un Shader?
Una de las deniciones clsicas de shader los muestra como: piezas de cdigo,
que implementan un algoritmo para establecer como responde un punto de una
supercie a la iluminacin. Es decir sirven para establecer el color denitivo de
los pixeles que se mostrarn en pantalla.
Gestion-Recursos Como veremos esta denicin ya no es del todo correcta. Los shader actuales
En algn momento comprenderis
cumplen muchas ms funciones, ya que pueden encargarse de modicar todo tipo
que hacer videojuegos, al nal, no de parmetros (posicin de vrtices, color de pixeles e incluso generar geometra en
es ms que gestionar recursos y ba- los ms avanzados). Hoy por hoy una denicin de shader ms acertada podra ser:
lancear calidad de imagen con rapi- Un conjunto de software que especica la forma en que se renderiza un conjunto
dez de ejecucin. Por eso conocer el
funcionamiento de un motor gr-
de geometra, denicin extraida del libro GPU Gems [33], ideal para aprender
co, conocimientos altos de ingenie- sobre tcnicas avanzadas de programacin grca.
ra del software y gestin de recur-
sos en un programa software son de Desde un punto de vista de alto nivel los shader nos permiten tratar el estado de
las habilidades ms demandadas. renderizado como un recurso, lo cual los convierte en una herramienta extremadamen-
te poderosa, permitiendo que el dibujado de una aplicacin, es decir, la conguracin
del dispositivo que se encarga de ello sea casi completamente dirigido por recursos
(de la misma forma en que la geometra y las texturas son recursos externos a la apli-
cacin). Facilitando de esta manera su reusabilidad en mltiples proyectos.
Las deniciones, en mi opinin, suelen ser siempre algo esotricas para los
profanos en una materia, por ello la mejor manera de terminar de explicar qu son
y cmo funcionan estas pequeas piezas de cdigo es repasando el funcionamiento
del pipeline grco: desde la transmisin de los vrtices a la GPU, hasta la salida por
pantalla de la imagen generada.
El motivo por el que se usan tarjetas grcas busca que la CPU delegue en la
medida de lo posible la mayor cantidad de trabajo en la GPU, y que as la CPU
quede liberada de trabajo. Delegar ciertas tareas en alguien o algo especializado en
algo concreto es siempre una buena idea. Existen adems dos claves de peso que
justican la decisin de usar un hardware especco para las tareas grcas. La
primera es que las CPU son procesadores de propsito general mientras que la tarea
de procesamiento de grcos tiene caractersticas muy especcas que hacen fcil
extraer esa funcionalidad aparte. La segunda es que hay mercado para ello, hoy en da
nos encontramos con que muchas aplicaciones actuales (videojuegos, simulaciones,
diseo grco, etc...) requieren de rendering interactivo con la mayor calidad posible.
Conectividad de vrtices
Fragmentos Fragmentos
Coloreados
Ensamblado Interpolacin,
Transformacin Operaciones
de Primitivas Texturizado
del Vrtice de Rasterizacin
y Rasterizacin y Coloreado
Vrtices Pxeles
Vrtices Actualizados
Transformados
Tipos de Shader
Aplicacin 3D
API 3D:
OpenGL o
Direct3D
Lmite CPU - GPU
Comandos
Primitivas Primitivas
de la GPU Flujo de ndices Flujo de localizaciones Actualizaciones
ensambladas ensambladas
y flujo de datos. de vrtices de pxeles de pxeles
Procesador
GPU Ensamblado Rasterizacin Operaciones
Programable Framebuffer
Front End de Primitivas e Interpolacin de Raster
de Geometra
Cada vertex shader afecta a un slo vrtice, es decir, se opera sobre vrtices
individuales no sobre colecciones de ellos. A su vez, tampoco se tiene acceso
a la informacin sobre otros vrtices, ni siquiera a los que forman parte de su
propia primitiva.
Al igual que los vertex shader, slo puede procesar un fragmento cada vez y
no puede inuir sobre, ni tener acceso a, ningn otro fragmento.
10 tFOutput OUT;
11 // Se aplica el color al fragmento
12 OUT.color = color;
13
14 return OUT;
15 }
Vertex Skinning
Los vrtices de una supercie, al igual que el cuerpo humano, son movidos a causa
de la inuencia de una estructura esqueltica. Como complemento, se puede aplicar
una deformacin extra para simular la dinmica de la forma de un msculo.
En este caso el shader ayuda a establecer cmo los vrtices se ven afectados por
el esqueleto y aplica las transformaciones en consecuencia.
Los vrtices pueden ser desplazados, por ejemplo verticalmente, en funcin de los
resultados ofrecidos por un algoritmo o mediante la aplicacin de un mapa de alturas
(una textura en escala de grises), consiguiendo con ello, por ejemplo, la generacin de
un terreno irregular.
Screen Effects
Los shader pueden ser muy tiles para lograr todo tipo de efectos sobre la imagen
ya generada tras el renderizado. Gracias a las mltiples pasadas que se pueden aplicar,
es posible generar todo tipo de efectos de post-procesado, del estilo de los que se usan
en las pelculas actuales.
Los fragment shader realizan el renderizado en una textura temporal (un frame-
buffer alternativo) que luego es procesada con ltros antes de devolver los valores de
color.
C26
Light and Surface Models
una altsima calidad, aunque habra que tener cuidad con su uso. El conseguir, por
ejemplo, luz por pixel implicara unas operaciones bastante complejas por cada pixel
que se vaya a dibujar en pantalla, lo cual, contando los pixeles de una pantalla, pueden
ser muchas operaciones.
Figura 26.17: Diferencia entre Per-Vertex Lighting (izquierda) y Per-Pixel Lighting (derecha).
Gracias a estas tcnicas es posible lograr que la visualizacin de los modelos sea
mucho mejor, sin incrementar la calidad del modelo. Ejemplo de esta tcnica sera el
Normal mapping, donde a partir de una textura con informacin sobre el valor de las
normales (codicada cada normal como un valor RGB, correspondiente al XYZ de
la misma) a lo largo de toda su supercie, permite crear la ilusin de que el modelo
cuenta con ms detalle del que realmente tiene.
Non-Photorealistic Rendering
Antes de empezar debemos tener claro que para usar los shader en Ogre debemos
contar con dos tipos de cheros. Por un lado tendremos los programas, o archivos .cg,
en los que se encontrarn los shaders escritos en Cg, y por otro lado tendremos los
archivos de efectos, o materiales, que denirn cmo se usan nuestros programas
Una vez sabido esto, el siguiente paso debera ser dejar claro donde se han colocar Log de Ogre
los cheros correspondientes a los shader, para que as puedan ser usados por Ogre.
Estos se deben colocar en la ruta: En el chero de log podremos en-
contrar informacin sobre los per-
les y funcionalidad soportada por
/[directorio_proyecto_ogre]/media/materials/programs nuestra tarjeta grca, as cmo in-
formacin sobre posibles errores en
la compilacin de nuestros materia-
Si queremos usar otro directorio deberemos indicarlo en el archivo resources.cfg, les o shaders.
bajo la etiqueta [popular] por ejemplo (hay que tener cuidado sin embargo con lo que
se coloca bajo esa etiqueta en un desarrollo serio).
[Popular]
...
FileSystem=../../media/materials/programs/mis_shaders
...
Por otro lado, es conveniente tener a mano el chero de log que genera Ogre en
cada ejecucin, para saber por qu nuestro shader hace que todo aparezca blanco (o
lo que es lo mismo por qu ha fallado su compilacin?).
Por ltimo, antes de denir el material en si, hay que indicar tambin a los shader
cuales son aquellos parmetros que Ogre les pasar. En este caso slo pasamos la
matriz que usaremos para transformar las coordenadas de cada vrtice a coordenadas
de cmara. Es importante denir esta informacin al principio del chero de material.
26.5. Desarrollo de shaders en Ogre [753]
Para este caso, el material quedara tal y como se ve en el listado 26.8. Y queda
claro cmo en la pasada se aplican los dos shader.
Declaracin shaders
Los dos programas que denen nuestros primeros fragment y vertex shader
Para ms informacin sobre la aparecen en los listados 26.9 y 26.10, y los dos deberan incluirse en el chero
declaracin de los shader, se-
ra buena idea dirigirse al ma- rstshaders.cg (o en el archivo que queramos), tal y como indicamos en el material.
nual en: http://www.ogre3d.org/-
docs/manual/manual_18.html
[754] CAPTULO 26. REPRESENTACIN AVANZADA
El vertex shader recibe cmo nico parmetro del vrtice su posicin, esta
es transformada a coordenadas del espacio de cmara gracias a la operacin que
realizamos con la variable que representa la matriz de transformacin.
Por ltimo se asigna el color primario al vrtice, que se corresponde con su
componente difusa, y que en este caso es verde.
El pipeline debe proveer de algn mecanismo para comunicar a los shader los
valores de aquellos elementos necesarios para conocer el estado de la simulacin (la
posicin de las luces, la delta de tiempo, las matrices de transformacin, etc...). Esto
se consigue mediante el uso de las variable declaradas como uniform. Para Cg, estas
son variables externas, cuyos valores deben ser especicados por otro elemento.
[756] CAPTULO 26. REPRESENTACIN AVANZADA
46 return manual;
47 }
El vertex shader lo nico que tendr que hacer es dejar pasar el vrtice, pero esta
vez, en vez de cambiarle el color dejamos que siga con el que se le ha asignado. Para
ello, en el mtodo que dene el punto de entrada del shader hay que indicarle que
recibe como parametro el color del vrtice.
El fragment shader podemos dejarlo igual, no es necesario que haga nada. Incluso
es posible no incluirlo en el chero de material (cosa que no se puede hacer con los
vertex shader).
El resultado obtenido, debera ser el mostrado en la gura 26.21.
Ahora que sabes cmo modicar el color de los vrtices. Seras capaz
Figura 26.22: Ogro dibujado como de pintar la entidad tpica del Ogro como si de textura tuviera un mapa
si tuviera un mapa de normales en- de normales? Figura 26.22 Tip: Has de usar la normal del vrtice para
cima. conseguirlo.
11 tFOutput OUT;
12 OUT.color = 1 - tex2D(texture, uv);
13 return OUT;
14 }
6 }
Listado 26.22: La composicin del color de la textura en varias posiciones diferentes da lugar
a un efecto borroso
1 // Como si dibujaramos tres veces la textura
2 tFOutput f_tex_blurry(
3 float2 uv : TEXCOORD0,
4 uniform sampler2D texture)
5 {
6 tFOutput OUT;
Figura 26.28: Efecto ondulado. 7 OUT.color = tex2D(texture, uv);
8 OUT.color.a = 1.0f;
9 OUT.color += tex2D(texture, uv.xy + 0.01f);
10 OUT.color += tex2D(texture, uv.xy - 0.01f);
11 return OUT;
12 } C26
Listado 26.23: Este efecto se consigue con una combinacin del efecto blur con la conversin
del color a escala de grises
1 // Dibuja la textura como si estuviera grabada en piedra
2 tFOutput f_tex_emboss(
Figura 26.29: Efecto borroso 3 float2 uv : TEXCOORD0,
(blur). 4 uniform sampler2D texture)
[762] CAPTULO 26. REPRESENTACIN AVANZADA
5 {
6 float sharpAmount = 30.0f;
7
8 tFOutput OUT;
9 // Color inicial
10 OUT.color.rgb = 0.5f;
11 OUT.color.a = 1.0f;
12
13 // Aadimos el color de la textura
14 OUT.color -= tex2D(texture, uv - 0.0001f) * sharpAmount;
15 OUT.color += tex2D(texture, uv + 0.0001f) * sharpAmount;
16
17 // Para finalizar hacemos la media de la cantidad de color de
cada componente
18 // para convertir el color a escala de grises
19 OUT.color = (OUT.color.r + OUT.color.g + OUT.color.b) / 3.0f;
20
21 return OUT;
22 }
34 }
35 }
36 }
37 }
El anterior era un shader muy sencillo, por lo que ahora intentaremos crear
nuestras propias animaciones mediante shaders.
Para ello necesitamos tener acceso a la delta de tiempo. En la pgina ocial1
aparecen listados los diferentes parmetros que Ogre expone para ser accedidos
mediante variables uniform. En este caso, en la denicin del vertex shader realizada
en el material debemos indicar que queremos tener acceso a la delta de tiempo, como
se ve en el Listado 26.26.
Figura 26.31: Resultado de las dos
pasadas. Listado 26.26: Acceso a la variable que expone la delta de tiempo
1 vertex_program VertexPulse cg
2 {
3 source vertex_modification.cg
4 entry_point v_pulse
5 profiles vs_1_1 arbvp1
6
7 default_params
8 {
9 param_named_auto worldViewMatrix worldviewproj_matrix
10 param_named_auto pulseTime time
11 }
12 }
Una vez tenemos accesible el tiempo transcurrido entre dos vueltas del bucle
principal, slo necesitamos pasarlo como parmetro a nuestro shader y usarlo para
nuestros propsitos. En este caso, modicaremos el valor del eje Y de todos los
vrtices del modelo, siguiendo una funcin cosenoidal, como se puede ver en el
Listado 26.32.
8 {
9 tVOutput OUT;
10
11 OUT.position = mul(worldViewMatrix, position);
12 OUT.position.y *= (2-cos(pulseTime));
13 OUT.uv = uv;
14
15 return OUT;
16 }
Sabiendo cmo hacer esto, se tiene la posibilidad de conseguir muchos otros tipos
de efectos, por ejemplo en el siguiente se desplaza cada cara del modelo en la direccin
de sus normales.
Listado 26.28: Uso de la normal y la delta de tiempo para crear un efecto cclico sobre los
vrtices
1 tVOutput v_extrussion(
2 float4 position : POSITION,
3 float4 normal : NORMAL,
4 float2 uv : TEXCOORD0,
5 uniform float pulseTime,
6 uniform float4x4 worldViewMatrix)
7 { Figura 26.33: Figura con sus vrti-
8 tVOutput OUT; ces desplazados en direccin de sus
9 OUT.position = position + (normal * (cos(pulseTime)*0.5f)); normales.
10 OUT.position = mul(worldViewMatrix, OUT.position);
11 OUT.uv = uv;
12
13 return OUT;
14 }
Seras capaz de montar una escena con un plano y crear un shader que
simule un movimiento ondulatorio como el de la Figura 26.34?
Listado 26.29: Ejemplo de declaracin de parmetros para ser usados como uniform por un
shader
1 default_params
2 {
26.6. Optimizacin de interiores [765]
26.6.1. Introduccin
Quiz cabe preguntarse si tiene sentido optimizar el dibujado de escenas ya que
todo eso lo hace la GPU. No es una mala pregunta, teniendo en cuenta que muchos
monitores tienen una velocidad de refresco de 60Hz, para qu intentar pasar de
una tasa de 70 frames por segundo a una de 100?. La respuesta es otra pregunta:
por qu no utilizar esos frames de ms para aadir detalle a la escena? Quiz se
podra incrementar el nmero de tringulos de los modelos, usar algunos shaders
ms complejos o aadir efectos de post-proceso, como antialiasing o profundidad
de campo.
En este curso las optimizaciones se han dividido en dos tipos: optimizacin de
interiores y optimizacin de exteriores. En este tema vamos a ver la optimizacin de
interiores, que consiste en una serie de tcnicas para discriminar qu partes se pintan
de una escena y cules no.
Una escena de interior es una escena que se desarrolla dentro de un recinto cerrado,
como por ejemplo en un edicio, o incluso entre las calles de edicios si no hay
grandes espacios abiertos.
Ejemplos claros de escenas interiores se dan en la saga Doom y Quake, de Id
Software, cuyo ttulo Wolfenstein 3D fue precursor de este tipo de optimizaciones.
Sin el avance en la representacin grca que supuso la divisin del espacio de
manera eciente, estos ttulos jams hubieran sido concebidos. Fue el uso de rboles
BSP (2D y 3D, respectivamente) lo que permiti determinar qu parte de los mapas se
renderizaba en cada momento.
C26
Dado que esta complejidad es demasiado alta para una aplicacin interactiva, se
hace necesario idear alguna forma para reducir el nmero de clculos. Una forma
sencilla es reducir la lista de posibles oclusores. Los tringulos ms cercanos a la
cmara tienen ms posibilidades de ser oclusores que aquellos que estn ms alejados,
ya que estadsticamente estos van a ocupar ms rea de la pantalla. De este modo,
usando estos tringulos como oclusores y los lejanos como ocluidos, se podr reducir
la complejidad hasta casi O(n). Si adems se tiene en cuenta que el frustum de la
cmara tiene un lmite (plano far), se podrn descartar aun ms tringulos, tomando
como ocluidos los que estn ms all que este plano.
Este algoritmo podra beneciarse del clipping y del culling previo de los
tringulos. No es necesario incluir en el algoritmo de oclusores ningn tringulo que
quede fuera del frustum de la vista actual, tampoco los tringulos que formen parte de
las caras traseras se pintarn, ni tendrn que formar parte de los oclusores. La pega es
que hay que realizar clipping y culling por software. Tras esto, se tendran que ordenar
los tringulos en Z y tomar un conjunto de los n primeros, que harn de oclusores
en el algoritmo. Computacionalmente, llevar a cabo todas estas operaciones es muy Recursividad
caro. Una solucin sera hacerlas slo cada algunos frames, por ejemplo cuando la
Como casi todos los algoritmos de
cmara se moviese lo suciente. Mientras esto no pasase, el conjunto de oclusores no construccin de rboles, el BSP es
cambiara, o lo hara de manera mnima. un algoritmo recursivo. Se dice que
una funcin es recursiva cuando se
llama a s misma hasta que se cum-
Listado 26.31: Actualizacin de los oclusores ple una condicin de parada. Una
funcin que se llama a s misma dos
1 const size_t maxOccluders = 300; veces se podr representar con un
2 rbol binario; una que se llame n-
3 newPos = calculateNewCameraPosition(); veces, con un rbol n-ario.
26.6. Optimizacin de interiores [767]
Algoritmo BSP
La solucin pasa por tomar los vrtices del tringulo atravesado como segmentos
y hallar el punto de interseccin de los mismos con el plano. Con esos puntos ser
trivial reconstruir los tringulos resultantes.
La estructura de rbol BSP podra estar representada en C++ como en el listado
siguiente:
10 vector<Triangle>::iterator it;
11
12 for (it = vt.begin(); vt != vt.end(); ++it) {
13 if (cuts(bsp->p, *it))
14 split(*it, bsp->p);
15 }
16 vector<Triangle> frontSet = getFrontSet(vtNew, t);
17 vector<Triangle> backSet = getBackSet(vtNew, t);
18
19 bsp->front = createBSP(frontSet);
20 bsp->back = createBSP(backSet);
21
22 return bsp;
23 }
La funcin anterior pinta los tringulos (incluidos los que quedan detrs de la
vista) en orden, desde el ms lejano al ms cercano.
Esto era muy til cuando el hardware no implementaba un Z-buffer, ya que est
funcin obtena los tringulos ordenados con un coste linear.
Si cambiamos el algoritmo anterior (le damos la vuelta) recorreremos las caras
desde las ms cercanas a las ms lejanas. Esto s puede suponer un cambio con el
hardware actual, ya que si pintamos el tringulo cuyo valor va a ser mayor en el Z-
buffer, el resto de los tringulos ya no se tendrn que pintar (sern descartados por el
hardware).
Clipping Jerrquico
Deteccin de la oclusin
Tambin es posible utilizar un rbol BSP para detectar oclusiones. Este uso se
populariz gracias al motor de Quake, que utilizaba un nuevo tipo de rbol llamado
leafy-BSP, donde se utilizaron por primera vez para el desarrollo de un videojuego. Su
propiedad principal es la de dividir de manera automtica el conjuntos de tringulos
entrante en un array de celdas convexas.
Este nuevo tipo de rboles son BSPs normales donde toda la geometra se ha
propagado a las hojas, en vez de repartirla por todos los nodos a modo de tringulos
divisores. De este modo, en un BSP normal, las hojas slo almacenan el ltimo
tringulo divisor.
Para transformar un BSP en un leafy-BSP lo que hay que hacer es agitar el rbol
y dejar caer los tringulos de los nodos intermedios en las hojas (ver gura 26.42)
Una vez que el rbol se haya generado, se podr almacenar la lista de tringulos
de cada nodo como una lista de celdas numeradas. Para el ejemplo anterior las celdas
se muestran en la gura 26.43.
Cada celda representa una zona contigua y convexa del conjunto de tringulos
inicial. Las paredes de las celdas pueden ser o bien reas ocupadas por geometra del
nivel o espacios entre las celdas. Cada espacio abierto debe pertenecer exactamente a
dos celdas.
Ntese que el algoritmo anterior convierte cualquier conjunto de tringulos en una
lista de celdas y de pasillos que las conectan, que es la parte ms complicada.
Es posible precalcular la visibilidad entre las celdas. Para ello se utilizan los
pasillos (o portales, aunque diferentes a los que se vern un poco ms adelante). Se
mandan rayos desde algunos puntos en un portal hacia los dems, comprobndose si
llegan a su destino. Si un rayo consigue viajar del portal 1 al portal 2, signica que
las habitaciones conectadas a ese portal son visibles mutuamente. Este algoritmo fue
presentado por Teller [97].
Esta informacin sobre la visibilidad se almacenar en una estructura de datos
conocida como PVS (Potential Visibility Set), que es slo una matriz de bits de N xN
que relaciona la visibilidad de la la i (celda i) con la de la columna j (celda j).
Rendering
Portal Rendering
dirigido. Cada nodo del grafo es una habitacin y cada vrtice del grafo es un portal.
Ya que es posible que en una habitacin se encuentren varios portales, es necesario
que la estructura de datos permita conectar un nodo con varios otros, o que dos nodos
estn conectados por dos vrtices.
Figura 26.45: Grafo que representa las conexiones del mapa de habitaciones.
Figura 26.44: Mapa de habitacio-
nes conectadas por portales
Al contrario que los BSP, la geometra de un nivel no determina de manera
automtica la estructura de portales. As, ser necesario que la herramienta que se use
como editor de niveles soporte la divisin de niveles en habitaciones y la colocacin
de portales en los mismos. De esto modo, la creacin de la estructura de datos
es un proceso manual. Estas estructuras no almacenan datos precalculados sobre la
visibilidad; esta se determinar en tiempo de ejecucin.
El algoritmo de renderizado comienza por ver dnde se encuentra la cmara en un
momento dado, utilizando los bounding volumes de cada habitacin para determinar
dentro de cul est posicionada. Primero se pintar esta habitacin y luego las que
estn conectadas por los portales, de forma recursiva, sin pasar dos veces por un
mismo nodo (con una excepcin que se ver en el siguiente apartado). Lo complejo
del algoritmo es utilizar los portales para hacer culling de la geometra.
Es necesario detectar qu tringulos se pueden ver a travs de la forma del portal,
ya que normalmente habr un gran porcentaje no visible, tapados por las paredes que
rodean al mismo. Desde un portal se puede ver otro, y esto tendr que tenerse en
cuenta al calcular las oclusiones. Se utiliza una variante de la tcnica de view frustum
(que consiste en descartar los tringulos que queden fuera de un frustum, normalmente
el de la cmara), que Dalmau llama portal frustum. El frustum que se utilizar para
realizar el culling a nivel de portal tendr un origen similar al de la cmara, y pasar
por los vrtices del mismo. Para calcular las oclusiones de un segundo nivel en el
grafo, se podr obtener la interseccin de dos o ms frustums.
Portal Un portal puede tener un nmero de vrtices arbitrario, y puede ser cncavo o
Tendr algo que ver esta tcnica
convexo. La interseccin de dos portales no es ms que una interseccin 2D, en la
con algn juego de ValveTM ? Gra- que se comprueba vrtice a vrtice cules quedan dentro de la forma de la recursin
cias a ella el concepto de una de sus anterior. Este algoritmo puede ralentizar mucho la representacin, puesto que el
franquicias ha sido posible. nmero de operaciones depende del nmero de vrtices, y la forma arbitraria de los
portales no ayuda a aplicar ningn tipo de optimizaciones.
Luebke y Jobes [59] proponen que cada portal tenga asociada un bounding
volume, que simplicar enormemente los clculos. Este bounding volume rodea a
todos los vrtices por portal, lo que har que el algoritmo de como visibles algunos
C26
Dalmau arma que con esta tcnica se evita pintar de media entre un 40 % y un
60 % de toda la geometra entrante.
Enfoques hbridos
Quadtree-BSP
Hay juegos que poseen escenarios gigantestos, con un rea de exploracin muy
grande. Si se enfoca la particin de este tipo de escenas como la de un rbol
BSP, el gran nmero de planos de divisin har que crezca la geometra de manera
exponencial, debido a los nuevos tringulos generados a partir de la particin.
Una forma de afrontar este problema es utilizar dos estructuras de datos. Una
de ellas se usar para realizar una primera divisin espacial de la supercie (2D, un
Quadtree, por ejemplo) y la otra para una divisin ms exhaustiva de cada una de esas
particiones. De esto modo, se podr utilizar un Quadtree donde cada nodo contiene un
BSP.
De este modo, se pueden utilizar las caractersticas especiales de cada uno de
ellos para acelerar la representacin. En un primer paso, el Quadtree facilitar la
determinacin de la posicin global de una manera muy rpida. Una vez que se sepa en
qu parte del escenario se encuentra la accin, se tomar el BSP asociado a la misma
y se proceder a su representacin como se mostr en el apartado anterior.
Este tipo de representaciones espaciales ms complejas no son triviales, pero a
veces son necesarias para llevar a cabo la implementacin exitosa de un videojuego.
En el siguiente captulo ese introducirn los quadtrees.
26.6. Optimizacin de interiores [777]
Las tarjetas grcas actuales proveen de mecanismos para llevar a cabo los
clculos de deteccin de la oclusin por hardware. Estos mecanismos consisten en
llamadas a funciones internas que reducirn la complejidad del cdigo. El uso de
estas llamadas no evitar la necesidad de tener que programar pruebas de oclusin,
pero puede ser una ayuda bastante importante.
Hardware La utilizacin del hardware para determinar la visibilidad se apoya en pruebas
sobre objetos completos, pudiendo rechazar la inclusin de los tringulos que los
El uso del hardware para realizar
los tests de oclusin es el futuro, forman antes de entrar en una etapa que realice clculos sobre los mismos. As, las
pero eso no quita que se deban co- GPUs actuales proveen al programador de llamadas para comprobar la geometra de
nocer las tcnicas en las que se ba- objetos completos contra el Z-buffer. Ntese como estas llamadas evitarn mandar
sa para poder utilizarlo de manera estos objetos a la GPU para se pintados, ahorrando las transformaciones que se
efectiva.
producen antes de ser descartados. Adems, como retorno a dichas llamadas se puede
obtener el nmero de pxeles que modicra dicho objeto en el Z-buffer, lo que
permitira tomar decisiones basadas en la relevancia del objeto en la escena.
Cabe destacar que si se usa la geometra completa del objeto, mandando todos
los tringulos del mismo al test de oclusin de la GPU, el rendimiento global podra
incluso empeorar. Es algo normal, puesto que en una escena pueden existir objetos con
un gran nmero de tringulos. Para evitar este deterioro del rendimiento, y utilizar esta
capacidad del hardware en benecio propio, lo ms adecuado es utilizar bounding-
boxes que contengan a los objetos. Una caja tiene tan solo 12 tringulos, permitiendo
realizar tests de oclusin rpidos y bastante aproximados. Es fcil imaginarse la
diferencia entre mandar 12 tringulos o mandar 20000.
Adems, si las pruebas de oclusin para todos los objetos se llevan a cabo de forma
ordenada, desde los ms cercanos a los ms lejanos, las probabilidades de descartar
algunos de ellos aumentan.
Como ejemplo, uno tomado de las especicaciones de occlusion query de las
extensiones de OpenGL [69].
El grafo de escena que se utiliza en OGRE se conoce como scene manager (gestor
de escena) y est representado por la clase Scene-Manager. El interfaz de la misma
lo implementan algunos de los gestores de escena que incluye OGRE, y tambin
algunos otros desarrollados por la comunidad o incluso comerciales.
En OGRE es posible utilizar varios gestores de escena a la vez. De esto modo,
es posible utilizar uno de interiores y de exteriores a la vez. Esto es til por ejemplo
cuando desde un edicio se mira por una ventana y se ve un terreno.
Un gestor de escena de OGRE es responsable entre otras cosas de descartar los
objetos no visibles (culling) y de colocar los objetos visibles en la cola de renderizado.
Interiores en OGRE
El gestor de escena para interiores de OGRE est basado en BSP. De hecho, este
gestor se utiliza con mapas compatibles con Quake 3. Hay dos formas de referirse a
este gestor, la primera como una constante de la enumeracin Ogre::SceneType
(ST_INTERIOR) y otra como una cadena ("BspSceneManager") que se reere
al nombre del Plugin que lo implementa. La forma ms moderna y la preferida es la
segunda.
En la lnea 9 se crea el SceneManager de BSP, y en la lnea 30 se carga el
mapa y se usa como geometra esttica.
Cabe destacar que para poder navegar por los mapas BSP de manera correcta hay
que rotar 90 grados en su vector pitch y cambiar el vector up de la cmara por el eje
Z.
26.7.1. Introduccin
Las diferencias entre una escena de interiores y una de exteriores son evidentes.
Mientras una escena de interiores se dar en entornos cerrados, con muchas paredes o
pasillos que dividen en espacio en habitaciones, una escena de exteriores normalmente
no tiene ningn lmite que no est impuesto por la naturaleza. Si bien es cierto que es
una escena de este tipo, por ejemplo, pudiesen existir colinas que se tapasen unas a
las otras, si estamos situados frente a algunas de ellas en una posicin muy lejana, el
nmero de tringulos que se deberan representar sera tan elevado que quiz ningn
hardware podra afrontar su renderizado.
Est claro que hay que afrontar la representacin de exteriores desde un enfoque
diferente: hacer variable el nivel de detalle LOD). De este modo, los detalles de una
montaa que no se veran a cierta distancia no deberan renderizarse. En general, el
detalle de los objetos que se muestran grandes en la pantalla (pueden ser pequeos
pero cercanos), ser mayor que el de los objetos menores.
Si bien el nivel de detalle es importante, tampoco se descarta el uso de oclusiones
en algunos de los algoritmos que se presentarn a continuacin, siguiendo de nuevo
la propuesta de Dalmau. Se comenzar haciendo una pasada rpida por algunas de las
estructuras de datos necesarias para la representacin de exteriores eciente.
Los mapas de altura (heigtelds o heightmaps) han sido utilizados desde hace
mucho tiempo como forma para almacenar grandes supercies. No son ms que
imgenes en las que cada uno de sus pxeles almacenan una altura.
Cuando se empez a usar esta tcnica, las imgenes utilizaban tan solo la escala
de grises de 8-bit, lo que supona poder almacenar un total de 256 alturas diferentes.
Los mapas de altura de hoy en da pueden ser imgenes de 32-bit, lo que permite
que se puedan representar un total de 4.294.967.296 alturas diferentes, si se usa el Figura 26.48: Mapa de altura ren-
derizado (W IKIMEDIA C OMMONS)
canal alpha.
26.7. Optimizacin de Exteriores [781]
Quadtrees
Un quadtree es un rbol donde cada nodo tendr exactamente cuatro hijos, as, se
dice que es un rbol 4-ario. Un quadtree divide un espacio en cuatro partes iguales por
cada nivel de profundidad del rbol (gura 26.49).
Un quadtree permite que ciertas reas del terreno se puedan representar con ms
detalle puesto que es posible crear rboles no equilibrados, utilizando ms niveles
C26
donde sea necesario. Una aproximacin vlida es comenzar con un mapa de altura y
crear un quadtree a partir del mismo. Las partes del escenario donde exista ms detalle
(en el mapa de altura, habr muchas alturas diferentes), se subdividirn ms veces.
GeoMipmapping
View-Frustum Culling
Ser necesario descartar los pedazos de terreno que no caigan dentro del frustum
de la vista (cmara) puesto que no sern visibles. Para ello, lo ideal es utilizar un
quadtree.
El quadtree ser precalculado antes de comenzar la parte interactiva de la
aplicacin y consistir tan solo en bounding boxes de tres dimensiones, que a su vez
contendrn otras correspondientes a los subnodos del nodo padre. En cada hoja del
rbol quedar un pedazo del nivel 0 de divisiones que se ha visto al principio. Es
suciente utilizar quadtrees y no octrees ya que la divisin se realiza de la supercie,
y no del espacio.
Para descartar partes del terreno, se recorrer el rbol desde la raz, comprobando
si la bounding box est dentro del frustum al menos parcialmente, y marcando dicho
nodo en caso armativo. Si una hoja est marcada, quiere decir que ser visible y que
se mandar a la cola de representacin. A no ser que el terreno sea muy pequeo,
se terminarn mandando muchos tringulos a la cola, y esta optimizacin no ser Figura 26.54: Ejemplo de una
suciente. Es aqu donde De Boer introduce el concepto de Geomipmapping. bounding-box (M ATH I MAGES
P ROJECT ).
26.7. Optimizacin de Exteriores [785]
Esta tcnica se basa en el hecho de que los bloques que estn ms lejos de la
cmara no necesitan representarse con tan nivel de detalle como los ms cercanos.
De este modo, podrn ser representados con un nmero mucho menor de tringulos,
lo que reducir enormemente el nmero de tringulos del terreno que se mandarn a
la cola de renderizado. Otro algortmos utilizan una aproximacin en la que hay que
analizar cada tringulo para poder aplicar una poltica de nivel de detalle. Al contrario,
esta tcnica propone una poltica discreta que se aplicar en un nivel ms alto.
La tcnica clsica de mipmapping se aplica a las texturas, y consiste en la
generacin de varios niveles de subtexturas a partir de la original. Este conjunto de
texturas se utilizan en una poltica de nivel de detalle para texturas, usando unas u
otras dependiendo de la distancia a la cmara. Esta es la idea que se va a aplicar a la
mallas 3D de terrenos.
Cada bloque de terreno tendr asociado varios mipmaps, donde el original corres-
ponde al bloque del mapa de altura. Estos GeoMipMaps pueden ser precalculados y
Figura 26.55: Construccin de los almacenados en memoria para poder ser utilizados en tiempo de ejecucin directa-
mipmaps. En cada nivel, la textura mente.
se reduce a un cuarto de su rea.
(A KENINE -M OLLER)
Figura 26.56: Diferentes niveles de detalle de la malla desde los GeoMipMaps : niveles 0, 1 y 2.
ROAM
De este modo se construye un rbol que representa la malla, donde cada nivel
del mismo corresponde a un conjunto de tringulos que pueden ser encolados para
su renderizado. El rbol podr expandirse en tiempo real si fuera necesario. Bajar un
nivel en el rbol equivale a una operacin de particin y subir a una de unin.
El problema viene de la unin de regiones triangulares con diferente nivel de
detalle, ya que si no se tienen en cuenta aparecern agujeros en la malla representada.
Mientras que en el geomipmapping se parchea la malla, en ROAM, cuando se detecta
una discontinuidad se utiliza un oversampling de los bloques vecinos para asegurar
que estn conectados de manera correcta. Para ello, se aade informacin a cada lado
de un tringulo y se utilizan alguna reglas que garantizarn la continuidad de la malla.
Se conoce como vecino base de un tringulo al que est conectado a este a travs
de la hipotenusa. A los otros dos tringulos vecinos se los conocer como vecino
izquierdo y derecho (gura 26.59). Analizando diferentes rboles se deduce que el
vecino base ser del mismo nivel o del anterior (menos no) nivel de detalle, mientras Figura 26.59: Etiquetado de un
que los otros vecinos podrn ser del mismo nivel o de uno ms no. tringulo en ROAM. El tringulo
junto a su vecino base forman un
diamante.
26.7. Optimizacin de Exteriores [787]
Las reglas propuestas para seguir explorando el rbol y evitar roturas en la malla
sern las siguientes:
Figura 26.60: Particin recursiva El algoritmo en tiempo de ejecucin recorrer el rbol utilizando alguna mtrica
hasta encontrar un diamante para el para determinar cundo tiene que profundizar en la jerarqua, y cmo ha de realizar
nodo de la izquierda. las particiones cuando lo haga depender de las reglas anteriores.
Realizar en tiempo real todos estos clculos es muy costoso. Para reducir este
coste, se puede dividir el terreno en arrays de BTTs y slo recalcular el rbol cada
ciertos frames y cuando la cmara se haya movido lo suciente para que cambie la
vista por encima de un umbral.
Otra aproximacin para optimizar el algoritmo es utilizar el frustum para deter-
minar qu nodos hay que reconstruir de nuevo, marcando los tringulos que quedan
dentro o fuera, o parcialmente dentro. Slo estos ltimos necesitarn atencin para
mantener el rbol actualizado. A la hora de representarlo, lo nico que habr que ha-
cer es mandar los nodos marcados como dentro, total o parcialmente.
Chunked LODs
Figura 26.61: Nodo y subnodos de las texturas en un quadtree para Chunked LODs.
C26
A cada uno de los nodos se le aade informacin acerca de la prdida del nivel de
detalle se produce al subir un nivel en la jerarqua. Si las hojas contienen imgenes de
32x32 pxeles, los pedazos de terreno contendrn 32x32 vrtices. El rbol de mallas
tambin forma parte del preproceso normalmente, partiendo de una malla muy grande
y con mucha resolucin y partindola en trozos ms pequeos.
[788] CAPTULO 26. REPRESENTACIN AVANZADA
Terrenos y GPU
Scenegraphs de Exteriores
Cada objeto slo tendr una instancia, y lo nico que se almacenar aparte de
esta sern enlaces a la misma.
Es necesario implementar alguna poltica de nivel de detalle, puesto que es
imposible mostrar por pantalla (e incluso mantener en memoria) absolutamente
todos los tringulos que se ven en una escena.
Se necesita una rutina muy rpida para descartar porciones no visibles del
terreno y del resto de objetos de la escena.
26.7. Optimizacin de Exteriores [789]
Mientras que para almacenar un terreno y las capas estticas que lleva encima es
muy buena opcin utilizar un quadtree, ser mejor utilizar alguna tabla de tipo rejilla
para almacenar la posicin de los objetos dinmicos para determinar cules de ellos
se pintar en cada fotograma de manera rpida.
Terrenos
En la lineas 3-5 se congura niebla en
la escena, usando un color oscuro para
hacer que parezca oscuridad. En las lneas 7-14 se congura una luz direccional que
en el terreno. A partir de ella se calcularn las sombras
se utilizar en el mismo. En
la lnea 16 se congura la luz ambiental de la escena. En la 19 se crea un nuevo
objeto de conguracin del terreno, que se rellenar ms adelante. En la 20 se crea
un objeto agrupador de terrenos. Este objeto es el responsable de agrupar diferentes
del terreno juntos para que puedan ser representados como proceda. En la
pedazos
lnea 26 se congura el error mximo medido en pxeles de pantalla que se permitir
al representar el terreno. En las lneas 28-31 se congura la textura que compodr con
l, dibujando las sombras. Primero se determina la distancia de representacin de las
luces, luego se selecciona la direccin de las sombras, que parte de una luz direccional
y luego se conguran el valor ambiental y difuso de iluminacin.
17
18
19 _tGlobals = OGRE_NEW Ogre::TerrainGlobalOptions();
20 _tGroup = OGRE_NEW Ogre::TerrainGroup(_sMgr,
21 Ogre::Terrain::ALIGN_X_Z,
22 513, 12000.0f);
23
24 _tGroup->setOrigin(Ogre::Vector3::ZERO);
25
26 _tGlobals->setMaxPixelError(8);
27
28 _tGlobals->setCompositeMapDistance(3000);
29 _tGlobals->setLightMapDirection(light->getDerivedDirection());
30 _tGlobals->setCompositeMapAmbient(_sMgr->getAmbientLight());
31 _tGlobals->setCompositeMapDiffuse(light->getDiffuseColour());
32
33 Ogre::Terrain::ImportData& di;
34 di = _tGroup->getDefaultImportSettings();
35
36 di.terrainSize = 513;
37 di.worldSize = 12000.0f;
38 di.inputScale = 600;
39 di.minBatchSize = 33;
40 di.maxBatchSize = 65;
41
42 di.layerList.resize(3);
43 di.layerList[0].worldSize = 100;
44 di.layerList[0].textureNames.push_back("dirt_diff_spec.png");
45 di.layerList[0].textureNames.push_back("dirt_normal.png");
46 di.layerList[1].worldSize = 30;
47 di.layerList[1].textureNames.push_back("grass_diff_spec.png");
48 di.layerList[1].textureNames.push_back("grass_normal.png");
49 di.layerList[2].worldSize = 200;
50 di.layerList[2].textureNames.push_back("growth_diff_spec.png");
51 di.layerList[2].textureNames.push_back("growth_normal.png");
52
53
54 Ogre::Image im;
55 im.load("terrain.png",
56 Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
57
58 long x = 0;
59 long y = 0;
60
61 Ogre::String filename = _tGroup->generateFilename(x, y);
62 if (Ogre::ResourceGroupManager::getSingleton().resourceExists(
_tGroup->getResourceGroup(), filename))
63 {
64 _tGroup->defineTerrain(x, y);
65 }
66 else
67 {
68 _tGroup->defineTerrain(x, y, &im);
69 _tsImported = true;
70 }
71
72 _tGroup->loadAllTerrains(true);
73
74 if (_tsImported)
75 {
76 Ogre::TerrainGroup::TerrainIterator ti;
77 ti = _tGroup->getTerrainIterator();
C26
78 while(ti.hasMoreElements())
79 {
80 Ogre::Terrain* t = ti.getNext()->instance;
81 initBlendMaps(t);
82 }
83 }
84
85 _tGroup->freeTemporaryResources();
86
[792] CAPTULO 26. REPRESENTACIN AVANZADA
87 Ogre::Plane plane;
88 plane.d = 100;
89 plane.normal = Ogre::Vector3::NEGATIVE_UNIT_Y;
90
91 _sMgr->setSkyPlane(true, plane,"Skybox/SpaceSkyPlane",
92 500, 20, true, 0.5, 150, 150);
93
94 }
95
96 void MyApp::initBlendMaps(Ogre::Terrain* t)
97 {
98 Ogre::TerrainLayerBlendMap* blendMap0 = t->getLayerBlendMap(1);
99 Ogre::TerrainLayerBlendMap* blendMap1 = t->getLayerBlendMap(2);
100
101 Ogre::Real minHeight0 = 70;
102 Ogre::Real fadeDist0 = 40;
103 Ogre::Real minHeight1 = 70;
104 Ogre::Real fadeDist1 = 15;
105
106 float* pBlend1 = blendMap1->getBlendPointer();
107 for (Ogre::uint16 y = 0; y < t->getLayerBlendMapSize(); ++y) {
108 for (Ogre::uint16 x = 0; x < t->getLayerBlendMapSize(); ++x){
109 Ogre::Real tx, ty;
110
111 blendMap0->convertImageToTerrainSpace(x, y, &tx, &ty);
112 Ogre::Real height = t->getHeightAtTerrainPosition(tx, ty);
113 Ogre::Real val = (height - minHeight0) / fadeDist0;
114 val = Ogre::Math::Clamp(val, (Ogre::Real)0, (Ogre::Real)1);
115 val = (height - minHeight1) / fadeDist1;
116 val = Ogre::Math::Clamp(val, (Ogre::Real)0, (Ogre::Real)1);
117 *pBlend1++ = val;
118 }
119 }
120 blendMap0->dirty();
121 blendMap1->dirty();
122 blendMap0->update();
123 blendMap1->update();
124 }
En 34 se obtiene la instancia que congura algunos parmetros del terreno.
El primero es el tamao del terreno, que corresponde al nmero de pxeles del
mapa de altura. El segundo el tamao total del mundo, en unidades virtuales. El
tercero (inputScale) corresponde a la escala aplicada al valor del pxel, que se
transformar en la altura del mapa. Las dos siguientes corresponden al tamao de
los bloques de terrenos que se incluirn en la jerarqua (diferentes LOD). Estos tres
ltimos valores tendrn que ser del tipo 2n + 1. El atributo layerList contiene un
vector de capas, que se rellenar con las texturas (color y mapa de normales en este
worldSize corresponde a la relacin de tamao entre la textura y
caso). El atributo
el mundo. En 55 se carga la imagen con el mapa de altura.
Las siguientes lneas son las que asocian el mapa de altura con el terreno que se va
a generar. En este ejemplo slo se genera elsubterreno (0,0) con lo que slo se cargar
una imagen. En las siguientes lneas 61-70 se dene el terreno, en este caso, slo para
el slot (0,0), aunque el ejemplo est preparado para ser extendido fcilmente y aadir
la denicin algunos ms. La llamada defineTerrain() expresa la intencin de
que contiene el mapa de altura. La ejecucin
crear ese pedazo de terreno con la imagen
de este deseo se realiza en la linea 72 . Sin esta llamada, el ejemplo no funcionara
puesto que se ejecutara el siguiente paso sin haber cargado (generado) el terreno.
26.7. Optimizacin de Exteriores [793]
En el bloque 74-83 se crea la capa de blending, esto es, la fusin entre las tres
texturas del terreno (habr dos de blending). Esta operacin se realiza en el
mapas
mtodo initBlendMaps 96-124 . Dependiendo de la altura a la que corresponda
un pxel de las imgenes a fusionar (el tamao ha de ser coherente con el mapa de
altura) as ser el valor de blending aplicado, con lo que se conseguir que cada altura
presente una textura diferente.
En las lneas 87-89 se aade un plano, y justo debajo, en la 91 se usa como un
Skyplane.
Ejemplo de Skybox
material SkyBoxCEDV
{
technique
{
pass
{
lighting off
depth_write off
C26
texture_unit
{
cubic_texture cubemap_fr.jpg cubemap_bk.jpg cubemap_lf.jpg\
cubemap_rt.jpg cubemap_up.jpg cubemap_dn.jpg separateUV
tex_address_mode clamp
}
}
[794] CAPTULO 26. REPRESENTACIN AVANZADA
}
}
nombre de los archivos que queda delate de _. En el caso anterior, sera cubemap.jpg. El
ltimo parmetro puede ser combinedUVW, si en una misma imagen estn contenidas las seis
caras, o separateUV si se separan por imgenes. La primera forma utilizar coordenadas de
textura 3D, la segunda usar coordenadas 2D ajustando cada imagen a cada una de las caras.
Usando tex_address_mode clamp har que los valores de coordenadas de texturizado
mayores que 1,0 se queden como 1,0.
Para utilizar una Skybox simplemente habr que ejecutar esta lnea:
Como cabra esperar, la skybox se congura para el gestor de escenas. sta se encontrar
a una distancia ja de la cmara (el tercer parmetro), y podr estar activada o no (primer
parmetro).
Ejemplo de SkyDome
Aunque slo se utiliza una textura, realmente una SkyDome est formada por las cinco
caras superiores de un cubo. La diferencia con una skybox es la manera en la que se proyecta
la textura sobre dichas caras. Las coordenadas de texturizado se generan de forma curvada y
por eso se consigue tal efecto de cpula. Este tipo de objetos es adecuado cuando se necesita
un cielo ms o menos realista y la escena no va a contener niebla. Funcionan bien con texturas
repetitivas como las de nubes. Una curvatura ligera aportar riqueza a una escena grande y una
muy pronunciada ser ms adecuada para una escena ms pequea donde slo se vea pedazos
de cielo de manera intermitente (y el efecto exagerado resulte atractivo).
Un ejemplo de material es el siguiente:
material SkyDomeCEDV
{
[...]
texture_unit
{
texture clouds.jpg
scroll_anim 0.15 0
}
[...]
}
Ejemplo de SkyPlane
Se propone crear una escena con cada uno de las tres tcnicas anteriores. Si
fuera posible, utilice el ejemplo de terreno anterior.
Materiales
En los materiales no slo se remite a las texturas sino a todas las propiedades
editables dentro del bloque technique. Para congurar un material con soporte para
LOD lo primero es congurar la estrategia (lod_strategy) que se va a utilizar de
las dos disponibles:
Tras esto, lo siguiente es determinar los valores en los que cambiar el nivel de de-
talle para dicha estrategia. Se usar para tal labor la palabra reservada lod_values
seguida de dichos valores. Es importante destacar que tendrn que existir al menos
tantos bloques de technique como valores, ya que cada uno de ellos estar relacio-
nado con el otro respectivamente. Cada uno de los bloques technique debe tener
asociado un ndice para el nivel de detalle (lod_index). Si no aparece, el ndice se-
r el 0 que equivale al mayor nivel de detalle, opuestamente a 65535 que corresponde
con el menor. Lo normal es slo tener algunos niveles congurados y probablemente
jams se llegue a una cifra tan grande. Aun as, es importante no dejar grandes sal-
C26
tos y hacer un ajuste ms o menos ptimo basado en las pruebas de visualizacin de
la escena. Es posible que varias tcnicas tengan el mismo ndice de nivel de detalle,
siendo OGRE el que elija cul es la mejor de ellas segn el sistema en el que se est
ejecutando.
Un ejemplo de material con varios niveles de detalle:
material LOD_CEDV
[796] CAPTULO 26. REPRESENTACIN AVANZADA
{
lod_values 200 600
lod_strategy Distance
technique originalD {
lod_index 0
[...]
}
technique mediumD {
lod_index 1
[...]
}
technique lowD {
lod_index 2
[...]
}
technique shaders {
lod_index 0
lod_index 1
lod_index 2
[...]
}
}
Como ejercicio se propone construir una escena que contenga 1000 objetos
con un material con tres niveles de detalle diferente. Siendo el nivel 0 bastante
complejo y el nivel 3 muy simple. Muestre los fotogramas por segundo y
justique el uso de esta tcnica. Se sugiere mostrar los objetos desde las
diferentes distancias conguradas y habilitar o deshabilitar los niveles de
detalle de baja calidad.
Modelos
OGRE proporciona tres formas de utilizar LOD para mallas. La primera es una
donde modelos con menos vrtices se generan de forma automtica, haciendo uso
de progressiveMesh internamente. Para utilizar la generacin automtica ser
necesario que la malla que se cargue no tenga ya de por s LOD incluido (por ejemplo,
la cabeza de ogro de las demos ya contiene esta informacin y no se podr aplicar).
Una vez que la entidad est creada, se recuperar la instancia de la malla contenida
dentro.
En la lnea 3 del ejemplo anterior se crea una lista de distancias para el
nivel de detalle. Esta lista no es ms que un vector de reales que determina
los valores que se utilizarn segn la estrategia elegida para esa entidad. Por
defecto corresponde con la distancia y en ese caso los valores corresponden a las
races cuadradas de la distancia en la que se producen cambios. La estrategia se
puede cambiar con setLodStrategy() usando como parmetro un objeto de
tipo base lodStrategy, que ser DistanceLodStrategy o PixelCount-
LodStrategy. Estas dos clases son singletons, y se deber obtener su instancia
utilizando getSingleton(). La segunda de ellas corresponde con la poltica
basada en el nmero de pxeles que se dibujan en la pantalla de una esfera que contiene
al objeto (bounding-sphere). En la lnea 12 se llama a la funcin que genera los
diferentes niveles a partir de la malla original. El segundo parmetro es corresponde
con el mtodo que se usar para simplicar la malla:
26.7.6. Conclusiones
En los dos ltimas secciones se ha realizado una introduccin a algunas de las
tcnicas utilizadas para aumentar el rendimiento de las representaciones en tiempo
real. Sin algunas de ellas, sera del todo imposible llevar a cabo las mismas. Si
bien es cierto que cualquier motor grco actual soluciona este problema, brindando
al programador las herramientas necesarias para pasar por alto su complejidad, se
debera conocer al menos de forma aproximada en qu consisten las optimizaciones
relacionadas.
Saber elegir la tcnica correcta es importante, y conocer cul se est utilizando
tambin. De esto modo, ser posible explicar el comportamiento del juego en
determinadas escenas y saber qu acciones hay que llevar a cabo para mejorarla.
Por desgracia, OGRE est cambiando alguno de los gestores de escena y se est
haciendo evidente en la transicin de versin que est sucediendo en el momento de
escribir esta documentacin.
La forma de realizar optimizaciones de estos tipos ha cambiado con el tiempo,
pasando de utilizar el ingenio para reducir las consultas usando hardware genrico, a
usar algoritmos fuertemente apoyados en la GPU.
Sea como sea, la evolucin de la representacin en tiempo real no slo pasa
por esperar a que los fabricantes aceleren sus productos, o que aparezca un nuevo
paradigma, sino que requiere de esfuerzos constantes para crear un cdigo ptimo,
usando los algoritmos correctos y eligiendo las estructuras de datos ms adecuadas
para cada caso.
Captulo
Plataformas Mviles
27
Miguel Garca Corchero
U
Motores de videojuego n motor de videojuegos es un termino que hace referencia a una serie de
herramientas que permiten el diseo, la creacin y la representacin de un
Existen otros motores de videojue-
gos ms utilizados en la industria videojuego. La funcionalidad bsica de un motor es proveer al videojuego
como CryEngine o Unreal Engine renderizacin, gestin de fsicas, colisiones, scripting, animacin, administracin de
y tienen ms funcionalidades que memoria o gestin del sonidos entre otras cosas.
Unity3D pero tambin es ms com-
plejo trabajar con ellos. En este captulo se trata el caso especco del motor de videojuegos Unity3D y se
realizar un repaso supercial por la forma de trabajar con un motor de videojuegos
mientras se realiza un videjuego de ejemplo para dispositivos mviles con el sistema
operativo iOS o Android.
El videojuego ser un shootem up de aviones con vista cenital.
799
[800] CAPTULO 27. PLATAFORMAS MVILES
Importacin de assets: Uno de los pasos iniciales dentro del entorno integrado Los scripts que utilizamos se pue-
den escribir en los lenguajes C#, Ja-
de Unity3D es aadir al proyecto todo el material generado anteriormente y vascript o BOO.
ajustar sus atributos; como formatos, tamaos de textura, ajuste de propiedades,
calculo de normales, etc.
Creacin de escenas: Crearemos una escena por cada nivel o conjunto de
mens del videojuego. En la escena estableceremos relaciones entre objetos y
crearemos instancias de ellos.
Creacin de prefabs: Los prefabs son agrupaciones de objetos que se salvan
como un objeto con entidad propia.
Optimizacin de la escena: Uno de los pasos fundamentales se lleva a cabo
al nal del desarrollo de la escena y es la optimizacin. Para ello emplearemos
tcnicas de lightmapping y occlusion culling con las herramientas del entorno.
1. Una escena por cada nivel del juego: Utilizaremos este enfoque cuando cada
nivel tenga elementos diferentes. Podrn repetirse elementos de otros niveles,
pero trataremos que estos elementos sean prefabs para que si los modicamos
en alguna escena, el cambio se produzca en todas.
27.2. Creacin de escenas [801]
Figura 27.3: En nuestro ejemplo se han utilizado capturas de pantalla de google map para obtener texturas
de terreno y se han mapeado sobre un plano en Blender. Posteriormente se ha utilizado el modo scuplt para
dar relieve al terreno y generar un escenario tridimensional.
Figura 27.4: Interface de Unity3D. Dividida en las vistas ms utilizadas: Jerarqua de escena, Assets del
proyecto, Vista del videojuego, Vista de escena y Vista de propiedades del asset seleccionado.
Como las escenas pueden cargarse desde otra escena. Podemos realizar un cambio
de escena cuando se ha llegado al nal de un determinado nivel por ejemplo. Este
cambio de escena mantendr en memoria los elementos comunes como texturas o
modelos 3D o sonidos que pertenezcan a los dos escenas, la escena que se descarga y
la escena que se carga, por lo que dependiendo del caso esta carga suele ser bastante
rpida. Tambin podemos realizar los mens en una escena y desde ah cargar la
escena del primer nivel del juego.
Figura 27.6: Para la escena de nuestro ejemplo hemos aadido el modelo 3D del escenario, los enemigos,
el jugador y hemos establecido las relaciones de jerarqua necesarias entre estos elementos.
Los scripts tienen algunos mtodos especiales que podemos implementar como:
Update: Este mtodo es invocado por el motor grco cada vez que el objeto
va a ser renderizado.
Start: Este mtodo es invocado por el motor grco cuando se instancia el
objeto que contiene a este script.
En nuestro escenario se han aadido nubes modeladas mediante planos y con una
textura de una nube con transparencias. Para darle mayor realismo a este elemento se
va a programar un script para mover las coordenadas u,v del mapeado de este plano
provocando que la textura se mueve sobre el plano y simulando un movimiento de
nubes.
C27
3 function Start(){
4 //Realizar una llamada al mtodo destruir pasados los segundos
de timeOUT
5 Invoke ("Destruir", timeOut);
6 }
7
8 function Destruir(){
9 //Destruir el objeto que contiene este script
10 DestroyObject (gameObject);
11 }
1 #pragma strict
2
3 var SonidoDisparo : AudioSource;
4 var SonidoExplosionAire : AudioSource;
5 var SonidoExplosionSuelo : AudioSource;
6 var SonidoVuelo : AudioSource;
7 var MusicaJuego : AudioSource;
[808] CAPTULO 27. PLATAFORMAS MVILES
20 function Awake(){
21 //Hacemos que el juego corra a 60 FPS como mximo
22 Application.targetFrameRate = 60;
23 }
24
25 function Start(){
26 //Obtenemos el puntero a Sonidos y ajustamos algunos valores
iniciales
27 var SonidosPointer : Transform = (GameObject.FindWithTag("
Sonidos")).transform;
28 SonidosStatic = (SonidosPointer.GetComponent("Sonidos") as
Sonidos);
29 SonidosStatic.PlayMusicaJuego();
30 SonidosStatic.PlaySonidoVuelo();
31 ScoreGUI.text="Score : "+Score;
32 }
33
34 function Update () {
35 if (enZonaFinal && velocidadCamara>0.0){
36 //Si estamos en la zona final paramos el movimiento de
manera gradual
37 velocidadCamara*=0.95;
38 if (velocidadCamara<0.1) { velocidadCamara=0; }
39 }
40
41 if (velocidadCamara>0.0){
42 //Movemos la cmara en su componente z para hacer scroll
43 Camara.position.z+=Time.deltaTime * velocidadCamara;
44 }
45 }
46
47 function EntrarEnZonaFinal(){
48 //Se ha entrado en el trigger de la zona final
49 enZonaFinal=true;
50
51 SonidosStatic.StopMusicaJuego();
52 SonidosStatic.PlayMusicaFinal();
53 }
54
55 function FinDeJuegoGanando(){
56 //Fin de partida cuando hemos completado la misin
57 FondoFinal.texture = TexturaFinalBien;
58 Restart();
59 }
60
61 function FinDeJuegoPerdiendo(){
62 //Fin de partida cuando hemos fallado la misin
63 FondoFinal.texture = TexturaFinalMal;
64 Restart();
65 }
66
67 function AddScore(valor : int){
68 //Aadimos puntos, por lo que hay que hacer la suma y
actualizar el texto
69 Score+=valor;
70 ScoreGUI.text="Score : "+Score;
71 }
72
73 function Restart(){
74 //Ocultamos los textos y botones
75 LifeGUI.enabled=false;
76 ScoreGUI.enabled=false;
77 BotonDISPARO.enabled=false;
78 BotonIZ.enabled=false;
C27
79 BotonDE.enabled=false;
80 FondoFinal.enabled=true;
81
82 //Esperamos 5 segundos y hacemos un reload de la escena
83 yield WaitForSeconds (5);
84 Application.LoadLevel(Application.loadedLevel);
85 }
[810] CAPTULO 27. PLATAFORMAS MVILES
13
14 private var llamasInstancia : Transform;
15 private var cantidadRotationCaida : Vector3 = Vector3(0,0,0);
16
17 function Update () {
18 if (transform.gameObject.rigidbody.useGravity){
19 //Si el enemigo est callendo, el avin rota sobre sus ejes
porque entra en barrena
20 transform.Rotate(Time.deltaTime * cantidadRotationCaida.x,
Time.deltaTime * cantidadRotationCaida.y,Time.deltaTime
* cantidadRotationCaida.z);
21 } else {
22 if (player!=null){
23 var distancia : float = transform.position.z-player.
position.z;
24 if (distancia<=rangoDisparo && distancia>0) {
25 //Si estamos en rango de disparo el avin dispara
al frente
26 if (Time.time > siguienteTiempoDisparo){
27 siguienteTiempoDisparo = Time.time +
tiempoRecarga;
28 Instantiate(disparoPrefab, transform.position,
Quaternion.identity);
29 }
30 }
31 }
32 }
33 }
34
35 function OnCollisionEnter(collision : Collision) {
36 //Determinar posicin y rotacin del punto de contacto de la
colisin
37 var contact : ContactPoint = collision.contacts[0];
38 var rot : Quaternion = Quaternion.FromToRotation(Vector3.up,
contact.normal);
39 var pos : Vector3 = contact.point;
40
41 var SonidosPointer : Transform = (GameObject.FindWithTag("
Sonidos")).transform;
42 var SonidosStatic : Sonidos = (SonidosPointer.GetComponent("
Sonidos") as Sonidos);
43
44 if (transform.gameObject.rigidbody.useGravity || collision.
collider.tag=="Player"){
45 //Si estamos callendo y hemos vuelto a colisionar entonces
explota
46 var ControlJuegoPointer : Transform = (GameObject.
FindWithTag("ControlJuego")).transform;
47 var ControlJuegoStatic : ControlJuego = (
ControlJuegoPointer.GetComponent("ControlJuego") as
ControlJuego);
48
49 SonidosStatic.PlaySonidoExplosionSuelo();
50
51 //Instanciamos la explosin final en la posicin del
impacto
52 Instantiate(explosionPrefab, pos, rot);
53 if (llamasInstancia!=null){
54 Destroy (llamasInstancia.gameObject);
55 }
56
57 if (jefeFinal) {
58 ControlJuegoStatic.AddScore(500);
59 ControlJuegoStatic.FinDeJuegoGanando();
C27
60 } else {
61 var cantidadScore : float = (rangoDisparo * (1.0/
tiempoRecarga))*5;
62 ControlJuegoStatic.AddScore(cantidadScore);
63 }
64
[812] CAPTULO 27. PLATAFORMAS MVILES
31 Instantiate(disparoPrefab, posicionDisparoDE.position,
posicionDisparoDE.rotation);
32 } else {
33 Instantiate(disparoPrefab, posicionDisparoIZ.position,
posicionDisparoIZ.rotation);
34 }
35 anteriorDisparoIZ=!anteriorDisparoIZ;
36 }
37
38 if (botonIzquierda || Input.GetButton("Left")){
39 //Si hay moverse a la izquierda se actualiza la posicin
del jugador
40 //Tambin se mueve un poco la cmara para simular un poco
de efecto parallax
41 if (transform.position.x>topeIZ.position.x) {
42 transform.position.x-= Time.deltaTime *
cantidadMovimiento;
43 camara.position.x-= Time.deltaTime * cantidadMovimiento
/2;
44 }
45 } else if (botonDerecha || Input.GetButton("Right")) {
46 //Si hay moverse a la derecha se actualiza la posicin del
jugador
47 //Tambin se mueve un poco la cmara para simular un poco
de efecto parallax
48 if (transform.position.x<topeDE.position.x) {
49 transform.position.x+= Time.deltaTime *
cantidadMovimiento;
50 camara.position.x+= Time.deltaTime * cantidadMovimiento
/2;
51 }
52 }
53 }
54
55 function OnCollisionEnter(collision : Collision) {
56 if (collision.collider.tag=="DisparoEnemigo" || collision.
collider.tag=="Enemigo"){
57 //Si el jugador colisiona con un disparo o un enemigo la
vida disminuye
58 vida--;
59 LifeGUI.text="Life : "+vida;
60
61 if (vida<=0){
62 //Si la vida es 0 entonces acaba la partida
63 var ControlJuegoPointer : Transform = (GameObject.
FindWithTag("ControlJuego")).transform;
64 var ControlJuegoStatic : ControlJuego = (
ControlJuegoPointer.GetComponent("ControlJuego") as
ControlJuego);
65 ControlJuegoStatic.FinDeJuegoPerdiendo();
66
67 //Reproducimos sonido de explosin
68 var SonidosPointer : Transform = (GameObject.
FindWithTag("Sonidos")).transform;
69 var SonidosStatic : Sonidos = (SonidosPointer.
GetComponent("Sonidos") as Sonidos);
70 SonidosStatic.PlaySonidoExplosionSuelo();
71
72 //Instanciamos un prefab de explosin en la posicin
del avin del jugador
73 Instantiate(explosionPrefab, transform.position,
Quaternion.identity);
74 //Eliminamos el avin del jugador
75 Destroy(gameObject);
C27
76 }
77 }
78 }
79
80
81 //Mtodos para controlar la pulsacin de los botones virtuales
[814] CAPTULO 27. PLATAFORMAS MVILES
82
83 function ActivarBotonIzquierda(){
84 botonIzquierda=true;
85 }
86
87 function ActivarBotonDerecha(){
88 botonDerecha=true;
89 }
90
91 function ActivarBotonDisparo(){
92 botonDisparo=true;
93 }
94
95 function DesactivarBotonIzquierda(){
96 botonIzquierda=false;
97 }
98
99 function DesactivarBotonDerecha(){
100 botonDerecha=false;
101 }
102
103 function DesactivarBotonDisparo(){
104 botonDisparo=false;
105 }
27 case tipoGUI.BotonShoot:
28 guiTexture.pixelInset = Rect (Screen.width/2 -
anchoBoton - margen, - Screen.height/2 + margen,
anchoBoton, altoBoton);
29 break;
30 }
31 }
52 case tipoGUI.BotonLeft:
53 PlayerStatic.DesactivarBotonIzquierda();
54 break;
55 case tipoGUI.BotonRight:
56 PlayerStatic.DesactivarBotonDerecha();
57 break;
58 case tipoGUI.BotonShoot:
59 PlayerStatic.DesactivarBotonDisparo();
60 break;
61 }
62 }
63
64 function Start () {
65 //Obtenemos el puntero a Player y ajustamos algunos valores
iniciales
66 var PlayerPointer : Transform = (GameObject.FindWithTag("Player
")).transform;
67 PlayerStatic = (PlayerPointer.GetComponent("Player") as Player)
;
68 wasClicked = false;
69 Boton.texture= TextureOFF;
70 }
27.5. Optimizacin
El motor grco nos proporciona dos herramientas imprescindibles para optimizar
nuestros videojuegos: lightmapping y occlusion culling. Aplicando estas tcnicas
reduciremos mucho la carga de renderizado y nos permitir que nuestros videojuegos
puedan correr a buena velocidad en dispositivos de poca potencia grca como
smartphones o tablets.
Figura 27.14: Ejemplo de las sombras que proyectan sobre el terreno los modelos tridimensionales de unas
ruinas colocados en la escena.
C27
Figura 27.16: Ejemplo de mapa generado mediante esta tcnica que despus se mapear sobre el modelo
3D de la escena. Este mapa de sombreado es de la zona que se aprecia en la gura 27.18.
[818] CAPTULO 27. PLATAFORMAS MVILES
Figura 27.19: En nuestro ejemplo se ha dividido el escenario en porciones para poder aplicar la tcnica, de
este modo en un momento determinado slo se renderiza una pequea parte del escenario.
C27
Figura 27.20: Nivel de granularidad elegido para la matriz de discretizacin del espacio en nuestro ejemplo.
Desarrollo de Componentes
El objetivo de este mdulo, titulado Desarrollo de Componentes
dentro del Curso de Experto en Desarrollo de Videojuegos, consiste
en profundizar en tcnicas especficas vinculadas al desarrollo de
videojuegos, como por ejemplo el uso de tcnicas de Inteligencia
Artificial o la programacin multijugador en red. Para ello, una de
las principales metas es la de complementar la visin general de la
arquitectura de un motor de juegos con cuestiones especficas que
resultan fundamentales para su desarrollo. Dentro del contexto de la
Inteligencia Artificial, en este mdulo se estudian tcnicas
fundamentales como la Lgica Difusa o los algoritmos genricos,
entre otras. As mismo, se realiza una discusin del diseo orientado
a agentes como pilar esencial en el desarrollo del componente
inteligente de un videojuego. En la parte relativa al juego
multijugador se exploran las posibilidades que ofrecen los sockets y,
posteriormente, se discute cmo el uso de herramientas de ms alto
nivel, como los middlewares de comunicaciones pueden contribuir a
facilitar el desarrollo del mdulo de networking. Finalmente, este
mdulo tambin contempla aspectos relativos a la edicin de audio,
la gestin de vdeo y la importancia de la integracin de nuevos
dispositivos de interaccin. En el contexto del desarrollo de
videojuegos, tcnicas como la visin por computador o la realidad
aumentada pueden contribuir a mejorar la experiencia del jugador.
Captulo
Inteligencia Articial
28
Javier Alonso Albusac Jimnez
Jos Jess Castro Snchez
David Vallejo Fernndez
Luis Jimnez Linares
Manuel Palomo Duarte
L
a Inteligencia Articial (IA) es un elemento fundamental para dotar de realismo
a un videojuego. Uno de los retos principales que se plantean a la hora
de integrar comportamientos inteligentes es alcanzar un equilibrio entre la
sensacin de inteligencia y el tiempo de cmputo empleado por el subsistema de
IA. Dicho equilibrio es esencial en el caso de los videojuegos, como exponente ms
representativo de las aplicaciones grcas en tiempo real.
Este planteamiento gira, generalmente, en torno a la generacin de soluciones que,
sin ser ptimas, proporcionen una cierta sensacin de inteligencia. En concreto, dichas
soluciones deberan tener como meta que el jugador se enfrente a un reto que sea
factible de manera que suponga un estmulo emocional y que consiga engancharlo al
juego.
En los ltimos aos, la IA ha pasado de ser un componente secundario en el
proceso de desarrollo de videojuegos a convertirse en uno de los aspectos ms
importantes. Actualmente, lograr un alto nivel de IA en un juego sigue siendo uno
de los retos ms emocionantes y complejos y, en ocasiones, sirve para diferenciar un
juego normal de uno realmente deseado por los jugadores.
Tal es su importancia, que las grandes desarrolladores de videojuegos mantienen
en su plantilla a ingenieros especializados en la parte de IA, donde los lenguajes de
scripting, como Lua o Python, y la comunicacin con el resto de programadores del
juego resulta esencial.
Figura 28.1: El robot-humanoide En este captulo se discuten cuestiones con gran relevancia en el mbito de la IA
Asimo, creado por Honda en el ao aplicada a videojuegos, haciendo especial hincapi en las tcnicas ms consolidadas,
2000, es uno de los exponentes ms
como por ejemplo el uso de mquinas de estados nitas, el diseo basado en agentes,
reconocidos de la aplicacin de tc-
nicas de IA sobre un prototipo fsi-
la aplicacin de algoritmos de bsqueda o la lgica difusa. En la ltima seccin del
co real. captulo se discute un caso de estudio prctico enfocado a la IA.
823
[824] CAPTULO 28. INTELIGENCIA ARTIFICIAL
a buscar soluciones ptimas, mien- la idea de mostrar algn tipo de inteligencia, aunque sea mnima.
tras que en el contexto de los video-
juegos la tendencia general consiste
en encontrar buenas soluciones.
[828] CAPTULO 28. INTELIGENCIA ARTIFICIAL
Agente Sensores
Percepciones
Medio o contexto
Actuadores
Acciones
Agente
ver accin
siguiente estado
Entorno
Accin, reaccin Una mquina de estados dene el comportamiento que especica las secuencias
de estados por las que atraviesa un objeto durante su ciclo de ejecucin en respuesta
El modelo de diseo de las mqui- a una serie de eventos, junto con las respuestas a dichos eventos. En esencia, una
nas de estados est basado en el tra-
dicional esquema de accin y reac- mquina de estados permite descomponer el comportamiento general de un agente en
cin. Bsicamente, ante el cumpli- pedazos o subestados ms manejables. La gura 28.9 muestra un ejemplo concreto de
miento de una condicin (accin) se mquinas de estados, utilizada para denir el comportamiento de un NPC en base a
produce una transicin (reaccin). una serie de estados y las transiciones entre los mismos.
Como el lector ya habr supuesto, los conceptos ms importantes de una mquina
de estados son dos: los estados y las transiciones. Por una parte, un estado dene una
condicin o una situacin durante la vida del agente, la cual satisface alguna condicin
o bien est vinculada a la realizacin de una accin o a la espera de un evento. Por
Fuzzy Logic otra parte, una transicin dene una relacin entre dos estados, indicando lo que ha
La lgica difusa o borrosa es una de ocurrir para pasar de un estado a otro. Los cambios de estado se producen cuando
tcnica que permite tratar la incerti- la transicin se dispara, es decir, cuando se cumple la condicin que permite pasar de
dumbre y la vaguedad presenta en la un estado a otro.
mayora de los problemas del mun-
C28
after (30s)
ruido
Inactivo Patrullando Rastreando
sin enemigos
enemigo
detectado
Atacando
El anterior listado de cdigo contiene una funcin predecir que debera implemen-
tar una IA ms sosticada, ms all de devolver valores aleatorios. Una posible opcin
sera utilizar la estructura de datos jugadas para generar algn tipo de patrn que mo-
delara el comportamiento del humano despus de varias rondas de juego. Se plantea
como ejercicio al lector la implementacin de la funcin predecir() con el objetivo
de estudiar la secuencia previa de predicciones por parte del jugador humano, con el
objetivo de ganar un mayor nmero de partidas.
5 5 x
5 5 x
4 4 4 4 x
3
22 2 2 2 2 2
11 1 1 1 1 1 1 1 1 1
Figura 28.11: Planteando un mdulo de IA para el Tetris. a) Limpieza de una lnea, b) Cuanticando la
altura del montn de chas, c) Problema de huecos perdidos (los puntos representan los huecos mientras
que las x representan los potenciales bloqueos).
donde:
Para aquellos que no estn familiarizados con esta tcnica, los dos principios
anteriores pueden resultar un tanto confusos; desarrollemos con mayor detalle la
principal idea en la que se basa cada uno de ellos.
En cuanto al primero, los computadores son mquinas que trabajan de forma
metdica y que resuelven los problemas con total exactitud. Normalmente, un
computador necesita unos parmetros de entrada, los procesa y genera unos datos
de salida como solucin a un problema. Sin embargo, los humanos suelen analizar las
situaciones o resolver los problemas de una manera ms imprecisa, sin la necesidad de
conocer todos los parmetros o que la informacin relativa a los mismos est completa.
Por ejemplo, supongamos el caso en el que dos personas se estn pasando una
pelota. El lanzador no es consciente en ningn momento, del ngulo que forma su
brazo con el cuerpo, ni la fuerza exacta que realiza para lanzar la bola. Uno de ellos
podra pedir al otro que le lanzara la pelota ms fuerte. El trmino lingstico ms
fuerte es impreciso en s; en ningn momento se especica cuanto debe aumentar
el valor de la fuerza para cumplir el objetivo deseado. La persona encargada de
lanzar, puede resolver el problema rpidamente, aumentando la fuerza con respecto
Figura 28.14: Profesor Lofti Za-
al lanzamiento anterior sin la necesidad de realizar clculos precisos. Esta forma de
deh, padre de la lgica difusa, pre- trabajar, en la que se manejan trminos lingsticos, conceptos vagos e imprecisos
sent su teora en el ao 1965 en la es un tanto opuesta a la forma en la que el computador trabaja. Sin embargo,
revista Information and Control. puede ofrecer soluciones rpidas y ecientes a problemas reales, con un bajo coste
computacional.
Por otro lado, el segundo principio enuncia que la lgica difusa se basa principal-
mente en el estudio de los grados de pertenencia a los conjuntos difusos denidos.
C28
riables pueden tomar) en el que se incluyen los conjuntos difusos. Segn el valor de
entrada de la variable, ste pertenecer a uno o varios conjuntos del dominio con un
cierto grado de pertenencia. Esta es una de las principales caractersticas que diferen-
cian a la lgica difusa de la lgica tradicional.
En la lgica bivaluada existen dos valores de verdad posibles: verdadero y falso.
En la lgica difusa pueden existir valores intermedios, es decir, un hecho podra ser
no del todo cierto y no del todo falso. En cuanto a la teora de conjuntos tradicional,
un elemento pertenece absolutamente a un conjunto o no pertenece. En el caso de
la lgica difusa, un elemento puede pertenecer a diferentes conjuntos con distintos
grados. Los grados de pertenencia pertenecen al intervalo [0,1], donde 1 representa
pertenencia absoluta y 0 el caso contrario. Por tanto, podemos decir que la lgica
difusa es una generalizacin de la lgica clsica.
Veamos un ejemplo; supongamos que un sistema posee una variable de entrada
llamada altura. El objetivo de este sistema podra ser catalogar a un conjunto de
personas adultas como muy bajo, bajo, mediano, alto o muy alto. Supongamos que la
altura puede variar entre 1.40 y 2.20. En el dominio de denicin de la variable altura
habr que indicar de algn modo la correspondencia entre los trminos muy bajo,
bajo, mediano, alto y muy alto, y el rango posible de valores numricos [1.40-2.20].
Supongamos que en un sistema clsico, se considera personas bajas hasta 1.69 y las
personas medianas desde 1.70 hasta 1.75. En este sistema, una persona que mida 1.69
sera clasicada como baja, mientras que una persona que mida 1.70 sera clasicada
como mediana.
Tal como se puede apreciar, la discriminacin en el proceso de clasicacin entre
una persona y otra es excesiva teniendo en cuenta que la diferencia es mnima (slo
un centmetro). La lgica difusa contempla la pertenencia a varios conjuntos al mismo
tiempo, es decir, una persona que mida 1.70 podra pertenecer al conjunto de los
medianos con un grado 0.6 mientras que al conjunto de los bajos podra ser 0.4. Este
es un ejemplo orientativo para concebir una idea general sobre la esencia en la que
se basa la lgica difusa; en secciones posteriores se explicar con mayor detalle la
denicin de conjuntos difusos y el clculo de grados de pertenencia.
Sistemas de escritura.
Mejora de la eciencia en el consumo de combustibles en vehculos.
Sistemas expertos que simulan el comportamiento humano.
Tecnologa informtica.
Figura 28.18: Denicin de domi- informacin sobre el personaje principal, o bien, la informacin de la que dispone es
nio difuso de la variable V mediante incompleta. Esto hace tambin que el comportamiento de los adversarios sea mucho
conjuntos curvilneos. menos previsible y rompe la monotona del videojuego.
[838] CAPTULO 28. INTELIGENCIA ARTIFICIAL
Proceso de Fuzzificacin
Entrada crisp Entrada difusa
Motor de inferencia
Proceso de inferencia Salida difusa
Proceso de defuzzificacin
Salida difusa Salida crisp
Altura
0
1.40 1.45 1.55 1.60 1.65 1.70 1.75 1.80 1.85 2.20
Figura 28.20: Dominio de denicin o espacio difuso denido para la variable altura.
Una vez denidas las variables y sus dominios de denicin, el siguiente paso
consiste en la fuzzicacin de variables a medida que reciban valores de entrada. Este
paso consiste bsicamente en el estudio de los valores de pertenencia a los conjuntos
difusos en funcin de los valores crisp de entrada. Por ejemplo, supongamos que una
persona mide 1.64, es decir, altura = 1.64. Dicho valor de altura estara comprendido
entre los conjuntos bajo y medio, con una pertenencia mayor al conjunto medio.
Un espacio difuso est correctamente denido, si la suma de todas las pertenencias
a los conjuntos siempre es 1, independientemente del valor de entrada. Formalmente,
la denicin de la funcin de pertenencia asociada a un trapecio o conjunto difuso
trapezoidal es de la siguiente manera:
0 u<a
(ua)
(ba) au<b
(u; a, b, c, d) = 1 buc (28.2)
(du)
(dc) c<ud
0 u>d
C28
Altura
0
1.40 1.45 1.55 1.60 1.65 1.70 1.75 1.80 1.85 2.20
Figura 28.21: Estudio de los valores de pertenencia a los conjuntos difusos a partir de una altura de 1.64.
8 else if (u == b)
9 result = 1;
10 else if (b < u && u <= c)
11 result = (c-u)/(c-b);
12
13 return result;
14 }
Motor de inferencia
Una vez que se ha realizado el proceso de fuzzicacin, para determinar el
grado de pertenencia de las variables a los conjuntos difusos, comienza el proceso
de inferencia o razonamiento para la toma de decisiones. Uno de los motores de
inferencia ms comunes es el basado en reglas difusas de tipo SI-ENTONCES. Este
tipo de reglas est formado por un conjunto de antecedentes conectados mediante
operadores lgicos (AND, OR, NOT) y uno o varios consecuentes. Cada antecedente
a su vez consiste en una variable difusa y el rango de etiquetas lingsticas que sta
debera tomar para que se produzca la activacin de la regla. Veamos un ejemplo:
Mtodo Mtodo
Indirecto Simplificado
Regla 1: IF x es A1 e y es B1 THEN z es C1
Regla 2: IF x es A2 e y es B2 THEN z es C2
A1 B1 C1
1 1 1
w1
0 x 0 y 0 z
A2 B2 C2
1 1 1
w2
0 x 0 y 0 z
x0 y0
C1 C2
1
0 z
z0 (centroide)
Proceso de defuzzicacin
El proceso de defuzzicacin es necesario slo si se desea un valor real o crisp
de salida en nuestro sistema (valor z0 en la gura 28.23). El proceso ms sencillo y
ampliamente utilizado se conoce como mtodo de centro de gravedad, en el que se
obtiene el punto central del rea generada en el consecuente de la regla.
Si tenemos en cuenta de nuevo el ejemplo anterior, donde se plantea el mtodo de
razonamiento de Mandani, los mtodos de defuzzicacin ms comunes son:
c (z)z dz
z0 =
c (z) dz
z0 = m
ax c (z)
z
Tal como se vio en las las secciones anteriores, existen diferentes alternativas para
denir los conjuntos difusos correspondientes a las etiquetas lingsticas especicadas
en el listado anterior: trapezoidales, triangulares, mediante curvas, etc. Queda a
eleccin del lector, elegir una de las opciones en funcin del comportamiento del
sistema deseado.
Por otro lado, las variables de salida son las que se incluirn en el consecuente
de las reglas y determinarn las acciones bsicas de cada individuo: acercarse, huir,
atacar y defenderse. A continuacin se detalla una breve descripcin sobre cada una
de ellas:
Si nalmente optamos por realizar una de las cuatro acciones posibles en cada
turno: acercarse, huir, atacar o defenderse; tan slo sera necesario estudiar la
activacin de las reglas y actuar segn aquella que tenga un grado de pertenencia
mayor.
Una vez implementando el sistema difuso, sera interesante realizar modicacio-
nes del dominio de denicin de cada variable para poder observar como vara el
comportamiento de los personajes sin la necesidad de variar la lgica de programa.
La inclusin de nuevas reglas o modicacin de las existentes, tambin contribuye al
C28
cambio de conductas.
[846] CAPTULO 28. INTELIGENCIA ARTIFICIAL
Seleccin de individuos
NO Solucin optima?/
Condicin de parada
SI
viceversa; el segundo de ellos incrementando o decrementando los valores numricos Reales 2.1 3.4 5.6 4.5 3.2 9.1 8.8
con una cantidad adicional; nalmente, el ltimo mtodo de representacin permite el Caracteres A C A A B C A
intercambio de letras.
Figura 28.29: Modos de represen-
tacin para secuencias genticas
Operadores Genticos
Operadores de seleccin
0.125 Seleccin por rueda de ruleta: En este caso, el individuo tambin tiene
0.25 una probabilidad de ser seleccionado. Sin embargo, pi es proporcional a la
0.125 diferencia entre su aptitud y la del resto de soluciones. Se puede entender como
una ruleta en la que cada sector representa la probabilidad de cada individuo
para ser seleccionado. Cuanto mayor sea la probabilidad, mayor ser el sector
0.5 para ese individuo. Cada vez que se gira la ruleta y se detiene en alguno de
los sectores se realiza la seleccin de uno de los miembros. A medida que se
obtengan nuevas generaciones, las soluciones deberan ir convergiendo hacia
un punto en comn de tal forma que la diferencia de aptitud entre los individuos
debe ser menor y la probabilidad de ser seleccionados muy similar.
Figura 28.30: Seleccin gentica
mediante el mtodo de ruleta. Cada Seleccin escalada: este mtodo soluciona el problema planteado en el punto
sector representar la probabilidad anterior, en el que los miembros de una poblacin poseen valores de aptitud
de un individuo de ser seleccionado similares. Esto suele suceder en generaciones posteriores y no es conveniente
hasta entonces utilizarlo. Con este tipo de seleccin se permite que la funcin
de aptitud sea ms discriminatoria.
Seleccin por torneo: En este mtodo se eligen subgrupos de la poblacin y se
enfrentan unos con otros. El vencedor de la competicin es seleccionado para
el proceso de reproduccin.
Seleccin por rango: En este mtodo se realiza un ranking de individuos en
base a un rango numrico asignado a cada uno de ellos en funcin de la aptitud.
La ventaja principal de este mtodo es que evita que individuos con un grado de
aptitud demasiado elevado ganen dominancia sobre los que tienen un valor ms
bajo. Esto es crtico sobre todo al principio, ya que soluciones que podran ser
descartadas en las primeras generaciones podran evolucionar progresivamente
hasta soluciones ptimas globales.
Seleccin generacional: Solo pasa a la siguiente generacin la descendencia de
la generacin anterior. En ningn momento se copian individuos.
Seleccin por estado estacionario: En las primeras generaciones se realiza una
seleccin menos compleja, poco rigurosa y menos discriminatoria, mientras que
en las sucesivas ocurre justo lo contrario. De esta forma se reduce el tiempo total
de clculo en la bsqueda de soluciones.
C28
[850] CAPTULO 28. INTELIGENCIA ARTIFICIAL
Mutacin
La mutacin consiste en alterar de forma aleatoria una o varias partes de la
solucin. En el caso de la naturaleza las mutaciones pueden llegar a ser letales, sin
embargo, contribuyen a la diversidad gentica de la especie. No es conveniente utilizar
con una frecuencia alta este operador para no reducir el AG a una bsqueda aleatoria;
tan slo es aconsejable cuando el algoritmo est estancado. Una buena frecuencia de
mutacin es 1/100 o 1/1000.
Para cada uno de los genes que constituyen una solucin se genera un nmero Original 1 0 1 1 0 0 1
Cruce (Crossover)
Este es el operador principal en un AG y el que se utiliza con mayor frecuencia. Figura 28.31: Ejemplo de muta-
Consiste en el intercambio gentico entre dos cromosomas; para ello se escogen dos cin en una cadena binaria
de los miembros elegidos en el proceso de seleccin y se intercambian algunas de las
partes de la cadena formada por los genes. Existen diferentes tipos de operadores de
cruce:
Adems de elegir los operadores genticos que se van a emplear hay que
determinar tambin su frecuencia de uso. Dependiendo de la fase o etapa de la
bsqueda en la que se encuentre un AG, es conveniente emplear unos tipos u otros.
En un principio, los mtodos ms ecaces son la mutacin y el cruce; posteriormente,
cuando la poblacin o gran parte de sta converge el cruce no es demasiado til ya
que nos vamos a encontrar con individuos bastantes similares. Por otro lado, si se
produce un estancamiento, y no se consiguen soluciones en las nuevas generaciones
que mejoren el valor de aptitud, la mutacin tampoco es aconsejable ya que se reduce
el AG a una bsqueda aleatoria. En tal caso, es aconsejable utilizar otro tipo de
operadores ms especializados.
Procesamiento paralelo
Se exploran varias soluciones de
forma simultnea
28.2. Tcnicas Fundamentales [851]
Padre
Madre
Descendientes
Padre
Madre
Descendientes
Padre
Madre
Descendientes
Una de las grandes ventajas de los AG es que estos tienen descendencia mltiple
y pueden explorar el espacio de soluciones de forma paralela. A diferencia de los
algoritmos que buscan una solucin siguiendo un nico camino, los AG no se ven
realmente penalizados en el caso de que uno de los caminos no llegue a una solucin
satisfactoria. Gracias a esta ventaja, los AG tienen un gran potencial para resolver
problemas cuyo espacio de soluciones es grande o para resolver problemas no lineales.
[852] CAPTULO 28. INTELIGENCIA ARTIFICIAL
Otra ventaja importante es que los AG son apropiados para manejar mltiples
parmetros simultneamente. Es posible, que el AG no encuentre una solucin ptima
global y pueden existir varias soluciones al problema en cuestin y que algunas de
ellas optimicen algunos de los parmetros de entrada y otras soluciones, optimicen
otros distintos.
Adems, los AG no tienen porque conocer nada sobre el problema a resolver. Mltiples parmetros
De hecho, el espacio inicial de soluciones puede ser generado aleatoriamente en su
Manejo simultneo de mltiples pa-
totalidad, y que estos individuos converjan hacia soluciones vlidas para el problema. rmetros de entrada, hasta encon-
Esta caracterstica es muy importante y hace que los AG sean realmente tiles para trar la conguracin adecuada
resolver problemas en el que se posee un gran desconocimiento sobre las posibles
soluciones y no sera posible denir una de ellas de forma analtica.
Desconocimiento
Limitaciones de los algoritmos genticos Permite llegar a las soluciones de
forma no analtica en caso de que
exista desconocimiento sobre como
Una de las posibles limitaciones de los AG reside en el modo de representacin resolver el problema
de las cadenas de genes. Como se coment anteriormente, el mtodo utilizado debe
tolerar cambios aleatorios que no produzcan una gran cantidad de soluciones o
cadenas que supongan resultados sin sentido. Normalmente, los tres mtodos de
representacin expuestos con anterioridad (representacin mediante cadenas binarias,
cadenas formadas por nmeros enteros o reales, cadenas de letras) ofrecen la
posibilidad de seleccin, mutacin y cruce.
Otro problema con el que nos encontramos en los AG es la denicin de una
funcin de aptitud apropiada. Si la denicin no es correcta puede que el AG sea
incapaz de encontrar las soluciones al problema. Adems, de elegir una funcin de
aptitud apropiada, es necesario tambin congurar el resto de parmetros del AG
correctamente, como por ejemplo, tamao inicial de la poblacin, frecuencia de cruce,
frecuencia de mutacin, etc.
Si el conjunto inicial es demasiado pequeo, es posible que el AG no explore el Representacin
espacio de soluciones correctamente; por otro lado, si el ritmo de cambio gentico es
El modo de representacin elegido
demasiado alto o los procesos de seleccin no hacen su trabajo correctamente, puede debe facilitar la aplicacin de los
que no se alcancen las soluciones apropiadas. operadores genticos
Cuando un individuo destaca los suciente en un principio con respecto al resto
Funcin de aptitud
de elementos de la poblacin, se puede producir una convergencia prematura. El
problema es que este individuo se puede reproducir tan abundantemente que merma Es fundamental disear una funcin
la diversidad de la poblacin. De esta forma el AG converge hacia un ptimo local de aptitud adecuada para encontrar
demasiado pronto y se limita la posibilidad de encontrar ptimos globales. Esto las soluciones deseadas
suele suceder sobre todo en poblaciones pequeas. Para solucionar este problema, los
mtodos de seleccin empleados deben reducir o limitar la ventaja de estos individuos;
los mtodos de seleccin por rango, escalada y torneo son aptos para solucionar este
problema.
Por ltimo, cabe destacar que no es muy aconsejable el uso de AG para problemas Mximos locales
que se pueden resolver de forma analtica, sobre todo por el alto coste computacional Se producen cuando ocurre una
que implican. El uso de AG es apropiado en problemas no lineales, donde el espacio convergencia prematura por la do-
de soluciones es grande y no se tiene una idea clara sobre la/las posibles soluciones. Si minancia de uno de los individuos
esa solucin adems depende de la conguracin de mltiples parmetros que deben
ser ajustados, los AG son idneos para encontrarla.
28.2. Tcnicas Fundamentales [853]
Una vez que el conjunto de soluciones inicial est disponible, debe comenzar
el proceso de seleccin y generar los descendientes. En este ejemplo, se ha elegido
un modo de seleccin por torneo, en el que los cromosomas se enfrentan por pares.
La forma de organizar los enfrentamientos es aleatoria y, al menos, cada cromosoma
debe participar en alguno de los torneos. En este caso, al tener 6 cromosomas, sern
necesarios 6/2 = 3 enfrentamientos. En la tabla 28.2 se puede apreciar como en una
primera ronda, de forma aleatoria, se han enfrentado los pares de cromosomas (2,5) ,
(1,6) y (3,4). Para mantener una poblacin de seis individuos, se ha vuelto a realizar
un segundo torneo (las 3-6) en el que se han enfrentado los pares: (1,5) , (2,3) y (4,6).
Despus de la seleccin se aprecia un incremento notable de la aptitud media en los
miembros de la generacin, aunque se mantiene el valor mximo.
Cuadro 28.2: Seleccin por torneo. Enfrentamiento entre los cromosomas del conjunto inicial
[854] CAPTULO 28. INTELIGENCIA ARTIFICIAL
Q
Q
Q
Q
Q
Q
Q
Q
Sinapsis
Dendrita Axn
Ncleo
Entrada 1 w1
Entrada 2
w2
ai
Salida
w3 h g
...
Entrada n
1 si x t
escalon(x) = 0 si x < t
1 si x 0
signo(x) = -1 si x < 0
Las unidades neuronales tambin pueden funcionar como puertas lgicas [79], tal
como se muestra en la gura 28.42. De esta forma, es posible construir redes que
calculen cualquier funcin booleana de las entradas.
W=1 W=1
W=-1
t = 1.5 t = 0.5 t = -0.5
W=1 W=1
Puerta AND Puerta OR Puerta NOT
Figura 28.42: Unidades con funcin escaln que actan como puertas lgicas.
Por ltimo, cabe mencionar una de las principales caractersticas de las redes
neuronales: su alto nivel de adptabilidad. Mediante procesos de aprendizaje es posible
ajustar los pesos de las conexiones y adaptarse correctamente a cualquier situacin.
Dicho aprendizaje puede ser supervisado o automtico. En el caso del aprendizaje
supervisado se conocen las entradas de la red y como deberan ser las salidas para cada
una de esas entradas. El ajuste de los pesos se realiza a partir del estudio de la salida
real obtenida a partir de las entradas y de la salida esperada, intentando minimizar el
error en la medida de los posible. Por otro lado, el aprendizaje automtico dispone
nicamente de los valores de entrada. Este tipo de algoritmos trata de ajustar los pesos
hasta encontrar una estructura, conguracin o patrn en los los datos de entrada.
1. Aplicaciones generales
Reconocimiento facial
Anlisis de imagen
Modelos nancieros
Perles de mercado-cliente
Aplicaciones mdicas (sntomas y diagnstico de enfermedades)
Optimizacin de procesos industriales
Control de calidad
Deteccin de SPAM en correo electrnico
Reconocimiento de voz
Reconocimiento de smbolos en escritura
C28
Clasicacin de personajes
Sistemas de control y bsqueda de equilibrio: minimizacin de prdidas o
maximizacin de ganancias
Toma de decisiones
Elaboracin de estrategias
Simulaciones fsicas
Estructuras de red
Existen multitud de tipos de estructuras de red y cada una de ellas otorga diferentes
caractersticas de cmputo [79]. Todos ellos se pueden clasicar en dos grandes
grupos: redes de alimentacin progresiva y redes recurrentes. En el primer caso,
las conexiones son unidireccionales y no se forman ciclos. Las neuronas de un nivel
o capa slo pueden estar conectadas con las del nivel siguiente. En la gura 28.43 se
muestra un ejemplo de una red de alimentacin progresiva con dos niveles; dicha red
consta de dos entradas, dos neuronas en la capa oculta y una nica salida. Al no existir
ciclos en las conexiones, el clculo se realiza de forma uniforme desde las unidades
de entrada hasta las unidades de salida.
W13
Entrada 1
W14 H3 W35 Salida
W23 O5
H4 W45
Entrada 2 W24
Figura 28.43: Red de alimentacin progresiva de dos niveles: dos entradas, dos neuronas en la capa oculta
y un nodo de salida
Precisin de la red
no, no tendra memoria a corto plazo; una red de recurrente, a diferencia de las de
N de Neuronas
Nmero de entradas
N de Neuronas
Generalidad
Nmero de salidas
Nmero de niveles
Cantidad de neuronas en cada una de las capas o niveles intermedios. Figura 28.45: Relacin entre n-
mero de neuronas y generalidad de
Cantidad de ejemplos de entrada para entrenar la red la red. Cuanto menor es el nmero
de neuronas ms fcil es conseguir
la generalidad.
28.2. Tcnicas Fundamentales [859]
A las dos primeras cuestiones es fcil encontrar una respuesta. Para la mayora
de problemas conocemos el nmero de entradas y las salidas deseadas. En el caso de
encontrar ciertas dicultades a la hora de elegir el nmero de parmetros de entrada
existe una relacin directa con los patrones que pretendemos aprender en la red.
Parmetros = log2 P
donde P es el nmero de patrones. Por otro lado, si queremos calcular un nmero de
patrones estimado, se puede emplear la siguiente frmula:
P = W 1/
donde W es el nmero de pesos en la capa intermedia y el error mnimo deseado en
la red, dicho error se podra jar en el siguiente intervalo: 0 1/8
Sin embargo, establecer una conguracin adecuada en cuanto al nmero de
niveles y neuronas es ms complicado. Normalmente, la conguracin se establece
mediante un proceso de aprendizaje supervisado por prueba y error. En dicho proceso
se conocen las entradas y las salidas que se deberan obtener ante tales entradas.
Los errores producidos en la salida producen un ajuste de pesos para el correcto
funcionamiento de la red. Si despus del proceso de entrenamiento, no se obtiene
el comportamiento deseado, es necesario modicar la conguracin de la red.
Al disear una red neuronal siempre hay que tratar de encontrar un equilibrio
entre precisin y generalizacin. Precisin se reere a obtener el menor error posible
en la salida ante los casos de entrada y generalizacin a la posibilidad de abarcar el
mayor nmero de casos posibles. Cuando el nmero de neuronas en la capa oculta es
alto, normalmente se consigue una mayor precisin a costa de un incremento de la
complejidad (red ms compleja implica mayor tiempo de entrenamiento).
Bsqueda de Equilibrio Por el contrario, cuando el nmero de neuronas es ms bajo, obtenemos una red
ms simple que nos permite alcanzar una mayor generalizacin. El uso de funciones de
En el diseo de una red neuronal activacin sigmoidales tambin fomenta la generalizacin de la red. Adems se debe
hay que buscar siempre un equi-
librio entre precisin y generaliza- tener especial cuidado a la hora de elegir el nmero de parmetros de entrada; cuanto
cin mayor sea la cantidad de parmetros, mayores distinciones podremos hacer entre
los individuos de una poblacin, pero ms complejo ser alcanzar esa generalidad
deseada.
Evidentemente, cuanto ms complejo sea el problema a tratar, mayor ser tambin
el nmero de neuronas necesarias en la capa oculta. En casi la totalidad de los casos,
la capa oculta necesita un menor nmero de neuronas que la capa de entrada. Segn
el teorema de Kolgomorov [58] "El nmero de neuronas en la capa oculta debe ser
como mximo dos veces el nmero de entradas".
Para la conguracin del nmero de neuronas en la capa oculta se pueden utilizar
algoritmos constructivos. Bsicamente, un algoritmo de este tipo parte de una
conguracin inicial en el que el nmero de neuronas es reducido. Se entrena la red
hasta que el error se mantenga constante. Posteriormente, si no se ha alcanzado el
error mnimo deseado, se puede agregar una neurona ms a la capa intermedia con
un peso pequeo y se repite el proceso anterior. Es importante tener en cuenta, que la
agregacin de neuronas no va a solucionar el problema si: i) el conjunto de datos de
entrada es insuciente, ii) hay datos que no se pueden aprender.
Para el clculo del error de salida en funcin de las entradas se puede utilizar el
error cuadrtico medio:
etotal = e21 + e22 + ... + e2n
donde ei es el error obtenido en la salida para el ejemplo o patrn de entrada i. Para
calcular el error cometido en uno de los patrones:
C28
En esta seccin se explica como se pueden ajustar los pesos de una red mediante
la regla de aprendizaje de Hebb. Para ello supongamos una red sencilla constituida por
dos entradas y una capa de salida formada por una nica neurona. El valor umbral de
activacin de la neurona se va a ajustar de forma dinmica y para ello se considerar
como una entrada ms con peso negativo. El esquema de dicha red se muestra en la
gura 28.46
-W0
X0 1
W1
X1 Salida
O
X2 W2
Para entrenar la red se toman de forma aleatoria los casos expuestos anteriormente
y se introducen en las entradas. Supongamos que inicialmente todos los pesos valen
cero (tal como se coment anteriormente, el valor inicial de los pesos debe ser bajo).
Supongamos tambin, que el valor de incremento/decremento empleado para los pesos
es 1.
Si el caso 1: X1 = 0, X2 = 0 fuera el primero en utilizarse para entrenar la red,
X0 W0 + X1 W1 + X2 W2 = 0, debido a que todos los pesos valen cero. Por tanto,
la neurona de salida no se activa. En realidad es el valor esperado y, por tanto, no se
producen modicacin de los pesos. En realidad, el nico ejemplo o caso que puede
producir un error con todos los pesos a cero, es el caso 4.
Supongamos que el caso 4 es el siguiente ejemplo empleado para entrenar la red.
X1 = 1, X2 = 1 y la salida esperada es 1; sin embargo X0 W0 + X1 W1 + X2 W2 = 0.
Se esperaba una activacin de la salida que no se ha producido y, as, es necesario
modicar los pesos incrementando los pesos de las neuronas activadas (en este caso
todas las entradas):
W0 = W0 + C = 0 + 1 = 1
W1 = W1 + C = 0 + 1 = 1
W2 = W2 + C = 0 + 1 = 1
De forma iterativa se van aplicando los casos de entrada hasta que no se producen
errores en la salida. Finalmente, para este ejemplo, el ajuste apropiado de los pesos es
el siguiente: W0 = 2, W1 = 1, W2 = 2.
X0
H1
X1 O1
X2 H2
X3 O2
H3
X4
Figura 28.47: Esquema de un perceptrn o red neuronal multicapa
Calcular las derivadas parciales del error con respecto a los pesos de salida
(unin de la capa oculta y la de salida)
Calcular las derivadas parciales del error con respecto a los pesos iniciales
(unin de las entradas con la capa oculta)
Ajustar los pesos de cada neurona para reducir el error.
4. FIN REPETIR
5. Continuar hasta minimizar el error.
En el caso de tener un solo nivel, la actualizacin de los pesos en funcin del error
es sencilla:
Error = valor esperado (T ) - valor real obtenido (O)
Si el error es positivo, el valor esperado es mayor que el real obtenido y por tanto ser
necesario aumentar O para reducir el error. En cambio, si el error es negativo habr
que realizar la accin contraria: reducir O.
Por otro lado, cada valor de entrada contribuir en la entrada de la neurona segn
el peso: Wj Ij (peso W y valor de entrada I). De esta forma, si Ij es positivo, un
aumento del peso contribuir a aumentar la salida y, si por el contrario, la entrada Ij
es negativa, un aumento del peso asociado reducir el valor de salida O. El efecto
deseado se resume en la siguiente regla:
Wj = Wj + Ij Error
Salida Oi
Wj,i
dad
aj
cultas
Entrada
Wj,i = Wj,i + aj i
j = g (entradaj ) i Wj,i i
C28
Wk,j = Wk,j + Ik j
Ejercicio propuesto: Dada una red neuronal con una nica neurona oculta,
con patrones de entrada P1 (1 0 1), P2 (1 1 0), P3 (1 0 0), llevar acabo un
proceso de entrenamiento mediante backpropagation para ajustar los pesos
de la red y que sta se comporte como la funcin XOR
Las redes neuronales se han aplicado con frecuencia en el juego de los asteroides,
en el que una nave debe evitar el impacto con asteroides que se mueven de forma
aleatoria es un espacio comn. La red neuronal permite modelar el comportamiento
de la nave para evitar el impacto con los asteroides ms cercanos. En [83] se plantea
una conguracin de red neuronal para solucionar el problema. Dicha conguracin
consta de cuatro entradas, una capa oculta con 8 neuronas y una capa de salida de tres
neuronas, donde cada una representa una posible accin.
Las dos primeras entradas corresponden con la componente x e y del vector entre
la nave y el asteroide ms cercano. La tercera entrada corresponde a la velocidad con
la que la nave y el asteroide ms cercano se aproximan. Finalmente, la cuarta entrada
representa la direccin que est siguiendo la nave.
La salida de la red neuronal son tres posibles acciones: i) aceleracin puntual
(turbo), ii) giro a la izquierda, iii) giro a la derecha. En [83] se encuentra publicada
una posible implementacin al problema.
O#BLC Una vez jados los estados de nuestro problema de la isla, tendremos que enumerar
>B <B >C <C las acciones que se pueden desarrollar en un determinado estado. Pongamos por
OB#LC OCB#L ejemplo el estado LOCB#, en esta situacin las acciones que podemos realizar son:
<O >O <O >O
coger la barca e ir de vacio a IslaB, que representamos mediante la secuencia de
caracteres >B, llevar el lobo a IslaB >L, llevar la oveja >O o llevar la col >C.
#BLOC C#BLO Considerando solamente las acciones vlidas, que no provocan la prdida de algn
elemento, tendremos que en el estado LOCB# slo son correctas las acciones: >B y
<B >B
>O.
B#LOC
En los trminos jados, una solucin a un problema es, precisamente, un camino >C
o secuencia de acciones vlidas que dirige al sistema de un estado inicial a un
estado meta. Al proceso de buscar entre todos los caminos posibles, que parten de
un estado inicial establecido, aquellos que son soluciones se denomina resolucin de L#BOC
problemas mediante tcnicas de bsqueda. Una solucin para el problema de la isla
se puede ver en la gura 28.51.
<O
33 else:
34 return None
35
36 def meta(self,estado):
37 return estado=="#BLOC"
>B >O
ID1* ID2* El rbol de bsqueda es una estructura que permite generar y almacenar todos
LOC#B LC#BO los posibles caminos de un espacio de estado.
Una posible realizacin del concepto de nodo del rbol de bsqueda se muestra
en la clase que se lista a continuacin. En esta clase class NodoArbol se crean un
nodo con los atributos que hemos indicado. Tambin se facilitan los mtodos para la
construccin del camino desde el nodo raiz del rbol a un nodo concreto (lnea 19),
y la creacin de un hijo (lnea 28). Se le ha aadido una valoracin general del nodo
val, que se usar posteriormente.
El algoritmo descrito selecciona, del conjunto de nodos hoja del rbol de bsque-
da, un nodo para analizar. Primero verica que no es una solucin y posteriormente lo
analiza. Este anlisis consiste en obtener a partir del problema, con la funcin sucesor,
el conjunto de nuevos nodos hijos e introducirlos en el rbol de bsqueda como nuevas
hojas al mismo tiempo que el nodo estudiado deja de ser hoja.
De lo descrito anteriormente el proceso de seleccin del nodo hoja a expandir es
de una importancia , ya que de una buena o mala seleccin depender que la bsqueda
de un nodo meta sea ms o menos eciente.
28.3. Algoritmos de bsqueda [869]
Al mecanismo seleccionado para la eleccin del nodo hoja que hay que
expandir se denomina estrategia de bsqueda.
1. Anchura Las hojas son seleccionadas por antiguedad, es decir, los nodos de
menor profundidad.
2. Profundida Las hojas son seleccionadas por novedad, es decir, los nodos de
mayor profundidad.
Una implentacin de las estructuras necesarias para realizar estas dos estrategias
son mostradas en las siguientes clases: AlmacenNodos, PilaNodos y ColaNodos.
10 7
#BLOC
Listado 28.12: Clases de Cola Pila
1 class AlmacenNodos:
[>O,1]
2 """Modela la frontera."""
9 6 3 def __init__(self,nombre="NoName"):
OB#LC 4 self.id=nombre
5 def add(self,elem):
[<B,1] 6 """Introduce un elemento."""
7 pass
8 5
8 def esVacia(self):
O#BLC
9 """Retorna True si no tiene ningun elemento."""
[>L,1]
10 pass
11 def get(self):
6 4 7 4 12 """Retorna un elemento."""
LOB#C OCB#L 13 pass
14 def esta(self,id):
[<O,1] [<O,1] 15 """Retorna True si el elemento con Id esta en la estructura
."""
4 3 5 3
16 pass
L#BOC C#BLO
17
[<C,1] [>L,1]
18 class PilaNodo(AlmacenNodos):
19 """Modela un Almacen como una Pila: Ultimo en entrar, primero
3 2 en salir."""
LCB#O 20 def __init__(self,nombre):
21 AlmacenNodos.__init__(self,nombre)
[<B,1] 22 self.p=[]
23 self.lest=[]
1 1 2 1
24 def esVacia(self):
LC#BO LOC#B
25 return len(self.p)==0
[>O,1] [>B,1]
26 def add(self,elem):
27 self.p.append(elem)
0 0 28 self.lest.append(elem.estado)
LOCB# 29 def get(self):
30 return self.p.pop()
31 def esta(self,estado):
32 return estado in self.lest
Figura 28.56: rbol de bsqueda 33
completo Anchura. 34 class ColaNodo(AlmacenNodos):
35 """ Modela un Almacen como una Cola: Primero en entrar, primero
en salir."""
36 def __init__(self,nombre):
37 AlmacenNodos.__init__(self,nombre)
C28
38 self.c=[]
39 self.lest=[]
40 def esVacia(self):
41 return len(self.c)==0
[870] CAPTULO 28. INTELIGENCIA ARTIFICIAL
42 def add(self,elem):
43 self.c.insert(0,elem)
44 self.lest.append(elem.estado)
45 def get(self):
46 return self.c.pop()
47 def esta(self,estado):
48 return estado in self.lest
10 7
Listado 28.13: Algoritmo de bsqueda #BLOC
2 if estrategia==PROFUNDIDAD:
8 6 9 6
3 frontera=PilaNodo(Profundidad) LOB#C OB#LC
4 elif estrategia==ANCHURA:
5 frontera=ColaNodo(Anchura) [<L,1] [<B,1]
6 n=NodoArbol(None,None,prob.estadoInit(),{Prof:0})
7 5
7 solucion=None O#BLC
8 frontera.add(n)
9 while (not frontera.esVacia()) and (solucion==None): [>C,1]
10 n=frontera.get()
11 if prob.meta(n.estado): 6
OCB#L
4
12 solucion=n
13 else: [<O,1]
14 for ac,est in prob.sucesor(n.estado):
15 if not frontera.esta(est): 4 3 5 3
L#BOC C#BLO
16 h=GeneraNodo(n,ac,est)
17 frontera.add(h) [<C,1] [>L,1]
18 return solucion
3 2
LCB#O
Sobre la eciencia de estas estrategias hay que destacar que la estrategia de [<B,1]
una complejidad temporal (tiempo de ejecucin) del orden O(bn ) para un problema [>O,1] [>B,1]
con b acciones en cada estado y una logitud de n acciones hasta un estado meta. La 0 0
estrategia de profundidad tiene el problema que se puede perder si el rbol de bsqueda LOCB#
Inicial
Ahora ya tenemos un valor para decidir que nodo hoja coger, el menor valor de
costo de los nodos hoja, o que el conjunto de nodos hojas estn ordenados de forma
n C(n)
creciente por el valor del costo.
(Acc,val) Con el objeto de generalizar esta estrategia y poder utilizar las estructuras
n1 C(n1)=C(n)+val
posteriormente, se ha denido una nueva estructura de almacen como una lista
ordenada por una valor del nodo. La estructura elegida para realizar la cola ordenada
ha sido el montculo, una estructura vectorial donde la posicin inicial del vector se
Figura 28.58: Costo asociado a un encuentran siempre el elemento menor.
nodo.
10 def add(self,elem):
11 print elem
12 valor,n=elem
13 heapq.heappush(self.p,(valor,n))
[872] CAPTULO 28. INTELIGENCIA ARTIFICIAL
14
15 def get(self):
16 return heapq.pop(self.p)
A la estrategia que selecciona un nodo hoja con valor de costo menor se denomina
estrategia de costo uniforme.
Como ejemplo denimos un nuevo problema. El problema es el conocido como 8
puzzle donde se tiene un artilugio con 8 piezas numeradas del 1 al 8 y un espacio sin
pieza. Las piezas se puden deslizar al espacio en blanco y as cambiar la conguracin
del puzzle. Si se representa una conguracin concreta del 8 puzzle con una cadena
de 9 caracteres donde los 3 primeros representan la primera la, y as sucesivamente
y el espacio en blanco por el caracter B, y consideramos que los movimientos son los
del espacio blanco. Si consideramos que de los cuatro movimientos posibles el costo
de los movimientos hacia arriba e izquierda van a costar 2 unidades frente a la unidad
de los movimientos abajo y derecha.
El problema propuesto es denido en el siguiente cdigo ejemplo.
47 else:
48 return None
49
50 def meta(self,estado):
51 return estado==self.estadoFin
12345678B
El coste de esta solucin es de 23 que podremos asegurar que es el menor coste
posible, o en otras palabras, que no existe otra solucin con un coste menor.
AL considerar el coste que se sufre al generar un nodo, hemos asegurado que el
resultado que obtenemos es el mejor de todos; pero no se ha solucionado el problema
de la complejidad. La complejidad de la estrategia de costo uniforme se puede
equiparar a la de estrategia en anchura haciendo que cada paso sea un incremento
Ic del costo O(b(n/Ic) ).
Considerando que la mejor estrategia, siempre que no se pierda, es la estrategia
en profundidad una opcin muy interesante podra ser combinar la estrategia de costo
uniforme con algo parecida a la estrategia en profundidad. Para poder llevar acabo esta
estrategia deberamos ser capaces de decidirnos por la accin que mejor nos lleve a
C28
una solucin. En otras palabras, tendramos que ser capaces de poder evaluar el coste
que nos queda por realizar desde la situacin actual a una meta.
[874] CAPTULO 28. INTELIGENCIA ARTIFICIAL
Este valor del costo que nos queda por realizar desde un estado para alcanzar un
estado meta se denomina valor de la heurstica.
Para aadir esta heurstica modicaremos la denicin de un problema incorpo-
rando una nueva funcin que evalue el valor de la heurstica. Dado un estado n la
funcin heuristica h(n) ser el valor estimado del costo que queda por realizar hasta
un estado meta.
22 n=0
23 for i in range(len(estado1)):
24 if estado1[i]<>estado2[i]:
25 n=n+1
26 return n
27 def movDistintas(self,estado1,estado2):
28 n=0
29 for i in range(len(estado1)):
30 if estado1[i]<>estado2[i]:
31 py1=i/3
32 px1=i %3
33 pc=estado2.find(estado1[i])
34 py2=pc/3
35 px2=pc %3
36 n=abs(px2-px1)+(py2-py1)
37 return n
38
39 def setHeuristica(self,h=DESCOLOCADAS):
40 self.h=h
41
42 def heuristica(self,estado):
43 if self.h==DESCOLOCADAS:
44 return self.posDistintas(estado,self.estadoFin)
45 elif self.h==CUNIFORME:
46 return 0
47 else:
48 return self.movDistintas(estado,self.estadoFin)
Llegados a este punto, y para combinar la informacin del costo desarrollado con
la del costo por desarrollar o funcin heurstica, deniremos el valor del nodo como
la suma del costo ms el valor de la funcin heurstica.
12 return NodoArbol(n,ac,est,{Prof:prof,Coste:coste,h:h},val
)
13
14 def BuscaSolucionesA(prob,estr=A):
15 frontera=AlmacenOrdenado()
16 h=prob.heuristica(prob.estadoInit())
17 n=NodoArbol(None,None,prob.estadoInit(),{Prof:0,Coste:0,h
:h},0)
18 solucion=None
19 frontera.add((n.val,n.estado,n))
20 while (not frontera.esVacia()) and (solucion==None):
21 n=frontera.get()
22 if prob.meta(n.estado):
23 solucion=n
24 else:
25 for ac,est in prob.sucesor(n.estado):
26 if not frontera.esta(est):
27 h=GeneraNodo(prob,n,ac,est,estr)
28 frontera.add((h.val,h.estado,h))
29 return solucion
Las operaciones bsicas denidas son: aadir un arco,un nodo y obtener los
sucesores de un nodo. Segn sea el acceso de un nodo a otro, encontraremos nodos
donde solo es posible en una direccin que denominaremos grafos dirigidos; en caso
contrario, tendremos grafos no dirigidos. Esta diferencia la introducimos en la clase
grafo mediante el valor tipo de la clase, que por defecto ser dirigido.
Denido la estructura de grafo, podemos construir un problema como se coment
en 28.3.1 para lo que denimos la clase ProblemaGrafo.
La incorporacin de la heurstica al problema del grafo se muentra en el siguiente Figura 28.63: Heursticas para la
cdigo. estrategia A*.
28.4. Planicacin de caminos [879]
14 if m==None:
15 self.CreaMap(self.tablero[0],self.tablero[1])
16 else:
17 self.mapa=m
18
19 def CreaCelda(self,color):
20 s=pygame.Surface((self.celda[0],self.celda[1]),0,16)
21 s.fill(color)
22 pygame.draw.rect(s,(255,181,66),pygame.Rect(0,0,20,20),2)
23 return s
24
25 def AnadeCelda(self,color):
26 self.elementos.append(self.CreaCelda(color))
27
28 def CreaMap(self,w,h):
29 self.mapa=[]
30 for y in range(h):
31 self.mapa.append([])
32 for x in range(w):
33 self.mapa[y].append(0)
34
35 def PintaMapa(self):
36 for y in range(self.tablero[1]):
37 for x in range(self.tablero[0]):
38 rect=pygame.Rect(x*self.celda[0],y*self.celda[1],
self.celda[0],self.celda[1])
39 self.s.blit(self.elementos[self.mapa[y][x]],rect)
40
41 def ModificaMapa(self,celdaIndx,infor):
42 titulo=pygame.display.get_caption()
43 pygame.display.set_caption(infor)
44 NoFin=True
45 while NoFin:
46 for event in pygame.event.get():
47 if event.type == pygame.KEYDOWN and event.key ==
pygame.K_ESCAPE:
48 NoFin=False
49 pos=pygame.mouse.get_pos()
50 pos=pos[0]/self.celda[0],pos[1]/self.celda[1]
51 b1,b2,b3= pygame.mouse.get_pressed()
52 rect=pygame.Rect(pos[0]*self.celda[0],pos[1]*self.celda
[1],self.celda[0],self.celda[1])
53 if b1:
54 self.s.blit(self.elementos[celdaIndx],rect)
55 self.mapa[pos[1]][pos[0]]=celdaIndx
56 elif b3:
57 self.s.blit(self.elementos[0],rect)
58 self.mapa[pos[1]][pos[0]]=0
59 pygame.display.flip()
60 pygame.display.set_caption(titulo[0])
61
62 def SalvaMapa(self,fn):
63 f=csv.writer(open(fn,"wr"),delimiter=,)
64 for l in self.mapa:
65 f.writerow(l)
66
67 def CargaMapa(self,fn):
68 f=csv.reader(open(fn,rb),delimiter=,)
69 self.mapa=[]
70 for r in f:
71 self.mapa.append([int(x) for x in r])
72 def DefMuros(self):
73 self.ModificaMapa(1,Definir Muros)
74 def Entrada(self):
75 self.ModificaMapa(2,Definir Entrada)
76 def Salida(self):
77 self.ModificaMapa(3,Defir Salida)
C28
[882] CAPTULO 28. INTELIGENCIA ARTIFICIAL
28.5.1. Qu es un agente?
No existe una denicin plenamente aceptada por la comunidad cientca del
concepto agente inteligente, siendo la ms simple la de Russell y Norvig [79],
que fue presentada en la seccin 28.1.3 y que conviene recordar aqu: un agente
es una entidad que percibe y acta sobre un entorno (vea la gura 28.7). En
trminos matemticos esta denicin lleva a pensar en un agente como una funcin
(el comportamiento) que proyecta percepciones en acciones. Cabe notar, que esta
denicin es tan amplia, que permite que a numerosos sistemas software se les pueda
asignar la etiqueta de agente, por otra parte hay que destacar que existe un gran inters
tanto acadmico como industrial en ello (vea la gura 28.70).
Figura 28.70: Desde que el Profe-
sor Nick Jennings enunciara la fra- Para delimitar un poco ms el concepto de agente, comienzan a aparecer en la
se Los agentes constituyen el pr- literatura una serie de calicativos cuyo objetivo es denotar aquellas propiedades que
ximo avance ms signicativo en el debe cumplir un agente. De este modo Wooldridge y Jennings [104] denen a un
desarrollo de sistemas y pueden ser agente como un sistema capaz de actuar de forma autnoma y exible en un entorno,
considerados como la nueva revo-
lucin en el software, muchos son
donde exible es entendido como:
los que se han subido al carro in-
C28
dicando que sus aplicaciones son Reactivo, con capacidad de respuesta a cambios en el entorno donde est
agentes. Los agentes estn de mo- situado.
da.
[884] CAPTULO 28. INTELIGENCIA ARTIFICIAL
Proactivo, con capacidad de decidir cmo comportarse para cumplir sus planes
u objetivos.
Social, con capacidad de comunicacin con otros agentes.
As, para que un software pudiera ser considerado un agente debera de tener las
caractersticas antes expuestas. No obstante, a lo largo de los aos, los investigadores
del campo han usado nuevas caractersticas para calicar a los agentes, a continuacin
se citan algunas de las recopiladas por Franklin y Graesser [36]:
puede determinar con precisin el estado que se alcanza desde el estado actual y
la accin del agente. Los entornos reales generalmente son estocsticos, siendo
lo preferible tratar con entornos deterministas.
[886] CAPTULO 28. INTELIGENCIA ARTIFICIAL
Estudiar el entorno.
Analizar que es lo qu debe ser capaz de hacer el agente. Estudiar cuales son
los comportamientos deseados y cuando ocurren en funcin del entorno.
Asociar a cada comportamiento una funcionalidad. Establecer que es lo que
debe ocurrir como accin asociada a cada comportamiento.
Relacionar los comportamientos. Establecer las conexiones que existen entre
Figura 28.74: Pasos en el diseo
los comportamientos, es decir como se conectan y cuando ocurrren. de agentes empleando los compor-
tamientos como gua de diseo.
El agente reactivo, en alguna de sus variantes, es el ms fcil de construir para
programar agentes basados en comportamientos, ya que realiza tareas sencillas y
de una forma rpida. Es por esto, por lo que resulta ms til en el campo de los
videojuegos. El esquema general del programa de agente reactivo es el que se muestra
en la gura 28.75.
Como se puede observar su funcionamiento se basa en una recepcin de percep-
ciones del entorno (proporcionados por los sensores) que al ser interpretada produce
un cambio en el estado interno del agente, lo cual lleva asociada la seleccin del pro-
cedimiento o rutina con la que se debe reaccionar en ese estado a esa percepcion, y
por ltimo se produce la reaccin propiamente dicha. sta consiste en la ejecucin
del procedimiento o rutina. En este esquema de funcionamiento no hay ningn meca-
nismo explcito de representacin del conocimiento ni hay procesos de manipulacin
sobre el mismo, es esto lo que simplica el diseo y construccin del agente, as como
su uso con tiempos de respuesta inmediatos.
28.5. Diseo basado en agentes [889]
estado INTERPRETAR(percepciones;
regla ELEGIR-REGLA(estado,reglas);
accion DISPARO(regla);
return accion;
Este tipo de agente se suele disear de forma parecida a los sistemas basados en
reglas, en los que existe una base de reglas y un motor de inferencia. Cada regla enlaza
una percepcin con una accin y el sistema de inferencia determina que regla ejecutar
en funcin de la percepcin recibida.
Los agentes reactivos ms usados en el campo de los videojuegos son los agentes
reactivos basados en modelos. Para el diseo de este tipo de agentes se emplean
autmatas de estados nitos (en la Seccin 28.1.4 ya se introdujo la idea). Se divide el
comportamiento general del objeto o personaje virtual del videojuego en un nmero
nito de estados, cada uno de ellos corresponde a una situacin del entorno, que tendr
asociado un comportamiento particular del personaje y se establecen las transiciones
que pueden ocurrir entre ellos y las condiciones o reglas que se deben cumplir
para pasar de un estado a otro de una manera determinista, lo cual implica que sea
predecible el comportamiento.
En la gura 28.76 se muestra cual es el esquema de funcionamiento del agente
reactivo basado en un atmata de estados nitos (FSM (Finite State Machine)). El
funcionamiento de este agente se basa en a partir del estado actual, almacenado en
la memoria, comprobar que regla de las posibles reglas que le permiten cambiar de
estado puede ser disparada en funcin de las percepciones del entorno recibidas. El
disparo de esa regla le permite cambiar de estado y ejecutar la accin asociada al
nuevo estado que se alcance. Una vez ejecutada esta accin hay que convertir el estado
alcanzado como estado actual. Conviene notar que solo ser posible disparar una nica
regla y que por lo tanto en cada instante el autmata se encontrar en un nico estado.
La eleccin de los autmatas como arquitectura de funcionamiento se debe a que:
regla ELEGIR-REGLA(percepcion,memoria,reglas);
estado REALIZAR-CAMBIO-ESTADO(memoria,regla);
accion EJECUTAR-ACCION(estado);
memoria ACTUALIZAR-ESTADO(estado);
return accion;
Figura 28.76: Visin abstracta del funcionamiento de un agente reactivo basado en un atmata de estados
nitos (FSM).
No es objetivo de esta seccin dar una denicin formal del autmata, para el
propsito de este curso basta con saber que es un modelo de comportamiento de un
sistema o un objeto complejo y que se compone de estados, eventos y acciones. Un
estado representa una situacin. Un evento es un suceso que provoca el cambio de un
estado a otro. Una accin es un tipo de comportamiento.
a,b
a,b,c
a q1 c
q3
b,c
q2 a,b,c
q0
Figura 28.77: Autmata que reconoce el lenguaje a(a|b) |(b|c)(a|b|c) , p.e. la cadena abb pertenecera
al lenguaje que reconoce el autmata, sin embargo la cadena acc no pertenecera a dicho lenguaje.
Shadow (Blinky)
Si han pasado
2 seg
Persig iendo Huyendo
Inactivo
Perseguir(); Si Comecocos ha
Huir();
Comido una pastilla
o punto especial
Si Comecocos lo traga
Figura 28.78: Comportamiento del fantasma Shadow (Blinky) del juego del Comecocos (PacMan),
modelado por medio de una mquina de Moore.
estado_siguiente = matriz[estado_actual][entrada]
Esto no es ms que una implementacin de la tabla de transiciones del autmata.
La matriz asociada al autmata de la gura 28.77, es la que se muestra en la gura
28.79. El cdigo en lenguaje C, que corresponde a la implementacin del autmata de
la gura 28.77 de esta forma, es el que se muestra a continuacin.
a b c
Listado 28.24: Autmata de la gura 28.77 (tabla transicin).
q0 q1 q2 q2
1 #include <stdio.h>
2 q1 q1 q1 q3
3 /* Se define la Tabla de Transicion del automata */
4 q2 q2 q2 q2
5 #define N 4 //numero de filas de la tabla de transicion
6 #define M 3 //numero de columnas de la tabla de transicion q3 q3 q3 q3
7
8 int TablaTrans[N][M] = { 1,2,2, Figura 28.79: Tabla de Transicin
9 1,1,3, para el autmata de la gura 28.77.
10 2,2,2,
11 3,3,3};
12
13 int AFD() {
14 int estado_actual, estado_siguiente;
15 int c;
16
17 estado_actual=0;
18 c=getchar();
19
20 while (c!=\n) {
21 switch (c) {
22 case a: estado_siguiente=TablaTrans[estado_actual][0];
23 break;
24 case b: estado_siguiente=TablaTrans[estado_actual][1];
25 break;
26 case c: estado_siguiente=TablaTrans[estado_actual][2];
27 break;
28 default: return 0;
29 }
30 if (c!=\n) estado_actual=estado_siguiente;
31 c=getchar();
32 }
33 if ((estado_actual==1) || (estado_actual==2)) return 1;
34 else return 0;
35 }
36
37 int main (int argc,char *argv[]) {
38 if(AFD())
39 printf("Es una cadena del Lenguaje\n");
40 else
41 printf("\nNo es una cadena del Lenguaje \n");
42 return 1;
43 }
7
8 int main() {
9 string palabra;
10 int n, i, mensaje=0;
11 StateType CurrentState;
12
13 cout << "Introduzca la cadena a analizar: "; cin >> palabra;
14 n = palabra.length(); i = 0;
15 CurrentState = q0;
16 while (i<=n-1) {
17 switch(CurrentState) {
18
19 case q0:
20 if (palabra[i]==a) CurrentState=q1;
21 else if (palabra[i]==b || palabra[i]==c) CurrentState=q2;
22 else CurrentState=error;
23 break;
24
25 case q1:
26 if (palabra[i]==a || palabra[i]==b) CurrentState=q1;
27 else if (palabra[i]==c) CurrentState=q3;
28 else CurrentState=error;
29 break;
30
31 case q2:
32 if ((palabra[i]==a) || (palabra[i]==b) || (palabra[i]==c
))
33 CurrentState=q2;
34 else CurrentState=error;
35 break;
36
37 case q3:
38 if ((palabra[i]==a) || (palabra[i]==b) || (palabra[i]==c
))
39 CurrentState=q3;
40 else CurrentState=error;
41 break;
42
43 default:
44 if (!mensaje) {
45 cout << "Error alfabeto no valido" << endl;
46 cout << palabra[i-1] << endl;
47 mensaje=1;
48 }
49 }
50 i++;
51 }
52
53 if ((CurrentState==q1) || (CurrentState==q2))
54 cout << "La cadena " + palabra + " pertenece al lenguaje." <<
endl;
55 else cout << "La cadena " + palabra + " NO pertenece al lenguaje.
" << endl;
56
57 return 0;
58 }
2 #include <string>
3
4 using namespace std;
5
[896] CAPTULO 28. INTELIGENCIA ARTIFICIAL
74 i = 0;
75 ejemplo.Inicializar();
76
77 while (i<=n-1) {
78 ejemplo.UpdateState();
79 i++;
80 }
81
82 State=ejemplo.Informar();
83 if ((State==q1) || (State==q2))
84 cout << "La cadena " + palabra + " pertenece al lenguaje." <<
endl;
85 else cout << "La cadena " + palabra + " NO pertenece al lenguaje.
" << endl;
86 return 0;
87 }
Se ha creado una clase llamada automata, que se usar para crear un objeto
autmata. Esta clase contiene una variable privada, CurrentState, que almacenar
el estado en el que se encuentra el autmata en cada momento. A esta variable so-
lo se podr acceder desde las funciones miembro de la clase, que son: Inicializar(),
ChangeState(), UpdateState() y Informar(). La funcin Inicializar(() es una fun-
cin empleada para poner al autmata en el estado inicial. La funcin ChangeState()
es empleada para cambiar el estado en el que se encuentra el autmata.
La funcin Informar() devuelve el estado en el que est el autmata en un instante
cualquiera. Por ltimo, la funcin UpdateState() es la que implementa la tabla de
transiciones del autmata. Para conseguir el funcionamiento deseado lo que se har
ser crear un objeto de la clase automata, posteriormente ser inicializado, luego se
consultar la tabla de transiciones (ejecuciones de la funcin UpdateState()) mientras
la cadena que se est analizando tenga elementos, momento en el que se consultar el
estado en el que se queda el autmata tras leer toda la cadena para chequear si es un
estado de aceptacin o no.
En todas las implementaciones, la cadena de entrada ser aceptada si, una vez
leda, el estado que se alcanza es uno perteneciente al conjunto de estados nales. En
caso contrario, la cadena no ser un elemento del lenguaje que reconoce el autmata.
los estmulos que disparan cada regla deben ser diferentes, se recuerda la necesidad
de estar en un nico estado en cada instante del funcionamiento del autmata (son
deterministas).
[898] CAPTULO 28. INTELIGENCIA ARTIFICIAL
Los juegos que son desarrollados usando lenguajes de alto nivel, como por ejemplo
C o C++, tpicamente almacenan todos los datos relacionados a cada agente en una
estructura o clase. Esta estructura o clase puede contener variables para almacenar
informacin como la posicin, vida, fuerza, habilidades especiales, y armas, entre
muchas otras. Por supuesto, junto a todos estos elementos, en la estructura o clase
tambin se almacenar el estado actual del autmata, y ser ste el que determine el
comportamiento del agente.
En el siguiente cdigo se muestra cmo se podra almacenar los datos que denen
al agente dentro de una clase.
c r32
r01
r11 r31
c0 c3
r21
c2
Para implementar este tipo de mquinas de estado nito dotadas con salida, o
ms concretamente agentes como los comentados en la seccin anterior para su uso
en videojuegos, existen varias alternativas. stas estn basadas en las que ya se
han presentado en la seccin anterior pero con mejoras. La manera ms simple es
por medio de mltiples sentencias If-Else o la mejora inmediata al uso de estas
sentencias, por medio de una sentencia switch. La sentencia switch(estado_actual)
es una sentencia de seleccin. Esta sentencia permite seleccionar las acciones a
realizar de acuerdo al valor que tome la variable estado_actual, habr un case
para cada estado posible. La forma general de usar esta sentencia para implementar
una mquina de estados es la siguiente:
1 switch (estado_actual)
2 {
3 case estado 1:
4 Funcionalidad del comportamiento del estado 1;
5 Transiciones desde este estado;
6 break;
7 case estado 2:
8 Funcionalidad del comportamiento del estado 2;
9 Transiciones desde este estado;
10 break;
11 case estado N:
12 Funcionalidad del comportamiento del estado N;
13 Transiciones desde este estado;
14 break;
15 default:
16 Funcionalidad por defecto;
17 }
Listado 28.29: Implementacin del autmata que simula el comportamiento del fantasma en el
juego del ComeCocos.
1 enum StateType{Inactivo, Huyendo, Persiguiento};
2
3 void Ghost::UpdateState(StateType CurrentState, GhostType GType)
4 {
5
6 switch(CurrentState)
7 {
8
9 case Huyendo:
10 Huir(GType);
11 if (PacManTraga())
12 {
13 ChangeState(Inactivo);
14 }
15 else if (Temporizador(10)) ChangeState(Persiguiendo);
16 break;
17
18 case Persiguiendo:
19 Perseguir(GType);
20 if (PacManComePastilla()) ChangeState(Huyendo);
21 break;
22
23 case Inactivo:
24 Regenerar();
25 if (Temporizador(2)) ChangeState(Persiguiendo);
26 break;
27 }
28 }
Figura 28.81: Los juegos de fran-
cotiradores son juegos con gran Otro ejemplo ms sobre esta forma de implementar los autmatas, se muestra en
aceptacin (p.e. Sniper Elite V2), el siguiente cdigo, que corresponde a la implementacin de la mquina de estados
probablemente por estar conectados
C28
Listado 28.30: Implementacin del autmata que simula el comportamiento de un soldado que
se enfrenta a un francotirador en un juego de tipo FPS (p.e. Sniper Elite V2)
1 enum StateType{Patrullando, Persiguiendo, Disparando, Huyendo,
Inactivo};
2
3 void Soldier::UpdateState(StateType CurrentState)
4 {
5 switch(CurrentState)
6 {
7 case Patrullando:
8 Patrullar();
9 if (ObjetivoLocalizado()) ChangeState(Persiguiendo);
10 else (AtaqueRecibido()) ChangeState(Huyendo);
11 break;
12
13 case Persiguiendo:
14 Perseguir();
15 if (ObjetivoVulnerable()) ChangeState(Disparando);
16 else ChangeState(Huyendo);
17 break;
18
19 case Disparando:
20 if (ObjetivoAbatible()) Disparar();
21 else ChangeState(Persiguiendo);
22 break;
23
24 case Huyendo:
25 Huir();
26 if (PuestoaSalvo()) ChangeState(Patrullando);
27 else if (Abatido()) ChangeState(Inactivo);
28 break;
29
30 case Inactivo:
31 Regenerar();
32 if (TranscurridoTiempo()) ChangeState(Patrullando);
33 break;
34 }
35 }
Aunque a primera vista, este enfoque puede parecer razonable por lo menos en
autmatas no complejos, cuando se aplica a juegos, o mejor an, a comportamientos
de objetos ms complejos, la solucin presentada se convierte en un largo y tortuoso
camino. Cuantos ms estados y condiciones se tengan que manejar, ms riesgo hay de
convertir este tipo de estructura en cdigo spaghetti (vea la gura 28.82), por lo que
el ujo del programa ser difcil de entender y la tarea de depuracin una pesadilla.
Adems, este tipo de implementacin es difcil de extender ms all del propsito de
28.5. Diseo basado en agentes [901]
Cuadro 28.6: Tabla de Transicin para el autmata implementado para simular el comportamiento del
fantasma en el juego del Comecocos (vea Figura 28.78.
Listado 28.31: Implementacin del autmata que simula el comportamiento del fantasma en el
juego del Comecocos
1 #include <iostream>
2 #include <string>
3
4 using namespace std;
C28
5
6 #define N 4 //numero de filas de la tabla de transicion
7 #define M 4 //numero de columnas de la tabla de transicion
[902] CAPTULO 28. INTELIGENCIA ARTIFICIAL
8
9 enum StateType {inactivo, persiguiendo, huyendo, error};
10 enum Rule {regenerado, pastillacomida, transcurridotiempo, comido};
11
12 class ghost {
13
14 StateType CurrentState;
15
16 /* Se define la Tabla de Transicion del automata */
17 StateType TablaTrans[N][M];
18
19 public:
20 void Inicializar(void);
21 void ChangeState(StateType State);
22 void UpdateState(Rule Transicion);
23 StateType Informar(void);
24 void Huir(void);
25 void Perseguir(void);
26 void Regenerar(void);
27 void Error(void);
28 };
29
30 void ghost::Inicializar(void) {
31 CurrentState=inactivo;
32 TablaTrans[0][0] = persiguiendo; TablaTrans[0][1] = error;
33 TablaTrans[0][2] = error; TablaTrans[0][3] = error;
34 TablaTrans[1][0] = error; TablaTrans[1][1] = huyendo;
35 TablaTrans[1][2] = error; TablaTrans[1][3] = error;
36 TablaTrans[2][0] = error; TablaTrans[2][1] = error;
37 TablaTrans[2][2] = persiguiendo; TablaTrans[2][3] = inactivo;
38 TablaTrans[3][0] = error; TablaTrans[3][1] = error;
39 TablaTrans[3][2] = error; TablaTrans[3][3] = error;
40 }
41
42 StateType ghost::Informar(void) {
43 return CurrentState;
44 }
45
46 void ghost::ChangeState(StateType State) {
47 CurrentState=State;
48 }
49
50 void ghost::UpdateState(Rule Transicion) {
51 StateType NextState;
52
53 NextState=TablaTrans[CurrentState][Transicion];
54 ChangeState(NextState);
55
56 switch(CurrentState) {
57 case huyendo:
58 Huir();
59 break;
60 case persiguiendo:
61 Perseguir();
62 break;
63 case inactivo:
64 Regenerar();
65 break;
66 default: Error();
67 }
68 }
69
70 /* Funciones de prueba */
71 void ghost::Huir(void) {
72 cout << "El fantasma huye!" << endl;
73 }
74
75 void ghost::Perseguir(void) {
76 cout << "El fantasma te persigue!" << endl;
77 }
78
28.5. Diseo basado en agentes [903]
79 void ghost::Regenerar(void) {
80 cout << "El fantasma se regenera!" << endl;
81 }
82
83 void ghost::Error(void) {
84 cout << "El fantasma esta en un estado de error!" << endl;
85 }
86
87 int main() {
88 ghost shadow;
89 StateType State;
90 string palabra;
91 shadow.Inicializar();
92 cout<<"Introduzca la regla: ";
93 cin>>palabra;
94
95 while (palabra.compare("salir")!=0) {
96 if (palabra.compare("regenerado")==0)
97 shadow.UpdateState(regenerado);
98 else if (palabra.compare("pastillacomida")==0)
99 shadow.UpdateState(pastillacomida);
100 else if (palabra.compare("transcurridotiempo")==0)
101 shadow.UpdateState(transcurridotiempo);
102 else if (palabra.compare("comido")==0)
103 shadow.UpdateState(comido);
104 else cout << "Accion no valida" << endl;
105 cout<<"Introduzca la regla: ";
106 cin>>palabra;
107 }
108
109 State=shadow.Informar();
110
111 if (State==error)
112 cout << "El fantasma no funciona bien." << endl;
113 else cout << "El fantasma funciona perfecto." << endl;
114
115 return 0;
116 }
La idea de este patrn es tener una clase, Contexto (Context class), que tendr
un comportamiento dependiendo del estado actual en el que se encuentre, es decir un
comportamiento diferente segn su estado. Tambin tiene una clase Estado abstracta
(State Class) que dene la interfaz pblica de los estados. En ella, se pondrn
todas las funciones del Contexto cuyo comportamiento puede variar. Luego hay una
implementacin de los estados concretos, que heredarn de la clase Estado abstracto.
La clase Contexto, poseer un puntero sobre el estado actual, que ser almacenado en
una variable miembro de la clase. Cuando se desea que el Contexto cambie de estado,
solo hay que modicar el estado actual.
El uso de este patrn implicar:
Denir una clase contexto para presentar una interfaz simple al exterior.
Denir una clase base abstracta estado.
Representar los diferentes estados del autmata como clases derivadas de la
clase base estado.
Denir la conducta especca de cada estado en la clase apropiada de las
denidas en el paso anterior (derivadas de la clase abstracta estado).
Mantener un puntero al estado actual en la clase contexto.
Para cambiar el estado en el que se encuentra el autmata, hay que cambiar el
puntero hacia el estado actual.
El patrn de diseo State no especica donde son denidas las transiciones entre
estados. Esto se puede hacer de dos formas diferentes: en la clase Context, o en cada
estado individual derivado de la clase State. En el cdigo mostrado anteriormente
se ha realizado de esta segunda forma. La ventaja de hacerlo as es la facilidad que
ofrece para aadir nuevas clases derivadas de la clase State. Su inconveniente es
que cada estado derivado de la clase State tiene que conocer como se conecta con
otras clases, lo cual introduce dependencias entre las subclases.
A modo de resumen, conviene indicar que no hay una nica manera de decribir
estados. Dependiendo de la situacin podra ser suciente con crear una clase abstracta
llamada State que posea unas funciones Enter(), Exit() y Update(). La creacin de
los estados particulares del autmata que se quiera implementar implicara crear una
subclase de la clase State por cada uno de ellos.
Listado 28.32: Implementacin del autmata que simula el comportamiento del fantasma en el
juego del Comecocos.
1 #include <iostream>
2 #include <string>
3
4 using namespace std;
5
6 class FSM;
7
8 // Clase base State.
9 // Implementa el comportamiento por defecto de todos sus mtodos.
10 class FSMstate {
11 public:
12 virtual void regenerado( FSM* ) {
13 cout << " No definido" << endl; }
14 virtual void pastillacomida( FSM* ) {
15 cout << " No definido" << endl; }
16 virtual void transcurridotiempo( FSM* ) {
17 cout << " No definido" << endl; }
18 virtual void comido( FSM* ) {
19 cout << " No definido" << endl; }
20 protected:
21 void changeState(FSM*, FSMstate*);
22 };
23
24 // Automa context class. Reproduce el intefaz de la clase State y
25 // delega todo su comportamiento a las clases derivadas.
26 class FSM {
27 public:
28 FSM();
29 void regenerado() { _state->regenerado(this); }
30 void pastillacomida() { _state->pastillacomida(this); }
31 void transcurridotiempo() { _state->transcurridotiempo(this); }
32 void comido() { _state->comido( this ); }
33
34 private:
35 friend class FSMstate;
36 void changeState( FSMstate* s ) { _state = s; }
37 FSMstate* _state;
38 };
39
40 void FSMstate::changeState( FSM* fsm, FSMstate* s ) {
41 fsm->changeState( s ); }
42
43 // Clase derivad de State.
44 class Inactivo : public FSMstate {
45 public:
46 static FSMstate* instance() {
if ( ! _instance ) _instance = new Inactivo;
C28
47
48 cout << "El fantasma se regenera!" << endl;
49 return _instance; };
50 virtual void regenerado( FSM* );
[906] CAPTULO 28. INTELIGENCIA ARTIFICIAL
51 private:
52 static FSMstate* _instance; };
53
54 FSMstate* Inactivo::_instance = 0;
55
56 class Persiguiendo : public FSMstate {
57 public:
58 static FSMstate* instance() {
59 if ( ! _instance ) _instance = new Persiguiendo;
60 cout << "El fantasma te persigue!" << endl;
61 return _instance; };
62 virtual void pastillacomida( FSM* );
63 private:
64 static FSMstate* _instance; };
65
66 FSMstate* Persiguiendo::_instance = 0;
67
68 class Huyendo : public FSMstate {
69 public:
70 static FSMstate* instance() {
71 if ( ! _instance ) _instance = new Huyendo;
72 cout << "El fantasma huye!" << endl;
73 return _instance; };
74 virtual void transcurridotiempo( FSM* );
75 virtual void comido( FSM* );
76
77 private:
78 static FSMstate* _instance; };
79 FSMstate* Huyendo::_instance = 0;
80
81 void Inactivo::regenerado( FSM* fsm ) {
82 cout << "Cambio de Estado a Persiguiendo." << endl;
83 changeState( fsm, Persiguiendo::instance()); };
84
85 void Persiguiendo::pastillacomida( FSM* fsm ) {
86 cout << "Cambio de Estado a Huyendo." << endl;
87 changeState( fsm, Huyendo::instance()); };
88
89 void Huyendo::transcurridotiempo( FSM* fsm ) {
90 cout << "Cambio de Estado a Persiguiendo." << endl;
91 changeState( fsm, Persiguiendo::instance()); };
92
93 void Huyendo::comido( FSM* fsm ) {
94 cout << "Cambio de Estado a Inactivo." << endl;
95 changeState( fsm, Inactivo::instance()); };
96
97 // El estado de comienzo es Inactivo
98 FSM::FSM() {
99 cout << "Inicializa:" << endl;
100 changeState( Inactivo::instance() );
101 }
102
103 int main() {
104 FSM ghost;
105 string input;
106
107 cout<<"Introduzca regla: ";
108 cin>> input;
109
110 while (input.compare("salir")!=0) {
111 if (input.compare("regenerado")==0)
112 ghost.regenerado();
113 else if (input.compare("pastillacomida")==0)
114 ghost.pastillacomida();
115 else if (input.compare("transcurridotiempo")==0)
116 ghost.transcurridotiempo();
117 else if (input.compare("comido")==0)
118 ghost.comido();
119 else cout << "Regla no existe, intente otra" << endl;
120
121 cout<<"Introduzca regla: ";
28.5. Diseo basado en agentes [907]
Usando sentencias Switch-If/Else. Como pros hay que destacar dos, la primera
es que es muy fcil aadir y chequear condiciones que permitan cambiar de
estado, y la segunda es que permite la construccin rpida de prototipos. La
principal contra es que cuando se tiene un nmero elevado de estados, el
cdigo se puede hacer con relativa rapidez, muy enredado y dcil de seguir
(cdigo spaghetti). Otra contra importante, es que es difcil programar
efectos asociados a la entrada salida de un estado (y se recuerda que esto es
muy habitual en el desarrollo de videojuegos).
Usando una implementacin orientada a objetos. Este mtodo posee como
Pros:
Altamente extensible: Incluir un nuevo estado simplemente implica crear
una nueva clase que hereda de la clase abstracta State.
Fcil de mantener: Cada estado reside en su propia clase, se podr ver
fcilmente las condiciones asociadas a ese estado que le permiten cambiar
de estado sin tener que preocuparse acerca de los otros estados.
Intuitivo: Este tipo de implementacin es ms fcil de entender cuando se
usa para modelar comportamientos complejos.
Como contra principal, hay que destacar el tiempo de aprendizaje de uso de este
mtodo que es superior al anterior.
c0 c1
c02 r32
r01 c11
r11 r31
c01 c04
c0
c12
c03 r21
Una lista de precondiciones. Estas deben ser ciertas para poder aplicar el
operador.
Una lista de proposiciones a aadir. La aplicacin del operador implica aadir
estas proposiciones al estado actual.
Una lista de proposiciones a eliminar. La aplicacin del operador implica
eliminar estas proposiones del estado actual.
Huir
Estado Inicial
Correr
Coger Descansar
Arma
Cargar
Arma
Ponerse a Apuntar
salvo
Curarse Disparar
Estado Objetivo
Jugador Abatido
El plan se ejecutar hasta que se complete, se invalide o hasta que haya un cambio
de objetivo. Si cambia el objetivo o no se puede continuar con el plan actual por alguna
razn, se aborta el plan actual y se genera uno nuevo. Existe tambin la posibilidad de
que cuando exista ms de un plan vlido, el planicador obtenga el de menor coste,
considerando que cada accin acarrea un coste. En cierto modo, tras todo este proceso
lo que hay es una bsqueda de un camino o del camino de menor coste.
En la gura 28.85 se muestra un plan para matar al jugador, en el se pasa del estado
inicial al estado objetivo mediante las acciones: Coger Arma, Cargar Arma, Apuntar
y Disparar.
Con el uso de GOAP:
Mushroom Men: The Spore Wars (Wii) - Red Fly Studio, 2008
Los Cazafantasmas (Wii) - Red Fly Studio, 2008
Silent Hill: Homecoming (X360/PS3) - Double Helix Games / Konami de 2008
Fallout 3 (X360/PS3/PC) - Bethesda Softworks, 2008
Figura 28.86: Los desarrolladores
del juego S.T.A.L.K.E.R.: Shadow Empire: Total War (PC) - Creative Assembly / Sega, 2009
of Chernobyl han usado agentes
reactivos basados en FSM, despus FEAR 2: Project Origin (X360/PS3/PC) - Monolith Productions Bros / Warner,
han empleado agentes basados en 2009
HFSM, para acabar usando agentes
deliberativos basados en planica- Demigod (PC) - Gas Powered Games / Stardock, 2009
dores GOAP jerrquicos.
Just Cause 2 (PC/X360/PS3) - Avalanche Studios / Eidos Interactive, 2010
Transformers: War for Cybertron (PC/X360/PS3) - High Moon Studios /
Activision, 2010
Trapped Dead (PC) - Juegos Headup, 2011
Deus Ex: Human Revolution (PC/X360/PS3) - Eidos Interactive, 2011
Abatir al Jugador
Armarse
Apuntar Disparar
Coger Cargar
Arma Arma
...se espera que un equipo de ftbol particularmente en el ftbol, no es una tarea trivial debido a la complejidad que supone
compuesto de robots fsicos autno- crear agentes autnomos capaces de jugar de una forma similar a la de un contrincante
mos sea capaz de derrotar a un equi-
po de ftbol real. humano.
[914] CAPTULO 28. INTELIGENCIA ARTIFICIAL
Pygame es altamente portable y, por lo tanto, se puede ejecutar sobre una gran
cantidad de plataformas y sistemas operativos. Adems, mantiene una licencia LGPL5 ,
permitiendo la creacin de open source, free software, shareware e incluso juegos
comerciales.
A continuacin se enumeran las principales caractersticas de Pygame:
5 http://www.pygame.org/LGPL
6 http://www.pygame.org/docs/ref/surface.html
[916] CAPTULO 28. INTELIGENCIA ARTIFICIAL
Por otra parte, el mtodo render() contiene las primitivas de Pygame necesarias
para dibujar los aspectos bsicos del terreno de juego. Note cmo se hace uso de las
primitivas geomtricas rect, circle y line, respectivamente, para dibujar las lneas de
fuera y el centro del campo. Adems de especicar el tipo de primitiva, es necesario
indicar el color y, en su caso, el grosor de la lnea.
Tpicamente, el mtodo render() se ejecutar tan rpidamente como lo permitan
las prestaciones hardware de la mquina en la que se ejecute el ejemplo, a no ser que
se establezca un control explcito del nmero de imgenes renderizadas por segundo. Figura 28.91: Resultado del rende-
rizado de un terreno de juego sim-
El siguiente listado de cdigo muestra cmo inicializar Pygame (lnea 14) e plicado mediante Pygame.
instanciar el terreno de juego (lnea 17). El resto de cdigo, es decir, el bucle innito
permite procesar los eventos recogidos por Pygame en cada instante de renderizado. Event-oriented
Note cmo en cada iteracin se controla si el usuario ha cerrado la ventana grca,
capturado mediante el evento QUIT, y se lleva a cabo el renderizado del terreno de La combinacin de un esquema ba-
sado en eventos y una arquitectura
juego (lnea 27) junto con la actualizacin de Pygame (lnea 29). con varias capas lgicas facilita la
delegacin y el tratamiento de even-
La gestin de la tasa de frames generados por segundo es simple mediante la clase tos.
Clock. De hecho, slo es necesario especicar la tasa de frames deseada mediante la
funcin clock.tick(FPS) para obtener una tasa constante.
La gura 28.93 muestra, desde un punto de vista de alto nivel, el diagrama de cla-
ses de la arquitectura propuesta. Como se puede apreciar, la clase SoccerField aparece
en la parte superior de la gura y est compuesta de dos equipos (SoccerTeam), dos
porteras (SoccerGoal), un baln (SoccerBall) y un determinado nmero de regiones
(SoccerRegion).
Por otra parte, el equipo de ftbol est compuesto por un determinado nmero de
jugadores, cuatro en concreto, modelados mediante la clase SoccerPlayer, que a su
vez mantiene una relacin de herencia con la clase MovingEntity. A continuacin se
realizar un estudio ms detallado de cada una de estas clases.
El terreno de juego
SoccerField
1..n 2 2 1
SoccerPlayer MovingEntity
ball, de tipo SoccerBall, que representa al baln usado para llevar a cabo las
simulaciones.
teams, de tipo diccionario, que permite indexar a dos estructuras de tipo
SoccerTeam a partir del identicador del equipo (red o blue).
goals, de tipo diccionario, que permite indexar a dos estructuras de tipo
SoccerGoal a partir del identicador del equipo (red o blue).
3
4 class SoccerField:
5
6 def __init__ (self, width, height):
7
8 self.width, self.height = width, height
9
10 self.surface = pygame.display.set_mode(
11 (self.width, self.height), 0, 32)
12 # Terreno real de juego.
13 self.playing_area = Rect(TOP_LEFT, WIDTH_HEIGHT)
14
15 # Walls.
16 self.walls = []
17 # Clculo de coordenadas clave del terreno de juego.
18 self.walls.append(Wall2d(top_left, top_right))
19 self.walls.append(Wall2d(top_right, bottom_right))
20 self.walls.append(Wall2d(top_left, bottom_left))
21 self.walls.append(Wall2d(bottom_left, bottom_right))
22
23 # Zonas importantes en el campo.
24 self.regions = {}
25 # Clculo de las regiones...
26
27 # Bola.
28 self.ball = SoccerBall(Vec2d(self.playing_area.center),
29 10, 2, Vec2d(0, 0), self)
30
31 # Equipos
32 self.teams = {}
33 self.teams[red] = SoccerTeam(red, RED, 4, self)
34 self.teams[blue] = SoccerTeam(blue, BLUE, 4, self)
35
36 # Porteras.
37 self.goals = {}
38 self.goals[red] = SoccerGoal(
39 red, RED, self.playing_area.midleft, self)
40 self.goals[blue] = SoccerGoal(
41 blue, BLUE, self.playing_area.midright, self)
En la gura 28.94 se muestra la divisin lgica del terreno de juego en una serie
de regiones con el objetivo de facilitar la implementacin del juego simulado, los
comportamientos inteligentes de los jugadores virtuales y el diseo de las estrategias
en equipo.
Internamente, cada regin est representada por una instancia de la clase Soc-
cerRegion, cuya estructura de datos ms relevante es un rectngulo que dene las
dimensiones de la regin y su posicin.
Aunque cada jugador conoce en cada momento su posicin en el espacio 2D, como Ms info...
se discutir ms adelante, resulta muy til dividir el terreno de juego en regiones para El uso de informacin o estructuras
implementar la estrategia interna de cada jugador virtual y la estrategia a nivel de de datos adicionales permite gene-
equipo. Por ejemplo, al iniciar una simulacin, y como se muestra en la gura 28.94, ralmente optimizar una solucin y
cada jugador se posiciona en el centro de una de las regiones que pertenecen a su plantear un diseo que sea ms sen-
cillo.
campo. Dicha posicin se puede reutilizar cuando algn equipo anota un gol y los
jugadores han de volver a su posicin inicial para efectuar el saque de centro.
El baln de juego
12
13 class MovingEntity:
14
15 def __init__ (self, pos, radius, velocity, max_speed,
[922] CAPTULO 28. INTELIGENCIA ARTIFICIAL
Figura 28.94: Divisin del terreno de juego en regiones para facilitar la integracin de la IA.
16 heading, mass):
17
18 self.pos = pos
19 self.radius = radius
20 self.velocity = velocity
21 self.max_speed = max_speed
22 self.heading = heading
23 # Vector perpendicular al de direccin (heading).
24 self.side = self.heading.perpendicular
25 self.mass = mass
Dentro de la clase SoccerBall existen mtodos que permiten, por ejemplo, detectar
cundo el baln sale fuera del terreno de juego. Para ello, es necesario comprobar,
para cada uno de los laterales, la distancia existente entre el baln y la lnea de fuera.
Si dicha distancia es menor que un umbral, entonces el baln se posicionar en la
posicin ms cercana de la propia lnea para efectuar el saque de banda.
5 for w in self.walls:
6 if distToWall(w.a, w.b, self.pos) < TOUCHLINE:
7 self.reset(self.pos)
Por otra parte, el baln sufrir el efecto del golpeo por parte de los jugadores
virtuales mediante el mtodo kick(). Bsicamente, su funcionalidad consistir en
modicar la velocidad del baln en base a la fuerza del golpeo y la direccin del
mismo.
pi Tambin resulta muy interesante incluir la funcionalidad necesaria para que los
jugadores puedan prever la futura posicin del baln tras un intervalo de tiempo. Para
llevar a cabo el clculo de esta futura posicin se ha de obtener la distancia recorrida
por el baln en un intervalo de tiempo t, utilizando la ecuacin 28.7,
1
distancia cubierta
x = ut + at2 (28.7)
2
donde x representa la distancia recorrida, u es la velocidad del baln cuando se
golpea y a es la deceleracin debida a la friccin del baln con el terreno de juego.
Tras haber obtenido la distancia a recorrer por el baln, ya es posible usar esta
informacin para acercar el jugador al baln. Sin embargo, es necesario hacer uso de
la direccin del propio baln mediante su vector de velocidad. Si ste se normaliza y
se multiplica por la distancia recorrida, entonces se obtiene un vector que determina
tanto la distancia como la direccin. Este vector se puede aadir a la posicin del baln
para predecir su futura posicin.
Este tipo de informacin es esencial para implementar estrategias de equipo y
planicar el movimiento de los jugadores virtuales. Dicha funcionalidad, encapsulada
en la clase SoccerBall, se muestra en el siguiente listado de cdigo.
14
15 # La posicin predicha es la actual
16 # ms la suma de los dos trminos anteriores.
17 return self.pos + ut + scalarToVector
[924] CAPTULO 28. INTELIGENCIA ARTIFICIAL
El jugador virtual
8 http://opensteer.sourceforge.net/
[926] CAPTULO 28. INTELIGENCIA ARTIFICIAL
1
MovingEntity SoccerPlayer SteeringBehaviours
GoalKeeper FieldPlayer
1 4
SoccerTeam
Figura 28.98: Diagrama de clases del modelo basado en comportamientos para la simulacin de ftbol.
15 lookAheadTime = 0.0
16
17 if self.ball.velocity.get_length() != 0.0:
18 sc_velocity = self.ball.velocity.get_length()
19 lookAheadTime = toBall.get_length() / sc_velocity
20
21 # Dnde estar la bola en el futuro?
22 target = self.ball.futurePosition(lookAheadTime)
23
24 # Delega en el comportamiento arrive.
25 return self.arrive(target)
Facilidad para llevar a cabo el pase sin que el equipo contrario lo intercepte.
3
Para ello, se puede considerar la posicin de los jugadores del equipo rival y el
tiempo que tardar el baln en llegar a la posicin deseada. Figura 28.99: Esquema de apoyo
Probabilidad de marcar un gol desde la posicin del jugador al que se le quiere de jugadores. El jugador 2 repre-
senta una buena alternativa mien-
pasar.
tras que el jugador 3 representa una
Histrico de pases con el objetivo de evitar enviar el baln a una posicin que mala.
ya fue evaluada recientemente.
28.7.1. Introduccin
Los sistemas expertos son una rama de la Inteligencia Articial simblica que
simula comportamientos humanos razonando con comportamientos implementados
por el diseador del software. Los sistemas expertos se usan desde hace ms de 30
aos, siendo unos de los primeros casos de xito de aplicacin prctica de tcnicas de
C28
Dado que el nmero de hechos que suele cambiar tras cada activacin de una regla
suele ser bajo, una adecuada organizacin de condicionantes permite, a nivel prctico,
dar unos tiempos de ejecucin adecuados. Si se desea ampliar informacin sobre este
asunto, el principal libro de referencia es [39].
9 No confundir con Common LISP (CLISP), otro lenguaje cuya sintaxis tambin se basa en parntesis
anidados.
[932] CAPTULO 28. INTELIGENCIA ARTIFICIAL
6
7 (ficha (equipo "A") (num 111) (pos-x 1) (pos-y 1)
8 (puntos 3) (descubierta 0))
9
10 (ficha (equipo "A") (num 929) (pos-x 1) (pos-y 2)
11 (puntos 3) (descubierta 1))
12
13 (ficha (equipo "B") (pos-x 1) (pos-y 3)
14 (descubierta 0)
15
16 (ficha (equipo "B") (pos-x 2) (pos-y 2) (puntos 3)
17 (descubierta 1))
A primera vista, esta regla puede parecer adecuada, al n y al cabo slo mueve una
cha hacia delante, que es donde estn los contrarios al comenzar la partida. Y como
su prioridad es bastante baja, slo se ejecutar si no hay otra regla de mayor prioridad
(que se supone har una accin mejor). Sin embargo es muy mejorable. Por ejemplo,
10 Los movimientos estn denidos como 1 avanza en X, 2 retrocede en X, 3 avanza en Y y 4 retrocede
en Y.
28.7. Sistemas expertos basados en reglas [933]
pudiera ser que la cha se dirigiera hacia una cha contraria de mayor puntuacin,
como podra ser la cha de 2 puntos del equipo prpura que est en la primera la
del tablero en la gura 28.106. Si se aplica la regla anterior a dicha cha se estaran
perdiendo varios turnos para suicidarla.
As que se podra cambiar la regla por la que aparece en el listado 28.47. En este
caso se han aadido dos condiciones nuevas. La primera (lnea 5) busca la existencia
de una cha contraria (equipo B) en la misma columna (pues es usa la misma
variable ?x1 para los dos campos pos-x en su lnea 5). Y la segunda condicin (lnea
7) comprueba que la cha contraria est arriba de la propia y que tenga menos valor
que esta. Ntese el uso de notacin preja, primero aparece el operador y despus los
operandos.
Puede observarse que la primera vez que aparece una variable se instancia al
valor de un hecho del sistema, y a partir de ah el resto de apariciones en esa y otras
condiciones ya se considera ligada a una constante. En caso de que se cumplan todas
Figura 28.106: Estado de partida las condiciones, la regla se considera activable. Si alguna de las condiciones no puede
para ataque. satisfacerse, se intenta satisfacer de nuevo instanciando las variables con otros hechos
del sistema.
Para evitar el suicidio hay que el elemento condicional not, que comprueba
la no existencia de hechos. Si se desea indicar la no existencia de hechos con
determinados valores constantes en sus campos simplemente se antepone not a la
condicin. Por ejemplo (not (cha (equipo A) (num 111))). Pero si se quieren incluir
comprobaciones ms complejas hay que incluir la conectiva Y, &. A continuacin
se muestra un ejemplo en que se aplicar la una regla de huida del listado 28.49,
quedando como ejercicio la aplicacin a las reglas anteriores de ataque.
Esta regla, sin embargo, adolece los problemas anteriormente comentados: posibi-
lidad de suicidio y aplicacin incluso con chas contrarias muy lejanas (vase estado
de la gura 28.107). Se propone como mejora la regla del listado 28.50.
tener dos reglas, una con una huida para cada lado, que al tener menos condiciones sera ms aplicables.
28.7. Sistemas expertos basados en reglas [935]
hay veces que los programadores se plantean poner reglas que tengan condiciones
excluyentes u ordenar todas las reglas por prioridades. Aunque esta estrategia no es
necesariamente mala, se pierde el potencial de los sistemas expertos basados en reglas,
pues realmente se estara implementando un rbol de decisin determinista.
Otro aspecto interesante es que tambin se pueden crear otros hechos para
facilitar la planicacin de estrategias. Por ejemplo, si una estrategia cambia su
comportamiento cuando pierde las chas de 5 y 6 puntos se aadira la primera regla
del listado 28.51 y el hecho fase 2 se usara como condicin de las reglas exclusivas
de dicho comportamiento.
C28
[936] CAPTULO 28. INTELIGENCIA ARTIFICIAL
I
nternet, tal y como la conocemos hoy en da, permite el desarrollo de videojuegos
en red escalando el concepto de multi-jugador a varias decenas o centenas de
jugadores de forma simultnea. Las posibilidades que ofrece la red al mercado de
los videojuegos ha posibilitado:
937
[938] CAPTULO 29. NETWORKING
Como podemos ver en la gura 29.1 cada funcionalidad necesaria se asocia a una
capa y es provista por uno o varios protocolos. Es decir, cada capa tiene la misin
especca de resolver un problema y, generalmente, existen varios protocolos que
resuelven ese problema de una forma u otra. En funcin de la forma en la cual resuelve
el problema el protocolo, dicha solucin tiene unas caractersticas u otras. En la capa
de aplicacin la funcionalidad es determinada por la aplicacin.
La agregacin de toda esta informacin dividida en capas se realiza mediante
un proceso de encapsulacin en el cual los protocolos de las capas superiores se
encapsulan dentro de las capas inferiores y al nal se manda la trama de informacin
completa. En el host destino de dicha trama de informacin se realiza el proceso
inverso. En la imagen 29.2 podemos ver un ejemplo de protocolo desarrollado para
un videojuego (capa de aplicacin) que utiliza UDP (User Datagram Protocol) (capa
de transporte), IPv4 (Capa de red) y nalmente Ethernet (capa de Acceso a Red).
En las siguientes secciones vamos a dar directrices para el diseo e implementa-
cin del protocolo relacionado con el videojuego as como aspectos a considerar para
seleccionar los protocolos de las capas inferiores.
29.2. Consideraciones de diseo [939]
Una vez implementadas estas tcnicas se debe estimar y medir los parmetros de
networking para ver la efectividad de las tcnicas, estudiar el comportamiento de la
arquitectura, detectar cuellos de botella, etc... Por lo tanto, las estimaciones de retardo
y en general, todos los parmetros de red deben ser incluidos en los tests.
Con estas tcnicas en mente, la responsabilidad del cliente, a grandes rasgos, queda
en un bucle innito mientras se encuentre conectado. En dicho bucle, para cada turno
o intervalo de sincronizacin, enva comandos de usuario al servidor, comprueba si
ha recibido mensajes del servidor y actualiza su visin del estado del juego si es as,
establece los clculos necesarios: prediccin, interpolacin, etc. y por ltimo muestra
los efectos al jugador: grcos, mapas, audio, etc.
El servidor por su parte, actualiza el estado del mundo con cada comando recibido
desde los clientes y enva las actualizaciones a cada uno de los clientes con los cambios
que se perciben en el estado del juego.
En ambos casos se necesita un algoritmo de sincronizacin que permita a clientes
y servidores tener consciencia del paso del tiempo. Existen varios algoritmos y
protocolos empleados para esta sincronizacin (a menudo heredados del diseo y
desarrollo de sistemas distribuidos).
C29
[942] CAPTULO 29. NETWORKING
29.3.2. Cliente-servidor
Con la llegada de los primeros FPS y la adopcin masiva de internet (mediante
mdem) a mediados de los 90, los efectos de la latencia de los que adolece
especialmente el modelo peer-to-peer obligaron a los desarrolladores a buscar otras
alternativas. Uno de los primeros juegos en probar algo diferente fue Quake, de id
Software R
En el modelo cliente-servidor muchos jugadores se conectan a un nico servidor
que controla el juego y mantiene la imagen global de lo que ocurre. Este modelo
elimina muchos de los problemas y restricciones del modelo peer-to-peer: ya no se
requiere mantener una realidad coherente compartida por todos los jugadores, puesto
que nicamente el servidor determina el estado del juego. Los clientes muestran
dicho estado con pequeas modicaciones que representan la evolucin inmediata
del escenario en funcin de la entrada del usuario. La topologa lgica pas de ser una
malla completa a una estrella de modo que mejor la escalabilidad y redujo la latencia;
ya no era necesario esperar al cliente ms lento. Tambin simplica la incorporacin
de nuevos jugadores a una partida en curso.
Este modelo desacopla en muchos casos el desarrollo del juego y las interacciones
entre los jugadores simplicando las tareas de la parte cliente.
29.4.3. Latencia
Es el tiempo que tarda un mensaje desde que sale del nodo origen hasta que llega
al nodo destino. Hay muchos factores que afectan a la latencia: las limitaciones fsicas
del medio, los procedimientos de serializacin de los mensajes, el sistema operativo,
el tiempo que los paquetes pasan en las colas de los encaminadores, los dispositivos
de conmutacin, etc.
Muy relacionado con la latencia est el tiempo de respuesta, que es el tiempo total
desde que se enva un mensaje hasta que se recibe la respuesta. En ese caso se incluye
tambin el tiempo de procesamiento del mensaje en el servidor, adems de la latencia
de ida y vuelta. Un juego con restricciones importantes debera evitar depender de
informacin que se obtiene como respuesta a una peticin directa. Es frecuente utilizar
mensajes con semntica oneway, es decir, que no implican esperar una respuesta en
ese punto.
An hay otra consideracin muy importante relacionada con la latencia: el jitter.
El jitter es la variacin de la latencia a lo largo del tiempo. Cuando se utilizan
redes basadas en conmutacin de paquetes con protocolos best-effort como IP
las condiciones puntuales de carga de la red en cada momento (principalmente
los encaminadores) afectan decisivamente al retardo de modo que puede cambiar
signicativamente entre un mensaje y el siguiente.
La latencia y el jitter permisible dependen mucho del tipo de juego y estn muy
inuenciados por factores psicomotrices, de percepcin visual del espacio, etc. Los
juegos ms exigentes son los que requieren control continuo, como la mayora de
los shotters, simuladores de vuelo, carreras de coches, etc., que idealmente deberan
tener latencias menores a 100 ms. El punto de vista subjetivo aumenta la sensacin de
realismo y provoca que el jugador sea mucho ms sensible, afectando a la jugabilidad.
En otros tipos de juegos como los de estrategia o que dependen de decisiones
colaborativas, el retardo admisible puede ser de hasta 500 ms o incluso mayor.
29.5. Distribucin de informacin [945]
vidor.
[948] CAPTULO 29. NETWORKING
Figura 29.3: Cuando la prediccin calcula una posicin fuera del umbral permitido (fuera de la lnea roja),
el servidor enva una actualizacin (marcas azules) que incluye la posicin, direccin y velocidad real
actual. El cliente recalcula su prediccin con la nueva informacin
a(t1 t1 )2
C29
Figura 29.4: Cuando se reajusta la posicin se producen saltos (lneas discontinuas azules) mientras que
aplicando un algoritmo de convergencia (lneas verdes) el objeto sigue una trayectoria continua a pesar de
no seguir elmente la trayectoria real y tener que hacer ajustes de velocidad
Aunque es una tcnica muy efectiva para evitar que el usuario sea consciente de
los errores cometidos por el algoritmo de prediccin, pueden presentarse situaciones
ms complicadas. Si en un juego de carreras, la ruta predicha implica que un coche
sigue en lnea recta, pero en la real toma un desvo, la ruta de convergencia llevara
el coche a atravesar una zona sin asfaltar, que puede transgredir las propias reglas del
juego. Este escenario es muy comn en los receptores de GPS (Global Positioning
System) que utilizan una tcnica de prediccin muy similar (ver gura 29.5).
Dentro de esta familia tambin se incluyen otro tipo de sockets como SOCK_RAW
que permite al programador acceder a las funciones de la capa de red directamente y
poder, por ejemplo, construirnos nuestro propio protocolo de transporte. El otro tipo
de socket que nos podemos encontrar es SOCK_SEQPACKET que proporciona una
comunicacin able, orientada a conexin y a mensajes. Mientras que las tres familias
anteriores estn soportadas por la mayora de los sistemas operativos modernos, la
familia SOCK_SEQPACKET no tiene tanta aceptacin y es mas difcil encontrar una
implementacin para segn qu sistemas operativos.
Si nos jamos, los tipos de sockets nos describen qu tipo de comunicacin
proveen. El protocolo utilizado para proporcionar dicho servicio debe ser especicado
en el tercer parmetro. Generalmente, se asocian SOCK_DGRAM al protocolo UDP,
SOCK_STREAM al protocolo TCP, SOC_RAW directamente sobre IP. Estos son los
protocolos que se usan por defecto, no obstante, podemos especicar otro tipos de
protocolos siempre y cuando estn soportados por nuestro sistema operativo.
Como ya hemos comentado, cada tipologa de socket nos proporciona una
comunicacin entre procesos con unas determinadas caractersticas y usa unos
protocolos capaces de proporcionar, como mnimo, esas caractersticas. Normalmente,
esas caractersticas requieren de un procesamiento que ser mas o menos eciente en
cuanto a comunicaciones, procesamiento, etc.
Es imprescindible que el desarrollador de la parte de networking conozca el coste
de esas caractersticas de cara a disear un sistema de comunicaciones eciente y que
cumpla con las necesidades del videojuego. La decisin de qu tipo de comunicacin
quiere determina:
Esta decisin, a groso modo est relacionada con si queremos una comunicacin
conable o no, es decir, en el caso de Internet, decidir entre usar un SOCK_STREAM
o SOCK_DGRAM respectivamente y por ende, en la mayora de los casos, entre TCP
o UDP.
El uso de SOCK_STREAM implica una conexin conable que lleva asociado
unos mecanismos de reenvo, ordenacin, etc. que consume tiempo de procesamien-
to a cambio de garantizar la entrega en el mismo orden de envo. En el caso de
SOCK_DGRAM no se proporciona conabilidad con lo que el tiempo de procesa-
miento se reduce.
Qu tipo de comunicacin empleo?, bien, en funcin del tipo de datos que este-
mos transmitiendo deberemos usar un mecanismo u otro. Como norma general todos
aquellos datos en tiempo real que no tiene sentido reenviar irn sobre SOCK_DGRAM
mientras que aquellos tipos de datos mas sensibles y que son imprescindibles para el
correcto funcionamiento deben ir sobre SOCK_STREAM.
Algunos ejemplos de uso de ambos protocolos nos pueden claricar qu usos se le
dan a uno y otro protocolo. En las ltimamente populares conexiones multimedia en
tiempo real, por ejemplo una comunicacin VoIP, se suele usar para la transmisin del
audio propiamente dicho el protocolo RTP (Real Time Protocol) que proporciona una
conexin no conable. Efectivamente, no tiene sentido reenviar una porcin de audio
que se pierde ya que no se puede reproducir fuera de orden y almacenar todo el audio
hasta que el reenvo se produce generalmente no es aceptable en las comunicaciones.
No obstante, en esas mismas comunicaciones el control de la sesin se delega en
el protocolo (SIP (Session Initiation Protocol)) encargado del establecimiento de la
llamada. Este tipo de datos requieren de conabilidad y por ello SIP se implementa
sobre TCP.
Podemos trasladar este tipo de decisiones al diseo de los mdulos de networking
de un determinado videojuego. Una vez decidida la informacin necesaria que se debe
transmitir entre los diversos participantes, debemos clasicar y decidir, para cada ujo
de informacin si va a ser mediante una conexin conable no conable.
Es difcil dar normas genricas por que, como adelantamos en la introduccin,
cada videojuego es diferente, no obstante, generalmente se utilizar comunicaciones
no conables para:
El resto de informacin que no cae en estas dos secciones puede ser susceptible de
implementarse utilizando comunicaciones conables.
Este primer paso de decisin de qu tipo de comunicacin se necesita es
importante ya que determina la estructura del cliente y el servidor.
C29
[954] CAPTULO 29. NETWORKING
struct hostent *gethostbyname(const char *name): Esta estructura nos sirve para
identicar al servidor donde nos vamos a conectar y puede ser un nombre (e.j.
ejemplo.nombre.es), direccin IPv4 (e.j. 161.67.27.1) o IPv6. En el caso de
error devuelve null.
int connect(int sockfd, const struct sockaddr
*serv_addr, socklen_t addrlen): Esta primitiva nos permite realizar la conexin
con un servidor especicado por serv_addr usando el socket sockfd. Devuelve
0 si se ha tenido xito.
ssize_t
send(int s, const void *msg, size_t len, int ags): esta primitiva es utilizada para
mandar mensajes a travs de un socket indicando, el mensaje (msg), la longitud
del mismo (len) y con unas opciones denidas en ags. Devuelve el nmero de
caracteres enviados.
ssize_t
recv(int s, void *buf, size_t lon, int ags): esta primitiva es utilizada para recibir
mensajes a travs de un socket ((s)), el mensaje es guardado en buf, se leen los
bytes especicados por lon y con unas opciones denidas en ags. Devuelve el
nmero de caracteres recibidos.
close(int sockfd): cierra un socket liberando los recursos asociados.
Con estas primitivas podemos escribir un cliente que se conecta a cualquier ser-
vidor e interacciona con l. Para la implementacin del servidor tenemos las mismas
primitivas de creacin, envo, recepcin y liberacin. No obstante, necesitamos algu-
nas primitivas mas:
Veamos una implementacin de ejemplo que nos sirva para inscribir a un personaje
en una partida gestionada por un servidor. En este primer ejemplo un usuario se
desea registrar en un servidor de cara a enrolarse en una partida multijugador. La
informacin de usuario ser expresada por una estructura en C que podemos ver en el
listado 29.1 conteniendo el nick del jugador, su tipo de cuenta y su clave. Este tipo
de informacin es necesario transmitirlo por una conexin conable ya que queremos
que el proceso se realice de forma conable.
29.10. Sockets TCP/IP [955]
20 serverAddr.sin_port = htons(serverPort);
[956] CAPTULO 29. NETWORKING
21
22 bind(serverSocket, (struct sockaddr *)&serverAddr, sizeof(
serverAddr));
23 listen(serverSocket, MAXCONNECTS);
24 while(1)
25 {
26 printf("Listening clients..\n");
27 len_client = sizeof(clientAddr);
28 serviceSocket = accept(serverSocket, (struct sockaddr *) &
clientAddr, &(len_client));
29 printf("Cliente %s conectado\n", inet_ntoa(clientAddr.
sin_addr));
30 new_player = (struct player *)malloc(sizeof(struct player));
31 read_bytes= recv(serviceSocket, new_player,sizeof(struct
player) ,0);
32 printf("Nuevo jugador registrado %s con cuenta %d y password
%s\n", new_player->nick, new_player->account_type,
new_player->passwd);
33 close(serviceSocket);
34 }
35
36 }
Una vez creado y vinculado un socket a una direccin, estamos listos para entrar
en un bucle de servicio donde, se aceptan conexiones, se produce el intercambio de
mensajes con el cliente conectado y con posterioridad se cierra el socket de servicio y
se vuelven a aceptar conexiones. Si mientras se atiende a un cliente se intenta conectar
otro, queda a la espera hasta un mximo especicado con la funcin listen. Si se supera
ese mximo se rechazan las conexiones. En este caso la interaccin se limita a leer los
datos de subscripcin del cliente.
El cliente, tal y como vemos en el listado 29.3, sigue unos pasos similares en
cuanto a la creacin del socket e inicializacin de las direcciones. En este caso se debe
introducir la direccin IP y puerto del servidor al cual nos queremos conectar. En este
caso localhost indica que van a estar en la misma mquina (linea 19). A continuacin
nos conectamos y enviamos la informacin de subscripcin.
En este ejemplo, el diseo del protocolo implementado no podra ser mas
simple, la sintaxis de los paquetes estn denidos por la estructura, la semntica es
relativamente simple ya que el nico paquete que se enva indican los datos de registro
mientras que la temporizacin tambin es muy sencilla al ser un nico paquete.
22 serverAddr.sin_family = AF_INET;
23 serverAddr.sin_port = htons(serverPort);
24
25 len_server = sizeof(serverAddr);
26 printf("Connecting to server..");
27 connect(serviceSocket,(struct sockaddr *) &serverAddr,(
socklen_t)len_server);
28 printf("Done!\n Sending credentials.. %s\n", myData.nick);
29 send_bytes = send(serviceSocket, &myData, sizeof(myData),0);
30 printf("Done! %d\n",send_bytes);
31 close(serviceSocket);
32 return 0;
33 }
Informacin de estado En el caso de las comunicaciones no conables, y tal y como vimos al principio
de esta seccin, se utilizan para enviar informacin cuya actualizacin debe ser muy
La informacin de estado que un
servidor le enva a un cliente es slo eciente y en la cual, la prdida de un paquete o parte de la informacin, o no tiene
aquella informacin relativa a su sentido un proceso de reenvo o bien se puede inferir. Para mostrar este ejemplo
visin parcial del estado del juego. podemos ver, para un juego FPS, como un cliente va a enviar informacin relativa a su
Es decir, la mnima imprescindible posicin y orientacin, y el servidor le enva la posicin y orientacin de su enemigo.
para que el cliente pueda tener la
informacin de qu le puede afectar Asumiremos que la informacin de la posicin del enemigo siempre es relevante
en cada momento de cara a la representacin del juego. En el listado 29.4 podemos ver la estructura
position que nos sirve para situar un jugador en un juego tipo FPS. Este es un ejemplo
de estructura que podemos asociar a comunicaciones no conables ya que:
Cada cambio en la posicin y orientacin del jugador debe generar una serie
de mensajes al servidor que, a su vez, debe comunicarlo a todos los jugadores
involucrados.
Al ser un tipo de informacin que se modica muy a menudo y vital para el
juego, se debe noticar con mucha eciencia.
En principio la prdida ocasional de algn mensaje no debera plantear mayores
problemas si se disea con una granularidad adecuada. La informacin perdida
puede inferirse de eventos anteriores y posteriores o incluso ignorarse.
El servidor (listado 29.5) en este caso prepara una estructura que representa la
posicin de un jugador (lineas 10-14), que podra ser un jugador manejado por la
IA del juego, a continuacin se crea el socket de servicio y se prepara para aceptar
paquetes de cualquier cliente en el puerto 3000. Finalmente se vincula el socket a
la direccin mediante la operacin bind y se pone a la espera de recibir mensajes
mediante la operacin recvfrom. Una vez recibido un mensaje, en la direccin
clientAddr tenemos la direccin del cliente, que podemos utilizar para enviarle
mensajes (en este caso mediante la operacin sendto).
C29
[958] CAPTULO 29. NETWORKING
El cliente en este caso guarda muchas similitudes con el servidor (listado 29.6).
En primer lugar se prepara la estructura a enviar, se crea el socket, y se prepara la
direccin de destino, en este caso, la del servidor que se encuentra en el mismo host
(lineas 21-25), una vez realizada la vinculacin se enva la posicin y se queda a la
espera de paquetes que provienen del servidor mediante la funcin recvfrom.
25 serverAddr.sin_port = htons(3000);
26
27 memset( &( serverAddr.sin_zero), \0, 8 );
28 length = sizeof( struct sockaddr_in );
29 bind( serviceSocket, (struct sockaddr *)&localAddr, length );
30 sendto(serviceSocket, &mypos, sizeof(struct position), 0, (struct
sockaddr *)&serverAddr, length );
31 recvfrom(serviceSocket, &enemy, sizeof(struct position), 0, (
struct sockaddr *)&serverAddr, &length );
32 printf("My enemy ID: %d is in position %f, %f, %f with orientation %
f\n",enemy.userID,enemy.x, enemy.y, enemy.z, enemy.rotation
[0][0]);
33 return 0;
34 }
A lo largo de esta seccin hemos visto las funciones bsicas de envo y recepcin
mediante sockets TCP/IP. Aunque ya tenemos la base para construir cualquier tipo de
servidor y cliente, en la parte de servidores hace falta introducir algunas caractersticas
adicionales de cara al desarrollo de servidores ecientes y con la capacidad de atender
a varios clientes de forma simultnea.
crear un nuevo proceso por cada peticin. El socket servidor recibe una peticin
de conexin y crea un socket de servicio. Este socket de servicio se pasa a
un proceso creado de forma expresa para atender la peticin. Este mecanismo
desde el punto de vista del diseo es muy intuitivo con la consiguiente ventaja
en mantenibilidad. De igual manera, para sistemas que slo tienen un slo
procesador, este sistema adolece de problemas de escalabilidad ya que el
nmero de cambios de contexto, para nmeros considerables de clientes, hace
perder eciencia a la solucin.
Utilizar un mecanismo que nos permita almacenar las peticiones y noticar
cuando un cliente nos manda una peticin al servidor.
La idea del select es que cuando retorna en cada uno de esos conjuntos se
encuentran los identicadores de los sockets de los cuales puedes leer, escribir o hay
alguna excepcin. Con las operaciones FD_ZERO, FD_SET, FD_CLR y FD_ISSET
se limpian, aaden y eliminan descriptores y se pregunta por un descriptor concreto
en cualquiera de los conjuntos respectivamente.
Supongamos que queremos atender distintos tipos de conexiones en varios sockets,
en principio no hay un orden de llegada de los paquetes y por lo tanto, no podemos
bloquearnos en uno slo de los sockets. La funcin select nos puede ayudar. Podemos
evolucionar nuestro servidor UDP desarrollado anteriormente para incluir la funcin
select (listado 29.7).
En el caso de send, tenemos la operacin sendall que, con la misma semntica que
el send, nos permite enviar todo un buffer.
Existen otros casos en el cual la gestin de buffers puede ralentizar las prestaciones
ya que se necesitan copiar de forma continua. Por ejemplo si leemos de un archivo, la
operacin read sobre un descriptor de archivo almacena la informacin en un buffer
interno al kernel que es copiado al buffer en espacio de usuario. Si a continuacin
queremos enviar esa informacin por un sockets, invocaramos la funcin send y
esa funcin copiara ese buffer, de nuevo a un buffer interno para su procesado.
Este continuo trasiego de informacin ralentiza la operacin si lo que queremos es
transmitir por completo el archivo. Lo ideal sera que las dos operaciones entre los
buffers internos al buffer de aplicacin se convirtiera en una nica operacin entre
buffers internos.
La tcnica que minimiza las copias innecesarias entre buffers se denomina zero-
copy y, a menudo, puede obtener una considerable mejora de las prestaciones. Es
difcil estudiar esta tcnica en profundidad porque depende del sistema operativo y de
la aplicacin concreta.
1 #include <arpa/inet.h>
2 uint32_t htonl(uint32_t hostlong);
3 uint16_t htons(uint16_t hostshort);
4 uint32_t ntohl(uint32_t netlong);
5 uint16_t ntohs(uint16_t netshort);
A mas alto nivel se puede usar XDR (eXternal Data Representation), un estndar
para la descripcin y representacin de datos utilizado por ONC (Open Network
Computing)-RPC (Remote Procedure Call) pero el cual se puede usar de forma directa
mediante las libreras adecuadas. Como veremos mas adelante, una de las principales
labores que nos ahorra el uso de middlewares es precisamente, la gestin de la
representacin de datos.
1 setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&argument,sizeof(argument))
en esta llamada a setsockopt (que nos ayuda a establecer las opciones de los socket)
donde bsicamente se especica que para una direccin multicast, vamos a reutilizar
la direccin compartiendo varios socket el mismo puerto. En este caso argument es un
u_int con un valor de 1. Faltara la segunda parte:
Objeto distribuido En un diseo orientado a objetos, el ingeniero puede decidir qu entidades del
dominio sern accesibles remotamente. Puede particionar su diseo, eligiendo qu
Es un objeto cuyos mtodos (algu- objetos irn en cada nodo, cmo se comunicarn con los dems, cules sern los ujos
nos al menos) pueden ser invocados
remotamente. de informacin y sus tipos. En denitiva est diseando una aplicacin distribuida.
Obviamente todo eso tiene un coste, debe tener muy claro que una invocacin remota
puede suponer hasta 100 veces ms tiempo que una invocacin local convencional.
Un videojuego en red, incluso sencillo, se puede disear como una aplicacin
distribuida. En este captulo veremos cmo comunicar por red las distintas partes del
juego de un modo mucho ms exible, cmodo y potente que con sockets.
Obviamente, el middleware se basa en las mismas primitivas del sistema operativo
y el subsistema de red. El middleware no puede hacer nada que no se pueda hacer
con sockets. Pero hay una gran diferencia; con el middleware lo haremos con mucho
menos esfuerzo gracias a las abstracciones y servicios que proporciona, hasta el punto
que habra muchsimas funcionalidades que seran prohibitivas en tiempo y esfuerzo
sin el middleware. El middleware encapsula tcnicas de programacin de sockets y
gestin de concurrencia que pueden ser realmente complejas de aprender, implemen-
tar y depurar; y que con l podemos aprovechar fcilmente. El middleware se encarga
de identicar y numerar los mensajes, comprobar duplicados, retransmisiones, com-
probaciones de conectividad, asignacin de puertos, gestin del ciclo de vida de las
conexiones, despachado asncrono y un largo etctera.
C29
[966] CAPTULO 29. NETWORKING
Servidor
Es la entidad pasiva, normalmente un programa que inicializa y activa los
recursos necesarios para poner objetos a disposicin de los clientes.
Objeto
Los objetos son entidades abstractas que se identican con un identidad, que
debera ser nica al menos en la aplicacin. Los objetos implementan al menos
una interfaz remota denida en una especicacin Slice.
2 http://www.zeroc.com
29.12. Middlewares de comunicaciones [967]
Sirviente
Es el cdigo concreto que respalda la funcionalidad del objeto, es quin
realmente hace el trabajo.
Cliente
Es la entidad activa, la que solicita servicios de un objeto remoto mediante
invocaciones a sus mtodos.
Proxy
Es un representante de un objeto remoto. El cliente en realidad interacciona (in-
voca) a un proxy, y ste se encarga con la ayuda del ncleo de comunicaciones,
de llevar el mensaje hasta el objeto remoto y despus devolverle la respuesta al
cliente.
$ slice2cpp Hello.ice
Esto genera dos cheros llamados Hello.cpp y Hello.h que deben ser
compilador para obtener las aplicaciones cliente y servidor.
29.12. Middlewares de comunicaciones [969]
Sirviente
Servidor
Cliente
El programa acepta por lnea de comandos la representacin textual del proxy del
objeto remoto. A partir de ella se obtiene un objeto proxy (lnea 8). Sin embargo esa
referencia es para un proxy genrico. Para poder invocar los mtodos de la interfaz
Hello se requiere una referencia de tipo HelloPrx, es decir, un proxy a un objeto
remoto Hello.
Para lograrlo debemos realizar un moldeado del puntero (downcasting4 ) mediante
el mtodo esttico HelloPrx::checkedCast() (lnea 9). Gracias al soporte de
introspeccin de los objetos remotos, Ice puede comprobar si efectivamente el objeto
remoto es del tipo al que tratamos de convertirlo. Esta comprobacin no se realizara
si empleramos la modalidad uncheckedCast().
Una vez conseguido el proxy del tipo correcto (objeto hello) podemos invocar
el mtodo remoto puts() (lnea 12) pasando por parmetro la cadena "Hello,
World!".
Compilacin
La gura 29.9 muestra el esquema de compilacin del cliente y servidor a partir del
chero de especicacin de la interfaz. El cheros marcados en amarillo corresponden
a aquellos que el programador debe escribir o completar, mientras que los cheros
generados aparecen en naranja.
Para automatizar el proceso de compilacin utilizamos un chero Makefile, que
se muestra en el listado 29.13
4 Consiste en moldear un puntero o referencia de una clase a una de sus subclases asumiendo que
Servidor
HelloI.h
HelloI.cpp
Server.cpp
Hello.ice
slice2cpp Hello.h
translator Hello.cpp
Client.cpp
Cliente
Ejecutando el servidor
$ ./Server
!! 03/10/12 19:52:05.733 ./Server: error: ObjectAdapterI.cpp:915: Ice
::InitializationException:
initialization exception:
object adapter HelloAdapter requires configuration
29.12. Middlewares de comunicaciones [973]
$ ./Server --Ice.Config=hello.cfg
hello1 -t:tcp -h 192.168.0.12 -p 9090:tcp -h 10.1.1.10 -p 9090
Ejecutando el cliente
Para ejecutar el cliente debemos indicar el proxy del objeto remoto en lnea de
comandos, precisamente el dato que imprime el servidor. Ntese que se debe escribir
entre comillas para que sea interpretado como un nico parmetro del programa:
twoway: Son las que I CE trata de realizar por defecto. La invocacin twoway
implica el uso de un protocolo de transporte conable (TCP) y es obligatorio si
el mtodo invocado tiene un valor de retorno.
oneway: Las invocaciones oneway slo pueden utilizarse cuando los mtodos
no retornan ningn valor (void) y no tienen parmetros de salida. Tambin
requieren de un protocolo conable, pero en determinadas circunstancias
pueden fallar la entrega.
datagram: Igual que las oneway nicamente pueden utilizase para invocar
mtodos sin valor de retorno y sin parmetros de salida. Utilizan un protocolo de
transporte no conable (UDP). A su vez eso implica que el tamao del mensaje
est limitado al mximo de un paquete IP (64KiB) y sufran de todas la otras
limitaciones de UDP: no hay control de orden, duplicados ni garanta de entrega.
La principal diferencia entre las invocaciones twoway con respecto a los otros tipos
es que el cliente recupera el control del ujo de ejecucin tan pronto como el mensaje
de invocacin es enviado, sin necesidad de esperar ningn tipo de conrmacin o
respuesta desde el servidor (ver gura 29.10).
41 }
[976] CAPTULO 29. NETWORKING
42
43 Client app;
44 return app.main(argc, argv);
45 }
Las 8-17 corresponden con la denicin de la clase callback FactorialCB. El
mtodo response() ser invocado por el ncleo de ejecucin cuando el mtodo
remoto haya terminado y el resultado est de vuelta. Tambin se dene un mtodo
failure() que ser invocado con una excepcin en el caso de producirse.
En la 26 se crea la instancia del callback, mediante una funcin especca para
este mtodo: newCallback_Math_factorial() pasndole una instancia de
nuestra clase FactorialCB y sendos punteros a los mtodos denidos en ella.
Esa funcin y todos los tipos nombrados siguiendo el patrn Callback_{interfa-
ce}_{mtodo} son generados automticamente por el compilador de interfaces en el
espacio de nombre Example.
Por ltimo en la 31 aparece la invocacin al mtodo remoto pasando la instancia
del callback (factorial_cb) como segundo parmetro.
IceStorm est implementado como un servicio I CE. Los servicios son librera
dinmicas que deben ser lanzadas con el servidor de aplicaciones cuyo nombre es
IceBox. Vemos la conguracin de IceBox en el siguiente listado:
Slo se denen dos propiedades: La carga del servicio IceStorm y los endpoints
del gestor remoto de IceBox. La conguracin de IceStorm a la que se hace referencia
(icestorm.config) es la siguiente:
Las lneas 13 especican los endpoints para los adaptadores del TopicManager,
el objeto de administracin y los publicadores de los canales. La lnea 4 es el nombre
del directorio donde se almacenar la conguracin del servicio (que es persistente
automticamente). La lnea 6 es el endpoint del adaptador del publicador, que se ha
incluido en este chero por simplicidad aunque no es parte de la conguracin de
IceStorm.
Una vez congurado podemos lanzar el servicio y probar el ejemplo. Lo primero
es arrancar el icebox (en background):
$ ./subscriber --Ice.Config=icestorm.config
$ ./publisher --Ice.Config=icestorm.config
$ ./subscriber --Ice.Config=icestorm.config
Event received: Hello World!
Event received: Hello World!
Event received: Hello World!
Event received: Hello World!
Event received: Hello World!
Event received: Hello World!
Event received: Hello World!
Event received: Hello World!
Event received: Hello World!
Event received: Hello World!
Este servicio puede resultar muy til para gestionar los eventos de actualizacin
en juegos distribuidos. Resulta sencillo crear canales (incluso por objeto o jugador)
para informar a los interesados de sus posicin o evolucin. El acoplamiento mnimo
que podemos conseguir entre los participantes resulta muy conveniente porque
permite que arranquen en cualquier orden y sin importar el nmero de ellos, aunque
C29
Tambin resulta muy interesante que los eventos estn modelados como invoca-
ciones. De ese modo es posible eliminar temporalmente el canal de eventos e invocar
directamente a un solo subscriptor durante las fases iniciales de modelado del proto-
colo y desarrollo de prototipos.
Sonido y Multimedia
Captulo 30
Guillermo Simmross Wattenberg
Francisco Jurado Monroy
E
n este captulo se discuten dos aspectos que son esenciales a la hora de afrontar
el desarrollo de un videojuego desde el punto de vista de la inmersin del
jugador. Uno de ellos est relacionado con la edicin de audio, mientras que
el otro est asociado a la integracin y gestin de escenas de vdeo dentro del propio
juego.
El apartado sonoro es un elemento fundamental para dotar de realismo a un
juego, y en l se aglutinan elementos esenciales como son los efectos sonoros y
elementos que acompaan a la evolucin del juego, como por ejemplo la banda
sonora. En este contexto, la primera parte de esta captulo describe la importancia
del audio en el proceso de desarrollo de videojuegos, haciendo especial hincapi en
las herramientas utilizadas, el proceso creativo, las tcnicas de creacin y aspectos
bsicos de programacin de audio.
Respecto a la integracin de vdeo, en la segunda parte del captulo se muestra la
problemtica existente y la importancia de integrar escenas de vdeo dentro del propio
juego. Dicha integracin contribuye a la evolucin del juego y fomenta la inmersin
del jugador en el mismo.
981
[982] CAPTULO 30. SONIDO Y MULTIMEDIA
30.1.1. Introduccin
Videojuegos, msica y sonido
Tanto la msica como los efectos sonoros son parte fundamental de un videojuego,
pero en ocasiones el oyente no es consciente de la capacidad que ambos tienen
para cambiar por completo la experiencia ldica. Una buena combinacin de estos
elementos aumenta enormemente la percepcin que el usuario tiene de estar inmerso
en la accin que sucede en el juego. La capacidad de la msica para cambiar el
estado de nimo del oyente es clave en el papel que ejerce en un videojuego: permite
transportar al jugador al mundo en el que se desarrolla la accin, inuirle en su
percepcin del contenido visual y transformar sus emociones.
Dependiendo del momento del juego, de la situacin y de las imgenes que Proceso de inmersin
muestre nuestro juego, la msica debe ser acorde con las sensaciones que se desee La msica es una herramienta nica
generar en el oyente: en un juego de estrategia militar, se optar por utilizar para hacer que el jugador se impli-
instrumentos musicales que nos evoquen al ejrcito: cornetas, tambores. . . si el que, viva y disfrute del videojuego.
juego es de estrategia militar futurista, sera buena idea combinar los instrumentos
mencionados con otros instrumentos sintticos que nos sugieran estar en un mundo
del futuro.
Pero la msica no es el nico recurso disponible que permite llevar al jugador
a otras realidades, los efectos de sonido tambin son una pieza fundamental en la
construccin de realidades virtuales.
La realidad cotidiana de nuestras vidas est repleta de sonidos. Todos ellos son
consecuencia de un cambio de presin en el aire producido por la vibracin de una
fuente. En un videojuego se intenta que el jugador reciba un estimulo similar al de la
realidad cuando realiza una accin. Cualquier sonido real se puede transportar a un
videojuego, pero y los sonidos que no existen? cmo suena un dragn? y lo ms
interesante cmo se crean los sonidos que emitira un dragn? seran diferentes esos
sonidos dependiendo del aspecto del dragn? El trabajo del creador de audio en un
videojuego es hacer que el jugador crea lo que ve, y un buen diseo del sonido en un
videojuego ayudar de forma denitiva a conseguir este objetivo.
El artista, por tanto, debe utilizar una buena combinacin de las dos herramientas
de las que dispone msica y efectos sonoros para conseguir ambientar, divertir,
emocionar y hacer vivir al jugador la historia y la accin que propone el videojuego.
Interactividad y no linealidad
Las acciones que se llevan a cabo al jugar a un videojuego pueden ser pulsaciones
de teclas, movimientos del ratn o del joystick, movimientos de un volante o de mando
inalmbrico y hoy en da hasta movimientos corporales del propio jugador. Todas estas
acciones pueden producir una reaccin en un videojuego.
Por todo lo anterior, el msico y diseador de sonido debe cumplir una serie
de aptitudes muy especiales para ser capaz de desarrollar tareas muy diversas, tanto
tcnicas como artsticas, para las que se necesitan conocimientos variados. Adems
de estas aptitudes, debe ser creativo, innovador, perseverante, trabajar rpido y ser
meticuloso.
Figura 30.1: Para comenzar a crear un estudio casero slo necesitaremos un ordenador con una buena
tarjeta de sonido. Poco a poco podremos complementarlo con instrumentos reales, diferente hardware y
nuevo software.
Secuenciad. multipistas
Muchos secuenciadores son tam-
bin multipistas en los que se pue-
den crear pistas de audio adems de
pistas MIDI. Esto permite mezclar
instrumentos reales con instrumen-
tos generados en el ordenador.
C30
[986] CAPTULO 30. SONIDO Y MULTIMEDIA
Software
Figura 30.2: Existen multitud de controladores MIDI en el mercado. En la imagen, Native Instruments
Maschine (que es adems un sampler y un secuenciador).
Preproduccin
Produccin
Una vez que el editor del juego aprueba el documento de diseo, el equipo de
desarrollo comienza su trabajo. La persona o personas encargadas de crear el audio de
un videojuego se incorpora al proyecto en las ltimas fases de desarrollo (al nalizar
la fase de produccin normalmente). Hasta este momento, lo ms probable es que el
equipo de programadores habr utilizado placeholders para los sonidos y su msica
favorita para acompaar al videojuego.
El creador de audio tiene que enfrentarse a la dura labor de crear sonidos y msica
lo sucientemente buenos como para conseguir que todo el equipo de desarrollo d
por buenos el nuevo material (aunque la decisin nal no sea del equipo de desarrollo,
pueden ejercer una gran inuencia sobre el veredicto nal).
En esta fase debe comprobarse tanto la validez tcnica de los sonidos y msica
(si se adaptan a las especicaciones) como la calidad del material entregado. Es
muy comn que se cambien, repitan o se aadan sonidos y/o composiciones en este
momento, por lo que el creador de audio debe estar preparado para estas contingencias
(previndolo en el presupuesto o creando a priori ms material del entregado).
Al nalizar el proceso de produccin, se determina cmo y en qu momentos
se reproducir qu archivo de sonido y con qu procesado en caso de utilizar
procesamiento en tiempo real. Por lo general, y pese a lo que pudiera parecer en
primera instancia, este proceso puede llevar hasta la mitad del tiempo dedicado a todo
el conjunto de la produccin.
dimensin a los videojuegos y otra variable con la que jugar durante el proceso de di-
seo: el sonido tridimensional puede convertirse en otro elemento protagonista para el
jugador, ya que puede permitirle posicionar cualquier fuente de sonido en un mundo
virtual (un enemigo, un objeto, etc.).
Post-produccin
Figura 30.5: Algunos motores y APIs de audio vienen acompaadas de herramientas para la creacin y
organizacin del material sonoro de un videojuego. En la imagen, FMOD Designer.
Motores y APIs
Los programadores encargados del audio pueden elegir utilizar una de las diferen-
tes herramientas software que existen en el mercado para facilitar su tarea o progra-
mar desde cero un motor de sonido. Esta decisin debe tomarse en las primeras fases
del ciclo de creacin del videojuego, para que en el momento en el que el desarrolla-
dor o grupo de desarrolladores especializados en audio que llevarn a cabo esta labor
sepan a qu tipo de reto deben enfrentarse.
Desde el punto de vista del creador de contenido, esta informacin tambin
resulta relevante, ya que en multitud de ocasiones sern estas APIs las encargadas
de reproducir este contenido, y porque algunas de ellas vienen acompaadas de
herramientas que debe conocer y ser capaz de manejar.
Existe un amplio nmero de subsistemas de audio en el mercado escritos explci-
tamente para satisfacer las necesidades de una aplicacin multimedia o un videojuego.
Estos sistemas se incorporan en libreras que pueden asociarse al videojuego tanto es-
ttica como dinmicamente en tiempo de ejecucin, facilitando la incorporacin de un
sistema de audio completo al programador sin conocimientos especializados en este
mbito.
La facilidad de integracin de estas libreras vara segn su desarrollador y la
plataforma a la que van dirigidas, por lo que siempre existir una cierta cantidad de
trabajo a realizar en forma de cdigo que recubra estas libreras para unir el videojuego
con la funcionalidad que ofrecen.
Algunas de las libreras ms utilizadas hoy da son OpenAL, FMOD, Miles
Sound System, ISACT, Wwise, XACT, Beatnik Audio Engine o Unreal 3 Sound
System.
Cuando se desarrollan aplicaciones que deben procesar vdeo digital ya sea para
su creacin, edicin o reproduccin, debe tenerse en cuenta cul es el estndar que Figura 30.10: Ogg emplea Vorbis
se est siguiendo, dado que de ello depende el formato de archivo contenedor que se para audio y Theora para video
manipular.
Cuando se habla de formato contenedor multimedia o simplemente formato
contenedor, se est haciendo referencia a un formato de archivo que puede contener
varios tipos diferentes de datos que han sido codicados y comprimidos mediante
lo que se conocen como cdecs. Los cheros que estn codicados siguiendo
un formato contenedor son conocidos como contenedores. stos almacenan todos
aquellos elementos necesarios para la reproduccin, como el audio, el vdeo, los
subttulos, los captulos, etc., as como los elementos de sincronizacin entre ellos.
Algunos de los formatos contenedores ms conocidos son AVI, MOV, MP4, Ogg o
Matroska.
Dada la complejidad que implicara elaborar una aplicacin que interprete los
cheros contenedores as como los datos multimedia contenidos en ellos y codicados
mediante codecs, para poder manipular el vdeo de modo que pueda ser integrado
en cualquier aplicacin en nuestro caso junto a grcos 3D, suelen emplearse
libreras o APIs (Application Programming Interface) que nos faciliten la tarea. En
las siguientes secciones nos adentraremos en algunas de ellas.
./autogen.sh
export CXXFLAGS=-I/usr/include/OGRE/
./configure
make
sudo make install
ln -s /usr/local/lib/Plugin_TheoraVideoSystem.so \
/usr/lib/OGRE/Plugin_TheoraVideoSystem.so
material VideoMaterial {
technique {
pass {
texture_unit {
texture_source ogg_video {
filename clip.ogg
precache 16
play_mode play
}
}
}
}
}
Figura 30.11: Material que incluye textura con vdeo.
para la compilacin y
para el enlazado.
30.6. Reproduccin de vdeo con GStreamer [999]
gestores de protocolos
fuentes de audio y vdeo
manejadores de formatos con sus correspondientes parsers, formateadores,
muxers, demuxers, metadatos, subttulos, etc.
codecs, tanto de codicacin como de decodiacin
ltros, como conversores, mezcladores, efectos, etc.
sinks para volcar la salida de audio y vdeo resultante de todo el proceso
gst-launch
Reproductor Audio/video Servidor de Editor de
gst-inspect
multimedia conferencia streamming vdeo
gst-editor
Framework de GStreammer
Clases base
Librera de utilidades
Pipeline Bus de mensajes
Plugins de sistema
Bindings de lenguajes
...
Gestores de Filtros:
Fuentes: Formatos: Codecs: Sinks:
protocolos: conversores,
Alsa, v4l2, avi, mp4, mp3, mpeg4, Alsa, xvideo,
http, fichero, mezcladores,
tcp/udp, etc. ogg, etc. vorbis, etc. tcp/udp, etc.
rstp, etc. efectos
Plugins de GStreammer
Plugins de terderos
Para trabajar con la cmara web se emplear v4l2 para capturar frames de la
cmara. Un ejemplo es el siguiente comando donde se captura un frame desde
v4l2src, se procesa por ffmpegcolorspace para que no aparezca con colores extraos,
transforma a formato png con pngenc y se pasa a un chero llamado picture.png con
lesink
Si lo que queremos es mostrar todos los frames que recoga la cmara web
directamente en una ventana, podemos hacerlo mediante el siguiente pilepile:
Pads. Son las conexiones de entradas y salidas de los elementos. Cada pad
gestiona datos de tipo especco, es decir, restringen el tipo de datos que
uye a travs de ellos. Son el elemento que permiten crear las pipelines entre
elementos.
Bins y Pipelines. Un bin es un contenedor de elementos. Una pipeline es un tipo
especial de bin que permite la ejecucin de todos los elementos que contiene.
Un bin es un elemento, y como tal puede ser conectado a otros elementos para
construir una pipeline.
Comunicacin. Como mecanismos de comunicacin GStreamer proporciona:
Buffers. Objetos para pasar datos entre elementos de la pipeline. Los
buffers siempre van desde los fuentes hasta los sinks
Eventos. Objetos enviados entre elementos o desde la aplicacin a los
elementos.
Mensajes. Objetos enviados por los elementos al bus de la pipeline.
Las aplicaciones pueden recoger estos mensajes que suelen almacenar
informacin como errores, marcas, cambios de estado, etc.
Consultas. Permiten a las aplicaciones realizar consultas como la dura-
cin o la posicin de la reproduccin actual.
29
30 gst_init(0, 0);
31
32 if (!g_thread_supported()){
33 g_thread_init(0);
34 }
35
36 mPlayer = gst_element_factory_make("playbin2", "play");
37
38 GstBus* bus = gst_pipeline_get_bus(GST_PIPELINE(mPlayer));
39 gst_bus_add_watch(bus, onBusMessage, getUserData(mPlayer));
40 gst_object_unref(bus);
41
42 mAppSink = gst_element_factory_make("appsink", "app_sink");
43
44 g_object_set(G_OBJECT(mAppSink), "emit-signals", true, NULL);
45 g_object_set(G_OBJECT(mAppSink), "max-buffers", 1, NULL);
46 g_object_set(G_OBJECT(mAppSink), "drop", true, NULL);
47
48 g_signal_connect(G_OBJECT(mAppSink), "new-buffer",
49 G_CALLBACK(onNewBuffer), this);
50
51 GstCaps* caps = gst_caps_new_simple("video/x-raw-rgb", 0);
52 GstElement* rgbFilter = gst_element_factory_make("capsfilter",
53 "rgb_filter");
54 g_object_set(G_OBJECT(rgbFilter), "caps", caps, NULL);
55 gst_caps_unref(caps);
56
57 GstElement* appBin = gst_bin_new("app_bin");
58
59 gst_bin_add(GST_BIN(appBin), rgbFilter);
60 GstPad* rgbSinkPad = gst_element_get_static_pad(rgbFilter,
61 "sink");
62 GstPad* ghostPad = gst_ghost_pad_new("app_bin_sink", rgbSinkPad);
63 gst_object_unref(rgbSinkPad);
64 gst_element_add_pad(appBin, ghostPad);
65
66 gst_bin_add_many(GST_BIN(appBin), mAppSink, NULL);
67 gst_element_link_many(rgbFilter, mAppSink, NULL);
68
69 g_object_set(G_OBJECT(mPlayer), "video-sink", appBin, NULL);
70 g_object_set(G_OBJECT(mPlayer), "uri", uri, NULL);
71
72 gst_element_set_state(GST_ELEMENT(mPlayer), GST_STATE_PLAYING);
73 }
8. Reempazo de la ventana de salida El paso nal es el de reemplazar la ventana
de salida por defecto conocida por video-sink, por nuestra aplicacin 69 e
indicar la uri del vdeo a ejecutar 70
9. Comienzo de la visualizacin del vdeo Bastar poner el estado de nuestro
objeto playbin2 a play.
algunos
Como ha podido verse, a lo largo del cdigo anterior se han establecido
callbacks. El primero de ellos ha sido onBusMessage en la lnea 39 del listado
anterior. En el listado siguiente es una posible implementacin para dicho mtodo,
el cual se encarga de mostrar el mensaje y realizar la accin que considere oportuna
segn el mismo.
El otro callback es onNewBuffer en la lnea 49 del mismo listado. Este mtodo
ser invocado por GStreamer cada vez que haya un nuevo frame disponible. Una
posible implementacin puede verse en el listado 30.3 continuacin, donde se pone
un atributo booleano llamado mNewBufferExists a verdadero cada vez que llega un
nuevo frame.
C30
[1004] CAPTULO 30. SONIDO Y MULTIMEDIA
30
31 TextureManager* mgr=Ogre::TextureManager::getSingletonPtr();
32
33 if (!mVideoTexture.isNull()){
34 mgr->remove(mVideoTexture->getName());
35 }
36
37 mVideoTexture = Ogre::TextureManager::getSingleton().
38 createManual(
39 "VideoTexture",
40 ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
41 TEX_TYPE_2D,
42 mVideoWidth, mVideoHeight,
43 0, PF_B8G8R8A8,
44 TU_DYNAMIC_WRITE_ONLY);
45
46 mVideoMaterial->getTechnique(0)->getPass(0)->
47 removeAllTextureUnitStates();
48 mVideoMaterial->getTechnique(0)->getPass(0)->
49 createTextureUnitState(mVideoTexture->getName());
50
51 float widthRatio =
52 mVideoWidth / static_cast<float>(mWindow->getWidth()) *
pixelRatio;
53 float heightRatio =
54 mVideoHeight / static_cast<float>(mWindow->getHeight());
55 float scale =
56 widthRatio > heightRatio ? widthRatio : heightRatio;
57
58 mVideoOverlay->setScale(widthRatio/scale, heightRatio/scale);
59 }
60
61 HardwarePixelBufferSharedPtr pixelBuffer =
62 mVideoTexture->getBuffer();
63 void* textureData = pixelBuffer->lock(
64 HardwareBuffer::HBL_DISCARD);
65 memcpy(textureData, GST_BUFFER_DATA(buffer),
66 GST_BUFFER_SIZE(buffer));
67 pixelBuffer->unlock();
68
69 gst_buffer_unref(buffer);
70 mNewBufferExists = false;
71 }
72 }
Interfaces de Usuario
31
Avanzadas
Francisco Jurado Monroy
Carlos Gonzlez Morcillo
L
as Interfaces Naturales de Usuario (del ingls Natural User Ingerface) son
aquellas que emplean los movimientos gestuales para permitir una interaccin
con el sistema sin necesidad de emplear dispositivos de entrada como el ratn,
el teclado o el joystick. As, el cuerpo, las manos o la voz del usuario se convierten en
el mando que le permite interactuar con el sistema.
Este es el paradigma de interaccin en el que se basan tecnologas como
las pantallas capacitivas tan empleadas en telefona movil y tabletas, sistemas de
reconocimiento de voz como el implantado en algunos vehculos para permitir
hablar al coche y no despistarnos de la conduccin, o dispositivos como Kinect de
Xbox que nos permite sumergirnos en mundos virtuales empleando nuestro cuerpo.
Este captulo se centrar en la construccin de interfaces naturales que empleen
movimientos gestuales mediante la aplicacin de tcnicas de Visin por Computador
Figura 31.1: Las interfaces natura-
o Visin Articial y el uso de dispositivos como el mando de la Wii de Nintendo
les ya no pertenecen slo al mundo
de la ciencia ccin
y el Kinect de XBox, abordando temas como la identicacin del movimiento, el
seguimiento de objetos o el reconocimiento facial, que puedan emplearse en el diseo
y desarrollo de videojuegos que sigan este paradigma de interaccin.
1007
[1008] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS
Con esto tendremos los requisitos mnimos para poder desarrollar, compilar y
ejecutar nuestras aplicaciones con OpenCV.
La distribucin viene acompaada de una serie de programitas de ejemplo en
diferentes lenguajes de programacin. Estos pueden resultar de mucha utilidad en
una primera toma de contacto. Si se ha realizado la instalacin mediante el sistema
de paquetes, el paquete opencv-doc es el encargado de aadir dichos ejemplos. Para
poder copiarlos y compilarlos haremos:
# cp -r /usr/share/doc/opencv-doc/examples .
# cd examples
# cd c
# sh build_all.sh
31.3.1. Nomenclatura
Como nemotcnico general para la interfaz de programacin en C, puede apreciar-
se cmo las funciones de OpenCV comienzan con el prejo cv seguido de la operacin
y el objeto sobre el que se aplicar. Veamos algunos ejemplos:
Ventanas
Como puede apreciarse, ninguna de las anteriores primitivas devuelve una referen-
cia a la ventana o recibe por parmetro una referencia. Esto es debido a que HighGUI
accede a las ventanas que gestiona mediante su nombre. De esta manera, cada vez que
se desee acceder a una ventana concreta, habr que indicar su nombre. Esto evita la
necesidad de emplear y gestionar estructuras adicionales por parte de los programa-
dores.
3 case CV_EVENT_LBUTTONDOWN:
4 if(flags & CV_EVENT_FLAG_CTRLKEY)
5 printf("Pulsado boton izquierdo con CTRL presionada\n");
6 break;
7
8 case CV_EVENT_LBUTTONUP:
9 printf("Boton izquierdo liberado\n");
10 break;
11 }
12 }
Para registrar el manejador y as poder recibir los eventos del ratn para una
ventana concreta, deber emplearse la funcin cvSetMouseCallback(window,mou-
seHandler,&mouseParam);.
Barra de progreso
Otro elemento interesante en las interfaces construidas con HighGUI son las barras
de progreso o trackbar. Estas suelen ser muy tiles para introducir valores enteros
al vuelo en nuestra aplicacin. Por ejemplo, para posicionar la visualizacin de un
vdeo en un punto determinado de la visualizacin o para establecer los valores de
determinados umbrales en los procesados (brillos, tonos, saturacin, matices, etc.).
El manejador del evento del trackbar tiene el siguiete aspecto:
As, para acceder a un pixel concreto para una imagen multicanal puede emplearse
el siguiente cdigo:
Del mismo modo, para acceder a todos los pixels de una imagen, bastar con
implementar un bucle anidado, tal y como se muestra en el siguiente cdigo de
ejemplo encargado de convertir una imagen a escala de grises:
Reservar:
CvMemStorage* cvCreateMemStorage(int block_size = 0)
void* cvMemStorageAlloc(CvMemStorage* storage, size_t size)
Liberar: void cvReleaseMemStorage(CvMemStorage** storage)
Vaciar: void cvClearMemStorage(CvMemStorage* storage)
Con esto se dispone de todo lo necesario para poder trabajar con memoria
dinmica en OpenCV.
Secuencias: CVSeq
Un tipo de objeto que puede ser guardado en los almacenes de memoria son las
secuencias. Estas son listas enlazadas de otras estructuras, de manera que se pueden
crear secuencias de cualquier tipo de objeto. Por su implementacin, las secuencias
permiten incluso el acceso directo a cada uno de los elementos.
Algunas de las principales primitivas para su manipulacin son:
Aclarado
esto, puede que el cdigo resulte bastante autodescriptivo. As, las lneas
1-2 muestran la inclusin de los cheros con las deniciones de las funciones tanto
para OpenCV como para Highgui. Lo primero que hace la aplicacin es una mnima
comprobacin de parmetros 6-7 . Si se ha introducido un parmetro, se entender
que es la ruta al chero a visualizar y se crear una captura para extraer la informacin
del mismo mediante la llamada a cvCreateFileCapture. En caso de que no se hayan
introducido parmetros o el parmetro no sea un chero de video, se realizar la
captura desde la WebCam empleando la llamada a cvCreateCameraCapture.
A continuacin, en la lnea 9 se crear una ventana llamada Window mediante
la llamada a cvNamedWindow. Como puede verse, esta funcin no devuelve ningn
puntero a la estructura que permita acceder a la ventana, sino que construye una
ventana con nombre. As, cada vez que deseemos acceder a una ventana concreta,
bastar con indicar su nombre, evitando emplear estructuras adicionales.
Los frames de los que consta el vdeo sern mostrados en la ventana mediante
el bucle de las lneas 12-20 . En l, se realiza la extraccin de los frames desde la
captura (chero de video o cmara) y se muestran en la ventana. El bucle nalizar
porque se haya
extraido el ltimo frame de la captura nea14 o se haya pulsado la tecla
de escape 18-19 por parte del usuario. Finalmente,se liberan
las estructuras mediante
las correspondientes llamadas del tipo cvRelease* 22-23 .
Como puede verse, en slo unas pocas lneas de cdigo se ha implementado un
reproductor de vdeo empleando OpenCV. En las siguientes secciones se mostrar
cmo sacarle algo de partido al potencial que nos ofrece la Visin por Computador.
En las lneas 9-10 se han creado dos ventanas, de manera que se puedan apreciar
los frames de entrada y de salida del ejemplo. Para almacenar las transformaciones
intermedias, se crearn una serie de imgenes mediante la primitiva cvCreateImage
17-20 , cuyo tamao es siempre el tamao del frame original.
La primitiva cvCreateImage ser una de las que ms se empleen cuando desa-
rrollemos aplicaciones con OpenCV. Esta crea la cabecera de la imagen y reserva la
memoria para los datos de la misma. Para ello toma tres parmetros: el tamao (si-
ze) que a su vez se compone de alto y ancho, la profundidad (depth) y el nmero de
canales (channels).
El proceso de ltrado est codicado en las lneas 22-24 . Como puede apreciarse
son tres los ltros que se le aplican:
31.5. Introduccin a los ltros [1017]
En la gura 31.4 puede verse cul ser la salida nal de la ejecucin de nuestra
aplicacin.
Figura 31.4: Salida del ejemplo. A la izquierda la imagen antes del proceso de ltrado. A la derecha los
bordes detectados tras el proceso de ltrado.
11 CV_RETR_TREE,
12 CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0) );
13 cvZero(gray);
14 if( contours )
15 cvDrawContours(gray, contours, cvScalarAll(255),
16 cvScalarAll(255), 100, 3, CV_AA, cvPoint(0,0) );
17 cvShowImage( "Contours", gray );
En la lnea 5 se denen una serie de colores que sern
empleados para marcar
con un rectngulo los objetos detectados. En las lneas 10-12 , cargamos la imagen
desde un chero, reservamos memoria para llevar a cabo el proceso de clasicacin y
cargamos la especicacin del clasicador.
Seguidamente, en las lneas 15-17 se limpia la memoria donde se realizar el
proceso de deteccin, y se pone a trabajar al clasicador sobre la imagen mediante
la llamada a la funcin cvHaarDetectObjects. Finalizado el proceso de deteccin por
parte del clasicador, se obtiene una secuencia de objetos (en nuestro caso caras)
almacenada en faces de tipo CvSeq (estructura de datos para almacenar secuencias en
OpenCV).
Bastar con recorrer dicha secuencia e ir creando rectngulos de diferentes colores
(empleando cvRectangle) sobre cada una de las caras identicadas. Esto es lo que se
realiza en el bucle de las lneas 19-25 . Finalmente se muestra la salida en una ventana
espera
y a que el usuario pulse cualquier tecla antes de liberar la memoria reservada
27-32 .
C31
[1020] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS
Llegados a este punto, el lector entender gran parte del cdigo, de modo que
nos centraremos en aquellas partes que resultan nuevas. As, en el bucle que
recoge
los frames de la captura, el clasicador es cargado slo la primera vez 32-34 . A
para
continuacin,
cada frame capturado, se realizar una transformacin a escala
de grises 36-37 , se reducir
la imagen a otra ms pequea y nalmente
se ecualizar el histograma 43 . Este ltimo paso de ecualizar el histograma, sirve
39-42
para tratar de resaltarn detalles que pudieran haberse perdido en los procesos de
transformacin a escala de grises y reduccin de tamao. Tras nalizar el proceso
de identicacin de rostros invocando la primitiva cvHaarDetectObjects 45-47 , si no
se ha encontrado ninguno se muestra por pantalla el mensaje Buscando objeto...
empleando para ello la primitiva de cvPutText 39 , la cual recibe la imagen donde
propiamente dicho, la fuente que se emplear y que fue
se pondr el texto, el texto
inicializada en la lnea 25 , y el color con el que se pintar. Si se han identicado
rostros en la escena, mostrar el mensaje Objetivo encontrado! 53 . Si el rostro
est en el cuarto derecho o izquierdo de la pantalla,mostrar
el mensaje oportuno
59-64
, adems de identicarlo mediante un recuadro
66 .
Figura 31.6: La aplicacin muestra cundo ha identicado un rostro y si este se encuentra a la izquierda o
a la derecha.
31.7. Interaccin mediante deteccin del color de los objetos [1023]
48 char c = cvWaitKey(5);
49 if(c==27) break;
50
51 cvReleaseImage(&imgHSV);
52 cvReleaseImage(&imgThreshed);
53 free(moments);
54 }
55
56 cvReleaseCapture(&capture);
57 cvDestroyWindow("Window");
58
59 return 0;
60 }
En l puede verse cmo, una vez seleccionado el color del objeto a detectar, el
procedimiento a llevar a cabo es el siguiente (la salida de su ejecucin aparece en la
gura 31.9):
1. Realizar un proceso de suavizado para eliminar el posible ruido en el color
21 .
2. Convertir la imagen RGB del frame capturado a una imagen HSV (Hue,
Saturation, Value) (Matiz, Saturacin, Valor) de modo que sea ms fcil
identicar el color a rastrear 23-24 . La salida de este ltro es la que se muestra
en la parte izquierda de la gura 31.8.
3. Eliminar todos aquellos pixels cuyo matiz no corresponda con el del objeto a
detectar. Esto devolver una imagen en la que sern puestos a 0 todos los pixels
que no estn dentro de nuestro criterio. En denitiva, la imagen slo contendr
aquellos pixels cuyo color deseamos detectar 25-27 en color blanco y el resto
todo en negro. La salida de este proceso es la que se muestra en la parte derecha
de la gura 31.8.
4. Identicar el momento
de los objetos en movimiento, es decir, su posicin en
coordenadas 29-34 .
5. Pintar una linea desde el anterior momento hasta el momento actual 29-43 Figura 31.7: Tringulo HSV donde
en una capa (en el cdigo es una imagen denominada imgScribble). se representa el matiz (en la ruleta),
6. Unir la capa que contiene
el trazo, con aquellaque contiene la imagen capturada
la saturacin y el valor (en el trin-
Figura 31.8: A la izquierda, se muestra la imagen HSV del Tux capturado con la cmara. A la derecha slo
aquello que coincida con el valor del color de las patas y el pico (amarillo en nuestro caso)
31.8. Identicacin del movimiento [1025]
Figura 31.9: Salida de la ejecucin del ejemplo de deteccin de objeto con determinado color. Puede verse
cmo se superpone a la imagen el trazo realizado por el rastreo de un pequeo objeto amarillo uorescente.
18 break;
19 }
Para realizar el anlisis de dicho cdigo debemos comenzar por el bucle principal.
En l puede verse cmo se captura la imagen, y si an no existe movimiento
detectado, se crea una nueva imagen cuyo origen se har coincidir con el del primer
frame capturado 7-12 . A continuacin, se actualiza el historial de movimiento
(MHI (Motion History
Image)) en la funcin update_mhi y se muestra la imagen
por pantalla 15 . La funcin update_mhi recibe tres parmetros: la imagen actual
procedente de la captura, la imagen resultante del movimiento y un umbral que
establece a partir de cundo la diferencia entre dos frames se considerar movimiento.
Mscara de la silueta que contiene los pixels que no estn puestos a 0 donde
ocurri el movimiento.
Historia de imgenes del movimiento, que se actualiza por la funcin y debe ser
de un nico canal y 32 bits.
Hora actual.
Duracin mxima para el rastreo del movimiento.
41
42 cvResetImageROI( mhi );
43 cvResetImageROI( orient );
44 cvResetImageROI( mask );
45 cvResetImageROI( silh );
46
47 // check for the case of little motion
48 if( count < comp_rect.width*comp_rect.height * 0.05 )
49 continue;
50
51 // draw a clock with arrow indicating the direction
52 center = cvPoint( (comp_rect.x + comp_rect.width/2),
53 (comp_rect.y + comp_rect.height/2) );
54
55 cvCircle( dst, center, cvRound(magnitude*1.2), color, 3, CV_AA,
0 );
56 cvLine( dst, center, cvPoint( cvRound( center.x + magnitude*cos
(angle*CV_PI/180)),
57 cvRound( center.y - magnitude*sin(angle*CV_PI
/180))), color, 3, CV_AA, 0 );
58 }
Figura 31.10: Deteccin del movimiento capturado. En azul aquellos pixels donde se ha localizado
movimiento. En negro el fondo esttico.
31.9. Comentarios nales sobre Visin por Computador [1029]
1. Elementos sensores
Tres acelermetros para medir la fuerza ejercida en cada uno de los ejes.
C31
[1030] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS
A travs del puerto de expansin situado en la parte inferior del mando, pueden
conectarse una serie de dispositivos adicionales para permitir incrementar las posi-
bilidades de interaccin que proporciona. Entre estos dispositivos caben destacar el
Nunchunk, el cul consiste en un joystick analgico con dos botones ms (comn-
mente utilizado para controlar el movimiento de los personajes), la Wii Wheel, que
transforma el mando en un volante para aquellos juegos de simulacin de vehculos,
o el Wii Zapper, que convierte al Wiimote+Nunchuk en una especie de pistola.
El objetivo de todos estos elementos es siempre el mismo: proporcionar una in-
teraccin lo ms cmoda y natural posible al usuario a partir de sensores y actuadores
que obtengan informacin acerca del movimiento que ste est realizando.
Interpretar esta informacin de forma correcta y crear los manejadores de eventos
apropiados para programar la aplicacin en funcin de las necesidades de interaccin,
corre a cargo de los programadores de dichas apliciaciones. Para podeer capturar
desde nuestras aplicaciones la informacin procedente de un dispositivo Wiimote, este
dispone del puerto Bluetooth. As, podrn programarse sobre la pila del protocolo
Bluetooth las correspondientes libreras para comunicarse con el mando. En el
siguiente apartado se abordar cmo trabajar con ste tipo de libreras.
Estructura wiimote_t
Puede verse cmo quedan representados los botones, los acelermetros, ngulos
de inclinacin, fuerzas de gravedad, el altavoz, la batera, etc.
Toda aplicacin que manipule el Wiimote deber contar con un bucle de captu-
ra/envo de datos desde y hacia el mando. Este tiene el aspecto que se muestra en el
listado analizado a continuacin.
En l cdigo se incluye la cabecera de la librera y en un momento dado se
inicializa la estructura wiimote_t 5 y se conecta con el mando mediante la primitiva
wiimote_connect 6 , a la que se le pasa una referencia a la anterior estructura y la
direccin MAC del mando. Si desconocemos la direccin del mando, en GNU/Linux
podemos emplear el comando hcitool mientras pulsamos simultaneamente los botones
1 y 2 del mando.
# hcitool scan
7 exit(1);
8 }
9
10 //Establecer el modo de captura
11 while (wiimote_is_open(&wiimote)) {
12 wiimote_update(&wiimote);// sincronizar con el mando
13 if (wiimote.keys.home) { // alguna comprobacin para salir
14 wiimote_disconnect(&wiimote);
15 }
16
17 //Realizar captura de datos segn el modo
18 //Realizar envo de datos
19 }
Para detectar si el usuario est pulsando alguno de los botones del mando o del
Nunchuk (incluido el joystick), podemos hacerlo comprobando directamente sus va-
lores a travs de los campos que se encuentran en wiimote.keys y wiimote.ext.nunchuk
de la variable de tipo wiimote_t, tal y como se muestra en el ejemplo.
Para cada uno de los ejes, el Wiimote proporciona valores recogidos por un
acelermetro. De este modo, si ponemos el Wiimote en horizontal sobre una mesa
los valores recogidos por los acelermetros tendern a cero.
Es preciso tener en cuenta que los acelermetros, como su nombre indica, miden
la aceleracin, es decir, la variacin de la velocidad con respecto del tiempo. No
miden la velocidad ni la posicin. Valores altos de uno de los acelermetros indicar
que el mando est variando muy deprisa su velocidad en el eje correspondiente. Que
el acelermetro proporcione un valor cero, indicar que la velocidad permaneciera
constante, no que el mando est detenido.
Adems de la aceleracin, resulta interesante medir los ngulos de inclinacin del
mando, y en consecuencia, la direccin en la que se ha realizado un movimiento. En
el Wiimote, estos ngulos se conocen como cabeceo (Pitch) arriba y abajo en el eje x,
balanceo o alabeo (Roll) en el eje y, y guiada o viraje a izquierda y derecha (Yaw) en
el eje z (ver gura 31.12).
Los movimientos de cabeceo (Pitch) y balanceo (Roll) pueden medirse directa-
mente por los acelermetros, pero la guiada (Yaw) precisa de la barra de LEDs IR
para permitir estimar el ngulo de dicho movimiento.
Yaw (guiada)
+Z
+X
+Y
Pitch (cabeceo)
W
II-
M
ot
e
-X
-Z
-Y
Roll (balanceo)
Ejes (axis): para proporcionar la aceleracin en cada uno de los ejes, es decir,
su vector aceleracin.
ngulos (tilt): para indicar el ngulo de inclinacin del acelermetro para el
balanceo, el cabeceo y el viraje.
Fuerzas (force): para representar al vector fuerza de la gravedad en sus
componentes (x, y, z).
31.12. Libreras para la manipulacin del Wiimote [1035]
Para enviar eventos al mando, tales como activar y desactivar los LEDs, provocar
zumbidos o reproducir sonidos en el altavz, bastar con activar el correspondiente
ag de la estructura wiimote_t, lo que accionar el actuador correspondiente en el
Wiimote.
En el siguiente cdigo se muestra cmo provocar determinados eventos en el
mando al pulsar el correspondiente botn del mismo.
16 while (wiimote_is_open(&wiimote)) {
17 wiimote_update(&wiimote);
18 if (wiimote.keys.home) {
19 wiimote_disconnect(&wiimote);
20 }
21
22 if (wiimote.keys.up)wiimote.led.one = 1;
23 else wiimote.led.one = 0;
24
25 if (wiimote.keys.down) wiimote.led.two = 1;
26 else wiimote.led.two = 0;
27
28 if (wiimote.keys.left) wiimote.led.three = 1;
29 else wiimote.led.three = 0;
30
31 if (wiimote.keys.right) wiimote.led.four = 1;
32 else wiimote.led.four = 0;
33
34 if (wiimote.keys.a) wiimote.rumble = 1;
35 else wiimote.rumble = 0;
36 }
37
38 return 0;
39 }
Para empelar el mando como puntero sobre la pantalla de modo que se puedan
acceder a los diferentes menus de una aplicacin o apuntar a un objetivo
concreto en la pantalla, solo tendr que conocerse la posicin a la que se seala.
Si se desea emplear el mando a modo de volante para una aplicacin de
simulacin automovilstica, bastar procesar la variacin de la fuerza sobre el
eje y y quiz implementar algunas funciones mediante los botones para frenar,
acelerar, etc.
22
23 printf ("\n");
24 }
Suponga que desea implementar un juego donde se deba lanzar una caa de
pescar o simular el golpe de un palo de golf. Deber tomar e interpretar del
modo adecuado el viraje de la mano. As, para el caso de la caa de pescar se
monitorizar la fuerza de la gravedad ejercida en el eje z para saber si est hacia
adelante y hacia atrs, siempre y cuando la inclinacin en el eje y sea negativo
(la caa debe estar arriba). Adems, deber calcularse la fuerza a partir de las
componentes del vector gravedad.
Como puede verse las posibilidades son mltiples, solo es cuestin de identicar
cules son los datos procedentes del mando que son imprescindibles para nuestra apli-
cacin, posibilitndo de este modo la construccin de interfaces de usuario naturales,
donde un elemento del mundo real externo a la aplicacin, puede interaccionar con el
mundo virtual dentro del computador.
Si en ocasiones no resulta evidente el conocer qu elementos deben procesarse
y con qu valores, existen aplicaciones que pueden ayudar a testear cules son las
variables que intervienen en un movimiento. En la gura 31.13 puede verse el aspecto
de una de ellas: WmGui. Se trata de una aplicacin para GNU/Linux donde podemos
monitorizar parmetros como los botones pulsados, la variacin sobre los ejes x, y, z,
la acceleracin como composicin del vector gravedad, la rotacin, el balanceo, etc.
Una cmara RGB que proporciona una resolucin VGA (Video Graphics
Adapter) de 8 bits (640x480).
Un sensor de profuncidad monocromo de resolucin VGA de 11 bist que
proporcina 211 = 2048 niveles de sensibilidad.
Un array de cuatro micrfonos.
libfreenect requiere que el usuario que accede a Kinect pertenezca a los grupos
plugdev y video. Deber garantizarse que el usuario pertenece a estos grupos mediante
los comandos:
1 http://openkinect.org/wiki/Main_Page
C31
[1040] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS
Tras esto, tendr que cerrarse la sesin para volver a acceder de nuevo y hacer as
efectivos los grupos a los que se ha aadido el usuario. Con ello, podrn ejecutarse
las aplicaciones que manipulen Kinect, como las que se incluyen en el paquete de
demostracin para probar la captura realizada por las cmaras de Kinect:
# freenect-glview
Con todo, lo que OpenKinect proporciona con libfreenect es un API para el acceso
a los datos procedentes de Kinect y para el control del posicionamiento del motor.
Sin embargo, procesar los datos ledos es responsabilidad de la aplicacin, por lo
que es comn su empleo junto a libreras como OpenCV para la identicacin del
movimiento, reconocimiento de personas, etc.
Si lo que se desea es poder disponer de un API de ms alto nivel, deberemos hacer
uso de algn framework, como el de OpenNI que pasaremos a describir en la siguiente
seccin.
31.15. OpenNI
OpenNI (Open Natural Interaction) es una organizacin sin nimo de lucro
promovida por la industria para favorecer la compatibilidad e interoperabilidad de
dispositivos, aplicaciones y middlewares que permitan la construccin de Interfaces
Naturales.
La organizacin ha liberado un framework para el desarrollo de aplicaciones, el
cual permite tanto la comunicacin a bajo nivel con los dispositivos de video y audio
de Kinect, como el uso de soluciones de alto nivel que emplean visin por computador
para realizar seguimiento de objetos como manos, cabeza, cuerpo, etc.
El framework OpenNI se distribuy bajo licencia GNU Lesser General Public
License (LGPL (Lesser General Public License)) hasta la versin 1.5, pasando a
distribuirse bajo Apache License desde la versin 2. Proporciona un conjunto de APIs
para diferentes plataformas con wrappers para diversos lenguajes de programacin
entre los que estn C, C++, Java y C#.
31.15. OpenNI [1041]
Capa de Hardware
Dispositivos y sensores Micrfonos Videocmaras Kinect
31.15.1. Instalacin
Para poder emplear OpenNI, deber descargarse el software OpenNI, el midd-
leware NiTE de PrimeSense y el driver del Sensor de la direccin http://www.
openni.org/openni-sdk/, donde pueden encontrarse los paquetes preparados
para Windows, Linux y Mac, as como el acceso a los repositorios github de los fuen-
tes para ser compilados e instalados.
Sin embargo, si se desea desarrollar aplicaciones que empleen OpenNI en otras
plataformas, una opcin interesante y sencilla puede pasar por seguir el proyecto
Simple-OpenNI2 , donde se pone a nuestra disposicin una versin algo reducida del
cdigo de OpenNI con un wrapper para Processing, pero con funcionalidad suente
para realizar una primera inmersin en el potencial de OpenNI.
As, desde la pgina de Simple-OpenNI pueden descargarse cheros zip con el
cdigo preparado para compilar e instalar en sistemas Linux de 64 bits, MacOSX
y Windows de 32 y 64 bits. Una vez descargado el chero zip para la plataforma
correspondiente, bastar descomprimirlo y se dispondr de directorios llamados
OpenNI-Bin-Dev-Linux-x64-v1.5.2.23, NITE-Bin-Dev-Linux-x64-v1.5.2.21 y Sensor-
Bin-Linux-x64-v5.1.0.41 si nos hemos descargado el cdigo para una arquitectura de
64 bits.
En cualquiera de los casos, si se ha descargado el zip completo desde Simple-
OpenNI o si se ha descargado desde los correspondientes repositorios github, para la
instalacin debern compilarse e instalarse por separado cada uno de los anteriores
paquetes, invocando al ./install que se encuentra en cada uno de ellos:
1. OpenNI
# cd OpenNI-Bin-Dev-Linux-x64-v1.5.2.23
# sudo ./install
2. Nite
# cd NITE-Bin-Dev-Linux-x64-v1.5.2.21
# sudo ./install
3. Driver Kinect
# cd Sensor-Bin-Linux-x64-v5.1.0.41
# sudo ./install
Mdulos
Los nodos de produccin son componentes que encapsulan una funcionalidad muy
concreta. Estos toman unos datos de un tipo concreto en su entrada, para producir unos
datos especcos de salida como resultado de un procesamiento concreto. Esta salida
puede ser aprovechada por otros nodos de produccin o por la aplicacin nal.
Existen nodos de produccin tando de los sensores como del middleware.
Aquellos nodos de produccin que proporcionan datos que pueden ser manipulados
directamente por nuestra aplicacin son los conocidos como generadores. Ejemplos
de generadores son: el generador de imagen, que proporciona la imagen capturada
por la cmara; el generador de profundidad, que proporciona el mapa de profundidad
capturado por el sensor 3D; el generador de usuario, que proporciona datos sobre
los usuarios identicados; el generador de puntos de la mano, que proporciona
informacin acerca del muestreo de las manos; el generador de alertas de gestos, que
proporciona datos a cerca de los gestos identicados en los usuarios, etc.
Cadenas de produccin
21 g_UserGenerator.GetSkeletonCap().SetSkeletonProfile(
22 XN_SKEL_PROFILE_ALL);
23
24 // Hacer que los generadores comiencen su trabajo
25 nRetVal = context.StartGeneratingAll();
26
27 while (TRUE){
28 // Tomamos el siguiente frame
29 nRetVal = context.WaitAndUpdateAll();
30
31 // Para cada usuario detectado por el sistema
32 XnUserID aUsers[15];
33 XnUInt16 nUsers = 15;
34 g_UserGenerator.GetUsers(aUsers, nUsers);
35 for (int i = 0; i < nUsers; ++i){
36 // Recogemos los datos deseados del esqueleto
37 if (g_UserGenerator.GetSkeletonCap().IsTracking(aUsers[i])){
38 XnSkeletonJointPosition LeftHand;
39 g_UserGenerator.GetSkeletonCap().GetSkeletonJointPosition(
40 aUsers[i], XN_SKEL_LEFT_HAND, LeftHand);
41
42 // Procesamos los datos ledos
43 if (LeftHand.position.X<0) printf("RIGHT ");
44 else printf("LEFT ");
45
46 if (LeftHand.position.Y>0) printf("UP");
47 else printf("DOWN");
48
49 printf("\n");
50 }
51 }
52 }
53
54 context.Shutdown();
55
56 return 0;
57 }
En toda aplicacin que manipule Kinect con el framework de OpenNI, se debe
comenzar con la inicializacin de un objeto Contexto 5-6 . ste contiene todo el
OpenNI. Cuando el Contexto no vaya a ser usado
estado del procesamiento usando
ms, este deber ser liberado 54 .
Una vez que se tiene el objeto Contexto construido, se pueden crear los nodos
de produccin. Para ello ser preciso invocar al constructor apropiado. As se
dispone de xn::DepthGenerator::Create() para generar datos de profundidad, xn::-
ImageGenerator::Create() para generar imagen RGB, xn::IRGenerator::Create(),
para generar datos del receptor de infrarrojos, xn::AudioGenerator::Create() para
generar datos de audio procedente de los micrfonos, xn::GestureGenerator::Create()
para generar datos de gestos, xn::SceneAnalyzer::Create() para generar datos de
la escena, xn::HandsGenerator::Create() para generar datos de las manos, xn::-
UserGenerator::Create() para generar datos del usuario, etc.
En el caso que nos ocupa, en el ejemplo se ha empleado un generador de usuario
9 , el cual producir informacin asociada a cada uno de los usuarios identicados en
una escena, tales como el esqueleto o la posicin de la cabeza, las manos, los pies, etc.
Tras esto se debern establecer las opciones que sean convenientes para procesar los
datos del generador elegido. Volveremos a las opciones establecidas para el generador
de usuario en breve, pero de momento continuaremos nuestra explicacin obviando
los detalles concretos de la inicializacin de este.
Una vez establecidos las opciones de los generadores, deber hacerse que estos
comiencen a producir la informacin de la que son responsables. Esto se har
con la funcin StartGeneratingAll() del Contexto 25 . Tras esto se podr entrar
en el bucle principal de procesamiento de la informacin para recoger los datos
31.15. OpenNI [1045]
producidos por los generadores. En dicho bucle, lo primero que se har es actualizar
mediante alguna de las funciones de la
la informacin procedente de los generadores
familia WaitAndUpdateAll() del Contexto 29 . A continuacin, se tendr disponible la
informacin proporcionada por los generadores, y que podr recogerse con mtodos
Get, los cuales dependern del generador o generadores que se estn manipulando.
En el ejemplo, lo que se hace es invocar al mtodo GetUsers 34 que devuelve el Id
para cada uno de los usuarios que ha reconocido el generador, as como el nmero de
usuarios detectados en la escena. Con esta informacin, para cada uno de los usuarios
identicados, se va recogiendo su esqueleto mediante el mtodo GetSkeletonCap(),
y a partir de este, la posicin en la que se encuentra la mano izquierda con el
mtodo GetSkeletonJointPosition() 38 al que se le ha indicado qu parte del esqueleto
se desea extraer (mediante el parmetro XN_SKEL_LEFT_HAND) y dnde se
almacenar la informacin (en la estructura LeftHand). partir de aqu, bastar con
A
procesar la informacin de la estructura reportada 42-49 .
Es preciso tener en cuenta que, cuando le pedimos datos del esqueleto al
generador, es desde el punto de vista de la cmara. Es decir, la mano izquierda que
ve Kinect se corresponder con mano la derecha del usuario.
Como se ha mencionado con anterioridad, para poder disponer de los datos
adecuados procedentes de los generadores, en ocasiones ser preciso establecer ciertos
parmetros de stos y personalizar determinada funcionalidad. Ha llegado el momento
de abordar de nuevo este fragmento del cdigo. As, en el ejemplo, en las lneas 11-22
puede verse cmo se han registrado determinados manejadores XnCallbackHandle
que indican al generador de usuario qu accin debe realizar cuando se ha detectado
un nuevo usuario o cuando se ha perdido uno (empleando para ello el mtodo
RegisterUserCallbacks), qu hacer cuando se ha registrado una determinada pose del
usuario (mediante RegisterToPoseCallbacks), y las acciones a realizar al comenzar y
terminar el proceso de calibracin (usando RegisterCalibrationCallbacks).
Una implementacin de estos manejadores o callbacks es la que se muestra en el
siguiente cdigo:
29
30 void XN_CALLBACK_TYPE
31 Calibration_End(xn::SkeletonCapability& capability, XnUserID nId,
32 XnBool bSuccess, void* pCookie){
33 if (bSuccess){
34 printf("Usuario calibrado\n");
35 g_UserGenerator.GetSkeletonCap().StartTracking(nId);
36 } else {
37 printf("Fallo al calibrar al usuario %d\n", nId);
38 g_UserGenerator.GetPoseDetectionCap().
39 StartPoseDetection(POSE_TO_USE, nId);
40 }
41 }
pose mismo, de modo que esta pose sirva para comenzar el proceso de calibracin
del
. Por su parte, cuando el usuario
desaparece de escena y se invoca al callback
User_LostUser no se hace nada 12-14 .
4-10
Utilizacin de objetos fsicos Ejemplo de proyeccin Ejemplo tpico de utilizacin Sistema de Realidad Virtual Sistema de Realidad Virtual
para crear modelos virtuales. espacial de Realidad de la librera ARToolKit. Semi-Inmersivo empleando tipo cueva Cave totalmente
Aumentada Bubble Cosmos el sistema Barco Baron. inmersivo.
Diagrama Adaptado de (Milgram y Kishino 94) y Material Fotogrfico de Wikipedia.
El 2009 se presenta ARhrrrr! (ver Figura 31.27), el primer juego con contenido de
alta calidad para smartphones con cmara. El telfono utiliza la metfora de ventana
virtual para mostrar un mapa 3D donde disparar a Zombies y facilitar la salida a los
humanos que estn atrapados en l. El videojuego emplea de forma intensiva la GPU
del telfono delegando en la tarjeta todos los clculos salvo el tracking basado en
caractersticas naturales, que se realiza en la CPU. As es posible distribuir el clculo
optimizando el tiempo nal de ejecucin.
El videojuego de PSP Invizimals (ver Figura 31.28), creado por el estudio espaol Figura 31.25: AR-Tennis.
Novorama en 2009, alcanza una distribucin en Europa en el primer trimestre de
2010 superior a las 350.000 copias, y ms de 8 millones de copias a nivel mundial,
situndose en lo ms alto del rnking de ventas. Este juego emplea marcas para
registrar la posicin de la cmara empleando tracking visual.
3. Alineacin 3D. La informacin del mundo virtual debe ser tridimensional Figura 31.28: Invizimals.
y debe estar correctamente alineada con la imagen del mundo real. As,
estrictamente hablando las aplicaciones que superponen capas grcas 2D sobre
la imagen del mundo real no son consideradas de Realidad Aumentada.
Siendo estrictos segn la denicin de Azuma, hay algunas aplicaciones que co-
mercialmente se venden como de realidad aumentada que no deberan ser consideradas
como tal, ya que el registro del mundo real no se realiza en 3D (como en el caso de
Wikitude o Layar por ejemplo).
31.16. Realidad Aumentada [1051]
31.17. ARToolKit
ARToolKit es una biblioteca de funciones para el desarrollo rpido de aplicaciones
de Realidad Aumentada. Fue escrita originalmente en C por H. Kato, y mantenida por
el HIT Lab de la Universidad de Washington, y el HIT Lab NZ de la Universidad de
Canterbury (Nueva Zelanda).
ARToolKit facilita el problema del registro de la cmara empleando mtodos
de visin por computador, de forma que obtiene el posicionamiento relativo de 6
grados de libertad haciendo el seguimiento de marcadadores cuadrados en tiempo real,
incluso en dispositivos de baja capacidad de cmputo. Algunas de las caractersticas
ms destacables son:
Color conversion should use x86 assembly (not working for 64bit)?
Enter : n
Do you want to create debug symbols? (y or n)
Enter : y
Build gsub libraries with texture rectangle support? (y or n)
GL_NV_texture_rectangle is supported on most NVidia graphics cards
and on ATi Radeon and better graphics cards
Enter : y
create ./Makefile
create lib/SRC/Makefile
...
create include/AR/config.h
Done.
27 glDepthFunc(GL_LEQUAL);
28
29 argConvGlpara(patt_trans, gl_para); // Convertimos la matriz de
30 glMatrixMode(GL_MODELVIEW); // la marca para ser usada
31 glLoadMatrixd(gl_para); // por OpenGL
32 // Esta ultima parte del codigo es para dibujar el objeto 3D
33 glEnable(GL_LIGHTING); glEnable(GL_LIGHT0);
34 glLightfv(GL_LIGHT0, GL_POSITION, light_position);
35 glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
36 glTranslatef(0.0, 0.0, 60.0);
37 glRotatef(90.0, 1.0, 0.0, 0.0);
38 glutSolidTeapot(80.0);
39 glDiable(GL_DEPTH_TEST);
40 }
41 // ======== init =================================================
42 static void init( void ) {
43 ARParam wparam, cparam; // Parametros intrinsecos de la camara
44 int xsize, ysize; // Tamano del video de camara (pixels)
45
46 // Abrimos dispositivo de video
47 if(arVideoOpen("") < 0) exit(0);
48 if(arVideoInqSize(&xsize, &ysize) < 0) exit(0);
49
50 // Cargamos los parametros intrinsecos de la camara
51 if(arParamLoad("data/camera_para.dat", 1, &wparam) < 0)
52 print_error ("Error en carga de parametros de camara\n");
53
54 arParamChangeSize(&wparam, xsize, ysize, &cparam);
55 arInitCparam(&cparam); // Inicializamos la camara con param"
56
57 // Cargamos la marca que vamos a reconocer en este ejemplo
58 if((patt_id=arLoadPatt("data/simple.patt")) < 0)
59 print_error ("Error en carga de patron\n");
60
61 argInit(&cparam, 1.0, 0, 0, 0, 0); // Abrimos la ventana
62 }
63 // ======== mainLoop =============================================
64 static void mainLoop(void) {
65 ARUint8 *dataPtr;
66 ARMarkerInfo *marker_info;
67 int marker_num, j, k;
68
69 double p_width = 120.0; // Ancho del patron (marca)
70 double p_center[2] = {0.0, 0.0}; // Centro del patron (marca)
71
72 // Capturamos un frame de la camara de video
73 if((dataPtr = (ARUint8 *)arVideoGetImage()) == NULL) {
74 // Si devuelve NULL es porque no hay un nuevo frame listo
75 arUtilSleep(2); return; // Dormimos el hilo 2ms y salimos
76 }
77 argDrawMode2D();
78 argDispImage(dataPtr, 0,0); // Dibujamos lo que ve la camara
79
80 // Detectamos la marca en el frame capturado (ret -1 si error)
81 if(arDetectMarker(dataPtr, 100, &marker_info, &marker_num) < 0){
82 cleanup(); exit(0); // Si devolvio -1, salimos del programa!
83 }
84 arVideoCapNext(); // Frame pintado y analizado... A por otro!
85 // Vemos donde detecta el patron con mayor fiabilidad
86 for(j = 0, k = -1; j < marker_num; j++) {
87 if(patt_id == marker_info[j].id) {
88 if (k == -1) k = j;
89 else if(marker_info[k].cf < marker_info[j].cf) k = j;
90 }
91 }
92 if(k != -1) { // Si ha detectado el patron en algun sitio...
93 // Obtenemos transformacion entre la marca y la camara real
94 arGetTransMat(&marker_info[k], p_center, p_width, patt_trans);
95 draw(); // Dibujamos los objetos de la escena
96 }
97 argSwapBuffers(); // Cambiamos el buffer con lo que dibujado
31.18. El esperado Hola Mundo! [1055]
98 }
99 // ======== Main =================================================
100 int main(int argc, char **argv) {
101 glutInit(&argc, argv); // Creamos la ventana OpenGL con Glut
102 init(); // Llamada a nuestra funcion de inicio
103 arVideoCapStart(); // Creamos un hilo para captura video
104 argMainLoop( NULL, NULL, mainLoop ); // Asociamos callbacks...
105 return (0);
106 }
Mundo! se
1. Captura. Se obtiene un frame de la cmara de vdeo. En el Hola
realiza llamando a la funcin arVideoGetImage en la lnea 73 .
31.18.1. Inicializacin
El bloque de inicializacin (funcin init), comienza abriendo el dispositivo de
video con arVideoOpen. Esta funcin admite parmetros de conguracin en una
cadena de caracteres.
A continuacin, lnea 48 , se obtiene el tamao de la fuente de vdeo mediante
arVideoInqSize. Esta funcin escribe en las direcciones de memoria reservadas
para dos enteros el ancho y alto del vdeo. En las siguientes lneas cargamos los
parmetros intrnsecos de la cmara obtenidos en la etapa de calibracin. En este
ejemplo utilizaremos un chero genrico camera_para.dat vlido para la mayora de
webcams. Sin embargo, para obtener resultados ms precisos, lo ideal es trabajar con
un chero adaptado a nuestro modelo de cmara concreto. Veremos cmo crear este
chero especco para cada cmara posteriormente.
As, con la llamada a arParamLoad se rellena la estructura ARParam especicada
como tercer parmetro con las caractersticas de la cmara. Estas caractersticas son
independientes de la resolucin a la que est trabajando la cmara, por lo que tenemos
que instanciar los parmetros concretamente a la resolucin en pxeles. Para ello, se
utiliza la llamada a arParamChangeSize (lnea 54 ) especicando la resolucin
concreta con la que trabajar la cmara en este ejemplo. Una vez obtenidos los
parmetros de la cmara instanciados al caso concreto de este ejemplo, se cargan en las
estructuras de datos internas de ARToolKit, mediante la llamada a arInitCparam.
En la ltima parte cargamos el patrn asociado a la marca (lnea 58 ). Finalmente
abrimos la ventana deOpenGL (mediante la biblioteca auxiliar Gsub de ARToolKit)
argInit en la lnea 61 , pasndole como primer parmetro la conguracin de la
cmara. El segundo parmetro indica el factor de zoom (en este caso, sin zoom).
muy pronto; con mayor frecuencia que la soportada por la cmara). Si esto ocurre, denir marcas personalizadas, las
simplemente dormimos el hilo 2ms (lnea 75 )y volvemos a ejecutar el mainLoop.
que ofrecen mejores resultados son
las basadas en patrones sencillos,
modo 2D, lnea 77 ) el buffer que
(en
A continuacin dibujamos en la ventana como la empleada en este ejemplo.
acabamos de recuperar de la cmara (lnea 78 ).
a arDetectMarker localiza las marcas en el buffer de entrada. En
La llamada
la lnea 81 el segundo parmetro de valor 100 se corresponde con el valor umbral
de binarizacin (a blanco y negro, ver Figura 31.35) de la imagen. El propsito
de este valor umbral (threshold) se explicar en detalle ms adelante. Esta funcin
nos devuelve en el tercer parmetro un puntero a una lista de estructuras de tipo
ARMarkerInfo, que contienen informacin sobre las marcas detectadas (junto con
un grado de abilidad de la deteccin), y como cuarto parmetro el nmero de marcas
detectadas.
De esta forma, ARToolKit nos devuelve posibles posiciones para cada una
de las marcas detectas. Incluso cuando estamos trabajando con una nica marca, es
comn que sea detectada en diferentes posiciones (por ejemplo, si hay algo parecido
a un cuadrado negro en la escena). Cmo elegimos la correcta en el caso de que
tengamos varias detecciones? ARToolKit asocia a cada percepcin una probabilidad
Figura 31.35: Ejemplo de proceso
de binarizacin de la imagen de
entrada para la deteccin de marcas.
31.19. Las Entraas de ARToolKit [1057]
de que lo percibido sea una marca, en el campo cf (condence value). En la tabla 31.1
se muestra la descripcin completa de los campos de esta estructura. Como se puede
comprobar, todos los campos de la estructura ARMarkerInfo se reeren a coordenadas
2D, por lo que an no se ha calculado la posicin relativa de la marca con la cmara.
As, en las lneas 86-91 guardamos en la variable k el ndice de la lista de marcas
detectadas aquella percepcin que tenga mayor probabilidad de ser la marca (cuyo
valor de abilidad sea mayor).
Mediante la llamada a arGetTransMat (lnea 94 ) obtenemos en una matriz
la transformacin relativa entre la marca y la cmara (matriz 3x4 de doubles); es
decir, obtiene la posicin y rotacin de la cmara con respecto de la marca detectada.
Para ello es necesario especicar el centro de la marca, y el ancho. Esta matriz ser
nalmente convertida al formato de matriz homogenea de 16 componentes utilizada
por OpenGL mediante la llamada a argConvGlpara en la lnea 29 .
Bsqueda de Marcas
Dibujado Objetos 3D
Registro Cmara 3D
Identificacin Patrn
Posicin 2D Marcas
Pos. + Rotacin 3D
Fuente de vdeo de En la imagen binaria se Los objetos virtuales 3D
entrada.La imagen ser 1 detectan contornos y se 2 3 4 5 6 se dibujan superpuestos a
binarizada a blanco y extraen las aristas y las la imagen real. En este
negro para buscar marcas esquinas del cuadrado. caso coincidiendo con el
cuadradas. centro de la marca.
Video: Este mdulo contiene las funciones para obtener frames de vdeo de los
dispositivos soportados por el Sistema Operativo. El prototipo de las funciones
de este mdulo se encuentran el chero de cabecera video.h.
Aplicacin Realidad Aumentada
AR: Este mdulo principal contiene las funciones principales de tracking de
marcas, calibracin y estructuras de datos requeridas por estos mtodos. Los ARToolKit
cheros de cabecera ar.h, arMulti.h (subrutinas para gestin multi-patrn) Gsub
y param.h describen las funciones asociadas a este mdulo. AR Video
Gsub_Lite
Gsub y Gsub_Lite: Estos mdulos contienen las funciones relacionadas con la GLUT
etapa de representacin. Ambas utilizan GLUT, aunque la versin _Lite es Bibs.
ms eciente y no tiene dependencias con ningn sistema de ventanas concreto. OpenGL APIs Vdeo
En estos mdulos se describen los cheros de cabecera gsub.h, gsub_lite.h Driver Tarjeta 3D Driver Cmara
y gsubUtil.h. Sistema Operativo
Estos mdulos estn totalmente desacoplados, de forma que es posible utilizar Figura 31.36: Arquitectura de AR-
ARToolKit sin necesidad de emplear los mtodos de captura de vdeo del primer ToolKit.
mdulo, o sin emplear los mdulos de representacin Gsub o Gsub_Lite, como
veremos en la posterior integracin con Ogre y OpenCV.
Xc R11 R12 R13 Tx Xm Xm
Yc R21 R22 R23 Ty Y m Ym
Z = R R32 R33 Tz Zm = Tcm Zm (31.1)
c 31
d 1 0 0 0 1 1 1
De este modo, conociendo la proyeccin de cada esquina de la marca (e) sobre las
coordenadas de pantalla (xc, yc), y las restricciones asociadas al tamao de las marcas
y su geometra cuadrada, es posible calcular la matriz Tcm . La Figura 31.40 resume
esta transformacin.
e
Ym Coordenadas
Convenio de xc de la Cmara
Xc
ARToolKit:
el eje Z sale
Xm desde el papel. Coordenadas (xce,yce)
en Pantalla
(xc, yc) Yc
Coordenadas
Zm
de la Marca
e
Xm Zc
yc
Ym
carlos@kurt:calib_camera2$ ./calib_camera2
Input the distance between each marker dot, in millimeters: 40
-----------
Press mouse button to grab first image,
or press right mouse button or [esc] to quit.
Hecho esto nos aparecer una ventana con la imagen que percibe la cmara. Mo-
veremos el patrn para que todos los puntos aparezcan en la imagen y presionaremos
el botn izquierdo del ratn una vez sobre la ventana para congelar la imagen. Ahora
tenemos que denir un rectngulo que rodee cada crculo del patrn (ver Figura 31.42
empleando el siguiente orden: primero el crculo ms cercano a la esquina superior
izquierda, y a continuacin los de su misma la. Luego los de la segunda la co-
menzando por el de la izquierda y as sucesivamente. Es decir, los crculos del patrn
Figura 31.42: Marcado de crculos
deben ser recorridos en orden indicado en la Figura 31.43. del patrn.
31.19. Las Entraas de ARToolKit [1061]
El programa marcar una pequea cruz en el centro de cada crculo que hayamos
marcado (ver Figura 31.42), y aparecer una lnea indicando que ha sido sealada
como se muestra a continuacin. Si no aparece una cruz en rojo es porque el crculo
no se ha detectado y tendr que ser de nuevo sealado.
1 2 3 4 5 6
Grabbed image 1.
7 8 9 10 11 12 -----------
Press mouse button and drag mouse to rubber-bound features (6 x 4),
or press right mouse button or [esc] to cancel rubber-bounding & retry
13 14 15 16 17 18 grabbing.
Marked feature position 1 of 24
19 20 21 22 23 24 Marked feature position 2 of 24
Marked feature position 3 of 24
Figura 31.43: Orden de marcado ...
de los crculos del patrn de calibra- Marked feature position 24 of 24
cin. -----------
Press mouse button to save feature positions,
or press right mouse button or [esc] to discard feature positions &
retry grabbing.
Una vez que se hayan marcado los 24 puntos, se pulsa de nuevo el botn izquierdo
del ratn sobre la imagen. Esto almacenar la posicin de las marcas para la primera
imagen, y descongelar la cmara, obteniendo una salida en el terminal como la
siguiente.
Precisin en calibracin
### Image no.1 ###
Como es obvio, a mayor nmero 1, 1: 239.50, 166.00
de imgenes capturadas y marca- 2, 1: 289.00, 167.00
das, mayor precisin en el proceso ...
de calibracin. Normalmente con 5 6, 4: 514.00, 253.50
6 imgenes distintas suele ser su- -----------
ciente. Press mouse button to grab next image,
or press right mouse button or [esc] to calculate distortion param.
-- loop:-50 --
F = (816.72,746.92), Center = (325.0,161.0): err = 0.843755
-- loop:-49 --
F = (816.47,747.72), Center = (325.0,162.0): err = 0.830948
...
Calibration succeeded. Enter filename to save camera parameter.
--------------------------------------
SIZE = 640, 480
Distortion factor = 375.000000 211.000000 -16.400000 0.978400 Figura 31.45: Dos imgenes resul-
770.43632 0.00000 329.00000 0.00000 tado del clculo del centro y el fac-
0.00000 738.93605 207.00000 0.00000 tor de distorsin.
0.00000 0.00000 1.00000 0.00000
--------------------------------------
Filename: logitech_usb.dat
2 Normalizacin 3 Resampling 4
1 Identificacin
de la marca
Comparacin
con datos
identic.patt
de ficheros
de patrones
Fichero
Figura 31.46: Pasos para la identicacin de patrones.
Limitaciones
Entre los factores que afectan a la deteccin de los patrones podemos destacar su
tamao, complejidad, orientacin relativa a la cmara y condiciones de iluminacin
de la escena (ver Figura 31.47).
150
El tamao fsico de la marca afecta directamente a la facilidad de deteccin; a
Rotacin
0
2
La orientacin relativa de la marca con respecto a la cmara afecta a la calidad 30
Error (cm)
1 45
de la deteccin. A mayor perpendicularidad entre el eje Z de la marca y el vector look
de la cmara, la deteccin ser peor. 0
-1
Finalmente las condiciones de iluminacin afectan enormemente a la deteccin
de las marcas. El uso de materiales que no ofrezcan brillo especular, y que disminuyan 0 10 20 30 40 50 60
el reejo en las marcas mejoran notablemente la deteccin de las marcas. Distancia (cm)
Figura 31.48: Comparativa de la trayectoria (en eje X) de 40 frames sosteniendo la marca manualmente
activando el uso del histrico de percepciones. Se puede comprobar cmo la grca en el caso de activar el
uso del histrico es mucho ms suave.
31.20. Histrico de Percepciones [1065]
C31
[1066] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS
44 }
45
46 // ======== cleanup ===============================================
47 static void cleanup(void) { // Libera recursos al salir ...
48 arVideoCapStop(); arVideoClose(); argCleanup();
49 free(objects); // Liberamos la memoria de la lista de objetos!
50 exit(0);
51 }
52
53 // ======== draw ==================================================
54 void draw( void ) {
55 double gl_para[16]; // Esta matriz 4x4 es la usada por OpenGL
56 GLfloat light_position[] = {100.0,-200.0,200.0,0.0};
57 int i;
58
59 argDrawMode3D(); // Cambiamos el contexto a 3D
60 argDraw3dCamera(0, 0); // Y la vista de la camara a 3D
61 glClear(GL_DEPTH_BUFFER_BIT); // Limpiamos buffer de profundidad
62 glEnable(GL_DEPTH_TEST);
63 glDepthFunc(GL_LEQUAL);
64
65 for (i=0; i<nobjects; i++) {
66 if (objects[i].visible) { // Si el objeto es visible
67 argConvGlpara(objects[i].patt_trans, gl_para);
68 glMatrixMode(GL_MODELVIEW);
69 glLoadMatrixd(gl_para); // Cargamos su matriz de transf.
70
71 // La parte de iluminacion podria ser especifica de cada
72 // objeto, aunque en este ejemplo es general para todos.
73 glEnable(GL_LIGHTING); glEnable(GL_LIGHT0);
74 glLightfv(GL_LIGHT0, GL_POSITION, light_position);
75 objects[i].drawme(); // Llamamos a su funcion de dibujar
76 }
77 }
78 glDisable(GL_DEPTH_TEST);
79 }
80
81 // ======== init ==================================================
82 static void init( void ) {
83 ARParam wparam, cparam; // Parametros intrinsecos de la camara
84 int xsize, ysize; // Tamano del video de camara (pixels)
85 double c[2] = {0.0, 0.0}; // Centro de patron (por defecto)
86
87 // Abrimos dispositivo de video
88 if(arVideoOpen("") < 0) exit(0);
89 if(arVideoInqSize(&xsize, &ysize) < 0) exit(0);
90
91 // Cargamos los parametros intrinsecos de la camara
92 if(arParamLoad("data/camera_para.dat", 1, &wparam) < 0)
93 print_error ("Error en carga de parametros de camara\n");
94
95 arParamChangeSize(&wparam, xsize, ysize, &cparam);
96 arInitCparam(&cparam); // Inicializamos la camara con param"
97
98 // Inicializamos la lista de objetos
99 addObject("data/simple.patt", 120.0, c, drawteapot);
100 addObject("data/identic.patt", 90.0, c, drawcube);
101
102 argInit(&cparam, 1.0, 0, 0, 0, 0); // Abrimos la ventana
103 }
104
105 // ======== mainLoop ==============================================
106 static void mainLoop(void) {
107 ARUint8 *dataPtr;
108 ARMarkerInfo *marker_info;
109 int marker_num, i, j, k;
110
111 // Capturamos un frame de la camara de video
112 if((dataPtr = (ARUint8 *)arVideoGetImage()) == NULL) {
113 // Si devuelve NULL es porque no hay un nuevo frame listo
114 arUtilSleep(2); return; // Dormimos el hilo 2ms y salimos
31.21. Utilizacin de varios patrones [1069]
115 }
116
117 argDrawMode2D();
118 argDispImage(dataPtr, 0,0); // Dibujamos lo que ve la camara
119
120 // Detectamos la marca en el frame capturado (ret -1 si error)
121 if(arDetectMarker(dataPtr, 100, &marker_info, &marker_num) < 0){
122 cleanup(); exit(0); // Si devolvio -1, salimos del programa!
123 }
124
125 arVideoCapNext(); // Frame pintado y analizado...
126
127 // Vemos donde detecta el patron con mayor fiabilidad
128 for (i=0; i<nobjects; i++) {
129 for(j = 0, k = -1; j < marker_num; j++) {
130 if(objects[i].id == marker_info[j].id) {
131 if (k == -1) k = j;
132 else if(marker_info[k].cf < marker_info[j].cf) k = j;
133 }
134 }
135
136 if(k != -1) { // Si ha detectado el patron en algun sitio...
137 objects[i].visible = 1;
138 arGetTransMat(&marker_info[k], objects[i].center,
139 objects[i].width, objects[i].patt_trans);
140 } else { objects[i].visible = 0; } // El objeto no es visible
141 }
142
143 draw(); // Dibujamos los objetos de la escena
144 argSwapBuffers(); // Cambiamos el buffer
145 }
La estructura de datos base del ejemplo es TObject, denida en las lneas 2-9 .
En esta estructura, el ltimo campo drawme es un puntero a funcin, que deber ser
asignado a alguna funcin que no reciba ni devuelva argumentos, y que se encargar
de dibujar el objeto.
Mantendremos en objects (lnea 11 ) una lista de objetos reconocibles en la
escena. La
memoria en la funcin addObject
para cada objeto de la lista se reservar
(lneas 15-29 ) mediante una llamada a realloc en 21-22 . Esta memoria ser
liberada cuando nalice el programa en cleanup (lnea 49 ).
De este modo, en la funcin de inicializacin init, se llama a nuestra funcin
addObject para cada objeto que deba ser reconocido, pasndole la ruta al chero
del patrn, el ancho, centro y el nombre de la funcin de dibujado asociada a esa
marca (lneas 99-100 ). En este
ejemplo se han denido dos sencillas funciones de
dibujado de una tetera 31-37 y un cubo 39-44 . En el caso de que una marca no tenga
asociado el dibujado de un objeto (como veremos en los ejemplos de la seccin 31.22),
simplemente podemos pasar NULL.
En el bucle principal se han incluido pocos cambios respecto de los ejemplos
anteriores. nicamenteen las lneas de cdigo donde se realiza la comprobacin de
las marcas detectadas 129-142 , se utiliza la lista de objetos global y se realiza la
comparacin con cada marca. As, el bucle for externo se encarga
de recorrer todos
los objetos que pueden ser reconocidos por el programa 129 , y se compara el factor
de conanza de cada objeto con el que est siendo estudiado 133 .A continuacin se
obtiene la matriz de transformacin asociada al objeto en 139-140 .
La funcin de dibujado (llamada en 144 y denida en 54-79
) utiliza igualmente
objetos. Para cada objeto visible 65-66 se carga
la informacin de la lista de su matriz
de transformacin 67-69 y se llama a su funcin propia de dibujado 75 .
C31
[1070] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS
X
arGetT
ransM
at
X Y
d? X Y
B
ar
Ge A-1
tTr
a ns
Ma
t X
t
sMa
B d
Tran et
A
ArG
SRU A
Veamos a continuacin un ejemplo que trabajar con las relaciones entre la cmara
y la marca. Tener claras estas transformaciones nos permitir cambiar entre sistemas
de coordenadas y trabajar con las relaciones espaciales entre las marcas que no son
ms que transformaciones entre diferentes sistemas de coordenadas. En este programa
queremos variar la intensidad de color rojo de la tetera segn la distancia de una marca
auxiliar (ver Figura 31.51).
El problema nos pide calcular la distancia entre dos marcas. ARToolKit, mediante
la llamada a la funcin arGetTransMat nos devuelve la transformacin relativa entre
la marca y la cmara. Para cada marca podemos obtener la matriz de transformacin
relativa hacia la cmara. Podemos verlo como la transformacin que nos posiciona la
cmara en el lugar adecuado para que, dibujando los objetos en el origen del SRU,
se muestren de forma correcta. Cmo calculamos entonces la distancia entre ambas
marcas?
En el diagrama de la Figura 31.53 se resume el proceso. Con la llamada a
arGetTransMat obtenemos una transformacin relativa al sistema de coordenadas
de cada cmara (que, en el caso de una nica marca, podemos verlo como si fuera el
origen del SRU donde dibujaremos los objetos). En este caso, tenemos las echas que
parten de la marca y posicionan relativamente la cmara sealadas con A y B.
Podemos calcular la transformacin entre marcas empleando la inversa de una
transformacin, que equivaldra a viajar en sentido contrario. Podemos imaginar
realizar la transformacin contraria; partiendo del origen del SRU, viajamos hasta la
Figura 31.51: Salida del ejemplo
marca de Identic (aplicando A1 , y de ah aplicamos la transformacin B (la que nos
de clculo de distancias entre mar- posicionara desde la segunda marca hasta la cmara). As, llegamos al punto nal
cas. La intensidad de la componen- (sealado con el crculo de color negro en la Figura 31.53.derecha). Su distancia al
te roja del color vara en funcin de origen del SRU ser la distancia entre marcas.
la distancia entre marcas.
La codicacin de esta operacin es directa. En la lnea 33 del listado anterior, si
ambos objetos son visibles 32 se calcula la inversa de la transformacin a la marca
de Identic (que est en la posicin 0 de la lista de objetos). Esta nueva matriz m se
multiplica a continuacin con la matriz de transformacin de la segunda marca 34 .
C31
[1072] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS
El VideoManager trabaja internamente con dos tipos de datos para la representa-
cin de los frames (ver lneas 11-12 ), que pueden ser convertidos entre s fcilmente.
El IplImage ha sido descrito anteriormente en la seccin. cv::Mat es una extensin
para trabajar con matrices de cualquier tamao y tipo en C++, que permiten acceder
cmodamente a los datos de la imagen. De este modo, cuando se capture un frame, el
VideoManager actualizar los punteros anteriores de manera inmediata.
El constructor de la clase (lneas 4-8 del siguiente listado) se encarga de obtener
el dispositivo de captura e inicializarlo con las dimensiones especicadas como
parmetro al constructor. Esto es necesario, ya que las cmaras generalmente permiten
En el destructor se libera el dispositivo de captura
trabajar con varias resoluciones.
creado por OpenCV (lnea 12 ).
rectngulo 2D (coordenadas paramtricas en la lnea 37 ), que aadiremos al grupo
41). Finalmente se aade el nodo, con el plano y la
que se dibujar primero (lnea
textura, a la escena (lneas 43-45 ) empleando el puntero al SceneManager indicado
en el constructor.
La actualizacin de la textura asociada al plano se realiza en el mtodo DrawCu-
rrentFrame, que se encarga de acceder a nivel de pxel y copiar el valor de la imagen
por la webcam. Para ello se obtiene un puntero compartido al pixel
obtenida buffer (l-
nea 64 ), y obtiene el uso de la memoriacon exclusin mutua (lnea 67 ). Esa regin
ser posteriormente liberada en la lnea 79 . La actualizacin
de la regin se realiza
recorriendo el frame en los bucles de las lneas 70-77 , y especicando el color de
pxel en valores RGBA (formato de orden de componentes congurado en lnea
cada
22 ).
El frame obtenido ser utilizado igualmente por ARToolKit para detectar las
marcas y proporcionar la transformacin relativa. Igualmente se ha denido una clase
para abstraer del uso de ARToolKit a la aplicacin de Ogre. El archivo de cabecera del
ARTKDetector se muestra a continuacin.
Listado 31.30: ARTKDetector.h
1 #include <AR/ar.h>
2 #include <AR/gsub.h>
3 #include <AR/param.h>
4 #include <Ogre.h>
5 #include <iostream>
6 #include "cv.h"
7
8 class ARTKDetector {
9 private:
10 ARMarkerInfo *_markerInfo;
11 int _markerNum;
12 int _thres;
13 int _pattId;
14 int _width; int _height;
15 double _pattTrans[3][4];
16 bool _detected;
17
18 int readConfiguration();
19 int readPatterns();
20 void Gl2Mat(double *gl, Ogre::Matrix4 &mat);
21
22 public:
23 ARTKDetector(int w, int h, int thres);
24 ~ARTKDetector();
25 bool detectMark(cv::Mat* frame);
26 void getPosRot(Ogre::Vector3 &pos, Ogre::Vector3 &look,
27 Ogre::Vector3 &up);
28 };
En este sencillo ejemplo nicamente se han creado dos clases para la deteccin de Compltame!
la marca detectMark, y para obtener los vectores de posicionamiento de la cmara en
Sera conveniente aadir nuevos
funcin de la ltima marca detectada (getPosRot). mtodos a la clase ARTKDetector,
que utilice una clase que encapsule
Listado 31.31: ARTKDetector.cpp los marcadores con su ancho, alto,
1 #include "ARTKDetector.h" identicador de patrn, etc...
2
3 ARTKDetector::ARTKDetector(int w, int h, int thres){
4 _markerNum=0; _markerInfo=NULL; _thres = thres;
5 _width = w; _height = h; _detected = false;
6 readConfiguration();
7 readPatterns();
8 }
9 ARTKDetector::~ARTKDetector(){ argCleanup(); }
10 // ================================================================
31.23. Integracin con OpenCV y Ogre [1077]
Esta clase est preparada para trabajar con una nica marca (cargada en el mtodo
privado readPatterns), consus variables
asociadas denidas como miembros de la cla-
se ARTKDetector (lneas 11-16 ). El siguiente listado implementa el ARTKDetector.
C31
[1078] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS
una
El mtodo getPosRot es el nico que vamos a comentar (el resto son traduccin
directa de la funcionalidad estudiada anteriormente). En la lnea 65 se utiliza un
mtodo de la clase para transformar la matriz de OpenGL a una Matrix4 de Ogre.
Ambas matrices son matrices columna!, por lo que habr que tener cuidado a la hora
de trabajar con ellas (el campo de traslacin viene denido en la la inferior, y no en
la columna de la derecha como es el convenio habitual).
Las operaciones de la lnea 67 sirven para invertir el eje Y de toda la matriz,
para cambiar el criterio del sistema de ejes de mano izquierda a mano derecha (Ogre).
Invertimos la matriz y aplicamos una rotacin de 90 grados en X para tener el mismo
sistema con el que hemos trabajado en sesiones anteriores del curso.
Finalmente calculamos los vectores de pos, look y up (lneas 71-74 ) que
utilizaremos en el bucle de dibujado de Ogre.
El uso de estas clases de utilidad es directa desde el FrameListener. Ser necesario
crear dos variables miembro del VideoManager y ARTKDetector (lneas 7-8 ). En
el mtodo
frameStarted bastar con llamar a la actualizacin del frame (lneas
15-16
). La deteccin de la marca se realiza pasndole el puntero de tipo Mat del
VideoManager al mtodo detectMark del Detector.
C
ada vez que creamos un nuevo proyecto con Ogre, existe un grupo de elementos
que necesitamos instanciar para crear un primer prototipo. Este conjunto de
clases y objetos se repiten con frecuencia para todos nuestros proyectos. En
este captulo estudiaremos los fundamentos de OgreFramework, que ofrece una serie
de facilidades muy interesantes.
A.1. Introduccin
OgreFramework fu creado por Philip Allgaier en 2009 y ofrece al programador
una serie de herramientas para la gestin de Ogre como:
1081
[1082] ANEXO A. INTRODUCCIN A OGREFRAMEWORK
Existen dos ramas del framework para Ogre, Basic y Advanced. La diferencia
entre los dos es la cantidad de recursos que manejan. Basic OgreFramework contiene
los recursos mnimos necesarios para crear una instancia del motor Ogre y que
muestre una escena como ejemplo. Y Advanced OgreFramework incluye un conjunto
completo de herramientas y clases para una gestin avanzada de nuestra aplicacin.
OgreFramework est desarrollado en C++ sobre Ogre y es multiplataforma, sin
embargo, es necesario realizar algunas modicaciones en el framework para que pueda
funcionar en OSX. Una de las mayores ventajas de utilizar esta tecnologa es la de
poder automatizar la carga inicial del motor y poder congurar Ogre con llamadas
simples a OgreFramework.
Inicializacin de Ogre.
Bucle de renderizado personalizable.
Escena bsica.
Gestin de teclado bsica.
Crear capturas de pantallas (Screenshots).
Informacin bsica de la escena (FPS, contador batch. . . ).
hg clone https://bitbucket.org/spacegaier/basicogreframework
A.2.1. Arquitectura
La rama Basic de OgreFramework contiene en exclusiva la instanciacin del motor
de renderizado. Se basa en el patrn Singleton. Cada vez que queramos hacer uso de
alguna llamada al motor ser necesario recuperar el puntero al Singleton del objeto. Figura A.1: Resultado del primer
Dentro de la clase OgreFramework nos encontramos con el arranque de los siguientes ejemplo con Ogre Framework.
gestores:
Ahora veremos los pasos necesarios para rellenar de contenido nuestra escena.
Para ello crearemos nodos y escenas igual que las creamos en Ogre pero llamando al
puntero de OgreFramework, como se muestra en el siguiente listado.
Como podemos observar, para crear una entidad y un nodo, es necesario acceder
al gestor de escena mediante el puntero Singleton a OgreFramework. Se realizar
la misma accin para crear las luces de la escena y el Skybox. Esta restriccin
nos ayudar a mantener separado el motor Ogre de nuestro modelo y permitir la
integracin con otros mdulos de forma ms homognea para el desarrollador.
Realmente, cuando accedamos a uno de los gestores de OgreFramework como el
SceneManager, utilizaremos las mismas primitivas que utilizabamos en Ogre. Por lo
tanto, podremos usar la documentacin estndar de Ogre.
Para gestionar nuestro bucle del juego necesitaremos la variable booleana m_bShutdown
la cual nos permitir cerrar la aplicacin en cualquier momento. Si se produce un error
inesperado, tambin saldr del bucle principal.
En cada iteracin del bucle:
Inicializacin de OGRE.
Carga de recursos bsicos.
Gestin de dispositivos de entrada basado en OIS.
Personalizacin del bucle de renderizado.
Jerarqua basada en estados.
Cargador de escenas mediante XML con DotSceneLoader.
Interfaz grca de usuario basada en SdkTrays.
Manipulacin de materiales en cdigo.
hg clone https://bitbucket.org/spacegaier/advancedogreframework
tiene una pila de estados activos y siempre ejecuta el estado de la cima de la pila. En
cualquier momento, se puede conectar otro estado de la pila, que ser usado hasta que
sea eliminado de la misma. En este caso, el manager resume el estado que dej en
pausa al introducir el anterior estado eliminado. En el momento en que la aplicacin
no contiene ningn estado activo, el manager cierra la aplicacin.
A.3.2. Arquitectura
La arquitectura de Advanced OgreFramework parece compleja pero, realmente, es
bastante fcil de entender. Esta herramienta gestiona la aplicacin de Ogre como un
conjunto de estados, que se van introduciendo en una pila. Esta gestin de estados es
la que comnmente hemos utilizado para los proyectos de Ogre.
OgreFramework
Para actualizar cada frame del motor Ogre, la clase AppStateManager llamar la
funcin updateOgre() para actualizar directamente Ogre. Este mtodo estar vaco
para que cada estado se encargue de actualizar el motor de Ogre, pero es necesario
ubicarlo aqu como tarea central del motor.
DotSceneLoader
Esta herramienta se usa para cargar las escenas en Ogre a partir de un chero XML.
Estos cheros XML son procesados mediante RapidXML; DotSceneLoader crea las
estructuras necesarias para ser cargadas en el SceneManager de Ogre. Si deseamos
aadir fsicas a un nodo, tendremos que aadir aqu su implementacin.
Podemos ver que para crear una escena es necesario introducir 4 argumentos: el
chero, el nombre del nodo, el SceneManager que se est gestionando en este estado
y el nodo donde queremos introducirlo. Es muy interesante poder cargar las escenas
desde un chero XML por que podremos crearlas y destruirlas de manera dinmica,
permitiendo una mejor gestin de la memoria y del nivel del juego.
Normalmente, se pueden agrupar las escenas de un nivel por bloques e ir cargando
cada uno cuando sea necesario. Si, por ejemplo, nos encontramos con un juego
tipo endless-running, es interesante ir destruyendo las escenas que quedan detrs
del personaje ya que no vam a ser accesibles; tambin es interesante para ir auto-
generando la escena de forma aleatoria hacia adelante.
Cada vez que carguemos una escena, podremos recuperar una entidad como se
describe en el ejemplo; por un nombre bien conocido. Este nombre estar asignado en
el chero XML y podremos recuperar el nodo desde el propio juego. De esta manera
podremos cambiar las propiedades o materiales de dicha entidad al vuelo como, por
ejemplo, aadir un cuerpo rgido al motor de fsicas y conectarlo a la entidad.
A.3. Advanced OgreFramework [1087]
AppState
En este archivo se denen dos clases. La primera clase ser la que herede el
AppStateManager que gestiona los estados, pero est denido en esta clase por
cuestiones de diseo. Esta contiene los mtodos necesarios para la gestin de los
estados, estos mtodos son abstractos y estn denidos en la clase AppStateManager.
La segunda clase ser AppState la cul ser la clase de la que hereden todos los
estados que se implementen en el juego. Esta clase contiene una serie de mtodos
para mantener la consistencia entre estados y poder tratar cada uno como estado
independiente. En l se incluyen los mtodos: enter, exit, pause, resume y update.
Estos mtodos nos permitirn sobre todo mantener la interfaz organizada en cada
estado como, por ejemplo, el track de msica que se est escuchando en cada estado.
Cuando creamos un estado nuevo y lo agregamos a la pila de estados.
AppStateManager
Esta clase ser la encargada de gestionar todos los estados. Tendr los mecanismos
para pausar, recuperar y cambiar un estado en cualquier instante del juego. Hereda
directamente del AppStateListener e implementa sus mtodos abstractos. Contiene
dos vectores:
Para cada estado existirn una serie de valores como su nombre, informacin y su
estado. Cada vez que se cree un estado, se llamar al mtodo manageAppState() para
que introduzca dicha informacin al estado y lo inserte en la pila de estados existentes.
En esta clase se encuentra el bucle principal del juego, dentro del mtodo start().
Dentro de este mtodo podemos destacar el clculo del tiempo de un frame a otro.
Aqu podremos realizar las modicaciones necesarias si necesitamos coordinar el
motor de renderizado con el motor de fsicas. Para poder arreglar el problema en
el desfase de tiempo entre ambos motores se ajusta un valor delta que discretiza el
tiempo entre un frame y otro. Esta tcnica nos ayudar a manejar correctamente el
tiempo en ambos motores para que se comporte de manera natural.
MenuState
GameState
Toda la lgica del juego est dentro del estado GameState. Aqu es donde
enlazaremos todos nuestros elementos de nuestro juego, como por ejemplo: el
motor de fsicas, el controlador de nuestro personaje principal, mdulos de inteligencia
articial, etc. En el caso de ejemplo de OgreFramework, incluye una escena mnima
que carga una escena con DotSceneLoader y rellena de contenido la pantalla.
Para crear los elementos necesarios de la interfaz se invoca a builGUI() para in-
sertar todos los elementos necesarios del estado mediante el gestor SdkTraysManager
que explicaremos en la siguiente seccin.
Para capturar los eventos de entrada por teclado, se utilizarn los mtodos
keyPressed() y keyReleased() para realizar los eventos correspondientes a si se pulsa
una tecla o se ha dejado de pulsar respectivamente. En nuestro caso, tendremos una
serie de condiciones para cada tecla que se pulse, las teclas son capturadas mediante
las macros denidas en la librera OIS.
A.4. SdkTrays
Tanto los ejemplos que vienen con el SDK de Ogre (SampleBrowser), como
OgreFramework utilizan SdkTrays. Este gestor de la interfaz se construy con el
propsito de ser sencillo, crear interfaces de una manera rpida y sin tanta complejidad
como CEGUI. El sistema de SdkTrays est basado en Ogre::Overlay por lo que nos
garantiza su portabilidad a todas las plataformas.
A.4. SdkTrays [1089]
A.4.1. Introduccin
El sistema de interfaz de SdkTrays se organiza en una serie de paneles (trays), que
se localizan en nueve de posiciones de nuestra pantalla. Cada introducimos un nuevo
Ogre::Overlay en nuestra escena, debemos calcular las coordenadas (x,y) del frame
actual, aadiendo complejidad a la hora de disear una interfaz. Cuando creamos un
widget nuevo con SdkTrays, debemos pasarle una posicin de las nueve posibles y
el tamao. Si ya existe una un widget en esa regin, el nuevo widget se colocar
justo debajo, manteniendo las proporciones de la pantalla y redimiensionando el panel
actual donde se encuentra dicho widget.
A.4.2. Requisitos
SdkTrays necesita la biblioteca OIS para la captura de datos de entrada mediante
ratn o teclado. Una vez que se han incluido estas dependencias al proyecto
simplemente es necesario incluir SdkTrays.h en cada clase. Y tambin es necesario
incluir el chero SdkTrays.zip a nuestro directorio de recursos, normalmente en
/media/packs.
A.4.3. SdkTrayManager
Para utilizar SdkTrays, es necesario crear un SdkTrayManager. Esta es la clase a
travs de la cual se van a crear y administrar todos los widgets, manipular el cursor,
cambiar la imagen de fondo, ajustar las propiedades de los paneles, mostrar dilogos,
mostrar / ocultar la barra de carga, etc. El SdkTrayManager requiere SdkTrays.zip,
por lo que slo se puede crear despus de cargar ese recurso. Es recomendable
asegurarse de que est utilizando el espacio de nombres OgreBites para poder incluir
las macros de localizacin. Estas macros permiten crear un widget en las esquinas, en
la parte central de la pantalla y en la parte central de los bordes.
A.4.4. Widgets
Existen 10 tipos de widgets bsicos. Cada widget es slo un ejemplo de
una plantilla de OverlayElement. Cada vez que se crea un widget es necesario
introducir las medidas en pixeles. Cada widget est identicado con un nombre
y se gestionar su creacin y destruccin mediante SdkTrayManager. Algunos de los
widgets predenidos que podemos crear son los siguientes:
A.4.5. SdkTrayListener
Esta clase contiene los controladores para todos los diferentes eventos que pueden
disparar los widgets. La clase SdkTrayManager es, en s misma, un SdkTrayListener
porque responde a los eventos de los widgets. Algunos widgets dan la opcin de no
disparar un evento cuando cambia su estado. Esto es til para inicializar o restablecer
un widget, en cuyo caso no debe haber una respuesta de cualquier tipo.
[1090] ANEXO A. INTRODUCCIN A OGREFRAMEWORK
A.4.6. Skins
SdkTrays no est orientado a una personalizacin profunda de los widgets como
en CEGUI. Pero eso no signica que no podamos cambiar su apariencia del modelo
estndar. Los recursos de las imgenes que utilizan los widgets se encuentran en el
chero SdkTrays.zip.
Si descomprimimos los recursos y modicamos el contenido, podemos persona-
lizar la apariencia de los widgets. Las imgenes que se muestran tienden a ser muy
pequeas con formas muy concretas. Este tipo de imgenes se utiliza para reducir el
espacio necesario para cada widget y que sea reutilizable por recursos. Si queremos
modicar la apariencia de un widget es recomendable seguir las mismas formas y
cambiar slo los colores o tambin podemos optar por cambiar la apariencia a co-
lores slidos/planos. Las imgenes del paquete SdkTrays.zip son PNG, por lo que
podemos crear transparencias si lo deseamos o sombreados.
La personalizacin es limitada, pero con diseo y creatividad se pueden conseguir
apariencias bastante diferentes de la original de OgreFramework.
A.4.7. OgreBites
Podemos observar como se distribuyen los widgets por regiones en la pantalla.
Estas posiciones vienen dadas por el espacio de nombres OgreBites, la cual incluye
una serie de macros que instancia los widgets en la regin deseada. Observamos
adems que si por ejemplo agregamos seis widgets en la parte inferior derecha, los
widgets se agrupan en un columna tipo pila. Este orden es el mismo que nosotros
agregaremos en las lineas de cdigo y las macros para instanciar los widgets son las
siguientes:
A.5. btOgre
btOgre es un conector ligero entre BulletOgre. Este conector no proporciona un
RigidBody ni CollisionShape directamente. En lugar de eso, se accede directamente
al motor correspondiente. La nica accin que realiza btOgre es la de conectar los
btRigidBodies con SceneNodes. Tambin puede convertir las mallas (mesh) de Ogre a
guras convexas en Bullet, proporcionando un mecanismo de debug de Bullet como
observamos en la imagen y herramientas de conversin de un vector de Bullet a un
quaternio de Ogre (ver Figura A.5).
En este ejemplo (listado A.4)comprobamos como btOgre conecta el Nodo de Ogre
con la forma convexa de Bullet. Cada vez que se aplique un cambio en el mundo fsico
de Bullet se vern sus consecuencias en el Nodo de Ogre. Esto permite la integracin
de los dos motores y el acceso independiente a las propiedades del objeto en ambos
mundos.
Cada vez que actualicemos el bucle principal del juego ser necesario hacer una
llamada a stepSimulation con el tiempo calculado entre frame y frame.
2 BtOgre::DebugDrawer m_pDebugDrawer =
3 new BtOgre::DebugDrawer(OgreFramework::getSingletonPtr()->
4 m_pSceneMgr->getRootSceneNode(), m_pDynamicsWorld);
5 m_pDynamicsWorld->setDebugDrawer(m_pDebugDrawer);
6
7 // Actualizamos el motor Bullet
8 m_pDynamicsWorld->stepSimulation(timeSinceLastFrame);
9 m_pDebugDrawer->step();
[1092] ANEXO A. INTRODUCCIN A OGREFRAMEWORK
A.6. Referencias
http://www.ogre3d.org/tikiwiki/Basic+Ogre+Framework
http://www.ogre3d.org/tikiwiki/Advanced+Ogre+Framework
http://bitbucket.org/spacegaier/basicogreframework
http://bitbucket.org/spacegaier/advancedogreframework
http://gafferongames.com/game-physics/fix-your-timestep/
http://ogrees.wikispaces.com/Tutorial+Framework+Avanzado+
de+Ogre
http://www.ogre3d.org/tikiwiki/SdkTrays
http://www.ogre3d.org/tikiwiki/OgreBites
http://github.com/nikki93/btogre
Vault Defense al detalle
Anexo B
David Frutos Talavera
E
n este anexo se explicarn ciertos detalles del juego Vault Defense. Detalles de
su GUI (Graphical User Interface), interfaz que usa CEGUI y con el cual, se
han creado unos botones propios, una pantalla de carga y un efecto de cmara
manchada de sangre, tambin se explicar al detalle el efecto de parpadeo de las luces,
usado para darles un aspecto de luz de antorcha, y por ltimo los enemigos, unos
agentes de estado que implementan ciertos comportamientos.
Pero antes de explicar dichos detalles del juego, vamos a explicar cmo congurar
Visual C++ 2010 Express 1 para poder compilar nuestros juegos con OGRE + CEGUI
para la plataforma Windows.
1093
[1094] ANEXO B. VAULT DEFENSE AL DETALLE
-----------
-- Renderers
1. Apartado C/C++ / general (Figura B.2). En dicho apartado se conguran los in-
cludes del proyecto, podemos observar que en CEGUIBase se aaden ../.
./../cegui/include; y, por otra parte, ../../../dependencies/
incluye; por eso es necesario colocar la carpeta dependencias en la carpeta
de CEGUI.
2. Vinculador / General (Figura B.3). Aqu es donde diremos dnde estn nuestras
bibliotecas (las carpetas Lib).
2 http://www.cegui.org.uk/wiki/index.php/Downloads
3 http://www.ogre3d.org/download/sdk
B.1. Conguracin en Visual 2010 Express [1095]
3. Vinculador / Entrada (Figura B.4). En este apartado se colocan los nombre de las
bibliotecas adicionales, es decir, las bibliotecas que estn en las rutas aadidas
en general y que vayamos a usar.
Plugin=RenderSystem_GL_d
B.1. Conguracin en Visual 2010 Express [1097]
Plugin=Plugin_ParticleFX_d
Ya con estos 3 archivos aadidos solo falta crear una carpeta plugins y aadir los
.dlls correspondientes dentro de ella.
Con los plugins colocados pasamos a aadir los .dlls que faltan. Estos varan segn
las bibliotecas usadas pero por lo general har falta OgreMain y OIS. Acordaros de
copiar los dlls correspondientes segn vuestra compilacin, si estis en modo debug
usad los que terminan en _d.
B.1. Conguracin en Visual 2010 Express [1099]
44 </DimOperator>
45 </UnifiedDim>
46 </Dim>
47 <Dim type="Height"><UnifiedDim scale="1"
48 type="Height" /></Dim>
49 </Area>
50 <Image imageset="VaultDefense"
51 image="BotonSinglePlayerNormal" />
52 <VertFormat type="Stretched" />
53 <HorzFormat type="Stretched" />
54 </ImageryComponent>
55 </ImagerySection>
56
57 <ImagerySection name="hover">
58 <!-- .................... -->
59 </ImagerySection>
60
61 <ImagerySection name="pushed">
62 <!-- .................... -->
63 </ImagerySection>
64
65 <ImagerySection name="disabled">
66 <!-- .................... -->
67 </ImagerySection>
68 <StateImagery name="Normal">
69 <Layer>
70 <Section section="normal" />
71 </Layer>
72 </StateImagery>
73 <StateImagery name="Hover">
74 <Layer>
75 <Section section="hover" />
76 </Layer>
77 </StateImagery>
78 <StateImagery name="Pushed">
79 <Layer>
80 <Section section="pushed" />
81 </Layer>
82 </StateImagery>
83 <StateImagery name="PushedOff">
84 <Layer>
85 <Section section="hover" />
86 </Layer>
87 </StateImagery>
88 <StateImagery name="Disabled">
89 <Layer>
90 <Section section="disabled">
91 <Colours topLeft="FF7F7F7F" topRight="FF7F7F7F"
92 bottomLeft="FF7F7F7F" bottomRight="FF7F7F7F" />
93 </Section>
94 </Layer>
95 </StateImagery>
96 </WidgetLook>
stas son las marcas ms usadas. Con estas marcas podemos crear unos botones
propios que acten como nosotros queremos. En el caso del juego Vault Defense los
botones estn creados por 3 zonas, una zona central donde se muestra la imagen del
botn, y 2 zonas, una a cada lado, que se usara en caso de que el botn este en estado
hover o pushed. La imagen BotonDer, BotonIzq es una imagen transparente que solo
se usa para rellenar el hueco, en los estados hover y pushed se pone una imagen distinta
dndole as un efecto curioso a los botones.
El codigo de hover, pushed y disable es similar al de normal, solo que cambia
las imagenes que carga. En hover en los bloques laterales, los que en normal son
BotonDer y BotonIzq cambian por otra imagen, en el caso del Vault Defense por
BotonDerHover y BotonIzqHover, imagenes que muestran un pico y una ballesta, que
apareceran a los lados del botn seleccionado.
Y lo ltimo es el archivo scheme:
Denimos que imageset vamos a usar, que looknfeel usamos, y luego pasamos a
crear los botones con:
En este cdigo podemos ver la creacin de unos botones muy simples, se ha cogido
como base un archivo scheme ya creado y se ha modicado. En WindowType ponemos
el identicador del window para poder usarlo en nuestros layouts y en LookNFeel
ponemos nuestro lookNFeel modicado.
En el cdigo es necesario cargar nuestros propios archivos:
Este mtodo de carga no calcula el tiempo real de lo que tardan las operaciones,
es una carga por pasos. Se denen una serie de cargas, y segun se van cargando se
va pasando de paso. En resumen, se dividen los calculos en grupos y se realiza un
RenderOneFrame por cada calculo realizado, cuando se realiza el calculo se ejecuta
este cdigo.
Esto se puede cambiar, se puede crear una barra propia y hacerle un escalado.
Tambin se puede poner una imagen en el centro e ir cambindola. Existen muchas
maneras, pero sta es la ms sencilla.
2 Name="UI/Fondo/Sangre10"><Property Name="Image"
3 Value="set:VaultDefenseSangre2 image:Sangre"/>
4 <Property Name="UnifiedAreaRect"
5 Value="{{0.0,0},{0.0,0},{1,0},{1,0}}"/>
6 <Property Name="BackgroundEnabled" Value="False"/>
7 <Property Name="Alpha" Value="0"/>
8 <Property Name="FrameEnabled" Value="False"/>
9 </Window>
Luego en alguna parte del juego, en el caso de Vault Defense cuando el personaje
recibe un impacto, activamos una de las sangres de manera aleatoria, mostrndola y
poniendo su alpha a 1, durante el enterFrame() vamos decrementando el alpha.
Los enemigos llevan a sus pies unos bloques pequeos ocultos. Dichos bloques se
usan para calcular colisiones entre cajas AABB.]
La parte importante son las lneas 15-16 . Dicha funcin comprueba la interseccin
de 2 nodos y devuelve una AxixAlignedBox, si es distinto de Null la caja invisible esta
chocando con otra caja entonces ambas cajas se repelen.
Bibliografa
[1] www.boost.org.
[2] www.cegui.org.uk.
[3] www.swig.org.
[4] ISO/IEC 9241. ISO/IEC 9241-11: Ergonomic requirements for ofce work with
visual display terminals (VDTs) Part 11: Guidance on usability. ISO, 1998.
[5] Advanced Micro Devices, Inc., Disponible en lnea en http://support.
amd.com/us/Processor_TechDocs/31116.pdf. BIOS and Kernel
Developers Guide (BKDG) For AMD Family 10h Processors, Apr. 2010.
[6] T. Akenine-Moller, E. Haines, and N. Hoffman. Real-Time Rendering (3rd
Edition). AK Peters, 2008.
[7] E. Akenine-Mller, T. Haines and N. Hoffman. Real-Time Rendering. AK
Peters, Ltd., 2008.
[8] T. Akenine-Mller, E. Haines, and N. Hoffman. Real-Time Rendering. AK
Peters, 3rd edition, 2008.
[9] Andrei Alexandrescu. Modern C++ Design: Generic Programming and
Design Patterns Applied. Addison-Wesley Professional, 2001.
[10] Grenville Armitage, Mark Claypool, and Philip Branch. Networking and Online
Games. Wiley, 2006.
[11] K. Beck. Extreme Programming Explained: Embrace Change. Addison- Wesley
Professional. Addison-Wesley Professional, 1999.
1109
[1110] CAPTULO 31. BIBLIOGRAFA
[34] Randima Fernando and Mark J. Kilgard. The Cg Tutorial: The Denitive Guide
to Programmable Real-Time Graphics. Addison-Wesley Longman Publishing
Co., Inc., Boston, MA, USA, 2003.
[35] K. Flood. Game unied process (gup).
[36] S. Franklin and Graesser A. Is it an agent, or just a program?: A taxonomy
for autonomous agents. In Proceedings of the Third International Workshop
on Agent Theories, Architectures, and Languages, pages 2135, London, UK,
1997. Springer-Verlag.
[37] E. Gamma. Design Patterns: Elements of Reusable Object-Oriented Software.
Addison-Wesley Professional, 1995.
[38] E. Gamma, R. Helm, R. Johnson, and J. Vlissided. Design Patterns. Addison
Wesley, 2008.
[39] Joseph C. Giarratano and Gary Riley. Expert Systems. PWS Publishing Co.,
Boston, MA, USA, 3rd edition, 1998.
[40] J. L. Gonzlez. Jugabilidad: Caracterizacin de la Experiencia del Jugador en
Videojuegos. PhD thesis, Universidad de Granada, 2010.
[41] T. Granollers. MPIu+a. Una metodologa que integra la ingeniera del
software, la interaccin persona-ordenador y la accesibilidad en el contexto
de equipos de desarrollo multidisciplinares. PhD thesis, Universitat de Lleida,
2004.
[42] J Gregory. Game Engine Architecture. AK Peters, 2009.
[43] Khronos Group. COLLADA Format Specication.
http://www.khronos.org/collada/, 2012.
[44] H. Hoppe. Efcient implementation of progressive meshes. Computers &
Graphics, 22(1):2736, 1998.
[45] IberOgre. Formato OGRE 3D. http://osl2.uca.es/iberogre, 2012.
[46] R. Ierusalimschy. Programming in LUA (2nd Edition). Lua.org, 2006.
[47] InniteCode. Quadtree Demo with source code.
http://www.innitecode.com/?view_post=23, 2002.
[48] Intel Corporation, Disponible en lnea en http://www.intel.com/
content/dam/doc/manual/. Intel 64 and IA-32 Architectures Softwa-
re Developers Manual. Volume 3B: System Programming Guide, Part 2, Mar.
2012.
[49] ISO/IEC. Working Draft, Standard for Programming Language C++. Docu-
ment number N3242=11-0012., Feb. 2011.
[50] D. Johnson and J. Wiles. Effective affective user interface design in games.
Ergonomics, 46(13-14):13321345, 2003.
[51] N.M. Josuttis. The C++ standard library: a tutorial and handbook. C++
programming languages. Addison-Wesley, 1999.
[52] G. Junker. Pro OGRE 3D Programming. Apress, 2006.
[53] G. Junker. Pro Ogre 3D Programming. Apress, 2006.
[1112] CAPTULO 31. BIBLIOGRAFA
[97] S.J. Teller and C.H. Squin. Visibility preprocessing for interactive walkth-
roughs. In ACM SIGGRAPH Computer Graphics, volume 25, pages 6170.
ACM, 1991.
[98] T. Theoharis, G. Papaioannou, and N. Platis. Graphics and Visualization:
Principles & Algorithms. AK Peters, 2008.
[99] Jos Toms Tocino, Pablo Recio, and Manuel Palomo-Duarte. Gades Siege.
http://code.google.com/p/gsiege, 2012.
[100] T. Ulrich. Rendering massive terrains using chunked level of detail control.
SIGGRAPH Course Notes, 3(5), 2002.
[101] Wang and Niniane. Let there be clouds! Game Developer Magazine, 11:3439,
2004.
[102] G. Weiss. Multiagent Systems: a Modern Approach to Distributed Articial
Intelligence. The MIT Press, 1999.
[103] Wikipedia. Stratego wikipedia, the free encyclopedia, 2014. [Online;
accessed 30-May-2014].
[104] M. Wooldridge and N.R. Jennings. Intelligent agents: Theory and practice. The
Knowledge Engineering Review, 10(2):115152, 1995.
[105] L.A. Zadeh. Fuzzy sets. Information and control, 8(3):338353, 1965.
[106] H. Zhang, D. Manocha, T. Hudson, and K.E. Hoff III. Visibility culling
using hierarchical occlusion maps. In Proceedings of the 24th annual confe-
rence on Computer graphics and interactive techniques, pages 7788. ACM
Press/Addison-Wesley Publishing Co., 1997.
Este manual fue maquetado en
una mquina GNU/Linux en
Junio de 2014