Développer un homebrew sur PSP
Exemple pas à pas
Création d'une carte
Comme nous l'avons vu précédemment, la PSP n'est pas capable de charger des images d'une résolution supérieure à 512x512 pixels. A travers l'exemple suivant, nous verrons comment pallier à ce problème en affichant une carte d'un jeu comme Mario dont la résolution dépasse largement la limite supportée.
Pour afficher notre image, nous allons utiliser un système de tuiles. Cela consiste à découper notre image en plusieurs blocs de 8x8 pixels et on aura ensuite une "factorisation" de notre image. Au final, un motif redondant ne sera présent qu'une seule fois sur l'image que nous chargerons en mémoire. Voici l'image originale représentant notre niveau, ses dimensions sont de 5210x448 pixels :

On notera une bande de couleur rose en haut de l'image. Cette bande sert à définir la couleur qui sera transparente. Le rose n'étant présent nul part ailleurs dans l'image, tous les éléments seront visibles. S'il y avait eu du rose ailleurs, la ou les zones concernées auraient été vides. Pour construire nos tuiles, nous allons utiliser un outil développé par un amateur, GBAGraphics. A l'aide de cet outil, nous pourrons créer nos tuiles mais également éditer notre carte.
Pour commencer, on charge notre carte dans le logiciel :

Les réglages à modifier sont la taille des tuiles, 16x16 pixels, le codage des couleurs, 32 bits. Le fichier de destination de la map, ici "til.map.c" sera le fichier qui définira le placement de chaque tuile de la carte. Il contiendra la déclaration d'un tableau à deux dimensions qui représentera la carte. On peut donc l'utiliser comme un entête C, dans ce cas on le renommera avec l'extension ".h". On peut ensuite valider.
A partir de ce moment, on peut voir à quoi ressemblera le fichier de tuiles et également éditer la carte :


On exporte ensuite notre fichier de tuiles qui nous appellerons "tileset.png". Voici le résultat obtenu :

