Hello @ tous, ce topic fait suite à celui-ci :
http://www.dev-fr.org/index.php/topic,3859.0.htmlAujourd'hui, le but va être de faire un Shoot em up très sommaire au scrolling infini.
Si vous n'avez pas lu le topic sur le Casse Briques, je vous conseille de le lire (ou de le relire) car il reprend pas mal de notions vues précédemment.
I Pré-requisCe tutoriel utilise l'
ulibrary de Brunni (Pas de Carla...

). Vous devez donc l'installer afin de pouvoir compiler le code sans soucis. Bien évidement, je suppose que vous avez bien le devkitpro ainsi que la dernière libnds d'installée.
II ConceptionUn shoot em up est un jeu ou vous contrôlez un vaisseau qui doit tirer sur des ennemis tout en esquivant leurs tirs. Dans notre petit shoot em up, nous allons gérer tout cela.
Pour commencer, le mieux est de définir les entités qui seront présentes dans le jeu. Il y aura un vaisseau, des bullets (ennemies et celles tirées par le vaisseau) ainsi que des ennemis.
Les règles du jeu seront les suivantes : Le vaisseau peut tirer des bullets en rafale, si une de celles-ci touche un ennemi, elle sera détruite et enlèvera de la vie a l'ennemi en question. Les ennemis peuvent aussi tirer des bullets qui infligeront des dégâts au vaisseau. De plus, si le vaisseau touche un bloc de la map, il sera détruit et la partie recommencera.
Le scrolling de la map sera un scrolling de type infini, c'est à dire que la map se répètera sans cesse de façon "invisible" et ce jusqu'à ce que le joueur perde ou qu'il arrête de jouer.
Voici les bases du gameplay de notre shoot em up, à notre code maintenant !
III Le codeTout d'abord, voyons voir le fichier header qui contient les déclarations des fonctions que nous allons utiliser.
(cpp):
//On inclue l'uLib
#include <ulib/ulib.h>
Ensuite, passons au fonctions, leur utilité parle d'elle même !
(cpp):
//On définit quelques fonctions utiles
//Affichage
void DessinerEnnemis();
void DessinerVaisseau();
void DessinerBullets();
void DessinerBrique(int x, int y);
void DessinerMap(int scrollX, int scrollY);
void drawTriangle(int x0, int y0, int x1, int y1, int x2, int y2, UL_COLOR color);
//Logique
void DemarrerJeu();
void ArreterJeu();
void RedemarrerJeu();
void GererCollisions();
bool RectCollision(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2);
//Mise a jour du jeu
void DeplacerVaisseau();
void DeplacerBullets();
void DeplacerEnnemis();
Nous avons aussi besoin d'un tableau à deux dimensions qui contiendra notre map.
(cpp):
//Map
extern bool map[12][32];
Ce tableau est composé de 12 lignes (192px) et 32 colonnes (512 px). Notre jeu est un jeu à scrolling horizontal... Oui, il est dans l'autre sens ! En fait, j'ai fait cela pour rendre le remplissage de ce dernier plus intuitif. Mais en vrai, dans le jeu il sera affiché dans la bonne orientation.
Quant à la nature de ce tableau, c'est simple, si par exemple map[10][5] == true alors il y a une brique à cet endroit, sinon il n'y en a pas !
Passons maintenant aux structures que nous allons utiliser.
Par rapport aux entités énumérées plus haut, il nous faut une structure pour contenir nos bullets...
(cpp):
//Structure d'une bullet
struct sBullet
{
int x, y; //Position de la balle dans le monde
int vx, vy; //Vecteur de déplacement
int vitesse; //Vitesse de la balle
int taille; //Largeur de la balle
bool type; //Bullet Héros/ Bullet Ennemie (la couleur change en conséquence)
bool libre; //Si la place pour la bullet est utilisée ou pas dans le tableau
};
... puis une pour le vaisseau...
(cpp):
//Structure du vaisseau
struct sVaisseau
{
int x, y; //Position du héros dans le monde
int hauteur, largeur; //Taille du vaisseau (pour les collisions)
int vie; //Points de vie du vaisseau
};
... puis une autre pour les ennemis...
(cpp):
//Structure d'un ennemi
struct sEnnemy
{
int x, y; //Position de l'ennemi dans le monde
int hauteur, largeur; //Taille de l'ennemi
int vie; //Points de vie de l'ennemi
int intervalleTir; //Intervalle de temps entre deux tentatives de tir
bool libre; //Définit si la structure peut contenir un nouvel ennemi
};
... et encore une autre pour les briques de la map...
(cpp):
//16 * 16
struct sBrique
{
int hauteur, largeur; //Taille de la brique
};
... et enfin une dernière pour les paramètres du monde !
(cpp):
//Monde composé de blocs de 16*16 (192/16 = 12 et 512/16 = 32)
struct sWorld
{
int hauteur, largeur; //Taille du monde en pixels
};
On peut remarquer qu'on peut se passer de ces deux dernières structures, mais ce code est conçu pour être évolutif, il est donc judicieux de prévoir une structure pour nos ajouts futurs (pour ceux qui voudront aller plus loin bien sûr).
Voilà, nous avons toutes nos structures de prêtes, passons au code pur et duuur maintenant

