Registrarse

[pokeruby] Dibujar Sprites en la OAM

Kaktus

Miembro insignia
Miembro insignia
¡Bueeenaass!

Hoy os traigo un tutorial de los intensos, de los interesantes, de esos que sé que vais a utilizar casi seguro, porque vamos a conseguir dibujar sprites en absolutamente cualquier momento que nos propongamos, aunque con ciertos límites, pues a no ser que estemos cargando un escenario nuevo el cuál no use ni los minis ni sus paletas típicas etc, como bien digo, estaremos algo limitados y seremos propensos a tener bugs gráficos, y ojo, digo gráficos, porque en principio (a no ser que la liéis mucho) no afectará a la jugabilidad.

Aquí os muestro un ejemplo de lo que se puede hacer con este tutorial, un menú que hice usando regiones, cargando sprites y animándolos con dos métodos distintos (aunque esos dos tutos de animaciones tocan los próximos días)


Bien, una vez os he dejado aquí con todo el hype y con las ganas de aprender a usar esto, debéis saber una cosa, por increíble que parezca, yo tan sólo he usado una paleta, y aún así, he tenido algún error que otro, así que cuidado cargando sprites en el overworld.

Bien, nos dejamos de rollos y vamos con los requisitos, por cierto, si alguien tiene la duda de que es la OAM, aquí tenéis un post de cosarara en el que se abarcan bastantes cosas sobre ella.

Por cierto, antes de empezar me gustaría agradecer a Karakara, un usuario de un foro, que fue el que descubrió cuáles eran todos los parámetros que necesitaba un sprite para poder ser dibujado correctamente en la OAM, por tanto, créditos a él y un gran agradecimiento, si no fuera por él, probablemente no hubiera sido tan fácil conseguirlo, a pesar de ello, no tenía la investigación muy avanzada y la mayoría de los parámetros no estaban muy claros, pero por mi parte, tras haber creado este menú, entiendo CASI a la perfección que poner en cada uno de ellos, así que, empezamos.

Requisitios

  • Tener ganas y no frustrarse a la primera
  • Tener previamente instalado pokeruby (obviamente)
  • Saber pasar archivos de .png a .gbapal y .4bpp, aquí el tutorial que creé sobre como hacerlo
  • Nociones básicas de programación para no perdernos por el camino
  • Character Maker Pro o cualquier otro programa que nos sirva para indexar, pero yo recomiendo este

He de decir que daré por hechas bastantes cosas, ya que no quiero hacer un tutorial demasiado extenso, pero sí que entraré a fondo en algunos temas que puedan causar confusión, aún así, si nuestro compilador nos tira un error, intentad leerlo e interpretarlo, que así es como se aprende.

¡Empezamos!

0.- Lo primero que necesitaremos, es tener nuestro archivo que indexaremos y guardaremos en .png, debéis pensar que la paleta debe estar ya acomodada tal y como la queremos, y que el color transparente deberá estar en primer lugar, tras tener todo listo e indexado, seguiremos el tutorial de gbagfx que os he dejado más arriba, para conseguir su archivo .gbapal y .4bpp (o .8bpp, etc, el que le corresponda, por lo general, .4bpp)

Aclarar que si estamos usando la misma paleta para varios iconos, o sprites, no tenéis que crear la paleta para cada uno de ellos, con crearla una vez y usarla para todos, sobra, obviamente.

1.- Ahora buscaremos el archivo .c en el que queremos que se cargue nuestro querido sprite y lo abrimos, o en caso de que queramos que sea un nuevo archivo, pues lo creamos, aunque si no sabéis como crear nuevos archivos .c, no lo hagáis.

2.- Por aquí os voy a ir dejando todo el código que un sprite siempre va a necesitar para que se pueda cargar, y voy explicándolos uno a uno.

Código:
static const u16 nombrePaleta[] = INCBIN_U16("graphics/menu_icons/iconPal.gbapal");
static const u8 nombreSprite[] = INCBIN_U8("graphics/menu_icons/dex.4bpp");
(A partir de aquí, disculpad si no soy muy técnico, no estoy acostumbrado al entorno de C)

Con estos códigos estamos guardando la paleta y el sprite respectivamente en unas variables, y es muy importante, que tan sólo cambiéis donde dice "nombrePaleta" y "nombreSprite" por el que queráis y la ruta donde se encuentran. Decir que se pueden crear nuevas carpetas en graphics si lo deseáis, en este caso, la carpeta es una que creé yo para el menú.

