Anuncios Google

PAlib básico (día 5)

Tutoriales avanzados de Homebrewes

Día 5

Muy bien, ya dominamos lo necesario de C/C++, y somos capaces de mostrar sprites y fondos en pantalla. Es hora de darle chicha a nuestros proyectos. Este tutorial trata completamente de las matemáticas básicas para darle vida a los mundos que creemos: desde velocidades distintas a diversos objetos a crear una gravedad para que todo en el aire caiga. Al fin y al cabo, para todo lo que queramos hacer en la DS (y en cualquier plataforma que programemos), tendrá que ser a base de cálculos matemáticos.

Antes de nada, y como tenemos por costumbre, habrá que repasar viejos conceptos y aprender otros nuevos. Éstos son los conceptos que necesariamente tendremos que conocer para seguir este tutorial:

  • Aceleración: La teoría indica que la aceleración es la cantidad de distancia por segundo en la que aumenta una velocidad. O en otras palabras: a cada segundo, velocidad=velocidad+aceleración.
  • Bitwise Shift: En español desplazamiento de bit". Lo vimos en el Día 2, con los símbolos >> y <<. Un desplazamiento de bit se basa en usar <<8 (multiplicar por 256) o >>8 (dividir por 256). NOTA: Existen otros desplazamientos, como >>2 y <<2, pero son menos frecuentes, y por el momento no les daremos uso.
  • Colisión: Una colisión se produce cuando un cuerpo toca otro cuerpo. Las colisiones nos interesan para que un sprite "choque" con otro, y reaccione en consecuencia (consigue más vida, muere...).
  • Coseno: Razón trigonométrica. El coseno es el resultado de dividir el cateto contiguo de un triángulo rectángulo por la hipotenusa, y es igual para todos sus triángulos semejantes.
  • Define: Un define, en C, es "un método de sustitución". Le indicamos una cadena de texto, y cada vez que esa cadena se encuentre en el código será sustituido por el valor que le demos. (Ir a la sección de gravedad para más información)
  • Gravedad: La gravedad es una aceleración, concretamente la aceleración con la que un cuerpo mayor atrae a otro menor. No os voy a marear con física :P, entended la aceleración con la que un sprite/fondo cae al suelo.
  • Seno: Razón trigonométrica. El seno es el resultado de dividir el cateto opuesto de un triángulo rectángulo por la hipotenusa, y es igual para todos sus triángulos semejantes.
  • Velocidad: En concepto teórico la velocidad es la distancia que recorre un cuerpo al segundo. En nuestro caso, será la cantidad de píxeles que se mueve un sprite/fondo al frame.

Muy bien, metámonos de lleno en el tutorial al que más intríngulis tiene de todos, y el que más útil os será. Intentaré ir poco a poco, de lo más sencillo a lo más complicado.

 

Funciones Random: Cuestión de azar

 

Los números aleatorios siempre son bastante necesarios. Pueden tener 12347907982 utilidades, desde tirar un dado a manejar una IA.

Si queremos obtener un número aleatorio, una línea nos lo hace todo:

PA_Rand();  //Con esto, obtenemos un número aleatorio. Para usarlo, almacenadlo en una variable.

Vale, una línea sencilla, pero con pros y contras.

Pros:

  • Obtenemos un número aleatorio.

Contras:

  • El número siempre es positivo (u32).
  • El número es ENORME.
  • Siempre devuelve el mismo número.

¿Cómo evitar estos contras? La solución es fácil, pero requiere aprender más código.

PA_RandMin(mínimo);   //Limita el número obtenido. Con esta función obtendrás un
//número mayor o igual que mínimo.
 
PA_RandMax(máximo);   //Limita el número obtenido. Con esta función el número estará
//entre 0 y el máximo dado.
 
PA_RandMinMax(mínimo,máximo);   //Limita totalmente el número. Este estará entre
//el mínimo y el máximo dado, ambos incluidos.
 
PA_InitRand();   //Inicia el sistema Random. El número que se obtenga con las funciones
//anteriores se basará en la fecha y hora de la DS.

Las tres primeras no creo que haga falta explicarlas, se entienden bien. Sin embargo, en la última, he de indicar varias cosas. La primera y más importante, es que no existe aparato tecnológico capaz de generar un número completamente aleatorio. Únicamente sistemas matemáticos para "simular" que el número obtenido es aleatorio. Para esto, en las ecuaciones se incluyen elementos totalmente variables. ¿Qué elemento más variable existe que la fecha y la hora? Usando estas funciones, el número que obtengamos con cualquiera de las funciones anteriores será siempre totalmente aleatorio. Bueno, para obtener exactamente el mismo número debereis ejecutar el código exactamente el mismo día a la misma hora. Algo no muy probable, ¿no? ;)

