Crea un juego 'Endless Runner' desde cero: Interacción de sprites
Spanish (Español) translation by steven (you can also view the original English article)
Bienvenido al cuarto tutorial de nuestra serie sobre cómo crear un juego de carrera desde cero con Corona SDK. En esta sección, agregaremos gravedad, detección de colisiones y la capacidad de saltar al sprite del juego. ¡Vámonos!
Con suerte, los tutoriales hasta ahora han sido útiles y fáciles de seguir. Como siempre, si tienes alguna pregunta, ¡deja un comentario! La última vez repasamos cómo crear bonitos sprites a partir de hojas de sprites. Hoy, vamos a tomar lo que aprendimos en el último tutorial e incorporar ese monstruo de sprites a nuestro juego real. Una vez que estés dentro, aprenderemos cómo controlarlo y hacer que nuestro juego sea interactivo. La forma en que voy a hacer esto es tomar el código fuente que teníamos del tutorial de movimiento de fondo y agregar primero nuestro material de animación de monstruos. Si descargas los archivos para el tutorial, notarás que hay 2 carpetas, llamadas "old" y "new". 'Old' contiene todos los archivos del tutorial de movimiento en segundo plano que necesitas para empezar a trabajar en este tutorial. 'New' contiene todo el código y todo lo que tendrás una vez que hayas completado este tutorial. Entonces, continúa y descarga los archivos y abre el archivo main.lua de la carpeta anterior. Voy a dividir todo lo que hacemos en tres secciones. El primero cubrirá la organización de nuestro juego, al que haremos algunos cambios. El segundo cubrirá tomar lo que aprendimos en el tutorial de uso de sprites e implementarlo aquí. Repasaremos esa sección con bastante rapidez, ya que los detalles de lo que está sucediendo ya se han cubierto. ¡La tercera sección le dará a nuestro pequeño tipo gravedad, detección de colisiones y la capacidad de saltar!
Hasta ahora ponemos nuestras imágenes en el código en el orden en que queremos que aparezcan en pantalla. Cuanto antes se llamen, más atrás aparecerán en las capas de la pantalla. Esto funciona, pero hay una mejor manera de hacerlo. No siempre será realista poner cada imagen en el orden exacto en que deseas que aparezcan, y es posible que en algún momento desees cambiar el orden en que aparecen los objetos en la pantalla. Entonces, abre el archivo main.lua de la carpeta anterior y comenzaremos a hacer algunos cambios.
Lo primero que vamos a hacer es agregar la siguiente línea en la parte superior de la página justo debajo de la línea display.setStatusBar.
local sprite = require("sprite")
A continuación, agrega las siguientes dos líneas justo debajo de donde creamos los bloques del grupo de visualización:
local player = display.newGroup() local screen = display.newGroup()
El grupo 'player' de visualización será el grupo de visualización que contiene nuestro sprite héroe y el grupo 'screen' será un grupo de visualización que contiene todo lo demás. Pongamos más código y luego terminaré de explicar.
Inserta el siguiente código debajo del bucle 'for' donde instanciamos nuestros bloques de tierra:
--create our sprite sheet local spriteSheet = sprite.newSpriteSheet("monsterSpriteSheet.png", 100, 100) local monsterSet = sprite.newSpriteSet(spriteSheet, 1, 7) sprite.add(monsterSet, "running", 1, 6, 600, 0) sprite.add(monsterSet, "jumping", 7, 7, 1, 1) --set the different variables we will use for our monster sprite --also sets and starts the first animation for the monster local monster = sprite.newSprite(monsterSet) monster:prepare("running") monster:play() monster.x = 110 monster.y = 200 --these are 2 variables that will control the falling and jumping of the monster monster.gravity = -6 monster.accel = 0 --rectangle used for our collision detection --it will always be in front of the monster sprite --that way we know if the monster hit into anything local collisionRect = display.newRect(monster.x + 36, monster.y, 1, 70) collisionRect.strokeWidth = 1 collisionRect:setFillColor(140, 140, 140) collisionRect:setStrokeColor(180, 180, 180) collisionRect.alpha = 0 --used to put everything on the screen into the screen group --this will let us change the order in which sprites appear on --the screen if we want. The earlier it is put into the group the --further back it will go screen:insert(backbackground) screen:insert(backgroundfar) screen:insert(backgroundnear1) screen:insert(backgroundnear2) screen:insert(blocks) screen:insert(monster) screen:insert(collisionRect)
Ahora, repasemos ese enorme bloque de código.
Las dos primeras secciones las vamos a saltar. Esas secciones solo crean nuestro sprite monstruo a partir de nuestra hoja de sprites. Si tienes alguna pregunta sobre lo que está sucediendo allí, haz una revisión rápida del último tutorial donde revisamos la creación de sprites a partir de hojas de sprites. En la siguiente sección, he creado una forma de rectángulo básica llamada collisionRect, así es como vamos a hacer nuestra detección de colisiones para que nuestro monstruo pueda interactuar con el mundo. Esencialmente, lo que está sucediendo es que estamos creando un cuadrado invisible que va frente a nuestro monstruo. Si haces que el rectángulo sea visible (es decir, simplemente cambia el alfa a 100), verás que se encuentra justo en frente del monstruo y flota un poco por encima del suelo.



