Version en ligne

Tutoriel : Programmez sur votre Nintendo DS !

Table des matières

Programmez sur votre Nintendo DS !
Les bases de PAlib
Les techniques avancées
Annexes

Programmez sur votre Nintendo DS !

Les bases de PAlib

Image utilisateur

Programmer sur ordinateur c'est chouette..
.. Mais sur Nintendo DS ?

La bibliothèque PAlib permet d'écrire des programmes en C / C++ pour votre Nintendo DS. Elle permet de tout faire (gestion des écrans, touches, stylet, son, image, etc.).

Le hic ? Bien qu'elle soit facile d'accès, les tutoriels sur le Net sont soit bancals soit très mal expliqués. Voilà pourquoi nous, Gambit2099 et snake_48, nous sommes associés pour vous faire découvrir cette lib !

En avant ! ^^

Les bases de PAlib

Les techniques avancées

Parce qu'il faut bien commencer quelque part, cette partie vous expliquera les notions de base de PAlib. Il n'y aura pas que du hello world, sinon ça ne serait pas marrant, avouez :D .

Note : vous devez avoir lu et compris les deux premières parties du cours sur le C de M@teo21 ! La partie consacrée à la SDL est facultative mais ça peut toujours aider.

Avant propos

Vous vous doutiez sûrement que les outils nécessaires à la programmation sur DS ne se trouvaient pas déjà sur votre ordinateur et qu'il ne suffisait pas de dire abracadabra pour que vous puissiez utiliser vos programmes sur votre DS :p . C'est pourquoi dans ce chapitre nous allons vous expliquer en détail ce dont vous avez besoin.

Vous n'apprendrez donc "rien" dans ce chapitre mais ne vous inquiétez pas ça arrivera plus vite que vous ne le pensez :-° .

Parlons hardware

À ce stade-ci deux chemins qui s'offrent à vous :

Le deuxième choix semble le plus intéressant, cela va de soi ! Toutefois, renseignez-vous sur son utilisation, car vous pouvez rapidement tomber dans l'illégalité. Voilà pourquoi nous n'expliquerons pas ici comment fonctionne un linker (tel que la R4, M3, etc.).

C'était juste à titre d'information, rien de plus. ;)

Installation des outils nécessaires

Bien ! Après vous avoir expliqué les deux possibilités qui s'offraient à vous, il est enfin temps d'installer PAlib et tout le nécessaire pour enfin programmer pour notre chère NDS. En avant :pirate: !

Pour ce faire, nous allons utiliser un auxiliaire de programmation utilisant le BATCH pour sa compilation. Il est donc important d'avoir un ordinateur sous WINDOWS. Pour les utilisateurs de Linux, ne vous inquiétez pas, on va arriver à votre cas ;) ! Pour ceux qui utilisent autre chose, il faudra, bien entendu, vous adapter au tutoriel.

L'auxiliaire de programmation

Nous utiliserons DevkitPro pour ce tutoriel. Téléchargez la dernière version (à l'heure où j'écris ces lignes c'est la version 1.5.0).

La bibliothèque : PALib

Pour information, PA veut dire Programmer's Arsenal et Lib veut dire Library (qui signifie "bibliothèque") tout simplement. Il nous sera très utile : c'est la bibliothèque que nous utiliserons pour la programmation sur DS. Télécharger PALib.
Actuellement c'est la version la plus récente est la 100707, selon le site elle serait maintenue jusqu'à ce qu'un autre développeur reprenne le projet...

L'installation

Ça y est, nous allons enfin installer les éléments nous permettant de programmer !

Tout d'abord lancez l'installateur devkitpro pour qu'il aille rechercher les fichiers nécessaires à l'installation.
Lors de celle-ci vous pouvez laisser le chemin par défaut (C:\devkitPro ; ce qui est conseillé si vous n'êtes pas très à l'aise avec les changements de répertoire et de variables d'environnement qui risquent d'arriver), et au choix des composants à installer vous pouvez ne cocher que les cases correspondant à la DS (si vous avez peur de faire une bêtise, laissez tout coché ce n'est pas gênant :) ).

Si vous avez téléchargé DevkitArm 20, alors remplacez la nouvelle version par l'ancienne (présente dans votre dossier devkitpro).

Ensuite il faut installer .Net framework pour que PAlib fonctionne correctement, il se peut qu'il soit déjà installé sur votre ordinateur, mais si vous hésitez téléchargez-le ici et (ré)installez-le ! Ne vous inquiétez pas si vous avez une erreur disant qu'il n'a pas pu l'installer, la raison est souvent qu'il est déjà installé.

Il nous reste plus que PALib ! Installez-la dans le même le dossier devkitpro, c'est très important.

Pour vérifier que tout a bien été installé rendez-vous dans C:\devkitPro\palib\examples\Text\Normal, choisissez un dossier (HelloWorld par exemple) et double cliquez sur build.bat. Une fenêtre de console devrait apparaître (le projet HelloWorld se compile en fait), s'il n'y a pas d'erreur vous devriez trouver un fichier HelloWorld.nds et HelloWorld.ds.gba dans le répertoire :) !

Citation

make[1]: *** [/c/devkitPro/palib/examples/Text/Normal/HelloWorld/HelloWorld.elf]
Error 1
make: *** [build] Error 2

Il vous faudra remplacer DevkitArm que vous avez par un DevkitARM antérieur.

Si vous avez une erreur qui s'apparente à celle-ci :

Citation

arm-eabi-gcc.exe: CreateProcess: No such file or directory

Il faudra modifier la valeur de la variable Path. Pour y accéder sous Vista, faites Démarrer -> Panneau de configuration -> Système -> Paramètres Système Avancés (colonne à gauche). Cliquez sur le bouton "Variable d'environnement", puis sur "PATH" et "Modifier...". Une fenêtre apparaîtra et, à la fin du texte éditable, mettez un point-virgule et ajoutez ce code :

Citation

C:\devkitPro\devkitARM\arm-eabi\bin;C:\devkitpro\devkitARM\bin;\msys\bin;c:\devkitpro\msys\bin;c:\devkitpro\devkitarm;C:\devkitPro\devkitARM\libexec\gcc\arm-eabi\4.1.1;

Vous pouvez remplacer C:\ par le chemin jusqu'à devkitpro (exemple : C:\bidule\).

Si votre compilateur se met à vous injurier comme ce n'est pas permis, c'est peut-être que la libnds ne correspond pas à PALib. Il faudra donc la changer en remplaçant le dossier "libnds" de devkitpro par le contenu de libnds (si le lien est mort vous pouvez recomposer le dossier à l'aide de : libnds, le dossier ; libfat, à ajouter dans le même dossier et dswifi, toujours dans le même dossier).

Il se peut que la compilation se fasse avec beaucoup de warnings, ignorez-les.

Si vous avez un autre type d'erreur, recommencez l'installation à zéro, ou cherchez sur le Web une solution (n'oubliez pas les forums du site) :) .

L'émulateur

Un émulateur est une application servant à visualiser un jeu (de DS ou autre). Il vous servira beaucoup pour tester vos jeux, c'est plus pratique que de prendre chaque fois sa DS :p . Sauf comme expliqué plus tôt, les émulateurs ne gèrent pas / gèrent mal les fichiers et le Wifi.

Nous vous conseillons un excellent émulateur : No$GBA que vous pouvez télécharger ici (plus bas choisissez "Download windows version" et non DOS).

Sinon pour votre gouverne sachez qu'il en existe pleins d'autres : PicoDriveDS, Spec DS, DeSmuMe, DSEMU, SnezziDS, GeoSIDeaS, Dualis, NDS NeoPop, etc.

Malgré le nombre d'émulateurs, nous vous conseillons no$gba qui est actuellement l'un des émulateurs DS (et GBA) les plus performants.

L'éditeur

Il est évident qu'il nous en faut un. Et bien que Bloc-Note pourrait suffire, pour une meilleure lisibilité du code (on parlera d'indentation et de coloration syntaxique du code), nous vous conseillons d'utiliser un éditeur plus évolué. En premier choix, nous vous proposons Notepad++ (version zip) / Notepad++ (version installateur, plus complet que le zip).
Toutefois vous pouvez continuer sous votre IDE préféré si ça vous chante, de toute façon nous ne compilerons pas avec eux, mais comme dit plus haut avec un fichier .bat. Si vous prenez un IDE, pensez bien à changer de Makefile !

Tiens d'ailleurs ça me fait penser qu'il est enfin temps de se jeter dans le vif du sujet, allons-y ! :pirate:

Création d'un projet

Juste avant de nous lancer dans la programmation nous devons créer un projet ! Voici les étapes (très simples) :

Et voilà, il ne vous restera plus qu'à renouveler cette action pour créer un nouveau projet !
A partir de là il suffit de créer un fichier main.c (ou .cpp si vous programmez en C++) dans le dossier source.

Comment compiler un projet ?

Il faut lancer build.bat (en double-cliquant dessus, ça suffit). Tiens en parlant de programme, ça vous dirait qu'on s'y mette ? Alors allons-y !! :D

Et sous Linux ?

L'installation

Téléchargez le devkitpro pour Linux (tout est réuni dans un gros fichier).
Décompressez-le dans votre dossier personnel (soit /home/nomdutilisateur/) :

tar -jxvf devkitpro-pour-linux.tar.bz2

Normalement, le chemin vers le devkitpro est donc /home/NOMDUTILISATEUR/devkitpro/. Allez donc dans ce dossier et décompressez-y les fichiers devkitARM-r21-linux.tar.bz2 et PAlib.tar.bz2 comme nous venons de le faire.

tar -jxvf devkitARM-r21-linux.tar.bz2
tar -jxvf PAlib.tar.bz2

Vous devriez avoir cette arboresence :

/
|--home
        |--NOMDUTILISATEUR        
                |--devkitpro
                        |-devkitARM
                        |-libnds
                        |-Other Libs
                        |-PAlib

Modifiez maintenant le fichier .bashrc (situé dans votre dossier personnel) en y ajoutant les 3 lignes suivantes :

export DEVKITPRO=/home/NOMDUTILISATEUR/devkitpro 
export DEVKITARM=$DEVKITPRO/devkitARM
export PAPATH=$DEVKITPRO/PAlib/lib

Exécutez la commande suivante dans un terminal pour appliquer les changements :

source .bashrc

Pour vérifier que les changements ont été appliqués, exécutez ensuite cette commande :

env | grep PAPATH

Si vous voyez quelque chose s'afficher à l'écran (un chemin, comme PAPATH=/home/rayman3640/devkitpro/PAlib/lib
), alors les changements ont été appliqués. Sinon, redémarrez votre terminal et réessayez.

Maintenant, il ne reste plus qu'à tester ! Le mieux est de compiler un exemple.
Allez dans le dossier du devkitpro, puis dans /PAlib/examples/Text/Normal/HelloWorld.
Exécutez cette commande :

make

Vous pouvez obtenir ceci :

bash: make : commande introuvable

Cela signifie que make n'est pas installé. Pour l'installer, exécutez donc cette commande avant de réessayer :

sudo apt-get install build-essential

Si vous obtenez quelque chose comme ceci après avoir exécuté make (noté que j'ai légèrement raccourci le code) :

arm-eabi-g++ -g -mthumb-interwork -mno-fpu -L/home/rayman3640/devkitpro/PAlib/lib/lib -Wl 
Nintendo DS rom tool 1.36 - Oct 23 2007 23:03:47
by Rafael Vuijk, Dave Murphy, Alexei Karpenko
built ... HelloWorld.ds.gba
dsbuild 1.21 - Oct 23 2007
using default loader

Alors vous avez réussi ! :)

L'émulateur

Sous Linux, le choix d'émulateurs est un peu plus réduit. Je vous recommande desMuMe, car il en existe une version pour Linux. Sur certaines distributions, il est possible de l'installer via un dépôt (c'est le cas notamment d'Ubuntu). Si votre distribution utilise des paquets DEB, essayez comme ceci :

apt-get install desmume

Si votre distribution utilisez des paquets RPM, essayez ceci :

yum install desmume

Sinon, allez dans le dossier du devkitpro puis dans /PAlib/emulators/desmume-linux et exécutez ces commandes pour installer desMuMe :

chmod +x desmume-gtk-0.7.0.x86.package
./desmume-gtk-0.7.0.x86.package

Enfin, il est possible d'utiliser no$gba (qui se trouve dans le dossier /PAlib/emulators/no$gba/ avec Wine en faisant :

wine no\$gba.exe

Vous pouvez même essayer d'utiliser les autres émulateurs du dossier avec Wine, mais desMuMe et no$gba sont meilleurs ;) .

Un grand merci à Rastagong d'avoir complété notre tutoriel :) !

Nous voilà fin prêts pour attaquer la programmation :lol: !

Notre premier programme

Maintenant, nous allons enfin commencer à programmer :) .

Le code par défaut

Dernières préparations

Il faut que vous créiez un fichier main.c (fichier C) ou main.cpp (fichier C++), selon le langage de programmation que vous avez choisi, dans le dossier "source". Si vous n'avez pas nommé le fichier main ou que vous ne l'avez pas mis dans le dossier source, alors il ne sera pas trouvé lors de la compilation.

Le code de base

Alors le voici, vous l'attendiez tous le code qu'il faut retenir par coeur :D .

#include <PA9.h>
 
int main (int argc, char **argv)
{
    PA_Init();
    
    while (1)
    {
        PA_WaitForVBL();
    }
    
    return 0;
}

:euh: Heu...

Pas de panique, je vais tout vous expliquer :D .

Explications

#include <PA9.h>

Inclusion du header de PAlib, je suppose que vous l'aviez deviné. ;) .

PA_Init();

Cette ligne est très importante ! Elle initialise PAlib (donc l'utilisation de ses fonctions), c'est pourquoi il ne faut surtout pas l'oublier :p .

while (1)

Boucle blanche (ou plus communément appelée boucle infinie), c'est ici que nous mettrons nos instructions. Elle s'exécutera jusqu'à ce que l'on appuie sur le bouton power.

PA_WaitForVBL();

En parlant de rafraîchissement ! Cette fonction permet de synchroniser le rafraîchissement de l'écran à 60 frames par secondes (abrégé fps) dans la boucle. Il y a 60 frames par secondes, donc on peut en déduire que les écrans de la Nintendo DS ont une fréquence de 60 Hz. N'oubliez surtout pas de la mettre sinon il se peut que votre programme soit rafraîchi beaucoup trop vite, et là c'est le plantage :-° .

Hello world !

On va y aller doucement pour les textes, sinon, on va vite se mélanger les pinceaux :lol: .

L'initialisation du texte

Il faut initialiser le texte avec la fonction PA_LoadDefaultText.

void PA_LoadDefaultText ( u8 ecran, u8 background ) ;

Voici ce qu'il faut donner comme arguments à cette fonction :

Pour le background, cela marche de la même façon que les calques sous Photoshop.

Le texte !

Il existe la fonction u16 PA_OutputSimpleText(u8 ecran, u16 colonne, u16 ligne, const char *texte) mais elle n'est pas comparable à printf. En effet, cette fonction sert à créer un texte statique. Autrement dit, on ne peut pas afficher de variables ("%d", "%f", "%c", "%s", etc...). C'est pour cette raison que nous la laissons tomber pour privilégier la fonction :

void PA_OutputText(u8 ecran, u16 x, u16 y, const char *texte, ...) ;

Cette fonction présente deux avantages : elle permet de créer un texte dynamique (grâce à l'affichage de variables modélisées par l'argument ...), et elle nous fait de la place sur le disque dur comparée à sa frangine :p .

Alors, analysons cette fonction :

Ce qui donnerait :

#include <PA9.h>
 
int main(int argc, char **argv)
{
    const char *pseudo="PODS";
 
    PA_Init();
 
    PA_LoadDefaultText(1,0);//on initialise l'écran du haut à la première couche
    PA_LoadDefaultText(0,3);//on initialise l'écran tactile à une couche peu profonde (on peut se le permettre ici, on n'affichera qu'un texte)
 
    //Le texte ne changeant pas, pas besoin de le mettre dans la boucle
    PA_OutputText(1, 2,  2, "Hello World !");
    PA_OutputText(0,5,5,"Mon pseudo est %s.",pseudo);
 
    while(1)
    {
        PA_WaitForVBL();
        //Boucle infinie (inutile de l'utiliser puisqu'il n'y aura pas de changement)
    }
    return 0;
}

Avec No$GBA :

Image utilisateur
#include <PA9.h>
 
int main(int argc, char **argv)
{
    int compteur=0;
 
    PA_Init();
 
    PA_LoadDefaultText(0,0);
 
    for(compteur = 0; compteur < 2147483647; compteur++)//On limite à la taille d'un int, c'est suicidaire mais en même temps ce n'est qu'un exemple
    {
        PA_OutputText(0,0,0,"%d",compteur);
        PA_WaitForVBL();
        PA_ClearTextBg(0);
        /* Il faudrait attendre plus longtemps pour éviter que le texte ne clignote, mais comme je l'ai dit ce n'est qu'un exemple ;) */
    }
    return 0;
}

Tadaaaa, nous y sommes arrivés :D !

Nous allons voir comment utiliser le stylet et les touches...

Les événements

Essayez de créer un jeu sans gérer l'appui des touches et le stylet... Difficile, hein ? :D Nous allons donc nous attaquer à cette tâche qui, comme nous allons le voir, est trop très facile !

Configurer l'émulateur

On va d'abord configurer les touches de no$gba, de DeSmuMe et de iDeaS. Si vous avez un autre émulateur, il faudra vous débrouiller ! :p

Pour no$gba

Faites F11 ou Options->Controls Setup.
Regardez la colonne de droite. Cliquez dans l'espace blanc à droite de la commande (ex : Up) et appuyez sur la touche de clavier désirée pour appeler cette commande.

Image utilisateur

Configuration de snake_48 :-°

Pour DeSmuMe

Faites Config->Control Config.
Cliquez dans le menu déroulant à droite de la commande et appuyez sur la touche de clavier désirée pour appeler cette commande.

Pour iDeaS

Faites Ctrl+K ou Options->Key Config.
Appuyez sur le bouton correspondant à la commande et appuyez sur la touche du clavier désirée pour l'appeler.

Les touches

Les touches sont représentées par la structure nommée "Pad". Cette structure en renferme d'autres :

Et chacune de ces structures-membres ont des membres correspondant aux touches :

Ces membres renvoient 0 ou 1 en fonction de la structure-membre qu'on appelle et, bien sûr, de l'action du joueur ;) . On peut ainsi avoir ceci :

/*Mettre le début du code*/
while(1)
{
    if(Pad.Newpress.A==1) //Si l'utilisateur fait un NOUVEL appui sur A
    /* instruction */
 
    else if(Pad.Held.R==1) //Si l'utilisateur laisse son doigt appuyé sur la touche R
    /* instruction */
 
    else if(Pad.Released.X==1) //Si l'utilisateur relâche la touche X
    /* instruction */
 
    PA_WaitForVBL();
}
/*Mettre la fin du code*/
if(Pad.Newpress.A==1)

Par :

if(Pad.Newpress.A)

Ainsi que :

if(Pad.Newpress.Y==0)

Par :

if(!Pad.Newpress.Y)

Mais je pense que l'utilisation des booléens est déjà acquise pour la plupart ;) .

Le stylet !

Vous vous demandiez tous "Comment gère-t-on le stylet ?", eh bien ouvrez grand vos yeux !

Image utilisateur

Le stylet est appelé par la structure "Stylus".
Celle-ci contient cinq membres : X et Y (vous avez sûrement deviné que ceci renvoie les coordonnées du stylet), Newpress, Held et Released :

/*Mettre le début du code*/
PA_LoadDefaultText(0,0);
while(1)
{
    PA_OutputText(0,0,0,"( %d ; %d )    ", Stylus.X, Stylus.Y);
    PA_WaitForVBL();
}
/*Mettre la fin du code*/

Avouez tout de même qu'on ne pouvait pas faire plus simple :D !

Le DS Motion Pak

Le DS Motion quoi ? o_O

Vous avez très bien lu, le DS Motion Pak (non il ne manque pas un "c" :p ).
Qu'est-ce que c'est ? Il s'agit d'un accéléromètre (un appareil qui mesure les mouvements de l'objet sur lequel il est fixé) pour votre Nintendo DS. Il se met dans le SLOT 2 (port GBA) et ressemble à ceci (cliquez pour agrandir) :

Image utilisateur

Il existe une version qui se met dans le SLOT 1 (port NDS),que je n'utiliserai pas dans ce tutoriel, appelé DS Motion Card (pour ceux qui mettent leurs homebrews sur un linker GBA) :

Image utilisateur

Bien sûr, ce n'est pas gratuit >_ . En effet, il faut compter 30 à 40 € pour l'un ou l'autre.
Mais je ne regrette pas l'achat. On peut contrôler des éléments, et tout ça sans bouton (donc ça nous libère des possibilités pour créer un jeu certes moins compatible, mais plus poussé). De plus, ça met une ambiance à la Wii ^^ . Un conseil, évitez de vous faire filmer quand vous jouez à un jeu nécessitant le Motion :-° ...

Maintenant que les présentations sont faites, je vais vous annoncer une bonne nouvelle : PALib implémente totalement le Motion !

Tout d'abord, il faut initialiser le système Motion avec void PA_MotionInit(void); .
Ensuite, il faut savoir si le DS Motion Pak est inséré. Pour ça il existe la fonction u8 PA_CheckDSMotion(); . Cette fonction retourne 0 si le DS Motion Pak n'est pas inséré ou 1 s'il l'est.

Et comment faire pour récupérer les données de l'accéléromètre ?

Il existe une variable Motion de type motion_struct.
Voici ses attributs :

Sachez que dans tous les codes utilisant Motion, seuls les membres X et Y sont utilisés. Vous pouvez cependant tester les membres avec PA_OutputText en tournant et en déplaçant votre Nintendo DS dans tous les sens :D !

Attendre un événement

Vous avez sûrement vu les fausses valeurs indiquées pour les coordonnées du stylet lorsque l'on n'a pas encore touché l'écran.
On va utiliser PA_WaitFor(evenement) prenant pour argument l'événement à attendre.

Voici un petit exemple :

#include <PA9.h>
 
int main(int argc, char ** argv)
{
    PA_Init();
        
    PA_LoadDefaultText(1,0);
 
    while(1)
    {
        PA_WaitFor(Stylus.Held); //Si on appuie ou si on reste appuyé
        PA_OutputText(1,0,0,"( %d ; %d )", Stylus.X, Stylus.Y);
        PA_ClearTextBg(1);
    }
    return 0;
}

On se retrouve quand même avec de fausses valeurs ! o_O Comment faut-il faire ?

Bonne question ^^ , en fait il est toujours bon de mettre 3 ou 4 rafraîchissements (PA_WaitForVBL(); ) avant le la boucle principale, pour "dérouiller" en quelque sorte la NDS :

#include <PA9.h>

int main(int argc, char ** argv)
{	
    PA_Init();

    PA_LoadDefaultText(1,0);

    PA_WaitForVBL();PA_WaitForVBL();PA_WaitForVBL();

    while(1)
    {
        PA_WaitFor(Stylus.Held);
        PA_ClearTextBg(1);
        PA_OutputText(1,0,0,"( %d ; %d )", Stylus.X, Stylus.Y);
    }
    return 0;
}

Mais vous pouvez aussi initialiser le stylet comme ceci par exemple :

Stylus.X=0;
Stylus.Y=0;

Exercice

Pour vous faire pratiquer avant le TP, je vous propose un petit exercice :) .

Présentation

À vous de jouer ! ;)

Correction

#include <PA9.h>
 
int main(int argc, char ** argv)
{
    PA_Init();
        
    PA_InitText(1,0);
 
    while(1)
    {
        if(Pad.Held.B)
        {
            PA_OutputText(1,0,0,"Utilise le stylet !");
            if(Stylus.Held)
            {
                PA_ClearTextBg(1); //On efface l'écran
                PA_OutputText(1,0,0,"x : %d, y: %d",Stylus.X,Stylus.Y);
            }
        }
        PA_WaitForVBL();
        PA_ClearTextBg(1); //On efface l'écran
    }
    return 0;
}

C'était pas si dur, et un jeu est composé de beaucoup d'événements !

TP: Plus ou Moins, votre premier jeu

Hé hé ! Si vous pensiez vous être débarrassés de ce maudit TP après la première partie du cours de M@teo21, détrompez-vous ! Il est de retour et désormais il compte bien s'attaquer à votre Nintendo DS ! :D

Préparatifs

Gestion du nombre aléatoire

Cette fois-ci nous n'allons pas utiliser time.h puisque ça ne fonctionnera pas avec la DS, il faut utiliser une autre fonction qui utilise l'horloge interne de la console, sans que vous ayez besoin de connaître les éléments qui permettent de récupérer l'heure. Nous allons juste utiliser des fonctions qui nous facilitent la vie en récupérant déjà l'heure.

Tout d'abord dire à la console que nous allons lui demander un nombre aléatoire :

u32 PA_InitRand ();

Et utiliser la fonction qui renvoie le nombre:

u32 PA_RandMax (u32 max);

Cette fonction renvoie un nombre en 0 et max INCLUS.

Voilà une autre fonction utile :

u32 PA_RandMinMax (u32 min, u32 max);

Vous remarquerez que la fonction renvoie et demande des u32 (32 bits), nos variables pourront être de type int ou long, il n'y a pas de soucis de ce coté. ;)

La gestion du clavier

Ne vous êtes-vous pas demandés comment diable l'utilisateur rentrera-t-il son nombre ? La solution : utiliser le clavier de la Nintendo DS bien sûr ;) !

Initialisation du clavier

Il faut d'abord initialiser le clavier avec :

void PA_InitKeyboard(numero_bg);

Apparition du clavier

Sachez que le clavier part du bas pour venir à la position voulue. Il faut juste que vous rentriez les coordonnées idéales du clavier pour voir une belle animation.
On utilisera :

void PA_KeyboardIn (s16 x, s16 y);

Mais le clavier vient du bas et un seul mouvement est possible (du bas vers le haut).
Je vais vous donner une fonction toute faite pour faire un déplacement comme vous le voulez :

//PROTOTYPE
void defiler_clavier(int,int,int,int,int);
//FONCTION
void defiler_clavier(int x,int y,int x1,int y1,int nvbl)
{
    /*Le clavier va de (x;y) à (x1;y1) avec nvbl VBL pour chaque étape du déplacement*/
    int i=0;
    PA_ScrollKeyboardXY(x,y); //On met le clavier à la position (x;y)
    while(1)
    {
        /*On se rapproche des coordonnées (x1;y1)*/
        if(x<x1)
            x++;
        else if(x>x1)
            x--;
        if(y<y1)
            y++;
        else if(y>y1)
            y--;
        if(x==x1 && y==y1)
            break;
        PA_ScrollKeyboardXY(x,y); //On replace le clavier à la nouvelle position
        /*On attend en fonction du nombre de VBL donné par la variable nvbl*/
        for(i=0;i<nvbl;i++)
            PA_WaitForVBL();
    }
}

Le clavier passera de (x;y) à (x1;y1) et à chaque tour de boucle, il y aura nvbl PA_WaitForVBL.

Traitement du clavier

Ça, c'est une toute autre histoire ! :p
Il faut opérer en ce sens :

Bon, côté interface, on écrira tout sur l'écran supérieur.
En premier, la console affichera :

Entrez un nombre entre 1 et 100 inclus :

Au-dessous, (ligne 3) vous écrirez l'entrée du joueur.
Si le nombre entré est plus petit que le nombre à trouver, on effacera l'écran supérieur pour écrire :

Le nombre %s est trop petit. Veuillez attendre 2 secondes...

Où %s est la chaîne entrée par l'utilisateur.
Si le nombre est trop grand, on écrira :

Le nombre %s est trop grand. Veuillez attendre 2 secondes...

Et si le nombre est trouvé, on écrira :

Bravo ! Vous avez réussi à trouver le nombre %s en %d tentative%s ! Veuillez attendre 2 secondes...

Où %d est le nombre de tentatives et le 2ème %s est le résultat d'une ternaire pour savoir si on met le pluriel ou pas.
N'oubliez pas de faire l'attente pour les 2 secondes.
Au passage, on effacera l'écran à chaque tour de boucle (si on efface le dernier caractère, la mise à jour sera automatique). Et si le joueur a trouvé le nombre, on quitte la boucle (utiliser while(1) avec un break lorsque l'objectif est atteint, afin d'attendre que l'utilisateur appuie sur Entrée). Et, après celle-ci, on affichera :