Antes de que me vengan dudas, indico que esta función se tiene que almacenar en una variable (cosa lógica porque genera un número). Así:

u32 variable=PA_Rand();

IMPORTANTE: ¡Ojo con los emuladores! Cuando ejecutes un emulador (iDeaS, DeSMuMe...), éste tendrá por defecto la fecha a 1 de enero a las 00:00, por lo que el número aleatorio que devuelva será el mismo si el aleatorio se genera al iniciar el programa. Así que mejor probad en la DS los números aleatorios, porque los emuladores no servirán de mucho...

 

Punto fijo: Exactitud sin comas

 

Para todo aquel adentrado y bien adentrado en este mundillo, sabrá que las variables float y double no son las más digeribles para cálculos en la DS (bueno, para la DS o para cualquier aparato tecnológico). Si no, pues ya lo he dicho yo xD.

¿Qué supone esto? Que si queremos hacer desplazamientos lentos, a menos de 1 píxel/frame, pues a narices que necesitaremos usar decimales, osease floats. Con un sprite/fondo, no hay mucho problema, pero si son muchos será un suplicio para el sistema, si además tiene que estar realizando otros cálculos a la par. Resulta evidente la necesidad de buscar alternativas.

Para este método, tremendísimamente efectivo, nos basaremos en una verdad:

Un número entero será siempre infinitamente más rápido de operar que un decimal

Y aquí entra en juego el punto fijo. Para la explicación, compararemos nuestro sistema matemático humano (la numeración decimal, usando 10 caracteres, de 0 a 9), con el sistema matemático del byte* (del byte, no del bit).

*NOTA: Este término es de mi invención, para que comprendáis el por qué de sus valores.

¿Cuál es el sistema del byte? Pues que el número 1 en el sistema decimal equivale a 256 en el del byte, valor máximo alcanzado por un byte u8.

Dejémonos de tecnicismos, y comencemos con las comparaciones. Centrémonos en esta realidad: 1=256 (explicado en el párrafo anterior). De aquí sacamos que:

1+1=256+256  ->  1*2=2*256  ->  2=512
 
1+1+1=256+256+256  ->  1*3=3*256  ->  3=768
 
1+1+1+...=256+256+256+...  ->  1*n=n*256

Traduciendo, si queremos un número n, multiplicamos éste por 256. Sencillito, ¿no? Ahora si os digo que, matemáticamente hablando, el comando <<8 equivale a multiplicar por 256, el código se vuelve aún más simple, es cambiar *256 por <<8.

Pero claro, nuestra intención era conseguir números menores de 1 sin usar decimales. Y multiplicando por 256, decimales no conseguimos. Peeeroooo...

1*0.5=0.5*256  ->  0.5=128

Con esto ya tenemos un número menor de 1, ¡y sin decimales!

Pero claro, esto es una "transformación", una conversión. Aquí usamos euros, lo pasamos a dólares, pero a la hora de gastarlo, tendremos que volverlo a pasar a euros. Pero hemos evitado realizar cálculos con el euro, que era lo que nos "ralentizaba".

En resumen:

Para pasar a punto fijo:
 
256*n=n<<8
 
Para usar el valor en funciones:
 
n>>8=n/256

Y por último, una comparación de un código con ambos métodos, el decimal y el del byte.

float variable_decimal;   //Como siempre,
s32 variable_byte;   //declaramos variables.
 
variable_decimal+=0.25;
variable_byte+=0.25<<8;
 
//NOTA: 0.25<<8=0.25*256=64
 
PA_SetSpriteAnimX(0,0,variable_decimal);
PA_SetSpriteAnimX(0,1,variable_byte>>8);
 
//NOTA: variable_byte>>8=variable_byte/256

 

Pros:

  • Con punto fijo, fuera decimales, fuera floats, bienvenida rapidez de cálculo.
  • Una vez te acostumbras, manejar punto fijo se vuelve tan intuitivo como la numeración decimal de toda la vida.
  • En programas de gran número (con gran digo GRAN) de cálculos paralelos, el punto fijo los hará más asequibles para el procesador.
  • En juegos, permite movimiento fluido de sprites y fondos sin sobrecargar el procesador para ello.

Contras:

  • Bueno vale, quizás es hasta ahora lo más difícil de los tutoriales, os puede llevar tiempo comprender y dominar el punto fijo, pero si a estas alturas seguís creyendo que programar es PA_QuieroUnJuegoPokemon(2players); (frase de KnightFox), os recomiendo dar la vuelta porque aquí perdeis el tiempo.
  • Si esto os parece difícil, os recuerdo que más abajo del tutorial tenemos gravedad y colisiones (risa maligna).

 

