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.
1 |
application = |
2 |
{
|
3 |
content = |
4 |
{
|
5 |
width = 320, |
6 |
height = 480, |
7 |
scale = "letterbox" |
8 |
}, |
9 |
} |
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.
1 |
Necessary Classes |
2 |
|
3 |
Variables and Constants |
4 |
|
5 |
Declare Functions |
6 |
|
7 |
contructor (Main function) |
8 |
|
9 |
class methods (other functions) |
10 |
|
11 |
call Main function |
8. Ocultar la barra de estado
1 |
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:
1 |
-- Physics |
2 |
|
3 |
local physics = require('physics')
|
4 |
physics.start() |
10. Antecedentes



La siguiente línea de código crea un fondo simple para la interfaz de la aplicación.
1 |
-- Graphics |
2 |
|
3 |
-- [Background] |
4 |
|
5 |
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:
1 |
-- [Title View] |
2 |
|
3 |
local title |
4 |
local playBtn |
5 |
local creditsBtn |
6 |
local titleView |
12. Vista de los créditos



Esta vista muestra los créditos y el copyright del juego. Esta variable lo almacena:
1 |
-- [CreditsView] |
2 |
|
3 |
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.
1 |
-- Instructions |
2 |
|
3 |
local ins |
14. Turret

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

