Table des matières

Jour 1 - Installation et compilation ← Précédent

Jour 2 - Apprentissage du C/C++

Maintenant que vous avez vu que le développement amateur pour DS peut être très simple, je parie que vous voulez savoir comment le codage en C fonctionne, pas vrai ? Ici, on va voir des notions basiques de C que vous aurez besoin d’utiliser. Ceci ne sera pas aussi complet que les tutoriaux sur le C qui se trouvent un peu partout sur le web ou encore des livres de C, mais j’espère que cela sera assez pour que vous compreniez et que vous puissiez développer vos propres jeux (ou applications) puis éventuellement vous améliorer par vous-mêmes. Voici les 2 premières notions que je sens que vous devriez bien connaître le plus rapidement possible : les variables et les boucles. Quand ceci sera fini, vous pourrez soit en apprendre plus sur le C en continuant ce tutorial, soit en apprendre plus sur le développement DS avant de finir de lire ce tutoriel.

... =)

Variables

Elles possèdent un nom, et une valeur. Elles sont quelque chose d’essentiel dans le langage C. Pourquoi me demanderez vous ? Parce que si vous ne les aviez pas, vous ne pourriez pas bouger un sprite, par exemple! Tout serait pré-réglé, il n’y aurait pas d’interactions, RIEN ! Il y a deux étapes dans l’utilisation d’une variable : la déclaration, ainsi que... son utilisation :)

Déclaration

Mais d’où cela vient-il ? En quoi est-ce utile ? Quand votre programme est compilé, le compilateur a besoin de savoir qu’est-ce que la variable... Pour vous, une variable est seulement un nom, comme sprite_numero, ou sprite_position, ou peu importe... pour le compilateur, c’est un brin plus compliqué puisqu’il y a différents types de variables. Je vais essayer de rendre cela simple.

D’abord, vous devez respecter certaines règles : le nom d’une variable ne peut etre uniquement composé de lettre, majuscules ou minuscules. Vous avez le droit de mettre des chiffres, mais pas au premier caractere. Ensuite, si vous voulez mettre 2 mots dans votre variable, d’abord vous n’avez n’avez pas le droit au espaces, et ensuite il est conseillé (pour pouvoir se retrouver lorsque votre code fera plus de 1000 lignes) de mettre toutes les lettres en minuscules sauf les premieres de chaque mot à partir du 2eme. ex : longueurDuVaisseauSpatial

Il y a différents types de variables :

En gros, si vous ne savez pas quoi utiliser, le meilleur choix serait d’utiliser des variables floats... Elles sont les plus intuitives, même si c’est le type de variable le plus lent. Plus tard, je vais vous expliquer pourquoi utiliser une sorte de variable plutôt qu’une autre, et compenser le fait qu’il n’y pas de virgule dans le nombre

Une variable float est déclarée de cette façon :

 float nomDeVariable;

Float est un type de variable, le nom est le nom que vous lui donnez, et le ‘;’ est là puisque chaque ligne de code C executée doit être terminée par un point virgule. Si vous ne le mettez pas, le code ira sur la ligne suivante.

Maintenant, si vous avez besoin d’un nombre sans virgule (par exemple, pour mémoriser une date d’anniversaire !), vous pouvez utiliser une variable integrer. Les variables integrer peuvent être de 8, 16 ou 32 bits (ou 64). Voici un tableau montrant les valeurs possibles selon le type de variable utilisée (signée ou non).

Unsigned Signed
8 bits 0 → 255 -128 → 127
16 bits 0 → 65 535 -32 768 → 32 767
32 bits 0 → 4 294 967 295 -2 147 483 648 → 2 147 483 647

Comme vous pouvez le voir, plus il y a de bits, plus le nombre peut être grand. Si vous ne savez pas quoi utiliser mais que vous voulez une variable integrée, utilisez la declaration suivante :

s32 nomDeVariable;

s veut dire signé ( u voudrait dire unsigned ), et 32 veut dire 32 bits. Avec cela, vous devriez en avoir assez :)

Globales et locales

Une autre chose à savoir à propos des variables dont on n’a pas encore parlé... Les variables peuvent être soit globales, soit locales... Quelle est la différence ?