Seno y coseno: Y la tierra era redonda

 

Vale, tenemos un control del sprite completo para arriba, abajo, derecha e izquierda, y una mezcla de ambas. Para la mayoría de juegos, es más que suficiente. Pero, en la vida real, no nos movemos sólo en cuatro direcciones, no miramos sólo en cuatro direcciones. Podemos movernos y mirar hacia cualquier dirección, concretamente 360º (vamos a despreciar la tercera dimensión de nuestro mundo, la altura).

Bueno, pues los sprites no van a quedarse cortos. ¿Qué utilidad tiene esto? Pues podéis hacer una ruleta, podéis hacer un shooter (arriba camina, y con izquierda y derecha va girando), podéis hacer una rotonda en un juego de coches... Aunque no lo parezca, los ángulos los necesitaremos en más de una situación. Pero claro, calcular el ángulo no es fácil. ¿Seguro? Aquí entra la magia de la trigonometría.

Bueeno, no voy a dar por supuesto que habéis dado esto en Matemáticas, pero tampoco os voy a explicar toda la trigonometría :P. La teoría que voy a dar sobre esto va a ser muuy básica, lo justo para entender este apartado. No esperéis nada más, pues la trigonometría dara para mucho más.

PD: Es muy recomendable que le echéis un vistazo al ejemplo /palib/examples/Math/AngleSinCos, compilándolo, para que tengais clara la idea del resultado.

 

Breve explicación: Seno y Coseno en la Círcunferencia Goniométrica

Cualquier ángulo de cualquier tamaño puede ser dibujado en una círcunferencia, tal que el centro de ésta sea el vértice de dicho ángulo, y éste forme un triángulo rectángulo. Basándonos en esta definición, observemos el siguiente dibujo:

Antes de nada, y como abréis notado ya, en la DS no se usan grados para medir los ángulos. Se miden en píxels (como casi todo en la DS, vaya), y la equivalencia es la siguiente:

90º=128 píxels

Por tanto, si el lado derecho de la pantalla es el ángulo 0º (o 0 píxels), 90º son 128 píxels, 180º son 256, y 270º son 384. Tened en cuenta que un ángulo de 360º (512 píxels), vendría a ser un ángulo 0.

Aparte de esto, por norma general, el centro de la circunferencia se considerará siempre la posición inicial del sprite.

Teniendo en cuenta estos detalles, pasemos al intríngulis de la cuestión.

 

Seno y Coseno

Esto es incluso más simple que el punto fijo :P. Pero había que tener claros los conceptos que aclaré en la circunferencia goniométrica.

¿Qué es el Seno y el Coseno? Pues una relación entre dos coordenadas, concretamente, y como ya se dijo, entre la posición inicial del sprite y la final. Imaginemos un ángulo de 45º (64 píxels), que vendría a ser la diagonal. Pues si cogemos esa diagonal, podemos dividirlo en un movimiento horizontal, y otro vertical. Una L, vamos. Pues bien, el Seno y el Coseno vendría a ser la velocidad, tanto X como Y, con la que el sprite se aleja/acerca respecto a su posición inicial. Por tanto, como cualquier velocidad, hay que sumarla a la posición para que el sprite se mueva, sabiendo que:

  • El Seno es la velocidad horizontal, o sea, el eje X.
  • El Coseno es la velocidad vertical, o sea, el eje Y.

Y ahora os estaréis diciendo "No me fastidies, ¿tengo que memorizarme el Seno y Coseno de cada grado que quiera usar?" Y yo os respondo: "¡Tranquilos, aquí llega PAlib al rescate!". Con estas dos funciones, ya tenemos la vida resuelta:

PA_Sin(ángulo);   //Esta función devuelve el Seno del ángulo que le demos, entre -255 y 255 (un s16)
 
PA_Cos(ángulo);   //Esta función devuelve el Coseno del ángulo indicado, un valor entre -255 y 255. (un s16

La cuestión del ángulo, se puede conseguir de mil maneras. Recomiendo usar Pad.Held Left y Right para modificar el ángulo.

Un último detalle: El motivo de que el seno y el coseno sean valores comprendidos entre +/- 256, es por el hecho de que está preparado para usar en punto fijo. No os olvidéis de luego mover el byte a la derecha (>>8), para que el movimiento sea correcto.

 

Gravedad: Para caer de morros

 

Jeje, ahora sí que "sus vía crujir vivos" (Gracias José Mota :) ).

Definimos gravedad como la fuerza con la que un cuerpo con masa atrae y es atraído por otro(s). Coloquialmente hablando, la gravedad es la rapidez con la que caemos al suelo.

De ese concepto va todo este apartado: todo lo que sube, baja. Organicemos nuestras ideas antes de nada.