La razón por la que hacemos esto es que nos proporcionará un sistema de colisión que es fácil de manejar. Debido a que estamos haciendo un juego de carreras sin fin, nos preocupa principalmente lo que sucede justo en frente del monstruo (normalmente, lo que viene detrás de él no lo matará). Además, lo levantamos un poco del suelo para que la caja nunca choque con el suelo debajo del monstruo, solo cosas frente a él. El propio monstruo se encargará de las colisiones con el suelo, solo necesitamos algo para manejar las colisiones con objetos externos que podrían golpearlo de frente. Esto tendrá aún más sentido en los próximos dos tutoriales a medida que agreguemos cosas para que nuestro monstruo se encuentre.
La siguiente sección es donde insertamos todo en la pantalla. Por lo tanto, la pantalla es solo un grupo de visualización, no tiene nada de mágico. Sin embargo, al hacer esto, nos da una gran ventaja sobre cómo colocamos las cosas en la pantalla antes, y así es como tenemos control sobre cómo se ordenan las cosas. Ahora, independientemente de cuándo creamos los sprites, ahora aparecerán en el orden en que los coloquemos en la pantalla del grupo screen. Entonces, si decidimos que el monstruo debe ir detrás del fondo cerca de los objetos, simplemente necesitaríamos insertarlos en el grupo 'screen' después de haber insertado el monstruo.