Remarquez que s32 HorizontalTile = 0; et s32 VerticalTile = 0; sont en dehors de main. Ce sont des variables globales, variables disponibles à chaque partie du programme.

void MyFunction(void)
{
   s32 variable = 0;
}

Cette variable est accessible SEULEMENT dans cette fonction, et nulle part ailleurs. Chaque fois que la fonction est appellée, la variable est recréée et remise à zéro...

Finalement... Si vous hésitez entre une variable locale ou globale... choisissez la locale ! Attention n’abusez pas des variables globales, la facilité réduit la performance et elles sont souvent source d’embrouilles ;) .

Opérations

Vous admettrez que les variables sont inutiles si vous ne pouvez rien en faire. Vous pouvez, bien sur, les utiliser dans des opérations basiques (nous n’en diront pas beaucoup plus). Les quatre opérations basiques sont + - * et / (addition, soustraction, multiplication, et division). Soyez conscients (comme Jean-Claude-Claude Van Damme) que les divisions sont vraiment lentes... Pour donner à une variable une nouvelle valeur, il faut employer le symbole ‘=’ : variable = valeur;

NB : l’opérateur ‘=’ n’a pas la même signification en mathématique qu’en informatique, attention a ne pas confondre les deux, je m’explique :

En suivant une logique mathématique, l’opérateur ‘=’ signifie que les deux valeurs à gauche et à droite sont équivalentes.

En suivant la logique informatique, l’opérateur ‘=’ signifie que l’on écrit la valeur à droite du ‘=’ dans la variable située à sa gauche ;) .

Exemple :

 
s32 maVariable;
maVariable = 12 + 15; // signifie que l'on écrit la valeur 12 + 15 ( 27 )
                      // dans la variable 'maVariable

Voici un exemple (ce n’est pas un code DS, mais ne vous en inquiétez pas)

float PI = 3.14;
float radius = 10;
float perimeter;
 
perimeter = 2*PI*radius;

Vous devez admettre que cela ne pourrait pas être plus facile...

Notez également qu’il existe une autre syntaxe pour les opérations qui s’écrit comme ceci :

variable += 2;

(2, ou un autre chiffre...) cela signifie variable = variable + 2; c’est juste plus court. Les autres opérations sont -=, *=, et bien sur /=.

Une dernière variante ressemble à ça:

variable++;
variable--;

variable ++; signifie que l’on incrémente la variable “variable” d’une unité. On pourrait l’écrire de la façon suivante : variable = variable + 1; ou encore : variable += 1;

variable –; signifie que l’on décrémente la variable “variable d’une unité”. On pourrait l’écrire de la façon suivante : variable = variable - 1; ou encore : variable -= 1;

Écrire ++ ou – est juste plus :-)

Décalages de bits

C’est plus rapide que la multiplication ou la division:

u8 num = 2 << 2;

Dans ce cas, num = 8.

Utiliser le décalage de bit comme ça (x « y) revient à faire x * 2^y. Utiliser le décalage de bit comme ça (x » y) revient à faire x/2^y.

if

Voila un exemple, un simple bout de code, pas compilable, seulement pour vous montrer...

if(Stylus.Held == 1)
{
    PA_OutputSimpleText(0, 0, 0, "Le stylet est appuyé !!!");
}

L’instruction if traite des booléens, en C, un booléen vaut 0 si il est FAUX et est différents de 0 si il est VRAI. Donc, 0 = FAUX et 1 = VRAI.

Comme vous pouvez le voir, c’est facile. Stylus.Held est une variable qui est à 0 (FAUX) par défaut, et qui passe à 1 (VRAI) si le stylet touche l’écran de la DS... Ce simple bout de code compare Stylus.Held à 1 (la commande ‘==’ contrôle l’égalité... ne mettez pas ‘=’ à la place, parce que nous avons vu que : ‘variable = 1’ attribue la valeur 1 à la variable)

Ce code pourrait avoir été écrit avec ‘if (Stylus.Held)’, parce que si vous ne mettez pas un contrôle d’égalité, ‘if’ vérifie si la valeur est VRAIE (différente de 0 ou égale à 1) et exécute le code dans les accolades si la condition est remplie...

