¡Hola! aquí estoy de nuevo escribiendo el tutorial, espero que ya le hayan dado una leída a los anteriores tutoriales, si no lo han hecho todavía, no importa, iré poniendo links en donde lo crea necesario para repasar :D
Pues bien, en esta entrega veremos como cargar imágenes, y mostrar animaciones usando sprites y arrays, en si lo que haremos es mostrar un personaje (Link) en pantalla, lo moveremos con las teclas direccionales y también haremos que colisione con los lados de la pantalla para que no se salga de límites.
Para esta práctica, he preparado unas imágenes que necesitarán para seguir el tutorial, descarga: aquí (Gracias a DeViaNTe por el trabajo de extraer los sprites :P).
Procurar que al extraer, las imágenes queden dentro de "ms0:/PSP/GAME/Tutorial/sprites/*.png" para poder seguir el tutorial al pie de la letra. Ya que tenemos todo listo, podemos empezar con el código, abran el script.
Para poder mostrar nuestro personaje, tendremos que cargar las imágenes, para esto, usaremos un array (recomiendo leer la parte de arrays antes de continuar) que tendrá todas las imágenes de manera organizada, así que, procedemos a crear la tabla:
link = {} link["stay"]={} link["walk"]={}
Nota: También se pudo hacer
link={stay={}, walk={}}
Con este código creamos dos arrays dentro de el array link, estos serán los que contengan los distintos objetos de imagen que usaremos, procedemos a cargar las imágenes:
link.stay.up = image.load("sprites/link_stay_up.png") link["stay"].down = image.load("sprites/link_stay_down.png") link.stay["right"] = image.load("sprites/link_stay_right.png") link["stay"]["left"] = image.load("sprites/link_stay_left.png")
Sintaxis: image.load recibe un parámetro que es la ruta de la imagen a cargar, puede ser jpg, png o gif (sólo el primer frame si es una animación), de preferencia usaremos png, debido a la calidad.
Notar que la ruta no es absoluta ("ms0:/PSP/GAME/Tutorial/sprites/"), es relativa, es decir, partiendo de donde está el eboot.pbp, vamos a la carpeta sprites y cargamos las imágenes. Se recomienda acostumbrar a usar rutas relativas, para compatibilidad con plugins como Game Categories.
Notar también que he cargado de cuatro formas distintas, se puede usar la que se desee, en lo personal prefiero la 4º.
Bien, ya tenemos cargadas las imágenes para cuando link esté quieto, ahora necesitamos cargar la animación, para variar un poco, usaremos la función rawset:
rawset(link["walk"],"up",image.loadsprite("sprites/link_walk_up.png",22,27)) rawset(link["walk"],"down",image.loadsprite("sprites/link_walk_down.png",22,27)) rawset(link["walk"],"right",image.loadsprite("sprites/link_walk_right.png",22,27)) rawset(link["walk"],"left",image.loadsprite("sprites/link_walk_left.png",22,27))
Sintaxis de rawset: recibe 3 argumentos y funciona de esta forma, por ejemplo tenemos: rawset(array,posicion,valor) en realidad estamos haciendo array[posicion] = valor, sin importar que posición sea un número o una cadena de texto.
Sintaxis de loadsprite: esta función recibe 3 argumentos también, la ruta, el ancho de cada cuadro, y el alto de cada cuadro. De esta forma tendremos un solo objeto para todas las imágenes, luego para cambiar a otro cuadro de la animación usaremos setframe, que lo veremos luego.
En este punto ya tenemos todas las imágenes necesarias, pero ahora necesitaremos algunas variables para determinar el estado, la dirección, la posición en pantalla y el cuadro de la animación a mostrar:
status = "stay" -- al principio estará quieto, luego cuando se presione algo pasará a "walk" y caminará direction = "down" -- que mire hacia abajo, asi "nos ve" xD, los otros serian "up", "left" y "right" x = 10 -- posicion en X en la que se mostrará inicialmente y = 10 -- posicion en Y en la que se mostrará inicialmente anim = 0 -- cuadro de la animación actual, cuenta desde 0
Con eso ya tenemos nuestras imágenes cargadas y nuestras variables con valores iniciales, ahora a hacer el programa principal. Empezamos con un bucle infinito y lectura de controles:
while true do controls.read()
Como vamos a mover a nuestro personaje con las flechas (arriba, abajo, izquierda, derecha), necesitaremos un if...else...end hecho de tal forma que si se presiona una de las flechas, pase al siguiente cuadro de animación y además ponga el estado a "walk", caso contrario que resetee la animación (frame 0) y que el estado sea "stay", ahora en código:
if controls.up() or controls.down() or controls.left() or controls.right() then anim = anim + 1 status = "walk" else anim = 0 status = "stay" end
Nota: si no han comprendido la condición, recomiendo leer la parte de operaciones
Pero falta algo más, algo específico para cada flecha, es decir, si se presiona arriba, que la posición en Y disminuya (así sube nuestro personaje), y también que la dirección cambie a "up", lo mismo para el resto pero modificando la coordenada en X o en Y según corresponda, así como la dirección:
if controls.up() then y=y-1 direction = "up" elseif controls.down() then y=y+1 direction = "down" elseif controls.right() then x=x+1 direction = "right" elseif controls.left() then x=x-1 direction = "left" end
En este punto ya tenemos el movimiento y una parte de la animación, digo una parte porque aun no hemos cambiado el frame a mostrar. El frame actual debe ser cambiado solamente cuando estemos en estado "walk", es decir, cuando nuestro personaje se mueva, por lo tanto, haremos lo siguiente:
if status=="walk" then link[status][direction]:setframe(anim) end
Sintaxis: setframe recibe 2 argumentos, ¿eh? pero si sólo veo uno que dice anim. Pues veamos, setframe recibe estos argumentos:
Ahora, en lua, las siguientes dos sentencias hacen exactamente lo mismo:
image.setframe(imagen, frame) imagen:setframe(frame)
Es decir, al usar dos puntos, se pasa el objeto como primer argumento, por eso, aunque setframe reciba 2 argumentos, se puede omitir el primero si se usan dos puntos (si no ha quedado claro, revisar la introducción que está en la documentación)
Ya con todo esto que hemos hecho, tenemos lista nuestra imagen para ponerla en pantalla, para esto usaremos la función blit del módulo image
link[status][direction]:blit(x,y)
Sintaxis: image.blit recibe 3 argumentos, una imagen (cargada con load o con loadsprite), la coordenada en X y la coordenada en Y. Como ya vimos arriba, si usamos dos puntos, el primer argumento será la imagen que en nuestro caso es link[status][direction].
Notar que se puede usar variables en cualquier lado, ya que representan el valor que tienen asignado.
Por último, haremos el cambio de pantallas (flip), el código para forzar un error, y el end del while, con lo que el código queda así:
link = {} link["stay"]={} link["walk"]={} link.stay.up = image.load("sprites/link_stay_up.png") link["stay"].down = image.load("sprites/link_stay_down.png") link.stay["right"] = image.load("sprites/link_stay_right.png") link["stay"]["left"] = image.load("sprites/link_stay_left.png") rawset(link["walk"],"up",image.loadsprite("sprites/link_walk_up.png",22,27)) rawset(link["walk"],"down",image.loadsprite("sprites/link_walk_down.png",22,27)) rawset(link["walk"],"right",image.loadsprite("sprites/link_walk_right.png",22,27)) rawset(link["walk"],"left",image.loadsprite("sprites/link_walk_left.png",22,27)) status = "stay" -- al principio estará quieto, luego cuando se presione algo pasará a "walk" y caminará direction = "down" -- que mire hacia abajo, asi "nos ve" xD, los otros serian "up", "left" y "right" x = 10 -- posicion en X en la que se mostrará inicialmente y = 10 -- posicion en Y en la que se mostrará inicialmente anim = 0 -- cuadro de la animación actual, cuenta desde 0 while true do controls.read() if controls.up() or controls.down() or controls.left() or controls.right() then anim = anim + 1 status = "walk" else anim = 0 status = "stay" end if controls.up() then y=y-1 direction = "up" elseif controls.down() then y=y+1 direction = "down" elseif controls.right() then x=x+1 direction = "right" elseif controls.left() then x=x-1 direction = "left" end if status=="walk" then link[status][direction]:setframe(anim) end link[status][direction]:blit(x,y) screen.flip() if controls.l() and controls.cross() then broke() end end
Listo, a ejecutarlo se ha dicho!
Bien, lo primero que notaremos es que la animación va completamente mal xD, esto se debe a que los frames avanzan muy rápido y además de eso, no pusimos algo que regrese la animación cuando pase del décimo cuadro, para esto necesitaremos reducir el valor del aumento de anim al mover y además poner una condición que pasado de tal número, regrese:
if controls.up() or controls.down() or controls.left() or controls.right() then anim = anim + 0.2 -- más lento status = "walk" if math.floor(anim)>9 then -- ya mismo lo explico anim = 0 -- regresar al primer frame end else anim = 0 status = "stay" end
Sintaxis: floor es la función piso, o de forma más clara, quita los decimales, ej: math.floor(2.165468) y math.floor(2.98474) dan como resultado: 2
Usamos esto para que al pasar el frame 10 (dice 9 porque hay que recordar que contamos desde 0) regrese a 0. Por el setframe no hay que preocuparse de que le pasamos un frame decimal, ya que según pruebas que he hecho, he llegado a la conclusión de que también redondea para abajo (math.floor).
Listo, con esto nuestro personaje casi se mueve bien, digo casi porque habrán notado que nuestro personaje se puede salir de la pantalla, para esto tendremos que poner límites para el movimiento, estos límites no son más que condiciones que pondremos justo antes de que se imprima:
if x<0 then x=0 elseif x+22>480 then x=480-22 end if y<0 then y=0 elseif y+27>272 then y=272-27 end
Para entender mejor esto, he elaborado el siguiente gráfico:
Nota: Los valores 22 y 27 son el ancho y el alto de la imagen respectivamente, como son conocidos lo he puesto directamente, pero también se pueden obtener usando estas funciones:
Dejo el código final por si acaso alguien se perdió:
link = {} link["stay"]={} link["walk"]={} link.stay.up = image.load("sprites/link_stay_up.png") link["stay"].down = image.load("sprites/link_stay_down.png") link.stay["right"] = image.load("sprites/link_stay_right.png") link["stay"]["left"] = image.load("sprites/link_stay_left.png") rawset(link["walk"],"up",image.loadsprite("sprites/link_walk_up.png",22,27)) rawset(link["walk"],"down",image.loadsprite("sprites/link_walk_down.png",22,27)) rawset(link["walk"],"right",image.loadsprite("sprites/link_walk_right.png",22,27)) rawset(link["walk"],"left",image.loadsprite("sprites/link_walk_left.png",22,27)) status = "stay" -- al principio estará quieto, luego cuando se presione algo pasará a "walk" y caminará direction = "down" -- que mire hacia abajo, asi "nos ve" xD, los otros serian "up", "left" y "right" x = 10 -- posicion en X en la que se mostrará inicialmente y = 10 -- posicion en Y en la que se mostrará inicialmente anim = 0 -- cuadro de la animación actual, cuenta desde 0, e irá hasta 10 while true do controls.read() if controls.up() or controls.down() or controls.left() or controls.right() then anim = anim + 0.2 status = "walk" if math.floor(anim)>9 then anim = 0 end else anim = 0 status = "stay" end if controls.up() then y=y-1 direction = "up" elseif controls.down() then y=y+1 direction = "down" elseif controls.right() then x=x+1 direction = "right" elseif controls.left() then x=x-1 direction = "left" end if status=="walk" then link[status][direction]:setframe(anim) end if x<0 then x=0 elseif x+22>480 then x=480-22 end if y<0 then y=0 elseif y+27>272 then y=272-27 end link[status][direction]:blit(x,y) screen.flip() if controls.l() and controls.cross() then broke() end end
Listo, con ese código ya tendremos a link moviéndose por la pantalla de nuestras PSP's, y además no podrá salir de ella >=) (bwa ha ha ha)
Límites con funciones math.min() y math.max()
Ya vimos que para limitar el área por la que puede andar link, pusimos estas condiciones:
if x<0 then x=0 elseif x+22>480 then x=480-22 end if y<0 then y=0 elseif y+27>272 then y=272-27 end
Pues bien, hay otra forma de hacer eso en dos líneas. En lua, disponemos de dos funciones muy útiles que son math.min() y math.max(), las dos reciben cualquier cantidad de argumentos numéricos, y devuelven el de menor valor y el de mayor valor respectivamente.
Sabiendo eso, podemos hacer que la coordenada en X tome el valor máximo entre 0 (borde izquierdo de la pantalla) y la posición actual, es decir:
x = math.max(0,x)
Pero también tenemos que verificar que no sobrepase el borde derecho, para esto usaremos math.min:
x = math.min(math.max(0,x), 480-22)
Notar que como primer argumento hemos pasado lo que ya teníamos para el borde izquierdo (math.max(0,x)), y como segundo argumento, el límite derecho, que es el borde derecho de la PSP (480) menos el ancho de la imagen (22).
Y se puede hacer lo mismo con la coordenada en Y:
y = math.min(math.max(0,y), 272-27)
Con esto nuestro código quedaría así:
link = {} link["stay"]={} link["walk"]={} link.stay.up = image.load("sprites/link_stay_up.png") link["stay"].down = image.load("sprites/link_stay_down.png") link.stay["right"] = image.load("sprites/link_stay_right.png") link["stay"]["left"] = image.load("sprites/link_stay_left.png") rawset(link["walk"],"up",image.loadsprite("sprites/link_walk_up.png",22,27)) rawset(link["walk"],"down",image.loadsprite("sprites/link_walk_down.png",22,27)) rawset(link["walk"],"right",image.loadsprite("sprites/link_walk_right.png",22,27)) rawset(link["walk"],"left",image.loadsprite("sprites/link_walk_left.png",22,27)) status = "stay" -- al principio estará quieto, luego cuando se presione algo pasará a "walk" y caminará direction = "down" -- que mire hacia abajo, asi "nos ve" xD, los otros serian "up", "left" y "right" x = 10 -- posicion en X en la que se mostrará inicialmente y = 10 -- posicion en Y en la que se mostrará inicialmente anim = 0 -- cuadro de la animación actual, cuenta desde 0, e irá hasta 10 while true do controls.read() if controls.up() or controls.down() or controls.left() or controls.right() then anim = anim + 0.2 status = "walk" if math.floor(anim)>9 then anim = 0 end else anim = 0 status = "stay" end if controls.up() then y=y-1 direction = "up" elseif controls.down() then y=y+1 direction = "down" elseif controls.right() then x=x+1 direction = "right" elseif controls.left() then x=x-1 direction = "left" end if status=="walk" then link[status][direction]:setframe(anim) end x = math.min(math.max(0,x), 480-22) y = math.min(math.max(0,y), 272-27) link[status][direction]:blit(x,y) screen.flip() if controls.l() and controls.cross() then broke() end end
Eso ha sido todo por hoy, si han llegado hasta aquí y han comprendido todo, tal vez ya puedan hacer incluso un pong :) sin sonido claro está. Cualquier duda, dejen un comentario.
Saludos!
PSP FAT [5.00 M33-6] [6.60 ME] [Hard Moded] / Nintendo Wii [4.1U] [cIOS Hermes] [D2X cIOS] / iPhone 3G [4.2.1] [BB 05.13.04] [redsn0w] / iPod Touch 4 [6.1] [evasi0n]
07/05/10: Tuve el placer de llamarle con todo derecho "N00B" a molotovi xDDD
Recuerda: Sé el intérprete -_-
Ayuda !
Ayuda !, por más que he indagado no encuentro el error, y, creo que aqui es el lugar correcto:
Me marca: Attempt to compare nil with number, en la linea 28.
Y aparte tmbn quiero ponerle limite a la bola1 ::(,
Ayuda.
ya te había contestado Rober...
En el foro, rober te dijo lo que era:
Ahi tienes variables llamadas x y y, que no estan en ningún lado del código, sólo allí, lo que si veo son otras variables como bola.x, bola.y, porteria.x, y porteria.y
Yo por lo general no contesto en el foro si veo que alguien ya ha contestado correctamente
Saludos!
PSP FAT [5.00 M33-6] [6.60 ME] [Hard Moded] / Nintendo Wii [4.1U] [cIOS Hermes] [D2X cIOS] / iPhone 3G [4.2.1] [BB 05.13.04] [redsn0w] / iPod Touch 4 [6.1] [evasi0n]
07/05/10: Tuve el placer de llamarle con todo derecho "N00B" a molotovi xDDD
Recuerda: Sé el intérprete -_-
Muchas
Muchas gracias, me sirvió, y perdona, a rober no le había entendido.
Difici,
Dificil, pero ahí vamos, gracias chime ! :D.
Genial ya me estoy
Genial ya me estoy aprendiendo el code
Mucho mejor que aprenderte el
Mucho mejor que aprenderte el code, trata de entenderlo ;)
Eso quise decir xD no dije
Eso quise decir xD no dije que me lo aprenderia de memoria jaja mas bien dije que lo estoy estudiando que es igual que entenderlo solo que puse aprendiendo.
Saludos
Follow me twitter nautilus1_jmccu!!
Sony, ya a nadie le interesa tus UMD si alguien compra una psp es por nosotros los coders. Madura y has un FW que haga la diferencia y anime a las personas en comprar un PSP
eh ?
Esto no va en la sección de tutoriales?
mmm...
Buen tutorial ;) aunque me
Buen tutorial ;) aunque me aparece un error (sale una ventanita como de Windows 98) y por más que reviso no doy en dónde esta el problema.
Tendré que desistir por hoy (regreso estresado y luego esto que no me sale... XD), revisaré todo de nuevo a ver en dónde está mi error...
PD: Pregunta de novato, porqué no se coloca el ";" (punto y coma) al finalizar una sentencia? en la preparatoria me comienzan a enseñar C (teórico hasta ahora...) y me dicen hasta el cansancio que deben terminar con ;
mmm...
Podrías poner el texto exacto que te dan las dos ventanitas (supongo que salen 2 seguidas) ?
FILE¦[string"os.makeniceerror
FILE¦[string"os.makeniceerror(2,"FILE¦ms0:/PSP/GAME/Tutorial/script.lua:LINE¦..."]:LINE¦1:[LDE065] [LDE059 <"FILE ¦ms0:/PSP/GAME/Tutorial/script.lua:LINE¦8:Error:>]
FILE¦ms0:/PSP/GAME/Tutorial/script.lua:LINE¦8:Error:
No existe el archivo: '27'
Compruebe que la ruta sea correcta.
mmm....
Que tienes en la línea 8 del script.lua?
Nada, ya está solucionado,
Nada, ya está solucionado, tenía _png en lugar de .png, y no coloqué la coma antes del 22, eso me pasa por escribir rápido (que vergüenza).
tg k akabar eso :P
o es string inacabada, o el tipico falta bla cerca de bla xD. Amos, error de sintaxis.
El error sale porque el
El error sale porque el código está pensado para usarse desde la psp, con LuaDEV.
No va ";" al final porque no hace falta. Esto NO ES C, es Lua. Se pueden poner, si gustas, pero te repito, no es necesario.
Hola Rober
Seguramente si lo esta usando desde la PSP. Solo que los errores en ocasiones LuaDEV los presenta como una ventana de W98 :)
Salu2
Respuesta
La respuesta a tu pregunta;
Pues por que LUA no es C :D, son cosas de sintaxis, por ejemplo en python tampoco tienes que usar el ";" para terminar una sentencia en Javascript tampoco aunque lo puedes poner para formar buenos habitos de programacion y acostumbrarse a un lenguaje como C o C++, no se si en LUA interfiera ponerlos, pero en Javascript no.
Ya te estan enseñando eso? :o. A mi no me han dejado de poner problemas de razonamiento xD. QUIERO PROGRAMAR!!! grr
Salu2
Excelente
le darè una leida cuando tenga tiempo.
Salu2
Excelente!!!
Excelente aporte, estaremos esperando ver más de esto ^^ salu2 y gracias
Excelente
Muy buen tuto y bien explicado, mis felicitaciones amigo, espero y ver mas de estos XD saludos
buena man
Buen tuto, se parece mucho a mi zelda... xDDDD
Par de cosasss, rawset no es necesario, pues rawset(array,key,valor) es lo mismo exactamente que array[key] = valor, pero sin invocar metametodos.
Ya que estructuras las imagenes dentro del objeto o array "link", podrias meter las variables que tienes sueltas y sirven para link también dentro, y no tenerlas sueltas, es una muy sana costumbre, es decir, link.x, link.y, link.direction, link.anim ... Para un futuro añadido de más muñekajos sueltos por la pantalla... :P
Bien, con esto ya tenemos un personaje con movimiento, en un area limitada, con "cámara fija". El siguiente tuto lo quiero ver de mapeado! Algo simple, una capa de mapa y una capa de acciones, cámara fija y area limitada, dibujado dinamico, y despues, rompiendo barreras, cámara siguiendo personaje y mapeado enoooooooooorme :D, como lo ves?
Un saludo gente!
Actualmente desarrollando nuestra web y UXCode : http://www.gcrew.es