Déclarons nos structures pour les utiliser plus tard :
(cpp):
sWorld world;
sBrique brique;
sVaisseau vaisseau;
sBullet bullets[64]; //Tableau de 64 bullets
sEnnemy ennemies[8]; //Tableau de 8 ennemis
Les deux derniers tableaux contiennent une "liste" des bullets et des ennemis dans le niveau. En vrai, ce n'est pas une "liste", car en effet, une liste chainée aurait été un conteneur plus approprié pour gérer cela. Mais, c'est bien trop complexe pour ce que nous voulons en faire. Les principaux inconvénients des tableaux sont que on est limité au niveau du nombre d'éléments maximum. Mais rassurez vous, on ne code pas un danmaku, il n'y aura jamais plus de 64 bullets à l'écran

Déclarons maintenant quelques variables utiles :
(cpp):
bool isPlaying;
int prochainBulletLibre = 0; //Indice de tableau dans lequel sera ajouté le prochain bullet
int prochainEnnemiLibre = 0; //Idem pour les ennemis
int tirVaisseauCounter = 0; //Compteur de temps permettant de limiter la cadence de tir du vaisseau
int ennemisCounter = 0; //Compteur de temps pour le tir des ennemis
int scrollX = 0; //Scrolling actuel de la map
int score = 0; //Score du joueur
int nbrEnnemis = 0; //Nombre d'ennemis à un instant donné
Passons maintenant à un petit point délicat, il s'agit de remplir notre map (le tableau de booléens) afin de donner la position des blocs de la map, un peu à la manière d'un éditeur de maps. Contrairement au casse briques, nous allons remplir cette map manuellement.
(cpp):
bool map[12][32] = { {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1},
{0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0},
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1} };
Ici, on remplit naturellement chaque ligne puis chaque colonne. Si une case == 0, alors il n'y a pas de brique, sinon, il y en a une. Notons que notre tableau est défini en temps que tableau de booléens, on aurait du donc mettre true ou false à la place de 0 ou 1. Mais en fait, le compilateur transtype de façon transparente chaque en int en bool selon la règle : si == 0, alors il met false, sinon si il est > 0, il met la valeur à true.
Simple isn't it ?
Notez de plus que l'inversion du tableau de bool nous permet ici de remplir de tableau comme il sera affiché dans le jeu.
Passons a la programmation des fonctions que nous avons dans les headers :
(cpp):
//Fonctions importantes
void DemarrerJeu()
{
//Autres
isPlaying = true;
}
void ArreterJeu()
{
//Autres
isPlaying = false;
}
Leur code parle de lui même. On ne se sert pas de ces fonctions dans le tuto mais c'est dans un but d'évolution.
(cpp):
void RedemarrerJeu()
{
//Initialisation des bullets
for(int k = 0; k < 64; k++)
{
bullets[k].libre = true;
}
for(int k = 0; k < 8; k++)
{
ennemies[k].libre = true;
}
vaisseau.vie = 3;
vaisseau.x = 40;
vaisseau.y = (192/2)-(16/2);
prochainBulletLibre = 0;
prochainEnnemiLibre = 0;
tirVaisseauCounter = 0;
ennemisCounter = 0;
scrollX = 0;
score = 0;
nbrEnnemis = 0;
//Autres
isPlaying = true;
}
Ici, on fait quelques initialisations. D'abord, on met le flag libre=true de façon à ce que au démarrage de chaque session de jeu, tout les ennemis et bullets soient vidés (en fait, ils sont juste ignorés en faisant ça, mais ça revient au même

)
Puis la fonction pour dessiner un ennemi. Un simple assemblage de rectangles et de triangles.
(cpp):
void DessinerVaisseau()
{
ulDrawFillRect(vaisseau.x, vaisseau.y, vaisseau.x+20, vaisseau.y+16, RGB15(31, 31, 31) );
ulDrawFillRect(vaisseau.x-3, vaisseau.y+4, vaisseau.x, vaisseau.y+4+8, RGB15(31, 31, 31) );
drawTriangle(vaisseau.x, vaisseau.y-6, vaisseau.x+6, vaisseau.y, vaisseau.x, vaisseau.y, RGB15(255, 255, 255) );
drawTriangle(vaisseau.x, vaisseau.y+6+16, vaisseau.x+6, vaisseau.y+16, vaisseau.x, vaisseau.y+16, RGB15(255, 255, 255) );
drawTriangle(vaisseau.x+20, vaisseau.y, vaisseau.x+20+6, vaisseau.y+8, vaisseau.x+20, vaisseau.y+16, RGB15(255, 255, 255) );
}
Puis, pour les ennemis :
(cpp):
void DessinerEnnemis()
{
for(int k = 0; k < 8; k++)
{
if(!ennemies[k].libre)
{
ulDrawFillRect(ennemies[k].x, ennemies[k].y, ennemies[k].x+ennemies[k].largeur, ennemies[k].y+ennemies[k].hauteur, RGB15(31, 31, 31) );
ulDrawFillRect(ennemies[k].x+4, ennemies[k].y-2, ennemies[k].x+4+4, ennemies[k].y-2+2, RGB15(31, 31, 31) );
ulDrawFillRect(ennemies[k].x+4, ennemies[k].y+8, ennemies[k].x+4+4, ennemies[k].y+8+2, RGB15(31, 31, 31) );
}
}
}
Ici, pour chaque ennemi et si et seulement si l'espace n'est pas libre, (c'est comme ça qu'on ignore un indice contenant un ennemi ayant le flag libre==true) on affiche l'ennemi.
Idem pour les bullets
(cpp):
void DessinerBullets()
{
for(int k = 0; k < 64; k++)
{
if(!bullets[k].libre)
{
if(!bullets[k].type)
ulDrawFillRect(bullets[k].x, bullets[k].y, bullets[k].x + bullets[k].taille, bullets[k].y + bullets[k].taille, RGB15(0, 0, 31) );
else
ulDrawFillRect(bullets[k].x, bullets[k].y, bullets[k].x + bullets[k].taille, bullets[k].y + bullets[k].taille, RGB15(31, 0, 0) );
}
}
}
Puis, la fonction pour afficher une brique à une position donnée :
(cpp):
void DessinerBrique(int x, int y)
{
ulDrawFillRect(x*brique.largeur - (scrollX>>8)%512, y*brique.hauteur, x*brique.largeur + brique.largeur - (scrollX>>8)%512, y*brique.hauteur + brique.hauteur, RGB15(31, 31, 31) );
ulDrawFillRect(x*brique.largeur - (scrollX>>8)%512+512, y*brique.hauteur, x*brique.largeur + brique.largeur - (scrollX>>8)%512+512, y*brique.hauteur + brique.hauteur, RGB15(31, 31, 31) );
}
Ce code mérite quelques explications. Je rappelle que le jeu est un jeu au scrolling infini. Il faut que quelque soit la valeur du scrolling, la map ne finisse jamais. C'est pourquoi, au niveau des coordonnées x de la brique, on fait ceci : (scrollX>>

