Corona SDK: Crear un juego de disparos
Spanish (Español) translation by Esther (you can also view the original English article)
En este tutorial, te mostraré cómo crear un juego de disparos con balas limitadas con el Corona SDK. El objetivo del juego es disparar a una gran cantidad de objetivos con solo cinco balas. Durante este tutorial, trabajarás con los temporizadores, los controles táctiles y la física. Para saber más, ¡sigue leyendo!
1. Descripción de la aplicación



Usando los gráficos suministrados codificaremos un juego de disparos usando Lua y las API's de Corona SDK. En el juego el jugador utiliza cinco balas para disparar a sus enemigos. Cada enemigo creará otras cuatro balas que ayudarán a derribar un mayor número de objetivos. Mientras codificas, puedes modificar los parámetros del código para personalizar tu juego.
2. Dispositivo de destino



Nuestro primer paso es seleccionar la plataforma en la que queremos ejecutar nuestra aplicación. Esto es importante para poder elegir el tamaño de nuestras imágenes.
La plataforma iOS tiene las siguientes características:
- iPad 1/2/Mini: 1024x768px, 132 ppi
- iPad Retina: 2048x1536, 264 ppi
- iPhone/iPod Touch: 320x480px, 163 ppi
- iPhone/iPod Retina: 960x640px, 326 ppi
- iPhone 5/iPod Touch: 1136x640, 326 ppi
Dado que Android es una plataforma abierta, existen diferentes dispositivos y resoluciones. Algunas de las características de pantalla más comunes son:
- Asus Nexus 7 Tablet: 800x1280px, 216 ppi
- Motorola Droid X: 854x480px, 228 ppi
- Samsung Galaxy SIII: 720x1280px, 306 ppi
En este tutorial nos centraremos en la plataforma iOS con el diseño gráfico, específicamente para desarrollar para la distribución a un iPhone/iPod touch, pero el código presentado aquí se aplica al desarrollo de Android con el Corona SDK también.
3. Interface



Utilizaremos una interfaz sencilla con múltiples formas, botones y mapas de bits. Los recursos gráficos de la interfaz necesarios para este tutorial se encuentran en la descarga adjunta.
4. Exportar gráficos



Dependiendo del dispositivo que hayas seleccionado, es posible que tengas que exportar los gráficos en los ppi recomendados. Puedes hacerlo en tu editor de imágenes favorito. Yo he utilizado la función Ajustar tamaño... de la aplicación Vista previa en Mac OS X.
Recuerda dar un nombre descriptivo a las imágenes y guardarlas en la carpeta del proyecto.
5. Configuración de la aplicación
Un archivo externo hace que la aplicación se convierta en pantalla completa en todos los dispositivos (el archivo config.lua). Este archivo muestra el tamaño original de la pantalla y el método utilizado para escalar ese contenido en caso de que la aplicación se ejecute en una resolución de pantalla diferente.
application = { content = { width = 320, height = 480, scale = "letterbox" }, }
6. Main.lua
¡Ahora vamos a escribir la aplicación! Abre tu editor de Lua preferido (cualquier editor de texto funcionará, pero no tendrás resaltado de sintaxis) y prepárate para escribir tu nueva aplicación. Recuerda guardar el archivo como main.lua en tu carpeta de proyecto.
7. Estructura del código
Estructuraremos nuestro código como si fuera una Clase. Si conoces ActionScript o Java, la estructura te resultará familiar.
Necessary Classes Variables and Constants Declare Functions contructor (Main function) class methods (other functions) call Main function
8. Ocultar la barra de estado
display.setStatusBar(display.HiddenStatusBar)
Este código oculta la barra de estado. La barra de estado es la barra en la parte superior de la pantalla del dispositivo que muestra la hora, la señal y otros indicadores.
9. Importar Physics
Utilizaremos la biblioteca Physics para manejar las colisiones. Usa este código para importarla:
-- Physics local physics = require('physics') physics.start()
10. Antecedentes



La siguiente línea de código crea un fondo simple para la interfaz de la aplicación.
-- Graphics -- [Background] local gameBg = display.newImage('gameBg.png')
11. Vista del título



