Crea un juego de corredor sin fin desde cero: Movimiento del fondo
Spanish (Español) translation by steven (you can also view the original English article)
Bienvenido al segundo tutorial de nuestra serie sobre cómo crear un juego de corredor sin fin desde cero con Corona SDK. En esta sección, veremos cómo poner en marcha rápidamente un nivel básico de desplazamiento en segundo plano. ¡Abre tu editor de texto y comencemos!
Requisitos previos
Si aún no has visto el primer tutorial de esta serie, compruébalo muy rápido antes de pasar a este. Si tienes un poco de experiencia en programación, podrás saltar rápidamente todo el primer tutorial, pero si no la tienes, entonces deberías sacar tiempo para ver algunos de los conceptos básicos de la programación trabajando con Lua y el Corona SDK.
Configuración del proyecto
Entonces, lo primero que debemos cubrir es el archivo build.settings. Este es un archivo que debe ir en la misma carpeta que el archivo main.lua, y es lo que usamos para configurar algunas de las especificaciones de nuestra aplicación. Ahora, dependiendo de la plataforma con la que estés trabajando (Android o iOS), tu archivo build.settings se usará para configurar diferentes cosas. Por ejemplo, los desarrolladores de Android tienen que lidiar con el archivo de manifiesto de sus aplicaciones y sus permisos, mientras que los desarrolladores de iOS tienen que preocuparse por su archivo *.plist. El archivo build.settings es donde se manejarán muchas de esas configuraciones de nivel de aplicación. Una de las cosas buenas de Corona SDK es que podemos combinar nuestra información build.settings de Android e iOS en el mismo archivo build.settings. De esta manera, si estás trabajando en ambos tipos de dispositivos, no tienes que administrar varios archivos build.settings. Para nuestros propósitos en este momento, solo usaremos el archivo build.settings para configurar la orientación de nuestro dispositivo. Por lo tanto, crea un nuevo archivo llamado build.settings en la misma carpeta que vas a tener tu archivo main.lua y pon este código en él:
--Notice you can use comments in this section as well settings = { orientation = { default = "landscapeRight", supported = { "landscapeRight", "landscapeLeft" }, }, }
Esto nos da un dispositivo orientado de forma horizontal en dispositivos iOS y Android. El código es bastante sencillo y repasaremos más configuraciones que querrás usar en futuros tutoriales, pero por ahora esto debería ser todo lo que necesitamos para avanzar. Si deseas profundizar por tu cuenta, puedes encontrar todas las opciones de build.settings aquí. Para asegurarnos de que el archivo funcionó, hagamos una prueba rápida en el simulador. Abre tu archivo main.lua y pon este código allí:
--This line will remove the status bar at the top of screen, --some apps you might want to keep it in, but not for games! display.setStatusBar(display.HiddenStatusBar) local testRect = display.newRect(0,0,50,50) testRect:setFillColor(150,0,0);
Entonces, después de juntar todo eso, si tu build.settings está en el lugar correcto, deberías tener algo que se vea así:



Si el dispositivo está en posición vertical (modo de retrato) y no está de costado como arriba (modo horizontal) significa que algo salió mal. Sin embargo, es de esperar que ese no sea el caso, ya que el código anterior es todo lo que hay que agregar. Entonces, ahora que eso está fuera del camino, podemos pasar a poner nuestro juego en funcionamiento. El objetivo de cada uno de los tutoriales es que tengamos una nueva sección de trabajo. Cada uno se basará en el anterior, pero después de cada iteración tendrás algo que debería ser jugable.
Agregar un fondo
Lo primero que vamos a querer hacer es mover nuestro nivel. Vamos a tener varias capas en nuestro juego que se desplazarán a diferentes niveles de velocidad. Esto nos dará la ilusión de lo que nos gusta llamar movimiento "parallax". Cuando estés ejecutando el programa en el simulador, continúa y selecciona iPhone por ahora (si aún no lo has hecho). Cambia esto yendo a Ventana > Ver como> iPhone en el simulador. Los recursos se han diseñado para un dispositivo que se ejecuta a una resolución de 480x320px. Hay, por supuesto, formas de asegurarse de que funcione para todas las resoluciones, pero para nuestros propósitos de prueba, ahora simplemente quédate con el simulador de iPhone. Continúa y coloca este código en tu programa:
--takes away the display bar at the top of the screen display.setStatusBar(display.HiddenStatusBar) --adds an image to our game centered at x and y coordinates local backbackground = display.newImage("images/background.png") backbackground.x = 240 backbackground.y = 160 local backgroundfar = display.newImage("images/bgfar1.png") backgroundfar.x = 480 backgroundfar.y = 160
Para que esto funcione, continúa y usa las imágenes que proporcioné. Para que el código funcione "tal cual", deberás colocar las imágenes en una carpeta llamada 'images'. La carpeta de imágenes debe estar en la misma carpeta que tu archivo main.lua y build.settings. Una vez que lo hagas, deberías obtener una pantalla que se ve así:



¿Observas cómo la imagen 'backgroundfar' está delante de la imagen 'backbackground'? Imagínate apilando varios platos uno encima del otro. Cuanto más apiles, más estarás cubriendo el primer plato en el que apilaste todo para empezar. Esto también es cierto con las imágenes y el texto que ingresas en tu código. Cada vez que desees agregar algo a la pantalla, a menos que se indique lo contrario, ese elemento agregado cubrirá todo lo que ya has apilado. Vamos a continuar y apilemos algunas cosas más en nuestra pantalla. Agrega esto a tu código debajo de las otras líneas:
local backgroundnear1 = display.newImage("images/bgnear2.png") backgroundnear1.x = 240 backgroundnear1.y = 160 local backgroundnear2 = display.newImage("images/bgnear2.png") backgroundnear2.x = 760 backgroundnear2.y = 160
Ahora, deberíamos tener otra capa apilada en la parte superior de la siguiente manera:



Ahora, cuando mires esto, notarás que aunque agregamos 2 instancias de bgnear2.png, solo una de ellas aparece en la pantalla. La razón por la que hacemos cosas así se hará un poco más obvia cuando comencemos a mover todo. Por ahora, continúa y cambia tu vista de iPhone a iPhone 4 de la misma manera que lo hicimos antes. Debido a que diseñamos nuestras imágenes con la resolución de un iPhone sin retina (por ejemplo, 480x320px), si cambiamos a una resolución más alta como el iPhone 4 (por ejemplo, 960x640px), todo se reduce y podemos ver más de lo que está sucediendo. Cambia a la resolución del iPhone 4 y deberías ver esto ahora:



Siempre que queramos ver qué está sucediendo, cambiar a la vista más grande será muy útil. Ten en cuenta que el hecho de que no puedas ver algo no significa que no esté pasando nada. Ahora que tenemos esas imágenes de fondo, hagamos que se muevan. A medida que avance en los tutoriales, intentaré documentar todo lo que está sucediendo dentro del código, así que si alguna vez no estás seguro de lo que el código hace, ¡asegúrate de leer los comentarios en el código! Ahora, debajo del código agregado anteriormente incluye esto:
--the update function will control most everything that happens in our game --this will be called every frame(30 frames per second in our case, which is the Corona SDK default) local function update( event ) --updateBackgrounds will call a function made specifically to handle the background movement updateBackgrounds() end function updateBackgrounds() --far background movement backgroundfar.x = backgroundfar.x - (.25) --near background movement backgroundnear1.x = backgroundnear1.x - (3) --if the sprite has moved off the screen move it back to the --other side so it will move back on if(backgroundnear1.x < -239) then backgroundnear1.x = 760 end backgroundnear2.x = backgroundnear2.x - (3) if(backgroundnear2.x < -239) then backgroundnear2.x = 760 end end --this is how we call the update function, make sure that this line comes after the --actual function or it will not be able to find it --timer.performWithDelay(how often it will run in milliseconds, function to call, --how many times to call(-1 means forever)) timer.performWithDelay(1, update, -1)
Cuando ejecutes esto, ejecútalo primero mientras aún estás en la vista del iPhone 4. Esto te permitirá ver lo que realmente está sucediendo con todos los diferentes sprites por los que nos movemos. Una vez que lo hayas mirado en esta vista, continúa y vuelve a la vista normal de iPhone para que puedas ver cómo se ve realmente. ¡Con todo eso allí, ahora deberías tener todo en movimiento! Ahora un par de cosas para mirar antes de continuar. ¿Observas que solo hay una instancia de backgroundfar, pero hay dos instancias de backgroundnear? Esto es algo que depende completamente de ti. La razón por la que hay dos es porque para el fondo más cercano (por ejemplo, árboles volando), queremos que se repita una y otra vez. Para el fondo lejano, quería que fuera algo que pasara muy lentamente y que no se repitiera (por ejemplo, normalmente solo hacer esto para fondos que están muy atrás, le da a los niveles una sensación única). Esto es algo que harías cuando tu nivel de desplazamiento va a terminar. Si realmente quisieras un desplazamiento interminable, querrías adoptar el mismo enfoque que utilizaste para el fondo cercano y colocar dos (uno al lado del otro). Al colocar dos (uno al lado del otro), o tres, o tantos como quieras, te permite crear mundos aparentemente grandes con recursos mínimos. Lo único que tenemos que hacer es mover el sprite al otro lado de la pantalla.
Lo siguiente que vamos a hacer es agregar el suelo. Esto tomará un par de pasos más, pero iremos despacio y nos aseguraremos de que todo se coloque en el lugar correcto. Si no hiciste el primer tutorial y no entiendes cómo usar grupos o no entiendes qué son, ahora sería un buen momento para revisarlo. Aquí está el código para incluir (colócalo justo debajo del código donde configuramos backgroundnear2, antes de la función 'update'):
--create a new group to hold all of our blocks local blocks = display.newGroup() --setup some variables that we will use to position the ground local groundMin = 420 local groundMax = 340 local groundLevel = groundMin --this for loop will generate all of your ground pieces, we are going to --make 8 in all. for a = 1, 8, 1 do isDone = false --get a random number between 1 and 2, this is what we will use to decide which --texture to use for our ground sprites. Doing this will give us random ground --pieces so it seems like the ground goes on forever. You can have as many different --textures as you want. The more you have the more random it will be, just remember to --up the number in math.random(x) to however many textures you have. numGen = math.random(2) local newBlock print (numGen) if(numGen == 1 and isDone == false) then newBlock = display.newImage("images/ground1.png") isDone = true end if(numGen == 2 and isDone == false) then newBlock = display.newImage("images/ground2.png") isDone = true end --now that we have the right image for the block we are going --to give it some member variables that will help us keep track --of each block as well as position them where we want them. newBlock.name = ("block" .. a) newBlock.id = a --because a is a variable that is being changed each run we can assign --values to the block based on a. In this case we want the x position to --be positioned the width of a block apart. newBlock.x = (a * 79) - 79 newBlock.y = groundLevel blocks:insert(newBlock) end
Los comentarios en el código deberían ayudarte a comprender qué hace todo hasta ahora. Con suerte, lo que estás viendo ahora se parece a esto:



Ahora, lo último que tenemos que hacer es empezar a mover los bloques. Vamos a seguir y agreguemos el código para eso. Coloca este código después del final de la función 'update':
function updateBlocks() for a = 1, blocks.numChildren, 1 do if(a > 1) then newX = (blocks[a - 1]).x + 79 else newX = (blocks[8]).x + 79 end if((blocks[a]).x < -40) then (blocks[a]).x, (blocks[a]).y = newX, groundLevel else (blocks[a]):translate(-5, 0) end end end
Antes de que algo se mueva realmente, debemos estar seguros de que realmente llamamos a la función desde la función de actualización. Por lo tanto, dentro de la función 'update' debajo de la línea updateBackgrounds(), llama a la función updateBlocks(). Con eso ahí, deberíamos tener todo en movimiento ahora. Observa que en lugar de mover manualmente los bloques, usamos la función de traducción. Esta función está disponible para cualquier objeto que creamos a través de la llamada newImage. Cualquiera de los métodos funcionará, así que usa uno u otro, o usa una mezcla. Otra cosa que debes notar es que hay un espacio entre algunos de los bloques. Esto sucede porque estamos configurando la ubicación de los nuevos bloques justo al lado de la ubicación anterior del último bloque. El problema es que cuando lo hacemos de esta manera, intentamos colocarlo junto a un objeto en movimiento, por lo que es posible que no siempre golpeemos justo al lado. Este problema se agrandará aún más una vez que intentemos aumentar la velocidad de desplazamiento de los bloques, cuanto más rápido se muevan, mayor será la brecha. Este problema se puede solucionar simplemente agregando una variable de velocidad y haciendo nuestros cálculos basados en esa velocidad. Regresa a las líneas de código donde inicializamos las variables groundMin
y groundMax
, y agrega esto:
local speed = 5;
A continuación, reemplaza las funciones updateBlocks() y updateBackgrounds() por estas:
function updateBlocks() for a = 1, blocks.numChildren, 1 do if(a > 1) then newX = (blocks[a - 1]).x + 79 else newX = (blocks[8]).x + 79 - speed end if((blocks[a]).x < -40) then (blocks[a]).x, (blocks[a]).y = newX, (blocks[a]).y else (blocks[a]):translate(speed * -1, 0) end end end function updateBackgrounds() --far background movement backgroundfar.x = backgroundfar.x - (speed/55) --near background movement backgroundnear1.x = backgroundnear1.x - (speed/5) if(backgroundnear1.x < -239) then backgroundnear1.x = 760 end backgroundnear2.x = backgroundnear2.x - (speed/5) if(backgroundnear2.x < -239) then backgroundnear2.x = 760 end end
¡Con esas funciones actualizadas, tus bloques deberían moverse bien sin espacios entre ellos!
Una última cosa para hacer esto más parecido a un juego. Regresa a la función 'update' y coloca esta línea de código después de las funciones de actualización:
speed = speed + .05
Una vez que estés allí, podrás ver cómo el nivel se acelera ante tus ojos. El valor exacto, por supuesto, es solo un ejemplo, la rapidez con la que actualices el juego dependerá de tu juego y de tu audiencia. Todo lo que queda en este punto es lanzar un héroe y algunos obstáculos y tenemos un juego. Sin embargo, eso vendrá más tarde, ya que esto concluye el proyecto por ahora. Hacer que todo eso se ejecutara no fue tan malo, y continuaremos agregando muchas funciones en cada iteración. Asegúrate de volver a consultar el siguiente tutorial y, si tienes alguna pregunta o comentario, házmelo saber en los comentarios a continuación.