1. Code
  2. Mobile Development
  3. Corona

Corona SDK: Crear un juego de disparos

Scroll to top

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

App OverviewApp OverviewApp Overview

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

Target DeviceTarget DeviceTarget Device

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

InterfaceInterfaceInterface

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

Export GraphicsExport GraphicsExport Graphics

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

BackgroundBackgroundBackground

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

Title ViewTitle ViewTitle View

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

Credits ViewCredits ViewCredits View

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

Instructions

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

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

Enemy

Los enemigos aparecen desde el borde de la pantalla, el siguiente Group los almacena.

1
-- Enemy
2
3
local enemies

16. Alerta

AlertAlertAlert

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

Sounds

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

Loading Screen

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

Icon

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

TestingTestingTesting

¡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

BuildBuildBuild

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!