A continuación, modifica tu función update() para que se parezca a esta:
local function update( event ) updateBackgrounds() updateSpeed() updateMonster() updateBlocks() checkCollisions() end
Esto llamará al resto de las funciones que necesitamos ejecutar para asegurarnos de que todo esté bien actualizado. Una cosa a tener en cuenta al trabajar con la función de actualización es que el orden sí importa. Para nuestro pequeño juego de carrera, el orden no es tan importante, ya que se llama treinta veces por segundo, por lo que cualquier cosa que se actualice se detectará extremadamente rápido. Además, no hay datos cruciales de que las funciones se puedan realizar entre sí. Sin embargo, habrá ocasiones en las que deberás tener cuidado con el orden en el que colocas las cosas. Esto es especialmente cierto cuando tienes varias funciones que actualizan las mismas variables de diferentes maneras. Por lo general, sin embargo, esto es solo una cuestión de usar el sentido común y podrás pasar lógicamente qué cosas deben manejarse primero.
Aquí están el resto de funciones que harán el trabajo que acabamos de llamar desde la función de actualización. Ponlos debajo de la función de actualización. Asegúrate de leer los comentarios, ya que los usaré para describir lo que está sucediendo.
function checkCollisions() wasOnGround = onGround --checks to see if the collisionRect has collided with anything. This is why it is lifted off of the ground --a little bit, if it hits the ground that means we have run into a wall. We check this by cycling through --all of the ground pieces in the blocks group and comparing their x and y coordinates to that of the collisionRect for a = 1, blocks.numChildren, 1 do if(collisionRect.y - 10 > blocks[a].y - 170 and blocks[a].x - 40 < collisionRect.x and blocks[a].x + 40 > collisionRect.x) then speed = 0 end end --this is where we check to see if the monster is on the ground or in the air, if he is in the air then he can't jump(sorry no double --jumping for our little monster, however if you did want him to be able to double jump like Mario then you would just need --to make a small adjustment here, by adding a second variable called something like hasJumped. Set it to false normally, and turn it to --true once the double jump has been made. That way he is limited to 2 hops per jump. --Again we cycle through the blocks group and compare the x and y values of each. for a = 1, blocks.numChildren, 1 do if(monster.y >= blocks[a].y - 170 and blocks[a].x < monster.x + 60 and blocks[a].x > monster.x - 60) then monster.y = blocks[a].y - 171 onGround = true break else onGround = false end end end function updateMonster() --if our monster is jumping then switch to the jumping animation --if not keep playing the running animation if(onGround) then --if we are alread on the ground we don't need to prepare anything new if(wasOnGround) then else monster:prepare("running") monster:play() end else monster:prepare("jumping") monster:play() end if(monster.accel > 0) then monster.accel = monster.accel - 1 end --update the monsters position accel is used for our jump and --gravity keeps the monster coming down. You can play with those 2 variables --to make lots of interesting combinations of gameplay like 'low gravity' situations monster.y = monster.y - monster.accel monster.y = monster.y - monster.gravity --update the collisionRect to stay in front of the monster collisionRect.y = monster.y end --this is the function that handles the jump events. If the screen is touched on the left side --then make the monster jump function touched( event ) if(event.phase == "began") then if(event.x < 241) then if(onGround) then monster.accel = monster.accel + 20 end end end end
Observa que la función 'touched' nunca se llama. En la parte inferior del código justo debajo de donde usa el temporizador que llama a la función de actualización, coloca este código:
Runtime:addEventListener("touch", touched, -1)
Repasemos un par de cosas del código que acabamos de introducir. La función 'touched' pasa en un evento. Cada "evento" tiene sus propiedades. Cuando decimos event.phase == "began", le estamos diciendo a Corona que queremos ser notificados tan pronto como el usuario toque la pantalla. Por el contrario, si hubiéramos dicho "finalizar" en lugar de 'comenzar', le estaríamos diciendo a Corona que no nos notifique hasta que el usuario haya levantado el dedo de la pantalla. También se almacenan en el evento las coordenadas del toque. Ésta es la razón por la que es importante usar comenzó y terminó. ¿Quieres conocer la ubicación de cuándo el usuario toca la pantalla por primera vez o cuándo la suelta? En la mayoría de las situaciones de juego, querrás saber tan pronto como el usuario toque la pantalla, pero no siempre. Para obtener una referencia completa de los eventos táctiles, puedes ir aquí (https://developer.anscamobile.com/reference/index/events/touch).
Entonces, cuando ejecutes esto, notarás que solo saltas si tocas el lado izquierdo de la pantalla. La razón por la que lo hacemos es porque queremos reservar el lado derecho de la pantalla para otras cosas, como disparar bolas de fuego. Eso no está incluido en el alcance de este proyecto, ¡pero llegaremos pronto! Con todo eso allí, ahora deberíamos estar listos para comenzar.



¡Con todo en su lugar, ahora deberías tener un monstruo que pueda correr y saltar desde el suelo! ¡Poco a poco, nuestro pequeño tutorial empieza a parecer un juego! Como siempre, si tienes alguna pregunta, dímela en la sección de comentarios a continuación y ¡gracias por seguirnos!