Nous sommes donc passé d'une image de 5210x448 pixels à une image de 512x48 pixels. Nous allons maintenant voir comment utiliser notre fichier de tuiles à l'aide de l'OSLib.
Affichage et défilement de la carte
Pour afficher la carte, nous utiliserons le fichier de tuiles précédemment créé mais aussi le fichier "til.mac.c" que nous avons renommé "mario.h". Le tableau à deux dimensions déclaré dans cet entête portera le nom "mario_map". Voici la source de notre fichier principal.
1 #include <oslib/oslib.h> 2 #include "devslib.h" 3 #include "mario.h" 4 #include <psputils.h> 5 6 // callbacks 7 PSP_MODULE_INFO("Plateforme", 0, 1, 0); 8 PSP_MAIN_THREAD_ATTR(PSP_THREAD_ATTR_USER | PSP_THREAD_ATTR_VFPU); 9 10 // pointeur sur notre image des tuiles 11 OSL_IMAGE *map_tile; 12 13 // pointeur sur notre carte 14 OSL_MAP *map; 15
On commence par déclarer nos entêtes propres à l'OSLib et au développement sur PSP. On ajoute également la notre. Ensuite on définit des callbacks. Le premier, PSP_MODULE_INFO, renseigne le nom, le mode de notre module (petit rappel, tout est module sur PSP) et des numéros de version, majeur et mineur. Avec le deuxième callback PSP_MAIN_THREAD_ATTR, on précise qu'on souhaite utiliser un thread supplémentaire.
En fait, en tant que développeur, on décide si le point d'entrée de notre module a besoin d'un nouveau thread séparé pour être exécuté ou alors s'il doit l'être dans le thread qui a chargé et démarrer le module. Dans le premier cas, on aura un nouveau thread "main thread" qui sera créé (le nom "main" n'a pas de relation avec la fonction classique C "main") et dans le second cas, il faudra faire attention aux opérations et tâches qui seront exécutés au point d'entrée : si le module est supposé être utilisé en tant que librairie de fonctions ou simplement lancer un autre thread d'exécution, le point d'entrée sera simple et un nouveau thread séparé ne sera pas forcément nécessaire. Au contraire, si l'on souhaite faire des choses bien plus complexes, on aura besoin d'un nouveau thread (on ne peut pas attendre dans le point d'entrée du module si l'on n'a pas de "main thread").
On déclare ensuite des pointeurs sur notre image de tuiles et sur notre future carte.
16 // initialisation de la carte 17 void InitMap(){ 18 // chargement de notre image des tuiles dans la RAM 19 map_tile = oslLoadImageFile("media/images/tileset.png", OSL_IN_RAM, OSL_PF_5551); 20 21 // pointeur sur la carte recréée 22 map = oslCreateMap( 23 map_tile, // pointeur sur l'image 24 mario_map, // tableau du fichier mario.h 25 16,16, // dimension des tuiles 26 320,28, // taille de la carte (taille du tableau mario_map) 27 OSL_MF_U16); // format de la carte 28 } 29
Cette fonction va initialiser les pointeurs de notre carte. On commence par charger notre image (ligne 19). On précise son nom exacte (sensibilité à la casse), qu'on la charge en RAM et le format des pixels choisi (ici 16 bits par pixel). Avec la fonction oslCreateMap on configure la carte à afficher. On précise le nom de l'image qui contient le fichier de tuiles, le nom du tableau déclaré dans le fichier d'entête, la taille des tuiles, la taille de la carte et le format de la carte.
30 // boucle principal 31 void mainLoop(){ 32 33 #ifdef PSP 34 scePowerSetClockFrequency(333, 333, 166); // on demande à utiliser le CPU à sa fréquence maximale 35 #endif 36 // initialisation de la librairie 37 oslInit(0); 38 39 // Initialisation du mode graphique 40 oslInitGfx(OSL_PF_8888, 1); 41 42 // initialisation de la carte 43 InitMap(); 44 45 int i; 46
On passe ensuite à la boucle principale. On commence par monter la fréquence du CPU à son maximum, 333 MHz. Ensuite on initialise l'OSLib puis le mode graphique (format de pixel à 32 bits par pixel). Enfin on initialise la carte avec la fonction précédente. On déclare ensuite une variable "i" qui servira au défilement de la carte.
47 while (!osl_quit){ 48 // permet de dessiner 49 oslStartDrawing(); 50 51 // vide l'écran 52 oslCls(); 53 54 // écoute la pression des touches 55 oslReadKeys(); 56 57 // Joystick pour déplacer notre map 58 for (i = 32; i <= 120; i += 48){ 59 // suivant l'axe des X 60 if (osl_keys->analogX > i){ 61 map->scrollX += 2; 62 } 63 if (osl_keys->analogX < -i){ 64 map->scrollX -= 2; 65 } 66 67 // puis l'axe des Y 68 if (osl_keys->analogY > i){ 69 map->scrollY += 2; 70 } 71 if (osl_keys->analogY < -i){ 72 map->scrollY -= 2; 73 } 74 } 75 76 // affiche la carte 77 oslDrawMapSimple(map); 78 79 //Fin du dessin 80 oslEndDrawing(); 81 82 // synchronise l'écran 83 oslSyncFrame(); 84 } 85 86 oslEndGfx(); 87 oslQuit(); 88 } 89 90 // fontion main 91 int main(int argc, char* argv[]){ 92 mainLoop(); 93 return 0; 94 }
On commence par appeler la fonction "oslStartDrawing" car on souhaite dessiner à l'écran. Il faut appeler cette fonction à chaque frame et avant chaque opération de dessin sous peine de planter notre programme. Avec la fonction "oslCls", on vide l'écran. On écoute ensuite les pressions sur les touches qui seront stockées dans la structure globale "osl_keys". On doit appeler cette fonction avant le moindre test de touche.
On regarde ensuite si le joystick analogique a été poussé vers la droite, la gauche, le haut et le bas. On fait défiler la carte selon l'inclinaison du joystick.
On affiche ensuite notre carte avec "oslDrawMapSimple(map)" puis on précise qu'on a fini nos opérations de dessins et enfin toutes les informations à afficher sont synchronisée pour l'affichage à l'écran. Si on quitte l'application via le menu de la PSP, des callbacks seront appelés pour quitter le mode graphique et notre exécutable.
Pour compiler notre programme, on peut utiliser le makefile suivant. Il se charge d'utiliser toutes les librairies nécessaires à notre application.
TARGET = PlateForme OBJS = main.o INCDIR = CFLAGS = -G4 -Wall -O2 -DPSPFW3XX CXXFLAGS = $(CFLAGS) -fno-exceptions -fno-rtti ASFLAGS = $(CFLAGS) BUILD_PRX = 1 PSP_FW_VERSION=371 LIBDIR = LDFLAGS = STDLIBS= -losl -lpng -lz \ -lpspsdk -lpspctrl -lpspumd -lpsprtc -lpsppower -lpspgu -lpspaudiolib -lpspaudio -lm -lDevslib -lpspaudiolib -lpspmpeg -lpspaudiocodec -lmad LIBS=$(STDLIBS)$(YOURLIBS) EXTRA_TARGETS = EBOOT.PBP PSP_EBOOT_TITLE = PlateForme PSPSDK=$(shell psp-config --pspsdk-path) include $(PSPSDK)/lib/build.mak
On a donc ainsi réussi à afficher notre carte avec des dimensions bien supérieures à ce qui est permis nativement par la PSP. Ce système de tuiles est couramment utilisé dans le monde des jeux vidéos amateurs et sur les autres plateformes telles que la Nintendo DS par exemple.
Afficher un sprite
Pour afficher un sprite, nous allons pouvoir reprendre ce qui a été vu précédemment pour l'initialisation de la librairie, du mode graphique, des dessins, etc... On va également faire appel à des fonctions de la DevsLib (les noms des fonctions ont été traduites en français par l'auteur mais il subsiste des fautes d'orthographes dans ces noms, c'est donc volontairement que j'ai laissé ces fautes dans le code).
Avant de commencer, on va créer notre planche de sprite. Pour cela j'ai utilisé l'image de Mario proposé aux IR2 (de l'année 2010) pour leur projet de Java Avancé.

Je l'ai ensuite modifié de telle sorte qu'on ait une grille d'animation avec les différents états de Mario : debout immobile, marche, saute et baissé. J'ai donc une seule image et l'animation se fera comme si une fenêtre de 16 pixels de largeur par 32 de hauteur survolait chaque position de Mario en boucle.

1 2 // on créé un pointeur pour notre personnage 3 OBJETS *Player; 4 5 // création du personnage 6 Player = DevsCreerSprite(oslLoadImageFile("image/mario.png", OSL_IN_RAM, OSL_PF_5551),0, 3 ,16,32,12); 7 8 // animation du personnage 9 DevsAnimationPlay(Player); 10 11 // position initiale du personnage 12 Player->sprite->x = 50; 13 Player->sprite->y = 50; 14
On créé un pointeur pour notre personnage et on créé notre sprite. La méthode "DevsCreerSprite" prend comme arguments l'image à charger pour créer l'animation (en utilisant l'OSLib), la position de départ de l'animation (en abscisse donc 0), le nombre de frames pour l'animation (ici 3), la largeur du sprite pour une frame, la hauteur du sprite pour une frame et la vitesse de l'animation (de la première frame à la dernière, selon l'effet souhaité on peut déterminer cette valeur de façon empirique). On démarre ensuite l'animation puis on positionne notre personnage.
On peut maintenant animer notre personnage en fonction des touches pressées. Le code suivant est à mettre dans la boucle principale.
15 oslReadKeys(); 16 17 // Croix : Saut 18 if (osl_keys->pressed.cross && Player->auSol) { 19 Player->vy = vitesseSaut; // on saute 20 Player->auSol = 0; // plus au sol 21 DevsChangePossition(Player, 32*2); 22 } 23
On écoute les touches puis on teste si le personnage doit sauter (appui sur la touche croix). On définit une vitesse de saut et l'état du personnage. Ce qui est intéressant ici c'est la fonction "DevsChangePossition". Cela va décaler la frame de notre sprite à la hauteur voulue. Une frame fait 32 pixels de haut, on décale donc à la 3ème ligne (la première commence à 0) de notre planche de sprite, il s'agit bien de l'animation de saut.
24 // Gauche : On tourne vers la gauche 25 if (osl_keys->held.left) { 26 // on veux bouger a gauche , on verifie que le joueur est au sol, sinon on laisse la position SAUT 27 if (Player->auSol) DevsChangePossition(Player, 32); 28 // on fait un effet de mirroir car on tourne a gauche 29 DevsSetFliphOn(Player); 30 // on se deplace 31 Player->sprite->x -= 2; 32 } 33
Maintenant on souhaite déplacer notre Mario vers la gauche. On passe l'animation à la marche (32 * 1 : 2ème ligne). On va ensuite utiliser un effet miroir horizontal pour avoir notre image tournée vers la gauche grâce à la fonction "DevsSetFliphOn". Puis on déplace notre personnage en changeant sa position "x". On répète les mêmes procédés pour les autres directions.
34 // Droite : On tourne vers la droite 35 if (osl_keys->held.right) { 36 if (Player->auSol) DevsChangePossition(Player, 32); 37 // on annule l'effet de miroir 38 DevsSetFliphOff(Player); 39 // on se deplace 40 Player->sprite->x += 2; 41 } 42 43 // Bas : On se baisse 44 if (osl_keys->held.down) { 45 if (Player->auSol) DevsChangePossition(Player, 32*3); 46 } 47 48 //si aucune touche n'est appuyée 49 if (!osl_keys->held.value) { 50 //si le joueur et au sol alors on met l'animation d'attente 51 if (Player->auSol) DevsChangePossition(Player, 0); 52 53 }
Finalement notre Mario est maitenant animé, il peut marcher, sauter, se baisser et rester immobile. En intégrant les deux exemples ensemble, on peut commencer et se lancer pour réaliser un premier "Mario Bros like".