viernes, 27 de junio de 2014

Corona SDK - Juego de naves (II) : Objetos de visualización y Gestión de eventos

  En este blog veremos como podemos crear un sencillo juego de naves para nuestro dispositivo de forma rápida gracias a las funcionalidades que nos ofrece Corona SDK.

  El juego consiste en una nave que podremos mover desplazando el dedo por la pantalla. La nave deberá esquivar las bombas que irán apareciendo para no explotar y perder la partida. Como podemos ver es un juego muy sencillo pero que nos permitirá ver algunas de las principales funciones de Corona como son los objetos de visualización, gestión de eventos, colisiones, gestión de escenas y el uso de spritee para la animación de la nave.

  A continuación se muestra una imagen del resultado final:



Objetos de Visualización


  Lo primero que pasaremos a crear será el fondo de nuestro juego. Para ello disponemos de la clase Display, con ella podemos crear cualquier objeto de visualización de nuestras escenas conocidos en Corona como DisplayObject. Para la creación y visualización de imágenes en la pantalla del dispositivo disponemos de dos funciones:

  display.newImage()
  display.newImageRect()

  La diferencia entre estas dos funciones es que si usamos la segunda cuando ejecutemos el juego en pantallas de diferente resolución Corona se encargará de re-dimensionarlas y ajustarlas a la nueva resolución, por tanto lo recomendable es esta. Una vez visto está diferencia pasamos a definir nuestros diferentes fondos:

local localGroup = display.newGroup( )
local backgroundGroup = display.newGroup( )

background = display.newImageRect( imgDir.."mainbackground.png", display.contentWidth, display.contentHeight )
background.x = display.contentCenterX ; background.y = display.contentCenterY
background:addEventListener( "touch", playerControl )
localGroup:insert(background)

middleBackground = display.newImageRect( imgDir.."bgLayer1.png", display.contentWidth+10, display.contentHeight )
middleBackground.x = display.contentCenterX ; middleBackground.y = display.contentCenterY
middleBackground.name = "middle"
backgroundGroup:insert( middleBackground )

beforeBackground = display.newImageRect( imgDir.."bgLayer2.png", display.contentWidth+10, display.contentHeight )
beforeBackground.x = display.contentCenterX ; beforeBackground.y = display.contentCenterY
beforeBackground.name = "before"

backgroundGroup:insert( beforeBackground )


 Hemos definido tres fondos para crear el cielo de la escena. El orden de creación de los objetos Display marcará el orden de visualización en pantalla, de tal forma que el último objeto creado se superpondrá a cualquier objeto que haya sido creado con anterioridad y ocupe el mismo espacio en la pantalla.

Cabe destacar en este código el uso de lo que se conoce en Corona como "Grupos". Un grupo es un contenedor de objetos DisplayObject con el que podremos gestionar de mejor forma nuestra escena. La clase Group hereda de DisplayObject y con esto podríamos destacar tres cosas muy importantes:

-  Un grupo puede contener objetos de visualización y/o a otros grupos.
-  A un grupo se le pueden aplicar las mismas transformaciones que a un objeto DisplayObject de tal forma que dichas transformaciones se le aplicará a todos los elementos que contiene.
-  Un objeto DisplayObject no puede estar contenido en dos grupos distintos.

  Con estas implicaciones en mente podemos por tanto plantearnos la gestión de una escena mediante el uso de un "árbol de escena". Un árbol de escena se podría ver como el árbol de directorios de un SO donde dicho árbol contendrá distintos nodos hoja que serán los diferentes objetos de visualización de nuestra escena y los nodos padre serán los objetos Group que los contendrán a los cuales les aplicaremos las distintas transformaciones.

  Otro de los objetos Display que más usaremos en un juego con animaciones 2D serán los sprite. En nuestro caso hemos hecho uso de estos sprites para crear la animación de la nave moviendo las alas:

 local options = {
    width = 115,
    height = 69,
    numFrames = 8
}
local playerSheet = graphics.newImageSheet( imgDir.."playerAnimation.png", options )
local sequenceData = { name = "player", start=1, count=8, time=1000, loopCount=0, loopDirection = "forward" }

spritePlayer = display.newSprite( playerSheet, sequenceData )
spritePlayer.x = display.contentCenterX/2 
spritePlayer.y = display.contentCenterY
spritePlayer.name = "player"
spritePlayer:play( )
backgroundGroup:insert( spritePlayer )

  Con la función "newImageSheet" de la clase Graphics nos creamos una imagen que Corona será capaz de manejar para obtener las diferentes imágenes o fotogramas que contiene para nuestra animación. La tabla sequenceData almacena las diferentes animaciones que podrá contener nuestro objeto Sprite, por tanto como podremos observar podemos definir distintas animaciones para una misma imagen dentro de esta tabla simplemente indicando el nombre "único" que identifique a la animación, el fotograma de inicio, el fotograma final y la duración de la animación. Una vez creado todos estos datos se los pasamos a la funcion "newSprite" para que nos cree nuestro objeto Sprite, lo posicionamos y iniciamos su animación.

  Por defecto se reproduce la primera animación que hayamos declarado en la tabla "sequenceData" pero si quisiéramos  iniciar otra simplemente debemos indicarlo mediante la función "setSequence("nombreAnimacion")" de la clase SpriteObject antes de ejecutar la llamada a la función "play()".