Esta es la Vista de Título; será la primera pantalla interactiva que aparecerá en nuestro juego. Estas variables almacenan sus componentes:
-- [Title View] local title local playBtn local creditsBtn local titleView
12. Vista de los créditos



Esta vista muestra los créditos y el copyright del juego. Esta variable lo almacena:
-- [CreditsView] local creditsView
13. Mensaje de instrucciones

Aparecerá un mensaje de instrucciones al comienzo del juego y se apagará después de dos segundos. Puedes cambiar el tiempo más adelante en el código.
-- Instructions local ins
14. Turret

Este es el gráfico de la torreta, se colocará en el centro de nuestro juego.
-- Turret local turret
15. Enemy

Los enemigos aparecen desde el borde de la pantalla, el siguiente Group
los almacena.
-- Enemy local enemies
16. Alerta



Esta es la alerta que se muestra cuando el jugador se queda sin balas. Muestra la puntuación y termina el juego.
-- Alert local alertView
17. Sonidos

Utilizaremos efectos de sonido para mejorar la sensación del juego. Los sonidos utilizados en este juego fueron creados en as3sfx y la música de fondo es de PlayOnLoop.
-- Sounds local bgMusic = audio.loadStream('POL-hard-corps-short.mp3') local shootSnd = audio.loadSound('shoot.wav') local exploSnd = audio.loadSound('explo.wav')
18. Variables
Estas son las variables que utilizaremos. Puedes leer los comentarios en el código para saber más sobre ellas.
-- Variables local timerSrc local yPos = {58, 138, 218} --posible Y positions for enemies local speed = 3 local targetX --stores position of enemy when shot local targetY
19. Declarar funciones
Declarar todas las funciones como local al principio.
-- Functions local Main = {} local startButtonListeners = {} local showCredits = {} local hideCredits = {} local showGameView = {} local gameListeners = {} local createEnemy = {} local shoot = {} local update = {} local onCollision = {} local addExBullets = {} local alert = {}
20. Constructor
A continuación crearemos la función que inicializa la lógica del juego:
function Main() -- code... end
21. Añadir vista de título
Ahora colocaremos el TitleView en el escenario y llamaremos a una función que añada los tap listeners a los botones.
function Main() titleBg = display.newImage('title.png') playBtn = display.newImage('playBtn.png', 212, 163) creditsBtn = display.newImage('creditsBtn.png', 191, 223) titleView = display.newGroup(titleBg, playBtn, creditsBtn) startButtonListeners('add') end
22. Oyentes del botón de inicio
Esta función añade los listeners necesarios a los botones del TitleView.
function startButtonListeners(action) if(action == 'add') then playBtn:addEventListener('tap', showGameView) creditsBtn:addEventListener('tap', showCredits) else playBtn:removeEventListener('tap', showGameView) creditsBtn:removeEventListener('tap', showCredits) end end
23. Mostrar créditos
La pantalla de créditos se muestra cuando el usuario toca el botón "Acerca de". Para eliminar la vista de créditos, se añade un oyente al toque.
function showCredits:tap(e) playBtn.isVisible = false creditsBtn.isVisible = false creditsView = display.newImage('credits.png', -110, display.contentHeight-80) transition.to(creditsView, {time = 300, x = 55, onComplete = function() creditsView:addEventListener('tap', hideCredits) end}) end
24. Ocultar créditos
Cuando se toque la pantalla de créditos, se interpolará fuera del escenario y se eliminará.
function hideCredits:tap(e) playBtn.isVisible = true creditsBtn.isVisible = true transition.to(creditsView, {time = 300, y = display.contentHeight+creditsView.height, onComplete = function() creditsView:removeEventListener('tap', hideCredits) display.remove(creditsView) creditsView = nil end}) end
25. Mostrar la vista del juego
Cuando se pulsa el botón Play, la vista del título se interpola y se elimina, revelando la vista del juego. Hay muchas partes involucradas en esta vista así que las dividiremos en los siguientes pasos.
function showGameView:tap(e) transition.to(titleView, {time = 300, x = -titleView.height, onComplete = function() startButtonListeners('rmv') display.remove(titleView) titleView = nil end})
26. Mensaje de instrucciones
Las siguientes líneas añaden el mensaje de instrucciones.
ins = display.newImage('ins.png', 135, 255) transition.from(ins, {time = 200, alpha = 0.1, onComplete = function() timer.performWithDelay(2000, function() transition.to(ins, {time = 200, alpha = 0.1, onComplete = function() display.remove(ins) ins = nil end}) end) end})
27. Indicador de balas a la izquierda
Esta sección añade las balas en la parte superior izquierda de la pantalla. Representa los disparos disponibles que le quedan al jugador.
-- Bullets Left bullets = display.newGroup() bulletsLeft = display.newGroup() for i = 1, 5 do local b = display.newImage('bullet.png', i*12, 12) bulletsLeft:insert(b) end
28. Puntuación TextField
Este es el TextField de puntuación creado en la parte superior derecha del escenario:
-- TextFields scoreTF = display.newText('0', 70, 23.5, 'Courier Bold', 16) scoreTF:setTextColor(239, 175, 29)
29. Torreta
Ahora colocaremos la torreta en el escenario.
-- Turret turret = display.newImage('turret.png', 220, 301)
30. Tabla de enemigos y música de fondo
A continuación, crearemos la tabla de enemigos, llamaremos a una función que añada los oyentes del juego e iniciaremos la música de fondo.
enemies = display.newGroup() gameListeners('add') audio.play(bgMusic, {loops = -1, channel = 1}) end
31. Juego de Oyentes
Esta función añade los oyentes necesarios para iniciar la lógica del juego:
function gameListeners(action) if(action == 'add') then timerSrc = timer.performWithDelay(1200, createEnemy, 0) Runtime:addEventListener('enterFrame', update) else timer.cancel(timerSrc) timerSrc = nil Runtime:removeEventListener('enterFrame', update) gameBg:removeEventListener('tap', shoot) end end
32. Crear enemigo
La siguiente función crea los enemigos. Comienza seleccionando una posición Y aleatoria de la tabla creada previamente, y luego añade la física al objeto recién creado. Añadiremos un escuchador de colisiones a cada enemigo y también los añadiremos a la tabla de enemigos.
function createEnemy() local enemy local rnd = math.floor(math.random() * 4) + 1 enemy = display.newImage('enemy.png', display.contentWidth, yPos[math.floor(math.random() * 3)+1]) enemy.name = 'bad' -- Enemy physics physics.addBody(enemy) enemy.isSensor = true enemy:addEventListener('collision', onCollision) enemies:insert(enemy) end
33. Disparar
Cuando el jugador toca la pantalla se crea una bala y se reproduce un sonido. Tiene propiedades físicas que detectan las colisiones.
function shoot() audio.play(shootSnd) local b = display.newImage('bullet.png', turret.x, turret.y) physics.addBody(b) b.isSensor = true bullets:insert(b)
34. Actualizar balas a la izquierda
Elimina una bala de la zona "Balas a la izquierda" en la parte superior izquierda del escenario.
-- Remove Bullets Left bulletsLeft:remove(bulletsLeft.numChildren) -- End game 4 seconds after last bullet if(bulletsLeft.numChildren == 0) then timer.performWithDelay(4000, alert, 1) end end
35. Mover enemigos
La siguiente función se ejecuta cada fotograma. Aquí la usamos para mover cada enemigo en la tabla de enemigos.
function update() -- Move enemies if(enemies ~= nil)then for i = 1, enemies.numChildren do enemies[i].x = enemies[i].x - speed end end
36. Mover disparar balas
Un método similar se utiliza para las balas.
-- Move Shoot bullets if(bullets[1] ~= nil) then for i = 1, bullets.numChildren do bullets[i].y = bullets[i].y - speed*2 end end
37. Mover balas de explosión
Cuando una bala golpea a un enemigo, se forman cuatro balas adicionales que se mueven de cuatro maneras diferentes. Este código mueve cada bala en la dirección correcta.
-- Move Explosion Bullets if(exploBullets[1] ~= nil) then for j = 1, #exploBullets do if(exploBullets[j][1].y ~= nil) then exploBullets[j][1].y = exploBullets[j][1].y + speed*2 end if(exploBullets[j][2].y ~= nil) then exploBullets[j][2].y = exploBullets[j][2].y - speed*2 end if(exploBullets[j][3].x ~= nil) then exploBullets[j][3].x = exploBullets[j][3].x + speed*2 end if(exploBullets[j][4].x ~= nil) then exploBullets[j][4].x = exploBullets[j][4].x - speed*2 end end end end
38. Colisiones
Esta función se ejecuta cuando la bala colisiona con un enemigo. Reproduce un sonido, llama a la función que añade las cuatro balas adicionales, aumenta la puntuación y elimina los objetos implicados en la colisión.
function onCollision(e) audio.play(exploSnd) targetX = e.target.x targetY = e.target.y timer.performWithDelay(100, addExBullets, 1) -- Remove Collision Objects display.remove(e.target) e.target = nil display.remove(e.other) e.other = nil -- Increase Score scoreTF.text = tostring(tonumber(scoreTF.text) + 50) scoreTF.x = 90 end
39. Añadir balas de explosión
Este código crea y coloca las cuatro balas adicionales en la posición correcta para ser movidas por la función de actualización.
function addExBullets() -- Explosion bullets local eb = {} local b1 = display.newImage('bullet.png', targetX, targetY) local b2 = display.newImage('bullet.png', targetX, targetY) local b3 = display.newImage('bullet.png', targetX, targetY) local b4 = display.newImage('bullet.png', targetX, targetY) physics.addBody(b1) b1.isSensor = true physics.addBody(b2) b2.isSensor = true physics.addBody(b3) b3.isSensor = true physics.addBody(b4) b4.isSensor = true table.insert(eb, b1) table.insert(eb, b2) table.insert(eb, b3) table.insert(eb, b4) table.insert(exploBullets, eb) end
40. Alerta
La función de alerta crea una vista de alerta, la anima y termina el juego.
function alert() audio.stop(1) audio.dispose() bgMusic = nil gameListeners('rmv') alertView = display.newImage('alert.png', 160, 115) transition.from(alertView, {time = 300, xScale = 0.5, yScale = 0.5}) local score = display.newText(scoreTF.text, (display.contentWidth * 0.5) - 18, (display.contentHeight * 0.5) + 5, 'Courier Bold', 18) score:setTextColor(44, 42, 49) -- Wait 100 ms to stop physics timer.performWithDelay(1000, function() physics.stop() end, 1) end
41. Llamar a la función Main
Para comenzar el juego, es necesario llamar a la función Main. Con el código anterior en su lugar, lo haremos aquí:
Main()
42. Pantalla de carga