ATENCIÓN, estamos considerando el caso de que queráis una gravedad similar a la de la Tierra. Estas ideas pueden variar un poco o mucho de las que tengáis en mente para vuestros proyectos (podéis hacer que el sprite caiga leeeentamente, o que haga un supersalto de 5 pisos).

  • Cuanta más masa tenga el cuerpo atraído, con más fuerza (velocidad) caerá al suelo (lo conocido como peso).
  • Todo objeto es atraído en la misma dirección. Por lo general, hacia abajo (podéis hacer que caiga en otras direcciones, como hacia la derecha, por ejemplo).

¿Listos? Vamos a adaptar estas ideas a la simulación en la DS.

Como es la idea más usada y extendida, vamos a tratar la gravedad hacia abajo. Considero que tenéis suficiente cabeza para saber qué hay que modificar en el código para que caiga hacia otra dirección.

Vayamos por partes: una aceleración hacia abajo (eje Y, número positivo), modifica una velocidad Y, aumentándola a cada frame. La velocidad de cada frame, modifica la posición Y de un sprite. Cuando un sprite está en una determinada coordenada Y, la velocidad es 0 (nula), lo que vendría a ser el suelo.

¿Lo tenéis claro? Recordad que si no entendéis, siempre podéis indicarlo para que modifique mi explicación ;).

Muy bien, crearemos nuestras variables:

s32 spritey;   //Con esta variable controlaremos la posición y del sprite
s32 vel_y;   //Con esta variable le daremos velocidad y al sprite
u16 despegue=1000;   //La velocidad inicial del salto (el impulso que cogemos al saltar)
u8 gravedad=32;   //Esta constante será la que arrastre hacia abajo el sprite.
#define FLOOR (190-32)<<8   //Definimos el suelo (el punto y donde el sprite dejaría de caer abajo)

 

Pequeño paréntesis: los defines

Si os fijasteis, en el anterior código hago uso de un define. Pero, ¿qué es un define? Pues viene a ser una orden dada al compilador, para que nos sustituya una cadena de caracteres por otros caracteres o valores que establezcamos previamente.

Los define por lo general se ponen junto a los include, pero se pueden localizar en cualquier parte del código. Recomiendo ponerlos junto a los include mientras no se dominen.

#define FLOOR 150<<8

Después de esta línea, cada vez que en el código se encuentre la secuencia de caracteres FLOOR, se sustituirá por 150<<8 (está en punto fijo, iros acostumbrando a esto). Imaginemos que, por casualidades del destino, tenemos esto en el código:

(Es una situación ficticia, es para que os hagáis una idea)

if(Pad.Newpress.A)
{
     PA_SetSpriteY(0,0,FLOOR);
}
 
AFLOORAMIENTO;

Si se sustituye FLOOR por 150<<8, en realidad tenemos esto:

if(Pad.Newpress.A)
{
     PA_SetSpriteY(0,0,150<<8);
}
 
A150<<8AMIENTO;

En el primer caso, usamos correctamente el objetivo del define, que es usar palabras en lugar de números, así no tendremos que memorizar el número al que queremos poner el suelo, y no reservamos espacio para variables.

En el segundo caso, que sería un mal uso, aunque pocas veces os pasará. Si el compilador se encuentra la secuencia de caracteres por algún lado, sustituirá por lo indicado sin excepción. Es por ello que lo recomendable es usar caracteres en mayúsculas, y que estéis seguros de que no sustituirá en lugares no deseados.

 

De nuevo al grano

Bien, tenemos el concepto de gravedad, y tenemos las variables. Ahora vamos a programar.

/*Aquí comienza todo. Con esta acción, al pulsar A, el sprite saltará, siempre que
éste se encuentre en el suelo que definimos antes.*/
if((spritey>=FLOOR) && Pad.Newpress.A)
{
    vel_y=-despegue;  //La velocidad comienza. Es negativa, para que el sprite vaya para arriba.
}
 
/*Estos cálculos se realizarán siempre. Porque la gravedad nunca deja de actuar, ¿no?*/
vel_y+=gravedad;   //La velocidad aumenta con la gravedad.
spritey+=vel_y;   //La posición del sprite cambia dependiendo de la velocidad.
 
/*Y, por último, mientras el sprite está en el suelo, no se mueve*/
if(spritey>=FLOOR)
{
    vel_y=0;   //Velocidad nula, para que el sprite no se mueva.
    spritey=FLOOR;   //La posición y es el suelo, para cuando aterrice el sprite no se nos "entierre"
}
 
PA_SetSpriteY(1,0,spritey>>8);   //Evidentemente, la posición del sprite debe actualizarse :P.