Gestión de eventos


  Los diferentes tipos de eventos que define Corona podrán ser capturados por los objetos DisplayObject y por el oyente global Runtime. La forma de añadir una captura de evento a un objeto se realiza de la siguiente forma:

  object : addEvenListener("nombreDelEvento", funcion_para_manejarlo)

  En Corona podemos distinguir dos tipos de eventos genéricos: los eventos en tiempo de ejecución y los eventos locales.

 Eventos en tiempo de ejecución


  Los eventos en tiempo de ejecución son capturados por el oyente Runtime ya que no van dirigidos a ningun objeto oyente en concreto. En este tipo de eventos podemos destacar los siguientes:

 -  EnterFrame: que se disparará en cada frame por segundo en el intervalo de nuestra aplicación (30 o 60).
 -  System: este tipo de evento se disparará cuando se produce algún evento externo a la aplicación, por ejemplo cuando el dispositivo se suspende.
 -  Orientation: se produce cuando la orientación del dispositivo cambia.

  Eventos locales


  Los eventos locales solo pueden ser capturados por los objectos Display ya que estos si van dirigidos a uno o varios objetos concretos. Entre todos los eventos locales podemos destacar los siguientes:

  - Hit: este tipo de evento ocurre cuando el usuario toca la pantalla del dispositivo.

  - Collision : se dispara cuando el motor de física detecta una colisión entre dos objetos.

  - Timer: este tipo de evento se dispara cuando transcurre X tiempo desde que se declaró, es el equivalente a un trigger.

  Cabe destacar y entrar en mayor profundidad en los eventos del tipo "hit" ya que serán unos de los más usados . Cuando el usuario pulsa la pantalla del dispositivo se dispararán dos tipos de eventos: "tap" y "touch". La diferencia entre ambos es que el evento tap solo se dispara cuando se pulsa la pantalla mientra que el evento "touch" será disparado en todo el ciclo de pulsación es decir cuando se pulsa, cuando se desplaza el dedo por la pantalla y cuando se suelta. Estos dos eventos serán lanzados para todos aquellos objetos que interceptan con la pulsación en pantalla. El primer objeto en recibir el evento será aquel que este por "encima" o con "menor profundidad" y se irá lanzando sucesivamente hasta el ultimo objeto con mayor profundidad. Esta cadena sucesiva de lanzamiento se podrá detener si la función donde se maneja el evento de cualquier objeto de la cadena devuelve "true".

  La forma de eliminar la captura de un evento de un objeto se realiza mediante la función :

   object : removeEvenListener("nombreDelEvento", funcion_para_manejarlo)

  Con respecto a dicha eliminación existe una gran diferencia entre los eventos locales y los de tiempo de ejecución. Cuando se destruye el objeto Display mediante la llamada a la función "removeSelf()"también se elimina el "listener" de este objeto para los distintos eventos que se haya asignado, por tanto si borramos un objeto Display para cambiar de escena no tenemos que preocuparnos de eliminar sus "listeners" asignados previamente. Sin embargo los "listeners" declarados para los eventos en tiempo de ejecución  permanecen entre el cambio de escena ya que el escuchador global Runtime no puede ser borrado y por tanto debemos asegurarnos de borrar los "listeners" cuando dejemos de necesitarlos para evitar errores indeseados.

  Una vez explicado todo esto pasamos a introducir la gestión de eventos en nuestra escena principal del juego:

local function playerControl( event )
-- body
local dx 
local dy

if (event.phase == "began") then
display.getCurrentStage():setFocus(event.target)

posInic.X = event.x
posInic.Y = event.y
elseif (event.phase == "moved") then

dx = event.x - posInic.X
dy = event.y - posInic.Y

spritePlayer:translate( dx, dy )

checkCollision()

posInic.X = event.x
posInic.Y = event.y
elseif (event.phase=='ended' or event.phase == "cancelled" ) then  
  display.getCurrentStage():setFocus(nil)
  end

return true
end

background:addEventListener( "touch", playerControl )

  En este código lo que hacemos es asignarle al fondo trasero la captura del evento "touch" para la pulsación sobre la pantalla por parte del usuario. La función encargada de manejar este evento será "playerControl" en la cual lo que hacemos es mover la nave a la posición en la que se encuentra el dedo del usuario, dicha posición nos la da las variables event,x y event.y. Como podemos observar en esta función hacemos una distinción de la "fase" en la que nos encontramos cuando se dispara el evento:

  - Si es la fase inicial asignamos el "focus" del programa a nuestra imagen del fondo trasero que fue la que lanzó este evento al ser pulsada (la podemos obtener mediante la variable event.target). Con esto lo que pretendemos es asegurarnos de que mientras el usuario mueva su dedo por la pantalla ningún otro listener sea disparado para este evento y por tanto solo se realice la funcionalidad de mover la nave mientras el usuario toca la pantalla y así evitar errores no deseados.
  - Si la fase es "moved" significa que el usuario no ha soltado el dedo de la pantalla y lo está moviendo por la pantalla por tanto cambiamos la posición de la nave y comprobamos si se ha detectado alguna colisión con alguna bomba.
  - Si la fase es final o cancelado significa que el usuario a dejado de pulsar sobre la pantalla o que por alguna razón externa se debe abortar la captura de este evento y por tanto lo que hacemos es quitar el "focus" del objeto fondo trasero.

  Por ultimo devolvemos "true" para indicar que hemos manejado este evento y que no se necesita seguir lanzandolo a objetos que se encuentre en un nivel inferior de la jerarquía de manejo para este evento.



El código completo lo podrás encontrar en la siguiente dirección:

https://github.com/Muffin-Apps/ShipGame-CoronaSDK

  

No hay comentarios:

Publicar un comentario