Bravo ! Vous avez réussi à trouver le nombre %s en %d tentative%s !

Disparition du clavier

La simple fonction

void  PA_KeyboardOut (void);

nous "rangera" automatiquement notre clavier ;) . Si vous ne voulez pas l'utiliser, vous pouvez utiliser notre fonction defiler_clavier.

Customisation du clavier :)

Vous pouvez personnaliser votre clavier. Vous devez créer d'abord un fichier image qui contiendra votre clavier. Ce fichier mesurera 256*1024 pixels. Aux coordonnées (0;0) dessinez votre clavier qui doit mesurer 208x96 pixels. La contrainte est que les touches doivent être de même dimensions que le clavier par défaut (il y a un exemple ci-dessous pour que vous puissiez bien voir les dimensions). Aux coordonnées (0;512) dessinez votre clavier (quand le joueur appuiera sur la touche Shift ou Caps).
Voici un exemple de clavier :

Keyboard personnalisé^^Keyboard personnalisé^^

Il s'agit de l'image devkitPro\PAlibExamples\Input\Keyboard\KeyboardCustom\source\gfx ^^ .
Bien sûr, il faut l'ajouter à votre dossier gfx comme ceci :

Citation

#TranspColor Magenta

#Sprites :

#Backgrounds :
clavier.png 16colors

Pour utiliser un clavier personnalisé, on utilise

void PA_InitCustomKeyboard(numero_bg, fichier_contenant_votre_clavier);

Voilà, vous savez tout à présent sur les claviers ;) .

Correction

Voici une correction parmi d'autres :) :

#include <PA9.h>

void defiler_clavier(int,int,int,int,int);

int main(int argc, char ** argv)
{
    int nombre,entre=0,i,compteur=0;
    char lettre;

    /*INITIALISATIONS*/
    PA_Init();

    PA_LoadDefaultText(1,0);
    PA_InitRand(); //On initialise le Rand
    PA_InitKeyboard(2); //On initialise le clavier sur le fond 2

    defiler_clavier(50,48,25,96,1); //Un défilement du clavier pour le faire apparaître, vous pouvez le changer
    nombre=PA_RandMinMax(1,100); //La variable nombre contient un nombre compris entre 1 et 100 inclus

    while(1) //Tant que le nombre entré par le joueur est différent de la variable nombre
    {
        PA_OutputText(1,0,0,"Entrez un nombre entre 1 et 100 inclus :");
        lettre=PA_CheckKeyboard(); //On récupère la valeur numérique de l'entrée de l'utilisateur
        if(lettre>='0'&&lettre<='9'&&entre<100) //Il faut que la touche pressée soit comprise entre 0 et 9 (et rien d'autre), et que le nombre entré soit inférieur à 100, car le nombre maximum est 100
        {
            entre*=10; //On décale tous les chiffres d'un cran vers la gauche (une dizaine)
            entre+=lettre-'0'; //On ajoute les nouvelles "unités"
        }
        else if(lettre==PA_BACKSPACE) //Si on appuie sur Backspace
            entre/=10; //On décale tous les chiffres d'un cran vers la droite
        else if(lettre=='\n') //Si on appuie sur l'équivalent de la touche Entrée
        {
            compteur++; //Le compteur de tentatives est augmenté
            PA_ClearTextBg(1); //On efface le texte de l'écran supérieur (celui où sont écrits tous les textes à afficher)
            if(entre<nombre)
            {
                PA_OutputText(1,0,0,"Le nombre %d est trop petit. Veuillez attendre 2 secondes...",entre);
                entre=0;
            }
            else if(entre>nombre)
            {
                PA_OutputText(1,0,0,"Le nombre %d est trop grand. Veuillez attendre 2 secondes...",entre);
                entre=0;
            }
            else
                break;
            for(i=0;i<120;i++)
                PA_WaitForVBL(); //On attend 2 secondes
        }
        if(entre!=0) //On écrit pas le nombre s'il est "vide"
            PA_OutputText(1,0,3,"%d",entre); //On laisse le nombre affiché
        PA_WaitForVBL();
        PA_ClearTextBg(1); //On efface l'écran supérieur
    }
    PA_OutputText(1,0,0,"Bravo ! Vous avez réussi à trouver le nombre %d en %d tentative%s !",entre,compteur-1,compteur-1<2?"":"s"); //Ternaire pour vérifier si, en fonction de compteur-1, on doit mettre du singulier ou du pluriel. Et je mets compteur-1 car je fais cadeau de la bonne tentative
    PA_KeyboardOut(); //On range notre clavier

    while(1)
        PA_WaitForVBL();

    return 0;
}

void defiler_clavier(int x,int y,int x1,int y1,int nvbl)
{
    /*Le clavier va de (x;y) à (x1;y1) avec nvbl VBL pour chaque étape du déplacement*/
    int i=0;
    PA_ScrollKeyboardXY(x,y); //On met le clavier à la position (x;y)
    while(1)
    {
        /*On se rapproche des coordonnées (x1;y1)*/
        if(x<x1)
            x++;
        else if(x>x1)
            x--;
        if(y<y1)
            y++;
        else if(y>y1)
            y--;
        if(x==x1&&y==y1)
            break;
        PA_ScrollKeyboardXY(x,y); //On replace le clavier à la nouvelle position
        /*On attend en fonction du nombre de VBL donné par la variable nvbl*/
        for(i=0;i<nvbl;i++)
            PA_WaitForVBL();
    }
}

Merci à daminator pour son optimisation du code !

Améliorations

On peut :