Los enemigos aparecen desde el borde de la pantalla, el siguiente Group los almacena.
1 |
-- Enemy |
2 |
|
3 |
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.
1 |
-- Alert |
2 |
|
3 |
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.
1 |
-- Sounds |
2 |
|
3 |
local bgMusic = audio.loadStream('POL-hard-corps-short.mp3')
|
4 |
local shootSnd = audio.loadSound('shoot.wav')
|
5 |
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.
1 |
-- Variables |
2 |
|
3 |
local timerSrc |
4 |
local yPos = {58, 138, 218} --posible Y positions for enemies
|
5 |
local speed = 3 |
6 |
local targetX --stores position of enemy when shot |
7 |
local targetY |
19. Declarar funciones
Declarar todas las funciones como local al principio.
1 |
-- Functions |
2 |
|
3 |
local Main = {}
|
4 |
local startButtonListeners = {}
|
5 |
local showCredits = {}
|
6 |
local hideCredits = {}
|
7 |
local showGameView = {}
|
8 |
local gameListeners = {}
|
9 |
local createEnemy = {}
|
10 |
local shoot = {}
|
11 |
local update = {}
|
12 |
local onCollision = {}
|
13 |
local addExBullets = {}
|
14 |
local alert = {}
|
20. Constructor
A continuación crearemos la función que inicializa la lógica del juego:
1 |
function Main() |
2 |
-- code... |
3 |
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.
1 |
function Main() |
2 |
titleBg = display.newImage('title.png')
|
3 |
playBtn = display.newImage('playBtn.png', 212, 163)
|
4 |
creditsBtn = display.newImage('creditsBtn.png', 191, 223)
|
5 |
titleView = display.newGroup(titleBg, playBtn, creditsBtn) |
6 |
|
7 |
startButtonListeners('add')
|
8 |
end |
22. Oyentes del botón de inicio
Esta función añade los listeners necesarios a los botones del TitleView.
1 |
function startButtonListeners(action) |
2 |
if(action == 'add') then |
3 |
playBtn:addEventListener('tap', showGameView)
|
4 |
creditsBtn:addEventListener('tap', showCredits)
|
5 |
else |
6 |
playBtn:removeEventListener('tap', showGameView)
|
7 |
creditsBtn:removeEventListener('tap', showCredits)
|
8 |
end |
9 |
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.
1 |
function showCredits:tap(e) |
2 |
playBtn.isVisible = false |
3 |
creditsBtn.isVisible = false |
4 |
creditsView = display.newImage('credits.png', -110, display.contentHeight-80)
|
5 |
transition.to(creditsView, {time = 300, x = 55, onComplete = function() creditsView:addEventListener('tap', hideCredits) end})
|
6 |
end |
24. Ocultar créditos
Cuando se toque la pantalla de créditos, se interpolará fuera del escenario y se eliminará.
1 |
function hideCredits:tap(e) |
2 |
playBtn.isVisible = true |
3 |
creditsBtn.isVisible = true |
4 |
transition.to(creditsView, {time = 300, y = display.contentHeight+creditsView.height, onComplete = function() creditsView:removeEventListener('tap', hideCredits) display.remove(creditsView) creditsView = nil end})
|
5 |
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.
1 |
function showGameView:tap(e) |
2 |
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.
1 |
ins = display.newImage('ins.png', 135, 255)
|
2 |
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.
1 |
-- Bullets Left |
2 |
|
3 |
bullets = display.newGroup() |
4 |
bulletsLeft = display.newGroup() |
5 |
for i = 1, 5 do |
6 |
local b = display.newImage('bullet.png', i*12, 12)
|
7 |
bulletsLeft:insert(b) |
8 |
end |
28. Puntuación TextField
Este es el TextField de puntuación creado en la parte superior derecha del escenario:
1 |
-- TextFields |
2 |
|
3 |
scoreTF = display.newText('0', 70, 23.5, 'Courier Bold', 16)
|
4 |
scoreTF:setTextColor(239, 175, 29) |
29. Torreta
Ahora colocaremos la torreta en el escenario.
1 |
-- Turret |
2 |
|
3 |
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.
1 |
enemies = display.newGroup() |
2 |
gameListeners('add')
|
3 |
audio.play(bgMusic, {loops = -1, channel = 1})
|
4 |
end |
31. Juego de Oyentes
Esta función añade los oyentes necesarios para iniciar la lógica del juego:
1 |
function gameListeners(action) |
2 |
if(action == 'add') then |
3 |
timerSrc = timer.performWithDelay(1200, createEnemy, 0) |
4 |
Runtime:addEventListener('enterFrame', update)
|
5 |
else |
6 |
timer.cancel(timerSrc) |
7 |
timerSrc = nil |
8 |
Runtime:removeEventListener('enterFrame', update)
|
9 |
gameBg:removeEventListener('tap', shoot)
|
10 |
end |
11 |
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.
1 |
function createEnemy() |
2 |
local enemy |
3 |
local rnd = math.floor(math.random() * 4) + 1 |
4 |
enemy = display.newImage('enemy.png', display.contentWidth, yPos[math.floor(math.random() * 3)+1])
|
5 |
enemy.name = 'bad' |
6 |
-- Enemy physics |
7 |
physics.addBody(enemy) |
8 |
enemy.isSensor = true |
9 |
enemy:addEventListener('collision', onCollision)
|
10 |
enemies:insert(enemy) |
11 |
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.
1 |
function shoot() |
2 |
audio.play(shootSnd) |
3 |
local b = display.newImage('bullet.png', turret.x, turret.y)
|
4 |
physics.addBody(b) |
5 |
b.isSensor = true |
6 |
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.
1 |
-- Remove Bullets Left |
2 |
bulletsLeft:remove(bulletsLeft.numChildren) |
3 |
-- End game 4 seconds after last bullet |
4 |
if(bulletsLeft.numChildren == 0) then |
5 |
timer.performWithDelay(4000, alert, 1) |
6 |
end |
7 |
end |
35. Mover enemigos
La siguiente función se ejecuta cada fotograma. Aquí la usamos para mover cada enemigo en la tabla de enemigos.
1 |
function update() |
2 |
-- Move enemies |
3 |
if(enemies ~= nil)then |
4 |
for i = 1, enemies.numChildren do |
5 |
enemies[i].x = enemies[i].x - speed |
6 |
end |
7 |
end |
36. Mover disparar balas
Un método similar se utiliza para las balas.
1 |
-- Move Shoot bullets |
2 |
if(bullets[1] ~= nil) then |
3 |
for i = 1, bullets.numChildren do |
4 |
bullets[i].y = bullets[i].y - speed*2 |
5 |
end |
6 |
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.
1 |
-- Move Explosion Bullets |
2 |
if(exploBullets[1] ~= nil) then |
3 |
for j = 1, #exploBullets do |
4 |
if(exploBullets[j][1].y ~= nil) then exploBullets[j][1].y = exploBullets[j][1].y + speed*2 end |
5 |
if(exploBullets[j][2].y ~= nil) then exploBullets[j][2].y = exploBullets[j][2].y - speed*2 end |
6 |
if(exploBullets[j][3].x ~= nil) then exploBullets[j][3].x = exploBullets[j][3].x + speed*2 end |
7 |
if(exploBullets[j][4].x ~= nil) then exploBullets[j][4].x = exploBullets[j][4].x - speed*2 end |
8 |
end |
9 |
end |
10 |
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.
1 |
function onCollision(e) |
2 |
audio.play(exploSnd) |
3 |
targetX = e.target.x |
4 |
targetY = e.target.y |
5 |
timer.performWithDelay(100, addExBullets, 1) |
6 |
-- Remove Collision Objects |
7 |
display.remove(e.target) |
8 |
e.target = nil |
9 |
display.remove(e.other) |
10 |
e.other = nil |
11 |
-- Increase Score |
12 |
scoreTF.text = tostring(tonumber(scoreTF.text) + 50) |
13 |
scoreTF.x = 90 |
14 |
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.
1 |
function addExBullets() |
2 |
-- Explosion bullets |
3 |
local eb = {}
|
4 |
local b1 = display.newImage('bullet.png', targetX, targetY)
|
5 |
local b2 = display.newImage('bullet.png', targetX, targetY)
|
6 |
local b3 = display.newImage('bullet.png', targetX, targetY)
|
7 |
local b4 = display.newImage('bullet.png', targetX, targetY)
|
8 |
physics.addBody(b1) |
9 |
b1.isSensor = true |
10 |
physics.addBody(b2) |
11 |
b2.isSensor = true |
12 |
physics.addBody(b3) |
13 |
b3.isSensor = true |
14 |
physics.addBody(b4) |
15 |
b4.isSensor = true |
16 |
table.insert(eb, b1) |
17 |
table.insert(eb, b2) |
18 |
table.insert(eb, b3) |
19 |
table.insert(eb, b4) |
20 |
table.insert(exploBullets, eb) |
21 |
end |
40. Alerta
La función de alerta crea una vista de alerta, la anima y termina el juego.
1 |
function alert() |
2 |
audio.stop(1) |
3 |
audio.dispose() |
4 |
bgMusic = nil |
5 |
gameListeners('rmv')
|
6 |
alertView = display.newImage('alert.png', 160, 115)
|
7 |
transition.from(alertView, {time = 300, xScale = 0.5, yScale = 0.5})
|
8 |
|
9 |
local score = display.newText(scoreTF.text, (display.contentWidth * 0.5) - 18, (display.contentHeight * 0.5) + 5, 'Courier Bold', 18) |
10 |
score:setTextColor(44, 42, 49) |
11 |
|
12 |
-- Wait 100 ms to stop physics |
13 |
timer.performWithDelay(1000, function() physics.stop() end, 1) |
14 |
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í:
1 |
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!