Comme vous l’avez probablement deviné, ‘==’ n’est pas le seul contrôle que vous pouvez faire. Les autres sont :

Si vous mettez ‘!’ devant un opérateur booléen, il transforme le faux en vrai et le vrai en faux.

Maintenant, voici un bout de code :

if (Stylus.Held) 
     PA_OutputSimpleText(0, 0, 0, "Le stylet est appuyé !!!");
 
if (Stylus.Held == 0)   
     PA_OutputSimpleText(0, 0, 0, "Le stylet n'est pas appuyé !!!");

Il y a un commentaire à faire. Comme vous pouvez le voir, ici, je n’ai pas mis les accolades... Si vous ne les mettez pas, seule l’instruction après ‘if’ est exécutée... En second lieu, je pourrais avoir mis 2 choses au lieu de if (Stylus.Held == 0) :

if (Stylus.Held)
     PA_OutputSimpleText(0, 0, 0, "Le stylet est appuyé !!!");
else   
     PA_OutputSimpleText(0, 0, 0, "Le stylet n'est pas appuyé !!!");

Si cette condition est remplie, faites ça, sinon (else), fait cette autre chose... Assez facile ! De la même manière que ‘if’, ‘else’ peut fonctionner avec des accolades (if{...} else{...}). Maintenant, que se passe-t-il si vous mettez ‘else’ pas directement après ‘if’ ? Le programme ne se compilera pas et vous obtiendrez une erreur.

/!\ Attention ! /!\

Si vous mettez trop de if sans else, le programme se compilera mais en testant, les instructions ne seront pas prises en comptes ! Pourquoi ? La DS est très limitée : résultat ? Si il y a trop de if, pour la DS c’est lourd, donc si vous mettez des else, ca allège. Réfléchissez ! En codant :

if(Pad.Held.Up) 
{ 
    moveUp(); 
}
if(Pad.Held.Down) 
{ 
    moveDown(); 
}

Vous remarquerez que l’on ne va pas appuyer sur les deux en même temps ! Donc il faut mettre un else. ;-) Cela allégera beaucoup votre code. :-)

switch...case

Je ne suis pas sur de cette partie donc si vous voyez des fautes n’hésitez pas à les corriger.

Cette instruction peut être traduite par “selon le cas”

Switch peut être utilisé pour executer des instructions différentes selon des conditions qui seront, elles aussi, différentes. Chaque case est comme un if, qui correspond à une valeur. Utilisez switch sur une variable ou une valeur et chaque cas sera testé et les executions faites en fonction de ce cas. Il est judicieux d’utiliser la commande Default à la fin d’un switch afin qu’une instruction soit executée lorsque qu’aucun des case n’est vrai.

Voici un exemple...

int testNum = 3;
 
switch(testNum)
{
    case 1: //execute if testNum is 1
        PA_OutputSimpleText(1,0,0,"Test Num is 1");
        break;
 
    case 2: //execute if testNum is 2
        PA_OutputSimpleText(1,0,0,"Test Num is 2");
        break;
 
    case 3: //execute if testNum is 3
        PA_OutputSimpleText(1,0,0,"Test Num is 3");
        break;
 
    default: //execute no matter what.
        PA_OutputSimpleText(1,0,0,"None is true");
        break;
};

Note: Quand une condition case est vraie, l’instruction est executé mais également tous les cas suivants. C’est pourquoi vous aurez besoin de break, cette instruction permet de quitter la boucle switch. Il peut arriver dans certains cas que vous n’ayez pas besoin de mettre des break partout. Par exemple ...

 
switch(testNum)
{
    case 1: //execute if testNum is 1
        PA_OutputSimpleText(1,0,0,"Case 1 executed");
 
    case 2: //execute if testNum is 2
        PA_OutputSimpleText(1,0,0,"Case 2 executed");
        break;
 
    case 3: //execute if testNum is 3
        PA_OutputSimpleText(1,0,0,"Case 3");
        break;
 
    default: //execute no matter what.
        PA_OutputSimpleText(1,0,0,"All other cases");
        break;
};

REMARQUE : Il est préférable de rajouter un point-virgule à la fin du switch même si cela n’est pas obligatoire. Cela peut éviter des bugs par la suite.