On va créer plusieurs niveaux de jeu ^^ . Ce sera utile pour la suite... Ça déborde un peu sur la deuxième partie mais ce n'est pas grave, on va faire un menu très léger. On a 3 choix : un nombre entre 1 et 100 ; entre 1 et 500 et enfin entre 1 et 1000.
Le choix courant sera en rouge par exemple, et les autres seront écrits dans la couleur par défaut.
Bien sûr, il faudra créer une variable max qui sera affichée pour Entrez un nombre entre 1 et [...] inclus.
Le mieux est de créer 2 boucles (une pour le menu, l'autre pour le jeu).
Voilà, vous avez assez de renseignements pour le coder tout seul ^^ .
Mais voici mon amélioration ^^ :

#include <PA9.h>

void defiler_clavier(int,int,int,int,int);

int main(int argc, char ** argv)
{
    int nombre,entre=0,i,compteur=0,max,menu=0;
    char lettre;

    PA_Init();

    PA_LoadDefaultText(1,0);
    PA_InitRand();

    //Boucle de menu
    while(!Pad.Held.A&&!Pad.Held.Start)
    {
        switch(menu)
        {
            case 0:
                PA_OutputText(1,5,9,"%c11 -> Entre 1 et 100");
                PA_OutputText(1,5,11,"2 -> Entre 1 et 500");
                PA_OutputText(1,5,13,"3 -> Entre 1 et 1000");
                break;
            case 1:
                PA_OutputText(1,5,9,"1 -> Entre 1 et 100");
                PA_OutputText(1,5,11,"%c12 -> Entre 1 et 500");
                PA_OutputText(1,5,13,"3 -> Entre 1 et 1000");
                break;
            default:
                PA_OutputText(1,5,9,"1 -> Entre 1 et 100");
                PA_OutputText(1,5,11,"2 -> Entre 1 et 500");
                PA_OutputText(1,5,13,"%c13 -> Entre 1 et 1000");
                break;
        }
        menu+=Pad.Newpress.Down-Pad.Newpress.Up;
        if(menu<0)
            menu=2;
        else if(menu>2)
            menu=0;
        PA_WaitForVBL();
        PA_ClearTextBg(1);
    }

    switch(menu)
    {
        case 0:
            nombre=PA_RandMinMax(1,100);
            max=100;
            break;
        case 1:
            nombre=PA_RandMinMax(1,500);
            max=500;
            break;
        default: //Il est bon de mettre un defalut, car si le choix n'est pas 3 (un bug ou alors un problème dans le code), une valeur est quand-même attribuée à nombre et à max. De plus, pour être désolé de ce problème, la valeur par défaut est celle comprise entre 1 et 1000 ! *rire sadique*
            nombre=PA_RandMinMax(1,1000);
            max=1000;
            break;
    }

    PA_InitKeyboard(2);
    defiler_clavier(50,48,25,96,1);

    //Boucle du jeu
    while(1)
    {
        PA_OutputText(1,0,0,"Entrez un nombre entre 1 et %d inclus :",max);
        lettre=PA_CheckKeyboard();
        if(lettre>='0'&&lettre<='9'&&entre<=max)
        {
            entre*=10;
            entre+=lettre-'0';
        }
        else if(lettre==PA_BACKSPACE)
            entre/=10;
        else if(lettre=='\n') 
        {
            compteur++;
            PA_ClearTextBg(1);
            if(entre<nombre)
            {
                PA_OutputText(1,0,0,"Le nombre %d est trop petit. Veuillez attendre 2 secondes...",entre);
                entre=0;
            }
            else if(entre>nombre)
            {
                PA_OutputText(1,0,0,"Le nombre %d est trop grand. Veuillez attendre 2 secondes...",entre);
                entre=0;
            }
            else
                break;
            for(i=0;i<120;i++)
                PA_WaitForVBL();
        }
        if(entre!=0)
            PA_OutputText(1,0,3,"%d",entre);
        PA_WaitForVBL();
        PA_ClearTextBg(1); //On efface l'écran supérieur
    }
    PA_OutputText(1,0,0,"Bravo ! Vous avez réussi à trouver le nombre %d en %d tentative%s !",entre,compteur-1,compteur-1<2?"":"s");
    PA_KeyboardOut(); //On range notre clavier

    while(1)
        PA_WaitForVBL();

    return 0;
}

void defiler_clavier(int x,int y,int x1,int y1,int nvbl)
{
    int i=0;
    PA_ScrollKeyboardXY(x,y);
    while(1)
    {
        if(x<x1)
            x++;
        else if(x>x1)
            x--;
        if(y<y1)
            y++;
        else if(y>y1)
            y--;
        if(x==x1&&y==y1)
            break;
        PA_ScrollKeyboardXY(x,y);
        for(i=0;i<nvbl;i++)
            PA_WaitForVBL();
    }
}

Voilà, vous avez assez de bases maintenant pour faire de petits jeux en console sur votre Nintendo DS !

La 2D de base

Les jeux de DS sont autre chose que des écrans noirs :lol: . Nous allons donc apprendre, à ce stade, à créer des arrière-plans et des sprites.

Et pourquoi pas des Coca-Cola ? :-°

Les sprites ne se boivent pas, mais ce sont des images comme celle du personnage principal d'un jeu, par exemple.

Outils nécessaires

On va travailler en 2D.
Pour cela, rendez-vous dans devkitpro\PAlib\Tools\PAGfx et copiez PAGfx.exe.

Allez dans le dossier de votre jeu et créez-y un répertoire nommé gfx et copiez-y l'exécutable. Dans ce même dossier, créez un fichier nommé PAGfx.ini, il contiendra les instructions pour convertir vos images.

Syntaxe du fichier PAGfx.ini

Ce fichier doit être composé de 3 parties commençant par un dièse (#) :

Exemple de PAGfx.ini :

Citation

#TranspColor Magenta

#Sprites :
hero.PNG 256colors sprites
jouer.PNG 256colors sprites
njeu.PNG 256colors sprites
fleche.PNG 256colors sprites
oui.PNG 256colors sprites
non.PNG 256colors sprites

#Backgrounds :
newfont.GIF TileBg
fontscreen1.GIF TileBg
bg_menu.PNG LargeMap
bg1.PNG LargeMap
confirmer.PNG EasyBg

Les arrière-plans

On va s'attaquer aux arrière-plans. :pirate:
Après avoir utilisé PAGfx, il faut coder (on est bien là pour ça :lol: ).
Au code minimal, il faut rajouter, dans la partie des includes :

#include "all_gfx.h"

Ce simple header permet d'inclure les images que vous avez faites auparavant.

Code pour EasyBg

Voici la fonction PA_LoadBackground :

void PA_LoadBackground(u8 ecran,u8 numero_bg,const PA_BgStruct* nom_bg);
#include <PA9.h>
#include "all_gfx.h"
 
int main (int argc, char **argv)
{
    PA_Init();
    
    PA_LoadBackground(0,1,&bg);

    while (1)
    {
        PA_WaitForVBL();
    }
   
    return 0;
}

Nous allons nous amuser à faire défiler l'arrière-plan (scroll). Voici le prototype de la fonction PA_EasyBgScrollXY :

void PA_EasyBgScrollXY(u8 ecran,u8 numero_bg,s32 x,s32 y);

Voici le code pour un petit défilement :

#include <PA9.h>
#include "all_gfx.h"
 
int main (int argc, char **argv)
{
    int scrollx=0,scrolly=0;
    PA_Init();
    
    PA_LoadBackground(0,1,&bg);
    while (1)
    {
        scrollx++;
        scrolly++;
        PA_EasyBgScrollXY(0,1,scrollx,scrolly);
        PA_WaitForVBL();
    }
    return 0;
}

Code pour TileBg (seulement pour les polices)

Voici la fonction de PA_LoadText :

void PA_LoadText(u8 ecran, u8 numero_bg, const PA_BgStruct* police);
#include <PA9.h>
#include "all_gfx.h"
 
int main(int argc, char ** argv)
{
    PA_Init();
        
    PA_LoadText(1,0,&police);
    PA_OutputText(1,0,0,"Pas mal, la nouvelle police :) ");
    while (1)
    {
        PA_WaitForVBL();
    }
    return 0;
}

Les sprites

A présent, passons aux sprites.
Il faut toujours inclure :

#include "all_gfx.h"

Code pour sprites standards

Tout d'abord, il faut charger la palette de sprites. On va utiliser cette fonction :

void PA_LoadSpritePal(u8 ecran,u8 numero_palette,(void*)fichier_palette_cree_par_PAGfx);

Mais comment connaître Fichier_palette_cree_par_PAGfx ?

C'est simple, supposons que votre PAGfx.ini comporte :

Citation

#TranspColor Magenta

#Sprites :
mario.PNG 256colors sprites
wario.PNG 256colors sprites

#Backgrounds :

Le troisième mot est la palette. Pour le code, il suffit de rajouter _Pal.
Ce qui donne :

#include <PA9.h>
#include "all_gfx.h"
 
int main(int argc, char ** argv)
{
    PA_Init();
 
    PA_LoadSpritePal(0,0,(void*)sprites_Pal);
 
    while(1)
    {
        PA_WaitForVBL();
    }
    return 0;
}

Et maintenant, on va créer le sprite avec PA_CreateSprite :

void PA_CreateSprite(u8 ecran,u8 numero_sprite,(void*)nom_sprite_cree_par_PAGfx,u8 forme,u8 taille,u8 mode_couleurs,u8 palette,s16 position_x,s16 position_y);

Pour obtenir Nom_sprite_cree_par_PAGfx, il faut regarder ça dans PAGfx.ini :

Citation

#TranspColor Magenta

#Sprites :
mario.PNG 256colors sprites
wario.PNG 256colors sprites

#Backgrounds :

Il s'agit en fait du nom des images sans l'extension.
À ces noms, il faut rajouter _Sprite.
Il y a plusieurs tailles différentes (variables forme et taille), voici la liste complète :

Et si mon image faisait 20*10 ? Il n'existe pas de constante adaptée...

Il suffit de prendre la constante qui est juste au-dessus de la valeur (hauteur ou largeur) la plus grande. Ici, 20 est la plus grande valeur, donc il faudra créer un sprite de 32*16, et les pixels qui ne seront pas utilisés (après 20 dans les x et après 10 dans les y) devront être de la couleur transparente définie dans PAGfx.ini.

Gnein ? o_O

Bon, je vais faire une image :

Image utilisateur

C'est un rectangle de 20*10, qui doit être mis en 32*16 ou plus.

Et si une des valeurs de mon image dépassait 64 ?

Vous avez trois solutions :

Ensuite, mode_couleur peut prendre deux valeurs : soit 0 ; soit 1.

Mais comment on fait pour savoir s'il faut mettre 1 ou 0 ?

Il faut regarder :

Citation

#TranspColor Magenta

#Sprites :
mario.PNG 256colors sprites
wario.PNG 256colors sprites

#Backgrounds :

Il faut mettre 1 pour 256 couleurs, et 0 pour 16 couleurs.
Palette est le numéro de la palette (définie plus haut).
Voici un code récapitulatif (avec notre rectangle :p ) :

#include <PA9.h>
#include "all_gfx.h"
 
int main(int argc, char ** argv)
{
    PA_Init();
 
    PA_LoadSpritePal(0,0,(void*)sprites_Pal);
    PA_CreateSprite(0,0,(void*)rectangle_Sprite,OBJ_SIZE_32X16,1,0,0,0);
    /* On crée un sprite sur l'écran tactile (0), son numéro est 0, c'est un sprite semblable à rectangle.png
       Vu qu'il fait 20*10, il faut le mettre en 32*16, 256 couleurs donc 1, utilise la palette 0, sa position dans
       les X est 0, comme dans les y. */
 
    while(1)
    {
        PA_WaitForVBL();
    }
    return 0;
}

On peut changer les coordonnées d'un sprite avec PA_SetSpriteXY :

void PA_SetSpriteXY(ecran,numero_sprite,position_x,position_y);

Voici un petit jeu :

#include <PA9.h>
#include "gfx/all_gfx.h"
 
int main(int argc, char ** argv)
{
    int x=100,y=100;
    PA_Init();
 
    PA_LoadSpritePal(0,0,(void*)sprites_Pal);
    PA_CreateSprite(0,0,(void*)rectangle_Sprite,OBJ_SIZE_32X16,1,0,0,0);
 
    while(1)
    {
        PA_SetSpriteXY(0,0,x,y);
        PA_WaitForVBL();
    }
    return 0;
}

Si vous devez gérer une IA, et que chaque élément contrôlé par ordinateur (ou par vous) est le même, vous pouvez utiliser la macro PA_CloneSprite(ecran,numero_du_sprite_a_creer,numero_du_sprite_qui_va_etre_cloné) pour cloner le sprite que vous voulez. Ainsi, si vous faites PA_CloneSprite(0,1,0); , vous créerez, dans l'écran 0, un clone du sprite 0 ayant pour numéro 1.
Vous pouvez aussi libérer la mémoire en détruisant des sprites qui ne servent plus à rien avec :

void PA_DeleteSprite(u8 ecran,u8 numero_sprite);

C'était un gros chapitre, qui pose les bases de la 2D, le suivant nous permettra de découvrir d'autres fonctions et utilités de la 2D ! :)

La 2D avancée

Nous allons voir comment créer des sprites qui vont sur les deux écrans (DualSprites), des sprites animés, des arrière-plans sur les deux écrans, ainsi que des rotations et des zooms.

Les arrière-plans

Je savais que vous vous rueriez sur cette partie pour faire de jolis arrière-plans sur les deux écrans simultanément :p .
De plus en plus de monde en raffole :) !
Sachez que nous utiliserons toujours des LargeBg pour faire des arrière-plans sur les deux écrans...
On va donc regarder la fonction PA_DualLoadBackground :

void PA_DualLoadBackground(u8 numero_bg, const PA_BgStruct* bg);

Voici un code complet :

#include <PA9.h>
#include "all_gfx.h"
 
int main (int argc, char **argv)
{
    PA_Init();
    
    PA_DualLoadBackground(1,&bg);
    while (1)
    {
        PA_WaitForVBL();
    }
   
    return 0;
}

Pour le faire défiler, c'est simple, on utilisera cette fonction :

void PA_DualEasyBgScrollXY(u8 bg_select, s32 x, s32 y);

On en tirera donc ce code :

#include <PA9.h>
#include "all_gfx.h"
 
int main (int argc, char **argv)
{
    int scrollx=0,scrolly=0;
    PA_Init();
    
    PA_DualLoadBackground(1,bg);
    while (1)
    {
        scrollx++;
        scrolly++;
        PA_DualEasyBgScrollXY(1,scrollx,scrolly);
        PA_WaitForVBL();
    }
   
    return 0;
}

Vous pouvez aussi modifier le nombre de pixels de décalage (dans les y) entre les deux écrans avec :

void PA_SetScreenSpace(s16 nombre_de_pixels);

Si vous voulez inverser les deux écrans, vous pouvez utiliser :

void PA_SwitchScreens(void);

Les sprites

Les DualSprites

Les sprites utilisables sur les deux écrans s'appellent les DualSprites.
Pour les DualSprites, vous n'avez pas besoin d'utiliser une autre syntaxe que les sprites en ce qui concerne PAGfx.ini.

D'abord, il faut charger la palette, mais à la différence des sprites normaux, on va utiliser PA_DualLoadSpritePal :

void PA_DualLoadSpritePal(u8 numero_palette,(void*)palette_cree_par_PAGfx);

Pour charger un DualSprite, nous utiliserons PA_DualCreateSprite :

void PA_DualCreateSprite(u8 numero_sprite,(void*)sprite_cree_par_PAGfx,u8 forme,u8 taille,u8 mode_couleurs,u8 numero_palette,u8 position_x,u8 position_y);

Voici un code complet :

#include <PA9.h>
#include "all_gfx.h"
 
int main(int argc, char ** argv)
{
    PA_Init();
 
    PA_DualLoadSpritePal(0,(void*)sprites_Pal);
    PA_DualCreateSprite(0,(void*)rectangle_Sprite,OBJ_SIZE_32X32,1,0,0,0);
 
    while(1)
    {
        PA_WaitForVBL();
    }
    return 0;
}

Pour déplacer des DualSprites, on va utiliser PA_DualSetSpriteXY :

void PA_DualSetSpriteXY(u8 numero_sprite,s16 position_x,s16 position_y);

Et un nouveau code ;) :

#include <PA9.h>
#include "all_gfx.h"
 
int main(int argc, char ** argv)
{
    int x=100,y=100;
    PA_Init();
 
    PA_DualLoadSpritePal(0,(void*)sprites_Pal);
    PA_DualCreateSprite(0,(void*)rectangle_Sprite,OBJ_SIZE_32X32,1,0,0,0);
 
    while(1)
    {
        y++;
        PA_DualSetSpriteXY(0,x,y);
        PA_WaitForVBL();
    }
    return 0;
}

Vous pouvez cloner des DualSprites avec :

void PA_DualCloneSprite(u8 numero_sprite_a_creer,u8 numero_sprite_cloné);

Ou encore les supprimer :

void PA_DualDeleteSprite(u8 numero_sprite);

Les animations de Sprites

Animations de sprites standards

Vous aurez sûrement besoin d'animer les sprites. Par exemple, si vous avez un personnage à faire marcher, il doit bouger :) !
Bon, pour cette partie, on va prendre notre joli rectangle ;) . L'animation sera la coloration de celui-ci.

Mais comment on va s'y prendre ?

On va créer une planche de sprites. En fait, on va multiplier la hauteur l'image contenant notre sprite par le nombre de frames (état d'une animation). Si on veut faire 3 frames (rectangle jaune, rectangle rouge et rectangle noir), il faudra avoir créé un sprite de 32*96 (puisque 32*3=96).
Voici notre planche de sprites (contenant 3 frames) :

Image utilisateur

Après avoir chargé la palette et le sprite, on va l'animer avec PA_StartSpriteAnim :

void PA_StartSpriteAnim(u8 ecran,u8 numero_sprite,s16 frame_de_depart,s16 frame_d_arrivee,s16 fps);

Fps est le nombre de frames par seconde.
Voici un code complet :

#include <PA9.h>
#include "all_gfx.h"
 
int main(int argc, char ** argv)
{
    PA_Init();
 
    PA_LoadSpritePal(0,0,(void*)sprites_Pal);
    PA_CreateSprite(0,0,(void*)rectangle_Sprite,OBJ_SIZE_32X32,1,0,0,0);
 
    PA_StartSpriteAnim(0,0,0,2,1);
    while(1)
    {
        PA_WaitForVBL();
    }
    return 0;
}

Pour arrêter l'animation d'un sprite, on peut utiliser :

void PA_StopSpriteAnim(u8 ecran, u8 numero_sprite);

Pour choisir le type d'animation, on peut utiliser :

void PA_StartSpriteAnimEx(u8 ecran, u8 numero_sprite, s16 frame_de_depart, s16 frame_d_arrivee, s16 fps, u8 type, s16 nombre_de_cycles);