También es muy importante que carguéis primero las paletas y luego los sprites.

3.-

Código:
static EWRAM_DATA u8 iconNameEWRAM = 0;
Con entender que este código servirá más tarde para poder eliminar/destruir los sprites que creemos es suficiente.

4.-

Código:
static const struct OamData spriteNameOamData =
{
    .y = 0, //Esto no hace lo que parece, no es necesario cambiarlo
    .affineMode = 0, //Esto es el modo afín, lo usaremos para las animaciones de tipo afín, pero de momento, nos olvidamos y lo dejamos en 0
    .objMode = 0, //Lo dejamos en 0
    .mosaic = 0, //Lo dejamos en 0
    .bpp = 0, //Lo dejamos en 0
    .shape = 0, //Hace que el sprite sea un: cuadrado = 0, rectángulo horizontal = 1, rectángulo vertical = 2
    .x = 0, //Esto no hace lo que parece, no es necesario cambiarlo
    .matrixNum = 0, //Lo dejamos en 0
    .size = 1, //Esto indica el tamaño de nuestro sprite, más abajo está la tabla
    .tileNum = 0, //Lo dejamos en 0
    .priority = 0, //Esto es la prioridad, a menor número, mayor prioridad, por tanto, para tener la máxima prioridad, lo dejamos en 0
    .paletteNum = 0, //Esto no hace lo que parece, no es necesario cambiarlo
    .affineParam = 0, //Esto lo dejaremos siempre en 0
};
Código:
8x8 = 0
16x16 = 1
32x32 = 2
64x64 = 3

Esta es la tabla de parámetros para la OAM que usaremos más adelante, id apuntando los nombres que le dais a cada cosa porque luego nos harán falta. Simplemente la editáis respecto a vuestras necesidades y listo.

He de decir, que si tenéis dos sprites con las mismas características, no creéis dos iguales, podéis usar el mismo para ambos (en este caso)

5.-

Código:
static const union AnimCmd nameSpriteAnimSeq0[] =
{
    ANIMCMD_FRAME(0, 5),
    ANIMCMD_END,
};

static const union AnimCmd *const nameSpriteAnimTable[] =
{
    nameSpriteAnimSeq0,
};
Estos simplemente son necesarios, usemos o no usemos animaciones, declaradlos y listo (y fijaos, que en la segunda función, se llama a la primera, por tanto, los nombres en verde oscuro deben ser los mismos)

Y ya tenemos todo lo que necesitábamos creando, por tanto, ahora tenemos que repetir este código con cada sprite que queramos cargar, y es totalmente obligatorio darle un nombre diferente a cada estructura.



Código:
struct SpriteSheet spriteSheet =
{
    .data = nombreSprite,
    .size = 512,
    .tag = 12, //Este es lugar en que el sistema dibujará el sprite en la OAM
};
struct SpritePalette spritePalette =
{
    .data = nombrePaleta,
    .tag = 12, //Este es lugar en que el sistema dibujará el sprite en la OAM
};
struct SpriteTemplate spriteTemplate =
{
    .tileTag = 12, //Este es lugar en que el sistema dibujará el sprite en la OAM
    .paletteTag = 12, //Este es lugar en que el sistema dibujará el sprite en la OAM
    .oam = &spriteNameOamData,
    .anims = nameSpriteAnimTable,
    .images = NULL, //Lo dejaremos siempre como NULL
    .affineAnims = gDummySpriteAffineAnimTable, //Esto es para las animaciones afines, de nuevo, si no las usamos, dejaremos esto por defecto
    .callback = SpriteCallbackDummy, //Esto es para las animaciones, si no las usaremos, también se quedará así
};
ATENCIÓN.- Es muy importante crear cada estructura de forma manual, y repito, no crear dos estructuras con el mismo nombre, además, no deberíais crear una función constructora para pasar parámetros, puesto que esto me dio ciertos problemas, ya que "al salir de la función los datos se borran", y tiende a dar error visuales.

Como podéis comprobar, esta es la parte más importante, y donde se acumula todo aquello que hemos hecho con anterioridad.

Y como también podréis observar, os he señalado con negrita y colores los parámetros a los que debéis estar atentos, en este caso los colores naranjas corresponde a nombres que hemos declarado fuera o con anterioridad, y los azules son los que hemos declarado en el propio código. Sé que puede parecer algo exagerado ponerlo por colores, pero seguro que así lo veis más sencillo.

