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 :

map

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 :

GBAGraphics

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 :

GBAGraphics
GBAGraphics

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

GBAGraphics

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

mario

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.

mario
 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".