Les arguments sont les mêmes que PA_StartSpriteAnim , sauf que l'on rajoute type et nombre_de_cycles. La variable type est le type d'animation. Elle peut prendre pour valeur ANIM_UPDOWN (joue l'animation dans un sens puis dans l'autre) ou ANIM_LOOP (animation normale). La variable nombre_de_cycles correspond au nombre d'exécutions de l'animation. Elle peut prendre pour valeur -1 (infini) ou tout autre nombre positif (n boucles). Il y a une valeur spéciale, qui donne une valeur aux deux dernièrs paramètres (au lieu de 7 ; il n'y en a plus que 6 à fournir) : ANIM_ONESHOT (une seule fois), ce qui correspond à ANIM_LOOP pour type et à 1 pour nombre_de_cycles.

Pour choisir l'image de l'animation à afficher, on utilise :

void PA_SetSpriteAnimFrame(u8 ecran, u8 numero_sprite, s16 frame);

frame est le numéro de frame que l'on veut appliquer au sprite.

Pour savoir le numéro de frame courant d'un sprite, on utilise :

u16 PA_GetSpriteAnimFrame(u8 ecran, u8 numero_sprite);

La valeur retournée est un u16 !

Pour (re)définir la rapidité de l'animation d'un sprite, on peut utiliser :

void PA_SetSpriteAnimSpeed(u8 ecran, u8 numero_sprite, s16 fps);

Pour obtenir le nombre de FPS de l'animation d'un sprite, on utilise :

u16 PA_GetSpriteAnimSpeed(u8 ecran, u8 numero_sprite)

La valeur retournée est un u16 !

Pour définir le nombre d'exécutions de l'animation d'un sprite, on utilise :

void PA_SetSpriteNCycles(u8 ecran, u8 numero_sprite, s16 numero_de_cycles);

Pour obtenir le nombre d'exécutions restantes de l'animation d'un sprite, on peut utiliser :

u16 PA_GetSpriteNCycles(u8 ecran, u8 numero_sprite);

La valeur retournée est un u16 !

Pour mettre en pause ou reprendre l'animation d'un sprite, on utilise :

void PA_SpriteAnimPause(u8 ecran, u8 numero_sprite, u8 pause);

La variable pause a une fonction de booléen, donc il faut mettre 1 pour mettre en pause ou 0 pour reprendre l'animation.

Animations de DualSprites

Là aussi, il faut déjà avoir créé la DualPalette et le DualSprite. Ensuite, pour animer, c'est simple avec PA_DualStartSpriteAnim (non, ce n'est pas de la propagande :p ) :

void PA_DualStartSpriteAnim(u8 numero_sprite,s16 frame_de_depart,s16 frame_d_arrivee,s16 fps);

Et voici notre dernier code complet :

#include <PA9.h>
#include "all_gfx.h"
 
int main(int argc, char ** argv)
{
    PA_Init();
 
    PA_DualLoadSpritePal(0,(void*)sprites_Pal);
    PA_DualCreateSprite(0,(void*)rectangle_Sprite,OBJ_SIZE_32X32,1,0,0,0);
 
    PA_DualStartSpriteAnim(0,0,2,1);
    while(1)
    {
        PA_WaitForVBL();
    }
    return 0;
}

Toutes les fonctions énoncées pour les animations de sprites standards sont les mêmes avec les dualsprites sauf que l'on met Dual_ devant et que l'on enlève le paramètre ecran.

Faire des rotations et des zooms

Voilà : on a appris comment créer un sprite, charger sa palette, etc. mais on va s'attaquer aux techniques avancées des sprites : les rotations et les zooms :pirate: .

Les rotations

Les arrière-plans

Pour les arrière-plans, il faut mettre le mode vidéo de la Nintendo DS en 2 sur l'écran tactile avec PA_SetVideoMode(0,2);
Ensuite, au lieu de charger normalement l'arrière-plan, on va utiliser la macro :

void PA_LoadPAGfxRotBg(ecran,numero_bg,nom_bg,taille)

On connaît déjà à quoi correspondent les 3 premiers arguments, mais pas le dernier : taille. Il est défini par une autre macro. On distingue :

Il n'en existe pas d'autre ! Il faut donc que votre arrière-plan soit à une de ces dimensions : 128*128 ; 256*256 ; 512*512 ou 1024*1024 pixels.
Pour effectuer notre rotation (la fonction fait zoom aussi :-° ), nous allons utiliser :

void PA_SetBgRot(u8 ecran, u8 numero_bg, s32 defilement_x, s32 defilement_y, s32 defilement_centre_x, s32 defilement_centre_y, s16 angle, s32 zoom);

La mesure d'un angle est comprise entre 0 et 511 inclus. Pour convertir des degrés en unités d'angle de palib, il faut les multiplier par 511 puis à les diviser par 360, c'est juste une question de proportionnalité !

Pourquoi avoir comme arguments defilement_x/defilement_centre_x et defilement_y/defilement_centre_y ? Les arguments defilement_x et defilement_y suffisent, non ?

Je vais vous expliquer avec des images. Supposons que nous ayons cette image comme arrière_plan :

Citation : Légende

Rouge = defilement_x
Bleu = defilement_y
Vert = defilement_centre_x
Jaune = defilement_centre_y

Image utilisateur

Ensuite, voilà à quoi ça ressemble avec une rotation quelconque (par exemple 44°) :

Image utilisateur

Voilà, vous venez de vous rendre compte : malgré la rotation, defilement_centre_x et defilement_centre_y restent dans les mêmes axes, alors que defilement_x et defilement_y subissent la rotation de l'image...

Voici un exemple, avec la première image par exemple que vous pouvez renommer "bg" par exemple :

#include <PA9.h>

#include "gfx/all_gfx.c"
#include "gfx/all_gfx.h"

int main(int argc, char ** argv)
{
    PA_Init();
    PA_InitVBL();
    
    PA_SetVideoMode(0, 2);

    PA_LoadPAGfxRotBg(0,3,bg,1);
    
    PA_InitText(1, 0);
   
    s32 scrollx = 0;
    s32 scrolly = 0;
    s32 rotcenterx = 0;
    s32 rotcentery = 0;
    s16 angle = 0;
    
    PA_OutputSimpleText(1, 2, 3, "ScrollX    : Gauche/Droite");
    PA_OutputSimpleText(1, 2, 4, "Scrolly    : Haut/Bas");
    PA_OutputSimpleText(1, 2, 5, "RotCenterX : A/Y");
    PA_OutputSimpleText(1, 2, 6, "RotCenterY : B/X");
    PA_OutputSimpleText(1, 2, 7, "Angle      : R/L");


    while (1)
    {
        if(Pad.Held.Right)
            scrollx++;
        else if(Pad.Held.Left)
            scrollx--;
        else if(Pad.Held.Down)
            scrolly++;
        else if(Pad.Held.Up)
            scrolly--;
        else if(Pad.Held.A)
            rotcenterx++;
        else if(Pad.Held.Y)
            rotcenterx--;
        else if(Pad.Held.B)
            rotcentery++;
        else if(Pad.Held.X)
            rotcentery--;
        else if(Pad.Held.R)
            angle++;
        else if(Pad.Held.L)
            angle--;
        
        PA_SetBgRot(0, 3, scrollx, scrolly, rotcenterx, rotcentery, angle,256);

        PA_WaitForVBL();
    }
    
    return 0;
}

Les sprites

Après avoir créé votre sprite, il faut dire que l'on veut faire tourner un sprite.
On utilise :

void PA_SetSpriteRotEnable(ecran,sprite,rotset);

ecran : le numéro de l'écran ;
sprite : le numéro du sprite ;
rotset : le numéro du rotset. Il doit être compris entre 0 et 31 inclus. Les sprites ayant le même numéro de rotset subiront la même rotation.

Ensuite pour effectuer la rotation on utilise :

void PA_SetRotsetNoZoom(u8 ecran, u8 rotset, s16 angle);

angle : souvenez-vous que c'est la mesure d'un angle compris entre 0 et 511 inclus et que pour convertir des degrés en unités d'angle de palib, il faut les multiplier par 511 puis à les diviser par 360, c'est juste une question de proportionnalité ^^ .

Pour dire que l'on ne veut plus faire tourner un sprite, on utilise :

void PA_SetSpriteRotDisable(ecran, sprite);

Les zooms

Les arrière-plans

On a vu que la fonction qui fait des rotations d'arrière-plans fait aussi leur zoom.
L'indice de zoom peut être de 256 : pas de zoom (zoom x1), de 512 : 2x plus petit (zoom x0.5), de 128 : 2x plus grand (zoom x2), etc.

#include <PA9.h>

#include "gfx/all_gfx.c"
#include "gfx/all_gfx.h"

int main(int argc, char ** argv)
{
    PA_Init();
    PA_InitVBL();
    
    PA_SetVideoMode(0, 2);

    PA_LoadPAGfxRotBg(0,3,Rot,BG_ROT_256X256);
    
    PA_InitText(1, 0);
    
    s32 zoom=256;

    PA_OutputSimpleText(1, 2, 3, "Zoom : Haut/Bas");

    while (1)
    {
        if(Pad.Held.Up)
            zoom--;
        else if(Pad.Held.Down)
            zoom++;
        
        PA_SetBgRot(0, 3,0,0,0,0, 0,zoom);

        PA_WaitForVBL();
    }
    
    return 0;
}

Les sprites

Pour effectuer la rotation on utilise :

void PA_SetRotsetNoAngle(u8 ecran, u8 rotset, u16 zoomx, u16 zoomy);

Si vous voulez faire des rotations et des zooms en même temps, il faut dire que l'on veut faire des rotations comme décrit dans la partie sur les rotations et utiliser cette fonction raccourci :

void PA_SetRotset(u8 ecran, u8 rotset, s16 angle, u16 zoomx, u16 zoomy);

Exercice : sortez l'Aspirine !

On a accumulé beaucoup de connaissances en deux chapitres seulement ! Il est temps de les tester un faisant un petit jeu : le jeu de l'Aspirine...

Présentation du projet

On va créer un jeu (très bête :lol: ), dans lequel le joueur va diriger une pastille effervescente (pour guérir vos maux de tête engendrés par cet exo :) ) uniquement pour aller à gauche ou à droite.
Comme une vraie, elle devra descendre du haut de l'écran tactile et tomber au fond du verre (comme si on la lâchait dans un verre d'eau). Cette pastille dégagera des bulles de différentes tailles qui se dirigeront vers le haut (il y en a 255). Une fois toutes les bulles parties, la pastille remontera.

Notions à utiliser et préparatifs

Tout d'abord, on va apprendre à créer des nombres aléatoires. Il faut initialiser le système avec PA_InitRand(); sinon les nombres que vous aurez seront les même ! En fait, PA_InitRand() se base sur l'heure de votre DS, il sera donc impossible de retrouver la même série de nombres.
Ensuite, il existe trois fonctions pour trouver des nombres aléatoires (si vous avez fait le TP du Plus ou Moins, vous pouvez passer au paragraphe ci-dessous) :

Ensuite, voici le dossier gfx : gfx.zip (100707) ; gfx.zip (080823).
N'oubliez pas d'inclure all_gfx, de charger la palette, de créer les sprites...
Je vous conseille de créer deux structures : la première pour la pastille et la deuxième pour les bulles.
Créez un tableau pour les bulles.
Définissez (à l'aide de defines) la largeur de l'écran (256), sa hauteur (192), la largeur et la hauteur de la pastille (regardez dans le fichier png), le diamètre maximal des bulles (6 ; elles sont toutes de largeur égale à la hauteur) et le nombre maximum de bulles.

Correction

Voici ma correction :

//INCLUDES
#include <PA9.h>

//IMAGES
#include "all_gfx.h"

//DEFINES
#define LARGEUR_ECRAN 256
#define HAUTEUR_ECRAN 192
#define MAX_BULLES 256
#define LARGEUR_PASTILLE 32
#define HAUTEUR_PASTILLE 9
#define DIAMETRE_BULLE 6

typedef struct
{
    int x,y;
    /*X : position dans les x de l'aspirine
    Y : position dans les y de l'aspirine*/
}pastille;

typedef struct
{
    int x,y,cree,tps_creation;
    /*X : position de la bulle dans les x
    Y : position de la bulle dans les y
    cree : sert à savoir si la bulle est créée et encore dans l'écran
    tps_creation : sert à savoir si une bulle n'est simplement pas créée ou si elle est sortie de l'écran*/
}bulle;

int main(int argc, char ** argv)
{
    int i , actionx=0, gravite=2, bulle_courante, nb_out=0, min=0, max=MAX_BULLES-1;
    /* i est le compteur général
    actionx sert à déterminer la direction de la pastille
    bulle_courante servira à créer les bulles
    nb_out compte le nombre de bulles sorties de l'écran
    min est le numéro de la première bulle pas créée
    max est le numéro de la dernière bulle pas créée*/
    pastille aspirine;
    bulle bubbles[MAX_BULLES];
    aspirine.x=(LARGEUR_ECRAN-LARGEUR_PASTILLE)/2; //ON LA CENTRE
    aspirine.y=0;
    for(i = 0; i <MAX_BULLES ; i++)
    {
        bubbles[i].x=0;
        bubbles[i].y=0;
        bubbles[i].cree=0;
        bubbles[i].tps_creation=0;
    }
    PA_Init();
    PA_InitVBL();
    PA_InitRand();

    PA_LoadSpritePal(0,0,(void*)sprites_Pal);
    PA_CreateSprite(0,0,(void*)cachet_Sprite,OBJ_SIZE_32X32,1,0,aspirine.x,aspirine.y);
    for(i = 0; i < MAX_BULLES - 1; i++)
    {
        PA_CreateSprite(0,i+1,(void*)bulle_Sprite,OBJ_SIZE_8X8,1,0,-8,-8);
        PA_StartSpriteAnim(0,i+1,PA_RandMax(2),0,0); //Bulle plus ou moins grosse
    }
    while (1)
    {
        aspirine.x+=Pad.Held.Right-Pad.Held.Left; //gestion des touches fléchées gauche et droite
        aspirine.y+=gravite; //gestion de la gravité
        if(nb_out==MAX_BULLES-1)
            gravite=-4; //si toutes les bulles sont parties
        nb_out=0;
        bulle_courante=PA_RandMinMax(min,max);
        if(!bubbles[bulle_courante].cree&&!bubbles[bulle_courante].tps_creation) //on crée une bulle si elle n'est pas créée
        {
            bubbles[bulle_courante].x=PA_RandMinMax(aspirine.x,aspirine.x+LARGEUR_PASTILLE-DIAMETRE_BULLE);
            bubbles[bulle_courante].y=aspirine.y;
            bubbles[bulle_courante].cree=1;
        }
        for(i=0;i<MAX_BULLES-1;i++)
        {
            if(bubbles[i].cree)
            {
                actionx=PA_RandMax(1);
                if(!actionx)
                    bubbles[i].x--;
                else
                    bubbles[i].x++;
                if(bubbles[i].x<0)
                    bubbles[i].x=0; //on ne dépasse pas de l'écran
                else if(bubbles[i].x+DIAMETRE_BULLE>LARGEUR_ECRAN)
                    bubbles[i].x=LARGEUR_ECRAN-DIAMETRE_BULLE; //idem
                bubbles[i].y--; //les bulles remontent
                if(bubbles[i].y+DIAMETRE_BULLE<0)
                    bubbles[i].cree=0; //si elle est sortie on la considère comme morte
                bubbles[i].tps_creation++; //son ancienneté augmente
                PA_SetSpriteXY(0,i+1,bubbles[i].x,bubbles[i].y); //on place la bulle
            }
            else
                nb_out++;//on augmente le nombre de bulles parties
        }
        for(i=0;i<MAX_BULLES-1;i++) //on restreint le minimum à la première bulle non créée
        {
            if(!bubbles[i].cree && !bubbles[i].tps_creation)
            {
                min=i;
                break;
            }
        }
        for(i=MAX_BULLES-1;i>=0;i--) //on restreint le maximum à la dernière bulle non créée
        {
            if(!bubbles[i].cree && !bubbles[i].tps_creation)
            {
                max=i;
                break;
            }
        }
        if(aspirine.x<0)
            aspirine.x=0;//on ne dépasse pas de l'écran
        else if(aspirine.x+LARGEUR_PASTILLE>LARGEUR_ECRAN)
            aspirine.x=LARGEUR_ECRAN-LARGEUR_PASTILLE;//on ne dépasse pas de l'écran
        if(aspirine.y+HAUTEUR_PASTILLE<0) //Si la pastille est remontée, on arrête le jeu
        {
            PA_DeleteSprite(0,0);
            break;
        }
        else if(aspirine.y+HAUTEUR_PASTILLE>HAUTEUR_ECRAN)
            aspirine.y=HAUTEUR_ECRAN-HAUTEUR_PASTILLE; //on ne passe pas sous l'écran
        PA_SetSpriteXY(0,0,aspirine.x,aspirine.y); //on place la pastille
        PA_WaitForVBL();
    }
    return 0;
}

Amélioration (exercice)

Pour notre première amélioration, on va mettre l'animation sur les deux écrans simultanément (pensez aux DualSprites !). N'oubliez pas d'enlever les indications d'écran pour les DualSprites !

Correction de l'amélioration

Voilà :

//INCLUDES
#include <PA9.h>
 
//IMAGES
#include "all_gfx.h"
 
//DEFINES
#define LARGEUR_ECRAN 256
#define HAUTEUR_ECRAN 192
#define MAX_BULLES 128
#define LARGEUR_PASTILLE 32
#define HAUTEUR_PASTILLE 9
#define DIAMETRE_BULLE 6
 
typedef struct
{
    int x,y;
    /*X : position dans les x de l'aspirine
    Y : position dans les y de l'aspirine*/
}pastille;
 
typedef struct
{
    int x,y,cree,tps_creation;
    /*X : position de la bulle dans les x
    Y : position de la bulle dans les y
    cree : sert à savoir si la bulle est créée et encore dans l'écran
    tps_creation : sert à savoir si une bulle n'est simplement pas créée ou si elle est sortie de l'écran*/
}bulle;
 
int main(int argc, char ** argv)
{
    int i , actionx=0, gravite=2, bulle_courante, nb_out=0, min=0, max=MAX_BULLES-1;
    /* i est le compteur général
    actionx sert à déterminer la direction de la pastille
    bulle_courante servira à créer les bulles
    nb_out compte le nombre de bulles sorties de l'écran
    min est le numéro de la première bulle pas créée
    max est le numéro de la dernière bulle pas créée*/
    pastille aspirine;
    bulle bubbles[MAX_BULLES];
    aspirine.x=(LARGEUR_ECRAN-LARGEUR_PASTILLE)/2; //ON LA CENTRE
    aspirine.y=0;
    for(i=0;i<MAX_BULLES;i++)
    {
        bubbles[i].x=0;
        bubbles[i].y=0;
        bubbles[i].cree=0;
        bubbles[i].tps_creation=0;
    }
    PA_Init();
    PA_InitVBL();
    PA_InitRand();
        
    PA_DualLoadSpritePal(0,(void*)sprites_Pal);
    PA_DualCreateSprite(0,(void*)cachet_Sprite,OBJ_SIZE_32X32,1,0,aspirine.x,aspirine.y);
    for(i=0;i<MAX_BULLES-1;i++)
    {
        PA_DualCreateSprite(i+1,(void*)bulle_Sprite,OBJ_SIZE_8X8,1,0,-8,-8);
        PA_DualStartSpriteAnim(i+1,PA_RandMax(2),0,0); //Bulle plus ou moins grosse
    }
        PA_SetScreenSpace(0);
    while (1)
    {
        aspirine.x+=Pad.Held.Right-Pad.Held.Left; //gestion des touches fléchées gauche et droite
        aspirine.y+=gravite; //gestion de la gravité
        if(nb_out==MAX_BULLES-1)
            gravite=-4; //si toutes les bulles sont parties
        nb_out=0;
        bulle_courante=PA_RandMinMax(min,max);
        if(!bubbles[bulle_courante].cree&&!bubbles[bulle_courante].tps_creation) //on crée une bulle si elle n'est pas créée
        {
            bubbles[bulle_courante].x=PA_RandMinMax(aspirine.x,aspirine.x+LARGEUR_PASTILLE-DIAMETRE_BULLE);
            bubbles[bulle_courante].y=aspirine.y;
            bubbles[bulle_courante].cree=1;
        }
        for(i=0;i<MAX_BULLES-1;i++)
        {
            if(bubbles[i].cree)
            {
                actionx=PA_RandMax(1);
                if(!actionx)
                    bubbles[i].x--;
                else
                    bubbles[i].x++;
                if(bubbles[i].x<0)
                    bubbles[i].x=0; //on ne dépasse pas de l'écran
                else if(bubbles[i].x+DIAMETRE_BULLE>LARGEUR_ECRAN)
                    bubbles[i].x=LARGEUR_ECRAN-DIAMETRE_BULLE; //idem
                bubbles[i].y--; //les bulles remontent
                if(bubbles[i].y+DIAMETRE_BULLE<0)
                    bubbles[i].cree=0; //si elle est sortie on la considère comme morte
                bubbles[i].tps_creation++; //son ancienneté augmente
                PA_DualSetSpriteXY(i+1,bubbles[i].x,bubbles[i].y); //on place la bulle
            }
            else
                nb_out++;//on augmente le nombre de bulles parties
        }
        for(i=0;i<MAX_BULLES-1;i++) //on restreint le minimum à la première bulle non créée
        {
            if(!bubbles[i].cree && !bubbles[i].tps_creation)
            {
                min=i;
                break;
            }
        }
        for(i=MAX_BULLES-1;i>=0;i--) //on restreint le maximum à la drnière bulle non créée
        {
            if(!bubbles[i].cree && !bubbles[i].tps_creation)
            {
                max=i;
                break;
            }
        }
        if(aspirine.x<0)
            aspirine.x=0;//on ne dépasse pas de l'écran
        else if(aspirine.x+LARGEUR_PASTILLE>LARGEUR_ECRAN)
            aspirine.x=LARGEUR_ECRAN-LARGEUR_PASTILLE;//on ne dépasse pas de l'écran
        if(aspirine.y+HAUTEUR_PASTILLE<0) //Si la pastille est remontée, on arrête le jeu
        {
            PA_DualDeleteSprite(0);
            break;
        }
        else if(aspirine.y+HAUTEUR_PASTILLE>HAUTEUR_ECRAN*2)
            aspirine.y=HAUTEUR_ECRAN*2-HAUTEUR_PASTILLE; //on ne passe pas sous l'écran
        PA_DualSetSpriteXY(0,aspirine.x,aspirine.y); //on place la pastille
        PA_WaitForVBL();
    }
    return 0;
}

Voici une vidéo :
Vidéo du TP.

Voilà, vous maîtrisez les techniques avancées de la 2D :) !

