Desarrollo de Videojuegos Android con Cocos2D

  • 30,485 views
Uploaded on

Desarrollo de Videojuegos Android con Cocos2D …

Desarrollo de Videojuegos Android con Cocos2D

Cocos 2D es un framework muy popular en el mundo del desarrollo de videojuegos móviles, especialmente en las plataformas iOs y Android.
El proyecto Cocos 2D está formado por un conjunto de librerías que nos facilitan la implementación de la mayor parte de las funcionalidades básicas de un videojuego.
• Bucle del Juego
• Animaciones
• Colisiones
• Sistemas de partículas
• Sonidos
Una de las principales ventajas de Cocos 2D es que utilizando resulta fácil desarrollar videojuegos ya que oculta varios de los aspectos técnicos complejos de la plataforma.

More in: Education
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
    Be the first to like this
No Downloads

Views

Total Views
30,485
On Slideshare
0
From Embeds
0
Number of Embeds
1

Actions

Shares
Downloads
38
Comments
0
Likes
0

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. 1 Desarrollo de Videojuegos Android con Cocos2D Autor: Jordán Pascual Espada Apuntes, practica Guiada. Introducción a Cocos2D ............................................................................................................ 2 1 Nuevo Proyecto Android........................................................................................................ 3 2 Director , vista y escena ......................................................................................................... 5 3 Representación gráfica de elementos: CCSprite y CGSize ..................................................... 8 4 Movimiento de elementos: CCMoveTo, CCCallFuncN, CCSequence .................................. 10 5 Eventos Touch: ccTouchesMoved....................................................................................... 13 6 Colisiones: CGRect............................................................................................................... 15 7 Animaciones: CCSpriteFrame , CCAnimation, CCAction ...................................................... 18 8 Fondo.................................................................................................................................... 23 9 Sistemas de partículas: CCParticleSystem........................................................................... 24 10 Condición y pantalla de perder: CCLabel , replaceScene, CCDelayTime............................ 25 11 Condición y pantalla de ganar............................................................................................ 28 12 Generación de niveles........................................................................................................ 29 13 Efectos de sonido: .............................................................................................................. 30
  • 2. 2 Introducción a Cocos2D Cocos 2D es un framework muy popular en el mundo del desarrollo de videojuegos móviles, especialmente en las plataformas iOs y Android. El proyecto Cocos 2D está formado por un conjunto de librerías que nos facilitan la implementación de la mayor parte de las funcionalidades básicas de un videojuego.  Bucle del Juego  Animaciones  Colisiones  Sistemas de partículas  Sonidos Una de las principales ventajas de Cocos 2D es que utilizando resulta fácil desarrollar videojuegos ya que oculta varios de los aspectos técnicos complejos de la plataforma. Descarga: http://cocos2d-x.org/projects/cocos2d-x/wiki/Download
  • 3. 3 1 Nuevo Proyecto Android Creamos un nuevo proyecto Android “Android Application project” Seleccionamos todos los valores por defecto. A continuación vamos a modificar la configuración de la aplicación. Abrimos el fichero AndroidManifest.xml, en la pestaña Manifest colocamos un nuevo Manifest Extras, de tipo Supports Screens.
  • 4. 4 Pulsamos sobre el elemento Suppots Screens para configurar la adaptación de la aplicación a las diferentes resoluciones. Abrimos el AndroidManifest.xml en modo XML pulsando sobre la última pestaña. Añadimos un parámetro de configuración que obligara a ejecutar la actividad en modo portrait. A continuación vamos a copiar la librería del framework Cocos2D en nuestra aplicación, arrastramos el fichero cocos2d.jar a la carpeta /libs/ de nuestro proyecto.
  • 5. 5 2 Director , vista y escena Lo primero que tenemos que hacer es ir a la implementación de la clase MainActivity y crear dos variables globales. protected CCGLSurfaceView vista; private CCScene scene; A continuación vamos a modificar el contenido del método onCreate: @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); vista = new CCGLSurfaceView(this); setContentView(vista); } Modificamos la ventana actual para que se muestre a pantalla completa. Creamos una vista de tipo CCGLSurfaceView y la establecemos como vista de la actividad. Ahora vamos a sobrescribir el método onStart() de la actividad, utilizamos el elemento CCDirector y para establecer los parámetros básicos del Juego. public void onStart() { super.onStart(); // Director CCDirector.sharedDirector().attachInView(vista); CCDirector.sharedDirector().setDeviceOrientation( CCDirector.kCCDeviceOrientationPortrait); CCDirector.sharedDirector().setDisplayFPS(true); CCDirector.sharedDirector().setAnimationInterval(1.0f / 40.0f); } A continuación tenemos que asociar una escena CCScene al director, la escena define y contiene los elementos del Juego. Pero antes de asociar la escena tenemos que definirla. Creamos la clase java CapaJuego para implementar una escena la clase ha de heredar de alguna de las clases de tipo Layer definidas en Cocos2D. En este caso utilizaremos la CCColorLayer.
  • 6. 6 El eclipse nos sugerirá que añadamos un constructor, elegimos el primero de ellos. Ahora implementaremos un método estático en la CapaJuego que genere una escena (CCScene) y la retorne. Creamos la escena y le asociamos una instancia de CapaJuego: public static CCScene scene() { CCScene scene = CCScene.node(); CCLayer layer = new CapaJuego(ccColor4B.ccc4(255, 255, 255, 255)); scene.addChild(layer); return scene; } Ahora ya podemos añadir la escena (que está completamente vacía) al CCDirector. @Override public void onStart() { super.onStart(); CCDirector.sharedDirector().attachInView(vista); CCDirector.sharedDirector().setDeviceOrientation( CCDirector.kCCDeviceOrientationPortrait); CCDirector.sharedDirector().setDisplayFPS(true); CCDirector.sharedDirector().setAnimationInterval(1.0f / 40.0f); scene = CapaJuego.scene(); CCDirector.sharedDirector().runWithScene(scene); } Con el objetivo de que el ciclo de vida de la actividad principal MainActivity se vea reflejado en el CCDirector, sobrescribimos los métodos onPause, onResume, onStop @Override public void onPause() { super.onPause(); CCDirector.sharedDirector().pause(); } @Override public void onResume() { super.onResume(); CCDirector.sharedDirector().resume(); } @Override
  • 7. 7 public void onStop() { super.onStop(); CCDirector.sharedDirector().end(); }
  • 8. 8 3 Representación gráfica de elementos: CCSprite y CGSize Actualmente la escena está vacía, pero vamos a incluir un nuevo elemento, la “bola”. Comenzamos importando todos los recursos gráficos que nos hemos descargado en la carpeta assets del proyecto. Para incluir un nuevo elemento en la capa lo declaramos inicializamos en el constructor de la capa. Cocos 2D utiliza la clase CCSprite para representar elementos en pantalla. A continuación vamos a: 1. Crear una variable Global de tipo CCSprite para la pelota. 2. Crear una variable Global de tipo CGSize para almacenar el tamaño de la pantalla del dispositivo (Sera útil para posicionar los elementos). 3. Inicializar CCSprite utilizando la imagen bola.png 4. Asignarle una posición a la pelota, por ejemplo el punto medio de la pantalla. 5. Añadir la pelota a la escena private CGSize winSize; private CCSprite pelota; protected CapaJuego(ccColor4B color) { super(color); winSize = CCDirector.sharedDirector().displaySize(); pelota = CCSprite.sprite("bola.png"); pelota.setPosition(CGPoint.ccp(winSize.width / 2.0f, winSize.height / 2.0f)); addChild(pelota); }
  • 9. 9 Si ejecutamos la aplicación podremos ver la escena compuesta por la capa CapaJuego y la pelota en el punto medio.
  • 10. 10 4 Movimiento de elementos: CCMoveTo, CCCallFuncN, CCSequence En primer lugar vamos a declarar dos variables globales que contendrán la velocidad de la pelota en el eje X e Y. private int velocidadPelotaX = 10; private int velocidadPelotaY = -10; Para asociarle un movimiento a la pelota tenemos que declarar una acción de tipo CCMoveTo. Las acciones se declaran especificando: 1) El tiempo que queremos que se tarde en realizar el movimiento 2) Hacía que destino queremos que se mueva el elemento CCMoveTo movimientoBasico = CCMoveTo.action( 1 / 40f, CGPoint.ccp(pelota.getPosition().x + velocidadPelotaX, pelota.getPosition().y + velocidadPelotaY)); Podemos especificar que cuando el movimiento finalice se invoque de manera automática a una función, para especificar el siguiente movimiento, en este caso cuando el movimiento termine se invocara la función nuevoMovimientoPelota (la implementaremos más tarde). CCCallFuncN movimientoFinzalizado = CCCallFuncN.action(this, "nuevoMovimientPelota"); Después tenemos que hacer una asociación entre el movimiento y la invocación de la función. CCSequence actions = CCSequence.actions(movimientoBasico, movimientoFinzalizado); El último paso consiste en asociar la secuencia al elemento pelota. pelota.runAction(actions); El aspecto final del constructor de la capa del juego será el siguiente: protected CapaJuego(ccColor4B color) { super(color); winSize = CCDirector.sharedDirector().displaySize(); pelota = CCSprite.sprite("bola.png"); pelota.setPosition(CGPoint.ccp(winSize.width / 2.0f, winSize.height / 2.0f)); addChild(pelota); CCMoveTo movimientoBasico = CCMoveTo.action( 1 / 40f, CGPoint.ccp(pelota.getPosition().x + velocidadPelotaX, pelota.getPosition().y + velocidadPelotaY)); CCCallFuncN movimientoFinzalizado = CCCallFuncN.action(this, "nuevoMovimientPelota"); CCSequence actions = CCSequence.actions(movimientoBasico, movimientoFinzalizado); pelota.runAction(actions); }
  • 11. 11 Si ejecutamos la aplicación la pelota se moverá mínimamente. Vamos a implementar el método nuevoMovimientoPelota para asociar un nuevo movimiento a la pelota (una vez haya acabado de realizar el anterior). Repetimos el mismo patrón de movimiento: public void nuevoMovimientPelota(Object sender) { float destinoPelotaX = pelota.getPosition().x + velocidadPelotaX; float destinoPelotaY = pelota.getPosition().y + velocidadPelotaY; CCMoveTo movimientoBasico = CCMoveTo.action(1 / 40f, CGPoint.ccp(destinoPelotaX, destinoPelotaY)); CCCallFuncN movimientoFinzalizado = CCCallFuncN.action(this, "nuevoMovimientPelota"); CCSequence actions = CCSequence.actions(movimientoBasico, movimientoFinzalizado); pelota.runAction(actions); } Vamos a hacer que cuando la pelota se salga fuera de la pantalla “rebote”, para ello tenemos que comprobar si la pelota está fuera de los márgenes de la pantalla y si es así invertir la aceleración. public void nuevoMovimientPelota(Object sender) { // Comprobar si rebota if ( pelota.getPosition().x <= 0 || pelota.getPosition().x >= winSize.width ){ velocidadPelotaX = velocidadPelotaX *-1; } if ( pelota.getPosition().y <= 0 || pelota.getPosition().y >= winSize.height ){ velocidadPelotaY = velocidadPelotaY *-1; } float destinoPelotaX = pelota.getPosition().x + velocidadPelotaX; float destinoPelotaY = pelota.getPosition().y + velocidadPelotaY; // (Paso 1) Cuando se ha movido volverlo a mover CCMoveTo movimientoBasico = CCMoveTo.action(1 / 40f, CGPoint.ccp(destinoPelotaX, destinoPelotaY)); CCCallFuncN movimientoFinzalizado = CCCallFuncN.action(this, "nuevoMovimientPelota"); CCSequence actions = CCSequence.actions(movimientoBasico, movimientoFinzalizado); pelota.runAction(actions); } Si ejecutamos ahora la aplicación la pelota rebotara por la pantalla, vamos a incluir una última comprobación para que el destino de la pelota no pueda exceder los límites de la pantalla. Antes de asignar posición comprobamos que las posiciones destino no están fuera de la pantalla. float destinoPelotaX = pelota.getPosition().x + velocidadPelotaX; float destinoPelotaY = pelota.getPosition().y + velocidadPelotaY; if (destinoPelotaX < 0){ destinoPelotaX = 0; }
  • 12. 12 if (destinoPelotaX > winSize.width){ destinoPelotaX = winSize.width; } if (destinoPelotaY < 0){ destinoPelotaY = 0; } if (destinoPelotaY > winSize.height){ destinoPelotaY = winSize.height; } // (Paso 1) Cuando se ha movido volverlo a mover CCMoveTo movimientoBasico = CCMoveTo.action(1 / 40f, CGPoint.ccp(destinoPelotaX, destinoPelotaY)); CCCallFuncN movimientoFinzalizado = CCCallFuncN.action(this, "nuevoMovimientPelota"); CCSequence actions = CCSequence.actions(movimientoBasico, movimientoFinzalizado); pelota.runAction(actions); }
  • 13. 13 5 Eventos Touch: ccTouchesMoved Pala A continuación vamos a repetir el procedimiento que seguimos para incluir la pelota pero esta vez para el elemento pala. Colocaremos la pala en la parte baja de la pantalla centrada en el eje X. protected CapaJuego(ccColor4B color) { super(color); winSize = CCDirector.sharedDirector().displaySize(); pelota = CCSprite.sprite("bola.png"); pelota.setPosition(CGPoint.ccp(winSize.width / 2.0f, winSize.height / 2.0f)); addChild(pelota); pala = CCSprite.sprite("barra2.png"); pala.setPosition(CGPoint.ccp(winSize.width/2.0f, 80f)); addChild(pala); Vamos a hacer que la pala se mueva en el eje X hacia la posición del dedo del usuario, cuando el usuario mueva el dedo la pala se moverá. En la clase CapaJuego y sobrescribimos uno de los tres métodos que sirven para captar eventos táctiles ccTouchesMoved. 1) Capturamos la posición del evento X del dedo del usuario, y lo comparamos con la posición de la pala: la diferencia entre esas dos mediciones es la distancia que la pala tiene que recorrer. 2) Para que la velocidad del movimiento sea uniforme establecemos que el tiempo que tardara en recorrer esa distancia será la distancia / la velocidad. 3) Por el momento fijaremos la velocidad de la pala en 400. 4) Incluimos una función de CCCallFuncN aunque en este caso no la vallamos a utilizar, cuando la pala llega a su destino no queremos que ocurra nada. @Override public boolean ccTouchesMoved(MotionEvent event) { float distancia = Math.abs(pala.getPosition().x - event.getX()); CCMoveTo moverPalaHaciaPunto = CCMoveTo.action(distancia / 400, CGPoint.ccp(event.getX(), pala.getPosition().y)); CCCallFuncN movimientoFinzalizado = CCCallFuncN.action(this, "finalMovimientoPala"); CCSequence actions = CCSequence.actions(moverPalaHaciaPunto, movimientoFinzalizado); pala.runAction(actions); return true; }
  • 14. 14 Para que la capa soporte eventos táctiles debemos incluir la directriz this.setIsTouchEnabled(true); en el constructor de CapaJuego. protected CapaJuego(ccColor4B color) { super(color); this.setIsTouchEnabled(true); winSize = CCDirector.sharedDirector().displaySize(); // Pelota pelota = CCSprite.sprite("bola.png"); pelota.setPosition(CGPoint.ccp(winSize.width / 2.0f, winSize.height / 2.0f)); addChild(pelota); Si ejecutamos el proyecto y pulsamos sobre la pantalla la pala se moverá.
  • 15. 15 6 Colisiones: CGRect Bloque A continuación vamos a repetir el procedimiento que seguimos para incluir la pelota y la pala, en esta ocasión incluiremos el elemento bloque. // Bloque bloque = CCSprite.sprite("cocodrilo_1.png"); bloque.setPosition(CGPoint.ccp(winSize.width/2.0f, winSize.height-20)); addChild(bloque); Estableceremos dos tipos de colisiones 1) Si la pelota colisiona contra la pala rebotara 2) Si la pelota colisiona contra el bloque, lo destruirá. Necesitamos un método que compruebe las colisiones entre los elementos. En la última línea del constructor de CapaJuego declaramos que el método gameLogic (que implementaremos a continuación) se ejecutara 40 veces por segundo. pelota.runAction(actions); this.schedule("gameLogic", 1/40f); } Implementamos el método gameLogic. Cocos2D ofrece un mecanismo “sencillo” para comprobar colisiones entre rectángulos. 1) Definimos los rectángulos de colisión de los 3 elementos 2) Comprobamos si la pala y la pelota colisionan. 3) En caso de que colisionen invertimos la velocidad actual de la pelota en eje Y 4) Modificamos la velocidad de la pelota en el eje X en función de la parte de la pala en la que haya impactado. Como la diferencia absoluta es numero demasiado grande para la velocidad (la bola iría demasiado rápido) vamos a dividir ese resultado entre dos. 5) Por ultimo le damos un pequeño empujón a la pelota para que salga de la intersección con la pala (modificamos la posición de la pelota). 6) Eliminamos el antiguo movimiento de la pelota y forzamos a que se vuelva a mover, ahora ya con sus nuevas aceleraciones. 3 – 5 = -2 Nueva velocidadX = -2 7 – 5 = 2 Nueva velocidadX = 2
  • 16. 16 public void gameLogic(float dt){ // Crear rectangulos de colision CGRect areaPala = CGRect.make( pala.getPosition().x - pala.getContentSize().width / 2.0f, pala.getPosition().y - pala.getContentSize().height / 2.0f, pala.getContentSize().width, pala.getContentSize().height); CGRect areaPelota = CGRect.make( pelota.getPosition().x - pelota.getContentSize().width / 2.0f, pelota.getPosition().y - pelota.getContentSize().height / 2.0f, pelota.getContentSize().width, pelota.getContentSize().height); CGRect areaBloque = CGRect.make( bloque.getPosition().x - bloque.getContentSize().width / 2.0f, bloque.getPosition().y - bloque.getContentSize().height / 2.0f, bloque.getContentSize().width, bloque.getContentSize().height); if (CGRect.intersects(areaPala, areaPelota)){ velocidadPelotaX = (int) ((pelota.getPosition().x - pala.getPosition().x)/2); velocidadPelotaY = velocidadPelotaY *-1; // Empujon pelota.setPosition(CGPoint.ccp(pelota.getPosition().x, pala.getPosition().y + pala.getContentSize().height / 2 + pelota.getContentSize().height / 2 + 1)); pelota.stopAllActions(); nuevoMovimientPelota(pelota); } } Si el la pelota colisiona con el bloque eliminamos el bloque de la escena. if (CGRect.intersects(areaPelota, areaBloque)){ removeChild(bloque, true); } Lista de Bloques En lugar de un bloque vamos a hacer que la escena tenga varios, declaramos una lista de bloques (CCSprite) como variable global. private CCSprite bloque; private LinkedList<CCSprite> listaBloques = new LinkedList<CCSprite>(); Creamos un método inicializarBloques al que posteriormente invocaremos desde el constructor de la capa. 1) Este crea e inserta n bloques en la pantalla 2) Va posicionando los bloques en columnas (empezando por la parte superior), hasta que la columna está completa. 3) Cuando la columna está completa cambia de fila public void inicializarBloques() { int insertados = 0; int fila = 0; int columna = 0; while (insertados < 25) { CCSprite bloque = CCSprite.sprite("cocodrilo_1.png"); float posX = (20 + bloque.getContentSize().width / 2.0f) + (bloque.getContentSize().width * columna); float posY = (winSize.height - bloque.getContentSize().height / 2.0f) - (bloque.getContentSize().height * fila); bloque.setPosition(CGPoint.ccp(posX, posY));
  • 17. 17 columna++; /* La posicionX del bloque esta fuera de la pantalla */ if (posX + bloque.getContentSize().width > winSize.width) { columna = 0; fila++; } else { listaBloques.add(bloque); addChild(bloque); insertados++; } } } Incluimos la llamada al método inicializarBloques en la última línea del constructor CapaJuego this.schedule("gameLogic", 1/40f); inicializarBloques(); } Ahora tenemos que modificar el método updateLogic para adaptarlo a la lista de bloques. 1) Recorremos la lista de boques. 2) Creamos el rectángulo de área de colisión para cada bloque y comprobamos si colisiona con la pelota. 3) Si existe colisión tenemos que eliminar el bloque de la lista, pero no podemos eliminar elementos de una lista mientras la recorremos. Guardamos el bloque seleccionado en la lista de los bloques que vamos a destruir. 4) Si hay algún bloque en la lista de bloques pendientes por destruir invertimos la velocidad de la bola (Rebote). 5) Recorremos la lista de los bloques que hay que destruir y los sacamos de la lista principal y de la escena. LinkedList<CCSprite> listaBloquesParaDestruir = new LinkedList<CCSprite>(); for (CCSprite bloque : listaBloques) { CGRect areaBloque = CGRect.make( bloque.getPosition().x - bloque.getContentSize().width/ 2.0f, bloque.getPosition().y - bloque.getContentSize().height/ 2.0f, bloque.getContentSize().width, bloque.getContentSize().height); if (CGRect.intersects(areaPelota, areaBloque)) { listaBloquesParaDestruir.add(bloque); removeChild(bloque, true); } } if (listaBloquesParaDestruir.size() > 0) { for (CCSprite bloque : listaBloquesParaDestruir) { listaBloques.remove(bloque); } velocidadPelotaX = velocidadPelotaX * -1; velocidadPelotaY = velocidadPelotaY * -1; } }
  • 18. 18 7 Animaciones: CCSpriteFrame , CCAnimation, CCAction Modificaremos el mecanismo de creación de los bloques para insertar una animación. public void inicializarBloques() { int insertados = 0; int fila = 0; int columna = 0; while (insertados < 25) { CCSprite bloque = CCSprite.sprite("cocodrilo_1.png"); float posX = (20 + bloque.getContentSize().width / 2.0f) + (bloque.getContentSize().width * columna); float posY = (winSize.height - bloque.getContentSize().height / 2.0f) - (bloque.getContentSize().height * fila); bloque.setPosition(CGPoint.ccp(posX, posY)); columna++; // La posicionX del bloque esta fuera de la pantalla if (posX + bloque.getContentSize().width > winSize.width) { columna = 0; fila++; } else { listaBloques.add(bloque); addChild(bloque); insertados++; } } } Para crear la animación vamos a: 1) Cachear el Sprite con la animación, Cocos2D utiliza unos ficheros XML (animacioncocodrilo.plist) para definir las animaciones. Vamos a examinar el fichero .plist para entender su estructura. ULTIMAS LINEAS <dict> <key>format</key> <integer>2</integer> <key>realTextureFileName</key> <string>animacioncocodrilo.png</string> <key>size</key> <string>{320,40}</string> <key>smartupdate</key> <string>$TexturePacker:SmartUpdate:0e07504f614e935fcfc28006d277442e$</string> <key>textureFileName</key> <string>animacioncocodrilo.png</string>
  • 19. 19 </dict> DECLARACIÓN DE FRAMES <key>cocodrilo1.png</key> <dict> <key>frame</key> <string>{{0,0},{40,40}}</string> <key>offset</key> <string>{0,0}</string> <key>rotated</key> <false/> <key>sourceColorRect</key> <string>{{0,0},{40,40}}</string> <key>sourceSize</key> <string>{40,40}</string> </dict> <key>cocodrilo2.png</key> <dict> <key>frame</key> <string>{{40,0},{40,40}}</string> <key>offset</key> <string>{0,0}</string> <key>rotated</key> <false/> <key>sourceColorRect</key> <string>{{0,0},{40,40}}</string> <key>sourceSize</key> <string>{40,40}</string> </dict> Una vez se carga este fichero tenemos toda la información de la animación en memoria. 2) Seleccionamos los frames de la animación que nos interesan, para ello utilizamos la clave de los frames: cocodrilo1.png, cocodrilo2.png, etc. Los introducimos en un array de CCSpriteFrame 3) Asignar los frames a un objeto de tipo CCAnimation, y le damos un nombre, en este caso “basica” 4) Colocamos el primero de los frames seleccionados como Sprite básico del elemento bloque, (nos servirá para darle las dimensiones al bloque). 5) Crear una acción CCAction a partir de la animación y un boolean que determina si la animación se ejecutara de manera continua. 6) Ordenamos al bloque que ejecute la acción que contiene la animación utilizando el método runAction cocodrilo2.png x=40, y=0 Alto=40 Ancho =40 Cocodrilo3.png x=80, y=0 Alto=40 Ancho =40
  • 20. 20 CCSpriteFrameCache.sharedSpriteFrameCache().addSpriteFrames("animacioncocodrilo.plist"); ArrayList<CCSpriteFrame> frames = new ArrayList<CCSpriteFrame>(); for(int i = 1; i<=8;++i) { frames.add(CCSpriteFrameCache.sharedSpriteFrameCache().spriteFrameByName("cocodrilo"+ i +".png")); } CCAnimation animacionBasica = CCAnimation.animation("basica", 0.1f, frames); CCSprite bloque = CCSprite.sprite(frames.get(5)); CCAction accionBasica = CCRepeatForever.action(CCAnimate.action(animacionBasica, true)); bloque.runAction(accionBasica); Generación aleatoria de Bloques Vamos a incluir un mecanismo para que los bloques sean generados de manera automática tengan diferente aspecto gráfico. En la carpeta de recursos gráficos tenemos las animaciones: animaciontigre y animacionpanda con sus correspondientes ficheros de definición .plist. Implementamos el método generaBloqueAletorio, que construye un bloque animado utilizando uno de los tres recursos gráficos, el cocodrilo, el tigre o el panda. La selección del recurso grafico se hace de manera aleatoria. Creamos un array con las 3 claves {[0]cocodrilo, [1]tigre, [2]panda} y seleccionamos una de manera automática generando un entero aleatorio comprendido entre 0 y 2. public CCSprite generaBloqueAleatorio() { CCSprite bloque = null; String[] animaciones = { "cocodrilo", "tigre", "panda" }; int index = new Random().nextInt(3); CCSpriteFrameCache.sharedSpriteFrameCache().addSpriteFrames( "animacion" + animaciones[index] + ".plist"); ArrayList<CCSpriteFrame> frames = new ArrayList<CCSpriteFrame>(); for (int i = 1; i <= 8; ++i) { frames.add(CCSpriteFrameCache.sharedSpriteFrameCache() .spriteFrameByName(animaciones[index] + i + ".png")); } CCAnimation animacionBasica = CCAnimation.animation("basica", 0.1f,frames); bloque = CCSprite.sprite(frames.get(0)); CCAction accionBasica = CCRepeatForever.action(CCAnimate.action( animacionBasica, true)); bloque.runAction(accionBasica); return bloque; } Modificamos la implementación inicializarBloques(), en lugar de crear el Sprite para el bloque solicitamos un Sprite aleatorio.
  • 21. 21
  • 22. 22 public void inicializarBloques() { int insertados = 0; int fila = 0; int columna = 0; while (insertados < 25) { CCSprite bloque = generaBloqueAleatorio(); float posX = (20 + bloque.getContentSize().width / 2.0f) + (bloque.getContentSize().width * columna); float posY = (winSize.height - bloque.getContentSize().height / 2.0f) - (bloque.getContentSize().height * fila); bloque.setPosition(CGPoint.ccp(posX, posY)); columna++;
  • 23. 23 8 Fondo Incluiremos el recurso gráfico fondo.png como fondo de pantalla de la CapaJuego. El proceso es idéntico al que seguimos para colocar la pelota y la pala. Como el orden de agregación de los CCSprite es relevante debemos añadir el fondo el primer lugar (para que no oculte al resto de elementos). Posicionaremos el fondo en el punto medio de la pantalla. protected CapaJuego(ccColor4B color) { super(color); winSize = CCDirector.sharedDirector().displaySize(); // fondo CCSprite fondo = CCSprite.sprite("fondo.png"); fondo.setPosition(CGPoint.ccp(winSize.width/2.0f, winSize.height/2.0f)); addChild(fondo); // Pelota pelota = CCSprite.sprite("bola.png"); pelota.setPosition(CGPoint.ccp(winSize.width / 2.0f, winSize.height / 2.0f)); addChild(pelota);
  • 24. 24 9 Sistemas de partículas: CCParticleSystem Cocos 2D incluye un sistema de soporte sencillo para definir “efectos visuales” basados en partículas, este sistema se llama CCParticleSystem y se utiliza comúnmente para realizar efectos especiales como: explosiones, humo, fuego, lluvia, etc. El usuario define una serie de parámetros relevantes en las partículas, tipo de partículas, velocidad de emisión, duración de la animación, velocidad, textura… y el más importante las coordenadas del punto donde se producirá la emisión de partículas. Algunos de los tipos de partículas más utilizados son: CCParticleRain, CCParticleSoke, CCParticleMeteor, CCParticleExplosion y CCParticleGalaxy, pero hay muchos otros. Vamos a incluir una emisión de partículas de tipo CCParticleRain cuando la pelota impacte contra un bloque. Localizamos la parte del método updateLogic() que elimina los bloques contra los que impacto la pelota, a continuación incluimos la llamada al sistema de partículas, estableciendo como punto de origen la posición de la pelota. if (listaBloquesParaDestruir.size() > 0) { for (CCSprite bloque : listaBloquesParaDestruir) { listaBloques.remove(bloque); } velocidadPelotaX = velocidadPelotaX * -1; velocidadPelotaY = velocidadPelotaY * -1; /* Explosion */ CCParticleSystem emitter = CCParticleRain.node(); emitter.setEmissionRate(100.0f); emitter.setDuration(0.01f); emitter.setGravity(CGPoint.ccp(0,0)); emitter.setSpeed(100.0f); emitter.setSpeedVar(70.0f); emitter.setPosition(pelota.getPosition()); emitter.setPosVar(CGPoint.ccp(0, 0)); emitter.setTexture( CCTextureCache.sharedTextureCache().addImage("bola.png")); emitter.setAutoRemoveOnFinish(true); addChild(emitter,8); }
  • 25. 25 10 Condición y pantalla de perder: CCLabel , replaceScene, CCDelayTime. Actualmente el juego no tiene final, no se puede ganar ni perder. Vamos a incluir una comprobación en la lógica del juego que evalué si la pelota se encuentra en una posición más baja que la paleta (coordenada Y), en caso de que así sea, se finalizara el juego y se enviara al usuario a una nueva pantalla (Escena) que le notifique que ha perdido. En primer lugar vamos a definir la pantalla que aparecerá cuando el usuario pierda el juego, creamos una nueva clase Java a la que llamaremos CapaPerder y heredara de la clase base CCColorLayer. public class CapaPerder extends CCColorLayer{ protected CapaPerder(ccColor4B color) { super(color); } } Al igual que hicimos en la CapaJuego en el constructor de la CapaPerder introducir aquellos elementos que se vayan a visualizar en la pantalla. En este caso queremos que tenga un fondo de color negro y un texto que le diga al usuario que ha perdido. protected CapaPerder(ccColor4B color) { super(color); this.setIsTouchEnabled(true); CGSize winSize = CCDirector.sharedDirector().displaySize(); CCLabel texto = CCLabel.makeLabel("Has perdido", "DroidSans", 32); texto.setColor(ccColor3B.ccWHITE); texto.setPosition(winSize.width / 2.0f, winSize.height / 2.0f); addChild(texto); } Ahora tenemos que crear el método que genera la escena a partir de la capa (de la misma manera que hicimos en CapaJuego. Creamos un método estático que inicializa un objeto CCScene (Basado en la capa) y lo retorna. public static CCScene scene() { CCScene scene = CCScene.node(); CapaPerder capaPerder = new CapaPerder(ccColor4B.ccc4(0, 0, 0, 255)); scene.addChild(capaPerder); return scene; } Por ultimo vamos a implementar un método JugarDeNuevo() que servirá para que el jugador pueda volver a iniciar el juego. Lo único que hace este método es volver a colocar la escena generada a partir de CapaJuego como escena principal. public void jugarDeNuevo() {
  • 26. 26 CCDirector.sharedDirector().replaceScene(CapaJuego.scene()); } Vamos a hacer que este método se invoque cuando el usuario pulse sobre la pantalla y levante el dedo. @Override public boolean ccTouchesEnded(MotionEvent event) { jugarDeNuevo(); return true; } Implementamos la comprobación de la posición actual de la pelota al final el método gameLogic(). Si la coordenada Y de la pelota es menor de 50 (parte baja de la pantalla) se ha perdido la partida y se establece como principal la escena generada a partir de la CapaPerder. if (pelota.getPosition().y < 50){ CCDirector.sharedDirector().replaceScene(CapaPerder.scene()); } Vamos a modificar la velocidad inicial en el eje X de la pelota para que caiga “recta”. public class CapaJuego extends CCColorLayer { private int velocidadPelotaX = 0; private int velocidadPelotaY = -10; Podemos incluir una condición en el constructor de la CapaPerder para que una acción se ejecute automáticamente transcurridos N segundos.
  • 27. 27 Vamos a hacer que el método JugarDeNuevo (que reinicia la partida) se ejecute cuando pasen tres segundos. protected CapaPerder(ccColor4B color) { super(color); this.setIsTouchEnabled(true); CGSize winSize = CCDirector.sharedDirector().displaySize(); CCLabel texto = CCLabel.makeLabel("Has perdido", "DroidSans", 32); texto.setColor(ccColor3B.ccWHITE); texto.setPosition(winSize.width / 2.0f, winSize.height / 2.0f); addChild(texto); this.runAction(CCSequence.actions(CCDelayTime.action(3.0f), CCCallFunc.action(this, "jugarDeNuevo"))); }
  • 28. 28 11 Condición y pantalla de ganar. Al igual que hicimos en la CapaPerder vamos a crear una nueva capa CapaGanar, debe de ser idéntica a la anterior pero en este caso tendrá en fondo verde y la letra blanca y le dirá al usuario que ha ganado la partida. public class CapaGanar extends CCColorLayer { protected CapaGanar(ccColor4B color) { super(color); this.setIsTouchEnabled(true); CGSize winSize = CCDirector.sharedDirector().displaySize(); CCLabel texto = CCLabel.makeLabel("Siguiente Nivel", "DroidSans", 32); texto.setColor(ccColor3B.ccWHITE); texto.setPosition(winSize.width / 2.0f, winSize.height / 2.0f); addChild(texto); this.runAction(CCSequence.actions(CCDelayTime.action(3.0f), CCCallFunc.action(this, "siguienteNivel"))); } public static CCScene scene() { CCScene scene = CCScene.node(); CapaGanar capaSiguienteNivel = new CapaGanar(ccColor4B.ccc4(0, 255, 0, 100)); scene.addChild(capaSiguienteNivel); return scene; } public void siguienteNivel() { CCDirector.sharedDirector().replaceScene(CapaJuego.scene()); } @Override public boolean ccTouchesEnded(MotionEvent event) { siguienteNivel(); return true; } } Incluiremos un sistema muy similar al visto anteriormente que índice si el usuario ha ganado, para ello Implementamos una comprobación al final el método gameLogic() que compruebe que si queda algún bloque sin destruir en la lista de bloques. if (pelota.getPosition().y < 50){ CCDirector.sharedDirector().replaceScene(CapaPerder.scene()); } if (listaBloques.size() == 0){ CCDirector.sharedDirector().replaceScene(CapaGanar.scene()); } }
  • 29. 29 12 Generación de niveles Cuando el usuario finaliza la partida se encuentra con que aparece una pantalla que le indica que ha superado el nivel, pero si pulsa sobre la pantalla vuelve a jugar exactamente el mismo nivel. Vamos a incluir un mecanismo sencillo que contabilice los niveles que el usuario logra completar, y cada vez genere un nivel con un mayor número de bloques. Inicialmente el nivel del juego será el nivel 1. public class CapaJuego extends CCColorLayer { public static int nivel = 1; private int velocidadPelotaX = 0; private int velocidadPelotaY = -10; Modificamos el método inicializarBloques() para que en lugar de generar 25 bloques genere un numero de bloques dependiente del nivel, por ejemplo 5 * nivel, que serían: 5 para el nivel 1, 10 para el nivel 2, 15 para el nivel 3, etc. public void inicializarBloques() { int insertados = 0; int fila = 0; int columna = 0; while (insertados < 5*nivel) { CCSprite bloque = generaBloqueAleatorio(); Modificamos el método siguienteNivel de la CapaGanar para que aumente en uno el nivel actual del juego. public void siguienteNivel() { CapaJuego.nivel++; CCDirector.sharedDirector().replaceScene(CapaJuego.scene()); }
  • 30. 30 13 Efectos de sonido: Creamos la carpeta res/raw/ e introducimos en ella todos los efectos de sonido del juego Creamos el método inicializarMusica() en la CapaJuego, este método iniciara la melodía de fondo. public static void iniciarMusica(){ /** Sonido */ Context context = CCDirector.sharedDirector().getActivity(); SoundEngine.sharedEngine().stopSound(); SoundEngine.purgeSharedEngine(); SoundEngine.sharedEngine().playSound(context, R.raw.sonidobucle, true); } En el constructor de la capa juego precargamos el sonido grunt (que utilizaremos posteriormente) y posteriormente inicializamos la música del juego. protected CapaJuego(ccColor4B color) { super(color); // Tamaño de la pantalla, util para colocar objetos winSize = CCDirector.sharedDirector().displaySize(); /** Sonido */ Context context = CCDirector.sharedDirector().getActivity(); SoundEngine.sharedEngine().preloadEffect(context, R.raw.grunt); iniciarMusica(); Nos situamos en el gameLogic() y reproducimos el sonido grunt cuando la pelota impacte con un bloque. if (CGRect.intersects(areaPelota, areaBloque)){ /** Sonido */ Context context = CCDirector.sharedDirector().getActivity(); SoundEngine.sharedEngine().playEffect(context, R.raw.grunt); listaBloquesParaDestruir.add(bloque); removeChild(bloque, true); } Creamos un nuevo método en la CapaJuego que sirva para pausar la música, invocaremos a este método cuando el usuario se salga del juego. public static void pausarMusica(){ // Sonido SoundEngine.sharedEngine().stopSound(); } Completamos los métodos onPause() y onResume() de MainActivity para que paren e inicien la música del juego.
  • 31. 31 @Override public void onPause() { super.onPause(); CCDirector.sharedDirector().pause(); CapaJuego.pausarMusica(); } @Override public void onResume() { super.onResume(); CCDirector.sharedDirector().resume(); CapaJuego.iniciarMusica(); }