45
303/10/Monday 21h34
Apprenez à programmer en C / C++ ! - Le Site du Zéro
Page 294 sur 377
http://www.siteduzero.com/tuto-29-8-0-apprenez-a-programmer-en-c-c.html
case SDLK_LEFT:
marioActuel = mario[GAUCHE];
deplacerJoueur(carte, &positionJoueur, GAUCHE);
break;
}
break;
}
Si on fait Echap, le jeu s'arrêtera et on retournera au menu principal.
Comme vous le voyez, il n'y a pas 36 évènements différents à gérer : on teste juste si le joueur appuie sur haut, bas, gauche ou droite
sur son clavier.
Selon la touche enfoncée, on change la direction de mario. C'est là qu'intervient marioActuel ! Si on appuie vers le haut, alors :
Code : C
marioActuel = mario[HAUT];
Si on appuie vers le bas, alors :
Code : C
marioActuel = mario[BAS];
marioActuel pointe donc sur la surface représentant Mario dans la position actuelle. C'est ainsi qu'en blittant marioActuel tout à l'heure,
on sera sûrs de blitter Mario dans la bonne direction
Maintenant, chose très importante : on appelle une fonction deplacerJoueur. Cette fonction va déplacer le joueur sur la carte
s'il a le
droit de le faire.
Par exemple, on ne peut pas faire monter Mario d'un cran vers le haut s'il se trouve déjà tout en haut de la carte.
On ne peut pas non plus le faire monter s'il y a un mur au-dessus de lui.
On ne peut pas le faire monter s'il y a 2 caisses au-dessus de lui.
Par contre, on peut le faire monter s'il y a juste une caisse au-dessus de lui.
Mais attention, on ne peut pas le faire monter s'il y a une caisse au-dessus de lui et que le caisse se trouve au bord de la carte !
C'est quoi cette prise de tête de fou ?
C'est ce qu'on appelle la gestion des collisions. Si ça peut vous rassurer, ici c'est une gestion des collisions extrêmement simple vu que le
joueur se déplace par "cases" et dans seulement 4 directions possibles à la fois.
Ah cool, je me sens rassuré là tu vois
Quoi, vous croyiez tout de même pas qu'un jeu se codait en claquant des doigts hein ?
Dans un jeu 2D où on peut se déplacer dans toutes les directions pixel par pixel, c'est donc une gestion des collisions bien plus complexe
(tout ça ne serait-ce que pour faire un RPG 2D !).
Mais il y a pire : la 3D. Je n'ai jamais expérimenté la gestion des collisions dans un jeu 3D, mais d'après ce que j'ai entendu dire, c'est la
bête noire des programmeurs
Heureusement, il existe des librairies de gestion des collisions en 3D qui font le gros du travail à notre place.
Bon je divague là.
Revenons à la fonction deplacerJoueur. On lui envoie 3 paramètres :
La carte (pour qu'il puisse la lire mais aussi la modifier si on déplace une caisse par exemple)
La position du joueur (là aussi la fonction devra lire et éventuellement modifier la position du joueur)
La direction dans laquelle on demande à aller. On utilise là encore les constantes HAUT, BAS, GAUCHE, DROITE pour plus de
VB.NET Image: Sharpen Images with DocImage SDK for .NET VB.NET Coding. When you have made certain corrections in your VB.NET project photo or image files, you might want to sharpen your pictures before saving them
how to extract a picture from a pdf; extract image from pdf java
57
303/10/Monday 21h34
Apprenez à programmer en C / C++ ! - Le Site du Zéro
Page 295 sur 377
http://www.siteduzero.com/tuto-29-8-0-apprenez-a-programmer-en-c-c.html
lisibilité.
Nous étudierons la fonction deplacerJoueur plus loin. J'aurais très bien pu mettre tous les tests dans le switch, mais ça serait devenu
énorme et illisible. C'est là que découper son programme en fonctions prend tout son intérêt.
Blittons, blittons, la queue du cochon
Notre switch est terminé, à ce stade la carte a changé (ou pas), et le joueur a changé de position et de direction (ou pas
).
Quoi qu'il en soit,
c'est l'heure du blit !
On commence par effacer l'écran en lui mettant une couleur de fond blanche :
Code : C
// Effacement de l'écran
SDL_FillRect(ecran, NULL, SDL_MapRGB(ecran->format, 255, 255, 255));
Et maintenant, on parcourt tout notre tableau à 2 dimensions carte pour savoir quel élément blitter à quel endroit sur l'écran.
On effectue une double boucle comme on l'a vu plus tôt pour parcourir toutes les 144 cases du tableau :
Code : C
// Placement des objets à l'écran
objectifsRestants = 0;
for (i = 0 ; i < NB_BLOCS_LARGEUR ; i++)
{
for (j = 0 ; j < NB_BLOCS_HAUTEUR ; j++)
{
position.x = i * TAILLE_BLOC;
position.y = j * TAILLE_BLOC;
switch(carte[i][j])
{
case MUR:
SDL_BlitSurface(mur, NULL, ecran, &position);
break;
case CAISSE:
SDL_BlitSurface(caisse, NULL, ecran, &position);
break;
case CAISSE_OK:
SDL_BlitSurface(caisseOK, NULL, ecran, &position);
break;
case OBJECTIF:
SDL_BlitSurface(objectif, NULL, ecran, &position);
objectifsRestants = 1;
break;
}
}
}
Pour chacune des cases, on prépare la variable position (de type SDL_Rect) pour placer l'élément actuel à la bonne position sur l'écran.
Le calcul est très simple :
Code : C
position.x = i * TAILLE_BLOC;
position.y = j * TAILLE_BLOC;
Il suffit de multiplier i par TAILLE_BLOC pour avoir position.x.
Ainsi, si on se trouve à la 3ème case, c'est que i vaut 2 (n'oubliez pas que i commence à 0 !). On fait donc le calcul 2 * 34 = 68. On
blittera donc l'image 68 pixels vers la droite sur ecran.
On fait la même chose pour les ordonnées y.
Ensuite, on fait un switch sur la case de la carte qu'on est en train d'analyser.
Là encore, avoir fait des constantes est vraiment pratique et ça rend les choses plus lisibles
On teste donc si la case vaut MUR, dans ce cas on blitte un mur. De même pour les caisses et les objectifs.
42
303/10/Monday 21h34
Apprenez à programmer en C / C++ ! - Le Site du Zéro
Page 296 sur 377
http://www.siteduzero.com/tuto-29-8-0-apprenez-a-programmer-en-c-c.html
Test de victoire
Vous remarquerez qu'avant la double boucle on initialise le booléen objectifsRestants à 0.
Ce booléen sera mis à 1 dès qu'on aura détecté un objectif sur la carte. S'il ne reste plus d'objectifs, c'est que toutes les caisses sont sur
des objectifs (il n'y a plus que des CAISSE_OK).
Il suffit de tester si le booléen vaut FAUX (= il ne reste plus d'objectifs).
Dans ce cas, on met la variable continuer à 0 pour arrêter la partie :
Code : C
// Si on n'a trouvé aucun objectif sur la carte, c'est qu'on a gagné
if (!objectifsRestants)
continuer = 0;
Le joueur
Et le joueur dans l'histoire ?
Le joueur, on le blitte juste après. Justement, venons-y :
Code : C
// On place le joueur à la bonne position
position.x = positionJoueur.x * TAILLE_BLOC;
position.y = positionJoueur.y * TAILLE_BLOC;
SDL_BlitSurface(marioActuel, NULL, ecran, &position);
On calcule sa position (en pixels cette fois) en faisant une simple multiplication entre positionJoueur et TAILLE_BLOC. On blitte ensuite
le joueur à la position indiquée.
Flip !
Bon ben on a tout fait je crois, on peut afficher le nouvel écran au joueur
Code : C
SDL_Flip(ecran);
Fin de la fonction : déchargements
Après la boucle principale, on doit faire quelques FreeSurface pour libérer la mémoire des sprites qu'on a chargés.
On désactive aussi la répétition des touches en envoyant les valeurs 0 à la fonction SDL_EnableKeyRepeat :
Code : C
// Désactivation de la répétition des touches (remise à 0)
SDL_EnableKeyRepeat(0, 0);
// Libération des surfaces chargées
SDL_FreeSurface(mur);
SDL_FreeSurface(caisse);
SDL_FreeSurface(caisseOK);
SDL_FreeSurface(objectif);
for (i = 0 ; i < 4 ; i++)
SDL_FreeSurface(mario[i]);
La fonction deplacerJoueur
C# Imaging - Scan RM4SCC Barcode in C#.NET & decode RM4SCC barcode from scanned documents and pictures in your Decode RM4SCC from documents (PDF, Word, Excel and PPT) and extract barcode value as
extract pictures from pdf; extract text from image pdf file
47
303/10/Monday 21h34
Apprenez à programmer en C / C++ ! - Le Site du Zéro
Page 297 sur 377
http://www.siteduzero.com/tuto-29-8-0-apprenez-a-programmer-en-c-c.html
La fonction deplacerJoueur se trouve elle aussi dans jeu.c.
C'est une fonction... très chiante à écrire
C'est peut-être là la principale difficulté du codage du jeu de Sokoban.
Rappel : la fonction deplacerJoueur vérifie si on a le droit de déplacer le joueur dans la direction demandée. Elle met à jour la position
du joueur (positionJoueur) et aussi la carte si une caisse a été déplacée.
Voici le prototype de la fonction :
Code : C
void deplacerJoueur(int carte[][NB_BLOCS_HAUTEUR], SDL_Rect *pos, int direction);
Ce prototype est un peu particulier. Vous voyez que j'envoie le tableau carte, et que je précise la taille de la deuxième dimension
(NB_BLOCS_HAUTEUR).
Pourquoi cela ?
C'est un problème qui m'a pas mal embêté pendant longtemps et je ne comprenais pas pourquoi. La réponse est un peu compliquée pour
que je la développe au milieu de ce cours. Grosso modo, le C ne devine pas qu'il s'agit d'un tableau à 2 dimensions, et il faut au moins
donner
la taille de la seconde dimension pour que ça fonctionne.
Donc, lorsque vous envoyez un tableau à 2 dimensions à une fonction, vous devez indiquer la taille de la seconde dimension dans le
prototype. C'est comme ça, c'est obligatoire.
Autre chose : vous noterez que positionJoueur s'appelle en fait "pos" dans cette fonction. J'ai choisi de raccourcir le nom parce que c'est
plus court à écrire, et vu qu'on va avoir besoin de l'écrire de nombreuses fois autant pas se fatiguer
Bon on commence par tester la direction dans laquelle on veut aller via un grand :
Code : C
switch(direction)
{
case HAUT:
/* etc. */
Et c'est parti pour des tests de folie !
On peut commencer à faire tous les tests, en essayant tant qu'à faire de n'oublier aucun cas
Voici comment je procède : je teste
toutes les possibilités de collision cas par cas, et dès que je détecte une collision (qui fait que le
joueur ne peut pas bouger), je fais un break; pour sortir du switch et donc empêcher le déplacement.
Voici par exemple toutes les possibilités de collision qui existent pour un joueur qui veut se déplacer vers le haut :
Le joueur est déjà tout en haut de la carte
Il y a un mur au-dessus du joueur
Il y a 2 caisses au-dessus du joueur (et il ne peut pas déplacer 2 caisses à la fois, rappelez-vous).
Si tous ces tests sont ok, alors je me permet de déplacer le joueur.
Je vais vous montrer les tests pour un déplacement vers le haut. Pour les autres sens, il suffira d'adapter un petit peu le code.
Code : C
if (pos->y - 1 < 0) // Si le joueur dépasse l'écran, on arrête
break;
On commence par vérifier si le joueur est déjà tout en haut de l'écran. En effet, si on essayait d'appeler carte[5][-1] par exemple, ce
serait le plantage de programme assuré !
On commence donc par vérifier qu'on ne va pas "déborder" de l'écran.
Ensuite :
53
303/10/Monday 21h34
Apprenez à programmer en C / C++ ! - Le Site du Zéro
Page 298 sur 377
http://www.siteduzero.com/tuto-29-8-0-apprenez-a-programmer-en-c-c.html
Code : C
if (carte[pos->x][pos->y - 1] == MUR) // S'il y a un mur, on arrête
break;
Là encore c'est simple. On vérifie s'il n'y a pas un mur au-dessus du joueur. Si tel est le cas, on arrête (break).
Ensuite (attention ça devient hardcore) :
Code : C
// Si on veut pousser une caisse, il faut vérifier qu'il n'y a pas de mur derrière (ou une
autre caisse, ou la limite du monde)
if ((carte[pos->x][pos->y - 1] == CAISSE || carte[pos->x][pos->y - 1] == CAISSE_OK) &&
(pos->y - 2 < 0 || carte[pos->x][pos->y - 2] == MUR ||
carte[pos->x][pos->y - 2] == CAISSE || carte[pos->x][pos->y - 2] == CAISSE_OK))
break;
Ce méga test de bourrin peut se traduire comme ceci :
SI au-dessus du joueur il y a une caisse (ou une caisse_ok, c'est-à-dire une caisse bien placée)
ET SI au-dessus de cette caisse il y a : soit le vide (on déborde du niveau car on est tout en haut), soit une autre caisse, soit une
caisse_ok) :
ALORS on ne peut pas se déplacer : break.
Si on arrive jusque-là, on a le droit de déplacer le joueur !
On appelle d'abord une fonction qui va déplacer une caisse si nécessaire :
Code : C
// Si on arrive là, c'est qu'on peut déplacer le joueur !
// On vérifie d'abord s'il y a une caisse à déplacer
deplacerCaisse(&carte[pos->x][pos->y - 1], &carte[pos->x][pos->y - 2]);
Le déplacement de caisse : deplacerCaisse
J'ai choisi de gérer le déplacement de caisse dans une autre fonction car c'est le même code pour les 4 directions. On doit juste s'être
assuré avant qu'on a le droit de se déplacer (ce qu'on vient de faire).
On envoie à la fonction 2 paramètres : le contenu de la case dans laquelle on veut aller, et le contenu de la case d'après.
Code : C
void deplacerCaisse(int *premiereCase, int *secondeCase)
{
if (*premiereCase == CAISSE || *premiereCase == CAISSE_OK)
{
if (*secondeCase == OBJECTIF)
*secondeCase = CAISSE_OK;
else
*secondeCase = CAISSE;
if (*premiereCase == CAISSE_OK)
*premiereCase = OBJECTIF;
else
*premiereCase = VIDE;
}
}
Cette fonction met à jour la carte car elle prend des pointeurs sur les cases concernées en paramètre.
Je vous laisse la lire, c'est assez simple à comprendre. Il ne faut pas oublier que si on déplace une CAISSE_OK, il faut remplacer la case
où elle se trouvait par un OBJECTIF. Sinon, si c'est une simple CAISSE, alors on remplace la case en question par du VIDE.
Déplacer le joueur
On retourne dans la fonction deplacerJoueur.
Cette fois c'est la bonne, on peut déplacer le joueur.
Comment on fait ?
47
303/10/Monday 21h34
Apprenez à programmer en C / C++ ! - Le Site du Zéro
Page 299 sur 377
http://www.siteduzero.com/tuto-29-8-0-apprenez-a-programmer-en-c-c.html
C'est supra-simple
Code : C
pos->y--; // On peut enfin faire monter le joueur (oufff !)
Il suffit de diminuer l'ordonnée y (car le joueur veut monter).
Résumé
En guise de résumé, voici tous les tests pour le cas HAUT :
Code : C
switch(direction)
{
case HAUT:
if (pos->y - 1 < 0) // Si le joueur dépasse l'écran, on arrête
break;
if (carte[pos->x][pos->y - 1] == MUR) // S'il y a un mur, on arrête
break;
// Si on veut pousser une caisse, il faut vérifier qu'il n'y a pas de mur derrière
(ou une autre caisse, ou la limite du monde)
if ((carte[pos->x][pos->y - 1] == CAISSE || carte[pos->x][pos->y - 1] == CAISSE_OK)
&&
(pos->y - 2 < 0 || carte[pos->x][pos->y - 2] == MUR ||
carte[pos->x][pos->y - 2] == CAISSE || carte[pos->x][pos->y - 2] == CAISSE_OK))
break;
// Si on arrive là, c'est qu'on peut déplacer le joueur !
// On vérifie d'abord s'il y a une caisse à déplacer
deplacerCaisse(&carte[pos->x][pos->y - 1], &carte[pos->x][pos->y - 2]);
pos->y--; // On peut enfin faire monter le joueur (oufff !)
break;
Je vous laisse le soin de faire du copier-coller pour les autres cas (attention, il faudra adapter le code, ce n'est pas exactement pareil à
chaque fois !).
Et voilà, on vient de finir de coder le jeu
Enfin presque, il nous reste à coder la fonction de chargement (et de sauvegarde) de niveau.
On verra ensuite comment créer l'éditeur (rassurez-vous, ça ira bien plus vite
)
Chargement et enregistrement de niveaux
fichiers.c contient 2 fonctions :
chargerNiveau
sauvegarderNiveau
Commençons par le chargement de niveau
chargerNiveau
Cette fonction prend un paramètre : la carte. Là encore, il faut préciser la taille de la seconde dimension car il s'agit d'un tableau à 2
dimensions.
La fonction renvoie un booléen : vrai si le chargement a réussi, faux si c'est un échec.
Le prototype est donc :
Code : C
54
303/10/Monday 21h34
Apprenez à programmer en C / C++ ! - Le Site du Zéro
Page 300 sur 377
http://www.siteduzero.com/tuto-29-8-0-apprenez-a-programmer-en-c-c.html
int chargerNiveau(int niveau[][NB_BLOCS_HAUTEUR]);
Voyons le début de la fonction :
Code : C
FILE* fichier = NULL;
char ligneFichier[NB_BLOCS_LARGEUR * NB_BLOCS_HAUTEUR + 1] = {0};
int i = 0, j = 0;
fichier = fopen("niveaux.lvl", "r");
if (fichier == NULL)
return 0;
On crée un tableau de char pour stocker le résultat du chargement du niveau temporairement.
On ouvre le fichier en lecture seule ("r"). On arrête la fonction en renvoyant 0 (faux) si l'ouverture a échoué. Classique.
Le fichier niveaux.lvl contient une ligne qui est une suite de nombres. Chaque nombre représente une case du niveau. Par exemple :
111110011111111114000001111100011001033101011011000002001211100101000011111100011211111111111001111130000001111111111111111111111111111111111111
On va donc lire cette ligne avec un fgets :
Code : C
fgets(ligneFichier, NB_BLOCS_LARGEUR * NB_BLOCS_HAUTEUR + 1, fichier);
On va analyser le contenu de ligneFichier. On sait que les 12 premiers caractères représentent la première ligne, les 12 suivants la
seconde ligne etc.
Code : C
for (i = 0 ; i < NB_BLOCS_HAUTEUR ; i++)
{
for (j = 0 ; j < NB_BLOCS_LARGEUR ; j++)
{
switch (ligneFichier[(i * NB_BLOCS_LARGEUR) + j])
{
case '0':
niveau[j][i] = 0;
break;
case '1':
niveau[j][i] = 1;
break;
case '2':
niveau[j][i] = 2;
break;
case '3':
niveau[j][i] = 3;
break;
case '4':
niveau[j][i] = 4;
break;
}
}
}
Par un simple petit calcul, on prend le caractère qui nous intéresse dans ligneFichier et on analyse sa valeur.
Ce sont des "lettres" qui sont stockées dans le fichier. Je veux dire par là que '0' est stocké comme le caractère ASCII '0',
et sa valeur n'est pas 0 !
Pour analyser le fichier, il faut tester avec case '0' et non avec case 0 ! Attention aux erreurs là
Bref, le switch fait la conversion '0' => 0, '1' => 1 etc... Et place tout dans le tableau carte (qui s'appelle niveau dans la fonction
d'ailleurs, mais ça ne change rien
)
Documents you may be interested
Documents you may be interested