Y aquí, chicos, llega el quiz de todo este apartado. Habíamos visto unas ideas previas, si mal no recordáis:

"Cuanta más masa tenga el cuerpo atraído, con más fuerza (velocidad) caerá al suelo (lo conocido como peso)."

Por tanto, cuanto más pese el sprite (supuestamente), más le costará despegar del suelo. Por tanto, se le dará un despegue menor. Resultado: el sprite se levanta menos del suelo.

"Todo objeto es atraído en la misma dirección. Por lo general, hacia abajo (podéis hacer que caiga en otras direcciones, como hacia la derecha, por ejemplo)"

Cambiar la dirección del suelo es fácil. Simplemente es cambiar el signo de la gravedad y del despegue para que caiga hacia arriba, cambiar la dirección de Y a X para la derecha, y cambiar la dirección de Y a X y el signo para la izquierda. Todo con el suelo debidamente indicado, claro.

NOTA FINAL: Tanto el despegue como la gravedad son valores orientativos, yo les pongo 1000 y 32 porque son los valores más cómodos para mí. Os recomiendo que vayais al ejemplo de gravedad de PAlib (/devkitpro/palib/examples/Math/Gravity), compileis el ejemplo, abrais el .nds y juguéis con las variables despegue y gravedad hasta dar con los números que más os atraigan.

 

Colisiones: La opción más chocante

 

Y con este último apartado aprenderemos a interactuar entre sprites. Venga, no neguéis que estábais deseando llegar a esta parte xD.

Por existir, existen diversos métodos de detectar colisiones. Por el momento, aprenderemos dos clases: colisión circular y colisión por cajas.

 

Colisión Circular

El porqué de este nombre viene del hecho de considerar los sprites como circunferencias. Técnicamente este tipo de colisión se puede usar para colisiones entre dos sprites cuadrados (8x8, 16x16, 32x32 y 64x64), aunque yo lo recomiendo sólo para sprites de pelotas, balones, etc., más que nada porque con otros tipos no les suele quedar muy bien el efecto.

El sistema es simple: para que dos circunferencias choquen, los centros tienen que estar a menos distancia que la suma de sus radios. Para ello, lo recomendable es tener una variable que indique el centro del sprite (sería la posición del sprite + la mitad de su tamaño).

Muy bien, pasemos al código:

if(((centrox1-centrox2)<(radio1+radio2)   //Si la diferencia de posiciones X es menor a la suma de radios (por la derecha),
  || (centrox1-centrox2)>(radio1+radio2))   //O menor por la izquierda
  && ((centroy1-centroy2)<(radio1+radio2)   //Y la diferencia de posiciones Y es menor a la suma de radios (por arriba)
  || (centroy1-centroy2)>(radio1+radio2)))   //O menor por abajo
 
  //Hay colisión
 
else //No hay

Este código es simple, y creo que se entiende. Los valores de los radios y centros varían dependiendo del tamaño del sprite, pero por las dudas os las indico aquí:

//Sprite 8x8
centrox=posicionx+3;
centroy=posiciony+3;
radio=8;
 
//Sprite 16x16
centrox=posicionx+7;
centroy=posiciony+7;
radio=8;
 
//Sprite 32x32
centrox=posicionx+15;
centroy=posiciony+15;
radio=16;
 
//Sprite 64x64
centrox=posicionx+31;
centroy=posiciony+31;
radio=32;

NOTA: Este método detecta la colisión. La consecuencia de ésta la tendréis que programar vosotros.

 

Colisión por cajas

Este ya es el método general. Como el propio nombre indica, para este tipo de colisión consideraremos los sprites como cajas.

Bueno, este método quizá no sea exacto de narices, pero oye, nos hace un apaño bastante bueno. Y reconocedlo, estabais deseando que tocase explicar esto, pillines xD.

Si entendisteis las colisiones circulares, esto no tiene ningún secreto para vosotros, pues viene a ser lo mismo con la diferencia de que la distancia del centro del sprite al borde no es la misma en todas las direcciones. Y esto nos hace usar una táctica "adaptada".

El sistema es simple, simplemente teneis que conocer la posición de los 4 lados del sprite.

lateralizq=posicionx;   //El lado izquierdo es la posición X 0 del sprite. Por tanto, es equivalente a la posición X de este.
 
lateralder=posicionx+ancho;   //El lado derecho es la posición X del sprite más su ancho.
 
lateralarr=posiciony;   //El lado de arriba es la posición Y 0 del sprite. Por tanto, es equivalente a la posición Y de este.
 
lateralaba=posiciony+alto;   //El lado de abajo es la posición Y del sprite más su alto.

Bien, pasemos al código:

//Código cedido por Cheleon
 