Dans cet exemple, si le cas 1 executé et comme il n’y a pas de break cela va executer le cas 1 ET 2. Si le cas 2 est vrai, cela n’executera QUE case 2 et si le cas 3 est vrai cela executera QUE case 3 et pour toutes les autres valeurs, l’execution defaut se fera.

while

while

while (en français “tant que”) est un autre objet conditionnel intéressant... Il fonctionne à peu près comme la fonction if, à ceci près qu’il boucle et redémarre tant que la condition en argument est vérifiée...

while (!Stylus.Held)
{
    PA_OutputSimpleText(0, 0, 0, "Touche l'écran pour continuer");
    PA_WaitForVBL();
}
PA_OutputSimpleText(0, 0, 0, "c'est parti !");

Ce code très basique n’en est pas moins très utile. Que fait-il? Eh bien, lisons-le donc ligne par ligne ... Il vérifie d’abord si le stylet touche l’écran... Si ce n’est pas le cas (en effet, Stylus.Held est précédé d’un “!”), il effectue la boucle, c’est-à-dire affiche qu’il faut toucher l’écran, puis attend la prochaine VBL (il se synchronise avec la vitesse de l’écran, soit 60 im/s ). Donc il vérifiera si l’on touche l’écran 60 fois par seconde, et ce jusqu’à ce qu’il soit enfin touché ! Une fois que vous touchez l’écran, il arrête de boucler, et continue, en affichant “c’est parti”.

Ce bout de code est donc utile lors de l’affichage d’un écran d’attente :-)

do...while

Voici une variante de while : do{...}while(...);

Il fonctionne de la même façon, mais au lieu de vérifier directement s’il va boucler, il effectue d’abord une fois le code, puis vérifie et boucle:

do
{
    PA_OutputSimpleText(0, 0, 0, "Toucher l'écran pour continuer");
    PA_WaitForVBL();
}
while (!Stylus.Held);
PA_OutputSimpleText(0, 0, 0, "C'est parti !");

Ici, même si le stylet touche déjà l’écran, il va afficher “toucher l’écran” et attendre 1 VBL...

Quand on utilise une boucle do{}while ou while{}, il y a une chose qu’il faut impérativement avoir en tête: dans cette boucle, il doit y avoir une condition d’arrêt (un moyen d’en sortir)... sinon, le programme bouclera à l’infini, et ne pourra rien faire d’autre. Voici un exemple de ce qu’il ne faut pas faire:

s32 variable = 1;
while (variable == 1)
{
    PA_OutputSimpleText(0, 0, 0, "Toucher l'écran pour continuer");
    PA_WaitForVBL();
}
PA_OutputSimpleText(0, 0, 0, "Go!");

Dans ce code, rien ne change la valeur de la variable, donc on ne sortira jamais de la boucle!!!!

for

Cette dernière boucle est en fait juste une variante du while{}, mais est assez utile. Je l’utilise tout le temps. Au lieu juste d’avoir une condition, il a 3 parties, par exemple :

s32 i;
for (i = 0; i < 120; i++){
    PA_WaitForVBL();
}

Maintenant, analysons ces 3 parties...

En utilisant while, vous pourriez le remplacer par ça :

s32 i;
i = 0; // Initialisation
while(i < 120)  // Condition
{
    PA_WaitForVBL();
    i++;  // Troisième partie
}

Ce qui n’est pas mal à propos de cette commande, c’est que dans l’exemple que je vous ai montré, ça vous donne une facilité pour faire la même chose un nombre de fois donné... Le premier exemple exécutera le WaitForVBL 120 fois avant de quitter, rien de plus, rien de moins... Et à quoi correspondent les 120 frames ? À 60 frames par seconde ? Bien, 2 secondes !

Avec ces notions, vous devriez déjà pouvoir comprendre un peu plus de choses, et si vous le souhaitez, vous pouvez passer au tutorial sur les sprites avant de finir ce tutorial... De cette façon vous verrez un peu plus les possibilités de développement sur DS, de quoi vous motiver :-)

break

Parfois, vous voulez quitter de force une boucle. Si vous souhaitez le faire, employez break...

while(true)
{
    break;
}

