Anuncios Google

Un nuevo tutorial de Lua, desde lo básico a lo concreto [9º Entrega][Imágenes, sprites, movimiento y límites]

¡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={}}
 pero he preferido hacerlo de ese modo.

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:

  1. Una imagen cargada con loadsprite
  2. Frame al que se desea cambiar la animación

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:

  • image.width(imagen) o imagen:width() devuelve el ancho, en nuestro caso: 22
  • image.height(imagen) o imagen:height() devuelve el alto, en nuestro caso: 27

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 -_-


Anuncios Google

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de barney77

Ayuda !

Ayuda !, por más que he indagado no encuentro el error, y, creo que aqui es el lugar correcto:

--Nivel 1
bola1 = {img = image.load("bolas/bola1.png"),x=436,y=136}
porteria = {img = image.load("porterias/porteria1.png"),x1=0,y1=0,x=0,y=0,x2=0,y2=0,dir="u",velocidad=2}
function move(porteria)
if porteria.dir=="u" then
	porteria.y=porteria.y+porteria.velocidad
end
if porteria.dir=="d" then
	porteria.y=porteria.y+porteria.velocidad
end
if porteria.dir=="u" and porteria.y>=porteria.y2+1 then
end
if porteria.dir=="d" and porteria.y>=porteria.y2+1 then
end
end
 
while true do
controls.read()
porteria.img:blit(porteria.x,porteria.y)
porteria.h=porteria.img:height()
bola1.img:blit(bola1.x,bola1.y)
if controls.up() then
	bola1.y=bola1.y-1
end
if controls.down() then
	bola1.y=bola1.y+1
end
if x<0 then
	x=0
elseif x+80>480 then
	x=480-80
end
 
if y<0 then
	y=0
elseif y+44>272 then
	y=272-44
end
if controls.press("cross") then 
	causaerror()
end
move(porteria)
screen.flip()
end

Me marca: Attempt to compare nil with number, en la linea 28.

Y aparte tmbn quiero ponerle limite a la bola1 ::(,

Ayuda.

Llora

Imagen de Chimecho

ya te había contestado Rober...

En el foro, rober te dijo lo que era:

if x<0 then
	x=0
elseif x+80>480 then
	x=480-80
end
 
if y<0 then
	y=0
elseif y+44>272 then
	y=272-44
end

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 -_-

Imagen de barney77

Muchas

Muchas gracias, me sirvió, y perdona, a rober no le había entendido.

Imagen de barney77

Difici,

Dificil, pero ahí vamos, gracias chime ! :D.

Imagen de NauTiluS1

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 ;)

Imagen de NauTiluS1

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

Imagen de Chimecho

eh ?

Esto no va en la sección de tutoriales?

mmm...

Imagen de Abaddon Ormuz

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 ;

Imagen de Chimecho

mmm...

Podrías poner el texto exacto que te dan las dos ventanitas (supongo que salen 2 seguidas) ?

Imagen de Abaddon Ormuz

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.

Imagen de Chimecho

mmm....

Que tienes en la línea 8 del script.lua?

Imagen de Abaddon Ormuz

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).

Imagen de DeViaNTe

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.

Imagen de iRVing_Prog

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


 

Imagen de iRVing_Prog

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


 

Imagen de iRVing_Prog

Excelente

le darè una leida cuando tenga tiempo.

Salu2

Excelente!!!

Excelente aporte, estaremos esperando ver más de esto ^^ salu2 y gracias

Imagen de -chus-

Excelente

Muy buen tuto y bien explicado, mis felicitaciones amigo, espero y ver mas de estos XD saludos

Imagen de DeViaNTe

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

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.