if(((lateralizq1<=lateralder2 && lateralizq1>=lateralizq2)   //Si el lateral izquierdo del sprite 1 está entre
                                                                     //el lateral derecho y el izquierdo de 2
   || (lateralizq1<=lateralder2 && lateralder1>=lateralizq2))   //O el lateral derecho del sprite 1 está entre
                                                                     //el lateral derecho y el izquierdo de 2
   && ((lateralarr1<=lateralaba2 && lateralarr1>=lateralarr2)   //Y el lateral de arriba del sprite 1 está entre
                                                                     //el lateral de arriba y el de abajo de 2
   || (lateralaba1<=lateralaba2 && lateralaba1>=lateralarr2)))   //O el lateral de abajo del sprite 1 está entre
                                                                     //el lateral de arriba y el de abajo de 2
 
//¡Colisión!

Si sustituímos en la imaginación las variables lateral por los sprites y sus lados, y los chocamos entre ellos, veremos el porqué de todo este rollo (que lo cierto un poquito lo es, pero también es fácil de razonar).

En todo caso, nos puede surgir una duda: ¿Por qué el mismo código para los 4 lados? ¿No bastaría con dar, por ejemplo, arriba y derecha? Error.

La razón es simple, por muy lógico que pueda parecer el "ahorrarnos" calcular dos lados. Imaginad dos cuadrados, uno se acerca a la izquierda del otro. Si no tenemos calculada la colisión para el lado izquierdo, cuando este lado toque el sprite no pasaría nada. Los cuadrados se van solapando, hasta que al final, el lado derecho también toca el otro cuadrado. Y entonces se detecta colisión. ¡Pero realmente no era colisión, realmente los cuadrados ya estaban solapados!

 

Aspectos a tener en cuenta del tutorial:

  • La gravedad es un caso concreto del uso de colisiones.
  • Algunas funciones aquí expuestas, como PA_Sin, PA_Cos y PA_Rand son funciones que devuelven valores. Acordaos siempre de almacenarlas en variables, o usarlas en cálculos.
  • Los sprites se pueden rotar sobre su centro, explicaré cómo hacerlo en próximos tutoriales. Lo digo por el cálculo de ángulos.
  • El punto fijo será seguramente lo que más empleemos de todo el tutorial a lo largo de los tutos, acostumbraos a ver los bitwise shift (>>8 y <<8) por todos lados.
  • Existen mil puntos de vista sobre cómo enfocar matemáticamente las colisiones, cuando domineis el tema sería interesante que os buscaseis el método que os sea más efectivo dependiendo cómo lo queráis usar.
  • Sobre el punto anterior, indicar que el sistema de colisiones explicado es el usado por mí, por lo que a lo mejor no lo veis tan claro como yo. Recordad que el tutorial es por y para vosotros, así que si no entendéis algo no dudeis en preguntar.

 

Y con esto doy por finiquitada la serie de tutoriales de PAlib básico. Esto es lo mínimo que hay que saber para adentrarnos en el mundo de la programación gracias a la ayuda que nos brinda PAlib. Ahora la recopilación seguirá otros derroteros: sabeis lo suficiente para intentar ir un poco más allá. A partir de hoy, dad por inaugurada la serie de tutoriales de PAlib medio.

Un Saludo seguidores de mis tutoriales.

4.431035
Tu voto: Ninguno Votos totales: 4.4 (58 votos)

Anuncios Google

Comentarios

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.

Gracias

Fantasticos tutoriales. Con lo que has expliado me voy a poner a trabajr en un juego. Saludos !!!

Imagen de exterminator

Fe de erratas

He cambiado el código de colisión del tutorial por el aportado de Cheleon, confiamos en que este código sea el definitivo para el tutorial. No obstante recordad que las colisiones son un mundo y el código indicado no deja de ser orientativo.

Salu2


Para recibir ayuda por parte de otros usuarios más rápidamente, recomendamos que pongas títulos descriptivos y no utilices abreviaturas (estilo MSN) en tus post de los foros. Recuerda que accediendo al Manual del perfecto forero y las Normas de la Comunidad aprenderás trucos para resolver tus dudas antes.

Imagen de Cheleon

La colision por cajas esta

La colision por cajas esta mal

tu tienes esto

if(((lateralizq1<=lateralder2 && lateralizq1>=lateralizq2)   //Si el lateral izquierdo del sprite 1 está entre
                                                                     //el lateral derecho y el izquierdo de 2
   || (lateralder1<=lateralder2 && lateralder1>=lateralizq2))   //O el lateral derecho del sprite 1 está entre
                                                                     //el lateral derecho y el izquierdo de 2
   && ((lateralarr1<=lateralaba2 && lateralarr1>=lateralarr2)   //Y el lateral de arriba del sprite 1 está entre
                                                                     //el lateral de arriba y el de abajo de 2
   || (lateralaba1<=lateralaba2 && lateralaba1>=lateralarr2)))   //O el lateral de abajo del sprite 1 está entre
                                                                     //el lateral de arriba y el de abajo de 2
 