À première vue, il semble qu’une boucle est infinie, mais break marque l’arrêt du programme immédiatement. Vous pouvez le mettre dans des branches conditionnels, et il stoppe immédiatement la boucle. Toutefois il est préférable de l’utiliser le moins possible tout comme les goto qui permettent d’acceder à une ligne du code.

Fonctions

Maintenant, voyons une partie plutôt importante de programmation de C : les fonctions !

Les Fonctions de base

Les fonctions sont des morceaux de code à exécuter. Ce main que vous avez vu plus tôt est assez curieusement, une fonction. Les fonctions exigent un type de retour, un nom de fonction, et quelques arguments. Après, il devrait y avoir une ouverture d’une fonction, le code, et puis la fermeture de la fonction.

typederetour nomdelafonction(arguments)
{
    //code
}

Le type de retour est ce qui est retourné par la fonction. Si votre fonction ne doit rien renvoyer, utilisez void. Le type de retour peut être n’importe quel type variable. Pour renvoyer une variable, employez return.

s32 obtenirNombre()
{
    return 2;
}

obtenirNombre est le nom de la fonction. Il n’y a aucun argument dans cette fonction. Elle renvoie le nombre 2. Gardez à l’esprit, cependant, que même les variables entières elles-mêmes peuvent être retournées.

Pour utiliser des arguments, vous devez mettre un type et un nom pour cette variable.

s32 obtenirNombre(s32 nb)
{
    return nb;
}

Tout ces fonctions étaient des fonctions basiques, mais vous pouvez en avoir de plus complexes. Voyons si nous pouvons créer une fonction qui ajoute 2 nombres donnés, et qui renvoie le résultat :

s32 AjoutNombres(s32 premier, s32 second)
{
    return (premier + second);
}

Ici, vous voyez que pour séparer plusieurs arguments, vous employez une virgule... Employer cette fonction dans un programme est simple :

nombre = AjoutNombres(3, 6);

J’ai mis 3 et 6 comme arguments, mais je pourrais aussi bien avoir mis variable1 et variable2, ou une combinaison de nombres et de variables.

Déclarer une fonction

La seule chose dont nous n’avons pas encore parlé à propos des fonctions, c’est la facon correcte de les déclarer. Comme vous pouvez l’imaginer, vous ne pouvez pas créer une nouvelle fonction en plein milieu de la fonction main par exemple... Alors voici le moyen correct de déclarer une fonction :

void myFunct(s32 num);
 
int main()
{
    myFunct();
 
    return 0;
}
 
void myFunct(s32 num)
{
}

C’est juste un exemple qui ne fait rien du tout, mais c’est simplement pour mieux comprendre comment faire. Ce que vous voyez en premier, c’est le “void myFunct(s32 num);” : on appele cela un prototype. Il sert à indiquer aux autres fonctions que la fonction “myFunct” existe, car si vous créez la fonction après l’avoir utilisée (dans le déroulement du fichier), vous obtiendrez des erreurs : déclarer une fonction permet de la créer plus tard.

Ensuite, vous pouvez voir la fonction main, dans laquelle on voit un appel à la fonction “myFunct”, précedemment déclarée.

Finalement, on trouve le code de la fonction “myFunct” déclarée plus haut. Elle est crée presque comme elle est déclarée, mais cette fois ci on ne mets pas de point-virgule à la fin de la premiere ligne, et on ajoute des accolades autour du code contenu dans la fonction ! :)

Surcharge des Fonctions (Uniquement en C++)

Si vous voulez rester dans la simplicité, vous n’avez pas besoin de la surcharge de fonction, donc sautez cette partie et allez directement aux tableaux... Mais si vous voulez tout savoir, lisez ceci :-)

Vous pouvez donc aussi surcharger des fonctions. Cela arrive quand deux (ou plus) fonctions ont le même nom, mais des arguments différents. Je veux dire par là que le TYPE et l’ORDRE des arguments est différent. Voici le nom complet de la fonction précédente.

addNumbers(s32,s32);

Les types sont différents, mais la méthode ne l’est pas pour autant.

Voici deux fonctions surchargées et différentes:

s32 addNumbers(s32 premier, s32 second)
{
    return (premier + second);
}
 
double addNumbers(double premier, double second)
{
    return (premier + second);
}