Debéis tener en cuenta que todos los parámetros que lleven "tag" como nombre, o como parte del nombre, no deben repetirse, me explico:

Por ejemplo, podemos crear la estructura de "SpriteSheet", "SpritePalette" y "SpriteTemplate" y usar en todas el número 12 como tag, pero teniendo en cuenta que estas estructuras van a corresponder al mismo Pokémon. Sin embargo, si creamos otras tres nuevas estructuras para otro icono, no debemos usar de nuevo el 12, si no cualquier otro número que no esté ocupado.

Y ahora, lo que todos estábamos esperando, cargar el sprite en pantalla, que lo haremos con esta serie de comandos:

Código:
LoadSpriteSheet(&spriteSheet);
LoadSpritePalette(&spritePalette);
iconNameEWRAM = CreateSprite(&spriteTemplate, x, y, 0);
En x e y, obviamente debéis colocar vuestras coordenadas, considerando que el punto (0,0) se encuentra en la esquina superior izquierda de la pantalla.

Es muy importante hacerlo en este orden, pues primer cargamos el SpriteSheet, luego el SpritePalette, y luego el SpriteTemplate

Por último, para destruirlo sería tan simple como:

Código:
DestroySpriteAndFreeResources(&gSprites[iconNameEWRAM]);

Y eso es todo, cualquier duda la podéis dejar por los comentarios, decidme que os ha parecido el post y un +Gracias se agradece :p

¡Chau!
 
Última edición:

kakarotto

Leyenda de WaH
El tutorial está espectacular, quizá nadie haya comentado por la complejidad que supone o por tanto código expuesto. Mi consejo es que lo dejes un poco más limpio con spoilers y más imagenes de ejemplos, aunque eso lo dejo a tu elección.
 

KleinStudio

Un plato es un plato
Miembro del equipo
Webmaster
Esto fue lo primero que estuve toqueteando de pokeruby y es que es una pasada.
Espero que el tutorial sirva para que más gente se anime y pruebe a hacer sus propias interfaces y demás, como dice kakarotto quizás se podría dejar más limpio pero en general está muy bien explicado ;)
 

CompuMax

Discord: CompuMax#0425
Miembro insignia
Pedazo de tutorial que hiciste para cargar objetos en la OAM. Te ha quedado excelente y pues hay unas cositas que das por ciertas y no son así. Ya paso a explicarte algunas:

Código:
static const struct OamData spriteNameOamData =
{
    .y = 0, //Si es la coordenada Y del objeto, sólo que después lo modificas con la función DrawSprite
    .affineMode = 0, //Esto es el modo afín, lo usaremos para las animaciones de tipo afín, pero de momento, nos olvidamos y lo dejamos en 0
    .objMode = 0, //0=Normal, 1=Semi-Transparente, 2=OBJ Window
    .mosaic = 0, //Activa la función Mosaico en el objeto, 0 la deja desactivada
    .bpp = 0, //Profundidad de bit por pixel 0 = 4bpp (16 colores), 1 = 8bpp (256 colores)
    .shape = 0, //Hace que el sprite sea un: cuadrado = 0, rectángulo horizontal = 1, rectángulo vertical = 2
    .x = 0, //Al igual que .y ésta es la coordenada X solo que después le pasas nuevos parámetros
    .matrixNum = 0, //Lo dejamos en 0
    .size = 1, //Esto indica el tamaño de nuestro sprite, más abajo está la tabla
    .tileNum = 0, //Lo dejamos en 0
    .priority = 0, //Esto es la prioridad, a menor número, mayor prioridad, por tanto, para tener la máxima prioridad, lo dejamos en 0
    .paletteNum = 0, //Esto no hace lo que parece, no es necesario cambiarlo
    .affineParam = 0, //Esto lo dejaremos siempre en 0
};
Al menos esas son las características que conozco. Estoy seguro que las demás tambien tienen su función jejeje

Y finalmente esto:

spriteSize: Este yo diría que es el valor más complicado/extraño, pues no entiendo porque hay que hacer esto, pero tras bastante romperme el coco viendo ejemplos, esto es lo que me funcionó: El valor de spriteSize debe ser la multiplicación de sus medidas partido de dos, por ejemplo, si tenemos un sprite de 32x32, haremos: (32*32)/2, lo que en este caso sería 512. Para las animaciones lo escribiremos de otra forma, pero para los sprites sin animaciones usaremos este método.

