Développer en Amateur sur Nintendo DS
Exemple pas à pas
La base
Le template de la PALib se présente de la façon suivante :
#include <PA9.h> // Include for PA_Lib
// Function: main()
int main(int argc, char ** argv) {
PA_Init(); // Initializes PA_Lib
PA_InitVBL(); // Initializes a standard VBL
// Infinite loop to keep the program running
while (1) {
PA_WaitForVBL();
}
return 0;
} // End of main()
On y remarque essentiellement 4 points importants :
- l'import de l'en-tête de la PALib (#include
) - l'initialisation de la bibliothèque au début du bloc main (fonction PA_Init)
- la boucle infinie (while(1)) qui permet au programme de ne jamais s'arrêter
- l'initialisation et l'attente du VBL à travers les fonctions PA_InitVBL et PA_WaitForVBL
Comme on peut le voir, le code de base est très simple. Il ne nous reste plus qu'à y ajouter notre propre code.
Afficher un fond
La première étape va être pour nous d'afficher les décors de notre jeu. Nous allons utiliser 3 images superposées afin d'obtenir un effet de profondeur.
Voici nos 3 images (cliquez dessus pour télécharger les images à la taille réelle) :
Avant toute chose, il faut savoir que la Nintendo DS ne peut pas utiliser directement ces bitmaps comme décors. Il va falloir les convertir en un format compréhensible par la console.
La PALib est fournie avec un utilitaire qui se charge de faire ça. Il s'agit de PAGfx. Ce logiciel, écrit en C#, prend une liste de bitmaps ainsi qu'une couleur à remplacer par de la transparence et génère en retour les fichiers appropriés pour la console.
Nos bitmaps utilisent la couleur magenta (le rose violacé) comme couleur transparente. Pour le spécifier à PAGfx vous pouvez soit utiliser l'interface graphique de PAGfx (Windows seulement) soit spécifier ça dans un fichier ini et lancer la version en ligne de commande de PAGfx.
Voici le fichier ini a utiliser pour convertir nos décors (téléchargeable ici) :
#Backgrounds :
bg0.bmp EasyBg bg0
bg1.bmp EasyBg bg1
bg2.bmp EasyBg bg2
La spécification d'un décors se fait par 3 mots clef : le nom du fichier sur le disque, le type de décors et le nom de la palette de couleur.
Dans le code du jeu, il vous devrez utiliser le nom du fichier comme identifiant pour l'image.
Concernant les types de décors, vous avez le choix entre :
- 8bit, pour les décors en 256 couleurs (images 8 bits)
- 16bit, pour les décors en 65535 couleurs (images 16 bits)
- TileBg, pour les décors dont la taille est 256x256, 256x512, 512x256 ou 512x512
- LargeMap, pour les décors dont la taille dépasse 512x512 pixels
- EasyBg qui se charge de choisir pour vous entre TileBg et LargeMap
Une fois les images converties, copiez soit les fichiers .c dans votre répertoire de source, soit les fichiers .bin dans votre répertoire "data".
Pour pouvoir les utiliser dans notre code, il nous faut inclure le fichier généré nommé "all_gfx.h" qui contient les déclarations de toutes les images spécifiées dans PAGfx.ini.
Maintenant, place au code pour afficher les décors (les nouvelles portions de code sont surlignées) :
#include <PA9.h> // Include for PA_Lib
// Function: main()
int main(int argc, char ** argv) {
PA_Init(); // Initializes PA_Lib
PA_InitVBL(); // Initializes a standard VBL
PA_DualLoadPAGfxLargeBg(1, bg0);
PA_DualLoadPAGfxLargeBg(2, bg1);
PA_DualLoadPAGfxLargeBg(3, bg2);
PA_DualInitParallaxX(0, 256, 128, 64); // Initializes the parallax effect
// Infinite loop to keep the program running
while (1) {
PA_WaitForVBL();
}
return 0;
} // End of main()
Ce code contient donc 3 points importants :
- l'inclusion du fichier d'en-tête "all_gfx.h" fin de pouvoir utiliser nos décors
- le chargement en mémoire des fonds (PA_DualLoadPAGfxLargeBg)
- l'initialisation du scrolling différentiel (aussi appelé le "parallax") avec la fonction PA_DualInitParallaxX.
La PALib regorge de fonctions pour charger ses décors. Dans l'exemple, nous utilisons PA_DualLoadPAGfxLargeBg car nous chargeons un décors de type "LargeBg" et car nous souhaitons l'afficher en mode "dual", c'est à dire : sur les 2 écrans en même temps (comme si nous n'avions qu'un seul grand écran). Pour plus d'informations sur les méthodes de chargements, vous pouvez consulter la documentation.
La fonction d'initialisation du scrolling différentiel prend 4 arguments qui représentent la vitesse de déplacement de chaque décors. Le premier argument représente la vitesse du décors 0, le deuxième argument la vitesse du décors 1 et ainsi de suite. La vitesse est à spécifier en fixed point.
Dans l'exemple le décors 0 ne se déplacera pas (vitesse nulle), le décors 1 (les plateformes) se déplacera à une vitesse normale (256/256 = 1), le décors 2 (les montagnes) se déplacera 2 fois moins vite (128/256 = 0,5) et le décors 3 (les nuages) se déplacera 4 fois moins vite que la normale (64/256 = 0,25).
Faire du scrolling
Le scrolling est très simple à faire avec la PALib. Tout (ou presque) est déjà inclus de base, il nous vous reste plus qu'à utiliser les fonctions de la bibliothèque.
Il existe 2 fonctions principales pour le scrolling : PA_ParallaxScrollX et PA_"Mode"Scroll. Ces 2 méthodes sont ensuite déclinées (en X et en Y pour ParallaxScroll ou avec le nom du mode correspondant pour PA_"Mode"Scroll) mais la façon de les utiliser reste identique.
Revoici donc notre code après l'ajout de quelques instructions pour le scrolling :
#include <PA9.h> // Include for PA_Lib
#include "all_gfx.h" // Include all our graphicals objects
// Function: main()
int main(int argc, char ** argv) {
PA_Init(); // Initializes PA_Lib
PA_InitVBL(); // Initializes a standard VBL
// Loads the backgrounds
PA_DualLoadPAGfxLargeBg(1, bg0);
PA_DualLoadPAGfxLargeBg(2, bg1);
PA_DualLoadPAGfxLargeBg(3, bg2);
PA_DualInitParallaxX(0, 256, 128, 64); // Initializes the parallax effect
// Infinite loop to keep the program running
while (1) {
PA_DualParallaxScrollX(--x_scrollpoint);
else if(Pad.Held.Right)
PA_DualParallaxScrollX(++x_scrollpoint);
PA_WaitForVBL();
}
return 0;
} // End of main()
Ce morceau de code est l'occasion de voir rapidement Pad.Held qui permet de vérifier si le joueur appuie sur une touche. Nous verrons ce mécanisme plus en détail dans la partie "déplacer un sprite".
En dehors de ceci, nous pouvons voir 2 points intéressants :
- la déclaration d'une variable x_scrollpoint en s32 qui représentera notre point de scrolling en horizontal. Il s'agit du point de référence de notre scène.
- l'utilisation de la méthode PA_DualParallaxScrollX pour déplacer (sur l'axe horizontal) la vue des 2 écrans en même temps.
Afficher un sprite
Les sprites sont gérées en hardware par la Nintendo DS, l'affichage d'un sprite à l'écran se fait donc très simplement.
La console enregistre principalement les informations suivantes pour chaque sprite :
- Image du sprite
- Position sur l'écran du sprite (en abscisse et en ordonnée)
- Taille du sprite (en 2 valeurs : forme et largeur)
- Nombre de couleurs (256 ou 16 couleurs)
- Numéro de palette
- Etat de l'effet de pixelisation (effait "mosaique")
- Etat de l'effet miroir en horizontal/vertical
- Priorité du sprite par rapport aux fonds
Comme pour les décors, nous devons commencer par convertir l'image de notre sprite au format de la Nintendo DS.
Voici l'image de notre héro (cliquez dessus pour télécharger l'image complète) :
Et revoici notre fichier PAGfx.ini avec, en plus, la ligne pour convertir cette image :
tux.bmp 256colors palette
#Backgrounds :
bg0.bmp EasyBg bg0
bg1.bmp EasyBg bg1
bg2.bmp EasyBg bg2
La spécification d'un sprite se fait par 3 mots clef : le nom du fichier sur le disque, le nombre de couleurs et le nom de la palette.
Le nombre de couleurs peut être soit "256colors", soit "16colors".
Nous allons ajouter quelques lignes à notre code pour afficher notre héro :
// Function: main()
int main(int argc, char ** argv) {
s32 x_scrollpoint = 0;
PA_Init(); // Initializes PA_Lib
PA_InitVBL(); // Initializes a standard VBL
// Loads the backgrounds
PA_DualLoadPAGfxLargeBg(1, bg0);
PA_DualLoadPAGfxLargeBg(2, bg1);
PA_DualLoadPAGfxLargeBg(3, bg2);
PA_DualInitParallaxX(0, 256, 128, 64); // Initializes the parallax effect
PA_DualLoadSpritePal(0, (void*) palette_Pal);
PA_DualCreateSprite(0, (void*) tux_Sprite, OBJ_SIZE_32X32, 1, 0, 96, 80);
// Infinite loop to keep the program running
while (1) {
[...]
Les 2 lignes ajoutées correspondent à :
- Chargement de la palette du sprite sur les 2 écrans
- Chargement du sprite sur les 2 écrans, il sera placé à la position [96, 80].
Animer un sprite
Avec la PALib, l'animation d'un sprite fonctionne essentiellement grâce à 3 fonctions : PA_StartSpriteAnim, PA_StopSpriteAnim et PA_SetSpriteAnimFrame.
- StartSpriteAnim sert à démarrer une animation constituée d'images successives. Cette animation tournera en boucle jusqu'à un appel à PA_StopSpriteAnim.
- PA_SetSpriteAnimFrame permet de choisir l'étape d'animation à afficher. Vous pouvez donc construire votre propre méthode d'animation grâce à cette fonction.
// Loads player's sprite
PA_DualLoadSpritePal(0, (void*) palette_Pal);
PA_DualCreateSprite(0, (void*) tux_Sprite, OBJ_SIZE_32X32, 1, 0, 96, 80);
// Infinite loop to keep the program running
while (1) {
[...]
Déplacer un sprite
Voici la dernière étape de cet exemple, nous allons faire marcher et sauter notre sprite.
Nous utiliserons les informations provenant des boutons et de la croix de direction pour contrôler le personnage. Ces informations sont stockées dans la variable globale "Pad".
Pour détecter l'emplacement des plateformes, nous lirons directement le contenu de l'image "bg0" (il s'agit de l'image contenant les plateformes).
Nous allons avoir besoin de plusieurs variables pour gérer les mouvements de notre sprite. Nous stockerons ces informations dans une structure nommée "character".
#include <PA9.h> // Include for PA_Lib
#include "all_gfx.h" // Include all our graphicals objects
u8 sprite_id;
s16 x;
s32 y; // This value will be stored in fixed point
s32 fall_speed; // This value will be stored in fixed point
u8 moving;
u8 moving_prev;
u8 jumping;
} character;
// Function: main()
int main(int argc, char ** argv) {
s32 x_scrollpoint = 0;
character player;
const u8 MAP_WIDTH = bg0_Info[1]/8;
const u8 BLANK_TILE = bg0_Map[0];
PA_Init(); // Initializes PA_Lib
PA_InitVBL(); // Initializes a standard VBL
// Loads the backgrounds
PA_DualLoadPAGfxLargeBg(1, bg0);
PA_DualLoadPAGfxLargeBg(2, bg1);
PA_DualLoadPAGfxLargeBg(3, bg2);
PA_DualInitParallaxX(0, 256, 128, 64); // Initializes the parallax effect
player.sprite_id = 0;
player.x = 32;
player.y = 32<<8;
player.fall_speed = 0;
player.moving = 0;
player.jumping = 0;
// Loads the player's sprite
PA_DualLoadSpritePal(0, (void*) palette_Pal);
// Infinite loop to keep the program running
while (1) {
player.moving_prev = player.moving;
// Moves the sprite
if(Pad.Held.Left) {
// If there is no wall on the left, move the player
if(bg0_Map[(player.x-8)/8 + (((player.y >> 8)+24)/8)*MAP_WIDTH] == BLANK_TILE)
player.x -= 2;
player.moving = 1;
PA_DualSetSpriteHflip(player.sprite_id, 1); // Flip the sprite horizontally to make it look to the left
}
else if(Pad.Held.Right) {
// If there is no wall on the right, move the player
if(bg0_Map[(player.x+8)/8 + (((player.y >> 8)+24)/8)*MAP_WIDTH] == BLANK_TILE)
player.x += 2;
player.moving = 1;
PA_DualSetSpriteHflip(player.sprite_id, 0);
}
// If the tile located right under the player isn't empty (!= BLANK_TILE), stop the fall (fall_speed = 0)
if(bg0_Map[player.x/8 + (((player.y >> 8)+16)/8)*MAP_WIDTH] != BLANK_TILE) {
player.fall_speed = 0;
if(player.jumping)
player.moving_prev = 0; // Asks to start the "moving" animation
player.jumping = 0;
}
// Otherwise, increase its fall speed (unless it is greater than 3px per frame)
else if(player.fall_speed < 768)
player.fall_speed += 32;
player.jumping = player.jumping || player.fall_speed != 0;
// If the player press the A button, make it jump
if(Pad.Newpress.A && player.jumping == 0) {
// Setting a negative value for the fall speed will make the sprite falling upward, thus jumping ;-)
player.fall_speed = -1024;
player.jumping = 1;
}
// Collision between the hero's head and a block
if(player.fall_speed < 0 && bg0_Map[player.x/8 + ((player.y >> 8)/8)*MAP_WIDTH] != BLANK_TILE)
player.fall_speed = 0;
// Collision with the top of the screen
if(player.y < 0 && player.fall_speed < 0)
player.fall_speed = 0;
// Moves the hero vertically
player.y += player.fall_speed;
PA_DualSetSpriteY(0, (player.y >> 8));
// Animating the player's sprite
if(player.jumping)
PA_DualStartSpriteAnim(player.sprite_id, 2, 2, 1);
else if(player.moving && !player.moving_prev)
PA_DualStartSpriteAnim(player.sprite_id, 0, 1, 6);
else if(!player.moving)
PA_DualStartSpriteAnim(player.sprite_id, 0, 0, 1);
// When the player reach one side of the map, teleports him to the other side
if(player.x > bg0_Info[1]) {
player.x -= bg0_Info[1];
layer++;
}
else if(player.x < 0) {
player.x += bg0_Info[1];
layer--;
}
// Updating the scrollpoint
x_scrollpoint = player.x - 128 + layer*bg0_Info[1];
PA_DualParallaxScrollX(x_scrollpoint);
PA_WaitForVBL();
}
return 0;
} // End of main()
Donc cette fois-ci, il y a beaucoup de nouveautés (au moins en apparence) dans le code. Voici les nouveaux points :
- l'ajout de la structure "character" pour stocker les informations supplémentaires des sprites
- la lecture des informations provenant de la manette (variable globale "Pad")
- l'utilisation de la fonction "PA_DualSetSpriteHflip" pour retourner horizontalement le sprite
- le déplacement d'un sprite (en vertical seulement dans cet exemple) avec "PA_DualSetSpriteY"
- la détection du sol en regardant le contenu d'un décors (bg0_Map)
- l'utilisation des informations à propos d'un décors (bg0_Info)