Note: Un type de retour différent ne suffit pas à surcharger des fonctions. il faut des arguments différents.

Les tableaux

Vous avez vu comment utiliser les variables, les boucles conditionnelles, les sprites et les backgrounds... C’était super mais maintenant nous allons voir une autre partie importante du C: les tableaux. Qu’est-ce qu’un tableau? C’est une longue liste de variables.

Déclarer un tableau est très simple. C’est comme déclarer une variable normale, mais en ajoutant ‘[’, la taille, et ‘]’ !

float MyArray[10000];

Ceci déclare un tableau de 10000 floats !

Vous pouvez trouver tout seul des tas d’utilités aux tableaux... non? :-P

Ok, je vais vous en donner un qui ne plait pas beaucoup, mais qui aide le code à s’exécuter beaucoup plus vite... comme vous le savez peut-être, certaines opérations sont... longues... la division par exemple... Donc une façon de les accélérer est d’avoir, au début de votre programme, un grand tableau avec toutes les divisions précalculées... compris ? :-P

float Division[1000]; // Tableau des divisions de 1 à 1000
s32 i; // Variable temporaire du for
 
for (i = 0; i < 1000; i++)
{
   Division[i] = 1/(i+1);
}

Cela remplit le tableau des divisions avec les nombres de 1 à 1000 pré-divisés (pas 0 car on ne peut pas diviser par 0, d’où la division par (i+1)). Pour l’utiliser, il suffit de faire:

float number = 10;
number = number * Division[3];

ça prend un nombre et le divise par (3+1), donc 4... les multiplications sont rapides et les divisions lentes, donc c’est plus rapide de faire cela que de faire directement number = number/4;

Je sais, ce n’est pas très intuitif, alors on va l’utiliser avec une fonction cool :

float Divide(float number, s32 divisor)
{
    return (number * Division[divisor-1]);
}

Pour l’utiliser, tout ce que vous avez à faire est:

float number = Divide(10, 3);

je pense que c’est mieux! :-)

Si vous voulez ne pas avoir à vous poser de question pour savoir si votre division est dans le tableau ou pas, vous pouvez aussi faire ceci :

float Divide(float number, s32 divisor)
{
    if(divisor <= 1000)
    {
        return (number * Division[divisor-1]);
    }
    else
    {
        return (number/divisor);
    }
}

Dans ce cas, si vous divisez par un nombre trop grand (par exemple 2000), la fonction retournera quand même le bon résultat.

Les structures

Les structures sont quelque chose que je trouve essentiel pour organiser votre projet et pour le rendre lisible... Elles sont un peu différentes des tableaux, et peuvent même être combinées avec eux.

Pour faire simple, une structure est un groupe de variable sous un seul nom. Je vous ferai voir leurs avantages grâce à quelques exemples.

Disons que j’ai un jeu de vaisseau spatial. Dans ce jeu, mon vaisseau principal (nous l’appellerons bob) a une position (des variables x et y), une vitesse horizontale et verticale sur l’écran (hspeed et vspeed), et c’est le sujet pour maintenant. Ainsi, déclarer ses variables voudrait :

float x, y, hspeed, vspeed;

(Si plusieurs variables ont le même type, vous pouvez les déclarer sur la même ligne comme ça...) Ok, je dois admettre, que ce n’est pas trop compliqué, et que vous n’aurez probablement pas besoin des structures...

Maintenant, disons que nous ajoutons un ennemi, celui-ci a une position et une vitesse exactement de la même manière... x et y étant prise, mettons donc enemx, enemy, etc... La déclaration entière devient :

float x, y, hspeed, vspeed; // Pour le vaisseau principal
float enemx, enemy, enemhspeed, enemvspeed; // Pour le vaisseau ennemi

C’est encore correct, mais très laid... Maintenant, que se produit-il si nous avons plusieurs vaisseaux ? Nous pourrions faire enem1x, enem2x, etc... Mais vous devez admettre que ça deviendrait plutôt stupide et compliqué...

Declarer des structures

Quelle tête cela aurait-il en utilisant une structure ? Premierement, nous allons creer une structure en utilisant la commande typedef, qui permet de declarer un nouveau type (donc vous aurez comme types : float, s32 et maintenant le nom de la nouvelle structure).