Le dessin

Vous cherchez à dessiner sur un écran de votre Nintendo DS ? Alors ce chapitre est fait pour vous ;) .On va apprendre à dessiner sur l'écran en le mettant en mode 16 bit. Certes c'est peu, mais ça peut être bien ^^ .

Le mode 16 bit

Pourquoi avoir choisi le 16 bit ?

Le 16 bit offre une grande gamme de couleurs, et avec ce mode (ainsi qu'avec le 8 bit, mais avec ce-dernier nous serions obligés de sélectionner 256 couleurs), on peut ainsi dessiner tout ce que l'on veut sur l'écran : allant du simple pixel à des disques (ou mieux :-° ).

Comme pour chaque partie de PALib, il faut initialiser l'écran avec void PA_Init16bitBg(u8 ecran,u8 arriere_plan) .
Pour afficher un pixel à l'écran, nous utiliserons void PA_Put16bitPixel (u8 ecran,s16 x,s16 y,u16 couleur) couleur est la valeur donnée par la macro PA_RGB8(r,g,b) .

Pour tracer une ligne en mode 16 bit, on utilisera void PA_Draw16bitLine (u8 ecran,u16 x1,u16 y1,u16 x2,u16 y2,u16 couleur) .
Pour dessiner un rectangle en mode 16 bit, il faut utiliser void PA_Draw16bitRect (u8 ecran,s16 debutX,s16 debutY,s16 finX,s16 finY,u16 color) .
Si vous voulez effacer l'écran, on utilisera la macro PA_Clear16bitBg(ecran) .

Exercices

La base était facile, nous allons donc nous attaquer à la pratique ;) !

Coloration du passage du stylet

Nous allons coder un programme qui colore le passage du stylet sur l'écran !
Si vous ne vous souvenez plus du stylet (ce dont je doute ;) ), vous pouvez à tout moment retourner au chapitre sur les événements...
Petite fantaisie : le dessin sera sur les deux écrans !

Mais comment on va faire ? Les deux écrans ne sont pas tactiles ! o_O

Le joueur va dessiner sur l'écran tactile, et le dessin sera reproduit sur l'écran supérieur.

Correction

#include <PA9.h>

int main(int argc, char ** argv)
{
    PA_Init();
    PA_InitVBL();

    PA_Init16bitBg(0,0);
    PA_Init16bitBg(1,0);
 
    while(1)
    {
        PA_WaitFor(Stylus.Newpress||Stylus.Held); //On attend un nouvel appui du stylet ou un maintient de celui-ci sur l'écran tactile
        PA_Put16bitPixel(0,Stylus.X,Stylus.Y,PA_RGB8(255,0,0)); //On dessine le pixel sur l'écran tactile
        PA_Put16bitPixel(1,Stylus.X,Stylus.Y,PA_RGB8(0,255,255)); //On dessine le pixel sur l'écran supérieur
        PA_WaitForVBL();
    }

    return 0;
}

Vous avez vu que le tracé est saccadé, il serait donc mieux d'utiliser des lignes.
Il faut utiliser un compteur, attendre un événement du stylet et bien sûr tracer des lignes ^^ .

Correction

#include <PA9.h>

int main(int argc, char ** argv)
{
    int i=0;
    s16 x1=0,y1=0,styletX=0,styletY=0;
    PA_Init();
    PA_InitVBL();
    
    PA_Init16bitBg(0,0);
    PA_Init16bitBg(1,0);
 
    while(1)
    {
        PA_WaitFor(Stylus.Newpress||Stylus.Held);
        styletX=Stylus.X;
        styletY=Stylus.Y;
        if(i==1)
        {
            x1=styletX;
            y1=styletY;
        }
        else
        {
            PA_Draw16bitLine(0,x1,y1,styletX,styletY,PA_RGB8(255,0,0));
            PA_Draw16bitLine(1,x1,y1,styletX,styletY,PA_RGB8(0,255,255));
            x1=styletX;
            y1=styletY;
        }
        i++;
        if(i>2)
            i=2; // Permet d'éviter que i prenne une valeur trop importante
        PA_WaitForVBL();
    }

    return 0;
}

Bon, je suis sûr que vous allez me détester, mais il existe une fonction toute faite et meilleure que la nôtre pour le faire : void PA_16bitDraw (u8 ecran,u16 couleur) . Ce n'est pas grave, c'est le besoin d'accomplissement personnel de la pyramide de Maslow qui vient de se combler :p .

Dessiner des cercles

De cette définition (ainsi que de la représentation graphique qui en découle), on peut se dire qu'on ne travaillera que sur un quart, et qu'on reportera les pixels sur les trois autres par miroir. Je travaille toujours sur le quart en haut à gauche, mais vous pouvez en choisir un autre ;) .

Vous le savez peut-être déjà, dans un repère orthonormé (ce qui est le cas des écrans de votre Nintendo DS), l'équation du cercle de centre O(a;b) et de rayon r est (x-a)^2+(y-b)^2=r^2.
On va exprimer x en fonction de y (on peut faire l'inverse aussi ^^ ).
Donc nous avons :
(x-a)^2+(y-b)^2=r^2(x-a)^2=r^2-(y-b)^2(x-a)^2=(r-y+b)(r+y-b)
Donc x-a=sqrt{(r-y+b)(r+y-b)}
et x-a=-sqrt{(r-y+b)(r+y-b)}.

On peut donc dire que x=a+sqrt{(r-y+b)(r+y-b)}
et x=a-sqrt{(r-y+b)(r+y-b)}.
Mais vu que l'on ne travaillera que sur le côté gauche, nous ne retiendrons que x=a-sqrt{(r-y+b)(r+y-b)}.

Ne vous arrêtez pas à la racine carrée, je sais que la racine carrée est une opération lourde, mais la technique employée est légère. En effet, pour un cercle de 100 pixels de diamètre, il n'y a que 50 tours de boucles contenant chacune 2 racines carrées, soit 100 racines carrées. De plus, j'ai testé le fps, il reste à 60 donc on ne peut pas dire que ça prenne beaucoup de CPU...

Comme on l'a fait dans l'exercice de la coloration du passage du stylet, il faudra utiliser les lignes et non les points.
Voici ce que le programme doit faire : par défaut, le centre du cercle doit être au centre de l'écran, et son rayon doit être de 100 px. En appuyant sur L, le rayon doit être augmenté de 1 px ; sur R, le rayon doit être diminué de 1 px ; et sur les touches multidirectionnelles, le centre doit se déplacer dans la direction voulue de 1 px. Bien sûr, à chaque fois que vous appuyez sur ces touches, l'écran doit être effacé. Le cercle ne doit pas dépasser de l'écran, sinon vous aurez une belle surprise :p ...
Je vous laisse coder :) .

Correction

#include <PA9.h>
#include <math.h>

#define LARGEUR_ECRAN 255
#define HAUTEUR_ECRAN 191

typedef struct
{
    int x,y;
}Point;

void dessiner_cercle(Point,int);

int main(int argc, char ** argv)
{
    Point centre;
    centre.x=LARGEUR_ECRAN/2;
    centre.y=HAUTEUR_ECRAN/2;
    int rayon=50;
    
    PA_Init();
    PA_InitVBL();
    
    PA_Init16bitBg(0,0);
    
    while(1)
    {
        if(Pad.Held.L)
        {
            rayon++;
            PA_Clear16bitBg(0);
        }
        if(Pad.Held.R)
        {
            rayon--;
            PA_Clear16bitBg(0);
        }
        if(Pad.Held.Left)
        {
            centre.x--;
            PA_Clear16bitBg(0);
        }
        else if(Pad.Held.Right)
        {
            centre.x++;
            PA_Clear16bitBg(0);
        }
        if(Pad.Held.Up)
        {
            centre.y--;
            PA_Clear16bitBg(0);
        }
        else if(Pad.Held.Down)
        {
            centre.y++;
            PA_Clear16bitBg(0);
        }
        if(rayon<10)
            rayon=10;
        else if(rayon>HAUTEUR_ECRAN/2)
            rayon=HAUTEUR_ECRAN/2;
        if(centre.x-rayon<0)
            centre.x=rayon;
        else if(centre.x+rayon>LARGEUR_ECRAN)
            centre.x=LARGEUR_ECRAN-rayon;
        if(centre.y-rayon<0)
            centre.y=rayon;
        else if(centre.y+rayon>HAUTEUR_ECRAN)
            centre.y=HAUTEUR_ECRAN-rayon;
        dessiner_cercle(centre,rayon);
        PA_WaitForVBL();
    }

    return 0;
}

void dessiner_cercle(Point centre,int rayon)
{
    int x,x1,y;
    for(y=centre.y-rayon;y<centre.y;y++)
    {
        x=(int)(centre.x-sqrt((rayon+y-centre.y)*(rayon-y+centre.y)));
        x1=(int)(centre.x-sqrt((rayon+y+1-centre.y)*(rayon-y-1+centre.y)));
        PA_Draw16bitLine(0,x,y,x1,y+1,PA_RGB8(255,0,0)); // En haut à gauche
        PA_Draw16bitLine(0,2*centre.x-x,y,2*centre.x-x1,y+1,PA_RGB8(255,0,0)); // En haut à droite
        PA_Draw16bitLine(0,x,2*centre.y-y,x1,2*centre.y-y-1,PA_RGB8(255,0,0)); // En bas à gauche
        PA_Draw16bitLine(0,2*centre.x-x,2*centre.y-y,2*centre.x-x1,2*centre.y-y-1,PA_RGB8(255,0,0)); // En bas à droite
    }
}

Dessiner la fonction PA_RandMax !

Vous connaissez sûrement le principe des fonctions mathématiques.
Je ne vais pas vous faire un cours de maths là-dessus ^^ , mais nous allons dessiner PA_RandMax.
Pour cela, nous allons dessiner une suite de lignes qui se suivent pour notre courbe. Nous allons d'abord créer un compteur que l'on incrémentera à chaque tour de boucle. Il représentera nos x. Son image (PA_RandMax(192) ) sera nos y.
Ce qui est logique puisque c'est comme ça que l'on dessine une fonction.
Les mathématiciens (et pas qu'eux ^^ ) mettent l'origine de leur repère orthonormé en bas à gauche. Il faudra donc soustraire à la hauteur de l'écran les y puisque ceux-ci sont inversés (le 0 en haut).

Vous savez assez de notions pour le faire maintenant.

Correction

Encore une fois, voici ma correction :) :

#include <PA9.h>

#define RGB(r,g,b) PA_RGB((int)(r*31./255.),(int)(g*31./255.),(int)(b*31./255.))

#define LARGEUR_ECRAN 256
#define HAUTEUR_ECRAN 192

int main(int argc, char ** argv)
{	
    int x=0,y=0,y1=0;
    PA_Init();
    PA_InitVBL();
	
    PA_Init16bitBg(0,0);
    PA_InitRand();
	
    while(x<LARGEUR_ECRAN)
    {
        if(!x)
            y=PA_RandMax(HAUTEUR_ECRAN);
        else
        {
            y1=PA_RandMax(HAUTEUR_ECRAN);
            if(y>=0&&y1>=0)
                PA_Draw16bitLine(0,x-1,y,x,y1,RGB(255,0,0));
            y=y1;
			
        }
        x++;
        PA_WaitForVBL();
    }
	
    return 0;
}

Et voilà, vous obtiendrez une courbe rouge sur un fond noir qui se trace en direct ^^ !

Voilà, vous savez comment dessiner sur votre écran :) . N'hésitez pas à relire le chapitre si vous n'avez pas bien assimilé une notion. Nous allons étudier un système indispensable pour tout jeu : le son :magicien: !

Le son

Vous avez déjà vu un jeu sans son ? Bon oui d'accord ça s'est déjà vu par le passé, mais à l'heure actuelle un jeu sans son serait assez mal vu. C'est pourquoi nous allons aborder cette notion tout de suite ;) !

Outils nécessaires

Les Nintendo DS savent lire deux types de fichiers : les .raw et les .mod . On va uniquement créer des fichiers .raw parce qu'ils sont plus faciles à créer.
Mais nous devons télécharger un programme qui peut transformer des fichiers .mp3, .wav, etc. en fichiers .raw.
Pour cela, je vous conseille Switch.
Vous aurez peut-être aussi besoin d'éditer les fichiers sons avec Audacity (petit tuto sur le sujet :) ). Mais je ne vais pas vous en dire plus sur ce dernier.

Mais comment on transforme un fichier .wav en fichier .raw, alors ?

Lancez Switch. Glissez le fichier à transformer dans le grand panel blanc où il est écrit :

Citation

Add Files to Convert into this list by clicking the "Add Files" button or pressing Alt+A on your keyboard.

