RECREACIÓN DEL JUEGO MANIC MINER PARA MOVILES
←
→
Transcripción del contenido de la página
Si su navegador no muestra la página correctamente, lea el contenido de la página a continuación
ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA TITULACIÓN: INGENIERÍA INFORMÁTICA RECREACIÓN DEL JUEGO MANIC MINER PARA MOVILES AUTOR: JOSÉ MANUEL GARCÍA MAESTRE DIRIGIDO POR: JOSÉ RAMÓN PORTILLO FERNÁNDEZ DEPARTAMENTO: MATEMÁTICA APLICADA I Sevilla, Septiembre de 2010
ÍNDICE 1. Introducción ………………………………………………. 4 1.1 Introducción a los videojuegos ……………………. 4 1.2 Introducción a las recreaciones …………………… 6 1.3 Introducción a los videojuegos para móviles .. 8 2. Definición de objetivos ………………………………………… 10 3. Análisis de antecedentes y aportaciones realizadas 12 3.1. Análisis de antecedentes ……………………………... 12 3.1.1. Manic Miner de 1983 ……………………………. 12 3.1.2. Conceptos para la creación de videojuegos 14 3.2 Aportaciones realizadas ……………………………… 18 4. Análisis de requisitos ……………………………………… 20 5. Diseño ……………………………………………………… 24 6. Implementación ……………………………………………….. 31 6.1. Introducción ………………………………………………. 31 6.2. Tamaño de los gráficos y programas usados 32 2
para las imágenes 6.3. Clase MidletInicio ……………………………………. 38 6.4. El game loop del juego ……………………………. 40 6.5. Implementación del salto ……………………….. 48 6.6. Implementación de las colisiones ……………… 50 6.7. Implementación de la barra de aire ……….. 67 7. Manual de usuario .......………………………………….. 71 8. Pruebas ……………………………………………………………... 72 9. Conclusiones ……………………………………………………. 73 10. Bibliografía ……………………………………………………. 74 3
1. INTRODUCCIÓN 1.1 Introducción a los videojuegos Desde el principio de los tiempos el hombre ha necesitado algo para divertirse, este hecho se da también en los animales. Los videojuegos ya no son un fenómeno aislado, es difícil ir a un sitio y no encontrarse a alguien jugando con una consola portátil, ya sea un móvil. A lo largo del tiempo se ha creado muchas clases de juegos muchas de ellos se han pasado a los videojuegos como los juegos de deportes, juegos de estrategia como el ajedrez o las damas, el parchís o las cartas. Los videojuegos es una forma de diversión que mueve mucho dinero, así, en España, los ingresos derivados del uso de los videojuegos mueve más dinero que el cine y la música juntos. Es un mercado inmenso que está creciendo cada año a pasos agigantados. Grandes compañías ganan grandes sumas de dinero en este mercado como SNK, Sega, Nintendo, etc. y otras crecen rápidamente como Sony desde el lanzamiento de PlayStation, avanzando actualmente con la psp. En 2009, este sector generó 55.000 millones de dólares (41.200 millones de euros) solo en venta de software. En hardware recaudaron 22.000 millones. Si se juntan los dos apartados, los 77.000 millones obtenidos se acercan cada vez más a la caja de Hollywood, que en 2009 estuvo en los 85.000 millones de dólares en todo el mundo. http://www.navegandoxlared.es/?p=748 http://www.elpais.com/articulo/ocio/industria/videojuego/crece/Espana/venta/consolas/elpe puteccib/20080410elpciboci_1/Tes 4
1. Venta de videojuegos en España en millones En la venta de videojuegos España es la cuarta a nivel Europeo detrás de Reino Unido, Alemania y Francia. El principal mercado de España en videojuegos es del extranjero y aunque se vende mucho no existe grandes compañías que arriesguen en este ámbito en España, por lo que existen riesgos pero también oportunidades de que el país adquiera dinero en este ámbito, ya existen estudios especializados para formar a personas para crear videojuegos en Madrid y fomentar su desarrollo. En lo que se refiere a sistema portátil todo comenzó con la GameBoy de Nintendo, y desde entonces casi todo el mundo se ha adentrado en el espacio portátil. La diferencia de estos, es que los juegos portátiles están al alcance de todos, el hardware es accesible y muy asequible. También se han unido a este mercado los móviles que solo se usaban para llamar y ahora poseen 5
un repertorio completo de juegos y muchas compañías apuestan por este mercado. Los juegos para móviles son llevados continuamente en nuestra vida diaria, para la comunicación del trabajo, esto ofrece ventajas sobre los videoconsolas portátiles que solo son usados para jugar y no siempre se lleva en el bolsillo. Cualquier persona que lleve un móvil y sienta necesidad de divertirse puede utilizar su teléfono aunque el sistema de juego sea peor que el videojuego portátil. 1.1 Introducción a las recreaciones En lo que se refiere a las recreaciones de juegos ya inventados, muchas empresas importantes han realizado esto debido a que sería un éxito garantizado si el juego es popular, además los costes y los riegos de un desarrollo se reducen en gran medida cuando se adapta un juego ya terminado. Un problema al adaptar un juego de un sistema a otro son los gráficos: convertir gráficos creados con píxeles de una pantalla a otra puede ser un gran problema porque los gráficos normalmente se han creado teniendo muy en cuenta las especificaciones de la plataforma de destino. El diseñador está muy al tanto de las limitaciones del sistema y de cómo estas afectan a sus imágenes. Todas las particularidades del sistema tienen su efecto en el diseño con pixeles. Algunos sistemas han limitado las paletas, y han reducido el número de colores disponibles. Otros no pueden mover grandes imágenes, así que los objetos individuales tienen que ser pequeños. Los sistemas cuya resolución es menor exigen un gran esfuerzo para hacer que los objetos que aparecen en pantalla sean más pequeños sin utilizar tantos detalles que se vuelvan irreconocibles. Los antiguos sistemas que utilizaban cartuchos limitaban el potencial por su escasa capacidad de almacenamiento. 6
Los teléfonos tienen una gran variedad de resoluciones y paletas, y el desarrollador tiene que decidir entre crear gráficos para el mínimo común denominador o recrear la imagen para cada uno de los tamaños de pantalla diferentes. La primera opción es la peor, ya que los propietarios de los teléfonos móviles nuevos y potentes pueden encontrarse con que los juegos se reproducen en ventanas minúsculas dentro de sus enormes pantallas. Grandes compañías han decidido adaptar sus juegos para otros sistemas así tenemos el salto de Yoshi’s Island SNES a Yoshi’s Island GBA, Metal Slug arcade a Metal Slug para teléfono móvil. Ilustración 1 Metal Slug Móvil Ilustración 2 Metal Slug Arcade Otros juegos han sido también recreados por fans de usuarios programadores para PC con mejores gráficos pero con una jugabilidad igual de buena que en la versión original, como puede ser Maniac Mansion de LucasArts para PC llamado Maniac Mansion Deluxe. 7
2 Goody para Spectrum 3 Remake de Goody Para PC 3 Maniac Mansion (PC) 5 Maniac Mansion Deluxe (PC) 1.3 Introducción a los videojuegos para móviles El botón de acción, que en casi todos los mandos de las consolas se controla con la otra mano, en los móviles está en el centro del panel de control. Como es difícil pulsar ese botón al mismo tiempo que se está pulsando una dirección muchos juegos lo que hacen es utilizar el teclado numérico como mando alternativo. No es un sistema ideal pero si lo suficientemente bueno para que la gente se descargue y disfrute de cientos de juegos. El teléfono móvil es una plataforma única para los desarrolladores. Los juegos no se lanzan para una o dos plataformas, como en el caso de las consolas, ni se desarrollan con unas especificaciones de hardware mínimas o máximas, como en los PC. Cada móvil tiene una resolución de 8
pantalla diferente y un hardware diferente con un sistema operativo diferente por lo que los juegos que se desarrollen tienen que verse y poder jugarse bien en cientos de teléfonos. Con cientos de sistemas, los gráficos deben optimizarse por familias de teléfonos que tengan una resolución de pantalla similar. Esto es solo los móviles de Nokia: Los juegos deben ser lo suficientemente cortos como para que los consumidores jueguen durante breves periodos de tiempo libre, por ejemplo, entre dos clases o reuniones, o mientras esperan en una cola. Puede haber nuevas formas de hacerlo y, si se llevaran a cabo, podrían expandir el mercado de los juegos exponencialmente. Gracias al bluetooth o la tecnología de internet para móviles podría haber juegos que se jugasen multijugador. 9
2. DEFINICIÓN DE OBJETIVOS El objetivo principal del proyecto consiste en la elaboración de una recreación del juego Manic Miner de Spectrum para móviles, con la misma jugabilidad pudiéndose mejorar los gráficos. A continuación desarrollamos una serie de subobjetivos: - Un objetivo importante es que funcione en la mayoría de los teléfonos móviles de diferentes generaciones, ya que a diferencia de otros aparatos eléctricos, existe una gran cantidad diferentes de móviles más antiguos que aun se venden por ser más económicos y más caros con mayor prestaciones y pantallas más grandes que los antiguos. - El teclado debe ser fácil de manejar. - Los gráficos se pueden mejorar, para que sea más atractivo al mercado de los juegos actuales para móviles - Conseguir que el muñeco se choque. - Conseguir que los colores no molesten a la vista. - Conseguir que el usuario diferencie entre el fondo y los objetos con los que el personaje puede colisionar. 10
- Tanto la jugabilidad como la dificultad debe ser idéntica al del juego original ya que es una recreación. El Spectrum fue uno de los ordenadores más vendidos en el mercado doméstico, en la época de los 80, haciendo la delicia de miles de aficionados a los juegos y la informática. Aun hoy en día existen fans que coleccionan y juegan a juegos con la ayuda de emuladores. Hubo juegos muy buenos y populares como el Manic Miner, Goody, Lode Runner, Snake, etc. Así que es un proyecto interesante, apoyarme en juegos que se hicieron que tuvieron éxito para aprender y conseguir un juego con éxito en el mercado actual de la telefonía móvil, por la popularidad, la jugabilidad y el entretenimiento de aquellos juegos de 2 dimensiones, además se siguen usando estas 2 dimensiones en los juegos de móvil más que en ninguna otro sistema. 11
3. ANÁLISIS DE ANTECEDENTES Y APORTACIÓN REALIZADA 3.1 ANALISIS DE ANTECEDENTES 3.1.1 Manic Miner de 1983 El juego original Manic Miner se realizo para ZX Spectrum por Matthew Smith, lanzado por Bug Byte en 1983 y relanzado más tarde por Software Projects. El juego se inspiro en el juego Miner2049er para Atari 800. Manic Miner nos pone en el papel de un minero en el que tenemos que recorrer diferentes salas e ir cogiendo llaves para salir de cada una de ellas. También nos encontrábamos con animales, monstruos e incluso objetos que se mueven y que teníamos que esquivar. Esto nos lo hace más difícil, y más divertido, provocando la muerte instantánea si nos chocamos con ellos o si caíamos de una altura muy grande. Además las salas tenían un oxigeno que se iba agotando poco a poco provocándonos la muerte si llegábamos a 0. 12
3. Niveles de Manic Miner (ZX Spectrum) Manic Miner contaba con 20 niveles cada uno de ellos con diferentes niveles de dificultad y solo 3 vidas, por lo que es difícil pasarse el juego, aunque así es muy entretenido. Manic Miner hacía un alarde de coloridos, diseño de niveles, efectos de sonido y música que lo elevó a los más alto en el mundo de los videojuegos, 4. Pantalla de Manic Miner posteriormente dio paso a una secuela que aumentaba considerablemente el número de 13
niveles, llamada “Jet Set Willy”. También destacar que Manic Miner fue el primer juego de ZX Spectrum que acompañaba una melodía al jugar, conocida como “En el Salón del Rey de la Montaña”. Fue emplazado en el puesto 25 de “Your Sinclair oficial top 100” de los juegos de Spectrum y fue el ganador de un “Golden Joystick Award” por el mejor juego arcade por la revista “Computer & video games” en la edición de 1983 y nombrado el tercer juego mejor del año por la misma revista. 3.1.2. Conceptos para la creación de videojuegos En un videojuego además del código de la lógica, tenemos que hacer que las animaciones se muevan al pulsar una tecla o que un gráfico se mueva hacia un lado o hacia otro. Hay que distinguir entre imagen y sprite primero. Ya en la década de los 80 se usaban sprites para desarrollar los videojuegos. Un sprite es un conjunto de imágenes que representan un personaje, u objeto el cual puede tener una animación interna o no tenerla. Generalmente son utilizados para producir una animación interna, como un personaje corriendo, alguna expresión facial o un movimiento corporal. 5. Sprite del personaje principal del juego desarrollado El paso de una imagen a otra del sprite da lugar a la animación interna, haciendo parecer que el personaje anda, solo faltaría añadirle que el 14
sprite se desplace por la pantalla. Para que el sprite del personaje se mueva por la pantalla no hace falta que este animado, pero si no lo animamos se desplazaría por la pantalla sin mover las piernas. Eso es lo que he usado para crear los personajes y es lo que usa muchos juegos de 2 dimensiones, para crear el mapa he usado una técnica que consiste en dividir el mapa en cuadrados: Dentro de cada cuadrado pondremos una serie de elementos llamado tiles (baldosas en español). 6. Tiles del primer nivel del juego Manic Miner desarrollado A cada tile le corresponde un numero, por ejemplo, en el mapa de tiles de arriba imagen con numero 6, el árbol verde seria el tile número 2, el 15
ladrillo, el numero 3…. El 0 se reservaría para decir que no hay nada. Y se irían colocando en una matriz para generar el mapa. Por ejemplo, suponiendo una matriz de 5x5 y rellenándola: int tilesMapa [] ={{2,0,0,0,3} {3,1,0,0,3} {3,0,0,0,3} {3,0,0,0,3} {1,1,1,1,1}}; Creando o utilizando un método que coloque las imágenes de los tiles correctamente según la matriz y dibujándolo quedaría: A la hora de desarrollar el código del juego, he de decir que el juego se ejecuta en un bucle infinito, igual que todos los juegos, llamado “game loop”. En cada vuelta del bucle, se comprueba si el usuario ha pulsado una tecla, las acciones a tomar de la tecla pulsada, las animaciones internas, comprobar las colisiones y su acción dependiendo de con que choque, y al final de cada bucle se pinta en la pantalla: 16
7. Estructura de un game loop 17
3.2 APORTACIONES REALIZADAS El juego que he desarrollado ya no funciona en un único sistema como los juegos de Spectrum sino que funciona en una gran cantidad de móviles, he aprovechado el hecho de tener que coger las imágenes del juego original y añadirle mas colores. También he introducido un fondo a los niveles, de manera que visualmente se pueda observar en qué lugar se encuentra el personaje. El juego original lo mostraba con un texto que ponía debajo, he decidido quitar dicho texto dado e introducir un fondo, de manera que el usuario pueda imaginárselo. Decidí no añadir las letras del lugar donde estaba dado que en algunos móviles quizás no podrían mostrarse todo el texto por la longitud del nombre del lugar, ya que dependiendo del móvil las letras son mas grandes o mas chicas y pueden caber en la pantalla o no caber. Dado que cada móvil posee una resolución distinta, hay 2 formas de desarrollar los gráficos para móvil: -La primera consiste en hacer los gráficos para que funcione en el móvil con la pantalla de menor, de manera que si tenemos un móvil con mayor resolución se muestre más elementos, pero se siga viendo todo lo importante. -La segunda consiste en desarrollar gráficos diferentes para diferentes resoluciones de móviles. 18
Evidentemente la segunda opción es mejor, ya que el usuario verá las imágenes acorde a su pantalla, pero opte por la primera opción ya que no tengo un equipo de diseñadores gráficos que me hagan dicho trabajo que requiere mucho tiempo, dado que los dibujos es a nivel de pixeles y no se pueden aumentar automáticamente y que quede bien en móviles de mayor tecnología. Además con la primera opción solo necesito desarrollar una versión para todos los móviles y no una para cada móvil, con sus gráficos de diferente tamaño. Esta misma opción uso Sega con su Master System de mayor resolución que la Game Gear en el juego Sonic. Sega adaptó el sonic The hedgehog 2 de Master System a Game Gear usando los sprites del anterior. 8. Sonic The Hedgehog 2 (Master System) Sonic The Hedgehog 2 (Game Gear) Como se observa en la imagen, se ve mucho más el escenario en la imagen de Master System que en la Game Gear por la resolución y al no cambiar el tamaño de los sprites y tiles del escenario. Del mismo modo si el juego desarrollado lo ponemos en un móvil con mayor resolución se verá más el escenario que con uno de menor resolución ya que no he hecho una versión del juego para cada resolución de móvil o tipos de móvil. 19
4. ANÁLISIS DE REQUISITOS Requisito 1 Uno de los requisitos para que el juego funcione en una gran cantidad de móviles con diferentes sistemas operativos es necesario que el lenguaje de programación sea J2ME, ya que está pensado para funcionar tanto en móviles o PDAs con cualquier sistema operativo. Requisito 2 El juego debe tener la misma jugabilidad que el original, para ello me fijo cuantos pixeles se mueve por fotograma el juego original, el tamaño de todos los elementos del juego (tiles, sprites, anchura del juego, etc.). De esta manera podemos conseguir un juego con los movimientos idénticos al original y posición de sus elementos, y por tanto la misma jugabilidad. Requisito 3 En lo que se refiere al objetivo de facilidad de manejo, es una de las desventajas de los móviles frente a las consolas portátiles ya que los paneles de control son muy malos: son rígidos y prácticamente inútiles para juegos de acción. Es casi imposible mover el dedo pulgar por un disco plano con precisión y velocidad y como todo jugador sabe, quien no controla el juego, muere. Por eso se necesita que además de controlar el personaje con el disco se pueda controlar con los números: 6 para la derecha 4 para la izquierda y 5 para saltar, ya que están más separadas en el teléfono. 20
Requisito 4 Las animaciones deben ser iguales parecidas, o mejores que la original, ya que estamos desarrollando una recreación y el aspecto visual debería ser parecido y que no parezca otro juego. Requisito 5 Para conseguir que los colores no molesten a la vista, hay que elegir colores parecidos al juego original y colores oscuros. Requisito 6 Para conseguir que el usuario diferencie entre el fondo y los objetos con los que el personaje puede colisionar hay que usar mas contraste de color entre el fondo y los objetos de colisión Requisito 7 Hay varios tipos de objetos que dependiendo de la colisión con ellos el personaje hará una cosa u otra. A continuación mostramos que hace cada objeto, desarrollando este requisito funcional: Hay un tile al que llamaremos bloque que el personaje chocara con el siempre. 9. Bloque 21
Existe otro tile al que llamaremos suelo y que el personaje no choca a no ser que este apoyado encima de él, es decir a no ser que choque con la cara de arriba, de ahí el nombre de 10. Suelo suelo que le damos. Existe otro suelo que he llamado “suelo que se cae” que al pasar sobre él se va desbaratando a medida que pasa el tiempo sobre él, hasta que el personaje se queda sin suelo, 11. Suelo que se cae provocando que el personaje caiga hacia abajo. Existe otro tipo de objeto, un tile que he llamado “muerte” que al colisionar contra el provoca la muerte del personaje. 12. Muerte Tile llamado llave que si el personaje colisiona con el desaparecerá del escenario haciendo entender que se ha cogido una llave más. 13. Llave Este objeto es un tile parecido al suelo, solo se puede colisionar con la parte superior pero provocando que el personaje se deslice, lo he llamado “cinta” de cinta 14. Cinta transportadora. 22
Existe un tipo de sprite que son los enemigos y que al chocar con el provocan la muerte del personaje 15. Monstruo Esto es una puerta, que debería ser un sprite, ya que ocupa más que un solo tile de 8x8 pixeles, esta puerta ocupa 16x16 pixeles. Al colisionar el personaje con él pasara de nivel si el personaje tiene todas las llaves. Requisito 9 La puerta debe abrirse cuando el personaje coja todas las llaves del nivel. Requisito 10 Coger las llaves y pasarse el nivel da una serie de puntos. Deben ser iguales o parecidos al original: 100 puntos por cada llave. 18 puntos por cada porcentaje de la barra de aire al final. Requisito 11 Al pasarse el nivel se ilumina el escenario con colores y se disminuye la barra para conseguir los puntos por el aire sobrado. 23
5. DISEÑO Antes de empezar a desarrollar el diseño estuve investigando a ver qué programa era el adecuado para realizar videojuegos para móviles con J2ME, busque en foros de desarrollo y descubrí que netBeans a diferencia de Eclipse tenía una interfaz para desarrollar los gráficos del juego, así que me decante por él, ya que me ahorra mucho trabajo al verlo visualmente en vez de crear la matriz del mapa yo. Aquí muestro algunas capturas de interfaces del desarrollo del escenario y los enemigos en netBeans del juego una vez completado: 16. Pantalla de netBeans para editar el diseño de niveles y personajes El tipo de fichero dedicado al desarrollo gráfico, está situado en la pestaña Projects y viene indicado con un símbolo de un mando de 24
videojuego. Este fichero es una clase que se genera automáticamente con los elementos que introduzcamos en la interfaz gráfica de la derecha. Se puede ver el código pulsando en la pestaña source una vez se ha abierto el fichero aunque solo se puede modificar la parte no generada automáticamente. O también se puede construir gráficamente con la pestaña Game Builder, para que se genere automáticamente el código. La interfaz gráfica principal de netBeans para desarrollar juegos para móviles se muestra dividida en tres zonas: -Scenes: Es la composición de un escenario entero, con los sprites y tiles colocados en una zona. Cuando creamos una escena colocaremos los TiledLayer y sprites en un escenario. -TiledLayer: Es donde se compone los mapas de tiles, es decir, es donde se hará la matriz donde se colocará los tiles, pero visualmente, es una de las grandes ventajas de esta interfaz, ya que no tengo que hacerlo antes en papel y puedo ver cómo queda o modificar algo visualmente sin tener que contar que fila y columna es la que falla. Además se pueden añadir tiles animados. -Sprites: Es donde se colocan los sprites de los personajes y donde se edita su animación. A continuación muestro imágenes de los editores de cada una de estas tres zonas: 25
17. Editor de Sprites 18. Editor de TiledLayer 26
19. Editor de Scene En el editor se puede tanto colocar los Sprites y tiles como indicar en qué capa se encuentra dicho tile o Sprite de manera que quede dibujado más hacia el fondo o hacia afuera. 27
28
En este diseño la clase que se ejecuta en primer lugar será MidletInicio, esta creara un hilo con la ejecución del método run(), de MinerCanvas, el cual iniciará los parámetros iniciales para configurar el escenario inicial y ejecutara el game loop. En el game loop tendremos que comprobar las teclas pulsadas, mover el sprite del personaje dependiendo de la tecla pulsada, comprobar si existe colisión al moverse el personaje, actuando en cada caso dependiendo del objeto con el que colisione, sumaremos los puntos obtenidos si cogemos una llave o pasamos el nivel, se comprobara si se ha muerto el personaje, etc. En lo que se refiere al nivel en el game loop, el nivel será el encargado de pintar todo, en cada ciclo del game loop, ya que es el que posee toda la información necesaria. El nivel tendrá información sobre la posición de los objetos al inicio del nivel, los monstruos que existen en dicho nivel, las llaves, etc. Todo dependiendo del nivel en que nos encontremos, para ello he creado una clase llamada FabricaNivel que obtendrá todo lo necesario dependiendo del nivel que nos encontremos usando el parámetro “nivel” de tipo entero. FabricaNivel se comunicará con otra fábrica que es generada por NetBeans automáticamente al pintar los escenarios y colocar los sprites, aunque a diferencia de la fábrica que hemos creado, esta no usa un 29
número indicando el nivel para devolver los sprites y tiles sino que cada método tiene un nombre distinto para cada Sprite y Personaje. La clase Monstruo hará que el monstruo se mueva, independizando esta tarea de MinerCanvas y de Nivel. 30
6. IMPLEMENTACIÓN 6.1. Introducción “J2ME es un entorno de producción para pequeños dispositivos que permite la ejecución de programas creados en Java. Una de las principales capacidades que añade esta tecnología a nuestros terminales es la posibilidad de descargar y ejecutar juegos con una calidad razonable. Hoy, nuestros teléfonos móviles corren auténticos sistemas operativos. El más conocido quizás es Symbian, que es el corazón de gran cantidad de móviles, como los Nokia, Sony-Ericsson, Motorola y otros. MIDP (Mobile Information Device Profile), define los requerimientos mínimos para poder ejecutar programas J2ME, sin embargo, ofrecían poca ayuda a la hora de crear juegos, por lo que había que recurrir a librerías propias de cada fabricante, haciendo necesario crear diferentes versiones de un juego para cada fabricante. La versión 2.0 subsana de alguna manera este problema, y nos ofrece una API mucho más adecuada para la programación de juegos.” “J2ME se basa en los conceptos de configuración y perfil. Una configuración describe las características mínimas en cuanto a la configuración hardware y software. La configuración que usa J2ME es la CLDC (Connected Limited Device Configuration).” “CLDC es una especificación general para un amplio abanico de dispositivos, que van desde PDAs a teléfonos móviles y otros. Un perfil define las características del dispositivo de forma más específica. MIDP(Mobile Information Device Profile) define las APIs y características hardware y software necesarias para el caso concreto de los teléfonos móviles.” (Texto del libro: Programación de juegos para móviles con J2ME. Autor: Alberto García Serrano. 31
www.agserrano.com) Así que decidí usa la versión 2.0 de MIDP (MIDP2), por el tema que tiene una API que define algunas clases para videojuegos ahorrándonos ese trabajo. Antes de implementar y mientras estaba realizando el diseño tuve que decidir que tamaño quería para el juego, explique más arriba en el apartado aportaciones realizadas que hice los sprites lo más pequeño posible de manera que pueda funcionar bien desde móviles con pequeña resolución hasta los que tienen gran resolución, además decidí elegirlo así porque cuando uno ve la pantalla de un móvil esta mucho más cerca de la pantalla que cuando coge un videojuego que no sea portátil. 6.2. Tamaño de los gráficos y programas usados para las imágenes Lo próximo era sacar las imágenes del juego original para ello me descargue un emulador de Spectrum llamado fuse y una imagen que mostraba todos los escenarios del juego original: 32
20. Mapas de Manic Miner Necesite un programa llamado “camStudio” para grabar en un video lo que muestra la pantalla del ordenador y así obtener las animaciones de los personajes. Una vez tenía el video en formato .avi use un programa que llamado “video to jpg converter” que convertía un video .avi en imágenes por cada fotograma grabado, pudiendo así obtener la imagen de cada una de las animaciones del mapa y de los personajes editando en photoshop. El tamaño de los tiles era de 8x8 pixeles, que era el mismo tamaño que en el programa fuse, ya que no estiraba la imagen, así que use ese mismo tamaño para mis tiles, los averigüé usando el photoshop y usando unas guías sobre el escenario para ver los diferentes elementos: 33
21. Método usado para averiguar cual eran los tiles Dado que todos los elementos de un mapa con tiles tienen que ser iguales, averiguando el tamaño de uno obtenía el tamaño de los demás y por tanto la de la imagen, así que elegí el tamaño del ladrillo amarillo para averiguarlo. La clase TiledLayer de MIDP2 nos permite almacenar todos los tiles en un solo archivo gráfico en lugar de almacenarlo por separado, esto nos ofrece una gran ventaja en cuanto a memoria, organización de contenidos y el tiempo de ejecución se reduce al tener que usar solo un archivo para varios tiles en vez de un archivo para cada tile. Decidí dividir cada fichero de tiles por niveles, de manera que la organización y la ejecución sea mejor, así tenemos los tiles del mapa1 en un único archivo: 34
22. Tiles mapa1 La forma en que estén dispuestos en el archivo gráfico no es importante. Siempre se capturan de izquierda a derecha y de arriba hacia abajo. Ejemplo: Image imagen = Image.createImage(“/tiles.png”); TiledLayer tiledlayer = new TiledLayer(10,10, imagen, 8,8); Creamos un mapa de tiles de 10x10 y cada tile ocupa 8 pixeles de ancho y 8 pixeles de alto respectivamente. En la imagen superior se observa muchas llaves, cada una de esas llaves es un frame (un fotograma) de una animación. Igual ocurre con la clase Sprite de MIDP2 que nos ofrece la posibilidad de poner todas los frames de un personaje en un único archivo. Se captura de izquierda a derecha y de arriba hacia debajo de la misma forma. Ejemplo: 35
Try{ Imagen = Image.createImage(“/hero.png”); }catch (IOException ioe) {}; Sprite sp = new Sprite(imagen,10,16); Cada frame del sprite ocupa 10 pixeles de ancho y 16pixeles de alto respectivamente. Todo el código de creación de Sprites, TiledLayer y Scenes lo genera automáticamente en una clase, NetBeans al indicárselo en la interfaz gráfica, creando los métodos necesarios para obtenerlos. A continuación muestro una serie de medidas que tome para crear el juego (anchoxalto) : Tiles de 8x8 pixeles Tamaño del mapa: 32x16 tiles -> 256x128 pixeles PIXELES PERSONAJES: (ANCHO X ALTO) MANIC MINER: 10X16 pixels MONSTRUO 1: 12X16 pixels MOVIMIENTOS 36
A LO ANCHO: 9.41 segundos en 28 tiles. -> avanza de pixel en pixel Pasar a pixeles por segundo -> 28tiles en 9.41 segundos -> 2.97 tiles/s -> 23.8 pixeles/s SALTO ARRIBA: 1.6 segundos. 2 tiles y medio que alcanza de altura existen aceleración y deceleración. SALTO HACIA DELANTE: 1.6 segundos ->2 tiles y medio de altura, 4 tiles a lo ancho, aceleración hacia arriba igual, misma velocidad a lo ancho que si funcionase normal. (Avanza de tile en tile hacia delante y hacia arriba acelera y desacelera) Caída libre: 2.30 segundos 8 tiles A PARTIR DE 5 TILES DE CAIDA SE MUERE EL PERSONAJE Tiempo de aire del personaje -> 2 minutos y 35 segundos. 23. Imagen que muestra a partir de que altura muere el personaje 37
El tiempo en cada dibujado lo calculé haciendo que el muñeco fuese de un lado a otro de la pantalla y midiendo el nº de segundos que tardaba en recorrer 30 tiles -> pase los tiles a pixeles multiplicando por 8 ya que recorría en anchura los pixeles y vi cuanto tiempo tardaba en recorrer un pixel, ya que es lo que recorre el personaje en un ciclo. Y obtuve 62 milisegundos. 6.3 Clase MidletInicio Añado una serie de códigos importantes explicándolos: package ManicMiner; import javax.microedition.midlet.*; import javax.microedition.lcdui.*; /** * @author supervisor */ public class MidletInicio extends MIDlet implements CommandListener { Command exit; Display d; MinerCanvas mc; public MidletInicio(){ exit = new Command("Salir",Command.EXIT, 2); 38
d = Display.getDisplay(this); mc = new MinerCanvas(); mc.addCommand(exit); mc.setCommandListener(this); new Thread(mc).start(); } public void commandAction(Command c, Displayable d){ if(c==exit) { destroyApp(false); notifyDestroyed(); } } public void startApp() { d.setCurrent(mc); } public void pauseApp() { } public void destroyApp(boolean unconditional) { } 39
} Esta es la primera clase que se ejecuta (MidletInicio), empieza ejecutándose en el constructor, en la cual se crea un comando para salir de la aplicación, con exit = new Command("Salir",Command.EXIT, 2); . Y se añade a mc de la clase GameCanvas con mc.addCommand, y la clase que lo escuchara que no será mc sino MidletInicio. Se crea un objeto “mc” de clase MinerCanvas que hereda de GameCanvas: mc = new MinerCanvas(); Se crea un nuevo hilo de ejecución y en el cual se ejecutara el método run de mc con: new Thread(mc).start(); 6.4 El game loop del juego El método run es el que poseerá el game loop, con todas las comprobaciones lógicas (colisiones, si ha muerto el personaje, si te pasas el nivel) del juego y dibujarlo en cada ciclo del bucle. Muestro el game loop para después explicar los distintos parámetros que he puesto. 40
public void run(){ int puntosAux; long start, end; int duration; // int sleepTime = 42; // Para jugar más rápido int sleepTime = 62; /*Lo uso para encontrar errores, ya que se ejecuta muy lentamente el juego 1 ciclo cada 1000 milisegundos -> 1ciclo/segundo*/ // int sleepTime = 1000; //Dice si se ha pasado el juego boolean gameCompleted = false; Graphics g = this.getGraphics(); // Game loop 1 while(true){ //Presentación 41
iniciar(); presentacion(g); //dentro de los niveles del juego. //Game loop 2 while(minero.getVidas()>0 && (!gameCompleted) ){ start= System.currentTimeMillis(); //Muevo el minero minero.mover(this.getKeyStates()); //Compruebo colisiones puntosAux = minero.colision(nivel.getTilesColision(), nivel.getMonstruos(),nivel.getTiles()); nivel.sumarPuntos(puntosAux); minero.animacionInterna(); 42
//Actualizo estado del minero, para comprobar si tiene suelo debajo y si tiene suelo que se cae actualizar también el suelo minero.actualizarEstado(nivel.getTilesColision(),nivel.getTiles()); nivel.comprobarBarra(); //comprueba si se quedo sin aire nivel.actualizarBarra(); nivel.animarMonstruos(); nivel.animarTiles(); if (minero.estaMuerto()){ animacionMatado(g); minero.reiniciar(); nivel.setNivel(nivel.getNivel(),this); minero = nivel.getPersonaje(); }else if(minero.nivelPasado && (nivel.getNivel())+1
animacionMatado(g); nivel.animarNivelPasado(g, this); minero.reiniciar(); nivel.siguienteNivel(this); minero = nivel.getPersonaje(); }else if(minero.nivelPasado && (nivel.getNivel()+1) >= Nivel.NIVELES){ animacionMatado(g); nivel.animarNivelPasado(g, this); gameCompleted = true; } if(!gameCompleted) nivel.hacerScroll(this); g.setColor(0,0,0); g.fillRect(0, 0, this.getWidth(), this.getHeight()); nivel.pintar(g,this); flushGraphics(0, 0, this.getWidth(), this.getHeight()); 44
try { end = System.currentTimeMillis(); duration = (int)(end - start); if (duration < sleepTime){ Thread.sleep(sleepTime-duration); // Thread.sleep(1000); } } catch (InterruptedException ex) { ex.printStackTrace(); } } //si se completo el juego muestro los créditos if(gameCompleted){ mostrarCreditos(g); gameCompleted=false; } 45
} } El entero sleepTime debe ser el tiempo que tarda en ejecutarse un ciclo, mientras menor sea más rápido se ejecutaran los ciclos y por tanto más rápido irá el juego. En caso de que el tiempo de ejecución de instrucciones del ciclo sea menor que sleepTime, el proceso dormirá un tiempo hasta que el tiempo de ejecución de ese ciclo coincida con sleepTime. La clase Graphics es la que se encargará de dibujar los gráficos a nivel de pixeles en la pantalla. Tenemos un objeto g que se relaciona con la pantalla actual, con el método getGraphics() de GameCanvas que es heredado a MinerCanvas ya que esta extiende a GameCanvas. Al dibujar con Graphics realmente se dibuja en una pantalla virtual, y no se vuelca en la pantalla real hasta que no se usa el método flushGraphics() de GameCanvas, con esto nos ahorramos que en la pantalla se vaya dibujando cosas a medida q se ejecutan las instrucciones de dibujado y de esta forma se dibuja en la pantalla realmente cuando ya esta dibujado todo. El método iniciar se encarga de preparar el nivel (crear el nivel, posición de los elementos, iniciar vidas del personaje, posición, estado, etc.): public void iniciar() { 46
nivel = new Nivel(); this.setFullScreenMode(true); nivel.setNivel(0,this); this.minero = nivel.getPersonaje(); } Presentación(Graphics g), es un método creado el cual muestra la presentación del juego antes de empezar a jugar, en mi caso he decidido poner un nivel cuyo número de nivel es el 0 que aparece en iniciar(), pero que no tiene enemigos ni se acaba el aire, ni tiles con animación, ya que esta fuera de la mina. En un principio, iba a poner la presentación como en el juego original, pero el tamaño de la presentación era demasiado ancho para la pantalla de un móvil y me pareció buena idea que el personaje se pudiese controlar con la presentación que ya estaba realizada para que el usuario pueda ver la presentación completa parecida al juego original con el scrolling de la pantalla, al mover el personaje. 47
24. Pantalla de presentación del juego Dentro del game loop 2, que es el que controla los siguientes niveles donde existen enemigos, llaves y tiles animados, realizamos las operaciones para que nuestro personaje se mueva, colisione con los tiles y enemigos y compruebe si se ha pasado el nivel y si termino el juego. Mientras no se quede sin vidas o no nos hayamos pasado el juego no salimos de este game loop. 6.5 Implementación del salto Para medir mejor el salto desarrolle en photoshop una imagen en la que compuse los distintos movimientos en un punto del sprite: 48
Los pixeles azules son el desplazamiento por ciclo del salto hacia delante, los puntos morados son del personaje andando, y los pixeles amarillos son de la caída. Para hacer el movimiento del salto tuve en cuenta que el desplazamiento horizontal es siempre de 2 pixeles por ciclo mientras este en el aire y que el desplazamiento vertical iba variando cada ciclo. Para solventar este último problema realice una matriz de enteros en la clase Personaje indicando cuanto se desplaza en cada ciclo mientras este saltando: int salto[] = {-4,-4,-3,-3,-2,-2,-1,-1,0,0,1,1,2,2,3,3,4,4}; //Indica cuanto tiene que avanzar verticalmente en cada ciclo 49
Para saber porque he puesto números negativos basta sabe que las coordenadas en el móvil empieza en x=0 y=0 en la esquina izquierda y arriba tal como muestra el siguiente gráfico: X será el eje horizontal e Y el vertical, de manera que si el sprite se tiene que desplazar hacia arriba le tenemos que sumar un número negativo a su posición actual y para bajar sumar un número positivo a su posición actual. 6.6 Implementación de las colisiones El tema de las colisiones ha sido el más desarrollado ya que aunque MIDP2 dispone de una API que detecta la colisión con tiles y sprites no nos dice con que tiles ha chocado ni la cara, con lo que opte por desarrollar métodos de colisión que me dijese con qué objeto choca el personaje y la cara con la que choca para actuar de una forma u otra en el personaje en la clase Personaje, ya que actuamos sobre él. Existen distintos tipos de objetos que al colisionar hay que actuar de una manera u otra, los hemos agrupado en: 50
OBJETOS COLISION 0. Nada - Transparente 1. BLOQUE – Rojo 2. SUELO - Negro 3. SUELO QUE SE CAE – Naranja 4. Cinta izquierda- Verde con una I 5. LLAVE - Amarillo 6. MUERTE - PURPURA OSCURO 7. Mecanismo - GRIS 8. Cinta derecha- Verde con una D 9. Puerta salida- Marrón con picaporte amarillo El número indica el nº de tile que le corresponde. Entonces, me di cuenta de que si cambiaba de orden o si metía mas tiles diferentes no funcionaría correctamente, así que utilice un mecanismo para que independientemente del gráfico que usase como tile el personaje colisionase y supiese con qué tipo de objeto colisiona. El mecanismo lo encontré en el tutorial de juegos de plataformas de un software llamado Game Maker para hacer videojuegos sin programar: http://www.yoyogames.com/make/tutorials El mecanismo consistía en desarrollar un conjunto de tiles únicos que fuesen de colisión. Los cuales introduje en un fichero gráfico que contenía la siguiente imagen: 25. Tiles de colisión Los colores indicados arriba coinciden con el color de estos tiles en los tipos de objetos. Por ejemplo el rojo coincide con el tile nº 1 de la 51
imagen de arriba y es rojo, el 0 que representa nada lo genera automáticamente netBeans y no hay que ponerlo en el fichero. Primero desarrollamos el escenario con los tiles que no indican el tipo de objeto sino que son partes del escenario gráfico y tienen tiles animados: 26. Fichero de tiles gráficos del mapa 1 27. Primer mapa de tiles 52
Después desarrollamos otro mapa de tiles pero con los objetos colisionables, los cuales no se mostraran al usuario pero son los que se comprueban cuando colisiona, hay veces que un tile de colisión tiene impacto sobre un tile gráfico, por ejemplo en las llaves, por lo que tenemos que pasar el tile de colisión t y el tile de gráficos t2, para saber con qué tile colisiono: 28. Mapa con Tiles de colisión Como se observa el posicionamiento de los elementos del mapa de colisión es igual que el del mapa de tiles gráficos, la diferencia es que el mapa de tiles gráficos tiene muchos más tiles diferentes. Este nuevo mapa nos ayudará a encontrar con qué tipo de objeto colisionamos fácilmente, algo que no se podría hacer fácilmente para el anterior mapa, por ejemplo si queremos saber si colisionamos con una llave, ya que existen varios tiles asociado a su animación y cada mapa puede tener sus propios tiles y ordenamiento de ellos en el fichero gráfico, haciendo que el número de tipo que le corresponde varié de un fichero 53
a otro, lo cual no ocurre con esta ultima técnica de colocar un mapa de colisiones con tiles únicos por cada tipo de elemento. Por último en el editor de escenarios (scene), ponemos este mapa de colisión por detrás del fondo, de manera que la lo que nos queda visualmente no aparece el mapa de colisión aunque si se usa en el código para comprobar las colisiones: 29. Composición del escenario 54
30. Colocación de las capas, mientras menor Z más hacia afuera esta el sprite o el TiledLayer Como se observa en la imagen superior el mapa de colisión lo hemos colocado detrás del todo y no se ve. Para aprovechar el mayor rendimiento en vez de comprobar si el personaje colisión con alguno de la gran cantidad de tiles que existe en el escenario, comprobamos los que están más cercanos. En la siguiente imagen se muestra cuales tiles del mapa se comprobara si colisiona con el personaje, indicados con rectángulos: 55
31. Lugares del mapa donde se comprobara si hubo colisión El cuadrado con líneas blancas y negras, corresponde al tamaño del sprite. matrizEspacio es una matriz con 2 dimensiones, la primera tendrá de tamaño del número de tiles horizontales a cubrir en la detección de colisiones y la otra dimensión el tamaño del número de tiles verticales. El número de tiles de esto se calcula teniendo en cuenta el número de tiles que ocupa el personaje, con el siguiente método: private int[][] tilesCuadrado(){ int x = sp.getWidth()/8; int y = sp.getHeight()/8; x = x+2; y++; 56
return new int[x][y]; } De esta forma comprobamos las colisiones, el tipo que colisionamos, la cara y si chocamos con un enemigo: int colision(TiledLayer t, IMonstruo [] monstruos,TiledLayer t2){ int puntos = 0; //PRIMERO MIRO LA COLISION CON LOS TILES /////////////////////////////// //Nos dara el tile en el que esta el pixel superior izquierdo del minero int tilex = conseguirTilex(t); int tiley = conseguirTiley(t); //Guarda el numero de tile x e y de la matriz que se está comprobando int xaux=0; int yaux=0; int cara = 0; //Cara 0 -> aun no inicializada //POSIBLES VALORES DE CARAS: //1 -> arriba 57
//2 -> lado //3 -> abajo //Tipo de tile con el que colisiona. Si vale -1 significa que no colisiono int tipoT = -1; int tipoAux = -1; int caraAux = 0; for(int i = 0; i < matrizEspacio.length; i++){ for(int j = 0; j< matrizEspacio[i].length;j++){ if(tilex+i < t.getColumns() && tiley +j< t.getRows() ){ tipoT = tipoColision(t,tilex + i, tiley+j); if(tipoT != -1){ xaux = tilex + i; yaux = tiley+j; cara = encontrarCara(t,xaux,yaux); if(tipoT == this.LLAVE) puntos = 100; //si choca con alguna cara if(cara!=0) 58
{ tratarColision(tipoT,cara,xaux,yaux, t,t2); } } } } } if(monstruos != null){ //MIRO A VER SI SE CHOCA CON ALGUN ENEMIGO for(int i = 0; i < monstruos.length; i++){ if(sp.collidesWith(monstruos[i].getSprite(), false)){ vidas--; this.muerto=true; } } } 59
return puntos; } “collidesWith(Sprite sp, boolean b)“ de la clase Sprite nos permite comprobar si un sprite colisiona con otro, lo que nos viene perfecto para comprobar la colisión con los enemigos. El boolean b si es cierto se comprueba la colisión a nivel de pixel de la figura interior del sprite. En caso de que colisione con un enemigo el personaje pasara al estado de muerto y tendrá 1 vida menos, en el game loop justo antes de dibujar se comprobara si el personaje ha muerto, en el caso en que este muerto se reinicia el nivel. Para comprobar las colisiones de un sprite con los tiles dado que no tenemos una función concreta que nos diga en qué posición colisionamos usamos el siguiente algoritmo: 60
Los sprites son todos rectangulares, pero la figura descrita dentro de él generalmente es irregular y existe casos en que un cuadrado no daría la precisión adecuada: 32. Precisión en colisiones En la imagen de ejemplo se puede comprobar que un programa detector de colisiones daría cierto en los 2 casos aunque en el caso del cuadrado no existiría colisión real solo en el caso de los círculos. En esta otra imagen se puede comprobar que el rectángulo es mejor detector de colisiones que los círculos. Se podría comprobar las colisiones a nivel de pixel, pero el tiempo de procesamiento sería muy grande, así que me decanté por usar el rectángulo. El algoritmo para calcular la colisión con 2 rectángulos en un sistema de coordenadas X e Y es sencillo. Primero indicamos las variables usadas: 61
W1 indica la coordenada X de los pixeles izquierdo del sprite, X1 indica la coordenada X de los pixeles derecho del sprite. W2 indica la coordenada X de los pixeles izquierdo del sprite, X2 indica la coordenada X de los pixeles derecho del sprite. H1 indica la coordenada Y de los pixeles inferior del sprite, Y1 indica la coordenada Y de los pixeles superiores. H2 indica la coordenada Y de los pixeles inferior del sprite, Y2 indica la coordenada Y de los pixeles superiores. Para que exista colisión en la coordenada X, se debe cumplir: W1 >X2 y X1 < W2. Al igual paraqué exista colisión en la coordenada Y, se debe cumplir: H1>Y2 y Y1 < H2 Si se cumple que existe colisión en la coordenada X y en la coordenada Y entonces el sprite colisiona, cumpliéndose: W1 >X2 y X1 < W2 y H1>Y2 y Y1 < H2 62
33. Imagen explicativa de la colisión Todo este algoritmo esta implementado en el método tipoColision: private int tipoColision(TiledLayer t,int tilex, int tiley) { int tipo = t.getCell(tilex,tiley); if(tipo != 0){ //Si es distinto de 0 hay colision! //donde se encuentra el pixel esquina superior izquierda int xpixelT = tilex*t.getCellWidth() + t.getX(); 63
int ypixelT = tiley*t.getCellHeight() + t.getY(); if((sp.getX() < xpixelT + t.getCellWidth()) && (sp.getX() + sp.getWidth() > xpixelT) && (sp.getY() < ypixelT + t.getCellHeight() && (sp.getY() + sp.getHeight() > ypixelT))) return tipo; } return -1; } Tilex y tiley es la posición en tiles en el mapa a comprobar. El método getCell(int x, int y) de tiledLayer nos da el número del tile en la posición x e y tiles del mapa de tiles, es decir el tipo de tile en esa posición. getX() y getY() de TiledLayer nos devuelve la posición del tiledLayer en el mapa. getCellWidth() el ancho en pixeles de un tile. getCellHeight() la altura en pixeles de un tile. 64
Para comprobar la cara del sprite con la que choca he desarrollado el método, encontrarCara. La colisión con la cara tiene en cuenta la posición anterior del personaje antes de chocar, después en el método tratarColision se comprueba si se ha chocado pero antes buscamos con qué cara colisiona. private int encontrarCara(TiledLayer t, int xtile, int ytile){ //Tengo que encontrar la cara con la que golpeo primero int cara = 0; //0 si no golpea con nada //donde se encuentra el pixel esquina superior izquierda int xpixelT = xtile*t.getCellWidth() + t.getX(); int ypixelT = ytile*t.getCellHeight() + t.getY(); int posxAnterior = sp.getX() - ultimodx; int posyAnterior = sp.getY() - ultimody; if((posxAnterior < xpixelT+t.getCellWidth()) && (posxAnterior + sp.getWidth() > xpixelT)){ if(posyAnterior + sp.getHeight() - 1 < ypixelT){ cara = 1; //es la cara de arriba } 65
También puede leer