typedef struct
{
    float x, y, hspeed, vspeed;
} shipinfo;

Donc, cela créé un nouveau type, appelé shipinfo. Son utilisation est simple : commencez par déclarer une variable utilisant ce type, comme mainship, puis utilisez-la :

shipinfo mainship;
mainship.x = 128;
mainship.y = 96;
...

Comme vous pouvez le voir, vous avez créé une nouvelle variable (mainship) qui est du type ‘shipinfo’. Pour accéder aux variables contenues en son sein, utilisez le ‘.’, comme mainship.hspeed, etc...

Tableau de Structures

Maintenant, la ou ca devient tres joli, c’est quand on combine les structures et les tableaux. Je posterai le code, et vous devriez obtenir ça tout de suite :

typedef struct
{
    float x, y, hspeed, vspeed;
} shipinfo;  // nouveau type pour vaisseaux...
 
shipinfo mainship;  // votre vaisseau principal
shipinfo ennemy[10]; // tableau de structures !

Ici, vous avez votre premier tableau de structures ! Ces quelques lignes créent 10 informations de vaisseaux ennemis différentes, numérotées de 0 à 9, et accessibles par

ennemy[0].x = 128; 
ennemy[3].x = 12;
...

Cool !!! C’est une meilleure méthode que d’appeler toutes vos variables enem105x, etc... non ? Mais il y a d’autres choses... que se produit t-il si nous combinons les tableaux de structures avec les boucles ? C’est quelque chose de facile... Rappelons-nous qu’à chaque frame, vos vaisseaux ennemis se déplacent avec hspeed et vspeed (horizontalement et verticalement). Si vous voulez les déplacer, vous devrez faire

ennemy[0].x += ennemy[0].hspeed; // la même chose que ennemy[0].x = ennemy[0].x + ennemy[0].hspeed; vous vous souvenez ?
ennemy[0].y += ennemy[0].vspeed;
 
ennemy[1].x += ennemy[1].hspeed; 
ennemy[1].y += ennemy[1].vspeed;
 
ennemy[2].x += ennemy[2].hspeed; 
ennemy[2].y += ennemy[2].vspeed;
 
ennemy[3].x += ennemy[3].hspeed; 
ennemy[3].y += ennemy[3].vspeed;

Ici, j’ai fait ça seulement pour... 4 vaisseaux (0-3), et ça prend déjà quelques lignes... Vous imaginez refaire ça pour... 10 vaisseaux ? 100 vaisseaux ? Pas moi !

Boucles For et structures

En utilisant For, c’est plus facile:

s32 numberofships = 10; // on a 10 vaisseaux (mais on peut en mettre plus)
s32 i; // variable temporaire pour la boucle du for...
 
for (i = 0; i < numberofships; i++)
{
    ennemy[i].x += ennemy[i].hspeed; // c'est comme ennemy[i].x = ennemy[i].x + ennemy[i].hspeed; vous vous rappelez ?
    ennemy[i].y += ennemy[i].vspeed;
}

N’est-ce pas magnifique? en quelques lignes, on augmente la vitesse verticale et horizontale de 10 vaisseaux! la boucle For donnera les valeurs 0 à 9 à ‘i’, et on utilise cela dans la structure du tableau! Si vous avez 100 vaisseaux, changez juste numberofships à la valeur 100 et la boucle For fera le reste :-P

Fonctions et Structures

Une méthode similaire à celle du ‘for’ est celle des fonctions... Créons-en une qui déplace un vaisseau en modifiant ses coordonnées...

void MoveEnemy(s32 number)
{
    enemy[number].x += enemy[number].hspeed; 
    enemy[number].y += enemy[number].vspeed;
}

Cette petite fonction ajoute à la position du vaisseau, sa vitesse. Pourquoi est-ce déclaré en void? Car il ne retourne rien du tout (vous voyez un ‘return’, vous?) Il ne fait qu’exécuter un code sur certaines variables... Vous pouvez ensuite utiliser ceci avec:

MoveEnemy(0);
 
// ou !
 
s32 i;
for (i = 0; i < 100; i++)
{
    MoveEnemy(i);
}