Ecrivez le chemin du dossier (dans lequel sera mis le fichier .raw) à droite de "Output Folder".
Ensuite, dans "Output Format", sélectionnez ".raw".
Puis, cliquez sur "Encoder Options..." et mettez :

Et cliquez sur ok.
Enfin, cliquez sur le gros bouton "Convert".
Et voilà :) .
Créez ensuite un dossier nommé "data" dans le répertoire de votre jeu et copiez-y le fichier .raw.

Le code, enfin

Il faut d'abord importer le son. Lorsque vous cliquez sur build.bat, le compilateur met le fichier raw et tous les fichiers mp3 dans du code : si vous avez le_nom_du_fichier.raw, vous devrez inclure le_nom_du_fichier.h, idem pour les fichiers mp3.
Il faut initialiser le son avec :

AS_Init(AS_MODE_MP3 | AS_MODE_SURROUND | AS_MODE_16CH);
AS_SetDefaultSettings(AS_PCM_8BIT, 11025, AS_SURROUND);

.
Et vous aurez un son en surround.

Les sons MP3

Les sons MP3 ont une façon spécifique de fonctionner. Pour jouer un MP3, il faut écrire (par exemple pour data/test.mp3) :

AS_MP3DirectPlay((u8*)test, (u32)test_size);

Si vous voulez jouer le son en boucle, vous utiliserez la fonction :

AS_SetMP3Loop(1);

A l'inverse, si vous ne voulez plus que le son soit joué en boucle, mettez 0.

Pour mettre un MP3 en pause, on utilisera AS_MP3Pause(); , pour arrêter un MP3 AS_MP3Stop(); .
Si vous voulez obtenir des indications sur le système MP3 (si un son est joué, en pause, arrêté, etc.), la fonction AS_GetMP3Status(); nous renvoie des flags pouvant être :

Les fichiers RAW

Si vous voulez jouer un son RAW (supposons que vous ayez data/boum.raw) :

AS_SoundQuickPlay(boum);

Et votre son sera joué ;) !

Le micro !

Vous attendiez sûrement ce moment : contrôler le micro :p !
Le mieux dans tout ça, est que c'est super facile.
Il y a quatre étapes :

Voici donc ce que l'on peut faire :

#include <PA9.h>
 
u8 buffer[100000];
int main(int argc, char ** argv)
{
    PA_Init();
    PA_InitVBL();
        
    PA_InitText(1,0);

    AS_Init(AS_MODE_SURROUND | AS_MODE_16CH);
    AS_SetDefaultSettings(AS_PCM_8BIT, 11025, AS_SURROUND);
 
    PA_OutputText(1,0,0,"Appuyez sur A pour enregistrer et sur B pour réécouter");
        
    while (1)
    {           
        if(Pad.Newpress.A)
            PA_MicStartRecording(buffer,sizeof(buffer)/sizeof(u8));
 
        else if(Pad.Newpress.B) 
            PA_MicReplay(buffer,sizeof(buffer)/sizeof(u8));
 
        PA_OutputText(1,0,10,"%d  ",PA_MicGetVol());
        PA_WaitForVBL();
    }
    return 0;
}

Avouez que ce n'était pas bien compliqué :p

Voilà, vous vous coucherez encore plus intelligent ce soir :p .

Des fonctionnalités utiles

Tous les jeux officiels (de DS) auxquels vous avez joués se mettent en pause lorsque vous refermez votre DS, récupèrent le nom de votre utilisateur, etc.
Nous allons apprendre à récupérer toutes ces données.

Les fonctions et les structures !

Vérifier si l'écran supérieur est rabattu

PAlib a créé une fonction pour ce cas, c'est void PA_CheckLid() . Cette fonction renvoie 1 si c'est fermé, et la Nintendo DS se met automatiquement en pause...

/*Mettre le code de début*/
while(1)
{
    PA_CheckLid();
    PA_WaitForVBL();
}
/*Mettre le code de fin*/

Si vous voulez simplement savoir si la Nintendo DS est fermée (sans pause), vous pouvez utiliser la macro PA_LidClosed() .
Si vous voulez savoir si la Nintendo DS est fermée, avec pause et en jouant un son (que vous choisissez), vous pouvez utiliser la macro PA_CloseLidSound(canal,son) , et si vous voulez mettre un son quand elle est fermée et un quand elle se réouvre, il faut utiliser la macro PA_CloseLidSound2(canal,son_ferme,son_ouvert) .

Récupérer le temps

La structure "PA_RTC" continent des membres exprimant la date :

On peut écrire :

#include <PA9.h>
 
int main(int argc, char ** argv)
{
        PA_Init();
        PA_InitVBL();
 
        PA_InitText(0,0);
        
        while (1)
        {               
                PA_OutputText(0, 2, 10, "%02d/%02d/%02d", PA_RTC.Day, PA_RTC.Month, PA_RTC.Year); // Date
                PA_OutputText(0, 2, 12, "%02d:%02d  %02d secondes", PA_RTC.Hour, PA_RTC.Minutes, PA_RTC.Seconds); // Time
                PA_WaitForVBL();
        }
        return 0;
}

Récupérer les données de l'utilisateur

La structure "PA_UserInfo" a pour membres :

Faire vibrer votre Nintendo DS

Pour ceux qui auraient un Rumble Pak (un composant qui permet des vibrations), vous pouvez l'utiliser dans vos homebrews, et simplement :soleil: ! Ce que nous allons voir ne dépend pas de PALib, mais de la libnds.
Il faut vérifier si le Rumble Pak est inséré avec bool isRumbleInserted(void); . Cette fonction renvoie vrai lorsque le Rumble Pak est inséré, et faux dans le cas contraire.
A présent, on peut faire vibrer notre Rumble Pak avec void setRumble(bool position); . Si position est vraie, votre Nintendo DS se met à vibrer. A l'inverse, si position est fausse, votre Nintendo DS s'arrête de vibrer.

Jouer avec la luminosité de l'écran

Une seule fonction permet de régler la luminosité d'un écran, applaudissons void PA_SetBrightness(u8 ecran, s8 brillance); :p ! La valeur de brillance doit varier entre -31 et 31. Si sa valeur est négative, l'écran penchera vers le noir (-31 est le noir complet). A l'inverse, si sa valeur est plus grande que 0 l'écran penchera vers le blanc(31 est le blanc total). Mettez brillance à 0 et vous aurez un éclairage normal. Cette fonction est souvent utilisée dans les transitions, les splashes et les menus.

Exercice

Présentation

Maintenant, on va faire un jeu qui annonce à l'utilisateur dans combien de jours son anniversaire viendra ou alors depuis combien de jour il est passé.
Il faut que la DS affiche :

Votre anniversaire est dans [1] [2].

Ou :

Votre anniversaire est passé de [1] [2].

Ou encore :

Joyeux anniversaire !

Aide

Il faudra d'abord créer une variable de type double qui va transformer la date actuelle (mois et jour) en nombre de jours. Vous devez donc trouvez un système pour convertir des mois en jours (d'où le double).
Ensuite, il faudra appliquer ce même système pour la date d'anniversaire.
Puis, on créera une fonction pour arrondir des doubles.
On mettra dans une variable de type int le résultat arrondi (d'où la fonction) de la différence entre la date d'anniversaire et la date actuelle.
Si cette différence est plus grande que 1 ou plus petite que -1, il faudra mettre "jour" au pluriel, sinon on le met au singulier.
Si la différence est négative, on affiche :

Votre anniversaire est passé de [1] [2].

Si la différence est positive, on affiche :

Votre anniversaire est dans [1] [2].

Si la différence est égale à zéro, on affiche :

Joyeux anniversaire !

Correction

#include <PA9.h>
#include <string.h>
 
int arrondir(double);
 
int main(int argc, char ** argv)
{
    PA_Init();
    PA_InitVBL();
        
    PA_InitText(1,0);
 
    double date_actuelle=((double)PA_RTC.Month/12.*365.)+(double)PA_RTC.Day;
    double date_anniv=((double)PA_UserInfo.BdayMonth/12.*365.)+(double)PA_UserInfo.BdayDay;
    int difference=arrondir(date_anniv-date_actuelle);
    char jour[6]="";
    if(difference<-1||difference>1)
        sprintf(jour,"jours");
    else
        sprintf(jour,"jour");
    if(difference<0)
        PA_OutputText(1,0,0,"Votre anniversaire est passé de %d %s.",-difference,jour);
    else if(difference>0)
        PA_OutputText(1,0,0,"Votre anniversaire est dans %d %s.",difference,jour);
    else
        PA_OutputText(1,0,0,"Joyeux anniversaire !");
        
    while (1)
    {
        PA_WaitForVBL();
    }
    return 0;
}
 
int arrondir(double nombre)
{
    if(nombre-(int)nombre>=.5)
        nombre+=.5;
    return (int)nombre;
}

Explications pour la fonction arrondir :
La variable nombre est un double. Si l'on fait (int)nombre, on obtiendra sa troncature à l'unité. Autrement dit, vous aurez la partie entière de nombre. La règle de l'arrondi est :

Citation

Si la décimale qui suit la décimale de la limite de l'arrondi est plus grand que 4, la décimale de la limite de l'arrondi est augmentée de 1.
En gros, si vous avez 4,45 ; son arrondi au dixième sera 4,5 ; et son arrondi à l'unité sera 4.

Donc si la différence entre nombre et sa troncature à l'unité (on obtient sa partie décimale) est supérieure ou égale à 0,5 alors on augmente nombre de 0,5 pour que (int)nombre prenne la valeur entière +1 de nombre.
On aurait aussi très bien pu faire :

int arrondir(double nombre)
{
    if(nombre-(int)nombre>=.5)
        return (int)nombre+ 1;
    else
        return (int)nombre;
}

Il ne faut pas oublier le prototype de la fonction "arrondir".
Explications du main :
Tout d'abord, on initialise PAlib et le texte sur l'écran supérieur (ça aurait pu être l'écran tactile aussi).
Pour mettre un mois en jour, il faut utiliser la proportionnalité. Il y a douze mois et trois cent soixante-cinq jours (au diable les années bissextiles :p ). Il faut donc diviser le numéro du mois par douze et multiplier le résultat par trois cent soixante-cinq. A ce résultat, il faut bien sûr ajouter le numéro du jour.

Bon, voilà c'était quand même assez facile :) .

Nous avons acquis les bases, maintenant attaquons-nous aux techniques avancées :pirate: !


Les techniques avancées

Les techniques avancées

Les bases de PAlib Annexes

Nous venons de voir les bases, maintenant nous allons voir les techniques avancées. Nous allons apprendre à créer des menus, gérer les fichiers, etc. Mais on y verra ce que vous attendez depuis toujours : la 3D :D !

Les menus et les sauvegardes

Vous vous demandiez comment faire un menu avec PALib ? Une seule solution (enfin, c'est la plus propre) : créer plusieurs boucles ! Nous ne ferons que de la théorie puisqu'il n'y a pas qu'une façon de faire des menus :) .

Créer un menu

Nous allons y aller en douceur ^^ pour commencer : nous allons créer un des menus les plus basiques qui soient.

Qu'est-ce qu'un menu ?

Un menu est constitué d'un arrière-plan, et d'éléments (boutons, texte, images, etc...) permettant d'accéder à diverses partie du jeu (nouveau jeu, options, etc...).

Comment créer un menu ?

Tout simplement en ajoutant une boucle :

#include <PA9.h>

int main(int argc,char **argv)
{
    PA_Init();
    PA_InitVBL();

    while(1) // Boucle du menu
    {
        PA_WaitForVBL();
    }

    while(1) // Boucle du jeu
    {
        PA_WaitForVBL();
    }

    return 0;
}

Bien sûr, vu que le menu appelle le jeu et que le jeu appelera le menu (si on a une fonctionnalité pour quitter le jeu), on peut créer deux fonctions : menu et jeu :

#include <PA9.h>

typedef enum Choix Choix; // Cette énumération permet de connaître le choix du joueur
enum Choix
{
    AUCUN,JOUER
};

void menu(void);
void jeu(void);

int main(int argc,char **argv)
{
    PA_Init();
    PA_InitVBL();

    menu();

    return 0;
}

void menu(void)
{
    Choix choix=AUCUN;
    while(choix==AUCUN)
    {
        PA_WaitForVBL();
    }
    
    switch(choix)
    {
        case JOUER:
            jeu();
            break;
        default:
            break;
    }
}

void jeu(void)
{
    int quitter=0;
    while(1)
    {
        if(quitter)
            break;
        PA_WaitForVBL();
    }
    menu();
}

Un bon menu, selon moi, est un menu qui comporte un arrière-plan pour les deux écrans, avec le nom du jeu sur l'écran supérieur, et les éléments du menu sur l'écran tactile.
Ce menu doit avoir deux méthodes de choix : un par le stylet et un par les touches multi-directionnelles pour déplacer un curseur avec A ou Start pour valider.
Ca c'est un bon menu basique ^^ .
A vous de réfléchir à des systèmes plus élaborés pour faire des systèmes plus originaux ;) .

Créer une sauvegarde

Les bases

Il faut commencer par inclure le header de FAT et stdio:

#include <stdio.h>
#include <fat.h>

Puis on initialise FAT avec bool fatInitDefault (void); (ne vous arrêtez pas à bool , cette fonction marche très bien en C ;) ).
Le reste est ce que vous avez déjà appris en C. En effet, pour créer une variable contenant un fichier on fait :

FILE *fichier=NULL;

Pour initialiser une variable il suffit de faire :

fichier=fopen("fichier.ext","mode");

mode peut être "w", "wb", "r", "rb", "a", etc...
Pour fermer un fichier il faut écrire :

fclose(fichier);

Pour écrire dans un fichier, on utilisera size_t fwrite(const void * contenu, size_t compte, size_t taille_du_contenu, FILE * fichier); contenu est le texte à écrire dans le fichier, compte est le nombre d'éléments (nous laisserons toujours 1), taille_du_contenu est la taille du contenu, et où fichier est le fichier.
Par exemple :

FILE *fichier=fopen("test.txt","w");
fwrite("hello !",1,8,fichier);

Pour lire un fichier, c'est quasiment pareil :) ! On crée une chaîne de caractères vide, et on fait un appel à fread qui prend les mêmes arguments que sa cousine fwrite. Par exemple :

char contenu[256];
FILE *fichier=fopen("test.txt","r");
fread(contenu,1,256,fichier);
/* Et voilà, la variable "contenu" contient le contenu du fichier test.txt ;) */

Lister les fichiers et les répertoires d'un dossier

On va inclure fat.h, bien sûr, mais aussi sys/dir.h pour lister le dossier courant.
On va écrire dans un fichier .txt le contenu du dossier.
Pour cela, on va devoir :

Voici à quoi ressemble notre code :

#include <PA9.h>
#include <fat.h>
#include <sys/dir.h>

int main(int argc, char ** argv)
{
    struct stat st;
    char buf[512]="",filename[256]="";
    DIR_ITER* dir = NULL;
    FILE *fichier=NULL;
    
    PA_Init();
    PA_InitVBL();
    
    PA_InitText(0,0);
    
    fatInitDefault();
    
    dir=diropen("/"); // Lit le dossier courant

    if (dir==NULL) // Le dossier n'a pas pu être lu
        PA_OutputText(0,0,0,"Une erreur est survenue, réessayez.");
    else 
    {
        PA_OutputText(0,0,0,"Lecture du dossier en cours...");
        fichier=fopen("liste.txt","w");
        while (dirnext(dir,filename,&st) == 0) 
        {
            // Si (st.st_mode&S_IFDIR) alors c'est un dossier, sinon c'est un fichier
            sprintf(buf,"%s : %s\n",(st.st_mode&S_IFDIR?"Dossier":"Fichier"),filename);
            fwrite(buf,1,sizeof(buf),fichier); // On écrit dans le fichier .txt
        }
        fclose(fichier);
        PA_OutputText(0,0,1,"Lecture du dossier termniée !");
    }
    
    while (1)
        PA_WaitForVBL();
    
    return 0;
}

A partir de maintenant, vous pouvez ajouter des menus sympas à vos jeux ;) ...

Les timers

Nous avons appris beaucoup de choses jusqu'à maintenant, mais nous n'avons pas encore vu comment créer des timers. Par exemple, vous faites un jeu dans lequel les personnages meurent quand ils n'ont plus de vie (logique :) ), et vous voulez qu'ils disparaissent 3 secondes plus tard, donc il faut utiliser les timers !

Les timers PALib