//¡Colisión!

y es esto

if(((lateralizq1<=lateralder2 && lateralizq1>=lateralizq2)   //Si el lateral izquierdo del sprite 1 está entre
                                                                     //el lateral derecho y el izquierdo de 2
   || (lateralizq1<=lateralder2 && lateralder1>=lateralizq2))   //O el lateral derecho del sprite 1 está entre
                                                                     //el lateral derecho y el izquierdo de 2
   && ((lateralarr1<=lateralaba2 && lateralarr1>=lateralarr2)   //Y el lateral de arriba del sprite 1 está entre
                                                                     //el lateral de arriba y el de abajo de 2
   || (lateralaba1<=lateralaba2 && lateralaba1>=lateralarr2)))   //O el lateral de abajo del sprite 1 está entre
                                                                     //el lateral de arriba y el de abajo de 2
 
//¡Colisión!


EDITO: Código cedido por cheleon :P ><

Imagen de rickper1

cuando van a empezar los

cuando van a empezar los tutoriales de palib medio?

Imagen de rickper1

Mi sprite no "salta"

pues eso que e colocado todos los códigos, todas las variables bien, y sin embargo mi sprite no se eleva. alguien me puede ayudar?

gracias 

Imagen de Anabol

¿Podrías poner el código? PD:

¿Podrías poner el código?

PD: Tenemos un foro de programación donde puedes preguntar también estas dudas.

Salu2TS!

Imagen de rickper1

Este es el código

// Includes

#include <PA9.h>       // Include for PA_Lib

#include "gfx/all_gfx.c"

#include "gfx/all_gfx.h"

#define FLOOR (190-32)<<8

s32 x;

s32 spritey;

s32 vel_y;

u16 despegue=1000;

u8 gravedad=32;

 

 

 

// Function: main()

int main(void)

{

PA_Init();    // Initializes PA_Lib

PA_InitVBL(); // Initializes a standard VBL

 

   PA_LoadSpritePal(0,1,(void*)ash_Pal);

   PA_CreateSprite(0,1,(void*)sprite2_Sprite, OBJ_SIZE_64X64,1,1,10,100);

   PA_SetSpriteHflip(0,1,1);

 

// Infinite loop to keep the program running

while (1)

{

  if((spritey>=FLOOR) && Pad.Newpress.A)

  {

     vel_y=-despegue;

  }

if(spritey>=FLOOR)

{

  vel_y=0;

spritey=FLOOR;

}

 

  

  

 

  if(Pad.Newpress.Left)

{

  PA_SetSpriteHflip(0,1,0);

  PA_StartSpriteAnim(0,1,1,6,6);

}

      if(Pad.Released.Left)

{

  PA_StopSpriteAnim(0,1);

  PA_SetSpriteAnim(0,1,0);

  

}   

if(Pad.Newpress.Right)

{

  PA_SetSpriteHflip(0,1,1);

  PA_StartSpriteAnim(0,1,1,6,6);

}

if(Pad.Released.Right)

{

  PA_StopSpriteAnim(0,1);

  PA_SetSpriteAnim(0,1,0);

   }   

 

x+=Pad.Held.Right-Pad.Held.Left;

PA_SetSpriteX(0,1,x);  

vel_y+=gravedad;

spritey+=vel_y;

PA_SetSpriteY(1,0,spritey>>8);   

 

 

  

  PA_WaitForVBL();

}

return 0;

} // End of main()


Imagen de exterminator

Coloca

las líneas

vel_y+=gravedad;
sprite_y+=vel_y;

entre el despegue y la detección del suelo. Así:

if(sprite_y>=FLOOR && Pad.Newpress.A)
{
    vel_y=-despegue;
}
 
vel_y+=gravedad;
sprite_y+=vel_y;
 
if(sprite_y>=FLOOR)
{
   vel_y=0;
   sprite_y=FLOOR;
}
 
PA_SetSpriteXY(...);

 

¿Por qué esto debe ser así? Pues por el sencillo motivo, de que no va a saltar nunca. Cuando pulsas A, y el sprite está en FLOOR o por debajo, vel_y adquiere un valor. Si se sigue tu código, la NDS interpreta lo siguiente: el if(sprite_y>=FLOOR). En ese momento, recuerda, sprite_y sigue valiendo más o igual a FLOOR. Se cumple la sentencia, por tanto, entra al bucle. Y ahí la hemos liado, porque en ese bucle nos encontramos con la línea vel_y=0. Conclusión, el sprite no se mueve.

