Programación de la PSP: intros multimedia
Existen ya dos recetas sobre la PSP en CRySoL: una pequeña introducción al uso de la PSP en sistemas GNU/Linux y otra sobre cómo instalarse el pspsdk completo en el sistema. Ya sólo faltaba ésta: cómo programar una aplicación. Para ello he pasado de hacer el hola mundo convencional y he decidido escribir una pequeña intro como las de la vieja escuela. De eso va esta receta: programar una pequeña intro para la PSP desde GNU/Linux.
Introducción
Para empezar tenemos que saber que es una intro: hace tiempo, cuando los sistemas monousuario tipo DOS pululaban nuestros PC's, antes de que Internet fuese lo que es hoy, la gente gustaba de hacer pequeñas joyas de programación (generalmente en ensamblador puro) que consistían en algún efecto visual acompañado de alguna musiquilla tipo nintendo NES. Se hacían competiciones (y se hacen) como las famosas Assembly's. Nosotros no vamos a programar en ensamblador del Mips, para eso tenemos Arquitectura e Ingeniería de Computadores, vamos a usar C++ (aunque casi podría tratarse de C) y no vamos a trabajar con la máquina desnuda, realizaremos llamadas a funciones de librerías. Para crear una intro tienes que tener en mente el efectillo que quieras crear, una musiquilla que acompañe y, finalmente, cúales y cómo serán los créditos que irán apareciendo. Una intro suele ser siempre cíclica y no precisa de interacción con el usuario (salvo para finalizar).Ingredientes
Tenéis que instalar el toolchain de la psp (ver receta al respecto) y el pspsdk completo, además de la librería cpplibs incluída como extra. A parte de esto es necesario el arte de la intro, que en nuestro caso se compondrá de un fondo de pantalla, un tipo de letra raster y un archivo mod de cuatro canales con una música al más puro estilo "pitiditos".Esqueleto de un programa para la PSP
Realmente no existe una gran diferencia entre hacer un programa para un sistema convencional o uno para la PSP, sólo debemos tener en cuenta una cosa muy importante: la terminación. Nuestros programas se lanzarán como un hilo de usuario dentro del XMB de la PSP. Para ello crearemos un nuevo hilo que esperará la llamada del XMB (un callback) para finalizar la aplicación, éste hilo terminará lo que tenga que terminar de nuestro programa, básicamente consiste en lo siguiente:/* * Esqueleto de aplicación para PSP */ #include <pspkernel.h> // Aquí configuramos nuestro hilo PSP_MODULE_INFO("nombre programa", 0, 1, 1); PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER); int done = 0; // Indica cuando terminar int SetupCallbacks(); int main(int argc, char* argv[]) { SetupCallbacks(); // Nuestras inicializaciones // y movidas... ... // Nuestro bucle principal, esto // se ejecutará hasta que nuestro // manejador del callback lo diga... while (!done) { // Aqui va el meollo del programa } // Ahora finalizamos las cosas que hayamos // inciado ... // Finalmente cerramos el hilo sceKernelExitGame(); return 0; } // Manejador del callback de salida int exit_callback(int arg1, int arg2, void *common) { done = 1; return 0; } // Crea el callback de salida int CallbackThread(SceSize args, void *argp) { int cbid; cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL); sceKernelRegisterExitCallback(cbid); sceKernelSleepThreadCB(); return 0; } // Crea y configura el hilo para el callback, retorna su ID int SetupCallbacks(void) { int thid = 0; thid = sceKernelCreateThread("update_thread", CallbackThread, 0x11, 0xFA0, 0, 0); if(thid >= 0) { sceKernelStartThread(thid, 0, 0); } return thid; }Y bueno... ahora sólo nos queda saber qué vamos a hacer... ;-)
Ambientación
O lo que es lo mismo... un fondo de pantalla y la musiquita ;-). Para esto ya necesitamos usar cpplibs así que ya sabéis:#include <libpsp2d/Screen.h> #include <libpsp2d/Image.h> #include <libpspsnd/Music.h>Para dibujar en la pantalla haremos lo siguiente: obtendremos un manejador de la VRAM de forma que todas las escrituras se realizan en un buffer de RAM (muy rápido) y una vez hayamos compuesto el frame en el buffer, lo volcaremos todo de una:
Screen *screen = Screen::getInstance();Para cargar la imagen y la música usaremos cpplib, es muy fácil:
Image background("background.png"); Music::init(); Music music("music.mod", true);...y reproducir la música en un hilo a parte y de forma cíclica (para eso el true de ahí arriba) hasta que se finalice el programa:
music.start();Ale... ahora sólo nos falta dibujar el fondo, esto lo haremos en cada frame (ahora no sería necesario, pero cuando haya animación sí):
while (!done) { // Volcamos el fondo (a un buffer intermedio) screen->blit(&background, 0, 0, 480, 272, 0, 0); // Aquí vendrá el codigo de los efectos // Esto dibuja el frame screen->flip(); }Con esto ya tenéis una aplicación que os mostrará una bonita imagen y estará sonando una musiquita hasta que se apague la PSP o se salga del juego.
Fuentes raster
Estas fuentes, a diferencia de las vectoriales clásicas, se almacenan en un PNG (o formato que permita transparencia). La imagen tiene unos píxeles de forma que delimitan cada carácter. Todo esto no debemos hacerlo nosotros, existe una librería que maneja estas fuentes:#include <libpsp2d/Font.h>Ahora debemos crear un objeto con la cadena que queramos mostrar:
string msg("My first intro by Int-0");Para poder centrar la cadena debemos conocer la fuente que vamos a usar y con ello la longitud en pixels de la cadena renderizada con el siguiente método:
Font font("font.png"); centerx = (480 - font.getTextWidth(msg)) / 2;Finalmente sólo nos queda renderizarla en el buffer de la VRAM, es muy simple, pero recordad que hay que volcarlo después de volcar el fondo (si no lo sobreescribiríamos):
font.drawText(screen, msg, centerx, 5);El problema de este método es que estas fuentes suelen ser muy grandes y no son muy útiles para mostrar mucho texto en la pantalla, por tanto los usaremos para títulos y encabezados.
El efectillo
El efecto que he pensado es muy simple: vamos a dibujar el logo de "PSP" a base de puntitos. Los puntitos irán girando y haciendo tontunas, además tendrán una pequeña estela (que consiste en los mismos puntos en la misma situación en la que estuvieron hace algunas iteraciones... pero pintados de un color más oscuro). Para situar los puntos haremos lo siguiente: dos vectores de enteros que contienen el desplazamiento en X y el desplazamiento en Y de cada punto teniendo como origen el centro del logo. Para dibujar el logo sólo tenemos que elegir un punto cualquiera de la pantalla, recorrer los vectores y pintar cada punto según el punto seleccionado y los desplazamientos de los vectores:// Añadimos un segundo desplazamiento a los puntos: la rotacion // para ello necesitamos variar un angulo de forma ciclica ang = (ang + 0.01); // El limite a 100 esta puesto a ojimetro (y con que tino!) if (ang > 100) { ang = 0;}; // Iteramos en todos los puntos de "PSP", si, son 156 puntos for (idx = 0; idx < 156; idx++){ // Posiciones absolutas del siguiente punto a dibujar // La razon de no calcular el seno y el coseno aqui esta en que // para dibujar la estela restaremos un valor al angulo para que // se quede igual que en iteraciones anteriores... muy feo! px = (int)(fx[idx] * 6); py = (int)(fy[idx] * 6); // Poniendo el punto que nos interesa y tres mas de estela... // Las estelas no habria que calcularlas... deberian estar en un // buffer, esto permitiria calcular un seno y un coseno por iteracion // y no las cuatro veces que se hace (aumentaria la velocidad // considerablemente) screen->putPixel(0x000000, 240 + (int)(px * cosf(ang - 0.12)), 130 + (int)(py * sinf(ang - 0.12))); screen->putPixel(0x4F4F4F, 240 + (int)(px * cosf(ang - 0.06)), 130 + (int)(py * sinf(ang - 0.06))); screen->putPixel(0x8F8F8F, 240 + (int)(px * cosf(ang - 0.02)), 130 + (int)(py * sinf(ang - 0.02))); screen->putPixel(0xFFFFFF, 240 + (int)(px * cosf(ang)), 130 + (int)(py * sinf(ang))); }
Los créditos
Finalmente quedan los créditos, para quien vea el código, veréis que hay un ltfont.c extraño por ahí...¿de que va eso? pues no es otra cosa que unos métodos que manejan fuentes gráficas simples (no rasters) formadas por matrices de 8x8 puntos (8 bytes por carácter) así al estilo antiguo. Es un código muy feo pero muy sencillo... además se sale un poco del objetivo de la receta, por tanto creo que no es útil ni necesario hablar de ello aquí. Sólo diré que todas las letrillas que aparecen por la intro se renderizan punto a punto con esa librería. La forma en la que imprimimos los créditos es la siguiente: primero una frase haciendo scrolling horizontal de forma contínua, esto se hace de forma gratuita en los framebuffers ya que si nos salimos por un lado de la pantalla el pixel aparece automáticamente por el otro... jejeje...// El cartel que va haciendo scrolling vertical myPutCicleStr(screen, tx++, 180, 0xFFFFFF, thks); tx = tx % 480;En cuanto los créditos propiamente dichos, pues fácil: según su coordenada Y sabremos si debemos subirlo, bajarlo o mantenerlo un rato:
// Los creditos myPutCicleStr(screen, xc, yc, 0xFFFFFF, credits[ic]); yc = yc + yinc; // Cuando alcanzan la altura deseada lo paramos un rato if (yc == 215) { yinc = 0; ++wt; } // Transcurrido el rato lo podemos volver a ocultar if (wt == 120) { yinc = 1; } // Una vez ocultado (aprovechamos el cropping que // implementamos en ltfont.c) vamos al siguiente... if (yc >= 291) { wt = 0; // Siguiente credito (este 53 de aqui da pena!) ic = (ic + 1) % 53; yc = 290; yinc = -1; xc = 240 - (credits[ic].length() * 4); }
Compilando el engendro
Sólo nos queda hacer el Makefile, esto nos obliga a conocer como llamar al compilador, librerías, etc. o también podemos usar las reglas predefinidas del pspsdk con lo que nos queda un archivo bastante sencillito:TARGET = crysol_intro OBJS = main.o INCDIR = CFLAGS = -G0 -Wall CXXFLAGS = $(CFLAGS) -fno-rtti ASFLAGS = $(CFLAGS) LIBDIR = LDFLAGS = LIBS = -lpspsnd -lpsp2d -lmikmod -lmmio -lpspaudiolib -lpspaudio -lpspgu -lpng -ljpeg -lz -lm -lstdc++ EXTRA_TARGETS = EBOOT.PBP PSP_EBOOT_TITLE = Crysol Intro PSP_EBOOT_ICON = icon0.png PSP_EBOOT_UNKPNG = tile.png PSP_EBOOT_PIC1 = pic1.png PSPSDK=$(shell psp-config --pspsdk-path) include $(PSPSDK)/lib/build.makPara usar las reglas predefinidas debemos crear unas variables para generar lo que deseemos, principalmente hay que definir las siguientes:
- EXTRA_TARGETS: indica qué queremos generar, podemos crear desde un simple EBOOT.PBP (lo que hay que hacer para firmwares customizados a unos archivos para el kxploit (lo necesario para firmware 1.50).
- PSP_EBOOT_TITLE: es el título que pondrá el menú del XMB cuando nuestro archivo esté seleccionado.
- PSP_EBOOT_ICON: el icono del programa dentro del XMB.
- PSP_EBOOT_UNKPNG: imagen transparente que superpondrá a PIC1.
- PSP_EBOOT_PIC1: Imagen de fondo que reemplazará al fondo del XMB cuando se seleccione nuestro programa.
Apéndice A: instalación en firmware 1.50
Para instalar las aplicaciones que generemos en este firmware, el objetivo a invocar por make debe ser kxploit o (preferiblemente) SCEkxploit. Los dos objetivos generan los mismos archivos, pero existe una diferencia: el nombre de los directorios. En un caso creará nombreproyecto/ y nombreproyecto%/ y en el otro __SCE__nombreproyecto y %__SCE__nombreproyecto. La ventaja de este último método es que al instalar nuestros programas, no aparecerá en el menú del XMB los incómodos "datos dañados" que aparecerán con el otro. Una vez compilada nuestra aplicación con cualquiera de estos dos objetivos, copiaremos esos directorios en /PSP/GAME de nuestra memorystick y descomprimiremos el arte en /PSP/GAME/nombreproyecto o /PSP/GAME/__SCE__nombreproyecto.Apéndice B: instalación en firmware 3.xx customs
Mucho más sencillo que en el caso anterior, simplemente debemos invocar la regla EBOOT.PBP del make y copiaremos el archivo y el arte en algún subdirectorio dentro de /PSP/GAME150.Enlaces
Pues... barriendo para casa... GNU/Linux y nuestra amada PSP ...e indispensable... Kit de desarrollo completo para PSP Los archivos de la receta: Sin más... disfrutadlo... a ver si hacemos unas cuantas intros chulas y podemos montar una mini-compo de intros... si tenéis dudas sobre el código o el proceso de creación ya sabéis, pero os recomiendo usar la lista de correo porque es más dinámica para estas cosas... en fin... nos vemos! ;) [ show comments ]
blog comments powered by Disqus