La première méthode est : utiliser les compteurs de PALib.
Pendant que vous faites appel à PA_WaitForVBL, les compteurs actifs augmentent de 1. Ainsi, vous pouvez connaître le nombre de VBL depuis le lancement du timer (il ne reste plus qu'à diviser par 60 pour avoir un nombre de secondes).

Mais, s'il y a un compteur, il doit y avoir risque de dépassement, non ?

En fait, si le type est un int, vous pouvez attendre 414 jours, 6 heures, 3 minutes et 14.133 secondes environ. Si c'est un unsigned int, vous aurez le droit au double. Donc je pense qu'il n'y a pas de soucis de ce côté là :p ...

Voici le prototype de la fonction permettant de démarrer/redémarrer un compteur : void PA_VBLCounterStart(u8 c); c est le numéro du compteur (doit être compris entre 0 et 15 inclus). Pour mettre un compteur en pause, on utilisera void PA_VBLCounterPause(u8 c); , et pour qu'un compteur reprenne le compte, on utilisera void PA_VBLCounterUnpause(u8 c); . Enfin, pour accéder au contenu du compteur, il faut écire PA_VBLCounter[c] (le résultat est en VBL).
Voici un petit exemple :

#include <PA9.h>

int main(int argc,char **argv)
{
	PA_Init();
	PA_InitVBL();
	
	PA_InitText(1,0);
	
	PA_VBLCounterStart(0);
	
	while(1)
        {
	    PA_OutputText(1,0,0,"%d secondes",PA_VBLCounter[0]/60);	
	    PA_WaitForVBL();
	}
	return 0;
}

La récupération directe du temps

C'est celle que j'utilise. Elle consiste à récupérer la durée en secondes depuis le 1er Janvier 1970 (c'est l'équivalent de la fonction time(NULL); qui ne fonctionne pas sur DS).
Voici la fonction (au départ je l'ai conçue en C++) :

int bi(int y)
{
    if((!(y%4)&&!(y%1000))||(!(y%4)&&(y%100)))
        return 1;
    else
        return 0;
}
unsigned int getTicks(void)
{
    unsigned int ret=0;
	int i;
	unsigned int mois[12]={31,0,31,30,31,30,31,31,30,31,30,31};
    for(i=0;i<30+PA_RTC.Year;i++)
        ret+=(bi(1970+i)?366:365)*24*3600;
    for(i=0;i<PA_RTC.Month-1;i++)
    {
        mois[1]=(bi(2000+PA_RTC.Year)?29:28);
        ret+=mois[i]*24*3600;
    }
    ret+=(PA_RTC.Day-1)*24*3600;
	ret+=PA_RTC.Seconds+PA_RTC.Minutes*60+(PA_RTC.Hour-1)*3600;
    return ret;
}

La fonction bi sert juste à savoir si une année est bissextile ou pas ; c'est la fonction getTicks qui nous intéresse.
Ainsi, nous pouvons faire le code suivant :

#include <PA9.h>

int main(int argc, char ** argv)
{
	PA_Init();
	PA_InitVBL();
	
	PA_InitText(1,0);
	
	PA_WaitForVBL();PA_WaitForVBL();PA_WaitForVBL();
	
	unsigned int debut=getTicks();
	
	while(1)
	{
		PA_OutputText(1,0,0,"%d",getTicks()-debut); // Affiche le nombre de secondes depuis que le jeu est lancé
		PA_WaitForVBL();
	}
	
	return 0;
}

Voilà, vous connaissez les deux méthodes, à vous de choisir :) !

A présent, vous contrôlez le temps :diable: ...

La 3D

Vous ne pensiez tout de même pas en rester à la 2D ! Nous allons donc voir comment créer un jeu en 3D ^^ .
Vous devez avoir acquis les 5 premiers chapitre de Créez des programmes en 3D avec OpenGL (de Kayl) car nous utiliserons des fonctions d'OpenGL.

Les bases

PALib ne gère pas la 3D. Il faudra donc passer par une bibliothèque utilisée par PALib mais de plus bas niveau (en tout cas, la 3D est de haut niveau, mais pas le reste ^^ ), la libnds. La 3D ne peut être utilisée uniquement sur l'écran tactile. Il faut tout d'abord initialiser OpenGL avec void glInit(); . Ensuite, on va définir notre caméra en plein écran avec void glViewport(uint8 x1, uint8 y1, uint8 x2, uint8 y2); , nous allons tout le temps l'utiliser comme ceci :

glViewport(0,0,255,191);

Puis il faut définir une couleur de rafraichissement opaque avec void glClearColor(uint8 r, uint8 g, uint8 b, uint8 a); r, g, b et a correspondent aux valeurs RGBA de la couleur (31 est la valeur maximale). Donc, si on veut que le fond soit noir (et opaque), il faudra écrire :

glClearColor(0,0,0,31);

Puis, on définit la perspective (exactement pareil qu'OpenGL sur ordinateur) :

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(70,(double)256/192,0.1,1000);

Et là, si vous voulez que votre scène ait un rendu, il faut écrire cette ligne :

glPolyFmt(POLY_ALPHA(31) | POLY_CULL_NONE );

Dans la boucle, l'écran exécute automatiquement le clear, donc nous n'avons pas besoin de le mettre.
Alors nous n'avons plus qu'à mettre :

glMatrixMode( GL_MODELVIEW );
glLoadIdentity();

Et à définir la position, la trajectoire et la verticale de la caméra comme sur ordinateur avec :

void gluLookAt(float eyex, float eyey, float eyez,float lookAtx, float lookAty, float lookAtz, float upx, float upy, float upz);

Vous pouvez ensuite programmer la scène comme bon vous chante, les fonctions étant les mêmes que sur ordinateur.

Pour afficher la scène il faut faire :

glFlush(0);

Et il ne reste qu'à mettre PA_WaitForVBL :) !
Voici un code d'exemple (dessin trouvé dans le tuto de Kayl) :

#include <PA9.h>

#define LARGEUR_ECRAN 256
#define HAUTEUR_ECRAN 192

int main(int argc, char ** argv)
{
	int i=0;
	
	PA_Init();
	PA_InitVBL();
	
	videoSetMode(MODE_0_3D);
	
	glInit();
	
	glViewport(0,0,LARGEUR_ECRAN-1,HAUTEUR_ECRAN-1);
	
	glClearColor(0,0,0,31);
	
	glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(70,(double)LARGEUR_ECRAN/HAUTEUR_ECRAN,.1,1000);
    
    glPolyFmt(POLY_ALPHA(31) | POLY_CULL_NONE );
	
	while(1)
	{
		glMatrixMode( GL_MODELVIEW );
		glLoadIdentity( );

		gluLookAt(3,4,2,0,0,0,0,0,1);
		
		glRotatef(i,0,0,1);

	    glBegin(GL_QUADS);

		    glColor3b(255,0,0); //face rouge
		    glVertex3f(1,1,1);
		    glVertex3f(1,1,-1);
		    glVertex3f(-1,1,-1);
		    glVertex3f(-1,1,1);

		    glColor3b(0,255,0); //face verte
		    glVertex3f(1,-1,1);
		    glVertex3f(1,-1,-1);
		    glVertex3f(1,1,-1);
		    glVertex3f(1,1,1);

		    glColor3b(0,0,255); //face bleue
		    glVertex3f(-1,-1,1);
		    glVertex3f(-1,-1,-1);
		    glVertex3f(1,-1,-1);
		    glVertex3f(1,-1,1);

		    glColor3b(255,255,0); //face jaune
		    glVertex3f(-1,1,1);
		    glVertex3f(-1,1,-1);
		    glVertex3f(-1,-1,-1);
		    glVertex3f(-1,-1,1);

		    glColor3b(0,255,255); //face cyan
		    glVertex3f(1,1,-1);
		    glVertex3f(1,-1,-1);
		    glVertex3f(-1,-1,-1);
		    glVertex3f(-1,1,-1);
			
		    glColor3b(255,0,255); //face magenta
		    glVertex3f(1,-1,1);
		    glVertex3f(1,1,1);
		    glVertex3f(-1,1,1);
		    glVertex3f(-1,-1,1);

		glEnd();

		i++;
		i%=360;

		glFlush(0);
		PA_WaitForVBL();
	}
	return 0;
}

Et vous obtenez un beu cube multicolore qui tourne sur son axe Z ^^ .

Les textures

Alors là, les choses se compliquent (j'ai passé une semaine à jouer aux 7 erreurs pour trouver comment créer une texture :-° ).

Créer la texture

Nous allons commencer par créer un dossier "data" dans le dossier de votre projet, c'est-à-dire là où se trouve build.bat.
Ensuite, il faut télécharger le logiciel Gimp.

Une fois installé, lancez-le et faites Ctrl+N (nouveau). Définissez le format de l'image à 128x128. Cliquez sur "Options avancées", choisissez 72,000 pour la résolution X et Y. Mettez "Couleur RVB" pour "Espace de couleurs" et "Blanc" pour "Remplir avec".

Image utilisateur

Faites votre dessin, puis faites Image -> Mode -> Couleurs Indexées :

Image utilisateur

Un fenêtre apparaît alors. Cochez "Générer une palette optimale", et mettez 255 à "Nombre maximal de couleurs" :

Image utilisateur

Il ne vous reste plus, ensuite, qu'à enregistrer l'image dans le dossier data, en mettant l'extension .pcx à votre fichier.

Charger la texture

Sachez que la mémoire graphique de la DS s'exprime, entre autres, par des VRAM.
Nous allons donc initialiser le système de textures avec :

vramSetMainBanks(VRAM_A_TEXTURE,VRAM_B_TEXTURE,VRAM_C_LCD,VRAM_D_LCD);

Comme ça, les VRAM A et B sont déclarées en mode texture, et les C et D sont normales. Vous pouvez y aller en tatonnant ^^ . Si certaines textures ne s'affichent pas, c'est qu'il n'y a pas assez de mémoire texture, donc vous pouvez initialiser d'autres VRAM en mode texture.
Puis, on active le système de textures :

glEnable(GL_TEXTURE_2D);

Et maintenant, on va charger la texture (ellse s'appelle tex_1.pcx).
Mais il faut définir une taille. Les constantes se trouvent dans l'énumération GL_TEXTURE_SIZE_ENUM. Cette-dernière contient (liste complète) :

Il faudra fournir d'abord la largeur, puis la hauteur. Ici, nos textures ne sont que des carrés de 128*128 ; donc nous mettrons deux fois TEXTURE_SIZE_128.

#include "tex_1.h"
int texture; // Contiendra notre texture
sImage pcx; // Représente l'image
loadPCX((u8*)tex_1, &pcx); // On charge l'image
image8to16(&pcx); // Obligatoire sinon un rendu bizarre :)
// On alloue de la mémoire pour la texture
glGenTextures(1, &texture);
glBindTexture(0, texture);
// On crée la texture
glTexImage2D(0, 0, GL_RGB, TEXTURE_SIZE_128 , TEXTURE_SIZE_128, 0, TEXGEN_TEXCOORD, pcx.image.data8);
imageDestroy(&pcx); // On détruit l'image (mais pas la texture :p )

Ensuite, pour définir la texture courante, on fait :

glBindTexture(GL_TEXTURE_2D, texture);

Et pour appliquer des coordonnées d'une texture sur un sommet, il suffit de faire :

glTexCoord2f(x,y);  glVertex3f(x1,y1,z);

Bonne nouvelle, j'ai créé une fonction toute simple qui retourne une texture :

int chargerTexture(const u8 *tex,GL_TEXTURE_SIZE_ENUM width,GL_TEXTURE_SIZE_ENUM height)
{
    sImage pcx;
    int texture;
    loadPCX((u8*)tex, &pcx);
    image8to16(&pcx);
    glGenTextures(1, &texture);
    glBindTexture(0, texture);
    glTexImage2D(0, 0, GL_RGB, width , height, 0, TEXGEN_TEXCOORD, pcx.image.data8);
    imageDestroy(&pcx);
    return texture;
}

Voici comment elle s'utilise :

#include "tex_1.h" // La texture
int texture=chargerTexture(tex_1,TEXTURE_SIZE_128,TEXTURE_SIZE_128);

Voici un pack de textures que j'ai programmé pour le cube (les textures sont donc censées être parfaites :) ) : télécharger le pack.
Et un code pour vous montrer que c'est facile :

#include <PA9.h>

#include "tex_1.h"
#include "tex_2.h"
#include "tex_3.h"
#include "tex_4.h"
#include "tex_5.h"
#include "tex_6.h"

#define LARGEUR_ECRAN 256
#define HAUTEUR_ECRAN 192

int chargerTexture(const u8 *tex,GL_TEXTURE_SIZE_ENUM width,GL_TEXTURE_SIZE_ENUM height);

int main(int argc, char ** argv)
{
	int x1=0,x2=0,y1=0,y2=0,c=0;
	int texture[6];
	float rotationX=0.,rotationZ=0.;
	
	PA_Init();
	PA_InitVBL();
	
	videoSetMode(MODE_0_3D);
	vramSetMainBanks(VRAM_A_TEXTURE,VRAM_B_TEXTURE,VRAM_C_LCD,VRAM_D_LCD);
	
	glInit();
	
	glEnable(GL_TEXTURE_2D);
	
	glViewport(0,0,LARGEUR_ECRAN-1,HAUTEUR_ECRAN-1);
	
	glClearColor(0,0,0,31);
	
	texture[0]=chargerTexture(tex_1,TEXTURE_SIZE_128,TEXTURE_SIZE_128);
	texture[1]=chargerTexture(tex_2,TEXTURE_SIZE_128,TEXTURE_SIZE_128);
	texture[2]=chargerTexture(tex_3,TEXTURE_SIZE_128,TEXTURE_SIZE_128);
	texture[3]=chargerTexture(tex_4,TEXTURE_SIZE_128,TEXTURE_SIZE_128);
	texture[4]=chargerTexture(tex_5,TEXTURE_SIZE_128,TEXTURE_SIZE_128);
	texture[5]=chargerTexture(tex_6,TEXTURE_SIZE_128,TEXTURE_SIZE_128);

	glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(70,(double)LARGEUR_ECRAN/HAUTEUR_ECRAN,.1,1000);
    
    glPolyFmt(POLY_ALPHA(31) | POLY_CULL_NONE );
	
	while(1)
	{
		glMatrixMode( GL_MODELVIEW );
		glLoadIdentity( );

		gluLookAt(3,4,2,0,0,0,0,0,1);
		
		glRotatef(rotationX,1,0,0);
		glRotatef(rotationZ,0,0,1);
		
		glColor3b(255,255,255);
		
		glBindTexture(GL_TEXTURE_2D, texture[0]);

	    glBegin(GL_QUADS);
		    glTexCoord2f(0,0);glVertex3f(1,1,1);
		    glTexCoord2f(0,1);glVertex3f(1,1,-1);
		    glTexCoord2f(1,1);glVertex3f(-1,1,-1);
		    glTexCoord2f(1,0);glVertex3f(-1,1,1);
		glEnd();
		
		glBindTexture(GL_TEXTURE_2D, texture[1]);
		
		glBegin(GL_QUADS);
		    glTexCoord2f(0,0);glVertex3f(1,-1,1);
		    glTexCoord2f(0,1);glVertex3f(1,-1,-1);
		    glTexCoord2f(1,1);glVertex3f(1,1,-1);
		    glTexCoord2f(1,0);glVertex3f(1,1,1);
		glEnd();
		
		glBindTexture(GL_TEXTURE_2D, texture[5]);
		
		glBegin(GL_QUADS);
		    glTexCoord2f(0,0);glVertex3f(-1,-1,1);
		    glTexCoord2f(0,1);glVertex3f(-1,-1,-1);
		    glTexCoord2f(1,1);glVertex3f(1,-1,-1);
		    glTexCoord2f(1,0);glVertex3f(1,-1,1);
		glEnd();
		
		glBindTexture(GL_TEXTURE_2D, texture[4]);
		
		glBegin(GL_QUADS);
		    glTexCoord2f(0,0);glVertex3f(-1,1,1);
		    glTexCoord2f(0,1);glVertex3f(-1,1,-1);
		    glTexCoord2f(1,1);glVertex3f(-1,-1,-1);
		    glTexCoord2f(1,0);glVertex3f(-1,-1,1);
		glEnd();
		
		glBindTexture(GL_TEXTURE_2D, texture[2]);
		
		glBegin(GL_QUADS);
		    glTexCoord2f(0,0);glVertex3f(1,1,-1);
		    glTexCoord2f(0,1);glVertex3f(1,-1,-1);
		    glTexCoord2f(1,1);glVertex3f(-1,-1,-1);
		    glTexCoord2f(1,0);glVertex3f(-1,1,-1);
		glEnd();
		
		glBindTexture(GL_TEXTURE_2D, texture[3]);
		
		glBegin(GL_QUADS);
		    glTexCoord2f(0,0);glVertex3f(1,-1,1);
		    glTexCoord2f(0,1);glVertex3f(1,1,1);
		    glTexCoord2f(1,1);glVertex3f(-1,1,1);
		    glTexCoord2f(1,0);glVertex3f(-1,-1,1);
		glEnd();
		
		if(Stylus.Held)
		{
			if(!(c%2))
			{
				x1=Stylus.X;
				y1=Stylus.Y;
			}
			else
			{
				x2=Stylus.X;
				y2=Stylus.Y;
				rotationZ+=x1-x2;
				rotationX+=(y2-y1)*LARGEUR_ECRAN/HAUTEUR_ECRAN;
			}
			c++;
		}
		else if(Stylus.Released)
			c=0;
		glFlush(0);
		PA_WaitForVBL();
	}
	return 0;
}

int chargerTexture(const u8 *tex,GL_TEXTURE_SIZE_ENUM width,GL_TEXTURE_SIZE_ENUM height)
{
    sImage pcx;
    int texture;
    loadPCX((u8*)tex, &pcx);
    image8to16(&pcx);
    glGenTextures(1, &texture);
    glBindTexture(0, texture);
    glTexImage2D(0, 0, GL_RGB, width , height, 0, TEXGEN_TEXCOORD, pcx.image.data8);
    imageDestroy(&pcx);
    return texture;
}

Charger des modèles

Le fait de coder point par point vous fait fuir ? Nous allons voir ici comment charger des fichiers .obj.

Charger des modèles de façon statique

J'ai développé un logiciel (dont le fonctionnement peut-être comparé avec PAGfx) qui code votre fichier .obj en fichiers .h et .cpp. Ne vous arrêtez pas à l'extension, le code que les fichiers contiennent est compatible C et C++... Pour le télécharger c'est ici. Son petit nom est Obj2NDS. Etonnant, non ;) ?

Comment ça fonctionne ?

Pour simplifier les explications, nous allons partir du principe que nous avons un fichier cube.obj (en bleu ce qui change en fonction du nom du fichier .obj).
Lancez le programme. Suivez les instructions (pour les tailles, ce sont celles qui correspondent à TEXTURE_SIZE_XXX). Ensuite vous aurez deux fichiers (cubeOBJ.cpp et cubeOBJ.h) dans le dossier du logiciel. Il faut les copier dans votre dossier source. Puis, comme pour les programmes précédents, il faut créer un dossier data et y mettre votre texture (n'oubliez pas qu'elle doit être au format .pcx et doit contenir 255 couleurs).
Dans votre fichier main, il faut inclure le fichier cubeOBJcpp.
Ce n'est pas tout, il faut initialiser la texture du modèle en faisant un appel à la fonction :

Citation

void initTexCube();

Enfin, pour dessiner votre modèle, il faut faire un appel à la fonction :

Citation

void dessinerCube();

Charger des modèles de façon dynamique

Ici, nous allons voir utiliser une petite lib que j'ai programmée qui charge votre fichier .obj dynamiquement. Pour commencer, il faut la télécharger ici. Puis, on extrait les deux fichiers dans le dossier source. Enfin, on inclut le header comme ceci :

#include "OBJlib.h"

(le fichier .c sera inclu lors de la compilation, automatiquement).

Nous allons créer une variable qui contiendra notre modèle comme ceci :

MeshObj obj;

Puis on charge le modèle avec int charger_obj(MeshObj *modele,char *nom,int largeur,int hauteur); , où modele est la variable qui recevra le modèle, nom le nom du fichier, largeur et hauteur les constante de OpenGL TEXTURE_SIZE_XXX.
En reprenant notre exemple, nous avons un code du genre :

charger_obj(&obj,"modele.obj",TEXTURE_SIZE_128,TEXTURE_SIZE_128);

Enfin, pour dessiner votre modèle, il faudra appeler void draw_model(MeshObj *modele); .
Cette fonction ne fait que dessiner votre modèle, vous pouvez donc appliquer des modifications de matrice (changement d'échelle, translation, rotation, etc.).

Voilà, bonne modélisation ;) !

Voilà, alors maintenant faites-moi de beaux jeux en 3D !

Systèmes de connexion

Maintenant que vous savez faire de belles oeuvres, il ne vous manque plus que le mode multijoueur ^^ . Dans cette partie, nous allons voir comment réaliser une connexion Wifi à un site Internet, mais aussi DS à DS.

Wifi

Nous allons voir ici comment nous connecter à une adresse, envoyer des messages et en recevoir.
Rassurez-vous, la méthode ne diffère pas trop de celle employée sur ordinateur...

Initialiser et arrêter le Wifi

Tout d'abord, jetez un oeil dans votre Makefile. Il faut décommenter #ARM7_SELECTED = ARM7_MP3_DSWIFI (enlevez le caractère # ) et commenter ARM7_SELECTED = ARM7_BASIC (ajoutez un # au début de la ligne).

Maintenant, retournons à notre fichier source :) ! Nous allons initialiser le système Wifi avec la fonction void PA_InitWifi(); . A présent, il faut utiliser les paramètres définis à partir d'un jeu commercial (il faut qu'il y ait le logo Wifi) avec la fonction bool PA_ConnectWifiWFC(); . Et là, sa valeur de retour compte plus que d'autres fonctions, car cette fonction rate assez souvent. Elle renvoie vrai si la connection a pu être effectuée ou faux dans le cas contraire.

Pour vous déconnecter du point d'accès, nous utiliserons Wifi_DisconnectAP(); , et pour arrêter le Wifi Wifi_DisableWifi(); .

Créer et utiliser des sockets

Pour établir une connexion en Wifi, il faut passer par les sockets. Comme sur l'ordinateur en fait :) . Une socket est une variable de type int donnée par la fonction int PA_InitSocket(int *socket,char *host,int port,int mode); socket est un pointeur sur votre socket à initialiser, host est l'adresse IP ou la DNS à laquelle on veut se connecter, port est le... port :p et mode est le type de socket. mode ne peut prendre que deux valeurs différentes :

L'envoi et la réception de messages sont exatement pareils que sur ordinateur. Nous allons voir cela par le biais d'exemples.

L'envoi

char buf[]="Hello World !";
send(sock,buf,sizeof(buf),0);

La réception

char buf[256];
recv(sock,buf,sizeof(buf),0);

Vous le savez sûrement, mais on peut tout envoyer avec les sockets.
Nous allons voir comment faire envoi et une réception optimisée.
Pour cela, nous allons créer deux fonctions (envoi et reception) qui auront pour arguments la socket et un tableau de char (pour envoi : le message à envoyer, pour reception : le contenu du message reçu).
L'envoi doit se faire comme ceci : on envoie d'abord la taille du message, puis le message lui-même.
Et la réception doit se faire comme ceci : on reçoit la taille du message, on fait une allocation dynamique, puis on récupère le message.
On supposera que les types sont traités de la même façon de part et d'autre de la connexion.
Je vous laisse coder ;) ...

void envoi(int sock,char *buf)
{
    int taille=sizeof(buf);
    send(sock,&taille,sizeof(taille),0);
    send(sock,buf,taille,0);
}

void reception(int sock,char *buf)
{
    int taille=0;
    if(buf)
        free(buf);
    recv(sock,&taille,sizeof(int),0);
    buf=(char*)malloc(taille*sizeof(char));
    recv(sock,buf,sizeof(buf),0);
}

DS à DS

Nous allons utiliser une bibliothèque qui permet une connexion DS à DS. Elle s'appelle la libLobby. Elle permet des connexions directes ou alors via des rooms.
Normalement, vous l'avez sous forme de zip dans votre disque dur. Allez dans devkitpro\Other libs\ et normalement vous avez un zip dont le nom commence par "liblobby". Ouvrez-le (au besoin, dézippez-le), allez dans le dossier "include" et copiez 802.11.h, lobby.h ainsi que MessageQueue.h.
Maintenant, nous allons modifier notre Makefile. Commentez ARM7_SELECTED = ARM7_BASIC et décommentez #ARM7_SELECTED = ARM7_MP3_LIBLOBBY .

Dans votre source, il faut inclure les trois headers :

#include "MessageQueue.h"
#include "802.11.h"
#include "lobby.h"

Maintenant on va initialiser la libLobby avec :

IPC_Init();
IPC_SetChannelCallback(0, &LWIFI_IPC_Callback);
LOBBY_Init();

Maintenant, il faut donner une fonction de réception à la libLobby. En fait, les communications sont non-bloquantes et il n'y a pas de fonction de réception. Vous devez la créer, puis la passer en argument à void LOBBY_SetStreamHandler(unsigned short streamID, LOBBY_STREAMHANDLER_PROC callback); pour indiquer à la libLobby que votre fonction doit être appelée à chaque réception.
Elle doit être de type void et doit avoir trois arguments (dans l'ordre) :

Voici donc un exemple de fonction de réception :

void reception(unsigned char *message,int taille,LPLOBBY_USER from)
{
    PA_OutputText(0,0,0,"Recu : %s",(char*)message);
}

Maintenant nous allons indiquer à la libLobby que la fonction reception est la fonction de réception :

LOBBY_SetStreamHandler(1,&reception);

Qu'est-ce que ce nombre avant le pointeur sur recepetion ?

Il s'agit de l'indentifiant d'un canal. D'après la source, ce nombre peut aller jusqu'à 32 768 mais le canal 32 767 est réservé.

Mais à quoi servent ces canaux ?

En fait, nous le verrons juste après, quand on fait un envoi, il faut préciser le canal. Ceci permet 32 767 fonctions de réceptions différentes. A vous de les gérer pour bien séparer les tâches.

A présent il faut connaître le nombre de joueurs connectés avec unsigned short LOBBY_GetNumberOfKnownUsers(void); .

Les fonctions sur les utilisateurs

On peut obtenir un utilisateur par son identifiant avec LPLOBBY_USER LOBBY_GetUserByID(unsigned short id); id est compris entre 0 et la valeur de retour de LOBBY_GetNumberOfKnownUsersnon inclus.

On peut aussi obtenir un utilisateur par son adresse MAC (à voir dans un jeu officiel) avec LPLOBBY_USER LOBBY_GetUserByMAC(unsigned char *adresse); .

Pour savoir si un utilisateur est déconnecté on utilise int LOBBY_IsTimedOut(LPLOBBY_USER utilisateur); . Cette fonction renvoie 1 s'il est déconnecté ou 0 dans le cas contraire.

Pour obtenir le nom d'un utilisateur on utilisera const char *LOBBY_GetUserName(LPLOBBY_USER utilisateur); .

Maintenant que nous connaissons beaucoup de fonctions concernant les utilisateurs, nous allons voir comment leur envoyer des messages.
Il existe la fonction void LOBBY_SendToUser(LPLOBBY_USER user,unsigned short canal,unsigned char *message,int taille); . C'est là qu'intervient la variable canal. Supposons que vous créez un jeu de combat en temps réel multijoueur. Vous pouvez utiliser la variable canal pour séparer les types d'envois. Par exemple le canal 1 peut servir d'envoi/réception d'infos concernant les autres joueurs connectés, et le canal 2 peut servir d'envoi/réception de messages du système de chat que vous avez récemment intégré au jeu ;) . Ca nous simplifie la vie, et comme nous avons quelques dizaines de milliers de canaux, on peut dire que vous n'avez pas de limite :) .

Vous pouvez aussi envoyer un message à tous les utilisateurs connus avec void LOBBY_SendToAll(unsigned short canal, unsigned char *message, int taille); .

Les rooms

Les rooms sont caractérisées par leur nom, le nombre d'utilisateur maximal, l'identifiant du jeu et sa version.

A quoi servent l'identifiant du jeu et sa version ?

L'identifiant du jeu est un nombre qui indique le jeu utilisant la room. Sa version permet de connaître le fonctionnement du jeu. En fait, si les deux joueurs ont le même jeu, et si chez un joueur, l'identifiant est le même que chez un autre, on peut penser que le fonctionnement est le même (sauf si la version est différente).

Alors créons notre room avec void LOBBY_CreateRoom(char *nom,int maxUsers,unsigned short gameCode,unsigned short version); .
On peut définit la visibilité de la room avec void LOBBY_SetRoomVisibility(int visible); . Si visible vaut 0 la room sera invisible. A l'inverse, si visible vaut 1 la room sera visible. Seul le créateur de la room peut effectuer cette action.

Pour savoir combien il y a de rooms visibles, on fera appel à unsigned short LOBBY_GetNumberOfKnownRooms(void); .

Pour obtenir une room, on utilisera LPLOBBY_ROOM LOBBY_GetRoomByID(unsigned long id);

Vous pouvez aussi obtenir une room à partir de l'adresse MAC de la DS du créateur de la room :D en utilisant LPLOBBY_ROOM LOBBY_GetRoomByMAC(unsigned char *mac); .

Si vous voulez obtenir la room d'un utilisateur en particulier, il faudra appeler la fonction LPLOBBY_ROOM LOBBY_GetRoomByUser(LPLOBBY_USER user); .

De même, si vous voulez obtenir une room à partir de son gameCode, il faudra appeler la fonction LPLOBBY_ROOM LOBBY_GetRoomByGame(LPLOBBY_ROOM anchor, unsigned short gameCode); . La variable anchor sert à déterminer à partir de quelle room on lance la recherche. Si c'est 0 ce sera la première trouvée.

Pour savoir combien il y a d'utilisateurs dans une room, on va utiliser unsigned short LOBBY_GetUsercountInRoom(LPLOBBY_ROOM room); .
De même, pour obtenir le nombre maximal d'utilisateurs, on appellera unsigned short LOBBY_GetMaxUsercountInRoom(LPLOBBY_ROOM room); . Ces deux fonctions sont très importantes car elles permettent de trouver le nombre de places restantes dans une room.

Vous pouvez obtenir le nom d'une room avec char* LOBBY_GetRoomName(LPLOBBY_ROOM room); , son gameCode avec unsigned short LOBBY_GetRoomGameCode(LPLOBBY_ROOM room); et sa version avec unsigned short LOBBY_GetRoomGameVersion(LPLOBBY_ROOM room); .

Avec les fonctions vues ci-dessus, vous pouvez choisir dans quelle room aller. Si vous voulez vous joindre à une room, il faudra utiliser void LOBBY_JoinRoom(LPLOBBY_ROOM room); . Si vous voulez la quitter, il faudra appeler void LOBBY_LeaveRoom(void); .

Vous pouvez envoyer des messages à tous les utilisateurs d'une room avec void LOBBY_SendToRoom(LPLOBBY_ROOM room, unsigned short canal, unsigned char *message, int taille); . Mais vous pouvez ne pas envoyer de message à tous. En fait, via l'argument canal, vous pouvez faire le tri. Par exemple, si vous voulez envoyer des messages au créateur de la room sans que les autres utilisateurs ne les voient, il faut créer un canal chez le créateur qui n'est pas créé chez les autres. Ainsi, par le biais de ce canal, seul le créateur de la room pourra recevoir les messages...

Dans vos boucles, au même titre que PA_WaitForVBL, il faudra systématiquement mettre :

IPC_RcvCompleteCheck();
LOBBY_Update();

Sinon vous ne pourrez jamais ni détecter des utilisateurs et des rooms, ni envoyer ou recevoir de messages...

L'émulateur

J'ai une bonne nouvelle : l'émulateur No$GBA supporte très bien la liblobby ! Pour utiliser plus de Nintendo DS au sein de votre émulateur, faites F11 et rendez-vous dans la section "Number of Emulated Gameboys", et modifiez le nombre en mettant 2 à la place. Faites OK puis Options->Save Options. Fermez NO$GBA et lancez votre homebrew de nouveau. A présent, vous pouvez constater la résultat :magicien: .
Par contre, si vous voulez revenir à une seule Nintendo DS, il faut aller dans le dossier de NO$GBA et ouvrir NO$GBA.ini. Cherchez une ligne commençant par :

Citation : NO$GBA.ini

Number of Emulated Gameboys ==

Remplacez cette ligne intégralement par :

Citation : NO$GBA.ini

Number of Emulated Gameboys == -One Machine

Et voilà ;) !