Ahora veamos el código puesto por mí. Cambiamos de sitio las líneas indicadas al principio del comentario. Se pulsa A, sprite_y es igual a FLOOR (cosa que nos garantiza if(sprite_y>=FLOOR) sprite_y=FLOOR;), y vel_y=-despegue. Después, entramos en las siguientes líneas: a vel_y se le resta la gravedad, y luego, se suma al sprite_y. Como es un número negativo, sprite_y decrece, y, por tanto, ya no es igual a FLOOR. Por tanto, no se entra en el bucle if(sprite_y>=FLOOR) hasta que se cumpla, y esta situación no se dará hasta la caída del salto.

Ten bastante cuidado con la sintaxis que empleas en tus códigos; como puedes ver, cambiar de sitio un par de operaciones la cosa cambia.

Salu2


Para recibir ayuda por parte de otros usuarios más rápidamente, recomendamos que pongas títulos descriptivos y no utilices abreviaturas (estilo MSN) en tus post de los foros. Recuerda que accediendo al Manual del perfecto forero y las Normas de la Comunidad aprenderás trucos para resolver tus dudas antes.

Imagen de exterminator

Corregido

el código de colisiones por cajas, debido a un despiste mío con un conector && no funcionaba. Gracias a Draco el dragon por indicarme el fallo y ayudarme a corregirlo.

Salu2


Para recibir ayuda por parte de otros usuarios más rápidamente, recomendamos que pongas títulos descriptivos y no utilices abreviaturas (estilo MSN) en tus post de los foros. Recuerda que accediendo al Manual del perfecto forero y las Normas de la Comunidad aprenderás trucos para resolver tus dudas antes.

Imagen de 123456abcdef

Cual es el código de si yo

Cual es el código de si yo toco un sprite con el stylus sucede una condicion. Por ejemplo:

While(1)

{

   if(código de si el sprite es tocado)

      {

      PA_InitText(1,2);

      PA_OutputSimpleText(1,5,5,"sprite tocado");

      }

   PA_WaitForVBL();

}


A que os mola la firma!!!

Imagen de The Dark Master

Una cosa

El PA_InitText procura dejarlo siempre fuera del while, la accion que necesitas es PA_SpriteTouched(numero_Sprite), en if quedaria:

if(PA_SpriteTouched(numero sprite)){

Salu2


Imagen de 123456abcdef

Pongo: if(PA_SpriteTouched(0_

Pongo:

if(PA_SpriteTouched(0_sprite))
     {
      PA_InitText(1,2);
      PA_InitText(0,2);
      PA_OutputSimpleText(1,15,5,"sprite tocado");
      PA_OutputSimpleText(0,3,20,"B para volver");
     }

Y me da este error:

error: invalid suffix "_sprite" on integer constant

 


A que os mola la firma!!!

Imagen de exterminator

Borra

el "_sprite" que sobra. El número que hay que poner es el que le asignas tú al sprite al crearlo: PA_CreateSprite(pantalla,nºsprite,(void*)nombresprite_Sprite,OBJ_SIZE_tamXxtamY,nºcolor,nºpaleta,posiciónx,posicióny);

Salu2


Para recibir ayuda por parte de otros usuarios más rápidamente, recomendamos que pongas títulos descriptivos y no utilices abreviaturas (estilo MSN) en tus post de los foros. Recuerda que accediendo al Manual del perfecto forero y las Normas de la Comunidad aprenderás trucos para resolver tus dudas antes.

Imagen de exterminator

Desde aquí

pido disculpas a todos los users por la tardanza al realizar este tutorial (una friolera de 3 meses), espero que sea de vuestro agrado, y dad por hecho que habrá más y mejor.

Salu2


Para recibir ayuda por parte de otros usuarios más rápidamente, recomendamos que pongas títulos descriptivos y no utilices abreviaturas (estilo MSN) en tus post de los foros. Recuerda que accediendo al Manual del perfecto forero y las Normas de la Comunidad aprenderás trucos para resolver tus dudas antes.

Imagen de magicblack2009

Muchas gracias por este gran tuto

Disculpas??

Si, encima de que los haces, te van a exigir cuando hacerlos.

Muy buen tuto, muchas gracias por hacerlo :)

Seguro que me sirve en poco tiempo :) :)

 


¿Quieres estar totalmente informado sobre el universo 3DS? Visita Magic3DS.

También puedes estar al tanto de toda la actualidad de 3DS en Twitter: @Magic3DS

Imagen de Anabol

Jaja no importa se perdona

Jaja no importa se perdona después de ver el trabajo que has echo Enorawena. Esto va a servir a más de uno #include <YO> Salu2TS y gran trabajo.

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.