%512. La valeur du scrollX est passée du format fixed point vers entier plus on réalise un modulo par 512 dessus, de façon à ce que même si la valeur de scrollX dépasse 512, on aura toujours une valeur entre 0 et 512. Ensuite, on affiche la même brique deux fois de façon à ne pas voir la jonction entre la première partie du scrolling infini et la 2ème. On décale celle-ci de 512 pixels sur la droite. Comme ça, quand on arrive au dernier bloc, on voit le premier bloc de la map juste à droite, et ainsi de suite.
Passons maintenant à la fonction permettant de déplacer le vaisseau et de tirer:
(cpp):
void DeplacerVaisseau()
{
//On met à jour la position du vaisseau
if(ul_keys.held.left )
vaisseau.x = vaisseau.x - 1; //On recule plus lentement qu'on accélère
if(ul_keys.held.right )
vaisseau.x = vaisseau.x + 2;
if(ul_keys.held.up )
vaisseau.y = vaisseau.y - 2;
if(ul_keys.held.down )
vaisseau.y = vaisseau.y + 2;
//Gestion des tirs
if(tirVaisseauCounter != 0)
tirVaisseauCounter--;
//Si A est appuyé ET que si la place pour le bullet est libre ET que si le l'intervalle de tir est égal à 0
if(ul_keys.held.A && bullets[prochainBulletLibre].libre && tirVaisseauCounter == 0)
{
//On ajoute une bullet
bullets[prochainBulletLibre].libre = false; //La bullet n'est désormais plus libre
bullets[prochainBulletLibre].x = vaisseau.x+25+4;
bullets[prochainBulletLibre].y = vaisseau.y+6;
bullets[prochainBulletLibre].taille = 2;
bullets[prochainBulletLibre].type = false; //La bullet est une bullet de type héros
bullets[prochainBulletLibre].vx = 1;
bullets[prochainBulletLibre].vy = 0;
bullets[prochainBulletLibre].vitesse = 3;
prochainBulletLibre++; //Incrémentation de façon à ce que le bullet venant d'être tiré ne soit pas remplacé par un autre
//On ajoute un "temps" de tir pour eviter que le tir se fasse en continu
tirVaisseauCounter = 12;
}
//Vérif des limites (collisions avec les bords de l'écran)
if(vaisseau.x <= 0)
vaisseau.x = 0; //A gauche
if(vaisseau.x + vaisseau.largeur > world.largeur)
vaisseau.x = world.largeur-vaisseau.largeur; //A droite
if(vaisseau.y <= 0)
vaisseau.y = 0;
if(vaisseau.y + vaisseau.hauteur > world.hauteur)
vaisseau.y = world.hauteur-vaisseau.hauteur;
//Redemarrage du jeu si toute la vie est perdue
if(vaisseau.vie <= 0)
RedemarrerJeu();
}
La première partie représente la gestion des touches.
Ensuite, on décrémente la variable de tir si sa valeur est différente de 0. De plus, le tir est autorisé que si cette valeur est égale à 0. De ce fait, dès qu'on vient de tirer une bullet, on met cette variable à une valeur telle que 10 de façon à ce que le héros soit autorisé à tirer seulement dans 10 frames (soit 1/6ème de s).
Ensuite, voyons voir la fonction permettant de déplacer les bullets
(cpp):
void DeplacerBullets()
{
//On déplace les bullets
for(int k = 0; k < 64; k++)
{
if(!bullets[k].libre)
{
//On déplace les bullets
bullets[k].x = bullets[k].x + bullets[k].vx*bullets[k].vitesse; //On ajoute a la position de la balle le produit de la composante x de son vecteur de déplacement et de sa vitesse
bullets[k].y = bullets[k].y + bullets[k].vy*bullets[k].vitesse; //Idem pour y
//Si une bullet est hors de l'écran on la désactive
if(bullets[k].x <= 0 || bullets[k].x >= 256+bullets[k].taille || bullets[k].y <= 0 || bullets[k].y >= 192+bullets[k].taille)
{
bullets[k].libre = true;
prochainBulletLibre = k; //Le prochain bullet libre est celui qui vient d'être supprimé
}
}
}
//On vérifie l'état du prochain bullet
if(prochainBulletLibre > 63)
prochainBulletLibre = 0;
}
Pour chaque bullet, on la déplace, puis on vérifie si elle est sortie de l'écran afin de la désactiver.
(cpp):
void DeplacerEnnemis()
{
//On ajoute des ennemis aléatoirement sur le niveau
ennemisCounter++;
if(ennemisCounter == 30)
{
if(nbrEnnemis < 8 && ennemies[prochainEnnemiLibre].libre)
{
//On ajoute un ennemi à une position aléatoire
ennemies[prochainEnnemiLibre].hauteur = ennemies[prochainEnnemiLibre].largeur = 8;
ennemies[prochainEnnemiLibre].x = (int)(rand()%64) + 256; //Ajout d'un petit décalage aléatoire (l'ennemi est placé d'abord hors de l'écran)
ennemies[prochainEnnemiLibre].y = (int)(rand()%190)+1; //Position aléatoire en y
ennemies[prochainEnnemiLibre].libre = false;
ennemies[prochainEnnemiLibre].vie = 2;
ennemies[prochainEnnemiLibre].intervalleTir = 0;
prochainEnnemiLibre++;
nbrEnnemis++;
}
//Réinitialisation du compteur
ennemisCounter = 0;
}
//Gestion des ennemis
for(int k = 0; k < 8; k++)
{
if(!ennemies[k].libre)
{
//Tir
int random = (int)(rand()%5); //4 chances sur 5 que l'ennemi tire
if(ennemies[k].intervalleTir == 0 && bullets[prochainBulletLibre].libre && random)
{
//On ajoute un bullet
bullets[prochainBulletLibre].libre = false;
bullets[prochainBulletLibre].x = ennemies[k].x;
bullets[prochainBulletLibre].y = ennemies[k].y+2;
bullets[prochainBulletLibre].taille = 2;
bullets[prochainBulletLibre].type = true;
bullets[prochainBulletLibre].vx = -1;
bullets[prochainBulletLibre].vy = 0;
bullets[prochainBulletLibre].vitesse = 3;
prochainBulletLibre++;
ennemies[k].intervalleTir = 10 + (int)(rand()%8); //Rend les tirs moins réguliers
}
//Déplacement
ennemies[k].x -= 2;
//Intervalle de tir
ennemies[k].intervalleTir--;
//On teste si il faut détruire l'ennemi
if(ennemies[k].vie <= 0 || ennemies[k].x < -ennemies[k].largeur)
{
ennemies[k].libre = true;
prochainEnnemiLibre = k;
nbrEnnemis--;
}
}
}
//Si le prochain ennemi libre est supérieur à 7, on met la valeur à 0 pour éviter un dépassement de tableau
if(prochainEnnemiLibre > 7)
prochainEnnemiLibre = 0;
}
Le code reprend beaucoup d'éléments de la fonction précédente, il n'est pas très difficile à comprendre.
Voici maintenant la plus grosse fonction du programme, celle permettant de gérer les collisions entres les différentes entités du jeu. On cherche à chaque fois si il y a collision en croisant les entités et en vérifiant leur position à l'écran.
(cpp):
void GererCollisions()
{
//On teste les collisions entre les bullets/vaisseau et les briques
//Pour chaque brique de la map
for(int x = 0; x < 32; x++)
{
for(int y = 0; y < 12; y++)
{
//On teste si il y a une brique a cet endroit
if(map[y][x] == true)
{
//Et pour chaque bullet
for(int k = 0; k < 63; k++)
{
//On teste les collisions entre le bullet et une brique
if(RectCollision(bullets[k].x, bullets[k].y, bullets[k].taille, bullets[k].taille, x*brique.largeur-(scrollX>>8)%512, y*brique.hauteur, brique.largeur, brique.hauteur) == true)
{
//On détruit la bullet
bullets[k].libre = true;
prochainBulletLibre = k;
}
}
//Pour le vaisseau
if(RectCollision(vaisseau.x, vaisseau.y, vaisseau.largeur, vaisseau.hauteur, x*brique.largeur-(scrollX>>8)%512, y*brique.hauteur, brique.largeur, brique.hauteur) == true)
{
//On détruit le vaisseau
vaisseau.vie = 0;
}
}
}
}
//On teste une 2ème fois car on affiche la map 2 fois pour le scrolling infini (souvenez vous, le décalage de 512px à droite :)
for(int x = 0; x < 32; x++)
{
for(int y = 0; y < 12; y++)
{
//On teste si il y a une brique a cet endroit
if(map[y][x] == true)
{
//Et pour chaque bullet
for(int k = 0; k < 63; k++)
{
//On teste les collisions entre le bullet et une brique
if(RectCollision(bullets[k].x, bullets[k].y, bullets[k].taille, bullets[k].taille, x*brique.largeur-(scrollX>>8)%512+512, y*brique.hauteur, brique.largeur, brique.hauteur) )
{
//On détruit la bullet
bullets[k].libre = true;
prochainBulletLibre = k;
}
}
//Pour le vaisseau
if(RectCollision(vaisseau.x, vaisseau.y, vaisseau.largeur, vaisseau.hauteur, x*brique.largeur-(scrollX>>8)%512, y*brique.hauteur, brique.largeur, brique.hauteur) == true)
{
//On détruit le vaisseau
vaisseau.vie = 0;
}
}
}
}
//Collisions bullets-ennemis
for(int k = 0; k < 64; k++)
{
if(!bullets[k].libre && !bullets[k].type)
{
for(int l = 0; l < 8; l++)
{
if(!ennemies[l].libre)
{
//On fait un test bullet VS ennemi
if(RectCollision(bullets[k].x, bullets[k].y, bullets[k].taille, bullets[k].taille, ennemies[l].x, ennemies[l].y, ennemies[l].largeur, ennemies[l].hauteur) )
{
ennemies[l].vie--; //On enlève de la vie à l'ennemi
bullets[k].libre = true; //On détruit la bullet
prochainBulletLibre = k;
score += 50; //Maj du score
}
}
}
}
}
//Collisions bullets-vaisseau
for(int k = 0; k < 64; k++)
{
if(!bullets[k].libre && bullets[k].type)
{
//On fait un test bullet VS ennemi
if(RectCollision(bullets[k].x, bullets[k].y, bullets[k].taille, bullets[k].taille, vaisseau.x, vaisseau.y, vaisseau.largeur, vaisseau.hauteur) )
{
vaisseau.vie--; //On enlève de la vie au vaisseau
bullets[k].libre = true; //On détruit la bullet
prochainBulletLibre = k;
}
}
}
//Collisions ennemis-vaisseau
for(int k = 0; k < 8; k++)
{
if(!ennemies[k].libre)
{
//On fait un test bullet VS ennemi
if(RectCollision(vaisseau.x, vaisseau.y, vaisseau.largeur, vaisseau.hauteur, ennemies[k].x, ennemies[k].y, ennemies[k].largeur, ennemies[k].hauteur) )
{
ennemies[k].vie = 0; //On détruit l'ennemi
vaisseau.vie--; //On enlève de la vie au vaisseau
}
}
}
}
Les explications sont dans les commentaires