Les connexions sur DS n'ont à présent plus aucun secret pour vous ;) !

Voilà, vous pouvez faire de bons jeux poussés sur votre Nintendo DS préférée :soleil: !


Les bases de PAlib Annexes

Annexes

Les techniques avancées

Ici se trouveront des systèmes particuliers dans un domaine précis, non essentiels au développement sur Nintendo DS.

Les labyrinthes

Nous allons apprendre comment utiliser les labyrinthes pour guider des personnages contrôlés par l'IA, pour éviter, entre autres, de marcher sur des murs ou d'autres personnages :p ...

Les labyrinthes

Vous vous doutez sûrement qu'il est difficile de créer un système aussi complexe qu'un labyrinthe. Ici, il n'est pas question de générer un labyrinthe, mais de pouvoir créer une IA se déplaçant à l'intérieur, sans jamais heurter les murs qui le composent, en arrivant toujours à l'endroit voulu (sauf s'il est entouré de murs :-° ). Mais en fait, grâce à PALib, cette tâche est rendue la plus simple possible.
En effet, il existe plusieurs méthodes pour aller d'un point à un autre. Celle utilisée par PALib est la méthode A* (A star). Cette méthode allège les temps de calcul, mais le chemin emprunté n'est pas forcément le plus court (ça dépend de la qualité de l'algorithme). Un tutoriel sur le site à cette adresse montre en détail l'algorithme A*.

Il faut penser en tiles : définir la taille d'un carré représentant l'unité utilisée par le labyrinthe (par exemple 8x8). Puis, il faut songer aux dimensions du labyrinthe. Elles peuvent être tout ce que vous voulez, mais le labyrinthe doit être absolument un carré. Donc, si vous utilisez un rectangle, vous devrez déclarer le carré à la longueur maximale de la scène, et donc certaines tiles ne seront pas utilisées, mais pas de problème ;) ...

Donc nous allons initialiser le système de labyrinthes avec void PA_InitAstar(u16 lx, u16 ly); lx est le nombre de tiles dans les X du labyrinthe et ly est le nombre de tiles dans les Y du labyrinthes.
Par exemple, si votre scène est un écran, et que vos tiles doivent mesurer 8*8 pixels, alors vous initialiserez le système ainsi :

PA_InitAstar(32,32); //N'oubliez pas, c'est un carré !

Ou si vous préférez les divisions, voici l'équivalent :

PA_InitAstar(256/8,256/8);

Cette fonction initialise une variable globale : maze (tableau de u16).

Comment utiliser cette variable ?

Pour y accéder, il faut faire :

maze[x/TAILLE_TILE][y/TAILLE_TILE]

Maintenant, il faut mettre notre labyrinthe dans la variable maze.
Il existe un "code" permettant de mettre notre labyrinthe dans maze :

Donc, si nos tiles mesurent 8*8 pixels, et que le tile (32;16;40;24) doit être un mur, il faudra écrire :

maze[32/8][16/8]=5;

Ce qui est l'équivalent de :

maze[4][2]=5;

Comment connaître le déplacement à effectuer ?

Avec s8 PA_Astar(u16 lx, u16 ly); prenant les mêmes arguments que void PA_InitAstar(u16 lx, u16 ly); .
La fonction retourne un entier qui peut être analysé comme ceci (pour des tiles de 8*8 pixels) :

switch(PA_Astar(256/8,256/8))
{
    case 1: // Gauche
        x-=8;
        break;
    case 2: // Droite
        x+=8;
        break;
    case 3: // Haut
        y-=8;
        break;
    case 4: // Bas
        y+=8;
        break;
    default:
        break;
}

Voilà, c'est tout, vous êtes dorénavant capable de réaliser une IA pouvant se déplacer dans un labyrinthe ^^ ...

Image utilisateur

Le tutoriel n'est pas fini ! Les chapitres sortent lorsque nous avons le temps de les rédiger, patience... :p


Les techniques avancées