Es curioso que no te hayas dado cuenta, pero paso a explicarte a que se debe ese numero ;) El spriteSize o tamaño del sprite es la cantidad de bytes ocupados por el objeto o sprite dichamente una vez descomprimido en la OAM y se calcula de la siguiente manera:

Código:
spriteSize = (Ancho x Alto x Profundidad de bit) / 8
Ok, ¿pero qué es todo eso y a que se debe la fórmula? Sencillo: Debido a que necesitas tener a la mano los datos de la imagen o frame (en caso de ser un sprite animado) es necesario tener un espacio de memoria disponible en la OAM para dichos datos. Supongamos que tenemos un sprite de 32x32 a 16 colores

Ancho = El ancho de la imágen, es decir, 32 pixel
Alto = El alto de la imágen, es decir, 32 pixel
Profundidad de bit = 16 colores, es decir, 4 bits por pixel

Sustituyendo en la fórmula daría 4096 bits (sin dividir entre 8) Y para pasarlo a bytes simplemente dividimos entre 8, ya que un byte son 8 bits. Así el spriteSize nos da 512

Así sabemos cuanta memoria requiere una imágen al ser descomprimida en la OAM o en la VRAM en general
 

Kaktus

Miembro insignia
Miembro insignia
¡Atención!

He actualizado el tutorial, con el paso del tiempo he ido aprendiendo como funciona esto, y a pesar de que cuando lo cree, conseguía cargar sprites de forma satisfactoria, solía tener bastantes problemas gráficos cuando empezabas a cargarlos y eliminarlos entre otras cosas, pero finalmente he dado con la clave de cargar sprites en la OAM, y os he cambiado gran parte del tutorial por el método correcto y sobretodo, más corto y sin constructores (que es precisamente lo que daba problemas)

¡Espero que lo disfrutéis! :D
 

Fran Agustín

Si el sol besa tus ojos, ni cuenta te das.
Miembro insignia
¡Hey! ¡Qué magnífico tutorial!
Como ya te dije ayer, estás muy embalado con el disassembly y eso me encanta. Vas obteniendo resultados espectaculares. ¡Te felicito!

Cargar sprites en la OAM no es la parte más fácil porque implica entender primero cómo funciona la OAM y eso puede llevarte un poquito de tiempo. Luego has tenido que vértelas con el código C, ver qué funciones y qué parámetros usar. ¡Ojo! Que no es tan sencillo como parece. A muchos pueden írsenos las ganas luego de un ratito de andar mirando porque puede ser un pelín tedioso en ocasiones.
Explicas bastante bien aunque creo que quizás deberías dar algunos detalles más, por ejemplo explicar más sobre qué sí hace cada dato de la OAMData. Entiendo que no hace falta preocuparse de algunos campos para lograr el objetivo del tutorial pero nunca está de más dar un poco más de información (con una advertencia si quieres) porque hay mucha gente que se interesa más por usar el tutorial para entender cómo funcionan las cosas que para lograr el objetivo.

No sé cómo estaría antes de esta actualización pero ahora veo un tutorial muy detallado en el que es difícil de perderse. Sólo se deben seguir los pasos numerados. Y el detalle del color en los códigos es una gran idea, siempre ayuda destacar visualmente algunos puntos.

En fin, nada más puedo decirte. ¡Espectacular! Ahora a esperar que nos cuentes sobre las animaciones... (?)
 

Kaktus

Miembro insignia
Miembro insignia
Oye @InmortalKaktus, yo me imagino que ha sido por el cambio de foro, pero el post principal luce un poco jodido con tags de BBCode por doquier.
Estaria bueno arreglarlo en algun momento.
Agradecería que se pudiera volver a usar el bbcode en el foro, es útil para remarcar cosas en el código, de momento me mantengo a la espera para ver si lo cambian p:
 

Jaizu

Usuario mítico
Agradecería que se pudiera volver a usar el bbcode en el foro, es útil para remarcar cosas en el código, de momento me mantengo a la espera para ver si lo cambian p:
El BBCode está activado, pero algunos han cambiado o quedado obsoletos. Lo que te ha pedido es que arregles el post.
 

Kaktus

Miembro insignia
Miembro insignia
Lo mismo he tardado demasiado en arreglarlo, pero bueno, imagino que más vale tarde que nunca.
Ya he quitado todos los BBCode y ahora el código se puede copiar y pegar sin problema.
 
Arriba