Puis, la fonction pour afficher la map (notez bien qu'elle est parcourue et affichéedans l'autre sens) :
(cpp):
void DessinerMap()
{
//Briques
for(int x = 0; x < 32; x++)
{
for(int y = 0; y < 12; y++)
{
//On teste si il y a une brique a cet endroit
if(map[y][x] == true)
{
DessinerBrique(x, y);
}
}
}
}
Ensuite, voici les 3 dernières fonctions, rien ou presque n'a changé depuis le tuto sur le casse briques :
(cpp):
bool RectCollision(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2)
{
if(x1+w1<x2) return false;
if(x2+w2<x1) return false;
if(y1+h1<y2) return false;
if(y2+h2<y1) return false;
//On arrive là, il y a donc collision
return true;
}
void drawTriangle(int x0, int y0, int x1, int y1, int x2, int y2, UL_COLOR color)
{
//We're drawing a simple colored triangle (untextured)
ulDisableTexture();
//Begins drawing quadliterals
ulVertexBegin(GL_TRIANGLES);
//You can define the color of each vertex before issuing the vertex command, or once for all, like here. If the vertices have different colors, a gradient will appear between each corner.
ulVertexColor(color);
//Draw our triangle (3 vertices)
ulVertexXY(x0, y0);
ulVertexXY(x1, y1);
ulVertexXY(x2, y2);
//End our drawing.
ulVertexEnd();
//Don't forget to auto-increment depth
ulVertexHandleDepth();
return;
}
//Fonction main
int main()
{
//Initialisation de l'uLib
ulInit(UL_INIT_ALL);
ulInitGfx();
ulInitText();
srand ( -(~IPC->unixTime) - 1) ; //Pour initialiser rand, si la lib ne le fait pas déjà ?
//On paramètre certains variables
world.largeur = 256;
world.hauteur = 192;
brique.largeur = 16;
brique.hauteur = 16;
vaisseau.largeur = 20;
vaisseau.hauteur = 16;
//Parametrage de la partie
RedemarrerJeu();
//Boucle Principale
while(true)
{
//Lecture de l'input
ulReadKeys(0);
//Update du jeu
scrollX += 256;
score++;
DeplacerVaisseau();
DeplacerBullets();
DeplacerEnnemis();
GererCollisions();
//AFFICHAGE
ulStartDrawing2D();
DessinerVaisseau();
DessinerBullets();
DessinerEnnemis();
DessinerMap();
ulSetTextColor(RGB15(31, 0, 31) );
ulPrintf_xy(5, 5, "Score : %d", score);
ulEndDrawing();
ulSyncFrame();
}
return 0;
}
IV ConclusionVoilà, votre petit shoot em up est prêt à être compilé !
Plusieurs pistes d'améliorations sont possibles :
- Faire de meilleurs graphismes
- Ajouter un menu de hi-scores
- Ajouter une gestion des power ups
- Transformer le jeu en Dodonpachi 4

Sources + Executable :
2HourShootGood luck !