El archivo Default.png es una imagen que se muestra justo al iniciar la aplicación mientras el iOS carga los datos básicos para mostrar la pantalla principal. Añade esta imagen a la carpeta de origen de tu proyecto, será añadida automáticamente por el compilador de Corona.
43. Icono

Usando los gráficos que creaste antes, ahora puedes crear un icono de aspecto agradable. El tamaño del icono del iPhone sin retina es de 57x57px, pero la versión con retina es de 114x114px y la tienda iTunes requiere una versión de 512x512px. Sugiero crear primero la versión de 512x512 y luego reducirla para los otros tamaños.
No es necesario que tenga esquinas redondeadas o un brillo transparente, iTunes y el iPhone lo hacen por ti.
44. Pruebas en el simulador



¡Es el momento de la prueba final! Abre el Corona Simulator, busca la carpeta de tu proyecto y haz clic en "Abrir". Si todo funciona como se espera, ¡estás listo para el último paso!
45. Construir



En el Corona Simulator ve a File > Build y selecciona tu dispositivo de destino. Rellena los datos necesarios y haz clic en Build. Espera unos segundos y tu aplicación estará lista para ser probada en el dispositivo y/o enviada para su distribución.
Conclusión
En este tutorial, hemos aprendido sobre física, temporizadores, escuchas táctiles y otras habilidades que son útiles en una gran variedad de juegos. ¡Experimenta con el resultado final e intenta hacer tu versión personalizada del juego!
Espero que hayas disfrutado de esta serie y la hayas encontrado útil. ¡Gracias por leer!