10.07.2015 Views

[ebook]Programacion de videojuegos con SDL

[ebook]Programacion de videojuegos con SDL

[ebook]Programacion de videojuegos con SDL

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LPROGRAMACIÓN DE VIDEOJUEGOS CON <strong>SDL</strong>0


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LProgramación <strong>de</strong> Vi<strong>de</strong>ojuegos <strong>con</strong> <strong>SDL</strong>Alberto García SerranoEsta obra está bajo una licencia Attribution-NonCommercial-NoDerivs 2.5 <strong>de</strong> CreativeCommons. Para ver una copia <strong>de</strong> esta licencia, visitehttp://creativecommons.org/licenses/by-nc-nd/2.5/ o envie una carta a Creative Commons,559 Nathan Abbott Way, Stanford, California 94305, USA.1


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LÍndice.Introducción ............................................................................................. 4Historia <strong>de</strong> los vi<strong>de</strong>ojuegos....................................................................... 6La llegada <strong>de</strong>l or<strong>de</strong>nador personal ...............................................................................................6Sistemas Operativos o la torre <strong>de</strong> Babel.......................................................................................8Anatomía <strong>de</strong> un Vi<strong>de</strong>ojuego..................................................................... 9Entrada....................................................................................................................................9Visualización. ..............................................................................................................................9Sonido........................................................................................................................................10Comunicaciones.........................................................................................................................10Game loop..................................................................................................................................10Los cimientos <strong>de</strong> un vi<strong>de</strong>ojuego...................................................................................................11La i<strong>de</strong>a. ......................................................................................................................................12Los elementos <strong>de</strong>l juego.............................................................................................................12Manos a la obra..........................................................................................................................14Primeros pasos <strong>con</strong> <strong>SDL</strong> ........................................................................ 23Trabajando <strong>con</strong> <strong>SDL</strong> ..................................................................................................................24Vi<strong>de</strong>o .............................................................................................................................................26Cargando y mostrando gráficos..................................................................................................28Efectos especiales: Transparencias y alpha-blending.................................................................32Otras funciones <strong>de</strong> interés ..........................................................................................................36Gestión <strong>de</strong> eventos........................................................................................................................38Tipos <strong>de</strong> eventos ........................................................................................................................39Lectura <strong>de</strong> eventos .....................................................................................................................39Eventos <strong>de</strong>l teclado ....................................................................................................................40Eventos <strong>de</strong> ratón.........................................................................................................................44Eventos <strong>de</strong>l joystick ...................................................................................................................45Otros eventos .............................................................................................................................45Joystick .........................................................................................................................................47Recopilando información sobre el joystick ................................................................................47Leyendo el joystick ....................................................................................................................48Audio.............................................................................................................................................51CD-ROM ......................................................................................................................................56El Window Manager....................................................................................................................60Timming .......................................................................................................................................60Librerías auxiliares para <strong>SDL</strong> ............................................................... 62<strong>SDL</strong>_ttf .........................................................................................................................................62<strong>SDL</strong>_image ...................................................................................................................................66<strong>SDL</strong>_mixer ...................................................................................................................................69Sonidos ......................................................................................................................................70Música .......................................................................................................................................73Sprites: héroes y villanos........................................................................ 76Control <strong>de</strong> sprites.........................................................................................................................77Implementando los sprites...........................................................................................................79Utilizando nuestra librería ..........................................................................................................86Un Universo <strong>de</strong>ntro <strong>de</strong> tu or<strong>de</strong>nador..................................................... 91Almacenando y mostrando tiles..................................................................................................93Diseñando el mapa .......................................................................................................................98Scrolling......................................................................................................................................100Enemigos, disparos y explosiones........................................................ 1052


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LTipos <strong>de</strong> inteligencia ..................................................................................................................105Comportamientos y máquinas <strong>de</strong> estado..................................................................................107Disparos y explosiones ...............................................................................................................108¡Que comience el juego! ...................................................................... 117Enemigos.....................................................................................................................................117Niveles.........................................................................................................................................119Temporización............................................................................................................................120Pantalla inicial, puntación y vidas ............................................................................................121¿Y ahora que? ............................................................................................................................136Instalación <strong>de</strong> <strong>SDL</strong>............................................................................... 137Windows (VC++) .......................................................................................................................137Linux...........................................................................................................................................141De C a C++ ........................................................................................... 143Clases y objetos ..........................................................................................................................144Herencia......................................................................................................................................147Polimorfismo ..............................................................................................................................147Punteros y memoria...................................................................................................................148Recursos................................................................................................ 150Bibliografía.................................................................................................................................150Programación...........................................................................................................................150Programación <strong>de</strong> vi<strong>de</strong>ojuegos ..................................................................................................150Gráficos ...................................................................................................................................151Enlaces........................................................................................................................................151Programación...........................................................................................................................151Programación <strong>de</strong> vi<strong>de</strong>ojuegos ..................................................................................................151Gráficos ...................................................................................................................................1523


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LIntroducciónLa imaginación es el motor <strong>de</strong> la creatividad, y la creatividad elmotor <strong>de</strong> la inteligencia.Muchas son las personas que opinan que la programación <strong>de</strong> or<strong>de</strong>nadores esuna ciencia, otros lo ven como una ingeniería, sin embargo, algunos preferimosverla como un arte. Si tienes este libro entre tus manos, es porque te interesala programación y también los vi<strong>de</strong>ojuegos. Si es así, enhorabuena, porqueestás a punto <strong>de</strong> entrar en un mundo realmente fascinante. La programación <strong>de</strong>vi<strong>de</strong>ojuegos es muy diferente a la programación tradicional, requiere <strong>de</strong> una grancreatividad, pero también <strong>de</strong> una gran curiosidad y ganas <strong>de</strong> <strong>de</strong>scubrir y, por que no,inventar cosas nuevas. De hecho, cuando creamos un vi<strong>de</strong>ojuego, estamos creando unmundo nuevo, <strong>con</strong> su propio aspecto y sus propias leyes. En ese sentido el programadores como un dios <strong>de</strong>ntro <strong>de</strong> ese mundo. La mayoría <strong>de</strong> las personas que se <strong>de</strong>dican a laprogramación se han planteado en algún momento el hacer un juego, y sin embargo sonpocos los programadores que se <strong>de</strong>ci<strong>de</strong>n a hacerlo. ¿Hace falta algún don especial parahacer vi<strong>de</strong>ojuegos? Probablemente no, pero hay muy buenos escritores <strong>de</strong> novelas queno son capaces <strong>de</strong> escribir poesía, y viceversa. En programación parece ocurrir lo mismo,hay programadores especialmente dotados para la programación <strong>de</strong> gestión y otros se<strong>de</strong>senvuelven mejor <strong>con</strong> una programación más cercana a la máquina.Supondré que el lector ya posee <strong>con</strong>ocimientos <strong>de</strong> programación, y más<strong>con</strong>cretamente <strong>de</strong>l lenguaje C. En el transcurso <strong>de</strong>l libro, vamos a introducir las técnicasbásicas utilizadas en la creación <strong>de</strong> vi<strong>de</strong>ojuegos <strong>de</strong> forma práctica y a través <strong>de</strong> ejemplos,incluyendo el <strong>de</strong>sarrollo <strong>de</strong> un juego completo. Vamos a utilizar para ello una libreríamultiplataforma llamada <strong>SDL</strong> (Simple Directmedia Layer) que nos va a permitir quenuestros juegos sean portables entre Windows, Linux, Macintosh, BeOs y otros. Tambiénvamos a introducir ciertas características <strong>de</strong> la programación orientada a objetos (POO)que nos ofrece C++, pero no <strong>de</strong>be preocuparse el lector <strong>de</strong>s<strong>con</strong>ocedor <strong>de</strong> la POO,porque <strong>de</strong>dicamos un apéndice completo para familiarizarnos <strong>con</strong> los elementos quevamos a usar <strong>de</strong> dicho lenguaje. La programación orientada a objetos es hoy necesaria ala hora <strong>de</strong> <strong>con</strong>seguir juegos <strong>de</strong> calidad profesional, por ello, introduciremos ciertosaspectos <strong>de</strong> este paradigma <strong>de</strong> programación.Respecto a al grafismo utilizado en el juego <strong>de</strong> ejemplo que <strong>de</strong>sarrollaremos durante ellibro, he utilizado la librería <strong>de</strong> sprites GPL <strong>de</strong> Ari Feldman (http://www.arifeldman.com/),al cual agra<strong>de</strong>zco, y animo a seguir ampliando.Antes <strong>de</strong> acabar, quiero hacer una aclaración. Este no es un libro sobre programacióny uso <strong>de</strong> <strong>SDL</strong>. Es un libro sobre programación <strong>de</strong> vi<strong>de</strong>ojuegos, por lo tanto, no esperesuna exhaustiva referencia sobre <strong>SDL</strong>. Iremos <strong>de</strong>scubriendo los diferentes aspectos <strong>de</strong>esta librería según los necesitemos en nuestro camino a la creación <strong>de</strong> un vi<strong>de</strong>ojuego, espor ello que habrá elementos <strong>de</strong> la librería que no formarán parte <strong>de</strong> los temas cubiertospor este libro.Quiero, finalmente, <strong>de</strong>jar patente el objetivo que siempre he perseguido mientrasescribía el presente texto: Mantener las cosas simples, sin entrar en tecnicismosinnecesarios que distraigan al programador menos formal <strong>de</strong>l objetivo final, que es dotarle<strong>de</strong> las herramientas necesarias para expresar toda su creatividad en forma <strong>de</strong>vi<strong>de</strong>ojuego. No me cabe duda que hay cientos o miles <strong>de</strong> potenciales buenosprogramadores <strong>de</strong> juegos que, aún teniendo los <strong>con</strong>ocimientos <strong>de</strong> programaciónnecesarios, no han podido acce<strong>de</strong>r a una información clara y carente <strong>de</strong> formalismos más4


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lpropios <strong>de</strong> un texto universitario. Espero que este sea un libro “para todos”, aunque ellosignifique sacrificar en precisión y formalidad. El intentar trasladar la información<strong>con</strong>tenida en el libro a un lenguaje sencillo y accesible me ha resultado a vecesciertamente una difícil tarea, por lo que pido excusas si en algún momento no he<strong>con</strong>seguido expresar las i<strong>de</strong>as <strong>con</strong> la suficiente simplicidad y claridad.Pue<strong>de</strong> <strong>con</strong>tactar <strong>con</strong> el autor en la dirección <strong>de</strong> correo electrónicosdlintro@agserrano.com.5


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LCapítulo1Historia <strong>de</strong> los vi<strong>de</strong>ojuegosAllá don<strong>de</strong> el tiempo pier<strong>de</strong> su memoria, gran<strong>de</strong>s hombres y mujeres<strong>con</strong>struyeron nuestro futuro.La prehistoria <strong>de</strong> los vi<strong>de</strong>ojuegos, se remonta a 1958. Bill Nighinbotthan presenta enuna feria científica un aparato que permite, mediante unos potenciómetros, mover unapequeña raqueta en un tubo <strong>de</strong> rayos catódicos. Bill no fue capaz <strong>de</strong> intuir el bastopotencial que aquel pequeño aparato tenía. Basándose en este aparato, NolanBushnell crea en 1972 un vi<strong>de</strong>ojuego llamado Pong. Nollan, que sí es capaz <strong>de</strong> ver lasposibilida<strong>de</strong>s <strong>de</strong> este nuevo mercado, funda Atari, pero antes, Nollan ya había comercializadosu primer vi<strong>de</strong>ojuego: Computer Space. Años más tar<strong>de</strong>, en 1976, un empleado <strong>de</strong> Atarillamado Steve Jobs, ayudado por Steve Wozniak, crean un vi<strong>de</strong>ojuego llamado BreakOut.Dos años <strong>de</strong>spués, ambos <strong>de</strong>jarán Atari para crear Apple Computer y pasar a la historia <strong>de</strong> lainformática.Steve Wozniak y Steve Jobs en su garaje, california. 1976Es en 1978 cuando Taito lanza al mercado el famoso Space Inva<strong>de</strong>rs. Este juego eracapaz <strong>de</strong> almacenar las puntuaciones máximas, <strong>con</strong>virtiéndose en todo un clásico.En los siguientes años, comienzan a aparecer en el mercado nuevos vi<strong>de</strong>ojuegos <strong>de</strong>excelente calidad, y que se <strong>con</strong>vertirán en clásicos. Juegos como Donkey Kong, Frogger,Galaga, Pac Man, etc...La llegada <strong>de</strong>l or<strong>de</strong>nador personalEn la década <strong>de</strong> los 80, los or<strong>de</strong>nadores personales comienzan a popularizarse, y ponenen manos <strong>de</strong> particulares la posibilidad, no sólo <strong>de</strong> jugar a vi<strong>de</strong>ojuegos creados para estosor<strong>de</strong>nadores, sino que ahora cualquiera pue<strong>de</strong> a<strong>de</strong>ntrarse en el mundo <strong>de</strong> la programación <strong>de</strong>6


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lvi<strong>de</strong>ojuegos. Estos pequeños or<strong>de</strong>nadores <strong>de</strong> 8 bits hicieron las <strong>de</strong>licias <strong>de</strong> miles <strong>de</strong>aficionados a la informática. En España se hizo muy popular el Sinclair Spectrum. Un pequeñoor<strong>de</strong>nador muy e<strong>con</strong>ómico <strong>con</strong> un procesador Z80 y una memoria <strong>de</strong> 48 kilobytes. Otrosor<strong>de</strong>nadores similares fueron el Amstrad 464, MSX y Commodore 64.Commodore 64De esta época tenemos juegos tan recordados como Manic Miner, Xevious, Jet Set Willy,Arkanoid y otros. A finales <strong>de</strong> los 80, surgen grupos <strong>de</strong> programadores en el Norte <strong>de</strong> Europaque comienzan a crear pequeñas joyas <strong>de</strong> la programación que intentaban exprimir elpotencial <strong>de</strong> estas pequeñas máquinas al máximo. Concretamente estos grupos se crean entorno a la máquina <strong>de</strong> Commodore, gracias a sus posibilida<strong>de</strong>s gráficas y <strong>de</strong> sonido, algo másavanzadas que la <strong>de</strong> sus competidores <strong>de</strong> la época. Estos pequeños programas es lo que hoyse ha dado en llamar Demos. Son por tanto los creadores <strong>de</strong> la actual <strong>de</strong>moscene.En la década siguiente, aparecen or<strong>de</strong>nadores más potentes. Son equipos <strong>con</strong>microprocesadores <strong>de</strong> 16 bits y tienen entre 512 kbytes y 1 Mb <strong>de</strong> memoria. Quizás el máspopular <strong>de</strong> estos fue el Amiga 500, también <strong>de</strong> Commodore, aunque sin olvidar al Atari ST o alAcorn Archime<strong>de</strong>s. Estos or<strong>de</strong>nadores tenían unas capacida<strong>de</strong>s gráficas muy superiores asus antecesores. Podían utilizar 4096 colores simultáneos, y su capacidad <strong>de</strong> sonido tambiéncomenzaba a ser bastante espectacular. De esta época son juegos como Defen<strong>de</strong>r of theCrown, Barbarian, Elvira, Lemings y Maniac Mansión.Paralelamente a esta evolución, otro or<strong>de</strong>nador iba ganando a<strong>de</strong>ptos a marchas forzadas.Sus capacida<strong>de</strong>s gráficas y <strong>de</strong> sonido no eran mejores que la <strong>de</strong> sus competidores, <strong>de</strong> hechoeran bastante inferiores, sin embargo, lograron hacerse poco a poco <strong>con</strong> el mercado <strong>de</strong> losor<strong>de</strong>nadores personales, hasta el punto <strong>de</strong> reinar casi en solitario (<strong>con</strong> permiso <strong>de</strong>l AppleMacintosh). Evi<strong>de</strong>ntemente hablamos <strong>de</strong>l PC. Afortunadamente, al <strong>con</strong>vertirse en el sistemamayoritario, sus capacida<strong>de</strong>s gráficas y <strong>de</strong> sonido comienzan a avanzar a pasos agigantados.Una fecha clave es abril <strong>de</strong> 1987, cuando IBM presenta en el mercado la tarjeta gráfica VGA.Dejando obsoletas a las legendarias EGA y CGA. A la VGA la seguiría la SVGA, y <strong>de</strong>spuéstoda una pléya<strong>de</strong> <strong>de</strong> tarjetas <strong>con</strong> cada vez más memoria y aceleración gráfica 3D, llegandohasta las actuales Vodoo, las Gforce <strong>de</strong> Nvidia y las Ra<strong>de</strong>on <strong>de</strong> ATI, <strong>con</strong> unas capacida<strong>de</strong>sgráficas fuera <strong>de</strong> lo común a un precio muy asequible. Como es normal, esta época estámarcada por juegos como Quake, Counter-Strike (half life), Heretic, Warcraft y Hexen.7


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LSistemas Operativos o la torre <strong>de</strong> BabelEl or<strong>de</strong>nador PC, comercializado por IBM en el año 1982, ha pasado por varias etapas <strong>de</strong>existencia. Aquellos primeros PCs iban equipados <strong>con</strong> el sistema operativo PC-DOS, que noera más que una versión <strong>de</strong> IBM <strong>de</strong>l MS-DOS <strong>de</strong> Microsoft. En los tiempos <strong>de</strong>l MS-DOS laprogramación <strong>de</strong> vi<strong>de</strong>ojuegos era bastante compleja, hacían falta gran<strong>de</strong>s <strong>con</strong>ocimientos <strong>de</strong>lenguaje ensamblador y <strong>de</strong> la estructura interna <strong>de</strong> la máquina, así como <strong>de</strong> la tarjeta <strong>de</strong> vi<strong>de</strong>oy <strong>de</strong> sonido. Toda la programación se hacía directamente sobre el hardware. Es fácil imaginarlos in<strong>con</strong>venientes, había que preparar el juego para que funcionara <strong>con</strong> muchos mo<strong>de</strong>los <strong>de</strong>tarjeta gráfica y también para los diferentes mo<strong>de</strong>los <strong>de</strong> tarjetas <strong>de</strong> sonido (Adlib, GravisUltrasound (GUS), Sound Blaster). Es <strong>de</strong>cir, hacer un juego, por simple que éste fuera,requería un esfuerzo muy alto, sin <strong>con</strong>tar <strong>con</strong> que no siempre toda la información necesariapara acce<strong>de</strong>r al hardware estaba al alcance <strong>de</strong> todo el mundo.La llegada <strong>de</strong> Windows tampoco mejora <strong>de</strong>masiado el panorama, ya que no permite unacceso directo al hardware y su rendimiento gráfico es pobre. El acceso a los recursos <strong>de</strong>lsistema también varía <strong>de</strong> un Sistema Operativo a otro. Se hace imprescindible, pues, crearuna interfaz común para el <strong>de</strong>sarrollo <strong>de</strong> vi<strong>de</strong>ojuegos que permita el acceso estandarizado y<strong>con</strong> una interfaz común a los recursos <strong>de</strong>l sistema. Algunas <strong>de</strong> estas propuestas son OpenGLy DirectX (éste último sólo para sistemas Windows).En el caso <strong>de</strong> OpenGL, se nos ofrece una interfaz multiplataforma, sin embargo, se limita aofrecernos una interfaz <strong>de</strong> acceso a gráficos (especializada en 3D, aunque <strong>con</strong> posibilida<strong>de</strong>s<strong>de</strong> 2D) y no nos permite el acceso a otros recursos <strong>de</strong>l sistema (teclado, sonido, joystick,timmers, etc...).DirectX, sin embargo, está compuesto por varias sub-interfaces <strong>con</strong> el SO y el hardware,como DirectDraw (Gráficos 2D), Direct3D (Gráficos 3D), DirectInput (Entrada y Salida),DirectPlay (Capacida<strong>de</strong>s <strong>de</strong> comunicación en re<strong>de</strong>s TCP/IP), DirectSound (Acceso ahardware <strong>de</strong> sonido) y algún que otro componente más. Como vemos, DirectX tiene grancantidad <strong>de</strong> capacida<strong>de</strong>s, pero presenta dos in<strong>con</strong>venientes. El primero es que sólo es válidopara sistemas Windows, y el segundo es que por la propia naturaleza <strong>de</strong> la programación enWindows, su uso es algo engorroso y no apto para programadores <strong>con</strong> poca experiencia <strong>con</strong>las APIs <strong>de</strong> Windows y más <strong>con</strong>cretamente <strong>con</strong> la interfaz COM+.Afortunadamente, la empresa Loki Games, ante la necesidad <strong>de</strong> hacer portables los juegos<strong>de</strong> Windows a Linux y a otros sistemas operativos, crean una librería multiplataforma llamada<strong>SDL</strong> (Simple Directmedia Layer), que nos ofrece acceso a los recursos <strong>de</strong> la máquina(gráficos, sonido, entrada/salida, timming, etc...) mediante una interfaz coherente ein<strong>de</strong>pendiente <strong>de</strong>l SO. <strong>SDL</strong>, a<strong>de</strong>más, nos permite utilizar la interfaz OpenGL para gráficos 3D.8


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LCapítulo2Anatomía <strong>de</strong> un Vi<strong>de</strong>ojuegoSomos nuestra memoria, somos ese quimérico museo <strong>de</strong> formas in<strong>con</strong>stantes, esemontón <strong>de</strong> espejos rotos.Jorge Luis BorgesCada vez que juegas a tu vi<strong>de</strong>ojuego preferido, <strong>de</strong>ntro <strong>de</strong>l or<strong>de</strong>nador están ocurriendomuchas cosas. Se hace patente que un factor importante en un juego es que semueva <strong>con</strong> soltura y a buena velocidad. Todos los elementos <strong>de</strong>l juego parecenfuncionar <strong>de</strong> forma in<strong>de</strong>pendiente, como <strong>con</strong> vida propia y a la vez. Sin embargo estoes sólo una apariencia, ya que <strong>de</strong>ntro <strong>de</strong>l programa se van sucediendo las diferentes fases <strong>de</strong>ejecución <strong>de</strong> forma secuencial y or<strong>de</strong>nada. En este capitulo vamos a tratar <strong>de</strong> dar una visióngeneral y sin entrar en <strong>de</strong>talles <strong>de</strong> implementación <strong>de</strong> la anatomía <strong>de</strong> un vi<strong>de</strong>ojuego. Vamos aver qué partes lo componen y como se relacionan.Entrada.Un vi<strong>de</strong>ojuego necesita comunicarse <strong>con</strong> el jugador a través <strong>de</strong> un dispositivo <strong>de</strong> entrada,como un Joystick o un teclado. La programación <strong>de</strong>l sistema <strong>de</strong> Entrada/Salida pue<strong>de</strong> parecera priori algo fácil <strong>de</strong> implementar, y <strong>de</strong> hecho lo es. Sin embargo, si no queremos ver comonuestro estupendo juego se hace poco “jugable” para el jugador hay que cuidar mucho esteaspecto. Una mala respuesta <strong>de</strong>l juego ante una pulsación <strong>de</strong>l teclado pue<strong>de</strong> hacer frustrantela experiencia <strong>de</strong> juego para el jugador. Otro factor importante es el soporte <strong>de</strong> una variedad<strong>de</strong> dispositivos <strong>de</strong> entrada. Algunos jugadores preferirán usar el teclado al ratón y viceversa. Sia<strong>de</strong>más queremos que nuestro juego tenga un toque profesional, hay que dar la posibilidad aljugador <strong>de</strong> que <strong>de</strong>fina las teclas <strong>con</strong> las que quiere jugar, a<strong>de</strong>más <strong>de</strong> dar soporte a losdiferentes tipos <strong>de</strong> teclados internacionales.Visualización.La misión <strong>de</strong> esta capa es la <strong>de</strong> <strong>con</strong>vertir el estado interno <strong>de</strong>l juego en una salida gráfica.Ya sea en 2D o 3D, la calidad gráfica es un factor importantísimo. Normalmente, elprogramador trabaja <strong>con</strong> unos gráficos creados por él mismo, y cuya calidad <strong>de</strong>pen<strong>de</strong>rá <strong>de</strong> lasdotes artísticas <strong>de</strong> éste. En una siguiente fase, un artista gráfico crea y sustituye estasimágenes por las <strong>de</strong>finitivas. Uno <strong>de</strong> los elementos principales <strong>de</strong> un buen juego es un buenmotor gráfico, capaz <strong>de</strong> mover una gran cantidad <strong>de</strong> Sprites en un juego 2D o una grancantidad <strong>de</strong> polígonos (triángulos) en el caso <strong>de</strong> un juego en 3D. En el capítulo 6 veremos quées un Sprite.9


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LEn el diseño <strong>de</strong>l motor gráfico, siempre hay que llegar a un compromiso entre lacompatibilidad y el rendimiento. Hay tarjetas gráficas en el mercado <strong>con</strong> unas capacida<strong>de</strong>scasi sobrenaturales, pero que no son estándar, sin embargo, tampoco po<strong>de</strong>mos dar laespalda a los jugadores “profesionales” que equipan sus or<strong>de</strong>nadores <strong>con</strong> la últimaaceleradora 3D <strong>de</strong>l mercado.Sonido.El sonido es un aspecto al que, a veces, no se le da la importancia que merece. Tanimportante como unos buenos gráficos es un buen sonido. Tanto el sonido como la bandasonora <strong>de</strong> un juego son capaces <strong>de</strong> transmitir una gran cantidad <strong>de</strong> sensaciones al jugador, yeso es, al fin y al cabo, lo que se busca. Tanto como la calidad, la buena sincronización <strong>con</strong> loque va sucediendo en pantalla es <strong>de</strong> suma importancia.Comunicaciones.Cada vez más, los juegos en red van ganando en a<strong>de</strong>ptos. La comunicación se realizanormalmente sobre re<strong>de</strong>s TCP/IP. Esto nos permite jugar <strong>con</strong>tra otros jugadores situados ennuestra propia red local o en Internet. La comunicación entre máquinas se lleva a cabomediante la programación <strong>de</strong> sockets. Afortunadamente <strong>SDL</strong> nos provee <strong>de</strong> herramientasmuy potentes para este tipo <strong>de</strong> comunicación, aunque esto es algo que queda fuera <strong>de</strong>lalcance <strong>de</strong> este libro.Game loop.El “game loop” o bucle <strong>de</strong> juego es el encargado <strong>de</strong> “dirigir” en cada momento que tarea seestá realizando. En la figura 2.1. po<strong>de</strong>mos ver un ejemplo <strong>de</strong> game loop. Es sólo un ejemplo, yaunque más o menos todos son similares, no tienen por que tener exactamente la mismaestructura. Analicemos el ejemplo. Lo primero que hacemos es leer los dispositivos <strong>de</strong> entradapara ver si el jugador ha realizado alguna acción. Si hubo alguna acción por parte <strong>de</strong>l jugador,el siguiente paso es procesarla, esto es, actualizar su posición, disparar, etc... <strong>de</strong>pendiendo <strong>de</strong>que acción sea. En el siguiente paso realizamos la lógica <strong>de</strong> juego, es <strong>de</strong>cir, todo aquello queforma parte <strong>de</strong> la acción <strong>de</strong>l juego y que no queda bajo <strong>con</strong>trol <strong>de</strong>l jugador, por ejemplo, elmovimiento <strong>de</strong> los enemigos, cálculo <strong>de</strong> trayectoria <strong>de</strong> sus disparos, comprobamos colisionesentre la nave enemiga y la <strong>de</strong>l jugador, etc... Fuera <strong>de</strong> la lógica <strong>de</strong>l juego quedan otras tareasque realizamos en la siguiente fase, como son actualizar el scroll <strong>de</strong> fondo (si lo hubiera),activar sonidos (si fuera necesario), realizar trabajos <strong>de</strong> sincronización, etc.. Ya por último nosresta volcar todo a la pantalla y mostrar el siguiente frame. Esta fase es llamada “fase <strong>de</strong>ren<strong>de</strong>r”.Normalmente, el game loop tendrá un aspecto similar a lo siguiente:int done = 0;while (!done) {// Leer entrada// Procesar entrada (si se pulsó ESC, done = 1)// Lógica <strong>de</strong> juego// Otras tareas// Mostrar frame}Antes <strong>de</strong> que entremos en el game loop, tendremos que hacer un Múltiples tareas, comoinicializar el modo gráfico, inicializar todas las estructuras <strong>de</strong> datos, etc...10


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LGameloopLeer eventos <strong>de</strong> entrada(Joystick, mouse, teclado)Procesar entrada(mover jugador)Lógica <strong>de</strong> juego(mover enemigos,procesar colisiones, etc...)Otras tareas(Animar fondo (scroll),sonidos,sincronizar tiempo, etc...)Visualizar en pantalla(ren<strong>de</strong>r)Figura 2.1. Game LoopLos cimientos <strong>de</strong> un vi<strong>de</strong>ojuegoComo toda buena obra <strong>de</strong> arte, un buen vi<strong>de</strong>ojuego comienza <strong>con</strong> una buena i<strong>de</strong>a, pero nobasta <strong>con</strong> eso, hay que hacer que esa i<strong>de</strong>a se transforme en una realidad. Es este capítulovamos a <strong>de</strong>sarrollar un pequeño juego que ilustre las fases y las i<strong>de</strong>as implicadas en eldiseño. Des<strong>de</strong> el momento que el diseño <strong>de</strong> un vi<strong>de</strong>ojuego no es una ciencia exacta, sino quetiene mucho <strong>de</strong> arte, este capítulo ha <strong>de</strong> tomarse como una guía general. Como aún nohemos visto nada sobre <strong>SDL</strong> ni sobre programación gráfica, vamos a crear un juego <strong>de</strong>aventuras tipo “<strong>con</strong>versacional”. Los que llevéis más tiempo en esto <strong>de</strong> los or<strong>de</strong>nadores,<strong>con</strong>oceréis ya como funcionan estos juegos (algunos recordarán “La aventura original” y “DonQuijote” por nombrar dos <strong>de</strong> ellos). Básicamente, el or<strong>de</strong>nador nos <strong>de</strong>scribe <strong>de</strong> forma textutallos escenarios sobre los que nos movemos. Po<strong>de</strong>mos dar or<strong>de</strong>nes a nuestro protagonistamediante frases que el or<strong>de</strong>nador interpreta. Por ejemplo, po<strong>de</strong>mos dar una or<strong>de</strong>n como:“coger amuleto”. Algunos <strong>de</strong> estos juegos podían interpretar frases muy complejas, peronosotros nos limitaremos a dos palabras <strong>con</strong> la forma verbo nombre. Por ejemplo: “ir norte” o“usar llave”.11


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LLa i<strong>de</strong>a.Hoy en día no basta <strong>con</strong> unos impresionantes gráficos y un buen sonido para hacer unjuego. Es muy importante tener una i<strong>de</strong>a sobre la que se asiente el juego, o lo que es lomismo, un buen argumento. Inventar una historia en la que pueda sumergirse el jugador ysentirse parte <strong>de</strong> ella es un trabajo más <strong>de</strong> un escritor que <strong>de</strong> un programador. Es por ello queen las empresas <strong>de</strong>dicadas a los vi<strong>de</strong>ojuegos, hay personas <strong>de</strong>dicadas exclusivamente aescribir guiones e historias. Como supongo que eres un buen programador y como para serbuen programador hay que ser una persona creativa, estoy seguro que no te costara<strong>con</strong>seguir una buena i<strong>de</strong>a. Si la musa no te inspira, siempre pue<strong>de</strong>s hacer “remakes” <strong>de</strong> otrosjuegos (intenta poner algo <strong>de</strong> tu propia cosecha para mejorarlo), basarte en alguna película oalgún libro (Poe pue<strong>de</strong> haber inspirado los escenarios <strong>de</strong> algunos <strong>de</strong> los juegos másterroríficos), hacer algún juego <strong>con</strong> temática <strong>de</strong>portiva o incluso basarte en algún sueño opesadilla que hayas tenido (y no me digas que Doom no parece sacado <strong>de</strong> la pesadilla <strong>de</strong> unprogramador adolescente).Pasemos a la práctica. Vamos a crear un argumento para nuestro juego <strong>de</strong> ejemplo:“Fernando es un chico <strong>de</strong> 17 años, su vida es aparentemente normal. Su vecina Carolina, <strong>con</strong>la que trata <strong>de</strong> salir <strong>de</strong>s<strong>de</strong> los 11 años, tiene una especial atracción por todo lo paranormal. Nolejos <strong>de</strong> sus casas hay una pequeña vivienda que según cuentan está embrujada y a la cualllaman la mansión <strong>de</strong>l terror. Según Carolina, todo aquél que entra queda atrapado y sin salidaposible. Fernando, que a<strong>de</strong>más <strong>de</strong> no creer en fantasma es muy dado a las bravu<strong>con</strong>adas, hahecho una apuesta <strong>con</strong> Carolina. Si <strong>con</strong>sigue salir <strong>de</strong> la casa, irá <strong>con</strong> él al baile <strong>de</strong> fin <strong>de</strong>curso. Fernando ha entrado por una ventana, que se ha cerrado <strong>de</strong> golpe cortando su únicasalida.¿Conseguirá Fernando salir <strong>de</strong> la mansión <strong>de</strong>l terror?¿Conseguirá Fernando ir al baile <strong>con</strong> Carolina?Todo <strong>de</strong>pen<strong>de</strong> <strong>de</strong> ti ahora...”Como pue<strong>de</strong>s ver, tampoco hay que ser un gran literato para crear una historia. Aun siendouna historia muy simple, se <strong>con</strong>sigue que el jugador tenga una meta y luche por algo. Si tienesla suerte <strong>de</strong> saber dibujar, pue<strong>de</strong>s incluso crear un story board <strong>con</strong> los <strong>de</strong>talles <strong>de</strong> la historia ylos escenarios <strong>de</strong>l juego.Los elementos <strong>de</strong>l juegoLos <strong>de</strong>talles son importantes y hay que tenerlos claros.¿Cómo va a ser la mansión?¿Cuántas habitaciones tiene?¿Qué objetos po<strong>de</strong>mos en<strong>con</strong>trar?¿Cómo <strong>con</strong>seguimos ganar?¿Cuáles son las dificulta<strong>de</strong>s?Es necesario hacerse todo este tipo <strong>de</strong> preguntas para tener claro como va a<strong>de</strong>sarrollarse una partida. Empecemos por nuestra misión. Para ganar, hemos <strong>de</strong> lograrsalir <strong>de</strong> la casa. Para ello hemos <strong>de</strong> <strong>con</strong>seguir la llave que abre la puerta <strong>de</strong> la misma.Vamos a crear una mansión <strong>de</strong> pequeño tamaño, <strong>con</strong> sólo 9 habitaciones (utilizaremosun mapa <strong>de</strong> 3x3). Veamos como queda el mapa.12


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LHemos numerado las habitaciones <strong>de</strong> la casa para tener una referencia inequívoca <strong>de</strong>cada una. Una vez tenemos claro el mapa, situamos los objetos que nos permitirán cumplirnuestra misión:Objeto 1 – Hueso – Está en la habitación 2.Objeto 2 – Llave – Está en la habitación 3.Objeto 3 – Escalera – Está en la habitación 9.Objeto 4 – Perro – Está en la habitación 9.y una lista <strong>de</strong> reglas sobre cómo usarlos:Habitación 3 – Para coger la llave hay que tener la escalera, ya que la llave está sobre lalámpara <strong>de</strong>l techo.Habitación 9 – Para po<strong>de</strong>r coger la escalera hay que darle un hueso al perro rabioso.Habitación 8 – Para abrir la puerta y salir <strong>de</strong> la mansión hay que tener la llave.Ya <strong>con</strong> el mapa finalizado, los objetos y su función <strong>de</strong>ntro <strong>de</strong>l juego, vamos a ver quéor<strong>de</strong>nes po<strong>de</strong>mos darle a nuestro protagonista y sobre qué objetos.CogerDarUsarIr– Hueso, Llave, Escaleras.– Hueso.– Llave.Es importante <strong>de</strong>finir bien que acciones son válidas sobre cada objeto. Ahora que hemospensado y plasmado por escrito los elementos <strong>de</strong>l juego, es el momento <strong>de</strong> pasar a la acciónempezar a escribir código.13


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LManos a la obraUna <strong>de</strong> las claves a la hora <strong>de</strong> hacer un buen programa es partir <strong>de</strong> unas buenasestructuras <strong>de</strong> datos. Han <strong>de</strong> permitirnos cierta flexibilidad y a su vez no <strong>de</strong>ben ser <strong>de</strong>masiadocomplejas <strong>de</strong> manejar. Empecemos por el mapa. La unidad básica más pequeña para po<strong>de</strong>r<strong>de</strong>scribir el mapa es una habitación, así que vamos a crear el mapa en memoria como unaserie <strong>de</strong> habitaciones. Para <strong>de</strong>scribir una habitación usaremos la siguiente estructura <strong>de</strong> datos:// Estructura <strong>de</strong> datos para las// habitaciónesstruct habitacion {char <strong>de</strong>sc[255];int norte;int sur;int este;int oeste;} habitaciones[10];Hemos creado un array <strong>de</strong> 10 habitaciones (<strong>de</strong> 0 a 9. Aunque usaremos 9. Del 1 al 9), queson precisamente las habitaciones que tiene nuestro mapa. Si recuerdas, en el mapa pusimosuna numeración a cada habitación. Esta numeración ha <strong>de</strong> coincidir <strong>con</strong> su índice en el array.Veamos el significado <strong>de</strong> cada campo.El primero es la <strong>de</strong>scripción textual <strong>de</strong> la habitación. Aquí <strong>de</strong>scribimos lo que el jugadorpue<strong>de</strong> ver. Esta <strong>de</strong>scripción es siempre fija, por lo que omitiremos indicar si hay algún objetoen la habitación, ya que estos podrían cambiar <strong>de</strong> sitios o ser cogidos y ya no estar en lahabitación.Los siguientes cuatro campos son los <strong>de</strong> dirección, correspondientes a Norte, Sur, Este yOeste, e indican a la habitación que llegaremos si optamos por dar la or<strong>de</strong>n <strong>de</strong> movernos enuna <strong>de</strong> éstas direcciones. Veámoslo <strong>con</strong> un ejemplo:// Habitación 4strcpy(habitaciones[4].<strong>de</strong>sc,"Estas en el pasillo.");habitaciones[4].norte=1;habitaciones[4].sur=7;habitaciones[4].este=5;habitaciones[4].oeste=0;El primer campo no necesita mucha explicación, simplemente muestra el texto <strong>de</strong>scriptivo<strong>de</strong> don<strong>de</strong> se haya el jugador. Los cuatro siguientes nos indican que si el jugador <strong>de</strong>ci<strong>de</strong> ir alnorte, llegará a la habitación 1, si <strong>de</strong>ci<strong>de</strong> ir al sur llegará a la habitación 7 y así sucesivamente.Si ponemos uno <strong>de</strong> estos campos a 0, significa que el jugardor no pue<strong>de</strong> moverse en esadirección. Por lo tanto, si en nuestro programa tenemos una variable llamadahabitacionActual en la que se almacena la habitación en la que se encuantra el jugadoren este preciso instante, para hacer el movimiento usaremos algo como:habitacionActual=habitaciones[habitacionActual].norte;Simple, ¿no? Sigamos <strong>con</strong> otra estructura <strong>de</strong> datos importante: la encargada <strong>de</strong> <strong>de</strong>scribirlos objetos. Echémosle un vistazo:// Estructuras <strong>de</strong> datos para los objetosstruct objeto {int estado; // Estado <strong>de</strong>l objetochar <strong>de</strong>sc1[80]; // Descripción para estado 1char <strong>de</strong>sc2[80]; // Descripción para estado 214


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lint hab;int lotengo;} objetos[5];// Habitación en la que se// encuentra// Indica si tengo este objeto// en mi inventarioAl igual que <strong>con</strong> las habitaciones, hemos creado un array <strong>de</strong> 5 elementos para los objetos(aunque usaremos 4. Del 1 al 4.). Veamos el significado <strong>de</strong> los campos <strong>de</strong> la estructura.El campo estado es para objetos que pue<strong>de</strong>n tener dos estados diferentes. Por ejemplo,imagina una linterna, que podría estar encendida o apagada. Los campos <strong>de</strong>sc1 y <strong>de</strong>sc2,<strong>con</strong>tienen la <strong>de</strong>scripción <strong>de</strong>l objeto cuando está en estado 1 y cuando está en estado 2. En<strong>de</strong>sc1 habría algo como “una linterna apagada” y en <strong>de</strong>sc2 tendríamos “unalinterna encendida”. Pue<strong>de</strong> que un objeto sólo tenga un estado, por ejemplo, una llave.En este caso el campo <strong>de</strong>sc2 pue<strong>de</strong> <strong>de</strong>jarse vació o <strong>con</strong>tener el mismo texto que <strong>de</strong>sc1, yaque nunca será mostrado.El campo hab, indica en que habitación se encuentra el objeto, y el campo lotengo siestá a 0 significa que el jugador no posee este objeto y si está a 1 es que lo tiene en suinventario. El siguiente ejemplo inicializa un objeto <strong>de</strong>l juego:// perroobjetos[PERRO].estado=1;strcpy(objetos[PERRO].<strong>de</strong>sc1,"un perro rabioso");strcpy(objetos[PERRO].<strong>de</strong>sc2,"un perro comiéndose un hueso");objetos[PERRO].hab=9;objetos[PERRO].lotengo=0;Este código inicializa el objeto perro. Por claridad hemos usado la <strong>con</strong>stante PERRO enlugar <strong>de</strong> usar directamente su posición en el array. En el caso <strong>de</strong>l perro, una vez que le damosel hueso, pasa al estado 2, en el que ya no es peligroso.Ahora que ya tenemos <strong>de</strong>finidas las estructuras <strong>de</strong> datos, vemos como quedaría el gameloop <strong>de</strong> nuestro juego.// Inicialización <strong>de</strong> estadoswhile (!done) {// Mostramos información <strong>de</strong> la habitación.// Leemos la entrada <strong>de</strong>l jugador}// Procesamos la entrada <strong>de</strong>l jugadorEn la inicialización <strong>de</strong> estados establecemos los valores iniciales <strong>de</strong> las variables que van a<strong>de</strong>finir el estado <strong>de</strong>l juego. Por ejemplo, la variable habitacionActual, indica en quéhabitación nos en<strong>con</strong>tramos. Tenemos que darle un valor inicial, que es la habitación don<strong>de</strong>comienza el jugador la partida. En este caso, la habitación 1.Ya <strong>de</strong>ntro <strong>de</strong>l game loop, la primera tarea es mostrar la <strong>de</strong>scripción <strong>de</strong>l lugar don<strong>de</strong> sehaya el jugador. Para este juego, <strong>con</strong>viene poner esta fase al principio <strong>de</strong>l bucle, sin embargo,en otros (como en el caso <strong>de</strong> 1945, que <strong>de</strong>sarrollaremos en los siguientes capítulos) nos<strong>con</strong>vendrá más situarlo en la parte final <strong>de</strong>l mismo.Hay tres informaciones que necesita el jugador: la <strong>de</strong>scripción <strong>de</strong> la habitación, si seencuentra algún objeto en la misma y por último, cuáles son las salidas válidas <strong>de</strong> la estancia.15


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LEmpecemos por el principio. El siguiente código es el encargado <strong>de</strong> mostrar la <strong>de</strong>scripción <strong>de</strong>la habitación:// Descripciónprintf("\n\n%s",habitaciones[habitacionActual].<strong>de</strong>sc);Como ves, no tiene nada <strong>de</strong> complicado. Simplemente escribe por pantalla la <strong>de</strong>scripción<strong>de</strong> la habitación <strong>con</strong>tenida en la estructura <strong>de</strong> datos que vimos antes. El siguiente paso esmostrar los objetos que hay en la habitación, si es que hubiera alguno.// Mostramos si hay algún objetofor (i=1 ; i


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L// cogerif (!strcmp(verbo,"coger")) {accion = 0;}// Huesoif (!strcmp(nombre,"hueso") && objetos[HUESO].hab == habitacionActual) {accion = 1;objetos[HUESO].hab=0;objetos[HUESO].lotengo=1;printf("\nHas cogido el hueso.");}Este fragmento <strong>de</strong> código muestra como se procesa la entrada <strong>de</strong>l jugador, en <strong>con</strong>cretopara el verbo coger y el objeto hueso. Antes <strong>de</strong> realizar cualquier acción, <strong>de</strong>bemosasegurarnos <strong>de</strong> que el objeto está en la habitación en la que se encuentra el jugador. Si todoes correcto, actualizamos la estructura <strong>de</strong> datos <strong>de</strong>l objeto hueso e informamos al jugador <strong>de</strong>que se realizó la acción.La variable accion es lo que se llama una variable tipo ban<strong>de</strong>ra o flag. Su misión esinformar que se ha realizado una acción o no, en un punto más a<strong>de</strong>lantado <strong>de</strong>l código.Necesitamos saber si se realizó alguna acción para informar covenientemente al jugador encaso <strong>de</strong> no po<strong>de</strong>r llevar a cabo su or<strong>de</strong>n. Si hemos cogido un objeto, ya no está en ningunahabitación, esto lo indicamos poniendo a 0 el campo hab <strong>de</strong> la estructura <strong>de</strong> datos <strong>de</strong>l objeto.El código completo <strong>de</strong>l juego queda así.17


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L// La mansión <strong>de</strong>l terror// Programación <strong>de</strong> vi<strong>de</strong>ojuegos <strong>con</strong> <strong>SDL</strong>// (c) Alberto García Serrano#inclu<strong>de</strong> #inclu<strong>de</strong> /***Declaración <strong>de</strong> estructuras <strong>de</strong> datos***/#<strong>de</strong>fine HUESO 1#<strong>de</strong>fine LLAVE 2#<strong>de</strong>fine ESCALERA 3#<strong>de</strong>fine PERRO 4// Estructuras <strong>de</strong> datos para los objetosstruct objeto {int estado; // Estado <strong>de</strong>l objetochar <strong>de</strong>sc1[80]; // Descripción para estado 1char <strong>de</strong>sc2[80]; // Descripción para estado 2int hab; // Habitación en la que se encuentraint lotengo; // Indica si tengo este objeto en mi inventario} objetos[5];// Estructura <strong>de</strong> datos para las habitaciónesstruct habitacion {char <strong>de</strong>sc[255];int norte;int sur;int este;int oeste;} habitaciones[10];void main() {/***Inicialización <strong>de</strong> estructuras <strong>de</strong> datos***/// Inicialización <strong>de</strong> objetos// huesoobjetos[HUESO].estado=1;strcpy(objetos[HUESO].<strong>de</strong>sc1,"un hueso");strcpy(objetos[HUESO].<strong>de</strong>sc2,"un hueso");objetos[HUESO].hab=2;objetos[HUESO].lotengo=0;// llaveobjetos[LLAVE].estado=1;strcpy(objetos[LLAVE].<strong>de</strong>sc1,"una llave sobre la lampara");strcpy(objetos[LLAVE].<strong>de</strong>sc2,"una llave sobre la lampara");objetos[LLAVE].hab=3;objetos[LLAVE].lotengo=0;// escaleraobjetos[ESCALERA].estado=1;strcpy(objetos[ESCALERA].<strong>de</strong>sc1,"una escalera");strcpy(objetos[ESCALERA].<strong>de</strong>sc2,"una escalera");objetos[ESCALERA].hab=9;objetos[ESCALERA].lotengo=0;// perroobjetos[PERRO].estado=1;strcpy(objetos[PERRO].<strong>de</strong>sc1,"un perro rabioso");strcpy(objetos[PERRO].<strong>de</strong>sc2,"un perro comiendose un hueso");objetos[PERRO].hab=9;objetos[PERRO].lotengo=0;// Datos <strong>de</strong>l mapa18


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L// Habitación 1strcpy(habitaciones[1].<strong>de</strong>sc,"Estas en una pequeña habitación pintada <strong>de</strong> blanco. Junto a tipue<strong>de</strong>s ver una cama y una mesita <strong>de</strong> noche.");habitaciones[1].norte=0;habitaciones[1].sur=4;habitaciones[1].este=0;habitaciones[1].oeste=0;// Habitación 2strcpy(habitaciones[2].<strong>de</strong>sc,"Estas en una habitación pintada <strong>de</strong> ver<strong>de</strong>. Junto a ti pue<strong>de</strong>s veruna cama y una mesita <strong>de</strong> noche.");habitaciones[2].norte=0;habitaciones[2].sur=5;habitaciones[2].este=0;habitaciones[2].oeste=0;// Habitación 3strcpy(habitaciones[3].<strong>de</strong>sc,"Estas en el salón <strong>de</strong> la casa. Pue<strong>de</strong>s ver una gran mesa ro<strong>de</strong>ada<strong>de</strong> sillas.");habitaciones[3].norte=0;habitaciones[3].sur=6;habitaciones[3].este=0;habitaciones[3].oeste=0;// Habitación 4strcpy(habitaciones[4].<strong>de</strong>sc,"Estas en el pasillo.");habitaciones[4].norte=1;habitaciones[4].sur=7;habitaciones[4].este=5;habitaciones[4].oeste=0;// Habitación 5strcpy(habitaciones[5].<strong>de</strong>sc,"Estas en el pasillo.");habitaciones[5].norte=2;habitaciones[5].sur=8;habitaciones[5].este=6;habitaciones[5].oeste=4;// Habitación 6strcpy(habitaciones[6].<strong>de</strong>sc,"Estas en el pasillo.");habitaciones[6].norte=3;habitaciones[6].sur=9;habitaciones[6].este=0;habitaciones[6].oeste=5;// Habitación 7strcpy(habitaciones[7].<strong>de</strong>sc,"Estas en el típico servicio, <strong>con</strong> sus típicas piezas.");habitaciones[7].norte=4;habitaciones[7].sur=0;habitaciones[7].este=0;habitaciones[7].oeste=0;// Habitación 8strcpy(habitaciones[8].<strong>de</strong>sc,"Estas en la entrada <strong>de</strong> la casa. Pue<strong>de</strong>s ver la puerta cerrada.");habitaciones[8].norte=5;habitaciones[8].sur=0;habitaciones[8].este=0;habitaciones[8].oeste=0;// Habitación 9strcpy(habitaciones[9].<strong>de</strong>sc,"Estas en la cocina.");habitaciones[9].norte=6;habitaciones[9].sur=0;habitaciones[9].este=0;habitaciones[9].oeste=0;/***Inicialización <strong>de</strong>l estado <strong>de</strong> juego.***/// variable que indica la habitación en la que estamosint habitacionActual = 1;// variable que indica cuantos objetos hayint nobjetos = 4;19


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L/***game loop***/char verbo[30], nombre[30];int i, accion;int done = 0;while (!done) {// Mostramos información <strong>de</strong> la habitación.// Descripciónprintf("\n\n%s",habitaciones[habitacionActual].<strong>de</strong>sc);// Mostramos si hay algun objetofor (i=1 ; i


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L}objetos[ESCALERA].hab=0;objetos[ESCALERA].lotengo=1;printf("\nHas cogido las escaleras.");} else {printf("\nEl perro gruñe y ladra y no te <strong>de</strong>ja cogerlo.");}if (accion == 0)printf("\nNo pue<strong>de</strong>s hacer eso.");}// darif (!strcmp(verbo,"dar")) {accion = 0;// Huesoif (!strcmp(nombre,"hueso") && objetos[HUESO].lotengo == 1 && objetos[PERRO].hab ==habitacionActual) {accion = 1;objetos[HUESO].lotengo=0;objetos[PERRO].estado=2;printf("\nEl perro coge el hueso y se retira a comerselo tranquilamente.");}}if (accion == 0)printf("\nNo pue<strong>de</strong>s hacer eso.");// usarif (!strcmp(verbo,"usar")) {accion = 0;// Huesoif (!strcmp(nombre,"llave") && objetos[LLAVE].lotengo == 1 && habitacionActual == 8) {accion = 1;printf("\nENHORABUENA!!! Has escapado <strong>de</strong> la mansión <strong>de</strong>l terror.");done = 1;}}if (accion == 0)printf("\nNo pue<strong>de</strong>s hacer eso.");// irif (!strcmp(verbo,"ir")) {accion = 0;// norteif (!strcmp(nombre,"norte") && habitaciones[habitacionActual].norte != 0) {accion = 1;habitacionActual=habitaciones[habitacionActual].norte;}// surif (!strcmp(nombre,"sur") && habitaciones[habitacionActual].sur != 0) {accion = 1;habitacionActual=habitaciones[habitacionActual].sur;}// esteif (!strcmp(nombre,"este") && habitaciones[habitacionActual].este != 0) {accion = 1;habitacionActual=habitaciones[habitacionActual].este;}// oesteif (!strcmp(nombre,"oeste") && habitaciones[habitacionActual].oeste != 0) {accion = 1;habitacionActual=habitaciones[habitacionActual].oeste;}21


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L}if (accion == 0)printf("\nNo pue<strong>de</strong>s hacer eso.");}}22


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LCapítulo3Primeros pasos <strong>con</strong> <strong>SDL</strong>Iniciar el camino es fácil, finalizarlo también. Lo meritorio es recorrerlo.Tanto si utilizas Windows, Linux o cualquier otro sistema operativo, <strong>SDL</strong> te va a ahorrarun montón <strong>de</strong> trabajo y <strong>de</strong> dolores <strong>de</strong> cabeza. El apéndice A está <strong>de</strong>dicado a lainstalación <strong>de</strong> la librería para ambos sistemas operativos, así que si aún no lo tienesinstalado es un buen momento para saltar a dicho apéndice e instalarlo. En estecapítulo, vamos a introducirnos en el manejo <strong>de</strong> esta librería. No <strong>de</strong>scribiremos todas y cadauna <strong>de</strong> las partes que la componen, ya que para ello necesitaríamos un libro <strong>de</strong>dicado a elloen exclusiva. Veremos, sin embargo, lo más importante y lo necesario para po<strong>de</strong>r <strong>con</strong>struir unvi<strong>de</strong>ojuego.<strong>SDL</strong> es el acrónimo <strong>de</strong> Simple DirectMedia Layer. Al igual que DirectX en Windows, <strong>SDL</strong>nos ofrece una completa API para el <strong>de</strong>sarrollo <strong>de</strong> juegos, <strong>de</strong>mos y todo tipo <strong>de</strong> aplicacionesmultimedia <strong>con</strong> in<strong>de</strong>pen<strong>de</strong>ncia <strong>de</strong>l sistema operativo y la plataforma. Actualmente <strong>SDL</strong>soporta Linux, Windows, BeOS, MacOS, MacOS X, FreeBSD, OpenBSD, BSD/OS, Solaris, yIRIX. Aunque tiene portes no oficiales a Windows CE, AmigaOS, Atari, QNX, NetBSD, AIX,OSF/Tru64, y SymbianOS.<strong>SDL</strong> está compuesto por subsistemas separados que nos ofrecen soporte a diferentespartes <strong>de</strong>l hardware.Subsistema <strong>de</strong> Vi<strong>de</strong>o.Es nuestra interfaz <strong>con</strong> el hardware <strong>de</strong> vi<strong>de</strong>o. Nos permite operaciones básicas como lainicialización <strong>de</strong> modos gráficos, trabajo <strong>con</strong> colores y paletas <strong>de</strong> color, manejo <strong>de</strong> surfaces,colores transparentes, alpha blending y otras (Veremos que significan estos términos cuandoprofundicemos en el subsistema <strong>de</strong> vi<strong>de</strong>o).Subsistema <strong>de</strong> Audio.Es el subsistema encargado <strong>de</strong> todo lo que tenga que ver <strong>con</strong> la reproducción <strong>de</strong> sonidos.Nos va a permitir reproducir archivos .wav <strong>de</strong> forma relativamente sencilla.Subsistema <strong>de</strong> manejo <strong>de</strong> eventos.Los eventos son la base <strong>de</strong> la interactividad. Los necesitamos para saber si el jugadorquiere mover la nave a la izquierda o por el <strong>con</strong>trario quiere hacer un disparo. Básicamentenos va a permitir <strong>con</strong>ocer el estado <strong>de</strong>l teclado y <strong>de</strong>l ratón en cualquier momento.23


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LJoysticks.Seguramente te estarás preguntando por qué no se <strong>con</strong>trola el joystick <strong>de</strong>s<strong>de</strong> elsubsistema <strong>de</strong> eventos. Yo también me lo pregunto y ciertamente no encuentro una buenarazón para ello. Probablemente la razón sea que existen multitud <strong>de</strong> sistemas diferentes <strong>de</strong>joysticks en el mercado, quizás los suficientes para justificar un módulo separado. Encualquier caso, gracias a este subsistemas, nuestros juegos podrán ser <strong>con</strong>trolados medianteun joystick.CD-ROM.Efectivamente, <strong>SDL</strong> nos permite el acceso a todas las funciones <strong>de</strong>l CD-ROM. Lasposibilida<strong>de</strong>s son muy atractivas. Por ejemplo, si distribuyes tu juego en CD, éste pue<strong>de</strong> teneruna pista sonora y reproducirla en background mientras el jugador juega. Interesante ¿no?Threads.Sin duda sabes lo que es la multitarea. Pue<strong>de</strong>s ejecutar múltiples programas al mismotiempo. La i<strong>de</strong>a <strong>de</strong> thread (hilo en español) es similar. Son pequeños procesos que se lanzansimultáneamente, pero que pertenecen a un mismo programa padre. Por ejemplo, un juegopue<strong>de</strong> tener un thread encargado <strong>de</strong> reproducir una música y otro que va calculando laposición <strong>de</strong> los enemigos. Cada sistema operativo nos ofrece su propia forma <strong>de</strong> trabajar <strong>con</strong>los threads, pero <strong>SDL</strong> se jacta <strong>de</strong> ofrecer una API multiplataforma para manejo <strong>de</strong> threads.Timers.Es posible que alguna vez hayas probado alguno <strong>de</strong> esos juegos antiguos (muy antiguos)<strong>de</strong> cuando los PC corrían a 8 ó 16Mhz en tu flamante Pentium IV. Si el juego no había tenidoen cuenta la velocidad <strong>de</strong>l or<strong>de</strong>nador, probablemente lo verás funcionar tan rápido que serásincapaz <strong>de</strong> jugar. Si el juego hubiera estado bien diseñado, habría tenido en cuenta estaeventualidad, ya que no todos los or<strong>de</strong>nadores funcionan a la misma velocidad. <strong>SDL</strong> nosofrece acceso a timers <strong>de</strong> alta resolución (o al menos <strong>de</strong> una resolución aceptable) para hacerque nuestro juego trabaje a la misma velocidad <strong>con</strong> in<strong>de</strong>pen<strong>de</strong>ncia <strong>de</strong> la máquina en la que seejecute.Extensiones.A<strong>de</strong>más <strong>de</strong> estos subsistemas básicos, <strong>SDL</strong> tiene ciertas extensiones que nos harán lavida más fácil, como son <strong>SDL</strong>_image que permite trabajar <strong>con</strong> múltiples formatos <strong>de</strong> archivosgráficos, <strong>SDL</strong>_ttf para manejo <strong>de</strong> fuentes true type, <strong>SDL</strong>_net para acceso a re<strong>de</strong>s TCP/IP y<strong>SDL</strong>_mixer que mejora sustancialmente el subsistema <strong>de</strong> audio <strong>de</strong> <strong>SDL</strong>.Trabajando <strong>con</strong> <strong>SDL</strong>Es hora <strong>de</strong> ponerse manos a la obra y poner a trabajar al compilador.Antes <strong>de</strong> llamar a cualquier función <strong>de</strong> <strong>SDL</strong>, hay que inicializarla y para ello se utiliza lafunción <strong>SDL</strong>_Init(Uint32 flags). Veámoslo <strong>con</strong> un ejemplo.#inclu<strong>de</strong> #inclu<strong>de</strong> "<strong>SDL</strong>.h"main(){if (<strong>SDL</strong>_Init(<strong>SDL</strong>_INIT_AUDIO|<strong>SDL</strong>_INIT_VIDEO)< 0) {printf("No se pue<strong>de</strong> iniciar <strong>SDL</strong>: %s\n",<strong>SDL</strong>_GetError());24


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L}exit(1);}atexit(<strong>SDL</strong>_Quit);En este fragmento <strong>de</strong> código hay varias cosas interesantes. Empecemos por la función<strong>SDL</strong>_Init. Como parámetro acepta un argumento <strong>de</strong> tipo Uint32. Como te dije antes, <strong>SDL</strong> esmultiplataforma, por lo que tiene <strong>de</strong>finidos una serie <strong>de</strong> tipos <strong>de</strong> datos que serán iguales <strong>con</strong>in<strong>de</strong>pen<strong>de</strong>ncia <strong>de</strong>l sistema en el que corra. La U quiere <strong>de</strong>cir que el tipo <strong>de</strong> datos es sin signo(unsigned), int, como podrás adivinar es por que es un entero y 32, porque es un entero <strong>de</strong> 32bits. Si en vez <strong>de</strong> U, usamos la S (signed) estaremos hablando <strong>de</strong> un entero <strong>con</strong> signo. Parael número <strong>de</strong> bits, los posibles valores son 8,16,32 o 64. Así, por ejemplo, Uint16, haráreferencia a un entero <strong>de</strong> 16 bits. Veamos en <strong>de</strong>talle los parámetros que hemos pasado a<strong>SDL</strong>_Init(). <strong>SDL</strong>_INIT_AUDIO|<strong>SDL</strong>_INIT_VIDEO. Con este parámetro le <strong>de</strong>cimos a <strong>SDL</strong>que sólo queremos inicializar el subsistema <strong>de</strong> audio y <strong>de</strong> vi<strong>de</strong>o. Los posibles valores son:<strong>SDL</strong>_INIT_VIDEO<strong>SDL</strong>_INIT_AUDIO<strong>SDL</strong>_INIT_TIMER<strong>SDL</strong>_INIT_CDROM<strong>SDL</strong>_INIT_JOYSTICK<strong>SDL</strong>_INIT_EVERYTHINGLos parámetros se pasan separados por la barra vertical (|). Si quisiéramos inicializara<strong>de</strong>más <strong>de</strong>l audio y el vi<strong>de</strong>o el cd-rom, los parámetros serían los siguientes:<strong>SDL</strong>_INIT_AUDIO|<strong>SDL</strong>_INIT_VIDEO|<strong>SDL</strong>_INIT_CDROMSi lo queremos activar todo, pasaremos como único parámetro <strong>SDL</strong>_INIT_EVERYTHING.Una vez inicializado <strong>SDL</strong>, si necesitamos inicializar otro subsistema po<strong>de</strong>mos hacerlo <strong>con</strong> lafunción <strong>SDL</strong>_InitSubSystem(Uint32 flags) <strong>de</strong> la siguiente manera.// Inicializamos el CD-ROMif (<strong>SDL</strong>_InitSubSystem(<strong>SDL</strong>_INIT_CDROM) == -1) {printf("No se pue<strong>de</strong> iniciar el cdrom: %s\n",<strong>SDL</strong>_GetError());exit(1);}Como habrás adivinado, la función <strong>SDL</strong>_GetError() <strong>de</strong>vuelve el último error interno <strong>de</strong> <strong>SDL</strong>en formato ca<strong>de</strong>na. Cuando una aplicación <strong>SDL</strong> es inicializada, se crean dos archivos,st<strong>de</strong>rr.txt y stdout.txt. Durante la ejecución <strong>de</strong>l programa, cualquier información que se escribaen la salida <strong>de</strong> error estándar se escribe en el archivo st<strong>de</strong>rr.txt. Igualmente, cualquierinformación que se escriba en la salida estándar se guardará en el archivo stdout.txt. Esto nosva a ser <strong>de</strong> gran ayuda a la hora <strong>de</strong> <strong>de</strong>purar programas. Una vez finalizada la ejecución, siestos archivos están vacíos son borrados automáticamente, si no, estos permanecen intactos.Al igual que inicializamos <strong>SDL</strong>, cuando hemos terminado el trabajo hemos <strong>de</strong> cerrarlo. Lafunción encargada <strong>de</strong> esta tarea es <strong>SDL</strong>_Quit(). En nuestro programa <strong>de</strong> ejemplo hemosusado la siguiente la línea atexit(<strong>SDL</strong>_Quit). La función atexit() toma como parámetro aotra función a la que llama justo antes <strong>de</strong> que finalice la ejecución <strong>de</strong>l programa. En nuestrocaso, antes <strong>de</strong> que finelice nuestro programa (ya sea por un error o porque el usuario forzó lasalida) se llamará a la función <strong>SDL</strong>_Quit(). Una <strong>de</strong> las particularida<strong>de</strong>s <strong>de</strong> atexit() es que lafunción a la que <strong>de</strong>be llamar no tiene que tener parámetros ni <strong>de</strong>volver nada.En el caso <strong>de</strong> que queramos finalizar sólo un subsistema <strong>de</strong> <strong>SDL</strong> usaremos la función<strong>SDL</strong>_QuitSubSystem(Uint32 flags). Por ejemplo, si quisiéramos <strong>de</strong>sactivar el subsistema <strong>de</strong>cdrom usaríamos la siguiente línea:25


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L<strong>SDL</strong>_QuitSubSystem(<strong>SDL</strong>_INIT_CDROM);Vi<strong>de</strong>oLa primera tarea que tenemos que acometer antes <strong>de</strong> empezar a mostrar informacióngráfica en la pantalla es establecer el modo <strong>de</strong> vi<strong>de</strong>o. <strong>SDL</strong> nos lo pone fácil <strong>con</strong> la función<strong>SDL</strong>_SetVi<strong>de</strong>oMo<strong>de</strong>(). La función tiene la siguiente forma:<strong>SDL</strong>_Surface *<strong>SDL</strong>_SetVi<strong>de</strong>oMo<strong>de</strong>(int width, int height, int bpp, Uint32flags)Esta función <strong>de</strong>vuelve un puntero <strong>de</strong>l tipo <strong>SDL</strong>_Surface. En <strong>SDL</strong> la “surface” osuperficie es el elemento básico que utilizamos para <strong>con</strong>struir los gráficos <strong>de</strong> nuestrojuego. Imagina una superficie como un rectángulo que <strong>con</strong>tiene información gráfica, porejemplo <strong>de</strong> nuestra nave. La única superficie que es visible, es <strong>de</strong>cir, que vemos en lapantalla <strong>de</strong>l or<strong>de</strong>nador, es ésta que nos <strong>de</strong>vuelve la función <strong>SDL</strong>_SetVi<strong>de</strong>oMo<strong>de</strong>().Los parámetros witdth y height son la anchura y la altura <strong>de</strong> la superficie enpíxeles. Es <strong>de</strong>cir, la resolución <strong>de</strong> la pantalla. En teoría aquí po<strong>de</strong>mos poner cualquiercosa, pero evi<strong>de</strong>ntemente el hardware ha <strong>de</strong> soportar dicha resolución. Si la resoluciónno es soportada, <strong>SDL</strong> intentará poner una resolución válida lo más cercana a larequerida. Un poco más a<strong>de</strong>lante veremos una función que nos permitirá <strong>con</strong>ocer cualesson los modos válidos. Con el parámetro bpp le indicamos a <strong>SDL</strong> cuántos bits por pixelsqueremos establecer, es <strong>de</strong>cir, la profundidad <strong>de</strong> color. Valores típicos son 8, 16, 24 y 32.Hoy en día no tiene sentido trabajar <strong>con</strong> 8, ya que sólo nos proporciona 256 coloresposibles, a<strong>de</strong>más, habría que trabajar <strong>con</strong> paletas en vez <strong>de</strong> <strong>con</strong> color real. En este librono vamos a cubrir el trabajo <strong>con</strong> paletas, ya que es tedioso y dudo que realmente lonecesites alguna vez.El último parámetro es flags, que es un campo <strong>de</strong> bits. Estos son los posiblesvalores y su significado.<strong>SDL</strong>_SWSURFACE Crea la superficie <strong>de</strong> ví<strong>de</strong>o en la memoria principal<strong>SDL</strong>_HWSURFACE Crea la superficie en la memoria <strong>de</strong> ví<strong>de</strong>o.<strong>SDL</strong>_ASYNCBLIT Modo <strong>de</strong> blit asíncrono. Mejora el rendimiento en máquinas <strong>con</strong> más <strong>de</strong> unprocesador, unque pue<strong>de</strong> disminuirlo en máquinas <strong>con</strong> una sólo procesador.<strong>SDL</strong>_ANYFORMAT Fuerza el uso <strong>de</strong> los bpp <strong>de</strong> la surface actual. Hay que usarlo cuandoqueramos crear la superficie en una ventana.<strong>SDL</strong>_HWPALETTE Da a <strong>SDL</strong> acceso exclusivo a la paleta <strong>de</strong> color.<strong>SDL</strong>_DOUBLEBUFSólo válido <strong>con</strong> <strong>SDL</strong>_HWSURFACE. Permite el uso <strong>de</strong>l doble buffer.<strong>SDL</strong>_FULLSCREEN Intenta poner el modo a pantalla completa.<strong>SDL</strong>_OPENGLCrea un <strong>con</strong>texto OpenGL en la superficie.<strong>SDL</strong>_OPENGLBLIT Igual que la anterior, pero permite que <strong>SDL</strong> haga el ren<strong>de</strong>r 2D.<strong>SDL</strong>_RESIZABLE Crea un ventana que pue<strong>de</strong> cambiar <strong>de</strong> tamaño.<strong>SDL</strong>_NOFRAMECrea una ventana pero sin bor<strong>de</strong>.He aquí un ejemplo práctico <strong>de</strong>l uso <strong>de</strong> <strong>SDL</strong>_SetVi<strong>de</strong>Mo<strong>de</strong>().26


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L<strong>SDL</strong>_Surface *screen;screen = <strong>SDL</strong>_SetVi<strong>de</strong>oMo<strong>de</strong>(640,480,24,<strong>SDL</strong>_SWSURFACE| <strong>SDL</strong>_DOUBLEBUF);if ( screen == NULL ){fprintf(st<strong>de</strong>rr, "No se pue<strong>de</strong> establecer el modo \<strong>de</strong> vi<strong>de</strong>o 640x480: %s\n", <strong>SDL</strong>_GetError());exit(1);}Le hemos pedido a <strong>SDL</strong> que establezca un modo <strong>de</strong> vi<strong>de</strong>o <strong>con</strong> una resolución <strong>de</strong>640x480 píxeles y 24 bits <strong>de</strong> color (true color). A<strong>de</strong>más le solicitamos que use lamemoria <strong>de</strong> ví<strong>de</strong>o y una estrategia <strong>de</strong> doble buffer para repintado.Antes hemos dicho que la superficie que nos <strong>de</strong>vuelve <strong>SDL</strong>_SetVi<strong>de</strong>oMo<strong>de</strong> es laúnica visible. Sin embargo, es seguro que necesitaremos crear y utilizar superficiesintermedias para almacenar gráficos, <strong>con</strong>struir escenas, etc... Estas superficies nopue<strong>de</strong>n ser directamente mostradas por pantalla, pero sí copiadas en la superficieprincipal (visible). A este proceso lo llamamos bit blitting y veremos más abajo cómo serealiza esta operación. La manera <strong>de</strong> crear una nueva superficie es mediante la función<strong>SDL</strong>_CreateRGBSurface que crea una superficie vacía y lista para usar. El formato <strong>de</strong>esta función es<strong>SDL</strong>_Surface *<strong>SDL</strong>_CreateRGBSurface(Uint32 flags, int width, int height, int<strong>de</strong>pth, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask);A simple vista parece algo complicada, así que vamos a <strong>de</strong>tenernos un poco en ella. Comopue<strong>de</strong>s observar, esta función nos <strong>de</strong>vuelve un puntero a una superficie, al igual que hacía lafunción <strong>SDL</strong>_SetVi<strong>de</strong>oMo<strong>de</strong>. Los parámetros width y height tienen el mismo significadoque en <strong>SDL</strong>_SetVi<strong>de</strong>oMo<strong>de</strong>, y <strong>de</strong>pth es lo mismo que bpp (me pregunto por qué no hanmantenido la misma nomenclatura). Los posibles valores para flags son:<strong>SDL</strong>_SWSURFACE<strong>SDL</strong>_HWSURFACECrea la superficie <strong>de</strong> ví<strong>de</strong>o en la memoria principalCrea la superficie en la memoria <strong>de</strong> ví<strong>de</strong>o.<strong>SDL</strong>_SRCCOLORKEY Permite el uso <strong>de</strong> transparencias (color key).<strong>SDL</strong>_SRCALPHAActiva el alpha-blending.Un poco más a<strong>de</strong>lante veremos más claramente el significado <strong>de</strong> las dos última opciones.Nos queda Rmask, Gmask, Bmask y Amask. Estos parámetros son un poco más complejos <strong>de</strong>compren<strong>de</strong>r sin entrar en mucho <strong>de</strong>talle en las estructuras <strong>de</strong> datos internas <strong>de</strong> <strong>SDL</strong>, en<strong>con</strong>creto en la estructura <strong>SDL</strong>_PixelFormat. Baste <strong>de</strong>cir que la R, la G, la B y la A <strong>con</strong> laque empiezan cada uno <strong>de</strong> estos parámetros vienen <strong>de</strong> Red, Green, Blue y Alpharespectivamente. Rmask, por ejemplo, es la mascara <strong>de</strong> bits que representa al color rojo puro.Recuerda que un color cualquiera viene dado por la mezcla <strong>de</strong> los colores rojo, ver<strong>de</strong> y azul,por lo tanto, Rmask serían los bits que es necesario activar para <strong>con</strong>seguir el color que tieneun 100% <strong>de</strong> rojo, un 0% <strong>de</strong> ver<strong>de</strong> y un 0% <strong>de</strong> azul. Lo mismo es válido para el resto <strong>de</strong>parámetros. Veamos un ejemplo <strong>de</strong> uso <strong>de</strong> esta función.rmask = 0xff000000;gmask = 0x00ff0000;bmask = 0x0000ff00;amask = 0x000000ff;surface = <strong>SDL</strong>_CreateRGBSurface(<strong>SDL</strong>_SWSURFACE, 640, 480, 24,rmask, gmask,bmask, amask);if(surface == NULL) {printf("Error al crear la superficie");27


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L}exit(1);Cargando y mostrando gráficosVale, todo esto está muy bien, pero ¿cuándo vamos a empezar a ver algo?Está bien. Pasemos directamente a un ejemplo completo. La figura3.1 muestra elresultado <strong>de</strong> ejecutar el programa ejemplo3_1.Figura3.1.28


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L/*****************************************************************Ejemplo3_1(C) 2003 by Alberto García SerranoProgramación <strong>de</strong> vi<strong>de</strong>ojuegos <strong>con</strong> <strong>SDL</strong>******************************************************************/#inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> int main(int argc, char *argv[]) {<strong>SDL</strong>_Surface *image, *screen;<strong>SDL</strong>_Rect <strong>de</strong>st;<strong>SDL</strong>_Event event;int done = 0;atexit(<strong>SDL</strong>_Quit);// Iniciar <strong>SDL</strong>if (<strong>SDL</strong>_Init(<strong>SDL</strong>_INIT_VIDEO) < 0) {printf("No se pudo iniciar <strong>SDL</strong>: %s\n",<strong>SDL</strong>_GetError());exit(1);}// Activamos modo <strong>de</strong> vi<strong>de</strong>oscreen = <strong>SDL</strong>_SetVi<strong>de</strong>oMo<strong>de</strong>(640,480,24,<strong>SDL</strong>_HWSURFACE);if (screen == NULL) {printf("No se pue<strong>de</strong> inicializar el modo gráfico: \n",<strong>SDL</strong>_GetError());exit(1);}// Cargamos gráficoimage = <strong>SDL</strong>_LoadBMP("nave.bmp");if ( image == NULL ) {printf("No pu<strong>de</strong> cargar gráfico: %s\n", <strong>SDL</strong>_GetError());exit(1);}// Definimos don<strong>de</strong> dibujaremos el gráfico// y lo copiamos a la pantalla.<strong>de</strong>st.x = 100;<strong>de</strong>st.y = 100;<strong>de</strong>st.w = image->w;<strong>de</strong>st.h = image->h;<strong>SDL</strong>_BlitSurface(image, NULL, screen, &<strong>de</strong>st);// Mostramos la pantalla<strong>SDL</strong>_Flip(screen);// liberar superficie<strong>SDL</strong>_FreeSurface(image);// Esperamos la pulsación <strong>de</strong> una tecla para salirwhile(done == 0) {while ( <strong>SDL</strong>_PollEvent(&event) ) {if ( event.type == <strong>SDL</strong>_KEYDOWN )done = 1;}}}return 0;29


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LHay una gran cantidad <strong>de</strong> cosas en este código que aún no hemos visto, pero noquerría seguir mostrándote una larga lista <strong>de</strong> funciones y ejemplos incompletos. Vamos ausar este programa para introducir nuevas capacida<strong>de</strong>s <strong>de</strong> <strong>SDL</strong>.La primera parte <strong>de</strong>l programa <strong>de</strong>bería ya resultarte familiar. En ella inicializamos <strong>SDL</strong>y también el modo gráfico. La primera función <strong>de</strong>s<strong>con</strong>ocida que en<strong>con</strong>tramos es<strong>SDL</strong>_LoadBMP(). El formato <strong>de</strong> esta función es:<strong>SDL</strong>_Surface *<strong>SDL</strong>_LoadBMP(<strong>con</strong>st char *file);Su funcionamiento es realmente sencillo. Sólo tiene un parámetro, que es el nombre (ypath) <strong>de</strong>l un fichero gráfico en formato .bmp (bitmap). Si la función tiene éxito nos<strong>de</strong>vuelve una superficie <strong>de</strong>l tamaño <strong>de</strong>l gráfico que acabamos <strong>de</strong> cargar, si no, <strong>de</strong>vuelveNULL. En nuestro ejemplo, hemos <strong>de</strong>clarado la variable *image <strong>de</strong> tipo <strong>SDL</strong>_Surfacepara que albergue la superficie cargada.La siguiente función que nos llama la atención es:int <strong>SDL</strong>_BlitSurface(<strong>SDL</strong>_Surface *src, <strong>SDL</strong>_Rect *srcrect, <strong>SDL</strong>_Surface*dst, <strong>SDL</strong>_Rect *dstrect);Esta función copia zonas rectangulares <strong>de</strong> una superficie a otra. Vamos a usarla paracopiar el gráfico que hemos cargado anteriormente en la superficie image en la superfivievisible (screen). Es <strong>de</strong>cir, para mostrar el gráfico en la pantalla.Si observamos los parámetros, hay dos <strong>de</strong> tipo <strong>SDL</strong>_Surface y otros dos <strong>de</strong>l tipo<strong>SDL</strong>_Rect. El primer tipo ya lo <strong>con</strong>ocemos, en <strong>con</strong>creto son los parámetros src y dst,que son la superficie fuente y la <strong>de</strong> <strong>de</strong>stino respectivamente. El parámetro srcrect<strong>de</strong>fine la zona o porción rectangular <strong>de</strong> la superficie <strong>de</strong>s<strong>de</strong> la que queremos copiar.Dstrect es la zona rectangular <strong>de</strong> la superficie <strong>de</strong> <strong>de</strong>stino en la que vamos a copiar elgráfico. El tipo <strong>SDL</strong>_Rect tiene la siguiente forma:type<strong>de</strong>f struct{Sint16 x, y;Uint16 w, h;} <strong>SDL</strong>_Rect;Esta estructura <strong>de</strong>fine un rectángulo. Como seguramente adivinarás x e y son laposición <strong>de</strong> la coor<strong>de</strong>nada superior izquierda <strong>de</strong>l rectángulo mientras que w y h son laanchura y la altura <strong>de</strong>l triangulo, respectivamente. Veamos en <strong>de</strong>talle como usamos estaestructura:<strong>SDL</strong>_Rect <strong>de</strong>st;<strong>de</strong>st.x = 100;<strong>de</strong>st.y = 100;<strong>de</strong>st.w = image->w;<strong>de</strong>st.h = image->h;<strong>SDL</strong>_BlitSurface(image, NULL, screen, &<strong>de</strong>st);Hemos <strong>de</strong>clarado la variable <strong>de</strong>st y a <strong>con</strong>tinuación hemos <strong>de</strong>finido el área rectangular<strong>de</strong> <strong>de</strong>stino al que vamos a copiar el gráfico. En nuestro ejemplo, vamos a copiar el gráficoen la posición (100,100) <strong>de</strong> la superficie <strong>de</strong> <strong>de</strong>stino. Para <strong>con</strong>ocer el tamaño <strong>de</strong> lasuperficie <strong>de</strong>l gráfico que hemos cargado po<strong>de</strong>mos acce<strong>de</strong>r a los campos w y h <strong>de</strong> laestructura <strong>SDL</strong>_Surface <strong>de</strong> dicho gráfico.Fíjate en el segundo parámetro, al que hemos dado el valor NULL. Con esto leindicamos a <strong>SDL</strong> que queremos copiar toda la superficie en la que se haya el gráfico.30


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LAlgún avispado lector se preguntará qué ocurre si los campos w y h <strong>de</strong> los parámetrossrc<strong>de</strong>st y dstrect no coinci<strong>de</strong>n. Realmente no ocurre nada, ya que los campos w y h<strong>de</strong> dstrect no se utilizan. Por último, <strong>de</strong>cir que si la la copia tiene éxito la función<strong>de</strong>vuelve el valor 0, y –1 en caso <strong>con</strong>trario.Otra utilidad <strong>de</strong> la estructura <strong>SDL</strong>_Rect es la <strong>de</strong> dibujar rectángulos en la pantallamediante la función <strong>SDL</strong>_FillRect. Ilustrémoslo <strong>con</strong> un ejemplo:<strong>SDL</strong>_Rect <strong>de</strong>st;<strong>de</strong>st.x = 0;<strong>de</strong>st.y = 0;<strong>de</strong>st.w = screen->w;<strong>de</strong>st.h = screen->h;<strong>SDL</strong>_FillRect(screen,&<strong>de</strong>st,<strong>SDL</strong>_MapRGB(screen->format,0,0,0));Esta función dibuja un cuadrilátero en una superficie <strong>con</strong> el color indicado. En elejemplo se dibuja un rectángulo <strong>de</strong> color negro <strong>de</strong>l tamaño <strong>de</strong> la superficie (pantalla), o loque es lo mismo, borra la pantalla. El formato <strong>de</strong> la función es:int <strong>SDL</strong>_FillRect(<strong>SDL</strong>_Surface *dst, <strong>SDL</strong>_Rect *dstrect, Uint32 color);El primer parámetro es la superficie <strong>de</strong> <strong>de</strong>stino, el siguiente parámetro es <strong>de</strong> tipo<strong>SDL</strong>_Rect y <strong>de</strong>fine el rectángulo. Por último, hay que indicar el color <strong>de</strong> relleno. Un pocomás a<strong>de</strong>lante compren<strong>de</strong>remos el uso <strong>de</strong> <strong>SDL</strong>_MapRGB.Sigamos <strong>con</strong> la siguiente función:int <strong>SDL</strong>_Flip(<strong>SDL</strong>_Surface *screen);Si estamos usando doble buffer (como es el caso <strong>de</strong> nuestro ejemplo), esta funciónintercambia los buffers, es <strong>de</strong>cir, vuelca el <strong>con</strong>tenido <strong>de</strong>l buffer secundario al principal. Sitiene éxito <strong>de</strong>vuelve 0, si no, <strong>de</strong>vuelve –1. Si no usáramos doble buffer o nuestrohardware no lo soportara, tendríamos que usar <strong>SDL</strong>_UpdateRect(screen,0,0,0,0)en lugar <strong>de</strong> <strong>SDL</strong>_Flip(). El formato <strong>de</strong> esta función es:void <strong>SDL</strong>_UpdateRect(<strong>SDL</strong>_Surface *screen, Sint32 x, Sint32 y, Sint32 w,Sint32 h);Su misión es asegurar que el área que especifiquemos (sus parámetros <strong>de</strong>ben serteya familiares) sea actualizada.Como te veo <strong>con</strong> cara rara, quizás sea este el momento <strong>de</strong> explicar en qué <strong>con</strong>sisteeso <strong>de</strong>l doble buffer.Imagina que tienes que volcar a la pantalla los gráficos <strong>de</strong> tu juego. Primero pondríasel fondo (calculando previamente que porción <strong>de</strong>l fondo tienes que mostrar), mostraríaslas naves enemigas (calculando su posición previamente) y <strong>de</strong>spués dibujarías al héroe<strong>de</strong> tu juego. Entre el primer y el último gráfico que dibujas en pantalla pue<strong>de</strong> pasar untiempo bastante gran<strong>de</strong> (informáticamente hablando). Durante este tiempo, la imagen <strong>de</strong>lmonitor ha tenido tiempo <strong>de</strong> actualizarse varias veces (retrazo vertical).Desgraciadamente, cuando esto ocurre aparecen parpa<strong>de</strong>os y guiños (flicking)in<strong>de</strong>seados y muy molestos. Mediante la técnica <strong>de</strong>l doble buffer, todo el pintado se haceen una pantalla virtual, y sólo cuando está todo pintado, volcamos su <strong>con</strong>tenido a lapantalla real (flipping). Este proceso <strong>de</strong> volcado se hace a<strong>de</strong>más <strong>de</strong> forma sincronizada<strong>con</strong> el retrazo vertical <strong>de</strong>l monitor para evitar los parpa<strong>de</strong>os.Es importante (sobre todo en un lenguaje como C/C++) liberar los recursos que ya novamos a necesitar. En nuestro ejemplo usamos la función <strong>SDL</strong>_FreeSurface() paraliberar (borrar) la superficie que <strong>con</strong>tiene la imagen cargada. Su formato es:31


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lvoid <strong>SDL</strong>_FreeSurface(<strong>SDL</strong>_Surface *surface);La misión <strong>de</strong> las siguientes líneas es esperar la pulsación <strong>de</strong> una tecla cualquieraantes <strong>de</strong> acabar. Veremos más en profundidad su significado cuando tratemos loseventos.Efectos especiales: Transparencias y alpha-blendingEl hecho <strong>de</strong> que seamos capaces <strong>de</strong> mostrar el grafico <strong>de</strong> un avión en la pantalla es unpaso importante, pero seamos realistas: no es <strong>de</strong>masiado espectacular. De hecho, eserecuadro rojo alre<strong>de</strong>dor <strong>de</strong>l avión es bastante antiestético ¿no? Ciertamente sí. Alguien pue<strong>de</strong>haber ya pensado en una solución (salomónica, me atrevería a apuntar), que es hacer queese horrible recuadro rojo sea <strong>de</strong> color negro, al igual que el fondo. Realmente es unasolución, si te gustan los juegos <strong>con</strong> fondos <strong>de</strong> pantalla algo monótonos. Como siempre <strong>SDL</strong>viene al rescate. Echemos un vistazo a la siguiente función:int <strong>SDL</strong>_SetColorKey(<strong>SDL</strong>_Surface *surface, Uint32 flag, Uint32 key);Esta función nos permite <strong>de</strong>signar un color en la superficie, que pasamos como parámetro,y que tendrá el comportamiento <strong>de</strong> un color transparente. Es <strong>de</strong>cir, que no se pintará cuandohagamos el blitting. La función nos <strong>de</strong>vuelve 0 si todo fue bién y –1 en otro caso. El parámetroflag sólo pue<strong>de</strong> tomar tres valores posibles:• 0 para <strong>de</strong>sactivar una transparencia previamente activada.• <strong>SDL</strong>_SRCCOLORKEY para indicar que el tercer parámetro <strong>de</strong> la función correspon<strong>de</strong> alcolor que queremos que sea transparente y• <strong>SDL</strong>_RLEACCEL que permite usar codificación RLE en la superficie para acelerar elblitting.•Este último flag sólo pue<strong>de</strong> usarse en combinación <strong>con</strong> <strong>SDL</strong>_SRCCOLORKEY, es <strong>de</strong>cir, sólose permiten tres valores posibles: 0, <strong>SDL</strong>_SRCCOLORKEY, <strong>SDL</strong>_SRCCOLORKEY|<strong>SDL</strong>_RLEACCEL.El tercer parámetro es el color, que queremos que sea transparente. Hay que tener encuenta que el color ha <strong>de</strong> estar en el mismo formato <strong>de</strong> píxel (píxel format) que la superficie.Lo habitual es utilizar la función <strong>SDL</strong>_MapRGB para <strong>con</strong>seguir el valor <strong>de</strong>l color que buscamos.Veamos un ejemplo:<strong>SDL</strong>_SetColorKey(image, <strong>SDL</strong>_SRCCOLORKEY|<strong>SDL</strong>_RLEACCEL, <strong>SDL</strong>_MapRGB(image->format,255,0,0));Aña<strong>de</strong> esta línea al ejemplo anterior <strong>de</strong>spués <strong>de</strong> la carga <strong>de</strong>l gráfico. Si ejecutas ahora elprograma veras el avión sin ese feo marco rojo alre<strong>de</strong>dor.32


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LFigura3.2.Lo que hemos hecho es, ni más ni menos, que <strong>de</strong>cirle a <strong>SDL</strong> que el color rojo <strong>de</strong> lasuperficie que <strong>con</strong>tiene el avión va a ser el color transparente. Como ves, esta función no tieneningún secreto, aunque nos falta aclarar el funcionamiento <strong>de</strong> la función <strong>SDL</strong>_MapRGB.Uint32 <strong>SDL</strong>_MapRGB(<strong>SDL</strong>_PixelFormat *fmt, Uint8 r, Uint8 g, Uint8 b);Existe un campo en la estructura <strong>SDL</strong>_Surface (<strong>con</strong>tiene la información <strong>de</strong> una superficie)llamado format que <strong>de</strong>fine el formato <strong>de</strong>l píxel (píxel format) <strong>de</strong> la superficie. Dependiendo<strong>de</strong>l formato que tenga el píxel (número <strong>de</strong> bits por pixels) la representación <strong>de</strong> un color<strong>con</strong>creto es diferente. Esta función nos <strong>de</strong>vuelve el color solicitado en el formato <strong>de</strong> píxel quele pasamos como primer parámetro.Observa que pue<strong>de</strong>s obtener el formato <strong>de</strong> píxel <strong>de</strong> una superficie accediendo a su campoformat (image->format). Los otros tres parámetros son las componentes <strong>de</strong> rojo, ver<strong>de</strong> y azul(formato RGB) <strong>de</strong>l color que queremos. En nuestro caso, el color rojo es (255,0,0).Ahora que po<strong>de</strong>mos usar colores transparentes, vamos a dar un paso más y vamos a utilizaruna <strong>de</strong> las características más espectaculares <strong>de</strong> <strong>SDL</strong>: el alpha-blending o alpha para losamigos. Gracias al alpha-blending po<strong>de</strong>mos hacer que una superficie sea translucida, es <strong>de</strong>cir<strong>con</strong> cierto nivel <strong>de</strong> transparencia. Este nivel <strong>de</strong> transparencia tiene un rango <strong>de</strong> 0 a 255.Don<strong>de</strong> 0 es transparente y 255 es totalmente opaco. Como una imagen vale más que milpalabras, vamos a ver un nuevo ejemplo en la figura3.3.33


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L/*******************************************Ejemplo3_2(C) 2003 by Alberto García SerranoProgramación <strong>de</strong> vi<strong>de</strong>ojuegos <strong>con</strong> <strong>SDL</strong>*******************************************/#inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> int main(int argc, char *argv[]) {<strong>SDL</strong>_Surface *image, *screen;<strong>SDL</strong>_Rect <strong>de</strong>st;<strong>SDL</strong>_Event event;int i, done = 0;atexit(<strong>SDL</strong>_Quit);// Iniciar <strong>SDL</strong>if (<strong>SDL</strong>_Init(<strong>SDL</strong>_INIT_VIDEO) < 0) {printf("No se pudo iniciar <strong>SDL</strong>: %s\n",<strong>SDL</strong>_GetError());exit(1);}// Activamos modo <strong>de</strong> vi<strong>de</strong>oscreen = <strong>SDL</strong>_SetVi<strong>de</strong>oMo<strong>de</strong>(640,480,24,<strong>SDL</strong>_HWSURFACE);if (screen == NULL) {printf("No se pue<strong>de</strong> inicializar el modo gráfico: \n",<strong>SDL</strong>_GetError());exit(1);}// Cargamos gráficoimage = <strong>SDL</strong>_LoadBMP("nave.bmp");if ( image == NULL ) {printf("No pu<strong>de</strong> cargar gráfico: %s\n", <strong>SDL</strong>_GetError());exit(1);}// Definimos color para la transparencia<strong>SDL</strong>_SetColorKey(image,<strong>SDL</strong>_SRCCOLORKEY|<strong>SDL</strong>_RLEACCEL,<strong>SDL</strong>_MapRGB(image->format,255,0,0));// Vamos a dibujar 100 graficosfor (i=1 ; iw;<strong>de</strong>st.h = image->h;<strong>SDL</strong>_BlitSurface(image, NULL, screen, &<strong>de</strong>st);// Mostramos la pantalla<strong>SDL</strong>_Flip(screen);// liberar superficie<strong>SDL</strong>_FreeSurface(image);// Esperamos la pulsación <strong>de</strong> una tecla para salirwhile(done == 0) {while ( <strong>SDL</strong>_PollEvent(&event) ) {if ( event.type == <strong>SDL</strong>_KEYDOWN )done = 1;}}}return 0;34


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L35


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LEl resultado <strong>de</strong> ejecutar el programa ejemplo3_2 es el siguiente:Figura 3.3.La función encargada <strong>de</strong> realizar este trabajo tiene la siguiente forma:int <strong>SDL</strong>_SetAlpha(<strong>SDL</strong>_Surface *surface, Uint32 flag, Uint8 alpha);Como ves, es muy parecida a <strong>SDL</strong>_SetColorKey. En el caso <strong>de</strong>l parámetro flag, losposibles valores son 0, para <strong>de</strong>sactivar el alpha, <strong>SDL</strong>_SRCALPHA para indicar que el tercerparámetro <strong>de</strong> la función es el alpha que queremos aplicar a la superficie y <strong>SDL</strong>_RLEACCELque se aplica junto a <strong>SDL</strong>_SRCALPHA para activar la aceleración RLE. El tercer parámetro esel nivel <strong>de</strong> alpha que queremos aplicar. Pue<strong>de</strong> variar <strong>de</strong> 0 a 255. Esta función <strong>de</strong>vuelve 0 sitodo fue bien, y –1, en caso <strong>de</strong> error.Otras funciones <strong>de</strong> interésNo quisiera terminar esta sección sobre el vi<strong>de</strong>o en <strong>SDL</strong> sin presentarte algunas funcionesmás que sin duda antes o <strong>de</strong>spués te serán útiles.Vamos a empezar por un par <strong>de</strong> funciones que nos van a permitir <strong>de</strong>finir zonas <strong>de</strong> clippingo recorte. Veamos en que <strong>con</strong>siste el clipping <strong>con</strong> un ejemplo. Supongamos que vamos ahacer un simulador <strong>de</strong> vuelo. En la parte inferior <strong>de</strong> la pantalla estarán los mando <strong>de</strong>l avión, enla parte superior habrá indicadores, y finalmente en la parte central <strong>de</strong> la pantalla está la zonadon<strong>de</strong> se <strong>de</strong>sarrolla la acción <strong>de</strong>l juego. En cada ciclo <strong>de</strong> juego, hay que redibujar esta zona,pero no el resto <strong>de</strong> la pantalla (panel <strong>de</strong> <strong>con</strong>trol, indicadores, etc...). Con la función<strong>SDL</strong>_SetClipRect po<strong>de</strong>mos <strong>de</strong>cirle a <strong>SDL</strong> que cualquier operación gráfica que se realicequedará <strong>de</strong>ntro <strong>de</strong> este rectángulo. Si el gráfico es mayor que este recuadro, se recorta. Elformato <strong>de</strong> la función es:void <strong>SDL</strong>_SetClipRect(<strong>SDL</strong>_Surface *surface, <strong>SDL</strong>_Rect *rect);El primer parámetro es la superficie sobre la que queremos efectuar el clipping, y elsegundo ya <strong>de</strong>bería serte familiar, es el rectángulo <strong>de</strong>ntro <strong>de</strong>l cual queremos permitir elvolcado gráfico.36


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LAña<strong>de</strong> las siguientes líneas al ejemplo anterior justo <strong>de</strong>spués <strong>de</strong> la inicialización <strong>de</strong>l modográfico.<strong>SDL</strong>_Rect clip_rect;clip_rect.x=100;clip_rect.y=100;clip_rect.w=440;clip_rect.h=280;<strong>SDL</strong>_SetClipRect(screen ,&clip_rect);Básicamente <strong>de</strong>finimos un área rectangular <strong>de</strong> clipping que comienza en la posición(100,100) y tiene una anchura <strong>de</strong> 440 píxeles y una altura <strong>de</strong> 280 píxeles.Figura3.4.Para <strong>con</strong>ocer qué área <strong>de</strong> clipping tiene una superficie po<strong>de</strong>mos usar la siguiente función:void <strong>SDL</strong>_GetClipRect(<strong>SDL</strong>_Surface *surface, <strong>SDL</strong>_Rect *rect);Como ves, el formato es idéntico al <strong>de</strong> <strong>SDL</strong>_SetClipRect, <strong>con</strong> la diferencia <strong>de</strong> que estafunción rellenará la estructura rect <strong>con</strong> los datos <strong>de</strong>l área <strong>de</strong> clipping.La siguiente función en la que vamos a <strong>de</strong>tenernos es la siguiente:<strong>SDL</strong>_Surface *<strong>SDL</strong>_ConvertSurface(<strong>SDL</strong>_Surface *src, <strong>SDL</strong>_PixelFormat *fmt,Uint32 flags);Su misión es básicamente <strong>con</strong>vertir una superficie al mismo formato <strong>de</strong> píxel que otra. Estoacelera las operaciones <strong>de</strong> blitting, ya que si dos superficies tienen diferente formato <strong>de</strong> píxel,<strong>SDL</strong> hace la <strong>con</strong>versión cada vez durante el blitting. El primer parámetro es la superficie quequeremos <strong>con</strong>vertir. El segundo, es el formato <strong>de</strong>l píxel <strong>de</strong> la superficie a la que queremos<strong>con</strong>vertir la primera. Como ya vimos antes, el formato <strong>de</strong>l píxel <strong>de</strong> una superficie se encuentraen el campo format <strong>de</strong> la superficie (superficie->format). El tercer parámetro pue<strong>de</strong><strong>con</strong>tener exactamente los mismo valores que la función <strong>SDL</strong>_CreateRGBSurface que vimosal principio <strong>de</strong> esta sección.37


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LPor último (por ahora) quiero presentaros la siguiente función:<strong>SDL</strong>_Vi<strong>de</strong>oInfo *<strong>SDL</strong>_GetVi<strong>de</strong>oInfo(void);Su misión es <strong>de</strong>volvernos información sobre el modo <strong>de</strong> vi<strong>de</strong>o actual. Si aún no se hainicializado un modo <strong>de</strong> vi<strong>de</strong>o, nos <strong>de</strong>vuelve el “mejor” modo disponible (siempre según <strong>SDL</strong>).Esta función no tiene parámetros, y <strong>de</strong>vuelve el resultado en una variable <strong>de</strong> tipo<strong>SDL</strong>_Vi<strong>de</strong>oInfo, que tiene la siguiente estructura:type<strong>de</strong>f struct{Uint32 hw_available:1;Uint32 wm_available:1;Uint32 blit_hw:1;Uint32 blit_hw_CC:1;Uint32 blit_hw_A:1;Uint32 blit_sw:1;Uint32 blit_sw_CC:1;Uint32 blit_sw_A:1;Uint32 blit_fill;Uint32 vi<strong>de</strong>o_mem;<strong>SDL</strong>_PixelFormat *vfmt;} <strong>SDL</strong>_Vi<strong>de</strong>oInfo;El significado <strong>de</strong> cada campo es el siguiente:Hw_available Indica si se pue<strong>de</strong>n crear superficies en memoria <strong>de</strong> vi<strong>de</strong>o.Wm_available Indica si hay un manejador <strong>de</strong> ventanas disponible.blit_hwblit_hw_CCblit_hw_Ablit_swblit_sw_CCblit_sw_Ablit_fillvi<strong>de</strong>o_memVfmtIndica si el blitting hardware - hardware está acelerado.Indica si el blitting <strong>con</strong> transparencias hardware – hardware está acelerado.Indica si el blitting <strong>con</strong> alpha hardware – hardware está acelerado.Indica si el blitting software - hardware está acelerado.Indica si el blitting <strong>con</strong> transparencias software – hardware está acelerado.Indica si el blitting <strong>con</strong> alpha software – hardware está acelerado.Indica si el rellenado <strong>de</strong> color está acelerado.Cantidad <strong>de</strong> memoria total en Kilobites.Puntero a la estructura <strong>SDL</strong>_PixelFormat que <strong>con</strong>tiene el formato <strong>de</strong> píxel <strong>de</strong>lsistema gráfico.En el tintero nos <strong>de</strong>jamos muchas cosas sobre el vi<strong>de</strong>o, pero ya tenemos las herramientasnecesarias para comenzar a programar vi<strong>de</strong>ojuegos. En la documentación <strong>de</strong> la librería <strong>SDL</strong>pue<strong>de</strong>s en<strong>con</strong>trar información sobre muchos más tópicos que no hemos tratado aquí, comotrabajo <strong>con</strong> paletas <strong>de</strong> color, acceso directo a las superficies, etc... Con los <strong>con</strong>ocimientos queahora tienes <strong>de</strong>berías po<strong>de</strong>r seguir sin problema esta documentación.Gestión <strong>de</strong> eventosLa gestión <strong>de</strong> eventos <strong>de</strong> <strong>SDL</strong> es realmente cómoda y fácil <strong>de</strong> usar. Seamos sinceros,si te las has visto <strong>con</strong> el sistema <strong>de</strong> eventos <strong>de</strong> Windows, <strong>SDL</strong> te va a parecer un juego<strong>de</strong> niños. El subsistema <strong>de</strong> eventos <strong>de</strong> <strong>SDL</strong> se inicializa junto al subsistema <strong>de</strong> vi<strong>de</strong>o, porlo que no hay que inicializarlo expresamente. Todo esto está muy bien, pero ¿qué es unevento? Bien, en general, un evento es algo que suce<strong>de</strong> durante la ejecución <strong>de</strong>lprograma.38


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L¿algo que suce<strong>de</strong>? ¿que respuesta es esa?Vale, vale... espera que te explique. En <strong>SDL</strong>, un evento está asociado a una acción <strong>de</strong>lusuario sobre la aplicación que se está ejecutando. Como sabes, para interactuar <strong>con</strong> unor<strong>de</strong>nador usamos periféricos <strong>de</strong> entrada, como un teclado, un ratón, etc...Tipos <strong>de</strong> eventosTecladoEl teclado es el principal dispositivo <strong>de</strong> entrada, aunque algunos se empeñen en quesea el ratón. Ahora, mientras tecleo estas líneas, lo estoy utilizando, y no puedo imaginarun dispositivo más cómodo para esta tarea.Cada vez que pulses una tecla, <strong>SDL</strong> crea un evento que indica, no sólo que se pulsóuna tecla, también cuál fue. Más <strong>con</strong>cretamente <strong>SDL</strong> lanza un evento cada vez que sepulsa una tecla y también cuando <strong>de</strong> suelta. También nos informa sobre si hay algunatecla especial pulsada como Ctrl, Alt o Shift.RatónEl otro gran dispositivo <strong>de</strong> entrada es el ratón, quizás más a<strong>de</strong>cuado para ciertos tipos<strong>de</strong> juegos que el teclado. Hay dos tipos <strong>de</strong> eventos que <strong>de</strong>vuelve el ratón, uno referenteal movimiento y el otro referente al estado <strong>de</strong> los botones. En lo referente al movimiento,<strong>SDL</strong> nos informa sobre la posición <strong>de</strong>l puntero, así como <strong>de</strong> la distancia que ha recorrido<strong>de</strong>s<strong>de</strong> el último evento <strong>de</strong> ratón. En lo referente a los botones, el evento <strong>con</strong>tieneinformación sobre si se ha pulsado o se ha soltado un botón <strong>de</strong>l ratón, y por supuesto,cual <strong>de</strong> ellos fué.JoystickSin duda, el joystick es el rey <strong>de</strong> los periféricos para jugar. Sin embargo en el PC noexiste un estándar, y hay multitud <strong>de</strong> ellos. Debido a esto, <strong>SDL</strong> tiene un subsistemacompleto <strong>de</strong>dicado al joystick. Desgraciadamente, y quizás por no ser un periféricoestándar, no está muy extendido. Los eventos que se generarán van a <strong>de</strong>pen<strong>de</strong>r <strong>de</strong>ljoystick. En principio los principales son el movimiento <strong>de</strong>l joystick y la pulsación <strong>de</strong> losbotones. Algunos Joystick pue<strong>de</strong>n generar otro tipo <strong>de</strong> eventos, pero vamos a centrarnosen estos dos, que son lo más comunes.Otros eventosTambién po<strong>de</strong>mos <strong>con</strong>ocer eventos relacionados <strong>con</strong> el sistema operativo, y que nosvan a ser muy útiles. Sin duda el más importante es el que nos indica que se va a cerrarel juego (o que se va a cerrar la ventana <strong>de</strong>l juego). Otros importantes son los referidos alcambio <strong>de</strong> tamaño <strong>de</strong> la ventana (si es que nuestro juego se está ejecutando en unaventana) o el que nos indica que <strong>de</strong>bemos redibujarla (por cualquier motivo relacionado<strong>con</strong> el SO, como por ejemplo, que se superpuso una ventana <strong>de</strong> otra aplicación, etc...).Lectura <strong>de</strong> eventosPo<strong>de</strong>mos interrogar al sistema por los eventos <strong>de</strong> tres formas posibles. Po<strong>de</strong>mos esperar aque ocurra cierto evento para realizar una acción <strong>con</strong>creta, por ejemplo, un programa pue<strong>de</strong>quedar a la espera <strong>de</strong> que pulsemos un botón en un cuadro <strong>de</strong> diálogo.39


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LTambién po<strong>de</strong>mos acce<strong>de</strong>r al hardware directamente y leer el estado <strong>de</strong>l dispositivo. Estono te lo a<strong>con</strong>sejo a no ser que te guste hacer las cosas <strong>de</strong> la forma más difícil.El tercer método es el llamado polling. Con este método los eventos se van almacenando,y pue<strong>de</strong>n ser <strong>con</strong>sultados en el momento que nos <strong>con</strong>venga. Esto cuadra muy bien <strong>con</strong> laforma en que funciona un juego. Acuérdate <strong>de</strong>l game loop, en el que leíamos la entrada <strong>de</strong>ljugador, la procesábamos, haciamos cálculos, movimientos o cualquier otra cosa y vuelta aempezar.La función que nos permite leer el siguiente evento disponible es:int <strong>SDL</strong>_PollEvent(<strong>SDL</strong>_Event *event);Esta función <strong>de</strong>vuelve 1 si había un evento pendiente y 0 en caso <strong>con</strong>trario. Si hay algúnevento disponible se almacena en el parámetro event. Como veremos a <strong>con</strong>tinuación, haydiferentes tipos <strong>de</strong> evento. Afortunadamente todos tiene un campo en común, que es elcampo type (en realidad es una unión <strong>de</strong> C que <strong>con</strong>tiene las diferentes estructuras para losdiferentes eventos), por lo que po<strong>de</strong>mos <strong>con</strong>sultar el tipo <strong>de</strong> evento <strong>de</strong>l que se trata. En elejemplo3_1 y ejemplo3_2 pue<strong>de</strong>s ver un ejemplo <strong>de</strong> cómo se utiliza esta función.Eventos <strong>de</strong>l tecladoCada vez que se produce un evento <strong>de</strong> teclado (pulsar o soltar una tecla) este sealmacena en la estructura <strong>SDL</strong>_KeyboardEvent que tiene la siguiente forma:type<strong>de</strong>f struct{Uint8 type;Uint8 state;<strong>SDL</strong>_keysym keysym;} <strong>SDL</strong>_KeyboardEvent;El campo type pue<strong>de</strong> tomar los valores <strong>SDL</strong>_KEYDOWN o <strong>SDL</strong>_KEYUP para indicar siel evento correspon<strong>de</strong> a una pulsación o si por el <strong>con</strong>trario se soltó la tecla.El campo state es redundante <strong>con</strong> el primero, ya que <strong>con</strong>tiene exactamente la mismainformación, por lo que po<strong>de</strong>mos ignorarlo tranquilamente. Para los más curiosos, losvalores posibles <strong>de</strong> este campo son <strong>SDL</strong>_PRESSED o <strong>SDL</strong>_RELEASED.El siguiente campo nos indica cual fue la tecla pulsada. Este campo es <strong>de</strong> tipo<strong>SDL</strong>_keysym que es una estructura que tiene el siguiente formato:type<strong>de</strong>f struct{Uint8 scanco<strong>de</strong>;<strong>SDL</strong>Key sym;<strong>SDL</strong>Mod mod;Uint16 unico<strong>de</strong>;} <strong>SDL</strong>_keysym;El primer campo es un código que genera el teclado (es <strong>de</strong>cir, un código generado porel hardware). Pue<strong>de</strong> cambiar <strong>de</strong> un sistema a otro, por lo que si quieres que tu juegopueda ejecutarse en otras plataformas será mejor que no lo uses. El campo sym es elmás interesante <strong>de</strong> la estructura, ya que <strong>con</strong>tiene un código i<strong>de</strong>ntificativo <strong>de</strong> la teclapulsada, pero al <strong>con</strong>trario que scanco<strong>de</strong>, este código es generado por <strong>SDL</strong> y por lo tantoes igual en todas las plataformas . A <strong>con</strong>tinuación se muestra el listado <strong>de</strong> códigos sym.40


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L<strong>SDL</strong>KeyASCII<strong>SDL</strong>KeyASCII<strong>SDL</strong>K_BACKSPACE'\b'<strong>SDL</strong>K_g'g'<strong>SDL</strong>K_TAB'\t'<strong>SDL</strong>K_h'h'<strong>SDL</strong>K_CLEAR<strong>SDL</strong>K_i'i'<strong>SDL</strong>K_RETURN'\r'<strong>SDL</strong>K_j'j'<strong>SDL</strong>K_PAUSE<strong>SDL</strong>K_k'k'<strong>SDL</strong>K_ESCAPE'^['<strong>SDL</strong>K_l'l'<strong>SDL</strong>K_SPACE' '<strong>SDL</strong>K_m'm'<strong>SDL</strong>K_EXCLAIM'!'<strong>SDL</strong>K_n'n'<strong>SDL</strong>K_QUOTEDBL'"'<strong>SDL</strong>K_o'o'<strong>SDL</strong>K_HASH'#'<strong>SDL</strong>K_p'p'<strong>SDL</strong>K_DOLLAR'$'<strong>SDL</strong>K_q'q'<strong>SDL</strong>K_AMPERSAND'&'<strong>SDL</strong>K_r'r'<strong>SDL</strong>K_QUOTE'''<strong>SDL</strong>K_s's'<strong>SDL</strong>K_LEFTPAREN'('<strong>SDL</strong>K_t't'<strong>SDL</strong>K_RIGHTPAREN')'<strong>SDL</strong>K_u'u'<strong>SDL</strong>K_ASTERISK'*'<strong>SDL</strong>K_v'v'<strong>SDL</strong>K_PLUS'+'<strong>SDL</strong>K_w'w'<strong>SDL</strong>K_COMMA','<strong>SDL</strong>K_x'x'<strong>SDL</strong>K_MINUS'-'<strong>SDL</strong>K_y'y'<strong>SDL</strong>K_PERIOD'.'<strong>SDL</strong>K_z'z'<strong>SDL</strong>K_SLASH'/'<strong>SDL</strong>K_DELETE'^?'<strong>SDL</strong>K_0'0'<strong>SDL</strong>K_KP0<strong>SDL</strong>K_1'1'<strong>SDL</strong>K_KP1<strong>SDL</strong>K_2'2'<strong>SDL</strong>K_KP2<strong>SDL</strong>K_3'3'<strong>SDL</strong>K_KP3<strong>SDL</strong>K_4'4'<strong>SDL</strong>K_KP4<strong>SDL</strong>K_5'5'<strong>SDL</strong>K_KP5<strong>SDL</strong>K_6'6'<strong>SDL</strong>K_KP6<strong>SDL</strong>K_7'7'<strong>SDL</strong>K_KP7<strong>SDL</strong>K_8'8'<strong>SDL</strong>K_KP8<strong>SDL</strong>K_9'9'<strong>SDL</strong>K_KP9<strong>SDL</strong>K_COLON':'<strong>SDL</strong>K_KP_PERIOD'.'<strong>SDL</strong>K_SEMICOLON';'<strong>SDL</strong>K_KP_DIVIDE'/'<strong>SDL</strong>K_LESS''<strong>SDL</strong>K_KP_PLUS'+'<strong>SDL</strong>K_QUESTION'?'<strong>SDL</strong>K_KP_ENTER'\r'<strong>SDL</strong>K_AT'@'<strong>SDL</strong>K_KP_EQUALS'='<strong>SDL</strong>K_LEFTBRACKET'['<strong>SDL</strong>K_UP<strong>SDL</strong>K_BACKSLASH'\'<strong>SDL</strong>K_DOWN<strong>SDL</strong>K_RIGHTBRACKET']'<strong>SDL</strong>K_RIGHT<strong>SDL</strong>K_CARET'^'<strong>SDL</strong>K_LEFT<strong>SDL</strong>K_UNDERSCORE'_'<strong>SDL</strong>K_INSERT<strong>SDL</strong>K_BACKQUOTE'`'<strong>SDL</strong>K_HOME<strong>SDL</strong>K_a'a'<strong>SDL</strong>K_END<strong>SDL</strong>K_b'b'<strong>SDL</strong>K_PAGEUP<strong>SDL</strong>K_c'c'<strong>SDL</strong>K_PAGEDOWN<strong>SDL</strong>K_d'd'<strong>SDL</strong>K_F1<strong>SDL</strong>K_e'e'<strong>SDL</strong>K_F2<strong>SDL</strong>K_f'f'<strong>SDL</strong>K_F341


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L<strong>SDL</strong>Key<strong>SDL</strong>K_F4<strong>SDL</strong>K_F5<strong>SDL</strong>K_F6<strong>SDL</strong>K_F7<strong>SDL</strong>K_F8<strong>SDL</strong>K_F9<strong>SDL</strong>K_F10<strong>SDL</strong>K_F11<strong>SDL</strong>K_F12<strong>SDL</strong>K_F13<strong>SDL</strong>K_F14<strong>SDL</strong>K_F15<strong>SDL</strong>K_NUMLOCK<strong>SDL</strong>K_CAPSLOCK<strong>SDL</strong>K_SCROLLOCK<strong>SDL</strong>K_RSHIFT<strong>SDL</strong>K_LSHIFT<strong>SDL</strong>K_RCTRL<strong>SDL</strong>K_LCTRL<strong>SDL</strong>K_RALT<strong>SDL</strong>K_LALT<strong>SDL</strong>K_RMETA<strong>SDL</strong>K_LMETA<strong>SDL</strong>K_LSUPER<strong>SDL</strong>K_RSUPER<strong>SDL</strong>K_MODE<strong>SDL</strong>K_HELP<strong>SDL</strong>K_PRINT<strong>SDL</strong>K_SYSREQ<strong>SDL</strong>K_BREAK<strong>SDL</strong>K_MENU<strong>SDL</strong>K_POWER<strong>SDL</strong>K_EUROASCII42


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LEl campo mod (<strong>de</strong> modificador) es un campo <strong>de</strong> bits que nos hablan <strong>de</strong>l estado <strong>de</strong>ciertas teclas especiales como CTRL, ALT, SHIFT, etc... Su posibles valores son:KMOD_NONEKMOD_NUMKMOD_CAPSKMOD_LCTRLKMOD_RCTRLKMOD_RSHIFTKMOD_LSHIFTKMOD_RALTKMOD_LALTKMOD_CTRLKMOD_SHIFTKMOD_ALTNingún modificadorNumlock esta pulsadoCapslock está pulsadoControl izquierdo está pulsadoControl <strong>de</strong>recho está pulsadoShift <strong>de</strong>recho está pulsadoShift izquierdo está pulsadoAlt <strong>de</strong>recho está pulsadoAlt iquierdo está pulsadoCualquier Control está pulsadoCualquier Shift está pulsadoCualquier Alt está pulsadoEl último campo <strong>con</strong>tiene el carácter unico<strong>de</strong> <strong>de</strong> la tecla pulsada. Por <strong>de</strong>fecto estecampo no se rellena, ya que la traducción <strong>con</strong>lleva cierta sobrecarga, por lo queasegúrate <strong>de</strong> activarlo sólo si lo necesitas. Para activar este campo usamos la siguientefunción:int <strong>SDL</strong>_EnableUNICODE(int enable);Esta función <strong>de</strong>vuelve el anterior estado antes <strong>de</strong>l cambio. El parámetro enable pu<strong>de</strong>tomar tres valores: 0 para <strong>de</strong>sactivar la traducción unico<strong>de</strong>, 1 para activarla y –1 para<strong>de</strong>jar el estado como está. Esto nos sirve para <strong>con</strong>ocer el estado mediante el valor <strong>de</strong>retorno <strong>de</strong> la función.Si los 9 bits más altos <strong>de</strong>l código están a 0, es un carácter ASCII. El siguiente códigonos <strong>de</strong>vuelve el carácter ASCII en la variable ch (en caso <strong>de</strong> que no sea un carácterunico<strong>de</strong>).char ch;if ( (keysym.unico<strong>de</strong> & 0xFF80) == 0 ) {ch = keysym.unico<strong>de</strong> & 0x7F;}else {printf("Es un carácter unico<strong>de</strong>.\n");}Tanto en el ejemplo3_1, como en el ejemplo3_2 <strong>de</strong> la sección anterior sobre el vi<strong>de</strong>o,pue<strong>de</strong>s ver un ejemplo <strong>de</strong> lectura <strong>de</strong>l teclado. Ahora <strong>de</strong>berías po<strong>de</strong>r enten<strong>de</strong>r este códigosin ningún problema.Po<strong>de</strong>mos, a<strong>de</strong>más <strong>de</strong> <strong>con</strong>sultar el teclado mediante eventos, hacer una <strong>con</strong>sultadirecta al teclado para <strong>con</strong>ocer su estado actual. Es como hacer una “fotografía” <strong>de</strong>lestado <strong>de</strong>l teclado en un momento dado. La siguiente función realiza esta tarea:Uint8 *<strong>SDL</strong>_GetKeyState(int *numkeys);Esta función nos <strong>de</strong>vuelve un puntero a un array <strong>con</strong> el estado <strong>de</strong> cada una <strong>de</strong> lasteclas <strong>de</strong>l teclado. En el parámetro numkeys se <strong>de</strong>vuelve el tamaño <strong>de</strong> este array.Normalmente le pasaremos el valor NULL como parámetro. Para <strong>con</strong>sultar este arrayutilizamos las mismas <strong>con</strong>stantes <strong>de</strong> teclado que vimos antes. Si la tecla estaba pulsada43


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lel valor almacenado en la posición <strong>de</strong>l array correspondiente a la tecla es 1, si no estabapulsada, será 0. Veámoslo <strong>con</strong> un ejemplo.Uint8 *keys;keys=<strong>SDL</strong>_GetKeyState(NULL);if (keys[<strong>SDL</strong>K_UP] == 1) {arriba();}if (keys[<strong>SDL</strong>K_DOWN] == 1) {abajo();}if (keys[<strong>SDL</strong>K_LEFT] == 1) {izquierda();}if (keys[<strong>SDL</strong>K_RIGHT] == 1) {<strong>de</strong>recha();}if (keys[<strong>SDL</strong>K_LSHIFT] == 1) {disparo();}Eventos <strong>de</strong> ratónTal y como comentábamos al inicio <strong>de</strong> esta sección, el ratón pue<strong>de</strong> proporcionar dostipos <strong>de</strong> eventos. El referente al movimiento y el referente a la pulsación <strong>de</strong> los botones.Cuando suce<strong>de</strong> un evento <strong>de</strong> movimiento <strong>de</strong> ratón, el subsistema <strong>de</strong> eventos nos<strong>de</strong>vuelve una estructura <strong>de</strong> tipo <strong>SDL</strong>_MouseMotionEvent.type<strong>de</strong>f struct{Uint8 type;Uint8 state;Uint16 x, y;Sint16 xrel, yrel;} <strong>SDL</strong>_MouseMotionEvent;El primer campo ya lo <strong>con</strong>ocemos. Su único posible valor es <strong>SDL</strong>_MOUSEMOTION. Elcampo state <strong>de</strong>vuelve el estado <strong>de</strong> los botones <strong>de</strong>l ratón. Es una campo <strong>de</strong> bits quepue<strong>de</strong> ser <strong>con</strong>sultado cómodamente <strong>con</strong> la macro <strong>SDL</strong>_BUTTON(), pasándole comoparámetro 1,2 o 3 para indicar botón izquierdo, central o <strong>de</strong>recho. X e Y son lascoor<strong>de</strong>nadas <strong>de</strong>l ratón. xrel e yrel son la posición relativa respecto al último evento <strong>de</strong>ratón.El otro tipo <strong>de</strong> evento relacionado <strong>con</strong> el ratón es el referente a la pulsación <strong>de</strong> losbotones. Cuando se pulsa un botón <strong>de</strong>l ratón se crea un evento <strong>de</strong>l tipo<strong>SDL</strong>_MouseButtonEvent.type<strong>de</strong>f struct{Uint8 type;Uint8 button;Uint8 state;Uint16 x, y;} <strong>SDL</strong>_MouseButtonEvent;El campo type pue<strong>de</strong> tomar los valores <strong>SDL</strong>_MOUSEBUTTONDOWN o<strong>SDL</strong>_MOUSEBUTTONUP para indicar si se pulsó o se soltó el botón. El campo buttonpue<strong>de</strong> tomar los valores <strong>SDL</strong>_BUTTON_LEFT, <strong>SDL</strong>_BUTTON_MIDDLE,<strong>SDL</strong>_BUTTON_RIGHT para indicar que se pulsó (o se soltó) el botón izquierdo, central o el<strong>de</strong>recho. El campo state <strong>con</strong>tiene la misma información que el campo type, por lo quepo<strong>de</strong>mos ignorarlo. Sus posibles valores son <strong>SDL</strong>_PRESSED o <strong>SDL</strong>_RELEASED. Porúltimo, los campos X e Y <strong>con</strong>tienen las coor<strong>de</strong>nadas <strong>de</strong>l ratón en el momento que seprodujo el evento.44


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LEventos <strong>de</strong>l joystickEn esta sección vamos a ver como el subsistema <strong>de</strong> eventos se comunica <strong>con</strong> eljoystick. Cuando lleguemos a la sección <strong>de</strong>l subsistema <strong>de</strong> gestión <strong>de</strong>l joystickentraremos más en profundidad. Vamos a ver sólo dos <strong>de</strong> los eventos <strong>de</strong>l joystick, elreferente al movimiento <strong>de</strong>l joystick y el referente a la pulsación <strong>de</strong> los botones. Elprimero <strong>de</strong> ellos genera un evento <strong>de</strong>l tipo <strong>SDL</strong>_JoyAxisEvent que tiene la siguienteforma:type<strong>de</strong>f struct{Uint8 type;Uint8 which;Uint8 axis;Sint16 value;} <strong>SDL</strong>_JoyAxisEvent;El campo type <strong>con</strong>tine el valor <strong>SDL</strong>_JOYAXISMOTION. El segundo campo nos indicaqué joystick produjo el evento, si es que tenemos más <strong>de</strong> uno <strong>con</strong>ectado al or<strong>de</strong>nador. Elcampo axis nos dice qué eje se movió, y por último, value nos <strong>de</strong>vuelve el valor <strong>de</strong>este movimiento <strong>de</strong>ntro <strong>de</strong>l rango que va <strong>de</strong> -32768 hasta 32767.El otro tipo <strong>de</strong> evento relacionado <strong>con</strong> el joystick es <strong>SDL</strong>_JoyButtonEvent.type<strong>de</strong>f struct{Uint8 type;Uint8 which;Uint8 button;Uint8 state;} <strong>SDL</strong>_JoyButtonEvent;El campo type pue<strong>de</strong> tomar los valores <strong>SDL</strong>_JOYBUTTONDOWN o<strong>SDL</strong>_JOYBUTTONUP. Los dos campos siguientes son idénticos al <strong>de</strong>l evento<strong>SDL</strong>_JoyAxisEvent. El último campo pue<strong>de</strong> tomar los valores <strong>SDL</strong>_PRESSED o<strong>SDL</strong>_RELEASED.Para po<strong>de</strong>r utilizar la gestión <strong>de</strong> eventos <strong>de</strong>l joystick hemos <strong>de</strong> activar esta opción antes<strong>con</strong> la siguiente función:int <strong>SDL</strong>_JoystickEventState(int state);Los posibles valores <strong>de</strong> state son <strong>SDL</strong>_QUERY, <strong>SDL</strong>_ENABLE o <strong>SDL</strong>_IGNORE. Conla primera <strong>con</strong>sultamos el estado actual (activado o <strong>de</strong>sactivado). Las otras dos opcionesson para activar y <strong>de</strong>sactivar la lectura <strong>de</strong>l joystick mediante eventos. Si como parámetrole pasamos <strong>SDL</strong>_QUERY a la función, nos <strong>de</strong>volverá el estado actual, si no, nos <strong>de</strong>volveráel nuevo estado.En la sección <strong>de</strong>dicada al joystick profundizaremos más.Otros eventosUno <strong>de</strong> los eventos más importantes es <strong>SDL</strong>_QuitEvent. Este evento suce<strong>de</strong> cuando elsistema operativo quiere cerrar la aplicación (ya sea porque el usuario ha cerrado la ventana<strong>de</strong>l juego o porque simplemente el SO lo <strong>de</strong>cidió así por alguna oscura razón).type<strong>de</strong>f struct{Uint8 type} <strong>SDL</strong>_QuitEvent;45


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LLa estructura sólo tiene el campo type, que tomará el valor <strong>SDL</strong>_QUIT. Cuando serecibe este evento, hay que realizar las operaciones necesarias para cerrar la aplicación,como liberar memoria, guardar datos, etc...El siguiente evento que vamos a ver es <strong>SDL</strong>_ExposeEvent ya que tiene exactamentela misma forma que <strong>SDL</strong>_QuitEvent.type<strong>de</strong>f struct{Uint8 type} <strong>SDL</strong>_ExposeEvent;El campo type <strong>con</strong>tendrá el valor <strong>SDL</strong>_VIDEOEXPOSE. Este evento se genera cuandoel gestor <strong>de</strong> ventanas (Windows, KDE, etc...) ha realizado alguna modificación en lasventanas (ya sea por que el usuario ha movido alguna ventana o por que simplemente elgestor <strong>de</strong> ventanas a realizado un refresco) y nuestra aplicación necesita ser redibujada.Relacionado también <strong>con</strong> el gestor <strong>de</strong> ventanas tenemos el evento<strong>SDL</strong>_ResizeEvent.type<strong>de</strong>f struct{Uint8 type;int w, h;} <strong>SDL</strong>_ResizeEvent;Este evento se genera cuando la ventana cambia <strong>de</strong> tamaño (si es que el juego seestá ejecutando en una ventana). Normalmente, si esto suce<strong>de</strong> tendremos que recalcularla posición <strong>de</strong> los elementos <strong>de</strong>l juego. El campo type <strong>con</strong>tiene el valor<strong>SDL</strong>_VIDEORESIZE. Los campos w y h son la nueva anchura y altura <strong>de</strong> la ventana.Vamos a terminar <strong>con</strong> el evento <strong>SDL</strong>_ActiveEvent. En un entorno multiventana, elusuario pue<strong>de</strong> pasar <strong>de</strong> una aplicación a otra. La forma <strong>de</strong> <strong>con</strong>ocer si tenemos laatención <strong>de</strong>l usuario es mediante este evento.type<strong>de</strong>f struct{Uint8 type;Uint8 gain;Uint8 state;} <strong>SDL</strong>_ActiveEvent;El primer campo tendrá el valor <strong>SDL</strong>_ACTIVEEVENT. El campo gain valdrá 1 si laaplicación a retomado la atención o 0 si la perdió. Por último, pero no por ello, menosimportante tenemos el campo state, que nos amplía más información sobre el campogain. Sus posibles valores son:<strong>SDL</strong>_APPMOUSEFOCUS si se ganó (o perdió, según el campo gain) el foco <strong>de</strong>l ratón, es<strong>de</strong>cir, si el ratón entró (o salió) <strong>de</strong>l área <strong>de</strong> la ventana <strong>de</strong> nuestra aplicación.<strong>SDL</strong>_APPINPUTFOCUS si se ganó (o perdió) el foco <strong>de</strong> entrada por teclado.<strong>SDL</strong>_APPACTIVE si la aplicación fue maximizada (o minimizada).46


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LJoystickYa hemos visto como se pue<strong>de</strong> <strong>con</strong>sultar el joystick mediante la <strong>con</strong>sulta <strong>de</strong> eventos.En esta sección vamos a profundizar en el uso <strong>de</strong>l joystick. Al igual que <strong>con</strong> el teclado, eljoystick también pue<strong>de</strong> ser <strong>con</strong>sultado <strong>de</strong> forma directa. A<strong>de</strong>más <strong>de</strong> <strong>con</strong>ocer el estado <strong>de</strong>ljoystick, nos pue<strong>de</strong> interesar <strong>con</strong>ocer otras informaciones, como por ejemplo, cuantosjoysticks hay <strong>con</strong>ectados (si es que hay alguno), que características tiene, cuantosbotones, etc...Vamos a centrarnos exclusivamente en los ejes y en los botones <strong>de</strong>l joystick. El<strong>con</strong>cepto <strong>de</strong> eje es muy simple. Cuando movemos la palanca <strong>de</strong> mando hacia arriba ohacia abajo, estamos moviendo el joystick sobre el eje vertical. De la misma forma, almover el mando <strong>de</strong> forma lateral lo movemos sobre el eje horizontal. Por lo tanto, unjoystick clásico tiene dos ejes. Si en vez <strong>de</strong> un joystick clásico tenemos un paddle(recuerda el clásico juego <strong>de</strong> raquetas pong, <strong>con</strong> un mando giratorio que movía laraqueta), este sólo <strong>con</strong>stará <strong>de</strong> un eje. Para po<strong>de</strong>r utilizar el joystick <strong>de</strong>s<strong>de</strong> <strong>SDL</strong> tenemosque inicializarlo <strong>con</strong> el flag <strong>SDL</strong>_INIT_JOYSTICK. Antes <strong>de</strong> entrar en las funciones quenos van a permitir <strong>con</strong>sultar los estados <strong>de</strong>l joystick, vamos a ver algunas <strong>de</strong>dicadas amostrar información sobre él.Recopilando información sobre el joystickLo primero que necesitamos saber es si hay algún joystick <strong>con</strong>ectado al or<strong>de</strong>nador, yen su caso, cuántos hay. La siguiente función nos provee esta información:int <strong>SDL</strong>_NumJoysticks(void);El valor <strong>de</strong>vuelto por esta función es el número <strong>de</strong> joystick <strong>con</strong>ectados al or<strong>de</strong>nador.Otra información que pue<strong>de</strong> sernos útil es la siguiente:<strong>con</strong>st char *<strong>SDL</strong>_JoystickName(int in<strong>de</strong>x);Nos <strong>de</strong>vuelve un puntero a una ca<strong>de</strong>na que <strong>con</strong>tiene el nombre <strong>de</strong>l joystick o su driver.El parámetro in<strong>de</strong>x es el número <strong>de</strong> joystick que queremos <strong>con</strong>sultar.Una vez que <strong>con</strong>ocemos el número <strong>de</strong> joysticks <strong>con</strong>ectados al or<strong>de</strong>nador, es elmomento <strong>de</strong> utilizarlos. Lo primero que hay que hacer es abrirlos.<strong>SDL</strong>_Joystick *<strong>SDL</strong>_JoystickOpen(int in<strong>de</strong>x);Como en la anterior función, in<strong>de</strong>x es el número <strong>de</strong>l joystick que queremos abrir. Lafunción <strong>de</strong>vuelve un puntero <strong>de</strong>l tipo <strong>SDL</strong>_Joystick. No vamos a entrar en <strong>de</strong>talle sobreesta estructura <strong>de</strong> datos. Simplemente saber que el tipo <strong>de</strong>vuelto es el que usaremos enlas siguientes funciones para referirnos al joystick que acabamos <strong>de</strong> abrir.Como abras adivinado, <strong>SDL</strong> dispone <strong>de</strong> otra función para cerrar el joystick.void <strong>SDL</strong>_JoystickClose(<strong>SDL</strong>_Joystick *joystick);Como parámetro hay que pasarle el puntero que nos <strong>de</strong>volvió la función<strong>SDL</strong>_JoystickOpen al abrir el joystick. Es importante que cerremos el joystick antes <strong>de</strong>finalizar el programa (en realidad es importante que cerremos todo lo que inicialicemos enun programa).47


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LSi tenemos más <strong>de</strong> un joystick <strong>con</strong>ectados, nos pue<strong>de</strong> resultar interesante sabercuáles están abiertos y cuáles no. La función <strong>SDL</strong>_JoystickOpened nos ofrece estainformación:int <strong>SDL</strong>_JoystickOpened(int in<strong>de</strong>x);El parámetro in<strong>de</strong>x es el número <strong>de</strong> joystick que queremos <strong>con</strong>sultar. Esta función<strong>de</strong>vuelve 1 si el joystick <strong>con</strong>sultado está abierto y 0 en caso <strong>con</strong>trario.Nos queda ver un par <strong>de</strong> funciones vitales antes <strong>de</strong> entrar en harina.int <strong>SDL</strong>_JoystickNumAxes(<strong>SDL</strong>_Joystick *joystick);int <strong>SDL</strong>_JoystickNumButtons(<strong>SDL</strong>_Joystick *joystick);Estas funciones nos <strong>de</strong>vuelven el número <strong>de</strong> ejes y el número <strong>de</strong> botonesrespectivamente que tiene el joystick. Como parámetro le pasamos el joystick quequeremos <strong>con</strong>sultar (el valor <strong>de</strong>vuelto por <strong>SDL</strong>_JoystickOpen).Leyendo el joystickComo comentamos al principio <strong>de</strong> la sección, nos vamos a centrar en la lectura <strong>de</strong> los ejesy <strong>de</strong> los botones. Si quieres profundizar, pue<strong>de</strong>s remitirte a la documentación <strong>de</strong> <strong>SDL</strong>.Antes <strong>de</strong> <strong>con</strong>sultar el estado <strong>de</strong>l joystick, hay que hacer una llamada a la función<strong>SDL</strong>_JoystickUpdate.void <strong>SDL</strong>_JoystickUpdate(void);Esta función, que no toma ni <strong>de</strong>vuelve ningún parámetro, actualiza el estado <strong>de</strong> losjoysticks abiertos. Antes <strong>de</strong> cada lectura <strong>de</strong> estado hay que hacer una llamada a estafunción.El estado <strong>de</strong> los ejes <strong>de</strong>l joystick lo <strong>con</strong>sultamos <strong>con</strong> la siguiente función:Sint16 <strong>SDL</strong>_JoystickGetAxis(<strong>SDL</strong>_Joystick *joystick, int axis);Como parámetros le indicamos el joystick y el eje que queremos <strong>con</strong>sultar. Nos<strong>de</strong>volverá el valor <strong>de</strong>l estado <strong>de</strong>l eje. Este valor estará entre -32768 y 32768.Para la <strong>con</strong>sulta <strong>de</strong> los botones utilizaremos la funciónUint8 <strong>SDL</strong>_JoystickGetButton(<strong>SDL</strong>_Joystick *joystick, int button);Esta función es similar a la anterior, sólo que en vez <strong>de</strong>l eje a <strong>con</strong>sultar, le pasamos elbotón a <strong>con</strong>sultar. El valor <strong>de</strong>vuelto pue<strong>de</strong> ser 1, el botón está pulsado y 0 en caso<strong>con</strong>trario.El siguiente ejemplo (ejemplo3_3) muestra el uso <strong>de</strong>l joystick.48


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L/*****************************************Ejemplo3_3(C) 2003 by Alberto Garcia SerranoProgramación <strong>de</strong> vi<strong>de</strong>ojuegos <strong>con</strong> <strong>SDL</strong>*****************************************/#inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> int main(int argc, char *argv[]) {<strong>SDL</strong>_Surface *image, *screen;<strong>SDL</strong>_Rect <strong>de</strong>st;<strong>SDL</strong>_Event event;Sint16 joyx,joyy;<strong>SDL</strong>_Joystick *joystick;int done = 0;int x,y,button;atexit(<strong>SDL</strong>_Quit);// Iniciar <strong>SDL</strong>if (<strong>SDL</strong>_Init(<strong>SDL</strong>_INIT_VIDEO|<strong>SDL</strong>_INIT_JOYSTICK) < 0) {printf("No se pudo iniciar <strong>SDL</strong>: %s\n",<strong>SDL</strong>_GetError());exit(1);}// Activamos modo <strong>de</strong> vi<strong>de</strong>oscreen = <strong>SDL</strong>_SetVi<strong>de</strong>oMo<strong>de</strong>(640,480,24,<strong>SDL</strong>_HWSURFACE);if (screen == NULL) {printf("No se pue<strong>de</strong> inicializar el modo gráfico: \n",<strong>SDL</strong>_GetError());exit(1);}// Activa el Joystickif (<strong>SDL</strong>_NumJoysticks() >= 1) {joystick = <strong>SDL</strong>_JoystickOpen(0);<strong>SDL</strong>_JoystickEventState(<strong>SDL</strong>_ENABLE);}// Cargamos gráficoimage = <strong>SDL</strong>_LoadBMP("nave.bmp");if ( image == NULL ) {printf("No pu<strong>de</strong> cargar gráfico: %s\n", <strong>SDL</strong>_GetError());exit(1);}// Definimos color para la transparencia<strong>SDL</strong>_SetColorKey(image,<strong>SDL</strong>_SRCCOLORKEY|<strong>SDL</strong>_RLEACCEL,<strong>SDL</strong>_MapRGB(image->format,255,0,0));x = y = 100;button = 0;while(done == 0) {// Borramos la pantalla<strong>de</strong>st.x=0;<strong>de</strong>st.y=0;<strong>de</strong>st.w=640;<strong>de</strong>st.h=480;<strong>SDL</strong>_FillRect(screen,&<strong>de</strong>st,<strong>SDL</strong>_MapRGB(screen->format,0,0,0));// Definimos don<strong>de</strong> dibujaremos el gráfico// y lo copiamos a la pantalla.<strong>de</strong>st.x = x;<strong>de</strong>st.y = y;<strong>de</strong>st.w = image->w;<strong>de</strong>st.h = image->h;<strong>SDL</strong>_BlitSurface(image, NULL, screen, &<strong>de</strong>st);// Mostramos la pantalla<strong>SDL</strong>_Flip(screen);49


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L// lectura directa <strong>de</strong>l joystickjoyx = <strong>SDL</strong>_JoystickGetAxis(joystick, 0);joyy = <strong>SDL</strong>_JoystickGetAxis(joystick, 1);if (joyy < -10) {y-=5;}if (joyy > 10) {y+=5;}if (joyx < -10) {x-=5;}if (joyx > 10) {x+=5;}// Lectura <strong>de</strong> eventoswhile ( <strong>SDL</strong>_PollEvent(&event) ) {// salir <strong>de</strong>l programa si se pulsa el boton <strong>de</strong>l joystickif ( event.type == <strong>SDL</strong>_JOYBUTTONDOWN) {done = 1;}}}// salir <strong>de</strong>l programa si se pulsa una tecla// o se cierra la ventanaif ( event.type == <strong>SDL</strong>_KEYDOWN || event.type == <strong>SDL</strong>_QUIT)done = 1;// liberar superficie<strong>SDL</strong>_FreeSurface(image);// cerramos el joystickif (<strong>SDL</strong>_JoystickOpened(0)) {<strong>SDL</strong>_JoystickClose(joystick);}}return 0;50


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LEste ejemplo muestra nuestro ya familiar avión, y a<strong>de</strong>más nos permite moverlo <strong>con</strong> eljoystick por la pantalla. Para ilustrar las dos formas <strong>de</strong> acce<strong>de</strong>r al joystick (mediante<strong>con</strong>sulta <strong>de</strong> eventos o <strong>de</strong> forma directa), hacemos la <strong>con</strong>sulta <strong>de</strong> los ejes (movimiento)mediante <strong>con</strong>sulta directa y la lectura <strong>de</strong>l botón <strong>de</strong>l joystick la hacemos mediante la<strong>con</strong>sulta <strong>de</strong> eventos. Para salir sólo hay que pulsar cualquier tecla o el botón <strong>de</strong>l ratón.AudioCréeme, no querrás <strong>de</strong>pen<strong>de</strong>r exclusivamente <strong>de</strong>l subsistema <strong>de</strong> audio <strong>de</strong> <strong>SDL</strong> para elsonido <strong>de</strong> tu juego. En comparación <strong>con</strong> el resto <strong>de</strong> los subsistemas, el audio <strong>de</strong> <strong>SDL</strong> esbastante espartano (y no precisamente porque sea <strong>de</strong>l sur <strong>de</strong>l Peloponeso).Afortunadamente, hay una librería llamada sdl_mixer que nos va a hacer la vida más fácil.La vamos a utilizar en el juego que <strong>de</strong>sarrollaremos en próximos capítulos, y a menosque alguien tenga una acentuada ten<strong>de</strong>ncia masoquista, la mayor parte <strong>de</strong> los juegoscreados <strong>con</strong> <strong>SDL</strong> la usan. Vamos a pasar <strong>de</strong> puntillas sobre este subsistema, y en elpróximo capitulo presentaremos sdl_mixer y sus virtu<strong>de</strong>s.Empecemos por la estructura <strong>de</strong> datos <strong>SDL</strong>_AudioSpec.type<strong>de</strong>f struct{int freq;Uint16 format;Uint8 channels;Uint8 silence;Uint16 samples;Uint32 size;void (*callback)(void *userdata, Uint8 *stream, int len);void *userdata;} <strong>SDL</strong>_AudioSpec;Esta estructura especifica el formato <strong>de</strong> audio que vamos a utilizar. Veamos más<strong>de</strong>talladamente cada campo.El primero es freq, que especifica la frecuencia (en Hertzios) <strong>de</strong> reproducción <strong>de</strong>lsample. Valores habituales son 11025 (calidad telefónica), 22050 (calidad radiofónica) o44100 (calidad CD).El campo format especifica el formato <strong>de</strong>l sample (bits y tipo). Los posibles valoresson:AUDIO_U8AUDIO_S8AUDIO_U16 o AUDIO_U16LSBAUDIO_S16 o AUDIO_S16LSBAUDIO_U16MSBAUDIO_S16MSBAUDIO_U16SYSAUDIO_S16SYSSample <strong>de</strong> 8 bits sin signoSample <strong>de</strong> 8 bits <strong>con</strong> signoSample <strong>de</strong> 16 bits sin signo enformato little-endian.Sample <strong>de</strong> 16 bits <strong>con</strong> signo enformato little-endian.Sample <strong>de</strong> 16 bits sin signo enformato big-endian.Sample <strong>de</strong> 16 bits <strong>con</strong> signo enformato big-endian.AUDIO_U16LSB oAUDIO_U16MSB <strong>de</strong>pendiendo <strong>de</strong>lpeso <strong>de</strong> tu sistema (big o littleendían)AUDIO_S16LSB oAUDIO_S16MSB <strong>de</strong>pendiendo <strong>de</strong>lpeso <strong>de</strong> tu sistema (big o littleendían)51


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LEl campo channels indica el número <strong>de</strong> canales <strong>de</strong> audio. Pue<strong>de</strong>n ser 1 para mono y2 para estereo.El campo silence, es un valor calculado que representa el valor para silencio.El siguiente campo es samples, y <strong>con</strong>tiene el tamaño <strong>de</strong>l buffer <strong>de</strong> audio en samples.El campo size, es el tamaño <strong>de</strong>l buffer, pero esta vez en bytes. Es un campocalculado, así que no <strong>de</strong>bemos preocuparnos por el. El campo que sigue es un puntero auna función <strong>de</strong> retrollamada, que es la función llamada cuando es necesario reproducir elsonido. Efectivamente, tal y como estas pensando, somos nosotros los encargados <strong>de</strong><strong>de</strong>sarrollar esta función. Su cometido es rellenar el buffer <strong>con</strong>tenido en el punterostream, <strong>con</strong> una cantidad <strong>de</strong> bytes igual a len. En principio pue<strong>de</strong> parecer que no esuna tarea <strong>de</strong>masiado compleja ¿verdad? Esta bien, si quieres reproducir un solo sonidoaislado es probable que no, pero el 99% <strong>de</strong> las veces necesitarás mezclar varios sonidos(por ejemplo, una música <strong>con</strong> una explosión, o simplemente un disparo y una explosión).Aunque <strong>SDL</strong> nos ofrece una forma <strong>de</strong> hacerlo, no es <strong>de</strong>masiado efectivo, <strong>de</strong> hecho, siquieres una calidad <strong>de</strong>cente, tendrás que programar tu propia función <strong>de</strong> mezcla, ycréeme, no querrás tener que hacerlo.El campo userdata es el mismo que se le pasa a la función <strong>de</strong> retrollamada.Po<strong>de</strong>mos pasar información a nuestra función en caso necesario.Vayamos ahora <strong>con</strong> las funciones <strong>de</strong> manejo <strong>de</strong> audio.int <strong>SDL</strong>_OpenAudio(<strong>SDL</strong>_AudioSpec *<strong>de</strong>sired, <strong>SDL</strong>_AudioSpec *obtained);Tal y como habrás adivinado, esta función abre el dispositivo <strong>de</strong> audio. Comoparámetros tiene dos punteros <strong>de</strong> tipo <strong>SDL</strong>_AudioSpec. En el primero le solicitamos lasespecificaciones <strong>con</strong> las que queremos trabajar. Esta función intentará abrir el dispositivo<strong>de</strong> audio <strong>con</strong> las especificaciones que le pidamos, aunque esto no siempre será posible.En cualquier caso, obtained apuntará a las especificaciones finales que la función hapodido <strong>con</strong>seguirnos. Si todo fue bien, nos <strong>de</strong>volverá 0, en caso <strong>con</strong>trario, -1.Como casi todo en <strong>SDL</strong> (y también en la vida cotidiana), todo lo que abrimos, hay quecerrarlo. La función encargada <strong>de</strong> esto es:void <strong>SDL</strong>_CloseAudio(void);Como no tiene ningún parámetro ni <strong>de</strong>vuelve ningún valor, es una candidata perfectapara usarla <strong>con</strong> la función atexit.Una vez abierto es dispositivo <strong>de</strong> audio, necesitamos una función para iniciar y para elaudio. Aquí la tenemos:void <strong>SDL</strong>_PauseAudio(int pause_on);Cuando pasamos como parámetro el valor 0, activamos la reproducción <strong>de</strong> sonido. Sipasamos un 1, hacemos una pausa.Por último veamos un par <strong>de</strong> funciones que nos permiten el trabajo <strong>con</strong> archivos enformato .wav:<strong>SDL</strong>_AudioSpec *<strong>SDL</strong>_LoadWAV(<strong>con</strong>st char *file, <strong>SDL</strong>_AudioSpec *spec, Uint8**audio_buf, Uint32 *audio_len);void <strong>SDL</strong>_FreeWAV(Uint8 *audio_buf);La primera función carga un archivo en formato .wav en memoria. El primer parámetroes una ca<strong>de</strong>na <strong>con</strong> el nombre <strong>de</strong>l archivo. Si todo fue bien, nos <strong>de</strong>volverá un puntero a laestructura <strong>SDL</strong>_AudioSpec <strong>de</strong>l fichero .wav. En audio_buff se encuentra la52


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Linformación <strong>de</strong>l sample, y en audio_len su tamaño. Como ves, todo listo para pasárseloa la función <strong>de</strong> retrollamada. La segunda función libera el espacio ocupado por el sample.El ejemplo3_4 nos aclara un poco la forma <strong>de</strong> utilizar el subsistema <strong>de</strong> audio. Lo quehace es reproducir un sonido aleatorio. Este código es muy sencillo y permite ver comoutilizar la función <strong>de</strong> retrollamada. En el próximo capitulo apren<strong>de</strong>remos como hacersonar a <strong>SDL</strong> <strong>con</strong> la ayuda <strong>de</strong> <strong>SDL</strong>_mixer.53


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L/*****************************************Ejemplo3_4(C) 2003 by Alberto Garcia SerranoProgramación <strong>de</strong> vi<strong>de</strong>ojuegos <strong>con</strong> <strong>SDL</strong>*****************************************/#inclu<strong>de</strong> #inclu<strong>de</strong> <strong>SDL</strong>_Surface* superficie;<strong>SDL</strong>_Event event;<strong>SDL</strong>_AudioSpec* <strong>de</strong>seado;<strong>SDL</strong>_AudioSpec* obtenido;int done;// <strong>de</strong>claración <strong>de</strong> la función <strong>de</strong> retrollamadavoid retrollamada(void* userdata,Uint8* buffer,int len);int main(int argc, char* argv[]) {// Inicializamos <strong>SDL</strong>if (<strong>SDL</strong>_Init(<strong>SDL</strong>_INIT_VIDEO|<strong>SDL</strong>_INIT_AUDIO) < 0) {printf("No se pudo inicializar <strong>SDL</strong>.\n");exit(1);}// Inicializamos modo <strong>de</strong> vi<strong>de</strong>o// (en Windows es necesario para que funcione el audio)if ( (superficie = <strong>SDL</strong>_SetVi<strong>de</strong>oMo<strong>de</strong>(640,480,0,<strong>SDL</strong>_ANYFORMAT)) == NULL) {printf("No se pudo inicializar la ventana.\n");exit(1);}// Alojamos espacio para almacenar las estructuras<strong>de</strong>seado=new <strong>SDL</strong>_AudioSpec;obtenido=new <strong>SDL</strong>_AudioSpec;// especificaciones <strong>de</strong>seadas<strong>de</strong>seado->freq=11025;<strong>de</strong>seado->format=AUDIO_S16SYS;<strong>de</strong>seado->channels=1;<strong>de</strong>seado->samples=4096;<strong>de</strong>seado->callback=retrollamada;<strong>de</strong>seado->userdata=NULL;// abrimos el dispositivo <strong>de</strong> audioif(<strong>SDL</strong>_OpenAudio(<strong>de</strong>seado,obtenido)


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L}int i;for(i=0 ; i


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LLa primera función nos <strong>de</strong>vuelve el número <strong>de</strong> CD-ROMs <strong>con</strong>ectados al sistema. Lasegunda nos <strong>de</strong>vuelve una ca<strong>de</strong>na <strong>con</strong> el nombre <strong>de</strong>l CD-ROM. Como parámetro recibeel número <strong>de</strong>l CD-ROM que queremos <strong>con</strong>sultar.Como era <strong>de</strong> esperar, antes <strong>de</strong> po<strong>de</strong>r utilizar el CD-ROM hay que abrirlo. La funciónencargada <strong>de</strong> esto es la siguiente:<strong>SDL</strong>_CD *<strong>SDL</strong>_CDOpen(int drive);Como parámetro le pasamos el CD-ROM que queremos abrir, y nos <strong>de</strong>volverá unpuntero a una estructura <strong>SDL</strong>_CD <strong>con</strong> la información <strong>de</strong>l CD. La función que cierra el CD-ROM esvoid <strong>SDL</strong>_CDClose(<strong>SDL</strong>_CD *cdrom);Como parámetro le pasamos el valor <strong>de</strong>vuelto por <strong>SDL</strong>_CDOpen al abrir el CD.La función que nos permite <strong>con</strong>ocer el estado <strong>de</strong>l CD es <strong>SDL</strong>_CDStatus.CDstatus <strong>SDL</strong>_CDStatus(<strong>SDL</strong>_CD *cdrom);Nos <strong>de</strong>volverá el estado actual <strong>de</strong>l CD-ROM. Los posibles valores son los mismos quepue<strong>de</strong> tomar el campo status <strong>de</strong> la estructura <strong>SDL</strong>_CD.Las siguientes dos funciones que vamos a presentar nos permiten reproducir pistas<strong>de</strong>l CD.int <strong>SDL</strong>_CDPlay(<strong>SDL</strong>_CD *cdrom, int start, int length);int <strong>SDL</strong>_CDPlayTracks(<strong>SDL</strong>_CD *cdrom, int start_track, int start_frame, intntracks, int nframes));La primera función comienza la reproducción <strong>de</strong>l CD en el frame indicado por start ydurante los frames indicados por length. Si hay algún problema <strong>con</strong> la reproducción<strong>de</strong>vuelve –1, si la reproducción tuvo éxito <strong>de</strong>volverá 0.La función <strong>SDL</strong>_CDPlayTracks reproduce una o varias pistas. Con el parámetrostart_track le indicamos la pista <strong>de</strong> inicio, y numtracks el número <strong>de</strong> pistas areproducir. El parámetro start_frame es el frame, <strong>de</strong>ntro <strong>de</strong> la pista, don<strong>de</strong> queremosiniciar la reproducción. Por último nframes es el número <strong>de</strong> frames <strong>de</strong> la última pista quequeremos reproducir.Seguramente querrás po<strong>de</strong>r comenzar la reproducción en un <strong>de</strong>terminado momentoexpresado en tiempo en vez <strong>de</strong> en frames. Pue<strong>de</strong>s usar la siguiente fórmula que nos<strong>de</strong>vuelve la duración en segundos.Longitud en frames / CD_FPSLa <strong>con</strong>stante CD_FPS <strong>con</strong>tiene los frames por segundos <strong>de</strong>l CD-ROM. Para finalizar,y antes <strong>de</strong> que veamos un ejemplo sobre el manejo <strong>de</strong>l CD-ROM, vamos a ver cuatrofunciones que requieren poca expliación:int <strong>SDL</strong>_CDPause(<strong>SDL</strong>_CD *cdrom);int <strong>SDL</strong>_CDResume(<strong>SDL</strong>_CD *cdrom);int <strong>SDL</strong>_CDStop(<strong>SDL</strong>_CD *cdrom);int <strong>SDL</strong>_CDEject(<strong>SDL</strong>_CD *cdrom);La primera función pone en pausa la reproducción <strong>de</strong>l CD, mientras que<strong>SDL</strong>_CDResume <strong>con</strong>tinúa <strong>con</strong> la reproducción. Si queremos parar la reproducción57


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lusaremos la función <strong>SDL</strong>_CDStop, y <strong>con</strong> <strong>SDL</strong>_CDEject expulsaremos la ban<strong>de</strong>ja <strong>de</strong>lCD_ROM.El ejemplo siguiente reproduce la primera pista <strong>de</strong>l CD que se encuentre en la primeralectora <strong>de</strong>l sistema.58


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L/*****************************************Ejemplo3_5(C) 2003 by Alberto Garcia SerranoProgramación <strong>de</strong> vi<strong>de</strong>ojuegos <strong>con</strong> <strong>SDL</strong>*****************************************/#inclu<strong>de</strong> #inclu<strong>de</strong> <strong>SDL</strong>_Surface* superficie;<strong>SDL</strong>_Event event;<strong>SDL</strong>_CD *cdrom;int pista = 0;int done;int main(int argc, char* argv[]) {// Inicializamos <strong>SDL</strong>if (<strong>SDL</strong>_Init(<strong>SDL</strong>_INIT_VIDEO|<strong>SDL</strong>_INIT_CDROM)track[pista].length)) < 0) {printf("No puedo reproducir el CD\n");exit(1);}// esperamos a que cierren la aplicacióndone = 0;while (!done) {if(<strong>SDL</strong>_PollEvent(&event)==0)if(event.type == <strong>SDL</strong>_QUIT) done=1;}}// <strong>de</strong>tenemos la reproducción y cerramos el CDROM<strong>SDL</strong>_CDStop(cdrom);<strong>SDL</strong>_CDClose(cdrom);return(0);59


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LEl Window ManagerUn window manager o gestor <strong>de</strong> ventanas es un programa que nos permite ejecutaraplicaciones <strong>de</strong>ntro <strong>de</strong> ventanas, realizar acciones como cortar y pegar entreaplicaciones, mover y cambiar el tamaño <strong>de</strong> las ventanas, etc... En el caso <strong>de</strong> Windows,el gestor <strong>de</strong> ventanas forma parte <strong>de</strong>l propio sistema operativo. En otros entornos, comoLinux, po<strong>de</strong>mos trabajar <strong>con</strong> el gestor <strong>de</strong> ventanas que más nos guste, como KDE oGnome por nombrar los dos más <strong>con</strong>ocidos. Habrás notado que todas las aplicacionesque hemos realizado hasta ahora, al ejecutarse mostraban el nombre <strong>SDL</strong>_app en laventana. Este es el nombre por <strong>de</strong>fecto. Po<strong>de</strong>mos cambiar este nombre mediante lasiguiente función:void <strong>SDL</strong>_WM_SetCaption(<strong>con</strong>st char *title, <strong>con</strong>st char *i<strong>con</strong>);Don<strong>de</strong> el primer parámetro es el título <strong>de</strong> la aplicación y el segundo el nombre <strong>de</strong>licóno. En Windows este segundo parámetro no se utiliza. Po<strong>de</strong>mos <strong>con</strong>ocer los valoresactuales <strong>con</strong> la siguiente función.void <strong>SDL</strong>_WM_GetCaption(char **title, char **i<strong>con</strong>);Para cambiar el i<strong>con</strong>o <strong>de</strong> la aplicación po<strong>de</strong>mos utilizar la funciónvoid <strong>SDL</strong>_WM_SetI<strong>con</strong>(<strong>SDL</strong>_Surface *i<strong>con</strong>, Uint8 *mask);El primer parámetro es una surface que <strong>con</strong>tiene el icóno. En Windows este icóno ha<strong>de</strong> tener una resolución <strong>de</strong> 32x32 píxeles. El segundo parámetro es un puntero a un array<strong>con</strong>teniendo la máscara <strong>de</strong> bits para el icóno (esto es usado para <strong>de</strong>finir la forma <strong>de</strong>licóno y sus partes transparentes). Si como valor pasamos NULL, las transparenciasestarán <strong>de</strong>finias por el color key <strong>de</strong> la superficie. Veamos un ejemplo simple:<strong>SDL</strong>_WM_SetI<strong>con</strong>(<strong>SDL</strong>_LoadBMP("i<strong>con</strong>.bmp"), NULL);Si queremos minimizar la aplicación mientras se ejecuta, usaremos la funciónint <strong>SDL</strong>_WM_I<strong>con</strong>ifyWindow(void);Si no se pudo minimizar la aplicación, esta función <strong>de</strong>volverá el valor 0.TimmingVamos a terminar esta introducción a <strong>SDL</strong> <strong>con</strong> el <strong>con</strong>trol <strong>de</strong> tiempo. Dejamos sin tratar todolo referido a multitarea y programación <strong>de</strong> hilos. Hay que saber bién lo que se está haciendo ytener, al menos, unos sólidos <strong>con</strong>ocimientos sobre multitarea y sistemas operativos para sacarprovecho a esta capacidad <strong>de</strong> <strong>SDL</strong>. Afortunadamente, no nos va a ser necesaria para crearvi<strong>de</strong>ojuegos.Lo que si nos va a hacer falta es po<strong>de</strong>r <strong>con</strong>trolar el tiempo. Es una taréa esencial, ya queno todos los or<strong>de</strong>nadores funcionan a la misma velocidad. Si no <strong>con</strong>trolaramos el tiempo,nuestro juego funcionaría bien en algunos or<strong>de</strong>nadores, pero en otros (los más rápidos) iría auna velocidad endiablada. Vamos <strong>con</strong> la primera función.Uint32 <strong>SDL</strong>_GetTicks(void);60


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LEsta función <strong>de</strong>vuelve el número <strong>de</strong> milisegundos transcurridos <strong>de</strong>s<strong>de</strong> que se inicializó<strong>SDL</strong>. Gracias a ella po<strong>de</strong>mos <strong>con</strong>trolar el tiempo transcurrido entre dos instantes dados<strong>de</strong>ntro <strong>de</strong>l programa, y por lo tan, <strong>con</strong>trolar la velocidad <strong>de</strong>l juego. Cuando <strong>de</strong>sarrollemosel nuestro durante los próximos capítulos, podremos ver cómo <strong>con</strong>trolar la velocidad <strong>de</strong>juego <strong>con</strong> esta función.La función siguiente nos permite <strong>de</strong>tener la ejecución <strong>de</strong>l juego durante un tiempo<strong>de</strong>terminado. Veamos que forma tiene.void <strong>SDL</strong>_Delay(Uint32 ms);Como parámetro le pasamos a la función el número <strong>de</strong> milisegundos que queremosesperar. Dependiendo <strong>de</strong>l Sistema Operativo, la espera será más o menos exacta, peropor regla general el error pue<strong>de</strong> ser <strong>de</strong> 10ms.61


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LCapítulo4Librerías auxiliares para <strong>SDL</strong>El uso <strong>de</strong> las faculta<strong>de</strong>s que me <strong>con</strong>cedió la naturaleza es el único placer que no <strong>de</strong>pen<strong>de</strong> <strong>de</strong> laayuda <strong>de</strong> la opinión ajena.Ugo Foscolo.Afortunadamente, a<strong>de</strong>más <strong>de</strong> las funciones que forman la librería <strong>SDL</strong>, po<strong>de</strong>mos<strong>con</strong>tar <strong>con</strong> una serie <strong>de</strong> librerías externas que nos van a ayudar bastante a lahora <strong>de</strong> programar nuestro juego. Las cuatro librerías auxiliares más habitulesson <strong>SDL</strong>_image, <strong>SDL</strong>_mixer, <strong>SDL</strong>_ttf y <strong>SDL</strong>_net. En el juego <strong>de</strong> ejemplo <strong>de</strong> estelibro utilizaremos <strong>SDL</strong>_mixer para la reproducción <strong>de</strong>l sonido y la música, y <strong>SDL</strong>_ttf parael manejo <strong>de</strong> fuentes <strong>de</strong> letra. <strong>SDL</strong>_net es una librería que nos permite la <strong>con</strong>exión are<strong>de</strong>s TCP/IP, y es utilizada para crear juegos multijugador en red. <strong>SDL</strong>_image es unacuriosa librería <strong>con</strong> una única función, que nos permite trabajar <strong>con</strong> múltiples formatosgráficos.<strong>SDL</strong>_ttfEs muy posible que hayas echado <strong>de</strong> menos alguna función en <strong>SDL</strong> para escribir texto enla pantalla gráfica. La librería <strong>SDL</strong>_ttf nos permite, básicamente, dibujar el texto que <strong>de</strong>seemosen una superficie utilizando la fuente <strong>de</strong> letra que queramos. La fuente <strong>de</strong> letra tiene que seren formato ttf (true type font). Como ya viene siendo habitual, lo primero que tenemos quehacer es inicializar la librería.int TTF_Init(void)Esta función <strong>de</strong>volverá 0 si se realizó correctamente la inicialización y –1 en caso <strong>con</strong>trario.Como algún avispado lector ya habrá supuesto, la función inversa tiene la siguiente forma:void TTF_Quit(void)Al no recibir ni <strong>de</strong>volver esta función ningún valor, es una candidata perfecta para utilizarlajunto <strong>con</strong> la función atexit().El siguiente paso es abrir la fuente que queremos utilizar, es <strong>de</strong>cir, cargarla <strong>de</strong>s<strong>de</strong> disco.Las dos siguientes funciones nos permiten abrir una fuente <strong>de</strong> letra.TTF_Font * TTF_OpenFont(<strong>con</strong>st char *file, int ptsize);TTF_Font * TTF_OpenFontIn<strong>de</strong>x(<strong>con</strong>st char *file, int ptsize, long in<strong>de</strong>x)62


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LAmbas funciones <strong>de</strong>vuelven un puntero a un tipo TTF_Font. No es necesario queentremos en <strong>de</strong>talle sobre su estructura interna, ya que nos vamos a limitar a pasar estepuntero a las funciones encargadas <strong>de</strong> realizar el pintado <strong>de</strong>l texto. El primer parámetro es elfichero a cargar (una fuente ttf). El segundo es el tamaño <strong>de</strong> la fuente. En la segunda función,tenemos a<strong>de</strong>más un parámetro adicional que es la fuente que queremos usar en caso <strong>de</strong> queel archivo ttf <strong>con</strong>tenga más <strong>de</strong> un tipo <strong>de</strong> letra. Cuando hayamos terminado <strong>de</strong> utilizar la fuentehemos <strong>de</strong> cerrarla <strong>con</strong> la siguiente función.void TTF_Close(TTF_Font *font)La función requiere poca explicación. Simplemente pasamos como parámetro la fuente quequeremos cerrar.El proceso <strong>de</strong> dibujar el texto en una superficie se llama ren<strong>de</strong>r. Hay un total <strong>de</strong> 12funciones <strong>de</strong>dicadas a este fin. Vamos a ver sólo 3 <strong>de</strong> ellas por ser las más habituales.<strong>SDL</strong>_Surface * TTF_Ren<strong>de</strong>rText_Solid(TTF_Font *font, <strong>con</strong>st char *text,<strong>SDL</strong>_Color fg)<strong>SDL</strong>_Surface * TTF_Ren<strong>de</strong>rText_Sha<strong>de</strong>d(TTF_Font *font, <strong>con</strong>st char *text,<strong>SDL</strong>_Color fg, <strong>SDL</strong>_color bg)<strong>SDL</strong>_Surface * TTF_Ren<strong>de</strong>rText_Blen<strong>de</strong>d(TTF_Font *font, <strong>con</strong>st char *text,<strong>SDL</strong>_Color fg)Básicamente son iguales. Lo único que las diferencia es la calidad <strong>con</strong> la que esren<strong>de</strong>rizado en texto. La primera función es la que ofrece menor calidad, y la última laque más. A mayor calidad, mayor tiempo necesita la función para realizar su cometido.Estas funciones <strong>de</strong>vuelven un puntero a una superficie que <strong>con</strong>tiene el texto dibujado. Elparámetro font es un puntero a una fuente ya abierta <strong>con</strong> TTF_OpenFont oTTF_OpenFontIn<strong>de</strong>x. El parámetro text es la ca<strong>de</strong>na <strong>de</strong> texto que queremos imprimiren la pantalla. El parámertro fg es el color <strong>de</strong> la fuente y bg es el color <strong>de</strong> fondo.Obsérvese que los colores se pasan a la función en formato <strong>SDL</strong>_Color. Antes <strong>de</strong> ver unejemplo completo <strong>de</strong>l uso <strong>de</strong> <strong>SDL</strong>_ttf, vamos a ver un par <strong>de</strong> funciones que pue<strong>de</strong>nsernos <strong>de</strong> utilidad.int TTF_GetFontStyle(TTF_Font *font)int TTF_SetFontStyle(TTF_Font *font, int style)Estas funciones permiten <strong>con</strong>ocer y seleccionar el estilo <strong>de</strong> texto que <strong>de</strong>seemos. Losposibles estilos son TTF_STYLE_BOLD para estilo negrita, TTF_STYLE_ITALIC, paraestilo itálica, y TTF_STYLE_UNDERLINE para estilo subrayado. El estado normal esTTF_STYLE_NORMAL.El siguiente programa es un ejemplo <strong>de</strong> uso <strong>de</strong> <strong>SDL</strong>_ttf.63


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L/***************************************************************************Ejemplo4_1(C) 2003 by Alberto Garcia SerranoProgramación <strong>de</strong> vi<strong>de</strong>ojuegos <strong>con</strong> <strong>SDL</strong>***************************************************************************/#inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> "<strong>SDL</strong>_ttf.h"int main(int argc, char *argv[]) {<strong>SDL</strong>_Color bgcolor,fgcolor;<strong>SDL</strong>_Rect rectangulo;<strong>SDL</strong>_Surface *screen,*ttext;TTF_Font *fuente;<strong>con</strong>st char texto[14]="Hola Mundo...";char msg[14];<strong>SDL</strong>_Event event;int done = 0;// Inicializamos <strong>SDL</strong>if (<strong>SDL</strong>_Init(<strong>SDL</strong>_INIT_VIDEO) < 0) {printf("No se pudo iniciar <strong>SDL</strong>: %s\n",<strong>SDL</strong>_GetError());return 1;}// Inicializamos modo <strong>de</strong> vi<strong>de</strong>oscreen = <strong>SDL</strong>_SetVi<strong>de</strong>oMo<strong>de</strong>(640,480,24,<strong>SDL</strong>_HWSURFACE|<strong>SDL</strong>_DOUBLEBUF);if (screen == NULL) {printf("No se pue<strong>de</strong> inicializar el modo gráfico: \n",<strong>SDL</strong>_GetError());return 1;}atexit(<strong>SDL</strong>_Quit);// Inicializamos <strong>SDL</strong>_ttfif (TTF_Init() < 0) {printf("No se pudo iniciar <strong>SDL</strong>_ttf: %s\n",<strong>SDL</strong>_GetError());return 1;}atexit(TTF_Quit);// carga la funte <strong>de</strong> letrafuente = TTF_OpenFont("ariblk.ttf",20);// inicializa colores para el textofgcolor.r=200;fgcolor.g=200;fgcolor.b=10;bgcolor.r=255;bgcolor.g=0;bgcolor.b=0;sprintf(msg,"%s",texto);ttext = TTF_Ren<strong>de</strong>rText_Sha<strong>de</strong>d(fuente,msg,fgcolor,bgcolor);rectangulo.y=100;rectangulo.x=100;rectangulo.w=ttext->w;rectangulo.h=ttext->h;// Usamos color rojo para la transparencia <strong>de</strong>l fondo<strong>SDL</strong>_SetColorKey(ttext,<strong>SDL</strong>_SRCCOLORKEY|<strong>SDL</strong>_RLEACCEL, <strong>SDL</strong>_MapRGB(ttext->format,255,0,0));// Volcamos la superficie a la pantalla<strong>SDL</strong>_BlitSurface(ttext,NULL,screen,&rectangulo);// <strong>de</strong>struimos la fuente <strong>de</strong> letraTTF_CloseFont(fuente);64


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L// liberar superficie<strong>SDL</strong>_FreeSurface(ttext);// Esperamos la pulsación <strong>de</strong> una tecla para salirwhile(done == 0) {while ( <strong>SDL</strong>_PollEvent(&event) ) {if ( event.type == <strong>SDL</strong>_KEYDOWN )done = 1;}}}return 0;65


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LEsto es lo que <strong>de</strong>beríamos ver al ejecutar el programa <strong>de</strong> ejemplo. Lo primero quehacemos es cargar la fuente arial black. Usando la función TTF_Ren<strong>de</strong>rText_Sha<strong>de</strong>,creamos una superficie <strong>con</strong> el color <strong>de</strong> fondo rojo. Antes <strong>de</strong> hacer el blitting a la pantalla,establecemos el color rojo como transparente.<strong>SDL</strong>_imageLa librería <strong>SDL</strong>_image es extremadamente simple, <strong>de</strong> hecho sólo tiene una función.<strong>SDL</strong>_Surface * IMG_Load(<strong>con</strong>st char *file)Esta función tiene exactamente el mismo cometido que <strong>SDL</strong>_LoadBMP, es <strong>de</strong>cir, cargaruna imagen y almacenarla en una superficie. Recor<strong>de</strong>mos, sin embargo, que <strong>SDL</strong>_LoadBMPsólo podía cargar archivos en formato BMP. Afortunadamente, la función IMG_Load pue<strong>de</strong>manejar ficheros en formato BMP, PNM, XPM, LBM, PCX, GIF, JPG, PNG y TGA. Veamos a<strong>SDL</strong>_image en acción <strong>con</strong> un ejemplo.66


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L67


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L/***************************************************************************Ejemplo4_2(C) 2003 by Alberto Garcia SerranoProgramación <strong>de</strong> vi<strong>de</strong>ojuegos <strong>con</strong> <strong>SDL</strong>***************************************************************************/#inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> “<strong>SDL</strong>_image.h”int main(int argc, char *argv[]) {<strong>SDL</strong>_Surface *image, *screen;<strong>SDL</strong>_Rect <strong>de</strong>st;<strong>SDL</strong>_Event event;int done = 0;atexit(<strong>SDL</strong>_Quit);// Iniciar <strong>SDL</strong>if (<strong>SDL</strong>_Init(<strong>SDL</strong>_INIT_VIDEO) < 0) {printf("No se pudo iniciar <strong>SDL</strong>: %s\n",<strong>SDL</strong>_GetError());exit(1);}// Activamos modo <strong>de</strong> vi<strong>de</strong>oscreen = <strong>SDL</strong>_SetVi<strong>de</strong>oMo<strong>de</strong>(640,480,24,<strong>SDL</strong>_HWSURFACE);if (screen == NULL) {printf("No se pue<strong>de</strong> inicializar el modo gráfico: \n",<strong>SDL</strong>_GetError());exit(1);}// Cargamos gráficoimage = IMG_Load("teapot.jpg");if ( image == NULL ) {printf("No pu<strong>de</strong> cargar gráfico: %s\n", <strong>SDL</strong>_GetError());exit(1);}// Definimos don<strong>de</strong> dibujaremos el gráfico// y lo copiamos a la pantalla.<strong>de</strong>st.x = 0;<strong>de</strong>st.y = 0;<strong>de</strong>st.w = image->w;<strong>de</strong>st.h = image->h;<strong>SDL</strong>_BlitSurface(image, NULL, screen, &<strong>de</strong>st);// Mostramos la pantalla<strong>SDL</strong>_Flip(screen);// liberar superficie<strong>SDL</strong>_FreeSurface(image);// Esperamos la pulsación <strong>de</strong> una tecla para salirwhile(done == 0) {while ( <strong>SDL</strong>_PollEvent(&event) ) {if ( event.type == <strong>SDL</strong>_KEYDOWN )done = 1;}}}return 0;68


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L<strong>SDL</strong>_mixerNo voy a volver a recordarte lo tedioso que es trabajar <strong>con</strong> el subsistema <strong>de</strong> audio <strong>de</strong> <strong>SDL</strong>.<strong>SDL</strong>_mixer nos va a facilitar la tarea. Una <strong>de</strong> las mayores ventajas <strong>de</strong> <strong>SDL</strong>_mixer es que seencarga <strong>de</strong> realizar la mezcla <strong>de</strong> los canales <strong>de</strong> audio <strong>de</strong> forma automática. También pue<strong>de</strong>sespecificar tu propia función <strong>de</strong> mezcla, pero esto cae fuera <strong>de</strong>l ámbito <strong>de</strong> este libro.<strong>SDL</strong>_mixer distingue entre la reproducción <strong>de</strong> sonidos y la reproducción <strong>de</strong> la música <strong>de</strong>ljuego (para la que reserva un canal exclusivo). Los formatos válidos para la música son WAV,VOC, MOD, S3M, IT, XM, Ogg Vorbis, MP3 y MIDI.Para po<strong>de</strong>r utilizar <strong>SDL</strong>_mixer, tendrás que inicializar el subsistema <strong>de</strong> audio <strong>de</strong> <strong>SDL</strong>, es<strong>de</strong>cir, al inicializar <strong>SDL</strong> <strong>con</strong> <strong>SDL</strong>_Init, tendrás que incluir el flag <strong>SDL</strong>_INIT_AUDIO. Loprimero que hemos <strong>de</strong> hacer es inicializar la librería.int Mix_OpenAudio(int frequency, Uint16 format, int channels, int chunksize)Los párametros <strong>de</strong> esta función son similares a los <strong>de</strong> la estructura <strong>SDL</strong>_AudioSpec.El primero es freq, que especifica la frecuencia (en Hertzios) <strong>de</strong> reproducción <strong>de</strong>lsample. Valores habituales son 11025 (calidad telefónica), 22050 (calidad radiofónica) o44100 (calidad CD).El parámetro format especifica el formato <strong>de</strong>l sample (bits y tipo). Los posiblesvalores son:AUDIO_U8AUDIO_S8AUDIO_U16 o AUDIO_U16LSBAUDIO_S16 o AUDIO_S16LSBAUDIO_U16MSBAUDIO_S16MSBAUDIO_U16SYSAUDIO_S16SYSSample <strong>de</strong> 8 bits sin signoSample <strong>de</strong> 8 bits <strong>con</strong> signoSample <strong>de</strong> 16 bits sin signo enformato little-endian.Sample <strong>de</strong> 16 bits <strong>con</strong> signo enformato little-endian.Sample <strong>de</strong> 16 bits sin signo enformato big-endian.Sample <strong>de</strong> 16 bits <strong>con</strong> signo enformato big-endian.AUDIO_U16LSB oAUDIO_U16MSB <strong>de</strong>pendiendo <strong>de</strong>lpeso <strong>de</strong> tu sistema (big o littleendían)AUDIO_S16LSB oAUDIO_S16MSB <strong>de</strong>pendiendo <strong>de</strong>lpeso <strong>de</strong> tu sistema (big o littleendían)El parámetro channels indica el número <strong>de</strong> canales <strong>de</strong> audio. Pue<strong>de</strong>n ser 1 para monoy 2 para estereo. Para chunksize, el valor habitual es 4096 según la documentaciónoficial <strong>de</strong> <strong>SDL</strong>. En breve veremos lo que es un chunk.Esta función <strong>de</strong>vuelve –1 si hubo algún error, y 0 en caso <strong>de</strong> que todo vaya bien.La función complementaria a Mix_OpenAudio es la siguiente:void Mix_CloseAudio(void)que recomiendo usar <strong>con</strong> atexit(). La misión <strong>de</strong> esta función, como bién <strong>de</strong>bes saber,es finalizar la librería <strong>SDL</strong>_mixer.69


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LSonidosTe estarás preguntando que es eso <strong>de</strong> un chunk. Básicamente un chunk es un sonidoo efecto sonoro. <strong>SDL</strong>_mixer hace una abstracción y almacena cada efecto en un chunk.No vamos a entrar en <strong>de</strong>talles innecesarios, baste <strong>de</strong>cir que la estructura <strong>de</strong> datos paraalmecenar estos chunks se llama Mix_Chunk. Una vez cargado un sonido en un chunk,la reproducción <strong>de</strong>l sonido se llevará a cabo mediante un canal <strong>de</strong> audio, el cual,podremos especificar manualmente o <strong>de</strong>jar que <strong>SDL</strong>_mixer lo seleccioneautomáticamente. Cada canal pue<strong>de</strong> reproducir simultáneamente un sólo sonido.Afortunadamente se soporta la reproducción <strong>de</strong> múltiples canales simultáneos, o lo quees lo mismo, <strong>de</strong> múltiples sonidos simultáneos.Sin más preámbulos veamos esta bonita función:Mix_Chunk *Mix_LoadWAV(char *file)Sólo hay que indicarle a la función el archivo que queremos cargar. Si hubo algúnerror, la función <strong>de</strong>volverá NULL y en caso <strong>con</strong>trario tendremos un puntero a un tipoMix_Chunk.Cuando ya no necesitemos el sonido, es buena práctica liberar los recursos que utiliza.Lo hacemos <strong>con</strong>:void Mix_FreeChunk(Mix_Chunk *chunk)Basta <strong>con</strong> pasar como parámetro el chunk a liberar.Respecto a los chunks, nos falta por ver una función.int Mix_VolumeChunk(Mix_Chunk *chunk, int volume)Efectivamente, esta función establece el volumen <strong>de</strong> reproducción <strong>de</strong>l sonido. El volumenestá <strong>de</strong>ntro <strong>de</strong>l rango <strong>de</strong> 0 a 128.Ahora que tenemos el sonido cargado en un chunk, po<strong>de</strong>mos reproducirlo mediante uncanal <strong>de</strong> audio. La función encargada <strong>de</strong> tal menester es:int Mix_PlayChannel(int channel, Mix_Chunk *chunk, int loops)Esta función reproduce el sonido apuntado por el puntero chunk en el canal señaladopor el parámetro channel. El parámetro loops indica cuántas veces ha <strong>de</strong> repetirse elsonido. Si sólo queremos reproducirlo una vez, pasamos 0 como valor. Con –1 sereproducirá in<strong>de</strong>finidamente. Si queremos que <strong>SDL</strong>_mixer selecciones el canal <strong>de</strong> formaautomática (es lo mejor, a no ser que quieras utilizar grupos <strong>de</strong> canales) hay que pasar –1 como valor para el canal. Es <strong>con</strong>veniente informar a <strong>SDL</strong>_mixer <strong>de</strong> cuántos canalesqueremos utilizar. Lo hacemos <strong>con</strong>:int Mix_AllocateChannels(int numchannels)Lo normal es que le pasemos tantos canales como sonidos simultáneos queramospo<strong>de</strong>r reproducir. Te a<strong>con</strong>sejo que no te que<strong>de</strong>s corto. Por otro lado, mientras máscanales, más recursos requeríra el audio.A<strong>de</strong>más <strong>de</strong> Mix_PlayChannel, disponemos <strong>de</strong> tres funciones más para reproducirsonidos.int Mix_PlayChannelTimed(int channel, Mix_Chunk *chunk, int loops, intticks)70


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lint Mix_Fa<strong>de</strong>InChannel(int channel, Mix_Chunk *chunk, int loops, int ms)int Mix_Fa<strong>de</strong>InChannelTimed(int channel, Mix_Chunk *chunk, int loops, intms, int ticks)La primera función es idéntica a Mix_PlayChannel, <strong>con</strong> la diferencia <strong>de</strong> que elsonido se reproducirá durante los milisegundos indicados en el parámetro ticks.La segunda función también realiza la reproducción <strong>de</strong>l sonido, pero <strong>con</strong> un efecto <strong>de</strong>fa<strong>de</strong> ascen<strong>de</strong>nte, es <strong>de</strong>cir, el volumen <strong>de</strong>l sonido irá aumentando <strong>de</strong>s<strong>de</strong> 0 hasta el quecorresponda al chunk <strong>de</strong> forma gradual. El tiempo que transcurrirá en ese aumento <strong>de</strong>volumen lo indicamos en <strong>con</strong> el parámetro ms. Este tiempo se expresa en milisegundos.Por último, la tercera función no requiere <strong>de</strong>masiada explicación, ya que es la unión <strong>de</strong>las dos anteriores.Mientras se reproduce un sonido, po<strong>de</strong>mos pausarlo momentaneamente o pararlo. Lassiguientes funciones nos permiten <strong>con</strong>trolar la reproducción.void Mix_Pause(int channel)void Mix_Resume(int channel)int Mix_HaltChannel(int channel)int Mix_Fa<strong>de</strong>OutChannel(int channel, int ms)Los nombres <strong>de</strong> las funciones son bastante <strong>de</strong>scriptivos. Todas aceptan comoparámetro un canal. La primera función pone en estado <strong>de</strong> pausa el canal indicado,mientras que la segunda función reanuda su reproducción. Las dos últimas funcionesparan la reproducción <strong>de</strong>l canal, <strong>con</strong> la diferencia <strong>de</strong> que Mix_HaltChannel para lareproducción en seco y Mix_Fa<strong>de</strong>OutChannel hace un efecto <strong>de</strong> fa<strong>de</strong> <strong>con</strong>trario al <strong>de</strong>Mix_Fa<strong>de</strong>InChannel. Esta última función, a<strong>de</strong>más <strong>de</strong>l canal, acepta un segundoparámetro que es el tiempo en milisegundos que durará el efecto <strong>de</strong> fa<strong>de</strong>.A veces nos pue<strong>de</strong> ser útil <strong>con</strong>ocer el estado <strong>de</strong> un canal <strong>con</strong>creto. Las siguientesfunciones nos ayudan en este cometido.int Mix_Playing(int channel)int Mix_Paused(int channel)La primera función <strong>de</strong>volverá 1 si el canal que pasamos como parámetro se estáreproducciendo actualmente y 0 en el caso <strong>de</strong> que esté en silencio. La segunda función<strong>de</strong>volverá 1 si el canal se está reproduciendo y 0 si está en pausa. El siguiente código <strong>de</strong>ejemplo reproduce un sonido y espera a que el usuario pulse una tecla para terminar.71


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L/***************************************************************************Ejemplo4_3(C) 2003 by Alberto Garcia SerranoProgramación <strong>de</strong> vi<strong>de</strong>ojuegos <strong>con</strong> <strong>SDL</strong>***************************************************************************/#inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> "<strong>SDL</strong>_mixer.h"int main(int argc, char *argv[]) {<strong>SDL</strong>_Surface *screen;<strong>SDL</strong>_Event event;Mix_Chunk *sonido;int done = 0;int canal;atexit(<strong>SDL</strong>_Quit);// Iniciar <strong>SDL</strong>if (<strong>SDL</strong>_Init(<strong>SDL</strong>_INIT_VIDEO|<strong>SDL</strong>_INIT_AUDIO) < 0) {printf("No se pudo iniciar <strong>SDL</strong>: %s\n",<strong>SDL</strong>_GetError());exit(1);}// Activamos modo <strong>de</strong> vi<strong>de</strong>oscreen = <strong>SDL</strong>_SetVi<strong>de</strong>oMo<strong>de</strong>(640,480,24,<strong>SDL</strong>_HWSURFACE);if (screen == NULL) {printf("No se pue<strong>de</strong> inicializar el modo gráfico: %s \n",<strong>SDL</strong>_GetError());exit(1);}// Inicializamos <strong>SDL</strong>_mixerif(Mix_OpenAudio(22050, AUDIO_S16, 2, 4096)) {printf("No se pue<strong>de</strong> inicializar <strong>SDL</strong>_mixer %s\n",Mix_GetError());exit(1);}atexit(Mix_CloseAudio);// Cargamos sonidosonido = Mix_LoadWAV("explosion.wav");if ( sonido == NULL ) {printf("No pu<strong>de</strong> cargar sonido: %s\n", Mix_GetError());exit(1);}// Reproducción <strong>de</strong>l sonido.// Esta función <strong>de</strong>vuelve el canal por el que se reproduce el sonidocanal = Mix_PlayChannel(-1, sonido, 0);// Esperamos la pulsación <strong>de</strong> una tecla para salirwhile(done == 0) {while ( <strong>SDL</strong>_PollEvent(&event) ) {if ( event.type == <strong>SDL</strong>_KEYDOWN )done = 1;}}// liberamos recursosMix_FreeChunk(sonido);}return 0;72


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LMúsicaComo dijimos anteriormente, <strong>SDL</strong>_mixer reserva un canal exclusivo para la música.A<strong>de</strong>más, nos da soporte a múltiples formatos. Para cargar un archivo <strong>de</strong> músicautilizamos la función Mix_LoadMUS.Mix_Music *Mix_LoadMUS(<strong>con</strong>st char *file)Mediante el parámetro file, especificamos el archivo <strong>de</strong> música que queremoscargar. La función nos <strong>de</strong>vuelve un puntero <strong>de</strong> tipo Mix_Music, que es parecido aMix_Chunk, pero que nos permite almacenar música. Para liberar los recursos utilizamosla siguiente función.void Mix_FreeMusic(Mix_Music)La reproducción <strong>de</strong> la musica pue<strong>de</strong> llevarse a cabo mediante alguna <strong>de</strong> las dosfunciones siguientes.int Mix_PlayMusic(Mix_Music *music, int loops)int Mix_Fa<strong>de</strong>InMusic(Mix_Music *music, int loops, int ms)Estas dos funciones son muy similares a Mix_PlayChannel yMix_Fa<strong>de</strong>InChannel. De hecho, la única diferencia es que el primer parámetro es unamúsica en lugar <strong>de</strong> un sonido. El resto <strong>de</strong> parámetros es exactamente igual.El volumen <strong>de</strong> la música se pue<strong>de</strong> establecer <strong>con</strong> la función Mix_VolumeMusic.int Mix_VolumeMusic(int volume)El rango válido para el volumen va <strong>de</strong> 0 a 128.El <strong>con</strong>trol <strong>de</strong> la música pue<strong>de</strong> llevarse a cabo <strong>con</strong> las funciones siguientes.void Mix_PauseMusic()void Mix_ResumeMusic()int Mix_HaltMusic()int Mix_Fa<strong>de</strong>OutMusic(int ms)Estas funciones han <strong>de</strong> serte ya familiares, ya que son similares a las utilizadas para<strong>con</strong>trolar la reproducción <strong>de</strong> sonidos. Las dos primeras nos permiten pausar y reanudar lareproducción <strong>de</strong> la música. Observa que no tiene ningún parámetro ni <strong>de</strong>vuelven ningúnvalor. Las dos útimas funciones paran la reproducción. Si hubo algún problema al intentarparar la reproducción <strong>de</strong>volverán –1, en caso <strong>con</strong>trario <strong>de</strong>volverán el valor 0. La últimafunción a<strong>de</strong>más nos permite realizar la parada <strong>de</strong> la música <strong>de</strong> forma gradual (fa<strong>de</strong>out)teniendo que proveerle los milisegundos que <strong>de</strong>seamos que dure el fa<strong>de</strong>.La música, a diferencia <strong>de</strong> los sonidos simples, permiten mayor <strong>con</strong>trol. A<strong>de</strong>más <strong>de</strong>reproducir, pausar y parar la música po<strong>de</strong>mos situar la reproducción en el lugar que<strong>de</strong>seemos. Contamos <strong>con</strong> dos funciones que nos permiten realizar esta tarea.void Mix_RewindMusic()int Mix_SetMusicPosition(double position)73


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LLa primera función es muy sencilla. Su misisón es volver la reproducción <strong>de</strong> la músicaal principio.La segunda función nos permite situar la posición <strong>de</strong> reproducción en el lugar<strong>de</strong>seado. La posición <strong>de</strong>pen<strong>de</strong>rá directamente <strong>de</strong>l formato <strong>de</strong>l archivo <strong>de</strong> música,pudiendo ser el tiempo en milisegundo para archivos <strong>de</strong> música digitalizada o la posición<strong>de</strong>l patrón o el compás si es un archivo MOD o MIDI.Vamos a terminar el capítulo <strong>de</strong> la música mostrando unas funciones que nos permiten<strong>con</strong>ocer el estado <strong>de</strong>l canal <strong>de</strong> música.Int Mix_PlayingMusic()Int Mix_PausedMusic()Mix_MusicType Mix_GetMusicType(<strong>con</strong>st Mix_Music *music)Las dos primeras son similares a las utilizadas en la reproducción <strong>de</strong> sonidos vista enla sección anterior y no requieren mayor explicación. La tercera sí es nueva paranosotros. Nos permite <strong>con</strong>ocer el formato <strong>de</strong>l archivo musical que le pasamos comoparámetro (o el que se está reproduciendo si music vale NULL). El valor <strong>de</strong>vuelto es <strong>de</strong>tipo Mix_MusicType, y pue<strong>de</strong> tomar los siguientes valores: MUS_WAV, MUS_MOD,MUS_MID, MUS_OGG, MUS_MP3. Si el valor <strong>de</strong>vuelto es MUS_NONE, significa que nohay ninguna música en reproducción. Si el valor <strong>de</strong>vuelto es MUS_CMD, significa que lareproducción no la está llevando a cabo <strong>SDL</strong>_mixer, sino un reproductor externo.El siguiente código <strong>de</strong> ejemplo carga un archivo <strong>de</strong> música en formato MIDI y loreproduce hasta que se pulse una tecla.74


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L/***************************************************************************Ejemplo4_4(C) 2003 by Alberto Garcia SerranoProgramación <strong>de</strong> vi<strong>de</strong>ojuegos <strong>con</strong> <strong>SDL</strong>***************************************************************************/#inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> "<strong>SDL</strong>_mixer.h"int main(int argc, char *argv[]) {<strong>SDL</strong>_Surface *screen;<strong>SDL</strong>_Event event;Mix_Music *musica;int done = 0;int canal;atexit(<strong>SDL</strong>_Quit);// Iniciar <strong>SDL</strong>if (<strong>SDL</strong>_Init(<strong>SDL</strong>_INIT_VIDEO|<strong>SDL</strong>_INIT_AUDIO) < 0) {printf("No se pudo iniciar <strong>SDL</strong>: %s\n",<strong>SDL</strong>_GetError());exit(1);}// Activamos modo <strong>de</strong> vi<strong>de</strong>oscreen = <strong>SDL</strong>_SetVi<strong>de</strong>oMo<strong>de</strong>(640,480,24,<strong>SDL</strong>_HWSURFACE);if (screen == NULL) {printf("No se pue<strong>de</strong> inicializar el modo gráfico: %s \n",<strong>SDL</strong>_GetError());exit(1);}// Inicializamos <strong>SDL</strong>_mixerif(Mix_OpenAudio(22050, AUDIO_S16, 2, 4096)) {printf("No se pue<strong>de</strong> inicializar <strong>SDL</strong>_mixer %s\n",Mix_GetError());exit(1);}atexit(Mix_CloseAudio);// Cargamos la músicamusica = Mix_LoadMUS("musica.mid");if ( musica == NULL ) {printf("No pu<strong>de</strong> cargar musica: %s\n", Mix_GetError());exit(1);}// Reproducción la música.// Esta función <strong>de</strong>vuelve el canal por el que se reproduce la músicacanal = Mix_PlayMusic(musica, -1);// Esperamos la pulsación <strong>de</strong> una tecla para salirwhile(done == 0) {while ( <strong>SDL</strong>_PollEvent(&event) ) {if ( event.type == <strong>SDL</strong>_KEYDOWN )done = 1;}}// paramos la músicaMix_HaltMusic();// liberamos recursosMix_FreeMusic(musica);}return 0;75


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LCapítulo5Sprites: héroes y villanosEnséñame un héroe y te escribiré una tragedia.Francis Scott Fitzgerald.Durante los capitulos siguientes vamos a profundizar en los diferentes aspectos<strong>con</strong>cernientes a la programación <strong>de</strong> vi<strong>de</strong>ojuegos. Ya dispones <strong>de</strong> lasherramientas necesarias para empren<strong>de</strong>r la aventura, así que siéntatecómodamente, flexiona tus <strong>de</strong>dos y prepárate para la diversión. Para ilustrarlas técnicas que se <strong>de</strong>scribirán en los próximos capítulos, vamos a <strong>de</strong>sarrollar unpequeño vi<strong>de</strong>ojuego. Va a ser un juego sin gran<strong>de</strong>s pretensiones, pero que nos va aayudar a enten<strong>de</strong>r los diferentes aspectos que encierra este fascinante mundo. Nuestrojuego va a <strong>con</strong>sistir en lo que se ha dado en llamar shooter en el argot <strong>de</strong> losvi<strong>de</strong>ojuegos. Quizás te resulte más familiar “matamarcianos”. En este tipo <strong>de</strong> juegosmanejamos una nave que tiene que ir <strong>de</strong>struyendo a todos los enemigos que se ponganen su camino. En nuestro caso, el juego va a estar ambientado en la segunda guerramundial, y pilotaremos un avión que tendrá que <strong>de</strong>struir una orda <strong>de</strong> aviones enemigos.El juego es un homenaje al mítico 1942.Figura 5.1. Juego 1942Este capítulo lo vamos a <strong>de</strong>dicar a los sprites. Seguro que alguna vez has jugado aSpace Inva<strong>de</strong>rs. En este juego, una pequeña nave situada en la parte inferior <strong>de</strong> lapantalla dispara a una gran cantidad <strong>de</strong> naves enemigas que van bajando por la pantallahacia el jugador. Pues bien, nuestra nave es un sprite, al igual que los enemigos, lasbalas y los escudos. Po<strong>de</strong>mos <strong>de</strong>cir que un sprite es un elemento gráfico <strong>de</strong>terminado(una nave, un coche, etc...) que tiene entidad propia y sobre la que po<strong>de</strong>mos <strong>de</strong>finir y76


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lmodificar ciertos atributos, como la posición en la pantalla, si es o no visible, etc... Unsprite, pues, tiene capacidad <strong>de</strong> movimiento. Distinguimos dos tipos <strong>de</strong> movimiento en lossprites. El movimiento externo, es <strong>de</strong>cir, el movimiento <strong>de</strong>l sprite por la pantalla, y elmovimiento interno o animación.Para posicionar un sprite en la pantalla hay que especificar sus coor<strong>de</strong>nadas. Es comoel juego <strong>de</strong> los barquitos, en el que para i<strong>de</strong>ntificar un cuadrante hay que indicar una letrapara el eje horizontal (lo llamaremos eje Y) y un número para el eje horizontal (al quellamaremos eje X). En un or<strong>de</strong>nador, un punto en la pantalla se representa <strong>de</strong> formaparecida. Tenemos un eje horizontal (eje X) y otro vertical (eje Y). La esquina superiorizquierda representa el centro <strong>de</strong> coor<strong>de</strong>nadas. La figura siguiente muestra el eje <strong>de</strong>coor<strong>de</strong>nadas en una pantalla <strong>con</strong> una resolución <strong>de</strong> 320 por 200 píxeles.Figura 5.2. Ejes <strong>de</strong> coor<strong>de</strong>nadas.Un punto se i<strong>de</strong>ntifica dando la distancia en el eje X al lateral izquierdo <strong>de</strong> la pantalla yla distancia en el eje Y a la parte superior <strong>de</strong> la pantalla. Las distancias se mi<strong>de</strong>n enpíxeles. Si queremos indicar que un sprite está a 100 píxeles <strong>de</strong> distancia <strong>de</strong>l eje verticaly 150 <strong>de</strong>l eje horizontal, <strong>de</strong>cimos que está en la coor<strong>de</strong>nada (100,150).Imagina ahora que jugamos a un vi<strong>de</strong>juego en el que manejamos a un hombrecillo(piensa en juegos como Pitfall o Renega<strong>de</strong>). Al mover a nuestro hombrecillo, po<strong>de</strong>mosobservar cómo mueve las piernas y los brazos según avanza por la pantalla. Éste es elmovimiento interno o animación. La siguiente figura muestra la animación <strong>de</strong>l sprite <strong>de</strong> ungato.Figura 5.3. Animación <strong>de</strong> un sprite.Otra característica muy interesante <strong>de</strong> los sprites es que nos permiten <strong>de</strong>tectarcolisiones entre ellos. Esta capacidad es realmente interesante si queremos <strong>con</strong>ocercuando nuestro avión ha chocado <strong>con</strong> un enemigo o <strong>con</strong> uno <strong>de</strong> sus misiles.Control <strong>de</strong> spritesVamos a realizar una pequeña librería (y cuando digo pequeña, quiero <strong>de</strong>cir realmentepequeña) para el manejo <strong>de</strong> los sprites. Luego utilizaremos esta librería en nuestro juego.Por supuesto, también pue<strong>de</strong>s utilizarla en tus propios juegos, así como ampliarla, ya quecubrirá sólo los aspectos básicos en lo referente a sprites.Realizaremos la librería en C++ en lugar <strong>de</strong> C. Podríamos haberla creadocompletamente en C, pero <strong>con</strong>si<strong>de</strong>ro <strong>con</strong>veniente introducir, aunque sea poco, algo <strong>de</strong>77


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lprogramación orientada a objetos para que te vayas familiarizando. Observarás que esmucho más fácil reutilizar esta librería en tu propio código gracias a la POO. Consulta elapéndice B <strong>de</strong>dicado a C++ si no estás familiarizado <strong>con</strong> esto <strong>de</strong> los objetos y las clases.Prometo no utilizar más POO en el resto <strong>de</strong>l libro (más <strong>de</strong> un lector lo lamentará, peroprefiero hacer el libro lo más accesible posible).Dotaremos a nuestra librería <strong>con</strong> capacidad para movimiento <strong>de</strong> sprites, animación (unsoporte básico) y <strong>de</strong>tección <strong>de</strong> colisiones.En el diagrama <strong>de</strong> clases <strong>de</strong> nuestra librería (Figura 5.4.) observamos que hay sólodos clases. Si no sabes lo que es un diagrama <strong>de</strong> clases, te bastará saber que cada cajarepresenta a una clase, y que la parte superior <strong>de</strong> la caja <strong>de</strong>scribe las variable miembro<strong>de</strong> la clase mientras que la parte inferior <strong>de</strong>scribe los métodos.La clase CFrame será la encargada <strong>de</strong> <strong>con</strong>tener un gráfico (superficie) in<strong>de</strong>pendiente.Tiene un sólo miembro <strong>de</strong> clase, img, que será la encargada <strong>de</strong> almacenar la superficie,y dos métodos, load para cargar el gráfico y unload para liberar los recursos <strong>de</strong> lasuperficie. La razón por la que hemos utilizado dos clases es porque un mismo gráficopodría formar parte <strong>de</strong> dos sprites diferentes (imagina dos explosiones distintas quecomparten los mismos fotogramas).Figura 5.4. Diagrama <strong>de</strong> clases UML <strong>de</strong> la librería <strong>de</strong> sprites.La clase CSprite representa a un sprite. Observa que tiene dos <strong>con</strong>structores (unejemplo <strong>de</strong>l polimorfismo en acción). Al crear un objeto <strong>de</strong> tipo CSprite po<strong>de</strong>mos indicarel número <strong>de</strong> frames (fotogramas) que tendrá el sprite. Si creamos el objeto sin pasarningún parámetro, el sprite tendrá un sólo frame. Añadiendo múltiples frames, podremoscrear sprites animados. Una vez que ya no necesitamos nuestro sprite, po<strong>de</strong>mos llamaral método finalize() que se encarga <strong>de</strong> llamar al método unload() <strong>de</strong> cada uno <strong>de</strong>los frames que forman parte <strong>de</strong>l sprite, es <strong>de</strong>cir, se encarga <strong>de</strong> liberar los recursosgráficos (superficies). La forma <strong>de</strong> añadir frames a nuestro sprite es mediante la funciónaddframe(). Sólo tenemos que pasar como parametro el frame que queremos añadir ala animación <strong>de</strong>l sprite. Con el método selframe() po<strong>de</strong>mos seleccionar el frameactual, es <strong>de</strong>cir, el que será dibujado. Para crear la sensación <strong>de</strong> movimiento interno,tendremos que ir dibujando nuestro sprite e ir cambiando el frame seleccionado cada vez.Para realizar la animación <strong>de</strong>s<strong>de</strong> nuestro programa, hemos <strong>de</strong> <strong>con</strong>ocer el número <strong>de</strong>frames que tiene el sprite. El método frames() nos informa sobre cuantos frames tieneel sprite actualmente.Profundicemos en los métodos que nos permiten mover el sprite por la pantalla. Conlos métodos setx() y sety() especificamos la posición <strong>de</strong>l sprite en la pantalla. Conlos métodos addx() y addy() po<strong>de</strong>mos indicar un <strong>de</strong>splazamiento <strong>de</strong>l sprite en pantallatomando como punto <strong>de</strong> referencia la situación actual, es <strong>de</strong>cir, addx(5) moverá el78


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lsprite 5 píxeles en el eje a la <strong>de</strong>recha, mientras que addx(-10) lo moverá 10 píxeles ala izquierda.Seguramente, necesitaremos en algún momento <strong>con</strong>ocer información <strong>de</strong>l sprite, comosu posición o su tamaño. Los métodos getx() y gety() nos informarán sobre laposición en la que está el sprite. Los métodos getw() y geth() nos permiten <strong>con</strong>ocer eltamaño <strong>de</strong>l sprite en horizontal y en vertical respectivamente.El método siguiente es draw(), y realizá la función más básica <strong>de</strong> un sprite, es <strong>de</strong>cir,¡dibujarlo! Este método simplemente dibuja el sprite en la posición y <strong>con</strong> el frame actual.Toda la información necesaria (posición, frame, etc...) está almacenada en miembrosprivados <strong>de</strong>l objeto.Por último, el método colision() nos permite comprobar si el sprite ha colisionado<strong>con</strong> otro. Sólo hemos <strong>de</strong> pasarle como parámetro el sprite <strong>con</strong> el que queremoscomprobar la posible colisión.Implementando los spritesLa clase CFrame es la encargada <strong>de</strong> cargar un gráfico que posteriormente será vinculadoal sprite. Sus dos métodos son realmente sencillos.// Método para cargar el framevoid CFrame::load(char *path) {img=<strong>SDL</strong>_LoadBMP(path);}// Asignamos el color transparente al color rojo.<strong>SDL</strong>_SetColorKey(img,<strong>SDL</strong>_SRCCOLORKEY|<strong>SDL</strong>_RLEACCEL,<strong>SDL</strong>_MapRGB(img->format,255,0,0));img=<strong>SDL</strong>_DisplayFormat(img);// Metodo para liberar el framevoid CFrame::unload(){<strong>SDL</strong>_FreeSurface(img);}El método load() carga un archivo en formato BMP mediante la función <strong>SDL</strong>_LoadBMP.Nuestros sprites van a tener como color transarente el rojo (255,0,0). Como es habitual,recurrimos a la función <strong>SDL</strong>_SetColorKey para tal fin. La función <strong>SDL</strong>_DisplayFormat seencarga <strong>de</strong> realizar una <strong>con</strong>versión <strong>de</strong>l gráfico cargado al formato <strong>de</strong> la pantalla. Esto agiliza elproceso blitting al no tener que realizar <strong>con</strong>versiones cada vez que dibujamos el sprite.El método unload() libera los recursos ocupados por el frame utilizando la función<strong>SDL</strong>_FreeSurface.Fácil ¿no? Como pue<strong>de</strong> observar, esta clase no tiene ningún misterio.La clase CSprite es algo más gran<strong>de</strong>, pero igual <strong>de</strong> sencilla. Cuando creamos un sprite,tenemos que indicar el número <strong>de</strong> frames que <strong>con</strong>tendrá (o que podrá <strong>con</strong>tener comomáximo). Necesitamos hacer esto para reservar el espacio necesario en el array <strong>de</strong> frames.Vamos a colocar este código en el <strong>con</strong>structor <strong>de</strong> la clase.CSprite::CSprite(int nf) {sprite=new CFrame[nf];nframes=nf;<strong>con</strong>t=0;}79


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LA<strong>de</strong>más <strong>de</strong> reservar los elementos necesarios en el array <strong>de</strong> frames, inicializamos lavariable nframes que <strong>con</strong>tiene el número <strong>de</strong> frames máximo <strong>de</strong>l sprite y la variable<strong>con</strong>t, que almacenará el número <strong>de</strong> frames que se van añadiendo.Para liberar los recursos haremos uso <strong>de</strong>l método unload() <strong>de</strong> la clase frame. Sólohemos <strong>de</strong> llamar este método por cada frame <strong>de</strong>l sprite.CSprite::finalize() {int i;}for (i=0 ; ih;}Para seleccionar el frame actual, es <strong>de</strong>cir, el que será dibujado en la próxima llamadaal método draw(), utilizamos la variable miembro estado. Esta variable la utilizaremospara almacenar el frame actual o estado <strong>de</strong>l sprite.void CSprite::selframe(int nf) {if (nf


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LEl método frames() simplemente <strong>con</strong>sulta la variable miembro <strong>con</strong>t, que <strong>con</strong>tiene elnúmero <strong>de</strong> frames añadidos hasta el momento.Int frames() {return <strong>con</strong>t;}El método draw() utiliza la función <strong>SDL</strong>_BlitSurface para dibujar el sprite.Tenemos que proveer al método <strong>con</strong> la superficie sobre la que queremos realizar elblitting. De las variables miembro posx y posy obtenemos la posición en la que tenemosque dibujar el sprite, y <strong>de</strong> la variable estado, el frame que <strong>de</strong>bemos dibujar.void CSprite::draw(<strong>SDL</strong>_Surface *superficie) {<strong>SDL</strong>_Rect <strong>de</strong>st;}<strong>de</strong>st.x=posx;<strong>de</strong>st.y=posy;<strong>SDL</strong>_BlitSurface(sprite[estado].img,NULL,superficie,&<strong>de</strong>st);Nos resta dotar a nuestra librería <strong>con</strong> la capacidad <strong>de</strong> <strong>de</strong>tectar colisiones entre sprites.La <strong>de</strong>tección <strong>de</strong> colisiones entre sprites pue<strong>de</strong> enfocarse <strong>de</strong>s<strong>de</strong> varios puntos <strong>de</strong> vista.Imaginemos dos sprites, nuestro avión y un disparo enemigo. En cada vuelta <strong>de</strong>l gameloop tendremos que comprobar si el disparo ha colisionado <strong>con</strong> nuestro avión. Podríamos<strong>con</strong>si<strong>de</strong>rar que dos sprites colisionan cuando alguno <strong>de</strong> sus píxeles visibles (es <strong>de</strong>cir, notransparentes) toca <strong>con</strong> un píxel cualquiera <strong>de</strong>l otro sprite. Esto es cierto al 100%, sinembargo, la única forma <strong>de</strong> hacerlo es comprobando uno por uno los píxeles <strong>de</strong> ambossprites. Evi<strong>de</strong>ntemente esto requiere un gran tiempo <strong>de</strong> computación, y es inviable en lapráctica. En nuestra librería hemos asumido que la parte visible <strong>de</strong> nuestro sprite coinci<strong>de</strong>más o menos <strong>con</strong> las dimensiones <strong>de</strong> la superficie que lo <strong>con</strong>tiene. Si aceptamos esto, yteniendo en cuenta que una superficie tiene forma cuadrangular, la <strong>de</strong>tección <strong>de</strong> unacolisión entre dos sprites se simplifica bastante. Sólo hemos <strong>de</strong> <strong>de</strong>tectar el caso en el quedos cuadrados se solapen.Figura 5.5. Método simple <strong>de</strong> <strong>de</strong>tección <strong>de</strong> colisionesEn la primera figura no existe colisión, ya que no se solapan las superficies (lassuperficies están representadas por el cuadrado que ro<strong>de</strong>a al gráfico). La segunda figuramuestra el principal problema <strong>de</strong> este método, ya que nuestra librería <strong>con</strong>si<strong>de</strong>rará que hahabido colisión cuando realmente no ha sido así. A pesar <strong>de</strong> este pequeño in<strong>con</strong>veniente,este método <strong>de</strong> <strong>de</strong>tección <strong>de</strong> colisiones es el más rápido. Es importante que la superficietenga el tamaño justo para albergar el gráfico. Este es el aspecto que tiene nuestrométodo <strong>de</strong> <strong>de</strong>tección <strong>de</strong> colisiones.int CSprite::colision(CSprite sp) {int w1,h1,w2,h2,x1,y1,x2,y2;w1=getw();h1=geth();w2=sp.getw();h2=sp.geth();// ancho <strong>de</strong>l sprite1// altura <strong>de</strong>l sprite1// ancho <strong>de</strong>l sprite2// altura <strong>de</strong>l sprite281


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lx1=getx();y1=gety();x2=sp.getx();y2=sp.gety();// pos. X <strong>de</strong>l sprite1// pos. Y <strong>de</strong>l sprite1// pos. X <strong>de</strong>l sprite2// pos. Y <strong>de</strong>l sprite2}if (((x1+w1)>x2) && ((y1+h1)>y2) && ((x2+w2)>x1) && ((y2+h2)>y1)) {return TRUE;} else {return FALSE;}Se trata <strong>de</strong> comprobar si el cuadrado (superficie) que <strong>con</strong>tiene el primer sprite, sesolapa <strong>con</strong> el cuadrado que <strong>con</strong>tiene al segundo.Hay otros métodos más precisos que nos permiten <strong>de</strong>tectar colisiones. Voy apresentar un método algo más elaborado. Consiste en dividir el esprite en pequeñassuperficies rectangulares tal y como muestra la próxima figura.Figura 5.6. Un método más elaborado <strong>de</strong> <strong>de</strong>tección <strong>de</strong> colisionesSe pue<strong>de</strong> observar la mayor precisión <strong>de</strong> este método. El proceso <strong>de</strong> <strong>de</strong>tección<strong>con</strong>siste en comprobar si hay colisión <strong>de</strong> alguno <strong>de</strong> los cuadros <strong>de</strong>l primer sprite <strong>con</strong>alguno <strong>de</strong> los cuadrados <strong>de</strong>l segundo utilizando la misma comprobación que hemosutilizado en el primer método para <strong>de</strong>tectar si se solapan dos rectangulos. Se <strong>de</strong>ja comoejercicio al lector la implementación <strong>de</strong> este método <strong>de</strong> <strong>de</strong>tección <strong>de</strong> colisiones (pista:tendrás que añadir a la clase sprite un array <strong>de</strong> elementos <strong>de</strong>l tipo <strong>SDL</strong>_Rect que<strong>con</strong>tenga cada uno <strong>de</strong> los cuadrados). A <strong>con</strong>tinuación se muestra el listado completo <strong>de</strong>nuestra librería. Está compuesta por dos archivos: el archivo <strong>de</strong> cabeceras csprite.h y el<strong>de</strong> implementación csprite.cpp.82


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lcsprite.h#ifn<strong>de</strong>f CSPRITE_H_#<strong>de</strong>fine CSPRITE_H_#<strong>de</strong>fine TRUE 1#<strong>de</strong>fine FALSE 0// CFrame representa un frame in<strong>de</strong>pendiente <strong>de</strong> un sprite.class CFrame {public:<strong>SDL</strong>_Surface *img;void load(char *path);void unload();};// La clase CSprite está formada por un array <strong>de</strong> frames;class CSprite {private:int posx,posy;int estado;int nframes;int <strong>con</strong>t;public:CFrame *sprite;CSprite(int nf);CSprite();void finalize();void addframe(CFrame frame);void selframe(int nf);int frames() {return <strong>con</strong>t;}void setx(int x) {posx=x;}void sety(int y) {posy=y;}void addx(int c) {posx+=c;}void addy(int c) {posy+=c;}int getx() {return posx;}int gety() {return posy;}int getw() {return sprite[estado].img->w;}int geth() {return sprite[estado].img->h;}void draw(<strong>SDL</strong>_Surface *superficie);int colision(CSprite sp);};#endif /* CSPRITE_H_ */83


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lcsprite.cpp#inclu<strong>de</strong> #inclu<strong>de</strong> "csprite.h"// Sprite Class implementationvoid CFrame::load(char *path) {img=<strong>SDL</strong>_LoadBMP(path);}// Asignamos el color transparente al color rojo.<strong>SDL</strong>_SetColorKey(img,<strong>SDL</strong>_SRCCOLORKEY|<strong>SDL</strong>_RLEACCEL, <strong>SDL</strong>_MapRGB(img->format,255,0,0));img=<strong>SDL</strong>_DisplayFormat(img);void CFrame::unload(){<strong>SDL</strong>_FreeSurface(img);}CSprite::CSprite(int nf) {sprite=new CFrame[nf];nframes=nf;<strong>con</strong>t=0;}CSprite::CSprite() {int nf=1;sprite=new CFrame[nf];nframes=nf;<strong>con</strong>t=0;}void CSprite::finalize() {int i;}for (i=0 ; ix1)&&((y2+h2)>y1)) {return TRUE;84


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L}} else {return FALSE;}85


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LUtilizando nuestra libreríaEn el ejemplo5_1 hay un listado completo <strong>de</strong> un programa que utiliza la librería queacabamos <strong>de</strong> <strong>de</strong>sarrollar. Al ejecutarlo verá la siguiente ventana.Po<strong>de</strong>mos manejar el avión <strong>con</strong> las teclas <strong>de</strong>l cursor o <strong>con</strong> el joystick. En la parte superiorhay un avión enemigo parado. Si colisionamos <strong>con</strong> él acaba el programa.86


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L/***************************************************************************Ejemplo5_1(C) 2003 by Alberto Garcia SerranoProgramación <strong>de</strong> vi<strong>de</strong>ojuegos <strong>con</strong> <strong>SDL</strong>***************************************************************************/#inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> "csprite.h"<strong>SDL</strong>_Surface *screen;CFrame fnave;CFrame fmalo;CSprite nave(1);CSprite malo(1);<strong>SDL</strong>_Rect rectangulo;<strong>SDL</strong>_Joystick *joystick;int joyx, joyy;int done=0;// estructura que <strong>con</strong>tiene la información// <strong>de</strong> nuestro aviónstruct minave {int x,y;} jugador;// Estructura que <strong>con</strong>tiene información// <strong>de</strong>l avión enemigostruct naveenemiga {int x,y;} enemigo;// Dibuja los esprites en la pantallavoid DrawScene(<strong>SDL</strong>_Surface *screen) {// borramos el avión dibujado// en el frame anteriorrectangulo.x=nave.getx();rectangulo.y=nave.gety();rectangulo.w=nave.getw();rectangulo.h=nave.geth();<strong>SDL</strong>_FillRect(screen,&rectangulo,<strong>SDL</strong>_MapRGB(screen->format,0,0,0));// dibuja aviónnave.setx(jugador.x);nave.sety(jugador.y);nave.draw(screen);// Dibuja enemigomalo.setx(enemigo.x);malo.sety(enemigo.y);malo.draw(screen);// ¿ha colisionado <strong>con</strong> la nave?if (malo.colision(nave) == TRUE) {done=1;}}// Mostramos todo el frame<strong>SDL</strong>_Flip(screen);// Inicializamos estadosvoid inicializa() {jugador.x=300;jugador.y=300;enemigo.x=100;enemigo.y=100;}void finaliza() {87


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L// finalizamos los spritesnave.finalize();malo.finalize();}// cerramos el joystickif (<strong>SDL</strong>_JoystickOpened(0)) {<strong>SDL</strong>_JoystickClose(joystick);}// Preparamos los espritesint InitSprites() {fnave.load("minave.bmp");nave.addframe(fnave);fmalo.load("nave.bmp");malo.addframe(fmalo);}return 0;int main(int argc, char *argv[]) {<strong>SDL</strong>_Event event;Uint8 *keys;if (<strong>SDL</strong>_Init(<strong>SDL</strong>_INIT_VIDEO|<strong>SDL</strong>_INIT_JOYSTICK) < 0) {printf("No se pudo iniciar <strong>SDL</strong>: %s\n",<strong>SDL</strong>_GetError());return 1;}screen = <strong>SDL</strong>_SetVi<strong>de</strong>oMo<strong>de</strong>(640,480,24,<strong>SDL</strong>_HWSURFACE);if (screen == NULL) {printf("No se pue<strong>de</strong> inicializar el modo gráfico: \n",<strong>SDL</strong>_GetError());return 1;}atexit(<strong>SDL</strong>_Quit);inicializa();InitSprites();while (done == 0) {// dibujamos el frameDrawScene(screen);// <strong>con</strong>sultamos el estado <strong>de</strong>l tecladokeys=<strong>SDL</strong>_GetKeyState(NULL);// <strong>con</strong>sultamos el estado <strong>de</strong>l joystick<strong>SDL</strong>_JoystickUpdate();joyx = <strong>SDL</strong>_JoystickGetAxis(joystick, 0);joyy = <strong>SDL</strong>_JoystickGetAxis(joystick, 1);if ((keys[<strong>SDL</strong>K_UP] || joyy < -10) && (jugador.y > 0)) {jugador.y=jugador.y-(5);}if ((keys[<strong>SDL</strong>K_DOWN] || joyy > 10) && (jugador.y < 460)) {jugador.y=jugador.y+(5);}if ((keys[<strong>SDL</strong>K_LEFT] || joyx < -10) && (jugador.x > 0)) {jugador.x=jugador.x-(5);}if ((keys[<strong>SDL</strong>K_RIGHT] || joyx > 10) && (jugador.x < 620)) {jugador.x=jugador.x+(5);}while (<strong>SDL</strong>_PollEvent(&event)) {if (event.type == <strong>SDL</strong>_QUIT) {done=1;}if (event.type == <strong>SDL</strong>_KEYDOWN || event.type == <strong>SDL</strong>_JOYBUTTONDOWN) {}if (event.key.keysym.sym == <strong>SDL</strong>K_ESCAPE) {done=1;}88


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L}}finaliza();}return 0;89


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LEn este ejemplo hay algunas diferencia <strong>con</strong> respecto a los anteriores. En la funciónmain() hay un bucle while que se parece bastante a un game loop. Después <strong>de</strong> inicializar laposición <strong>de</strong> los aviones mediante la función inicializa() y cargar e inicializar los sprites enla función InitSprites(), entramos en un bucle en el que lo primero que hacemos esllamar a la función DrawScene(), que se encarga <strong>de</strong> dibujar los dos sprites en la pantalla.Acto seguido, <strong>con</strong>sultamos el teclado y el joystick para comprobar si hay que realizar algúnmovimiento en nuestro avión, y en caso afirmativo, aplicamos dicho movimiento. La funciónDrawScene() es realmente la única que se encarga <strong>de</strong> dibujar los sprites. Esta función esllamada en cada iteración <strong>de</strong>l game loop. Cada vez que se pintan los sprites, <strong>de</strong>cimos que seha dibujado un frame. Lo primero que hace es borrar el cuadrado que acupaba el avión en elframe anterior (realmente pintar un cuadro <strong>de</strong> color negro). Si no hiciéramos esto, el avión<strong>de</strong>jaría un feo rastro al moverse (pue<strong>de</strong>s probar a quitar estas líneas y ver el efecto). Losiguiente que hacemos es actualizar las coor<strong>de</strong>nadas <strong>de</strong>l sprite y dibujarlo <strong>con</strong> el métododraw(). Hemos almacenado esta información en dos estructuras que <strong>con</strong>tienen lascoor<strong>de</strong>nadas tanto <strong>de</strong> nuestro avión como <strong>de</strong>l avión enemigo.Despues <strong>de</strong>l pintado, comprobamos la colisión <strong>de</strong> la nave enemiga <strong>con</strong> la nuestra. Si seproduce la colisión, salimos <strong>de</strong>l game loop poniendo la variable done a 1.La clase InitSprites() también es interesante. En ella cargamos los frames que van aformar parte <strong>de</strong> los sprites (1 sólo frame en éste caso), y acto seguido lo añadimos al sprite.90


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LCapítulo6Un Universo <strong>de</strong>ntro <strong>de</strong> tuor<strong>de</strong>nadorCuando quieres realmente una cosa, todo el Universo <strong>con</strong>spira para ayudarte a <strong>con</strong>seguirla.Paulo Coelho.En el segundo capítulo, ya <strong>de</strong>sarrollamos un sencillo juego para el que creamos unpequeño mundo. La mansión <strong>de</strong>l terror. Todo lo aplicado en el capítulo segundo esperfectamente válido. Si quisieramos rehacer el juego, pero esta vez <strong>de</strong> formagráfica y manejando un personaje que se mueve a traves <strong>de</strong> las habitaciones,tendríamos que modificar un poco las estructuras <strong>de</strong> datos. En aquel juego, la<strong>de</strong>scripción <strong>de</strong> las estancias <strong>de</strong> la mansión se presentaba al jugador en forma <strong>de</strong> texto. Ahora,si queremos representar gráficamente las habitaciones, hemos <strong>de</strong> <strong>con</strong>ocer la situación <strong>de</strong> loselementos que la componen. Deberíamos añadir a la estructura <strong>de</strong> datos un array <strong>de</strong> loselementos que componen la habitación. Tendría el aspecto siguiente:// Estructura <strong>de</strong> datos que <strong>de</strong>scribe un// elemento <strong>de</strong> una habitaciónstruct item {<strong>SDL</strong>_Surface *img;int posx;int posy;}// Estructura <strong>de</strong> datos para las habitaciónesstruct habitacion {struct item elemnto[MAX_ELEMENTS];int norte;int sur;int este;int oeste;}La estructura item <strong>de</strong>scribe un elemento individual <strong>de</strong> la habitación, como una cama,una mesa o una silla. Tiene tres elementos en la estructura que la <strong>de</strong>scriben. Por un ladosus dos coor<strong>de</strong>nadas <strong>de</strong>ntro <strong>de</strong> la habitación, y por supuesto, un gráfico <strong>con</strong> el querepresentar el objeto. En la estructura habitacion, sólo hemos hecho un cambio.Hemos sustituido el campo que <strong>con</strong>tenía el texto <strong>de</strong>scriptor <strong>de</strong> la habitación por un array<strong>de</strong> elementos <strong>de</strong> tipo item. Un posible código que mostrara los elementos <strong>con</strong>tenidos enuna habitación podría tener el siguiente aspecto.91


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L<strong>SDL</strong>_Rect <strong>de</strong>st;struct habitacion room[NUM_ROOMS];// Mostrar elementos <strong>de</strong> la habitaciónfor (int i=1 ; i


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LUridiumLas líneas rojas divi<strong>de</strong>n los tiles empleados para <strong>con</strong>struir la imagen.Almacenando y mostrando tilesEn nuestro juego vamos a manejar mapas sencillos. Van a estar compuestos pormosaicos <strong>de</strong> tiles simples. Algunos juegos tienen varios niveles <strong>de</strong> tiles (llamados capas).Por ahora, vamos a almacenar la información sobre nuestro mapa en un array <strong>de</strong> enterostal como este.int mapa[100] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,1,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};Este array representa un array <strong>de</strong> 10x10 tiles. Vamos a utilizar los siguientes tiles,cada uno <strong>con</strong> un tamaño <strong>de</strong> 64x64 píxeles.Tiles utilizados en nuestro juegoPara cargar y manejar los tiles vamos a apoyarnos en la librería <strong>de</strong> manejo <strong>de</strong> spritesque <strong>de</strong>sarrollamos en el capítulo anterior.CFrame tile1;CFrame tile2;CFrame tile3;93


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LCSprite suelo[3];tile1.load("tile0.bmp");suelo[0].addframe(tile1);tile2.load("tile1.bmp");suelo[1].addframe(tile2);tile3.load("tile2.bmp");suelo[2].addframe(tile3);Hemos creado un array <strong>de</strong> tres sprites, uno por cada tile que vamos a cargar.El proceso <strong>de</strong> representación <strong>de</strong>l escenario <strong>con</strong>siste en ir leyendo el mapa y dibujar elsprite leido en la posición correspondiente. El siguiente código realiza este proceso.int i,j,x,y,t;//dibujar escenariofor (i=0 ; i


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L/***************************************************************************Ejemplo6_1(C) 2003 by Alberto Garcia SerranoProgramación <strong>de</strong> vi<strong>de</strong>ojuegos <strong>con</strong> <strong>SDL</strong>***************************************************************************/#inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> "csprite.h"// Mapa <strong>de</strong> tiles// cada tile tiene 64x64 píxeles// una pantalla tiene 10x10 tilesint mapa[100]= {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,1,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};<strong>SDL</strong>_Surface *screen;CFrame fnave;CFrame fmalo;CFrame tile1;CFrame tile2;CFrame tile3;CSprite nave(1);CSprite malo(1);CSprite suelo[3];<strong>SDL</strong>_Rect rectangulo;<strong>SDL</strong>_Joystick *joystick;int joyx, joyy;int done=0;// estructura que <strong>con</strong>tiene la información// <strong>de</strong> nuestro aviónstruct minave {int x,y;} jugador;// Estructura que <strong>con</strong>tiene información// <strong>de</strong>l avión enemigostruct naveenemiga {int x,y;} enemigo;// Dibuja los esprites en la pantallavoid DrawScene(<strong>SDL</strong>_Surface *screen) {int i,j,x,y,t;//dibujar escenariofor (i=0 ; i


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L// Dibuja enemigomalo.setx(enemigo.x);malo.sety(enemigo.y);malo.draw(screen);// ¿ha colisionado <strong>con</strong> la nave?if (malo.colision(nave) == TRUE) {done=1;}}// Mostramos todo el frame<strong>SDL</strong>_Flip(screen);// Inicializamos estadosvoid inicializa() {jugador.x=300;jugador.y=300;enemigo.x=100;enemigo.y=100;}void finaliza() {// finalizamos los spritesnave.finalize();malo.finalize();suelo[0].finalize();suelo[1].finalize();suelo[2].finalize();}// cerramos el joystickif (<strong>SDL</strong>_JoystickOpened(0)) {<strong>SDL</strong>_JoystickClose(joystick);}// Preparamos los espritesint InitSprites() {fnave.load("minave.bmp");nave.addframe(fnave);fmalo.load("nave.bmp");malo.addframe(fmalo);tile1.load("tile0.bmp");suelo[0].addframe(tile1);tile2.load("tile1.bmp");suelo[1].addframe(tile2);tile3.load("tile2.bmp");suelo[2].addframe(tile3);}return 0;int main(int argc, char *argv[]) {<strong>SDL</strong>_Event event;Uint8 *keys;if (<strong>SDL</strong>_Init(<strong>SDL</strong>_INIT_VIDEO|<strong>SDL</strong>_INIT_JOYSTICK) < 0) {printf("No se pudo iniciar <strong>SDL</strong>: %s\n",<strong>SDL</strong>_GetError());return 1;}screen = <strong>SDL</strong>_SetVi<strong>de</strong>oMo<strong>de</strong>(640,480,24,<strong>SDL</strong>_HWSURFACE);if (screen == NULL) {printf("No se pue<strong>de</strong> inicializar el modo gráfico: \n",<strong>SDL</strong>_GetError());96


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L}return 1;atexit(<strong>SDL</strong>_Quit);inicializa();InitSprites();while (done == 0) {// dibujamos el frameDrawScene(screen);// <strong>con</strong>sultamos el estado <strong>de</strong>l tecladokeys=<strong>SDL</strong>_GetKeyState(NULL);// <strong>con</strong>sultamos el estado <strong>de</strong>l joystick<strong>SDL</strong>_JoystickUpdate();joyx = <strong>SDL</strong>_JoystickGetAxis(joystick, 0);joyy = <strong>SDL</strong>_JoystickGetAxis(joystick, 1);if ((keys[<strong>SDL</strong>K_UP] || joyy < -10) && (jugador.y > 0)) {jugador.y=jugador.y-(5);}if ((keys[<strong>SDL</strong>K_DOWN] || joyy > 10) && (jugador.y < 460)) {jugador.y=jugador.y+(5);}if ((keys[<strong>SDL</strong>K_LEFT] || joyx < -10) && (jugador.x > 0)) {jugador.x=jugador.x-(5);}if ((keys[<strong>SDL</strong>K_RIGHT] || joyx > 10) && (jugador.x < 620)) {jugador.x=jugador.x+(5);}while (<strong>SDL</strong>_PollEvent(&event)) {if (event.type == <strong>SDL</strong>_QUIT) {done=1;}if (event.type == <strong>SDL</strong>_KEYDOWN || event.type == <strong>SDL</strong>_JOYBUTTONDOWN) {}if (event.key.keysym.sym == <strong>SDL</strong>K_ESCAPE) {done=1;}}}finaliza();}return 0;97


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LEl resultado <strong>de</strong> ejecutar este código <strong>de</strong> ejemplo se muestra en la próxima figura. Elprograma hace exactamente lo mismo que el <strong>de</strong>l anterior capítulo, es <strong>de</strong>cir, podrás moverel avión por la pantalla hasta colisionar <strong>con</strong> el avión enemigo. La diferencia es que ahoratenemos un bonito fondo en lugar <strong>de</strong> aquel color negro.Nota que hemos eliminado el código necesario para borrar la anterior posición <strong>de</strong>lavión en su movimiento. Al redibujar todo el fondo cada vez, ya no es necesario, ya quese actualiza la pantalla completa.Diseñando el mapaEl código anterior es válido siempre que no se <strong>de</strong>n dos <strong>con</strong>diciones. La primera es que elmapa no ocupe la pantalla, y la segunda es que el mapa no sea <strong>de</strong>masiado complejo. Imaginatener que hacer inmensos y complejos arrays <strong>de</strong> enteros para representa un gran mapa.Habitualmente, los programadores <strong>de</strong> vi<strong>de</strong>ojuegos usan lo que se llaman editores <strong>de</strong>mapas. Un editor <strong>de</strong> mapas nos permite diseñar nuestro mapa <strong>de</strong> forma visual. Así vamosviendo como va quedando mientras lo diseñamos. Hay dos opciones, o <strong>de</strong>sarrollar uno propio(no es <strong>de</strong>masiado complejo, aunque lo parezca) o utilizar alguno existente. Por ahoraoptaremos por la segunda opción. Hay algunos buenos programas editores <strong>de</strong> mapa, peronosotros vamos a utilizar en este libro uno llamado Mappy, que es sencillo, flexible, y sobretodo, gratuito. Pue<strong>de</strong>s <strong>de</strong>scargarlo <strong>de</strong> la siguiente dirección web:http://www.tilemap.co.uk/mappy.php .Este programa permite <strong>con</strong>figurar el formato <strong>de</strong>l fichero que genera, <strong>con</strong> lo que nos sirveperfectamente. El formato que vamos a utilizar no pue<strong>de</strong> ser más sencillo. Es un ficherobinario (ojo, no <strong>de</strong> texto) en el que cada byte representa una posición <strong>con</strong>secutiva <strong>de</strong>l mapa(un elemento <strong>de</strong>l array en el anterior programa). Nuestro mapa va a tener 10x40 tiles, es <strong>de</strong>cir,10 tiles en horizontal y 40 en vertical. Por lo tanto, si cada tile está representado por un byte, elarchivo para el mapa ocupará exactamente 400 bytes. Para que Mappy pueda generar estetipo <strong>de</strong> archivo hay que <strong>con</strong>figurarlo editando en archivo MAPWIN.INI. Más <strong>con</strong>cretamentehay que cambiar la línea maptype="LW4H4A4-1" por ésta otra maptype="LA1-1". No voy a98


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lentrar en <strong>de</strong>talle sobre este fichero <strong>de</strong> <strong>con</strong>figuración, pero si tienes curiosidad pue<strong>de</strong>s<strong>con</strong>sultar el manual <strong>de</strong> Mappy.Veamos paso a paso como creamos un mapa para nuestro juego. Ejecuta el programa yseleciona New Map... <strong>de</strong>l menu file. Aparecerá un diálogo preguntandote si quieres utilizar eldiálogo avanzado o el sencillo. Pulsa No para usar el sencillo.Introduce los valores que ves en la figura, es <strong>de</strong>cir, 64 pixels tanto <strong>de</strong> anchura como <strong>de</strong>altura para los tiles. En la anchura <strong>de</strong> tiles ponemos 10 y en la altura 40. Seguidamente pulsaOK.Ahora vamos a cargar los tiles. Lo mejor es nombrar los tiles como tile1.bmp, tile2.bmp,tileN.bmp, etc... Así luego no tendremos problema <strong>con</strong> el or<strong>de</strong>n <strong>de</strong> los tiles. Para cargar lostiles en el programa seleccionamos import... <strong>de</strong>l menu file. Se nos abrirá un diálogo paraseleccionar un archivo. Cargamos el primer tile. Nos aparecerá un dialogo que reza: “Make allimported graphics into NEW block structures?”. Respon<strong>de</strong>mos que sí y repetimos esteproceso para todos los tiles <strong>de</strong>l juego. Los tiles que se van cargando se van situando en laventana <strong>de</strong>l laterar <strong>de</strong>recho. A partir <strong>de</strong> ahí sólo tienes que seleccionar el tile que quieresdibujar y situarlo en la ventana central en el lugar <strong>de</strong>seado.Edición <strong>de</strong> un mapa <strong>con</strong> Mappy99


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LUna vez que hemos terminado el diseño <strong>de</strong>l mapa, hemos <strong>de</strong> grabarlo selecionando saveas... <strong>de</strong>l menú file. Po<strong>de</strong>mos grabar el archivo <strong>con</strong> dos extensiones diferentes: .FMP y .MAP.El formato .FMP es el formato interno <strong>de</strong> Mappy, y el formato .MAP es nuestro formato (el que<strong>de</strong>finimos al editar el archivo MAPWIN.INI). Si lo grabamos exclusivamente en nuestroformato, es <strong>de</strong>cir .MAP, Mappy no será capaz <strong>de</strong> volver a leerlo, así que lo mejor es guardarloen formato .FMP y seguidamente volver a guardarlo en formato .MAP.Cargar el mapa <strong>de</strong>s<strong>de</strong> nuestro juego es sumamente sencillo. Las siguientes líneas realizanesta tarea.#<strong>de</strong>fine MAXMAP 400char mapa[401];FILE *f;// Carga <strong>de</strong>l mapaif((f=fopen("map.map","r")) != NULL) {c=fread(mapa,MAXMAP,1,f);fclose(f);}El array mapa, <strong>con</strong>tiene ahora toda la información <strong>de</strong>l mapa.ScrollingSe hace evi<strong>de</strong>nte que un mapa <strong>de</strong> 10x40 tiles no cabe en la pantalla. Lo lógico es quesegún se mueva nuestro avión por el escenario, el mapa avance en la misma dirección. Este<strong>de</strong>splazamiento <strong>de</strong>l escenario se llama scrolling. Si nuestro avión avanza un tile en la pantalla,hemos <strong>de</strong> dibujar el escenario, pero <strong>de</strong>splazado (offset) un tile. Desafortunamente, haciendoesto exclusivamente, veríamos como el escenario va dando saltitos, y lo que buscamos es unscroll suave. El siguiente fragmento <strong>de</strong> código cumple este cometido.#<strong>de</strong>fine MAXMAP 400indice=MAXMAP-100;indice_in=0;// movimiento <strong>de</strong>l scenario (scroll)indice_in+=2;if (indice_in>=64) {indice_in=0;indice-=10;}if (indice


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L}}suelo[t].sety(y);suelo[t].draw(screen);Este código está basado en el ejemplo6_1. Como diferencia en<strong>con</strong>tramos dos nuevasvariables. La variable indice <strong>con</strong>tiene el <strong>de</strong>splazamiento (en bytes) a partir <strong>de</strong>l cual secomienza a dibujar el mapa. La variable indice_in, es la encargada <strong>de</strong> realizar el scrollfino. Al dibujar el tile, se le suma a la coor<strong>de</strong>nada Y el valor <strong>de</strong> la variable indice_in,que va aumentando en cada iteración. Cuando esta variable alcanza el valor 64, es <strong>de</strong>cir,la altura <strong>de</strong>l tile, ponemos la variable a 0 y restamos 10 a la variable indice, o lo que eslo mismo, el offset a partir <strong>de</strong>l que dibujamos el mapa. Se realiza una resta porque lalectura <strong>de</strong>l mapa la hacemos <strong>de</strong> abajo a arriba (<strong>de</strong>l último byte al primero). Recuerda queel mapa tiene 10 tiles <strong>de</strong> anchura. Es por eso que restamos 10 a la variable indice.Una vez que llegamos al principio <strong>de</strong>l mapa, comenzamos <strong>de</strong> nuevo por el final, <strong>de</strong> formaque se va repitiendo el mismo mapa <strong>de</strong> forma in<strong>de</strong>finida. Ten en cuenta que las primeras10 filas <strong>de</strong>l mapa tienen que ser iguales que las 10 últimas si no quieres notar un molestosalto cuando recomienza el recorrido <strong>de</strong>l mapa. En el ejemplo6_2 vemos el códigocompleto.101


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L/***************************************************************************Ejemplo6_2(C) 2003 by Alberto Garcia SerranoProgramación <strong>de</strong> vi<strong>de</strong>ojuegos <strong>con</strong> <strong>SDL</strong>***************************************************************************/#inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> "csprite.h"#<strong>de</strong>fine MAXMAP 400<strong>SDL</strong>_Surface *screen;CFrame fnave;CFrame fmalo;CFrame tile1;CFrame tile2;CFrame tile3;CSprite nave(1);CSprite malo(1);CSprite suelo[3];<strong>SDL</strong>_Rect rectangulo;<strong>SDL</strong>_Joystick *joystick;char mapa[401];int joyx, joyy;int done=0;int indice, indice_in;FILE *f;// estructura que <strong>con</strong>tiene la información// <strong>de</strong> nuestro aviónstruct minave {int x,y;} jugador;// Estructura que <strong>con</strong>tiene información// <strong>de</strong>l avión enemigostruct naveenemiga {int x,y;} enemigo;// Dibuja el escenariovoid DrawScene(<strong>SDL</strong>_Surface *screen) {int i,j,x,y,t;// movimiento <strong>de</strong>l scenario (scroll)indice_in+=2;if (indice_in>=64) {indice_in=0;indice-=10;}if (indice


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L// dibuja aviónnave.setx(jugador.x);nave.sety(jugador.y);nave.draw(screen);// Dibuja enemigomalo.setx(enemigo.x);malo.sety(enemigo.y);malo.draw(screen);// ¿ha colisionado <strong>con</strong> la nave?if (malo.colision(nave) == TRUE) {done=1;}}// Mostramos todo el frame<strong>SDL</strong>_Flip(screen);// Inicializamos estadosvoid inicializa() {int c;jugador.x=300;jugador.y=300;enemigo.x=100;enemigo.y=100;indice=MAXMAP-100;indice_in=0;}// Carga <strong>de</strong>l mapaif((f=fopen("map.map","r")) != NULL) {c=fread(mapa,MAXMAP,1,f);fclose(f);}void finaliza() {// finalizamos los spritesnave.finalize();malo.finalize();suelo[0].finalize();suelo[1].finalize();suelo[2].finalize();}// cerramos el joystickif (<strong>SDL</strong>_JoystickOpened(0)) {<strong>SDL</strong>_JoystickClose(joystick);}// Preparamos los espritesint InitSprites() {fnave.load("minave.bmp");nave.addframe(fnave);fmalo.load("nave.bmp");malo.addframe(fmalo);tile1.load("tile0.bmp");suelo[0].addframe(tile1);tile2.load("tile1.bmp");suelo[1].addframe(tile2);tile3.load("tile2.bmp");suelo[2].addframe(tile3);103


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L}return 0;int main(int argc, char *argv[]) {<strong>SDL</strong>_Event event;Uint8 *keys;if (<strong>SDL</strong>_Init(<strong>SDL</strong>_INIT_VIDEO|<strong>SDL</strong>_INIT_JOYSTICK) < 0) {printf("No se pudo iniciar <strong>SDL</strong>: %s\n",<strong>SDL</strong>_GetError());return 1;}screen = <strong>SDL</strong>_SetVi<strong>de</strong>oMo<strong>de</strong>(640,480,24,<strong>SDL</strong>_HWSURFACE);if (screen == NULL) {printf("No se pue<strong>de</strong> inicializar el modo gráfico: \n",<strong>SDL</strong>_GetError());return 1;}atexit(<strong>SDL</strong>_Quit);inicializa();InitSprites();while (done == 0) {// dibujamos el frameDrawScene(screen);// <strong>con</strong>sultamos el estado <strong>de</strong>l tecladokeys=<strong>SDL</strong>_GetKeyState(NULL);// <strong>con</strong>sultamos el estado <strong>de</strong>l joystick<strong>SDL</strong>_JoystickUpdate();joyx = <strong>SDL</strong>_JoystickGetAxis(joystick, 0);joyy = <strong>SDL</strong>_JoystickGetAxis(joystick, 1);if ((keys[<strong>SDL</strong>K_UP] || joyy < -10) && (jugador.y > 0)) {jugador.y=jugador.y-(5);}if ((keys[<strong>SDL</strong>K_DOWN] || joyy > 10) && (jugador.y < 460)) {jugador.y=jugador.y+(5);}if ((keys[<strong>SDL</strong>K_LEFT] || joyx < -10) && (jugador.x > 0)) {jugador.x=jugador.x-(5);}if ((keys[<strong>SDL</strong>K_RIGHT] || joyx > 10) && (jugador.x < 620)) {jugador.x=jugador.x+(5);}while (<strong>SDL</strong>_PollEvent(&event)) {if (event.type == <strong>SDL</strong>_QUIT) {done=1;}if (event.type == <strong>SDL</strong>_KEYDOWN || event.type == <strong>SDL</strong>_JOYBUTTONDOWN) {}if (event.key.keysym.sym == <strong>SDL</strong>K_ESCAPE) {done=1;}}}finaliza();}return 0;104


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LCapítulo7Enemigos, disparos yexplosionesPerdona siempre a tu enemigo. No hay nada que le enfurezca más.Oscar Wil<strong>de</strong>.En los ejemplos <strong>de</strong>l anterior capítulo, nuestro avión enemigo está inmóvil. Eneste capítulo vamos a hacer que nuestros enemigos cobren vida propia.Existen multiples técnicas relacionadas <strong>con</strong> la inteligencia artificial (IA) y queson ampliamente utilizadas en programación <strong>de</strong> juegos. La IA es un tópico losuficientemente extenso como para rellenar varios libros <strong>de</strong>l tamaño <strong>de</strong>l quetienes ahora entre manos. Aún así, exploraremos algunas sencillas técnicas que nospermitiran dotar a los aviones enemigos <strong>de</strong> nuestro juego <strong>de</strong> una chispa vital. Tambiénvamos a hacer diaparar a los aviones enemigos y al nuestro, explosiones incluidas.Tipos <strong>de</strong> inteligenciaHay, al menos, tres ten<strong>de</strong>ncias <strong>de</strong>ntro <strong>de</strong>l campo <strong>de</strong> la inteligencia artificial.- Re<strong>de</strong>s neuronales- Algoritmos <strong>de</strong> búsqueda- Sistemas basados en <strong>con</strong>ocimientoSon tres enfoque diferentes que tratan <strong>de</strong> buscar un fin común. No hay un enfoque mejorque los <strong>de</strong>más, la elección <strong>de</strong> uno u otro <strong>de</strong>pen<strong>de</strong> <strong>de</strong> la aplicación.Una red neuronal trata <strong>de</strong> simular el funcionamiento <strong>de</strong>l cerebro humano. El elementobásico <strong>de</strong> una red neuronal es la neurona. En una red neuronal, un <strong>con</strong>junto <strong>de</strong> neuronastrabajan al unísono para resolver un problema. Al igual que un niño tiene que apren<strong>de</strong>r alnacer, una red <strong>de</strong> neuronas artificial tiene que ser entrenada para po<strong>de</strong>r realizar su cometido.Este aprendizaje pue<strong>de</strong> ser supervisado o no supervisado, <strong>de</strong>pendiendo si hace faltaintervención humana para entrenar a la red <strong>de</strong> neuronas. Este entrenamiento se realizanormalmente mediante ejemplos. La aplicación <strong>de</strong> las re<strong>de</strong>s neuronales es efectiva encampos en los que no existen algoritmos <strong>con</strong>cretos que resuelvan un problema o sean<strong>de</strong>masiado complejos <strong>de</strong> computar. Don<strong>de</strong> más se aplican es en problemas <strong>de</strong>re<strong>con</strong>ocimiento <strong>de</strong> patrones y pronósticos.105


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LEl segundo enfoque es el <strong>de</strong> los algoritmos <strong>de</strong> búsqueda. Es necesario un <strong>con</strong>ocimientorazonable sobre estructuras <strong>de</strong> datos como árboles y grafos. Una <strong>de</strong> las aplicacionesinteresantes, sobre todo para vi<strong>de</strong>ojuegos, es la busqueda <strong>de</strong> caminos (pathfinding).Seguramente has jugado a juegos <strong>de</strong> estrategia como Starcraft, Age of Empires y otros <strong>de</strong>lestilo. Pue<strong>de</strong>s observar que cuando das la or<strong>de</strong>n <strong>de</strong> movimiento a uno <strong>de</strong> los pequeñospersonajes <strong>de</strong>l juego, éste se dirige al punto indicado esquivando los obstáculos queencuantra en su camino. Este algoritmo <strong>de</strong> búsqueda en grafos es llamado A*. Tomemoscomo ejemplo el mapa <strong>de</strong> la mansión <strong>de</strong>l capítulo 2. Suponiendo que el jugardor está en lahabitación 1, éste es el arbol <strong>de</strong> los posible caminos que pue<strong>de</strong> tomar para escapar <strong>de</strong> lamansión.147 52 6 83 9Figura 7.1. Arbol <strong>de</strong> los posibles caminos <strong>de</strong>ntro <strong>de</strong> mapa <strong>de</strong> juegoCada círculo representa un nodo <strong>de</strong>l arbol. El número que encierra es el número <strong>de</strong>habitación. Si quisieramos en<strong>con</strong>trar la salida, usaríamos un algoritmo <strong>de</strong> búsqueda(como por ejemplo A*) para recorrer todos los posibles caminos y quedarnos <strong>con</strong> el quenos interesa. El objetivo es buscar el nodo 8, que es el que tiene la puerta <strong>de</strong> salida. Elcamino <strong>de</strong>s<strong>de</strong> la habitación 1 es: 1 4 5 8. El algoritmo A* a<strong>de</strong>más <strong>de</strong> en<strong>con</strong>trar el nodoobjetivo, nos asegura que es el camino más corto. No vamos a entrar en más <strong>de</strong>talle, yaque cae fuera <strong>de</strong> las pretensiones <strong>de</strong> este libro profundizar en la implementación <strong>de</strong>algoritmos <strong>de</strong> búsqueda.Por último, los sistemas basados en reglas se sirven, valga la redundancia, <strong>de</strong><strong>con</strong>juntos <strong>de</strong> reglas y hechos. Los hechos son informaciones relativas al problema y a suuniverso. Las reglas son precisamente eso, reglas aplicables a los elementos <strong>de</strong>luniverso y que permiten llegar a <strong>de</strong>ducciones simples. Veamos un ejemplo:Hechos: Las moscas tienen alas.Las hormigas no tienen alas.Reglas: Si (x) tiene alas, entonces vuela.Un sistema basado en <strong>con</strong>ocimiento, <strong>con</strong> estas reglas y estos hechos es capaz <strong>de</strong><strong>de</strong>ducir dos cosas. Que las moscas vuelan y que las hormigas no.106


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LSi (la mosca) tiene alas, entonces vuela.Uno <strong>de</strong> los problemas <strong>de</strong> los sistemas basados en <strong>con</strong>ocimiento es que pue<strong>de</strong>n ocurrirsituaciones como estas.Si (la gallina) tiene alas, entonces vuela.Desgraciadamente para las gallinas, éstas no vuelan. Pue<strong>de</strong>s observar que la<strong>con</strong>strucción para comprobar reglas es muy similar a la <strong>con</strong>strucción IF/THEN <strong>de</strong> loslenguajes <strong>de</strong> programación.Comportamientos y máquinas <strong>de</strong> estadoUna máquina <strong>de</strong> estados está compuesta por por una serie <strong>de</strong> estados y una serie <strong>de</strong>reglas que indican en que casos se pasa <strong>de</strong> un estado a otro. Estas máquinas <strong>de</strong> estados nosperniten mo<strong>de</strong>lar comportamientos en los personajes y elementos <strong>de</strong>l juego. Vamos ailustrarlo <strong>con</strong> un ejemplo. Imagina que en el juego <strong>de</strong> la mansión <strong>de</strong>l terror <strong>de</strong>l capítulo 2 hayun zombie. El pobre no tiene una inteligencia <strong>de</strong>masiado <strong>de</strong>sarrollada y sólo es capaz <strong>de</strong>andar hasta que se pega <strong>con</strong>tra la pared. Cuando suce<strong>de</strong> esto, lo único que sabe hacer esgirar 45 grados a la <strong>de</strong>recha y <strong>con</strong>tinuar andando. Vamos a mo<strong>de</strong>lar el comportamiento <strong>de</strong>lzombie <strong>con</strong> una máquina <strong>de</strong> estados. Para ello primero tenemos que <strong>de</strong>finir los posiblesestados.- Andando (estado 1)- Girando (estado 2)Las reglas que hacen que el zombie cambie <strong>de</strong> un estado a otro son las siguientes.- Si está en el estado 1 y choca <strong>con</strong> la pared pasa al estado 2.- Si esta en el estado 2 y ha girado 45 grados pasa al estado 1.Con estos estados y estas reglas po<strong>de</strong>mos <strong>con</strong>struir el grafo que representa a nuestramáquina <strong>de</strong> estados.Choca <strong>con</strong> la paredAndando (1) Girando (2)Gira 45 gradosFigura 7.2.La implementación <strong>de</strong> la máquina <strong>de</strong> estado es muy sencilla. La siguiente función simula elcomportamiento <strong>de</strong>l zombie.107


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lint angulo;void zombie() {int state, angulo_tmp;// estado 1if (state == 1) {andar();if (colision()) {state=2;angulo_tmp=45;}}}// estado 2if (state == 2) {angulo_tmp=angulo_tmp-1;angulo=angulo+1;if (angulo_tmp


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lstruct disparo {int x,y;} bala[MAXBALAS+1];La <strong>con</strong>stante MAXBALAS <strong>con</strong>tiene el número máximo <strong>de</strong> balas simultáneas que nuestroavión pue<strong>de</strong> disparar. Por lo tanto, tenemos un array <strong>de</strong> balas listas para utilizar. Cada vezque pulsamos la tecla <strong>de</strong> disparo, hemos <strong>de</strong> utilizar uno <strong>de</strong> los elementos <strong>de</strong> este array paraalmacenar la información <strong>de</strong>l disparo, por lo tanto la primera tarea es en<strong>con</strong>trar un elementolibre (que no se esté usando) <strong>de</strong>ntro <strong>de</strong>l array. Una vez en<strong>con</strong>trado, hemos <strong>de</strong> inicializar eldisparo <strong>con</strong> la posición <strong>de</strong>l avión. La función creadisparo() realiza este trabajo.void creadisparo() {int libre=-1;// ¿Hay alguna bala libre?for (int i=0 ; i=0) {bala[libre].x=nave.getx();bala[libre].y=nave.gety()-15;}Para saber si una bala está o no libre, usaremos su coor<strong>de</strong>nada X. Si vale 0 asumiremosque está libre, si no, es un elemento <strong>de</strong>l array en uso y tendremos que buscar otro.En cada vuelta <strong>de</strong>l game loop, hemos <strong>de</strong> dibujar los disparos activos si es que los hubiera,el código siguiente se encarga <strong>de</strong> ello.void muevebalas() {int i;for (i=0 ; i


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LNos resta dibujar los disparos. De nuevo, necesitamos recorrer todo el array <strong>de</strong> disparos ydibujar los que están activos.// dibuja disparosfor (i=0 ; i=7) {exp.activo=0;done=1;}}La explosión ha <strong>de</strong> activarse cuando el sprite <strong>de</strong>l avión enemigo colisione <strong>con</strong> unabala. Al producirse el impacto, hemos <strong>de</strong> eliminar el avión enemigo para que no sigadibujándose y activar la explosión. Las coor<strong>de</strong>nadas <strong>de</strong> la explosión serán las mismasque las que tenía la nave enemiga.// ¿ha colisionado el disparo <strong>con</strong> la nave?if (malo.colision(mibala) == TRUE) {enemigo.estado=0;exp.activo=1;exp.nframe=1;110


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L}exp.x=enemigo.x;exp.y=enemigo.y;Tienes el código completo en el programa ejemplo7_1.Figura 7.4.111


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L/***************************************************************************Ejemplo7_1(C) 2003 by Alberto Garcia SerranoProgramación <strong>de</strong> vi<strong>de</strong>ojuegos <strong>con</strong> <strong>SDL</strong>***************************************************************************/#inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> "csprite.h"#<strong>de</strong>fine MAXMAP 400#<strong>de</strong>fine MAXBALAS 8<strong>SDL</strong>_Surface *screen;CFrame fnave;CFrame fmalo;CFrame tile1;CFrame tile2;CFrame tile3;CFrame labala;CFrame ex1;CFrame ex2;CFrame ex3;CFrame ex4;CFrame ex5;CFrame ex6;CFrame ex7;CSprite nave(1);CSprite malo(1);CSprite suelo[3];CSprite mibala(1);CSprite explo<strong>de</strong>(8);<strong>SDL</strong>_Rect rectangulo;<strong>SDL</strong>_Joystick *joystick;char mapa[401];int joyx, joyy;int done=0;int indice, indice_in;FILE *f;// estructura que <strong>con</strong>tiene la información// <strong>de</strong> nuestro aviónstruct minave {int x,y;} jugador;// Estructura que <strong>con</strong>tiene información// <strong>de</strong>l avión enemigostruct naveenemiga {int x,y,estado;} enemigo;// Estructura que <strong>con</strong>tine información// <strong>de</strong> los disparos <strong>de</strong> nuestro aviónstruct disparo {int x,y;} bala[MAXBALAS+1];// Estructura que <strong>con</strong>tiene información// <strong>de</strong> la explosiónstruct explosion {int activo,x,y,nframe;} exp;void muevenave() {// estado 1. Movimiento a la <strong>de</strong>recha.if (enemigo.estado == 1) {112


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L}enemigo.x=enemigo.x+2;if (enemigo.x>600) enemigo.estado=2;}// estado2. Movimiento a la izquierda.if (enemigo.estado == 2) {enemigo.x=enemigo.x-2;if (enemigo.x


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lfor (i=0 ; i=7) {exp.activo=0;done=1;}}// ¿ha colisionado <strong>con</strong> la nave?if (malo.colision(nave) == TRUE) {done=1;}// ¿ha colisionado el disparo <strong>con</strong> la nave?if (malo.colision(mibala) == TRUE) {enemigo.estado=0;exp.activo=1;exp.nframe=1;exp.x=enemigo.x;exp.y=enemigo.y;}}// Mostramos todo el frame<strong>SDL</strong>_Flip(screen);void creadisparo() {int libre=-1;// ¿Hay alguna bala libre?for (int i=0 ; i=0) {bala[libre].x=nave.getx();bala[libre].y=nave.gety()-15;}// Inicializamos estadosvoid inicializa() {int i,c;jugador.x=300;jugador.y=300;enemigo.x=100;enemigo.y=100;explo<strong>de</strong>.finalize();indice=MAXMAP-100;indice_in=0;enemigo.estado=1;exp.activo=0;114


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L// Inicializamos el array <strong>de</strong> balasfor (i=0 ; i


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lint main(int argc, char *argv[]) {<strong>SDL</strong>_Event event;Uint8 *keys;if (<strong>SDL</strong>_Init(<strong>SDL</strong>_INIT_VIDEO|<strong>SDL</strong>_INIT_JOYSTICK) < 0) {printf("No se pudo iniciar <strong>SDL</strong>: %s\n",<strong>SDL</strong>_GetError());return 1;}screen = <strong>SDL</strong>_SetVi<strong>de</strong>oMo<strong>de</strong>(640,480,24,<strong>SDL</strong>_HWSURFACE);if (screen == NULL) {printf("No se pue<strong>de</strong> inicializar el modo gráfico: \n",<strong>SDL</strong>_GetError());return 1;}atexit(<strong>SDL</strong>_Quit);inicializa();InitSprites();while (done == 0) {// movemos el avión enemigamuevenave();// movemos los disparosmuevebalas();// dibujamos el frameDrawScene(screen);// <strong>con</strong>sultamos el estado <strong>de</strong>l tecladokeys=<strong>SDL</strong>_GetKeyState(NULL);// <strong>con</strong>sultamos el estado <strong>de</strong>l joystick<strong>SDL</strong>_JoystickUpdate();joyx = <strong>SDL</strong>_JoystickGetAxis(joystick, 0);joyy = <strong>SDL</strong>_JoystickGetAxis(joystick, 1);if ((keys[<strong>SDL</strong>K_UP] || joyy < -10) && (jugador.y > 0)) {jugador.y=jugador.y-(5);}if ((keys[<strong>SDL</strong>K_DOWN] || joyy > 10) && (jugador.y < 460)) {jugador.y=jugador.y+(5);}if ((keys[<strong>SDL</strong>K_LEFT] || joyx < -10) && (jugador.x > 0)) {jugador.x=jugador.x-(5);}if ((keys[<strong>SDL</strong>K_RIGHT] || joyx > 10) && (jugador.x < 620)) {jugador.x=jugador.x+(5);}if (keys[<strong>SDL</strong>K_LSHIFT]) {creadisparo();}while (<strong>SDL</strong>_PollEvent(&event)) {if (event.type == <strong>SDL</strong>_QUIT) {done=1;}if (event.type == <strong>SDL</strong>_KEYDOWN || event.type == <strong>SDL</strong>_JOYBUTTONDOWN) {}if (event.key.keysym.sym == <strong>SDL</strong>K_ESCAPE) {done=1;}}}finaliza();}return 0;116


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LCapítulo8¡Que comience el juego!Todo comienzo tiene su encanto.Goethe.Aestas alturas estás casi preparado para comenzar a hacer tu propio juego.Seguro que ya tienes alguna i<strong>de</strong>a en mente, pero antes, vamos a finalizar elque tenemos entre manos. Todavía nos quedan por ver algunos <strong>de</strong>talles, queaún siendo pequeños, no <strong>de</strong>jan <strong>de</strong> ser importantes. Es este capítulo vamos acompletar todos los aspectos <strong>de</strong>l juego y a ofrecer su listado completo. Por cierto, aún nole hemos puesto un nombre. Lo llamaremos 1945, como homenaje al juego 1942.EnemigosNuestro juego va a tener tres tipos <strong>de</strong> aviones enemigos. El primer tipo son los kamikaze,que se lanzarán en diagonal por la pantalla en dirección <strong>de</strong> nuestro avión tratando <strong>de</strong> impactar<strong>con</strong> nosotros. El segundo tipo, los cazas, realizarán una maniobra <strong>de</strong> aproximación, y cuandonos tengan a tiro, lanzarán un proyectil para <strong>de</strong>spués comenzar una acción evasiva. El tercertipo es el avión jefe, <strong>con</strong> el que tendremos que enfrentarnos al final <strong>de</strong> cada nivel, y al quenecesitaremos disparar 100 veces para po<strong>de</strong>r <strong>de</strong>struirlo. A<strong>de</strong>más, varios <strong>de</strong> estos enemigosse en<strong>con</strong>trarán a la vez en pantalla. Para manejar los aviones enemigos necesitamos mejorarla estructura <strong>de</strong> datos que utilizamos en el capítulo anterior.struct naveenemiga {int activo,x,y,dx,dy,tipo,estado,impactos,nframe;} enemigo[7];Como podrá haber un máximo <strong>de</strong> 7 enemigos a la vez en la pantalla, creamos un array<strong>de</strong> 7 elementos. El campo activo, nos indicará si se está utilizando ese elemento <strong>de</strong>larray actualmente o por el <strong>con</strong>trario está libre para po<strong>de</strong>r utilizarlo (igual que hacíamos<strong>con</strong> el array <strong>de</strong> disparos). Los campos X e Y son las coor<strong>de</strong>nadas <strong>de</strong>l avión. Los camposDX y DY son importantes: indican la dirección <strong>de</strong>l avión para su movimiento. Estos valoresse suman a las coor<strong>de</strong>nadas <strong>de</strong>l avión en cada frame. Veámoslo mejor <strong>con</strong> un ejemplo.Si queremos que nuestra nave se mueva <strong>de</strong> arriba a abajo <strong>de</strong> forma vertical, a unavelocidad <strong>de</strong> 3 píxeles por frame, daremos los siguientes valores a estos campos:117


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D LDX = 0DY = 3La operación que se realiza en cada frame es:X = X + DXY = Y + DYCon lo que las coor<strong>de</strong>nadas <strong>de</strong>l avión quedarán actualizadas a la nueva posición.Dándole un valor <strong>de</strong> –3 a DY <strong>con</strong>seguiremos que el avión ascienda en vez <strong>de</strong> <strong>de</strong>scen<strong>de</strong>rpor la pantalla, y dándole un valor <strong>de</strong> 3 tanto a DX como a DY <strong>con</strong>seguiremos que semueva <strong>de</strong> forma diagonal <strong>de</strong>scen<strong>de</strong>nte. El campo tipo nos indican que tipo <strong>de</strong> enemigoes. Si vale 0 será un caza, si vale 1 será un kamikaze y si vale 3 será el avión jefe. Elcampo estado nos indica el estado <strong>de</strong>l avión. Lo utilizaremos en el caza para saber si suestado es el <strong>de</strong> acercarse para disparar o el <strong>de</strong> dar la vuelta para huir. El campoimpactos indica el número <strong>de</strong> impactos que ha sufrido el avión. Lo utilizaremos <strong>con</strong> elavión jefe al que sólo podremos <strong>de</strong>struir <strong>de</strong>spués <strong>de</strong> 100 impactos. Por último, el camponframe lo utilizaremos para almacenar el frame actual <strong>de</strong>ntro <strong>de</strong> la animación. En<strong>con</strong>creto, lo utilizaremos en el caza para realizar la animación <strong>de</strong> looping que realiza parahuir <strong>de</strong>spués <strong>de</strong> soltar el proyectil.La función siguiente será la encargada <strong>de</strong> ir creando enemigos.void creaenemigo() {int libre=-1;// ¿Hay algún enemigo libre?for (int i=0 ; i=0) {enemigo[libre].activo=1;enemigo[libre].nframe=0;enemigo[libre].x=rand();if (enemigo[libre].x > 640)enemigo[libre].x=(int)enemigo[libre].x % 640;enemigo[libre].tipo=rand();if (enemigo[libre].tipo >= 2)enemigo[libre].tipo=(int)enemigo[libre].tipo % 2; // 2 tipos <strong>de</strong>enemigos (0,1)if (enemigo[libre].tipo==0) {enemigo[libre].y=-30;enemigo[libre].dx=0;enemigo[libre].dy=5;enemigo[libre].estado=0;}if (enemigo[libre].tipo==1) {enemigo[libre].y=-30;if (enemigo[libre].x>nave.getx()) {enemigo[libre].dx=-3;} else {enemigo[libre].dx=3;}enemigo[libre].dy=5;118


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L}enemigo[libre].estado=0;}}Al igual que hacíamos <strong>con</strong> los disparos, al en<strong>con</strong>trar un elemento libre <strong>de</strong>l array <strong>de</strong>enemigos, lo ponemos activo e inicializamos su estado. La coor<strong>de</strong>nada X inicial <strong>de</strong>l aviónse calcula <strong>de</strong> forma aleatoria. Tambien establecemos <strong>de</strong> forma aleatoria que tipo <strong>de</strong>enemigo es (tipo 1, es <strong>de</strong>cir, el caza o 2, que es el kamikaze). Dependiendo <strong>de</strong>l tipo <strong>de</strong>enemigo, inicializamos sus coor<strong>de</strong>nadas (X e Y) y sus parámetros <strong>de</strong> movimiento (DX yDY). La función <strong>de</strong> movimiento sólo ha <strong>de</strong> actualizar las coor<strong>de</strong>nadas <strong>de</strong>l avión <strong>de</strong> lasiguiente forma.enemigo[i].x=enemigo[i].x+enemigo[i].dx;enemigo[i].y=enemigo[i].y+enemigo[i].dy;Durante el juego, vamos a generar un avión enemigo cada 20 ciclos <strong>de</strong> juego (o lo quees lo mismo, 20 vueltas <strong>de</strong>l game loop). Para saber cuando tenemos que generar unenemigo utlizamos la fórmula ciclos%20. Para crear un jefe, en cambio, esperamos 5000ciclos (ciclos%5000).En lo referente a los disparos enemigos, la técnica es exacta a la utilizada en elanterior capítulo <strong>con</strong> dos diferencias. La primera es que usamos un array <strong>de</strong> disparos,igual que hacemos <strong>con</strong> los enemigos, y la otra es que introducimos en la estructura <strong>de</strong>datos un campo llamado time. La función <strong>de</strong> este campo es <strong>con</strong>trolar el tiempo parasaber cuando hacer parpa<strong>de</strong>ar el disparo. El disparo enemigo parpa<strong>de</strong>a cada 5 ciclospara hacerlo más visible.NivelesCualquier juego que se precie tiene que tener niveles. El avión jefe es el guardián <strong>de</strong>lsiguiente nivel. Para crear los niveles, vamos a seguir unas simples normas. Los mapas y<strong>de</strong>más archivos necesarios para los niveles van a estar almacenados en una estructura<strong>de</strong> directorios como sigue:./levels//level1/level2/levelNEs <strong>de</strong>cir, <strong>de</strong>ntro <strong>de</strong>l directorio level crearemos un subdirectorio por cada nivel. Dentro<strong>de</strong> cada uno <strong>de</strong> estos subdirectorios almacenaremos un archivo llamado map.map <strong>con</strong> la<strong>de</strong>scripción <strong>de</strong>l mapa. Otro archivo llamado level.dsc que <strong>con</strong>tendrá en formato texto elnúmero <strong>de</strong> tiles que compone el mapa menos 1, es <strong>de</strong>cir, si tiene 7 tiles, este archivo<strong>con</strong>tendrá el valor 6. Por último, estarán aquí también todos los tiles que componen elmapa nombrados como tile0.bmp, tile1.bmp, etc... El siguiente código se encarga <strong>de</strong>cargar un nivel.void LoadLevel(int l) {InitEstados();leveltime=100;119


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L// Cargar mapa (10x40)char tmplevel[50];int i, c, ntiles;sprintf(tmplevel,"levels/level%d/map.map",l);if((f=fopen(tmplevel,"r")) != NULL) {c=fread(mapa,MAXMAP,1,f);fclose(f);}// Cargar tiles <strong>de</strong>l suelosprintf(tmplevel,"levels/level%d/level.dsc",l);if((f=fopen(tmplevel,"r")) != NULL) {fgets(tmplevel,255,f);ntiles=atoi(tmplevel);fclose(f);}}for (i=0 ; i=1) && leveltime--%2) {sprintf(msg,"Level %d",level);ttext = TTF_Ren<strong>de</strong>rText_Sha<strong>de</strong>d(fuente,msg,fgcolor,bgcolor);rectangulo.y=240;rectangulo.x=310;rectangulo.w=ttext->w;rectangulo.h=ttext->h;<strong>SDL</strong>_SetColorKey(ttext,<strong>SDL</strong>_SRCCOLORKEY|<strong>SDL</strong>_RLEACCEL, <strong>SDL</strong>_MapRGB(ttext->format,255,0,0));<strong>SDL</strong>_BlitSurface(ttext,NULL,screen,&rectangulo);}Cada 2 ciclos mostramos (leveltime--%2) el mensaje, <strong>de</strong> forma que dará la apariencia<strong>de</strong> para<strong>de</strong>o.TemporizaciónControlar la velocidad <strong>de</strong>l juego es importante. No po<strong>de</strong>mos permitirnos que nuestrojuego se ejecute a una velocidad diferente en dos máquinas distintas. Vamos a servirnos<strong>de</strong> dos funciones para realizar este <strong>con</strong>trol.120


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lvoid ResetTimeBase() {ini_milisegundos=<strong>SDL</strong>_GetTicks();}int CurrentTime() {fin_milisegundos=<strong>SDL</strong>_GetTicks();return fin_milisegundos-ini_milisegundos;}La función ResetTimeBase() utiliza la función <strong>SDL</strong>_GetTicks para almacenar eltiempo en milisegundos transcurridos <strong>de</strong>s<strong>de</strong> la inicialización <strong>de</strong> <strong>SDL</strong>. Este valor, esutilizado por la función CurrentTime() para calcular cuantos milisegundos hantranscurrido <strong>de</strong>s<strong>de</strong> la llamada a ResetTimeBase(). El procedimiento <strong>de</strong> <strong>con</strong>trol es elsiguiente. Al principio <strong>de</strong>l game loop realizamos una llamada a ResetTimeBase() parainicializar el <strong>con</strong>tador. Al finalizar el game loop utilizamos el siguiente código.do {frametime=CurrentTime();} while (frametimew;rectangulo.h=ttext->h;<strong>SDL</strong>_SetColorKey(ttext,<strong>SDL</strong>_SRCCOLORKEY|<strong>SDL</strong>_RLEACCEL, <strong>SDL</strong>_MapRGB(ttext->format,255,0,0));<strong>SDL</strong>_BlitSurface(ttext,NULL,screen,&rectangulo);Este código no necesita <strong>de</strong>masiada explicación. Simplemente comentar que utilizamosla función sprintf() para pasar el valor <strong>de</strong> la puntuación, que es <strong>de</strong> tipo int, a unaca<strong>de</strong>na <strong>de</strong> texto tipo char.El número <strong>de</strong> vidas se va a mostrar al jugador <strong>de</strong> forma gráfica <strong>con</strong> pequeñosavioncitos. Tantos como vidas tengamos. Este código dibuja los avioncitos (almacenadosen el sprite life) cada 22 píxeles.// Dibuja las vidas <strong>de</strong>l jugadorfor (i=1 ; i


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L}life.draw(screen);Es habitual ofrecer al jugador una vida extra cuando <strong>con</strong>sigue algún objetivo <strong>con</strong>creto.En nuestro caso, vamos a regalar una vida cada 2000 puntos.// ¿vida extra?if (score % 2000 == 0 && score > 0) {score+=10;jugador.vidas++;}Por útimo, lo que le falta a nuestro juego es una portada. Al ejecutar el juego, haremosaparecer una pantalla <strong>de</strong> presentación y esperaremos la pulsación <strong>de</strong> una tecla o <strong>de</strong>lbotón <strong>de</strong>l joystick para empezar a jugar. Para ello tenemos una variable llamada estado.Si estado vale 0, el game loop muestra la pantalla <strong>de</strong> presentación, si vale 1, quiere<strong>de</strong>cir que estamos jugando y muestra el juego. Este es el código completo <strong>de</strong>l juego.Figura 8.1. Aspecto <strong>de</strong> nuestro juego: 1945122


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L/***************************************************************************- 1945 -(C) 2003 by Alberto Garcia SerranoProgramación <strong>de</strong> vi<strong>de</strong>ojuegos <strong>con</strong> <strong>SDL</strong>***************************************************************************/#inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> "<strong>SDL</strong>_mixer.h"#inclu<strong>de</strong> "<strong>SDL</strong>_ttf.h"#inclu<strong>de</strong> "csprite.h"// mapa#<strong>de</strong>fine MAXTILES 10#<strong>de</strong>fine MAXMAP 400char mapa[401];<strong>SDL</strong>_Surface *screen;<strong>SDL</strong>_Rect rect;CFrame fnave;CFrame fmalo1_1;CFrame fmalo1_2;CFrame fmalo1_3;CFrame fmalo1_4;CFrame fmalo1_5;CFrame fmalo2;CFrame fmalo3;CFrame bala;CFrame ex1;CFrame ex2;CFrame ex3;CFrame ex4;CFrame ex5;CFrame ex6;CFrame ex7;CFrame dis;CFrame dis2;CFrame vida;CFrame pant;CFrame frsuelo[MAXTILES];CFrame flevel;CSprite slevel(1);CSprite suelo[MAXTILES];CSprite pantalla(1);CSprite explo<strong>de</strong>(12);CSprite nave(1);CSprite malo2(1);CSprite malo3(1);CSprite malo1(5);CSprite mibala(1);CSprite dispene(2);CSprite life(1);int ciclos,leveltime;int indice, indice_in;int nexplosiones=7,nmalos=6,nbalas=6,ndispenemigos=7,mibalax[7],mibalay[7];int done=0,estado=0, <strong>con</strong>jefe=0, level=1, score=0;Uint32 ini_milisegundos, fin_milisegundos, frametime;Sint16 joyx,joyy;Mix_Music *musica;Mix_Chunk *explosion,*disparo;<strong>SDL</strong>_Joystick *joystick;TTF_Font *fuente;<strong>SDL</strong>_Surface *ttext;<strong>SDL</strong>_Color bgcolor,fgcolor;<strong>SDL</strong>_Rect rectangulo;FILE *f;struct minave {int activo,x,y,vidas,time;} jugador;123


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lstruct naveenemiga {int activo,x,y,dx,dy,tipo,estado,impactos,nframe;} enemigo[7];struct explosion {int activo,x,y,nframe;} exp[8];struct disparo {int activo,x,y,dx,dy,estado,time;} disp[8];int dx=2,dy=2;void creaexplosion(int);void creadispenemigo(int);void ResetTimeBase(void);void creajefe(void);void LoadLevel(int);int CurrentTime(void);void DrawScene(<strong>SDL</strong>_Surface *screen) {int i,j,t,x,y;char msg[30];// movimiento <strong>de</strong>l scenario (scroll)indice_in+=2;if (indice_in>=64) {indice_in=0;indice-=10;}if (indice


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L}malo1.draw(screen);// ¿ha colisionado <strong>con</strong> la nave?if (malo1.colision(nave) == TRUE && jugador.activo==1) {jugador.vidas--;creaexplosion(255);jugador.time=30;jugador.activo=0;}if (enemigo[i].tipo==1) {malo2.setx(enemigo[i].x);malo2.sety(enemigo[i].y);malo2.draw(screen);// ¿ha colisionado <strong>con</strong> la nave?if (malo2.colision(nave) == TRUE && jugador.activo==1) {jugador.vidas--;creaexplosion(255);jugador.time=30;jugador.activo=0;}}if (enemigo[i].tipo==3) {malo3.setx(enemigo[i].x);malo3.sety(enemigo[i].y);malo3.draw(screen);// ¿ha colisionado <strong>con</strong> la nave?if (malo3.colision(nave) == TRUE && jugador.activo==1) {jugador.vidas--;creaexplosion(255);jugador.time=30;jugador.activo=0;}}}}// Dibujamos las explosionesfor (i=0 ; i=7) {exp[i].activo=0;}}}// Dibujamos los disparosfor (i=0 ; i


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lenemigo[j].activo=0;mibalax[i]=0;score=score+10;}break;case 1:// comprobamos impacto <strong>con</strong> nave tipo 1malo2.setx(enemigo[j].x);malo2.sety(enemigo[j].y);if (mibala.colision(malo2)) {// Le hemos dadocreaexplosion(j);enemigo[j].activo=0;mibalax[i]=0;score=score+10;}break;case 3:// comprobamos impacto <strong>con</strong> nave tipo 3malo3.setx(enemigo[j].x);malo3.sety(enemigo[j].y);if (mibala.colision(malo3)) {// Le hemos dadoenemigo[j].impactos++;mibalax[i]=0;// 100 impactos para <strong>de</strong>struir al jefeif (enemigo[j].impactos >=100 ) {creaexplosion(j);enemigo[j].activo=0;score=score+100;<strong>con</strong>jefe=2; // el jefe ha muertoleveltime=100; // tiempo hasta el cambio <strong>de</strong> nivellevel++;}}break;}}}}}// Dibujamos los disparos enemigosfor (i=0 ; i=5) {disp[i].time=0;if (disp[i].estado==0) {dispene.selframe(1);disp[i].estado=1;} else {dispene.selframe(0);disp[i].estado=0;}}dispene.draw(screen);}}// ¿nos han dado?if (dispene.colision(nave) && jugador.activo==1) {jugador.vidas--;creaexplosion(255);jugador.time=30;jugador.activo=0;}126


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L// Dibuja las vidas <strong>de</strong>l jugadorfor (i=1 ; iw;rectangulo.h=ttext->h;<strong>SDL</strong>_SetColorKey(ttext,<strong>SDL</strong>_SRCCOLORKEY|<strong>SDL</strong>_RLEACCEL, <strong>SDL</strong>_MapRGB(ttext->format,255,0,0));<strong>SDL</strong>_BlitSurface(ttext,NULL,screen,&rectangulo);// Muestra el nivel en el que estamos jugandoif ((leveltime>=1) && leveltime--%2) {sprintf(msg,"Level %d",level);ttext = TTF_Ren<strong>de</strong>rText_Sha<strong>de</strong>d(fuente,msg,fgcolor,bgcolor);rectangulo.y=240;rectangulo.x=310;rectangulo.w=ttext->w;rectangulo.h=ttext->h;<strong>SDL</strong>_SetColorKey(ttext,<strong>SDL</strong>_SRCCOLORKEY|<strong>SDL</strong>_RLEACCEL, <strong>SDL</strong>_MapRGB(ttext->format,255,0,0));<strong>SDL</strong>_BlitSurface(ttext,NULL,screen,&rectangulo);}}// Mostramos todo el frame<strong>SDL</strong>_Flip(screen);void muevenaves() {int i;// Esperamos a que el jugador esté preparado para seguir jugando// <strong>de</strong>spues <strong>de</strong> per<strong>de</strong>r una vida.if (jugador.activo==2) {jugador.time--;if (jugador.time


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L}}}// Animación (interna) <strong>de</strong> los spritesfor (i=0 ; i500) enemigo[i].dx=-3;if (enemigo[i].x480 || enemigo[i].y


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lint libre=-1;// ¿Hay alguna bala libre?for (int i=0 ; i=0) {mibalax[libre]=nave.getx();mibalay[libre]=nave.gety()-15;}}// sonido <strong>de</strong>l disparoMix_PlayChannel(-1,disparo,0);void creaenemigo() {int libre=-1;// ¿Hay algún enemigo libre?for (int i=0 ; i=0) {enemigo[libre].activo=1;enemigo[libre].nframe=0;enemigo[libre].x=rand(); // Pos.X aleatoriaif (enemigo[libre].x > 640)enemigo[libre].x=(int)enemigo[libre].x % 640;enemigo[libre].tipo=rand(); // tipo <strong>de</strong> enemigo aleatorioif (enemigo[libre].tipo >= 2)enemigo[libre].tipo=(int)enemigo[libre].tipo % 2; // 2 tipos <strong>de</strong> enemigos (0,1)// tipo cazaif (enemigo[libre].tipo==0) {enemigo[libre].y=-30;enemigo[libre].dx=0;enemigo[libre].dy=5;enemigo[libre].estado=0;}// tipo kamikazeif (enemigo[libre].tipo==1) {enemigo[libre].y=-30;if (enemigo[libre].x>nave.getx()) {enemigo[libre].dx=-3;} else {enemigo[libre].dx=3;}enemigo[libre].dy=5;enemigo[libre].estado=0;}}}void creajefe() {int libre=1;enemigo[libre].activo=1;enemigo[libre].tipo=3;enemigo[libre].y=100;if (enemigo[libre].x>nave.getx()) {enemigo[libre].dx=-3;} else {enemigo[libre].dx=3;}enemigo[libre].dy=0;129


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lenemigo[libre].estado=0;enemigo[libre].impactos=0;}void creaexplosion(int j) {int libre=-1;for(int i=0; i=0) {if (j!=255) { // si j = 255 la explosión es para el jugadorexp[libre].activo=1;exp[libre].nframe=1;exp[libre].x=enemigo[j].x;exp[libre].y=enemigo[j].y;} else {exp[libre].activo=1;exp[libre].nframe=1;exp[libre].x=jugador.x;exp[libre].y=jugador.y;}}}// sonido <strong>de</strong> explosionMix_PlayChannel(-1,explosion,0);void creadispenemigo(int j) {int libre=-1;for(int i=0; i=0) {disp[libre].activo=1;if (enemigo[j].x>nave.getx()) {disp[libre].dx=-3;} else {disp[libre].dx=3;}disp[libre].dy=3;disp[libre].x=enemigo[j].x+30;disp[libre].y=enemigo[j].y+20;disp[libre].estado=0;disp[libre].time=0;}void InitEstados() {int i;jugador.activo=1;jugador.vidas=3;jugador.x=320;jugador.y=400;// Inicializamos el array <strong>de</strong> balasfor (i=0 ; i


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L// Inicializamos el array <strong>de</strong> explosionesfor (i=0; i


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lvoid finaliza() {}// <strong>de</strong>struimos la fuente <strong>de</strong> letraTTF_CloseFont(fuente);// <strong>de</strong>strimos la musicaMix_FreeMusic(musica);// cerramos el joystickif (<strong>SDL</strong>_JoystickOpened(0)) {<strong>SDL</strong>_JoystickClose(joystick);}int InitSprites() {int i;// Inicializamos el generador <strong>de</strong> números aleatoriossrand( (unsigned)time( NULL ) );// Inicializamos el array <strong>de</strong> balasfor (i=0 ; i


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D Lfmalo1_1.load("pics/enemy1_1.bmp");malo1.addframe(fmalo1_1);fmalo1_2.load("pics/enemy1_2.bmp");malo1.addframe(fmalo1_2);fmalo1_3.load("pics/enemy1_3.bmp");malo1.addframe(fmalo1_3);fmalo1_4.load("pics/enemy1_4.bmp");malo1.addframe(fmalo1_4);fmalo1_5.load("pics/enemy1_5.bmp");malo1.addframe(fmalo1_5);fmalo2.load("pics/nave2.bmp");malo2.addframe(fmalo2);fmalo3.load("pics/gordo.bmp");malo3.addframe(fmalo3);bala.load("pics/balas.bmp");mibala.addframe(bala);vida.load("pics/life.bmp");life.addframe(vida);return 0;}void ResetTimeBase() {}ini_milisegundos=<strong>SDL</strong>_GetTicks();int CurrentTime() {}fin_milisegundos=<strong>SDL</strong>_GetTicks();return fin_milisegundos-ini_milisegundos;int main(int argc, char *argv[]) {<strong>SDL</strong>_Event event;Uint8 *keys;int salir=0;if (<strong>SDL</strong>_Init(<strong>SDL</strong>_INIT_VIDEO|<strong>SDL</strong>_INIT_AUDIO|<strong>SDL</strong>_INIT_JOYSTICK) < 0) {printf("No se pudo iniciar <strong>SDL</strong>: %s\n",<strong>SDL</strong>_GetError());return 1;}screen = <strong>SDL</strong>_SetVi<strong>de</strong>oMo<strong>de</strong>(640,480,24,<strong>SDL</strong>_HWSURFACE|<strong>SDL</strong>_DOUBLEBUF|<strong>SDL</strong>_FULLSCREEN);if (screen == NULL) {printf("No se pue<strong>de</strong> inicializar el modo gráfico: \n",<strong>SDL</strong>_GetError());return 1;}atexit(<strong>SDL</strong>_Quit);if (Mix_OpenAudio(MIX_DEFAULT_FREQUENCY,MIX_DEFAULT_FORMAT,1,4096) < 0) {printf("No se pudo iniciar <strong>SDL</strong>_Mixer: %s\n",Mix_GetError());return 1;}atexit(Mix_CloseAudio);if (TTF_Init() < 0) {printf("No se pudo iniciar <strong>SDL</strong>_ttf: %s\n",<strong>SDL</strong>_GetError());return 1;133


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L}atexit(TTF_Quit);inicializa();InitSprites();while (done == 0) {switch (estado) {case 0:InitEstados();pantalla.setx(0);pantalla.sety(0);pantalla.draw(screen);<strong>SDL</strong>_Flip(screen);// <strong>de</strong>sactivamos la música (si estaba activada)Mix_HaltMusic();while (estado==0 && done==0) {while (<strong>SDL</strong>_PollEvent(&event)) {if (event.type == <strong>SDL</strong>_QUIT) {done=1;}if (event.type == <strong>SDL</strong>_KEYDOWN || event.type == <strong>SDL</strong>_JOYBUTTONDOWN) {}if (event.key.keysym.sym == <strong>SDL</strong>K_ESCAPE) {done=1;} else {score=0;estado=1;LoadLevel(1);}// Activamos la músicaMix_PlayMusic(musica,-1);}}break;case 1:// Inicializamos el timer para <strong>con</strong>trol <strong>de</strong> tiempo.ResetTimeBase();ciclos++;// cada 20 ciclos se crea un enemigoif ((!(ciclos % 20)) && <strong>con</strong>jefe==0) {creaenemigo();}// ¿final <strong>de</strong> fase?if (ciclos>=5000 && <strong>con</strong>jefe==0) {// creamos la nave jefe.creajefe();<strong>con</strong>jefe=1;}// tiempo <strong>de</strong>s<strong>de</strong> que se <strong>de</strong>struye al jefe// hasta que cambiamos <strong>de</strong> nivel.if ((<strong>con</strong>jefe == 2) && (leveltime-- 2) level=1;LoadLevel(level);134


P R O G R A M A C I Ó N D E V I D E O J U E G O S C O N S D L}muevenaves();DrawScene(screen);// ¿vida extra?if (score % 2000 == 0 && score > 0) {score+=10;jugador.vidas++;}// <strong>con</strong>sultamos el estado <strong>de</strong>l tecladokeys=<strong>SDL</strong>_GetKeyState(NULL);// <strong>con</strong>sultamos el estado <strong>de</strong>l joystick<strong>SDL</strong>_JoystickUpdate();joyx = <strong>SDL</strong>_JoystickGetAxis(joystick, 0);joyy = <strong>SDL</strong>_JoystickGetAxis(joystick, 1);if ((keys[<strong>SDL</strong>K_UP] || joyy < -10) && (jugador.y > 0)) {jugador.y=jugador.y-(5);}if ((keys[<strong>SDL</strong>K_DOWN] || joyy > 10) && (jugador.y < 460)){jugador.y=jugador.y+(5);}if ((keys[<strong>SDL</strong>K_LEFT] || joyx < -10) && (jugador.x > 0)) {jugador.x=jugador.x-(5);}if ((keys[<strong>SDL</strong>K_RIGHT] || joyx > 10) && (jugador.x < 620)){jugador.x=jugador.x+(5);}if (keys[<strong>SDL</strong>K_LSHIFT] && jugador.activo!=0) {creadisparo();}while (<strong>SDL</strong>_PollEvent(&event)) {}// evento <strong>de</strong>l boton <strong>de</strong>l joystickif (event.type ==<strong>SDL</strong>_JOYBUTTONDOWN && jugador.activo != 0) {creadisparo();}// evento <strong>de</strong> cierre <strong>de</strong>l programaif (event.type == <strong>SDL</strong>_QUIT) {done=0;}// evento pulsación <strong>de</strong> teclaif (event.type == <strong>SDL</strong>_KEYDOWN) {}if (event.key.keysym.sym == <strong>SDL</strong>K_ESCAPE) {estado=0;}// Esperamos a que transcurran al menos 30 ms para// generar el siguiente frame, y evitar así// que el juego se acelere en máquinas muy rápidas.do {frametime=CurrentTime();} while (frametime


¿Y ahora que?Ahora te queda la mayoría <strong>de</strong>l camino por recorrer. Si este libro cumple bien el objetivo<strong>con</strong> el que fue escrito, espero haberte hecho dar los primeros pasos en la direccióncorrecta. Me voy a permitir un último <strong>con</strong>sejo: haz tu propio juego. No es necesario quesea muy complejo, lo realmente importante es que lo termines. Y recuerda que la mejorforma <strong>de</strong> apren<strong>de</strong>r a programar es ¡programando! Tampoco te hará mal leer códigoajeno. Explora todo el código que puedas. Hay montones <strong>de</strong> juegos <strong>de</strong> código abierto.Vivimos unos buenos tiempos para los que tenemos curiosidad y ganas <strong>de</strong> apren<strong>de</strong>r.Internet nos brinda acceso a gran cantidad <strong>de</strong> información que está disponible a un click<strong>de</strong> distancia. El apéndice C pue<strong>de</strong>s en<strong>con</strong>trar numerosos recursos sobre programación<strong>de</strong> vi<strong>de</strong>ojuegos. También hay muy buenos libros en el mercado, quien sabe, pue<strong>de</strong> quenos en<strong>con</strong>tremos <strong>de</strong> nuevo en una segunda parte <strong>de</strong> este libro. Según te vayasintroduciendo en este mundo <strong>de</strong>scubrirás que cada vez te falta más y más por apren<strong>de</strong>r.No te <strong>de</strong>sanimes. La programación <strong>de</strong> juegos está compuesta por muy diferentesaspectos, como la IA, gráficos 3D, programación <strong>de</strong> audio, etc... y no pue<strong>de</strong>s ser unexperto en todos.Quizás el siguiente paso sea entrar en el mundo <strong>de</strong> las tres dimensiones y <strong>SDL</strong> ofreceun buen soporte para OpenGL. Todo lo aprendido en este libro es la base y es aplicableen los juegos 3D, aunque, por supuesto, tendrás que seguir aprendiendo y recorriendo elcamino antes <strong>de</strong> crear tu primer juego en tres dimensiones. No utilces atajos, es peor,créeme.Cuando lleves algún tiempo en esto, es probable que quieras <strong>de</strong>dicarteprofesionalmente a la creación <strong>de</strong> los vi<strong>de</strong>ojuegos. Aquí los curriculums valen <strong>de</strong> poco, almenos, si estás intentando meter la cabeza en el mundillo. Tu carta <strong>de</strong> presentación serátu trabajo, así que prepara algunas <strong>de</strong>mos <strong>de</strong> tus juegos y prepárate a mandarlas a todaslas compañías que puedas en<strong>con</strong>trar. Te advierto que, o lo haces por vocación, o lopasarás mal. Crear un juego comercial es como una carrera <strong>de</strong> fondo, tienes que disfrutarrealmente <strong>con</strong> lo que haces. Piensa que un juego comercial medio pue<strong>de</strong> llevar un año<strong>de</strong> trabajo para su finalización, pero si es lo que quieres... hazlo y no te <strong>de</strong>jes influir por loque nadie te cuente.Dice Oscar Wil<strong>de</strong> en “El retrato <strong>de</strong> Dorian Gray”:“Porque influir en una persona es darle la propia alma. Esa persona <strong>de</strong>ja <strong>de</strong> pensar suspropias i<strong>de</strong>as y <strong>de</strong> ar<strong>de</strong>r <strong>con</strong> sus pasiones. Sus virtu<strong>de</strong>s <strong>de</strong>jan <strong>de</strong> ser reales. Suspecados, si es que los pecados existen, son prestados. Se <strong>con</strong>vierte en el eco <strong>de</strong> lamúsica <strong>de</strong> otro, en un actor que interpreta un papel que no se ha escrito para él. Lafinalidad <strong>de</strong> la vida es el propio <strong>de</strong>sarrollo. Alcanzar la plenitud <strong>de</strong> la manera más perfectaposible, para eso estamos aquí.”Ar<strong>de</strong> <strong>con</strong> tus propias pasiones y...¡Disfruta!136


ApéndiceAInstalación <strong>de</strong> <strong>SDL</strong>La dirección web oficial <strong>de</strong>l proyecto <strong>SDL</strong> es http://www.libsdl.org. Allí podrásen<strong>con</strong>trar gran cantidad <strong>de</strong> información, documentación y aplicaciones<strong>de</strong>sarrolladas <strong>con</strong> <strong>SDL</strong>. Las direcciones directas para <strong>de</strong>scargar <strong>SDL</strong> y suslibrerías auxiliares son:Librería <strong>SDL</strong><strong>SDL</strong>_mixer<strong>SDL</strong>_ttf<strong>SDL</strong>_imagehttp://www.libsdl.org/download-1.2.phphttp://www.libsdl.org/projects/<strong>SDL</strong>_mixer/http://www.libsdl.org/projects/<strong>SDL</strong>_ttf/http://www.libsdl.org/projects/<strong>SDL</strong>_image/En este apéndice vamos a ver como utilizar estas librerías tanto en Windows (<strong>con</strong> VC++)como en Linux (<strong>con</strong> GCC).Windows (VC++)Lo primero que necesitamos es tener instalado el compilador <strong>de</strong> C. Supondré queutilizas Visual C++ 6.0, ya que es el más extendido y utilizado en entornos Windows. Sitienes otro compilador, los pasos a realizar serán similares, aunque ya <strong>de</strong>pen<strong>de</strong>rá <strong>de</strong>cada caso. Lo mejor es <strong>con</strong>sultar la documentación <strong>de</strong> tu compilador.El primer paso es <strong>de</strong>scargar las librerías necesarias. Asegúrate que sean las<strong>de</strong>velopment libraries. Si utilizas VC++ existe una versión especial <strong>de</strong> las librerías, porejemplo, el archivo que <strong>de</strong>bes <strong>de</strong>scargar para VC es <strong>SDL</strong>-<strong>de</strong>vel-1.2.6-VC6.zip. Una vez<strong>de</strong>scargado <strong>de</strong>scomprímelo en el directorio raiz. Ahora <strong>de</strong>bes tener un directorio llamado\<strong>SDL</strong>-1.2.6\. En lugar <strong>de</strong> 1.2.6, podrían ser otros valores <strong>de</strong>pendiendo <strong>de</strong> la versión <strong>de</strong> lalibrería.Arranca VC++ y selecciona la opción new... <strong>de</strong>l menú file. Podrás ver la ventana <strong>de</strong>nuevo proyecto. En la pestaña Project selecciona WIN32 Application, escribe el nombre<strong>de</strong>l proyecto en el cuadro <strong>de</strong> texto project name, y selecciona el directorio don<strong>de</strong> quieresalmacenarlo en location. Seguidamente pulsa el botón OK.En la siguiente ventana nos pregunta que tipo <strong>de</strong> aplicación queremos crear.Seleccionamos un proyecto vacío (An empty project) y pulsamos el botón finish. Yatenemos nuestro proyecto creado. Ahora vamos a <strong>con</strong>figurar el proyecto para po<strong>de</strong>rutilizar las librerías <strong>de</strong> <strong>SDL</strong>.137


Ventana <strong>de</strong> nuevo proyecto en VC++Selecciona la opción Settings... <strong>de</strong>l menú Project. Verás como aparece una ventana <strong>de</strong><strong>con</strong>figuración. Selecciona la pestaña C/C++, aparecerá un campo <strong>de</strong>splegable llamadoCategory. Selecciona co<strong>de</strong> generation en este <strong>de</strong>splegable. En el <strong>de</strong>splegable llamadoUse run-time library, selecciona Multithrea<strong>de</strong>d DLL.Ventana <strong>de</strong> <strong>con</strong>figuración C/C++->Co<strong>de</strong> generationEl siguiente paso es seleccionar la opción Preprocessor en el <strong>de</strong>splegable Category.En el campo Additional inclu<strong>de</strong> directories, incluimos la ubicación <strong>de</strong>l directorio inclu<strong>de</strong>que hay <strong>de</strong>ntro <strong>de</strong>l directorio <strong>de</strong> <strong>SDL</strong>. En este caso es C:\<strong>SDL</strong>-1.2.6\inclu<strong>de</strong>.138


Ventana <strong>de</strong> <strong>con</strong>figuración C/C++->reprocessorAhora vamos a <strong>con</strong>figurar el enlazador (linker) <strong>de</strong>l compilador para que pueda enlazarlas librerías. Pulsamos la pestaña Link, y seleccionamos input. El el cuadro <strong>de</strong> textoObject/library modules añadimos sdlmain.lib y sdl.lib. En caso <strong>de</strong> que queramos utilizarotras librerías, como por ejemplo, <strong>SDL</strong>_mixer, hemos <strong>de</strong> añadirlas también aquí. En elcampo Aditional library path, vamos a añadir la ubicación <strong>de</strong> las librerías. En este casoC:\<strong>SDL</strong>-1.2.6\lib. Una vez hecho esto, pulsamos el botón OK y ya tenemos todo listo.Ventana <strong>de</strong> <strong>con</strong>figuración Link->inputVamos a añadir código a nuestro proyecto. Selecciona la opción New... <strong>de</strong>l menú file.Veremos una ventana que nos permite seleccionar el tipo <strong>de</strong> archivo. Asegúrate que estáseleccionada la pestaña File. En la ventana selecciona C++ Source File. En el campo Filename, introduce el nombre <strong>de</strong>l archivo. En este caso lo llamaremos main.c.Seguidamente pulsamos OK.139


Nuevo archivoAhora tendremos una ventana <strong>de</strong> edición lista para empezar a teclear. Introduce elsiguiente código que utilizaremos para probar la librería.#inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> int main(int argc, char *argv[]) {<strong>SDL</strong>_Surface *screen;<strong>SDL</strong>_Event event;int done = 0;atexit(<strong>SDL</strong>_Quit);// Iniciar <strong>SDL</strong>if (<strong>SDL</strong>_Init(<strong>SDL</strong>_INIT_VIDEO) < 0) {printf("No se pudo iniciar <strong>SDL</strong>: %s\n",<strong>SDL</strong>_GetError());exit(1);}// Activamos modo <strong>de</strong> vi<strong>de</strong>oscreen = <strong>SDL</strong>_SetVi<strong>de</strong>oMo<strong>de</strong>(640,480,24,<strong>SDL</strong>_HWSURFACE);if (screen == NULL) {printf("No se pue<strong>de</strong> inicializar el modo gráfico:\n",<strong>SDL</strong>_GetError());exit(1);}// Esperamos la pulsación <strong>de</strong> una tecla para salirwhile(done == 0) {while ( <strong>SDL</strong>_PollEvent(&event) ) {if ( event.type == <strong>SDL</strong>_KEYDOWN )done = 1;}140


}}return 0;Una vez introducido el código, pulsamos F7 para compilar el programa. Si todo vabien, VC++ nos informará que no ha habido ningún error. Si no es así repasa el código.Como por <strong>de</strong>fecto VC++ está en modo <strong>de</strong>bug, el compilador nos habrá creado undirectorio llamado Debug en el directorio don<strong>de</strong> se encuentra el proyecto. Dentro <strong>de</strong> estedirectorio en<strong>con</strong>tramos un ejecutable. Éste es nuestro programa ya compilado, sólo nosresta un paso. Para que los programas que utilizan <strong>SDL</strong> funcionen, hemos <strong>de</strong> copiar elarchivo <strong>SDL</strong>.dll en el mismo directorio don<strong>de</strong> se encuentre el programa ejecutable (oañadirlo al directorio \windows\system32). Ya está listo para su ejecución pulsando dobleclick sobre él. Si todo ha ido bién, verás una ventana en negro. Pulsa cualquier tecla paracerrar el programa.Observa que si actualizas la versión <strong>de</strong> <strong>SDL</strong>, o intentas cargar un proyecto que utiliceotra versión distinta a la que tienes instalada, tendrás que actualizar la ubicación <strong>de</strong> lascabeceras y las librerías en el proyecto. Por ejemplo, si antes tenías tus cabeceras enC:\<strong>SDL</strong>-1.2.4\inclu<strong>de</strong> y te actualizas a la versión 1.2.6 <strong>de</strong> <strong>SDL</strong>, tendrás que cambiar laubicación en la <strong>con</strong>figuración a C:\<strong>SDL</strong>-1.2.6\inclu<strong>de</strong>.La instalación <strong>de</strong> las librerías auxiliares es muy sencilla. Descarga la versión especificapara VC++, y copia los archivos <strong>de</strong> cabecera (*.h) al directorio inclu<strong>de</strong> <strong>de</strong> <strong>SDL</strong> (C:\<strong>SDL</strong>-1.2.6\inclu<strong>de</strong>) y las librerías (*.lib) al directorio lib <strong>de</strong> <strong>SDL</strong> (C:\<strong>SDL</strong>-1.2.6\lib). Asegúrate <strong>de</strong>copiar el archivo DLL <strong>de</strong> la librería (por ejemplo <strong>SDL</strong>_mixer.dll) en el directorio don<strong>de</strong> seencuentre el archivo ejecutable.LinuxTal y como hicimos <strong>con</strong> Windows, lo primero que tenemos que hacer es localizar elpaquete que nos <strong>con</strong>viene <strong>de</strong>scargar. Si tu distribución Linux está basada en Red Hat otrabaja <strong>con</strong> paquetes en formato RPM, tendrás que bajarte los paquetes que finalizan <strong>con</strong>la extensión .rpm. La instalación <strong>de</strong> un paquete RPM se hace <strong>de</strong> la siguiente manera(siempre como usuario root).rpm –ivh nombre_paquetePor ejemplo, si el paquete que has <strong>de</strong>scargado es <strong>SDL</strong>-1.2.6-1.i386.rpm, la línea querealiza la instalación es:rpm –ivh <strong>SDL</strong>-1.2.6-1.i386.rpmSi tu distribución está basada en Debian, podrás utilizar la utilidad apt para <strong>de</strong>scargare instalar <strong>SDL</strong>. La siguiente línea <strong>de</strong>scarga e instala <strong>SDL</strong>.apt-get install libsdl1.2-<strong>de</strong>vEsta línea podría variar <strong>de</strong>pendiendo <strong>de</strong> la última versión <strong>de</strong> <strong>SDL</strong> disponible. Es<strong>con</strong>veniente ejecutar la línea siguiente antes para asegurarte que <strong>de</strong>scargas la últimaversión.apt-get update141


Seguidamente, para <strong>con</strong>ocer que versiones y que librerías para <strong>SDL</strong> hay disponiblesutliza la siguiente línea.apt-cache search sdlUna vez instalado <strong>SDL</strong>, las librerías <strong>de</strong>berían estar instaladas en el directorio /usr/lib, ylos archivos <strong>de</strong> cabecera en /usr/inclu<strong>de</strong>/<strong>SDL</strong>.No vamos a entrar en <strong>de</strong>talle en el funcionamiento <strong>de</strong>l compilador GCC, simplementete mostraré las líneas necesarias para realizar la compilación.Para realizar una compilación utilizando librerías dinámicas, compila el programa <strong>de</strong> lasiguiente forma.gcc -L/usr/lib -l<strong>SDL</strong> -lpthread -I/usr/inclu<strong>de</strong>/<strong>SDL</strong> -D_REENTRANT main.cpp csprite.cpp -oejemplo6_1El modificador –L permite indicar la ubicación <strong>de</strong> las librerías, y el modificador –Iindica la ubicación <strong>de</strong> los archivos <strong>de</strong> cabecera. El modificador –l, nos permite indicar alcompilador qué librerías queremos enlazar <strong>con</strong> nuestro programa. Si quisieramos enlazarla librería principal <strong>de</strong> <strong>SDL</strong> y también <strong>SDL</strong>_mixer utilizaríamos –l<strong>SDL</strong> –l<strong>SDL</strong>_mixer.Por último, indica los archivos fuente que quieres compilar (main.cpp y csprite.cpp)y el nombre <strong>de</strong>l archivo ejecutable que quieres generar <strong>de</strong>spués <strong>de</strong>l modificador –o. Si noentien<strong>de</strong>s el resto <strong>de</strong> modificadores no te preocupes, déjalos tal cual, ya que si no, nofuncionará el archivo ejecutable.Para realizar una compilación enlazando las librerías estáticas, hazlo <strong>de</strong> la siguienteforma.gcc -L/usr/lib -l<strong>SDL</strong> -lpthread -lm -L/usr/X11R6/lib -lX11 -lXext -ldl-I/usr/inclu<strong>de</strong>/<strong>SDL</strong> -D_REENTRANT main.cpp csprite.cpp-o ejemplo6_1Para ejecutar el programa ya compilado usamos./ejemplo6_1142


ApéndiceBDe C a C++Que tengas este libro entre tus manos, significa que tienes unos <strong>con</strong>ocimientosmínimamente razonables <strong>de</strong> programación. Probablemente estarás familiarizado<strong>con</strong> el lenguaje C. En el presente capítulo voy a tratar <strong>de</strong> hacer una introduccióna la programación orientada a objetos en C++. Evi<strong>de</strong>ntemente, para hacer unabuena introducción necesitaríamos todo un libro. En la bibliografía hay algunosmuy buenos que te servirán para profundizar. El objetivo, pues, <strong>de</strong> este apéndice esacercar la POO en C++ a los programadores <strong>con</strong> <strong>con</strong>ocimientos previos <strong>de</strong> C. Noscentraremos en los aspectos más relevantes y, <strong>de</strong>sgraciadamente, por la extensiónlimitada <strong>de</strong> la presente obra, <strong>de</strong>jaremos <strong>de</strong> lado las características más complejas omenos útiles <strong>de</strong> este lenguaje. Ni que <strong>de</strong>cir tiene que si ya eres un experto en C++,pue<strong>de</strong> prescindir alegremente este apéndice.Pue<strong>de</strong> que en estos momentos te estés preguntando si es realmente necesario utilizarC++ y si C no es suficiente. Rotundamente sí. Pue<strong>de</strong>s acometer el <strong>de</strong>sarrollo <strong>de</strong> juegosrazonablemente complejos en C, sin embargo, la POO nos aporta ciertamente una serie<strong>de</strong> ventajas que hemos <strong>de</strong> tener muy en cuenta a la hora <strong>de</strong> sentarnos a programar,como:• Una mayor abstracción <strong>de</strong> datos.• Más facilidad para la reutilización <strong>de</strong> código.• Mantenimiento y extensión <strong>de</strong> las aplicaciones más fácil.• Mayor potencia (herencia y polimorfismo).• Mo<strong>de</strong>lado <strong>de</strong> problemas reales <strong>de</strong> forma más directa.Quizás todo esto te suene un poco ajeno a la forma a la que estás acostumbrado aprogramar, e incluso pue<strong>de</strong> que ni te suenen algunos <strong>de</strong> estos <strong>con</strong>ceptos, pero al finalizar elcapítulo, <strong>de</strong>berán serte familiares.Todo esto suena bien, pero ¿qué es un objeto? Trataré, sin entrar en <strong>de</strong>masiadosformalismos, explicarlo <strong>de</strong> la forma más intuitiva posible. Si te pido que pienses en un objeto,seguramente pensarás en un lápiz, una mesa, unas gafas <strong>de</strong> sol, un coche o cualquier otracosa que caiga <strong>de</strong>ntro <strong>de</strong> tu radio <strong>de</strong> visión. Esta es la i<strong>de</strong>a intuitiva <strong>de</strong> objeto. Algo físico ymaterial. En POO, el <strong>con</strong>cepto <strong>de</strong> objeto no es muy diferente. Una <strong>de</strong> las diferencias básicasevi<strong>de</strong>ntes es que un objeto en C++ pue<strong>de</strong> hacer referencia a algo abstracto.Como en el ejemplo <strong>de</strong>l coche, un objeto pue<strong>de</strong> estar compuesto por otra clase <strong>de</strong> objetos,como ruedas, carrocería, etc... Este <strong>con</strong>cepto <strong>de</strong> clase <strong>de</strong> objeto es importante. Un objetosiempre pertenece a una clase <strong>de</strong> objetos. Por ejemplo, todas las ruedas, <strong>con</strong> in<strong>de</strong>pen<strong>de</strong>ncia<strong>de</strong> su tamaño, pertenecen a la clase “rueda”. Hay muchos objetos rueda diferente quepertenecen a la clase rueda. Cada una <strong>de</strong> estas ruedas diferentes se llaman instancias <strong>de</strong> laclase “rueda”. Tenemos, pues, instancias <strong>de</strong> la clase rueda que son ruedas <strong>de</strong> camión, ruedas<strong>de</strong> coches o ruedas <strong>de</strong> moto.143


Volvamos al ejemplo <strong>de</strong>l coche. Vamos a <strong>de</strong>finir otra clase <strong>de</strong> objeto, la clase “Coche”. Laclase coche <strong>de</strong>fine a “algo” que está compuesto por objetos (instancias) <strong>de</strong> la clase rueda, laclase carrocería, la clase volante, etc... Ahora vamos a crear un objeto <strong>de</strong> la clase coche, alque llamaremos coche_rojo. En este caso hemos instanciado un objeto <strong>de</strong> la clase coche yhemos <strong>de</strong>finido uno <strong>de</strong> sus atributos, el color, al que hemos dado el valor <strong>de</strong> rojo. Vemos puesque un objeto pue<strong>de</strong> poseer atributos. Sobre el objeto coche po<strong>de</strong>mos <strong>de</strong>finir tambiénacciones u operaciones posibles. Por ejemplo, el objeto coche, entre otras cosas, pue<strong>de</strong>realizar las operaciones <strong>de</strong> acelerar, frenar, girar a la izquierda, etc... Estas operaciones quepue<strong>de</strong>n ser ejecutadas sobre un objeto se llaman métodos <strong>de</strong>l objeto.Po<strong>de</strong>mos hacer ya una primera <strong>de</strong>finición <strong>de</strong> lo que es un objeto. Es la instancia <strong>de</strong> unaclase <strong>de</strong> objeto <strong>con</strong>creta, que está compuesta por atributos y métodos. Esta <strong>de</strong>finición nosmuestra ya una <strong>de</strong> las tres principales características que <strong>de</strong>finen a la POO. Me refiero alEncapsulamiento, que no es, ni más ni menos, que la capacidad que tiene un objeto <strong>de</strong><strong>con</strong>tener datos (atributos) y código (métodos).Clases y objetosVeamos en la práctica como se <strong>de</strong>clara una clase y se instancia un objeto en C++.Podríamos afirmar que, en C++, una clase es algo muy similar a una estructura <strong>de</strong> C en la quea<strong>de</strong>más <strong>de</strong> almacenar datos, po<strong>de</strong>mos añadir las funciones (métodos) que harán uso <strong>de</strong>estos datos. Siguiendo <strong>con</strong> el ejemplo anterior, vamos a <strong>de</strong>clarar la clase “coche”.// Declaración <strong>de</strong> la clase cocheclass Coche {// Atributos <strong>de</strong> la clase cocheint velocidad;// Métodos <strong>de</strong> la clase cochevoid acelerar(int velocidad);void frenar();};En este ejemplo hemos <strong>de</strong>clarado la clase coche. Lo primero que pue<strong>de</strong> chocar alprogramador <strong>de</strong> C es la forma <strong>de</strong> incluir comentarios en el código. C++ permite seguir usandocomentarios al estilo <strong>de</strong> C, pero también aña<strong>de</strong> una nueva forma <strong>de</strong> hacerlo mediante las dosbarras //. C++ interpreta que lo que sigue a las dos barras y hasta el final <strong>de</strong> la línea es uncomentario.La <strong>de</strong>claración <strong>de</strong> la clase coche muestra claramente dos partes diferenciadas. Por unlado, tenemos la <strong>de</strong>claración <strong>de</strong> los atributos <strong>de</strong> la clase (también llamados datos miembro <strong>de</strong>la clase), que en este caso <strong>de</strong>scribe la velocidad <strong>de</strong>l coche. Po<strong>de</strong>mos <strong>de</strong>cir que las variablesmiembro <strong>de</strong> una clase <strong>de</strong>finen el estado <strong>de</strong>l objeto (en este caso el estado <strong>de</strong>l coche estáformado exclusivamente por su velocidad). Ni que <strong>de</strong>cir tiene que esto es un ejemplo muysimplificado y que una clase que tratara <strong>de</strong> <strong>de</strong>scribir el comportamiento <strong>de</strong> un coche seríamucho más compleja. La segunda parte <strong>de</strong> la <strong>de</strong>claración <strong>con</strong>tiene los métodos (funciones)que nos van a permitir realizar operaciones o acciones sobre el objeto. En el caso <strong>de</strong> la clasecoche hemos <strong>de</strong>finido un método acelerar() que nos va a permitir acelerar el coche hastauna <strong>de</strong>terminada velocidad. El método frenar() va a hacer que la velocidad sea 0, es <strong>de</strong>cir,que el coche se pare. Esta es la forma en que se implementa un método en C++. Lassiguientes líneas implementan el método frenar() y acelerar().void Coche::frenar() {// Ponemos a 0 el valor <strong>de</strong>l atributo velocidadvelocidad = 0;}144


void Coche::acelerar(int velocidad) {// Actualizamos la velocidadthis.velocidad = velocidad;}Se pue<strong>de</strong> observar que <strong>de</strong>clarar un método es muy similar a <strong>de</strong>clarar una funciónen C, salvo que es necesario indicar a qué clase correspon<strong>de</strong> el método. Esto se haceañadiendo el nombre <strong>de</strong> la clase seguido <strong>de</strong> dobles dos puntos antes <strong>de</strong>l nombre <strong>de</strong>lmétodo. En el método acelerar, pue<strong>de</strong>s observar que en vez <strong>de</strong> nombrar directamente ala variable velocidad, hemos utilizado this.velocidad. El motivo, comoseguramente estarás suponiendo, es que la variable que hemos pasado como parámetroal método se llama igual que la variable miembro <strong>de</strong> la clase. Con this.velocidadinformamos a C++ <strong>de</strong> que nos referimos a la variable miembro, en caso <strong>con</strong>trariointerpretará que nos referimos a la variable recibida como parámetro.Ya sabemos cómo <strong>de</strong>clarar una clase y como implementarla. Nos resta <strong>con</strong>ocercómo utilizarla. No po<strong>de</strong>mos utilizar una clase directamente. Una clase es como unpatrón a partir <strong>de</strong>l cual se crean (instancian) objetos <strong>de</strong>ribados <strong>de</strong> ella. La forma <strong>de</strong> crearun objeto coche es <strong>de</strong>clarando el objeto, tal y como haríamos <strong>con</strong> una variable.Coche micoche;Ahora sí tenemos un objeto llamado micoche, que es una instancia <strong>de</strong> la claseCoche y que po<strong>de</strong>mos empezar a utilizar.Para hacer uso <strong>de</strong> un método <strong>de</strong>l objeto micoche, proce<strong>de</strong>mos <strong>de</strong> la siguienteforma.// Primero aceleramos hasta 100 KM/Hmicoche.acelerar(100);// Y <strong>de</strong>spues frenamosmicoche.frenar()Como ves, el acceso a un método es simple, sólo hay que anteponer el nombre <strong>de</strong>lobjeto al que nos referimos seguido <strong>de</strong> un punto y el nombre <strong>de</strong>l método. De la mismaforma po<strong>de</strong>mos acce<strong>de</strong>r directamente a los atributos o variables miembro <strong>de</strong>l objeto.// Establecemos la velocidad directamentemicoche.velocidad = 100;Tengo que <strong>de</strong>cir que ésta no es la manera más <strong>con</strong>veniente <strong>de</strong> proce<strong>de</strong>r. La mejor<strong>de</strong>cisión <strong>de</strong> diseño <strong>de</strong> clases es es<strong>con</strong><strong>de</strong>r las variables miembro para que no seanvisibles <strong>de</strong>s<strong>de</strong> fuera <strong>de</strong> la clase, y ofrecer los métodos necesarios para manejarlas. Así,siempre po<strong>de</strong>mos filtrar <strong>de</strong>s<strong>de</strong> los métodos los valores que toman los atributos. Noqueremos que nadie haga algo como micoche.velocidad = 1000. No sería nadabueno para el coche. Para especificar quién tiene acceso y quién no a las variablesmiembro y a los métodos utilizamos los modificadores <strong>de</strong> acceso. Hay tres modificadores:- Public. Permite el acceso sin restricciones.- Private. Sólo se permite el acceso <strong>de</strong>s<strong>de</strong> <strong>de</strong>ntro <strong>de</strong> la clase.- Protected. Permite sólo el acceso <strong>de</strong>s<strong>de</strong> <strong>de</strong>ntro <strong>de</strong> la clase o <strong>de</strong>s<strong>de</strong> unasubclase.Un poco más abajo veremos lo que es una subclase, pero antes veamos un ejemplo<strong>de</strong> uso <strong>de</strong> los modificadores.// Declaración <strong>de</strong> la clase cocheclass Coche {145


Atributos <strong>de</strong> la clase cocheprivate:int velocidad;// Métodos <strong>de</strong> la clase cochepublic:void acelerar(int velocidad);void frenar();};Al <strong>de</strong>clarar la clase <strong>de</strong> esta forma, el atributo velocidad no es accesible <strong>de</strong>s<strong>de</strong>fuera <strong>de</strong> la clase, es <strong>de</strong>cir, no podríamos hacer algo como micoche.velocidad=100.La unica forma <strong>de</strong> escribir en la variable coche es hacerlo mediante los métodosacelerar() y frenar().Al instanciar un objeto, nunca po<strong>de</strong>mos estar seguro <strong>de</strong> cual es su estado (el valor<strong>de</strong> sus atributos). Necesitamos un mecanismo que nos permita inicializar un objeto <strong>con</strong>los valores <strong>de</strong>seados. Esto po<strong>de</strong>mos hacerlo mediante el uso <strong>de</strong> <strong>con</strong>structores. Un<strong>con</strong>structor es un método especial <strong>de</strong> la clase que se ejecuta <strong>de</strong> forma automática alinstanciar la clase. Veámoslo sobre un ejemplo.// Declaración <strong>de</strong> la clase cocheclass Coche {// Atributos <strong>de</strong> la clase cocheprivate:int velocidad;// Métodos <strong>de</strong> la clase cochepublic:Coche();void acelerar(int velocidad);void frenar();};Coche::Coche() {// Inicializamos la velocidad a 0velocidad = 0;}Observa que el <strong>con</strong>tructor tiene exactamente el mismo nombre que la clase(incluida la primera letra mayúscula). Otra cosa a tener en cuenta es que un <strong>con</strong>structorno <strong>de</strong>vuelve nigún valor, ni siquiera void. Cuando instanciemos un objeto <strong>de</strong> la claseCoche, se ejecutará el código <strong>de</strong>l <strong>con</strong>structor <strong>de</strong> forma automática, sin necesidad <strong>de</strong>invocar el método <strong>con</strong>structor. En este caso, inicializa la variable velocidad a 0.Al igual que una clase pue<strong>de</strong> tener un <strong>con</strong>structor (en realidad pue<strong>de</strong> tener más <strong>de</strong>uno, pero no a<strong>de</strong>lantemos a<strong>con</strong>tecimientos), también pue<strong>de</strong> tener un <strong>de</strong>structor (en elcaso <strong>de</strong>l <strong>de</strong>structor sólo uno). En el <strong>de</strong>structor po<strong>de</strong>mos añadir código que será ejecutadojusto antes <strong>de</strong> que el objeto sea <strong>de</strong>struido <strong>de</strong> forma automática (sin necesidad <strong>de</strong>invocarlo), por ejemplo, por una salida fortuita <strong>de</strong>l programa. Un <strong>de</strong>structor se <strong>de</strong>claraigual que un <strong>con</strong>structor, pero anteponiendo el símbolo <strong>de</strong> til<strong>de</strong> <strong>de</strong>lante (~).// Destructor <strong>de</strong> la clase cocheCoche::~Coche() {if (velocidad != 0)velocidad = 0;}Este <strong>de</strong>structor se asegura <strong>de</strong> que si el coche está en movimiento, su velocidad sereduzca a 0.146


HerenciaNo sé <strong>de</strong> color tienes los ojos, pero puedo asegurar que <strong>de</strong>l mismo color que alguno<strong>de</strong> tus ascendientes. Este mecanismo biológico fue <strong>de</strong>scrito por Men<strong>de</strong>l (armado <strong>con</strong> unabuena dosis <strong>de</strong> paciencia y una gran cantidad <strong>de</strong> guisantes) y se llama herencia. Laherencia se transmite <strong>de</strong> padres a hijos, nunca al revés. En POO, la herencia funcionaigual, es <strong>de</strong>cir, en un sólo sentido. Mediante la herencia, una clase hija (llamadasubclase) hereda los atributos y los métodos <strong>de</strong> su clase padre. Vamos a verlo mejor <strong>con</strong>un ejemplo. Imaginemos que queremos crear una clase llamada CochePolicia, quea<strong>de</strong>más <strong>de</strong> acelerar y frenar pueda activar y <strong>de</strong>sactivar una sirena. Podríamos crear unaclase nueva llamada CochePolicia <strong>con</strong> los atributos y clases necesarios tanto para frenary acelerar como para activar y <strong>de</strong>sactivar la sirena. En lugar <strong>de</strong> eso, vamos a aprovecharque ya tenemos una clase llamada Coche y que ya <strong>con</strong>tiene algunas <strong>de</strong> lasfuncionalida<strong>de</strong>s que queremos incluir en CochePolicia. Veámoslo sobre un ejemplo.Class CochePolicia:public Coche {private:int sirena;}public:sirenaOn() { sirena=1; }sirenaOff() { sirena=0; }Observa cómo hemos implementado los métodos sirenaOn() y sirenaOff()<strong>de</strong>ntro <strong>de</strong> la misma clase. Esto es completamente válido, y se suele utilizar cuando elcódigo <strong>de</strong>l método es pequeño, como en este caso. Lo primero que nos llama la atención<strong>de</strong> la <strong>de</strong>claración <strong>de</strong> la clase es su primera línea. Tras el nombre <strong>de</strong> la clase, hemosañadido dos puntos seguidos <strong>de</strong> la clase padre, es <strong>de</strong>cir, <strong>de</strong> la que heredamos losmétodos y atributos. El modificador public hace que los métodos y atributos heredadosmantengan sus modificadores <strong>de</strong> acceso originales. Si queremos que se here<strong>de</strong>n comoprotegidos, utilizaremos el modificador protected. Ahora la clase CochePoliciaposee dos atributos, velocidad, que ha sido heredado y sirena, que ha sido <strong>de</strong>clarado<strong>de</strong>ntro <strong>de</strong> la clase CochePolicia. Con los métodos suce<strong>de</strong> exactamente igual. La clasehija ha heredado acelerar() y frenar(), y a<strong>de</strong>más le hemos añadido los métodossirenaOn() y sirenaOff(). Un objeto instancia <strong>de</strong> CochePolicia pu<strong>de</strong> utilizar sinningún problema los métodos acelerar() y frenar() tal y como hacíamos <strong>con</strong> losobjetos instanciados <strong>de</strong> la clase Coche.Es posible heredar <strong>de</strong> dos o más clases a la vez. Esto se llama herencia múltiple.Para heredar <strong>de</strong> más <strong>de</strong> una clase, las enumeramos separadas por comas <strong>de</strong>trás <strong>de</strong> losdos puntos. Sinceramente, a no ser que te guste el riesgo, la herencia múltiple, si no setiene cuidado, te traerá más dolores <strong>de</strong> cabeza que otra cosa.PolimorfismoEl polimorfismo es otra <strong>de</strong> las gran<strong>de</strong>s característica <strong>de</strong> la POO. La palabrapolimorfismo <strong>de</strong>riva <strong>de</strong> poli (multiples) y <strong>de</strong>l término griego morfos (forma). Es <strong>de</strong>cir,múltiples formas.147


Vamos a verlo <strong>con</strong> un ejemplo. Supongamos que queremos dotar al método frenar<strong>de</strong> más funcionalidad. Queremos que nos permita reducir hasta la velocidad quequeramos. Para ello le pasaremos como parámetro la velocidad, pero también sería útilque frenara completamente si no le pasamos ningún parámetro. El siguiente códigocumple estos requisitos.// Declaración <strong>de</strong> la clase cocheclass Coche {// Atributos <strong>de</strong> la clase cocheint velocidad;// Métodos <strong>de</strong> la clase cochevoid acelerar(int velocidad);void frenar();void frenar(int velocidad);};void Coche::frenar() {// Ponemos a 0 el valor <strong>de</strong>l atributo velocidadvelocidad = 0;}void Coche::frenar() {// Ponemos a 0 el valor <strong>de</strong>l atributo velocidadvelocidad = 0;}void Coche::frenar(int velocidad) {// Reducimos la velocidadif (velocidad < this.velocidad)this.velocidad = velocidad;}Como ves tenemos dos métodos frenar. En C, esto causaría un error. Cuandollamemos al método frenar(), C++ sabrá cual tiene que ejecutar <strong>de</strong>pendiendo <strong>de</strong> si lollamamos <strong>con</strong> un parámetro <strong>de</strong> tipo entero o sin parámetros. Esto que hemos hecho sellama sobrecarga <strong>de</strong> métodos. Po<strong>de</strong>mos crear tantas versiones diferentes <strong>de</strong>l métodosiempre y cuando sean diferentes.El <strong>con</strong>structor <strong>de</strong> una clase también pue<strong>de</strong> ser sobrecargado.Punteros y memoriaC++ aña<strong>de</strong> un nuevo operador para reservar memoria y crear punteros. Se trata <strong>de</strong>loperador new. Este operado reserva la memoria para <strong>con</strong>tener un tipo <strong>de</strong> datos <strong>de</strong>terminado,y <strong>de</strong>vueve y puntero a la memoria reservada.// Creamos un puntero <strong>de</strong> tipo coche// Ojo, no creamos un objeto coche, sólo un punterocoche *micoche;// creamos un objeto coche y <strong>de</strong>volvemos// un puntero al objetomicoche = new Coche;// Llamada a un método <strong>de</strong>l objetomicoche->acelera(100);148


liberar memoria<strong>de</strong>lete coche;Lo primero que hemos hecho es <strong>de</strong>clara un puntero <strong>de</strong> tipo coche. En este momento nohemos creado ningún objeto, sino un puntero. El encargado <strong>de</strong> crear el objeto es el operadornew, que a<strong>de</strong>más <strong>de</strong> crear la instancia <strong>de</strong>vuelve un puntero al objeto recién creado.La siguiente línea llama al método acelera(), pero observa que en vez <strong>de</strong> un punto usamoslos caracteres -> (signo <strong>de</strong> resta seguido <strong>de</strong>l signo mayor que). Siempre que trabajamos <strong>con</strong>un puntero a un objeto sustituimos el punto por ->.Por último, para liberar los recursos reservados por new, utilizamos el operador <strong>de</strong>lete,que va a liberar la memoria ocupada por el objeto Coche.Hemos visto en éste apéndice las características más importantes <strong>de</strong> C++ relacionadas<strong>con</strong> la POO, sin embargo este lenguaje es mucho más extenso y no po<strong>de</strong>mos hacer unacompleta introducción por problemas <strong>de</strong> espacio. Si has sido capaz <strong>de</strong> asimilar la información<strong>de</strong> este apéndice me doy por <strong>con</strong>tento por ahora, aunque mi <strong>con</strong>sejo es que sigasprofundizando en este lenguaje.149


ApéndiceCRecursosBibliografíaProgramaciónProgramación orientada a objetos <strong>con</strong> C++. 2ª Edición.Fco. Javier CeballosEd. Ra-ma.ISBN: 84-7897-268-4<strong>Programacion</strong> en C.Mitchell Waite/Stephen PrataEd. Anaya.ISBN: 84-7614-374-5Programación en Linux <strong>con</strong> ejemplos.Kurt WallEd. Prentice Hall.ISBN: 987-9460-09-XVisual C++ 6Jon Bates/Tim TomkinsEd. Prentice Hall.ISBN: 8483220776Programación <strong>de</strong> vi<strong>de</strong>ojuegosWindows Game Programming for Dummies, Se<strong>con</strong>d Edition.André LaMothe.Ed. IDG Books.ISBN: 0764516787OpenGL Game Programming.Dave Astle/kevin Hawkins/André LaMothe.Ed. Learning ExpressISBN: 0761533303AI Techniques for Game Programming.150


Mat Buckland.Ed. Learning ExpressISBN: 193184108XBeginning Direct3D game programming, 2nd Edition.Wolfgang f. Engel.Ed. Learning ExpressISBN: 193184139XGráficosComputer Graphics, principles and practice.Foley/van Dam/Feiner/HughesEd. Addison WesleyISBN: 0-201-12110-7OpenGL(R) Programming Gui<strong>de</strong>: The Official Gui<strong>de</strong> to Learning OpenGL, Version 1.2 (3rdEdition).Mason Woo y otrosEd. Addison-WesleyISBN: 0201604582OpenGL SuperBible, Se<strong>con</strong>d Edition (2nd Edition).Richard S. Wright Jr./Michael R. Sweet.Ed. Waite Group PressISBN: 1571691642Programación multimedia avanzada <strong>con</strong> DirectXConstantino Sánchez BallesterosEd. Ra-maISBN: 8478973427EnlacesProgramaciónhttp://www.dcp.com.ar/http://www.programacion.net/http://www.planetiso.com/http://www.mindview.net/Books/TICPP/ThinkingInCPP2e.htmlProgramación <strong>de</strong> vi<strong>de</strong>ojuegoshttp://www.flipco<strong>de</strong>.com/151


http://www.game<strong>de</strong>v.net/http://www.gamasutra.com/http://gpp.netfirms.com/http://www.gametutorials.com/http://www-cs-stu<strong>de</strong>nts.stanford.edu/~amitp/gameprog.htmlGráficoshttp://nehe.game<strong>de</strong>v.net/http://www.opengl.org/http://www.sha<strong>de</strong>rx.com/direct3d.net/in<strong>de</strong>x.htmlhttp://www.geocities.com/Sili<strong>con</strong>Valley/Way/3390/in<strong>de</strong>x.html152


Índice AlfabéticoAalgoritmos <strong>de</strong> búsqueda, 106alpha-blending, 32atexit, 25Audio, 51Bbit blitting, 27CC++, 143CD-ROM, 56Chchunk, 70CMix_Fa<strong>de</strong>OutChannel, 71Mix_Fa<strong>de</strong>OutMusic, 73Mix_FreeChunk, 70Mix_FreeMusic, 73Mix_GetMusicType, 74Mix_HaltChannel, 71Mix_HaltMusic, 73Mix_LoadMUS, 73Mix_LoadWAV, 70Mix_OpenAudio, 69Mix_Pause, 71Mix_Paused, 71Mix_PausedMusic, 74Mix_PauseMusic, 73Mix_PlayChannel, 70Mix_PlayChannelTimed, 70Mix_Playing, 71Mix_PlayingMusic, 74Mix_PlayMusic, 73Mix_Resume, 71Mix_ResumeMusic, 73Mix_RewindMusic, 73Mix_SetMusicPosition, 73Mix_VolumeChunk, 70Mix_VolumeMusic, 73clases, 144códigos sym, 40colisiones, 81Dnew, 148niveles, 119N<strong>de</strong>lete, 54, 149Disparos, 108objetos, 144Oenemigos, 117event, 40explosiones, 108EGGame loop, 10Gestión <strong>de</strong> eventos, 38herencia, 147IMG_Load, 66inteligencia artificial, 105linux, 141HILMmapas, 98Mappy, 98máquinas <strong>de</strong> estado, 107Mix_AllocateChannels, 70Mix_CloseAudio, 69Mix_Fa<strong>de</strong>InChannel, 71Mix_Fa<strong>de</strong>InChannelTimed, 71Mix_Fa<strong>de</strong>InMusic, 73Pantalla inicial, 121polimorfismo, 147puntación, 121punteros, 148Ratón, 39red neuronal, 105Scrolling, 100<strong>SDL</strong>, 23<strong>SDL</strong>_ActiveEvent, 46<strong>SDL</strong>_AudioSpec, 51<strong>SDL</strong>_BlitSurface, 30<strong>SDL</strong>_BUTTON, 44<strong>SDL</strong>_CD, 56<strong>SDL</strong>_CDClose, 57<strong>SDL</strong>_CDEject, 57<strong>SDL</strong>_CDName, 56<strong>SDL</strong>_CDNumDrives, 56<strong>SDL</strong>_CDOpen, 57<strong>SDL</strong>_CDPause, 57<strong>SDL</strong>_CDPlay, 57<strong>SDL</strong>_CDPlayTracks, 57<strong>SDL</strong>_CDResume, 57<strong>SDL</strong>_CDStatus, 57<strong>SDL</strong>_CDStop, 57<strong>SDL</strong>_CDtrack, 56<strong>SDL</strong>_CloseAudio, 52PRS153


<strong>SDL</strong>_ConvertSurface, 37<strong>SDL</strong>_CreateRGBSurface, 27<strong>SDL</strong>_Delay, 61<strong>SDL</strong>_EnableUNICODE, 43<strong>SDL</strong>_ExposeEvent, 46<strong>SDL</strong>_FillRect, 31<strong>SDL</strong>_Flip, 31<strong>SDL</strong>_FreeSurface, 31<strong>SDL</strong>_FreeWAV, 52<strong>SDL</strong>_GetClipRect, 37<strong>SDL</strong>_GetError, 25<strong>SDL</strong>_GetKeyState, 43<strong>SDL</strong>_GetTicks, 60<strong>SDL</strong>_GetVi<strong>de</strong>oInfo, 38<strong>SDL</strong>_image, 66<strong>SDL</strong>_Init, 24<strong>SDL</strong>_InitSubSystem, 25<strong>SDL</strong>_JoyAxisEvent, 45<strong>SDL</strong>_JoyButtonEvent, 45<strong>SDL</strong>_JoystickClose, 47<strong>SDL</strong>_JoystickEventState, 45<strong>SDL</strong>_JoystickGetAxis, 48<strong>SDL</strong>_JoystickGetButton, 48<strong>SDL</strong>_JoystickName, 47<strong>SDL</strong>_JoystickNumAxes, 48<strong>SDL</strong>_JoystickNumButtons, 48<strong>SDL</strong>_JoystickOpen, 47<strong>SDL</strong>_JoystickOpened, 48<strong>SDL</strong>_JoystickUpdate, 48<strong>SDL</strong>_KeyboardEvent, 40<strong>SDL</strong>_keysym, 40<strong>SDL</strong>_LoadBMP, 30<strong>SDL</strong>_LoadWAV, 52<strong>SDL</strong>_MapRGB, 31, 33<strong>SDL</strong>_mixer, 69<strong>SDL</strong>_MouseButtonEvent, 44<strong>SDL</strong>_MouseMotionEvent, 44<strong>SDL</strong>_NumJoysticks, 47<strong>SDL</strong>_OpenAudio, 52<strong>SDL</strong>_PauseAudio, 52<strong>SDL</strong>_PixelFormat, 27<strong>SDL</strong>_PollEvent, 40<strong>SDL</strong>_Quit, 25<strong>SDL</strong>_QuitEvent, 45<strong>SDL</strong>_QuitSubSystem, 25<strong>SDL</strong>_Rect, 30<strong>SDL</strong>_ResizeEvent, 46<strong>SDL</strong>_SetAlpha, 36<strong>SDL</strong>_SetClipRect, 36<strong>SDL</strong>_SetColorKey, 32<strong>SDL</strong>_SetVi<strong>de</strong>oMo<strong>de</strong>, 26<strong>SDL</strong>_Surface, 30<strong>SDL</strong>_ttf, 62<strong>SDL</strong>_UpdateRect, 31<strong>SDL</strong>_Vi<strong>de</strong>oInfo, 38<strong>SDL</strong>_WM_GetCaption, 60<strong>SDL</strong>_WM_I<strong>con</strong>ifyWindow, 60<strong>SDL</strong>_WM_SetCaption, 60<strong>SDL</strong>_WM_SetI<strong>con</strong>, 60sistemas basados en reglas, 106sprites, 76Teclado, 39temporización, 120this, 145tiles, 93Timming, 60Transparencias, 32TTF_Close, 63TTF_Font, 63TTF_GetFontStyle, 63TTF_Init, 62TTF_OpenFont, 62TTF_OpenFontIn<strong>de</strong>x, 62TTF_Quit, 62TTF_Ren<strong>de</strong>rText_Blen<strong>de</strong>d, 63TTF_Ren<strong>de</strong>rText_Sha<strong>de</strong>d, 63TTF_Ren<strong>de</strong>rText_Solid, 63TTF_SetFontStyle, 63vidas, 121Vi<strong>de</strong>o, 26Visual C++, 137Window Manager, 60TVW154

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!