Séparer votre code comme ceci le rend beaucoup plus facile à lire, vous ne trouvez pas? Avec cette fonction, il est plus facile de déplacer un ennemi seul, ou tous en utilisant une boucle ‘for’. Une autre chose sympa est que si vous voulez ajouter du code au mouvement de l’ennemi, c’est simple: vous avez juste à les ajouter dans la fonction MoveEnnemy.

Utiliser plusieurs fichiers: C, headers...

Utiliser plusieurs fichiers séparés, pour écrire un programme, s’appelle programmation modulaire. Cette forme de programmation est inévitable lorsque votre code contient de centaines de lignes, si ce n’est de centaines de fonctions différentes.

Imaginez que dans votre programme vous ayiez 3 fonctions différentes. Alors, si un jour vous décidez d’en modifier une vous allez devoir parcourir tout le fichier, pour trouver la fonction à modifier. Or si en plus vous allez devoir modifier le valeur de retour ou les argument de votre fonction, alors vous allez aussi devoir modifier ces mêmes données au niveau de la déclaration de vos fonction (les headers). Et si en plus vous avez des fonctions surdéfinies (en C++), la catastrophe n’est pas loin! Ainsi, plus le fichier contient plus de lignes plus il est difficile de gérer ou de mettre à jour.

La programmation modulaire vous permet d’une part, de séparer les différentes fonctions en les écrivant dans des fichiers à part, et d’autre part séparer, pour une même fonction sa définition (ou implémentation dans un fichier .c ou .cpp) de sa déclaration (dans un fichier .h ou .hpp)

Ainsi supposons nos deux fonctions:

   int Multiplier(int a, int b);
   float Diviser (int a, int b);

Nous allons voir comment la programmation modulaire va traiter ces 2 fonctions.

Premièrement, pour chaque fonction / class nous allons créer deux fichiers. Mais puisque nous allons travailler en C, nous n’aurons pas besoin de créer un header propre à chaque class, donc au résultat nous aurons ceci:

* myheader.h

* multiplier.cpp

* diviser.cpp

Le fichier .h doit en outre contenir ces informations:

#ifndef MYHEADER_H
#define MYHEADER_H
. . .	// Ici j'écris mon code
#endif /* MYHEADER_H */

et chaque fichier .c ou doit contenir cette ligne.

 #include myheader.h  
 

(et à fortiori tous les header auxquels il fait appel dans son implémentation, dans le cas des fonctions amies (en c++) )

Nous allons maintenant voir le contenu de nos 3 fichiers (+ un 4ème: le fichier main.c) et comment ils sont organisés.

le fichier myheader.h contient uniquement les déclarations de nos fonctions.

#ifndef MYHEADER_H
#define MYHEADER_H
 
   int Multiplier(int, int);
   float Diviser (int, int);
 
#endif /* MYHEADER_H */

Les 2 autres fichiers contiennent chacun la définition d’une seule fonction.

multiplier.c

#include myheader.h
 
   int Multiplier(int a, int b)
{
  
    int res;
    res = a*b;
    return res;
 
}

diviser.c

#include myheader.h
 
   float Diviser(int a, int b)
{
  
    float res;
    if(a!=0 || b!=0) {
    res = a/b;
    }
    else{
    res = 0;
    }
    return res;
 
}

Enfin, le fichier main.c

#include <PA9.h> //Inclure PAlib
#include "myheader.h"
#include "multiplier.c"
#include "diviser.c"
 
int main(void)
{
        int a = 6, b = 2, m;
        float d;
	PA_Init();   //Initialise la librairie principal
	PA_InitVBL(); // Initialise le VBL
        
        m = Multiplier(a, b); //Appel de la fonction int multiplier(int, int)
        d = Diviser(a, b); // //Appel de la fonction float diviser(int, int)
 
	PA_InitText(1,2); // Dire au programme d'utiliser le texte sur l'écran 1 (du bas)et sur la background numéro 2
	
        PA_OutputText(1,1,1,"%d multiplié par %d = %d",a, b, m);
 
        PA_OutputText(1,2,2,"%d divisé par %d = %f",a, b, d);
 
	while(1)
	{
		// une boucle infinie
	}
	
	return 0;
}

Prochain → Jour 3 - Entrée/Sortie

Mollusk 28/11/2005 00:36