Pages: [1]   Bas de page
Imprimer
Auteur Fil de discussion: [Tuto/NDS/ulib] Shoot shoot, le vaisseau qui shoote...  (Lu 4588 fois)
0 Membres et 1 Invité sur ce fil de discussion.
morukutsu Hors ligne
Sr. Member
****
Messages: 499


Voir le profil
Noctambule

« 18 Novembre 2009, 20:22:35 »

Hello @ tous, ce topic fait suite à celui-ci : http://www.dev-fr.org/index.php/topic,3859.0.html

Aujourd'hui, le but va être de faire un Shoot em up très sommaire au scrolling infini.
http://morukutsuland.free.fr/data/tutos/screen2.png
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é-requis
Ce tutoriel utilise l'ulibrary de Brunni (Pas de Carla...  whistle). 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 Conception
Un 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 code
Tout d'abord, voyons voir le fichier header qui contient les déclarations des fonctions que nous allons utiliser.

Code
(cpp):
//On inclue l'uLib
#include <ulib/ulib.h>

Ensuite, passons au fonctions, leur utilité parle d'elle même !
Code
(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.
Code
(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...
Code
(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...
Code
(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...
Code
(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...
Code
(cpp):
//16 * 16
struct sBrique
{
int hauteur, largeur;  //Taille de la brique
};

... et enfin une dernière pour les paramètres du monde !
Code
(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  police

Déclarons nos structures pour les utiliser plus tard :
Code
(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 Cheesy

Déclarons maintenant quelques variables utiles :
Code
(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.

Code
(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 :
Code
(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.

Code
(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 Smiley )

Puis la fonction pour dessiner un ennemi. Un simple assemblage de rectangles et de triangles.
Code
(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 :
Code
(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
Code
(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 :
Code
(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>>Cool%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:
Code
(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
Code
(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.

Code
(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.
Code
(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 Cheesy

Puis, la fonction pour afficher la map (notez bien qu'elle est parcourue et affichéedans l'autre sens) :
Code
(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 :
Code
(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 Conclusion
Voilà, 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  Cheesy

Sources + Executable : 2HourShoot

Good luck !
« Dernière édition: 18 Novembre 2009, 21:55:25 par morukutsu » Journalisée
MasterDje Hors ligne
Diet Coke Addict
Global Moderator
*****
Messages: 3249


Voir le profil WWW
« Réponse #1 : 18 Novembre 2009, 21:35:56 »

Oh joli !
Juste le temps de descendre l'ascenseur en lisant en diagonale...
Chapeau, Morukutsu... Cool à toi de relancer les tutos, et avec la manière, s'il vous plait  Smiley

Je poste mon bravo et je retourne lire ça à tête reposée !
Journalisée

EvilTroopa Hors ligne
Administrateur
*****
Messages: 648


Voir le profil WWW
1010011010 the Number of the Beast

« Réponse #2 : 19 Novembre 2009, 01:09:27 »

I Pré-requis
Ce tutoriel utilise l'ulibrary de Brunni (Pas de Carla...  whistle).

Pourtant quelqu'un m'a dit... que... tu codais encore, c'est quelqu'un qui m'a dit... que... tu codais encore, serait-ce possible alors ?

Ce commentaire vous a été offert par le ministère de la culture

Edit:

Une fois avoir passé le premier chapitre et repris mes esprits, j'ai décidé de finir de lire le tuto Azn
Je lève mon verre de sirop d'orgeat à ta santé Morukutsu !!
Très bon tuto pour débuttant Smiley
Les descriptions et explications me paraissent tout à fait claires et enrichissantes pour qui veut se lancer dans µlib !
« Dernière édition: 19 Novembre 2009, 01:41:14 par Spoon » Journalisée

A mushroom a day, keeps the koopas away.
t4ils Hors ligne
Branleur
Elite Member
**
Messages: 961


Voir le profil WWW
« Réponse #3 : 19 Novembre 2009, 06:46:21 »

Excellent moru, joli tuto Smiley

Petit truc qui pique les yeux :
Code:
vaisseau.y = (192/2)-(16/2);

Utilise le décalage binaire ici aussi Azn
Bon, c'est vrai que c'est pas dans une boucle de jeu donc ça bouffera pas des masses, mais on peut quand même éviter la division  angel
Journalisée

Le codage amateur ? Parce que je le veux bien.
Copper Hors ligne
Mega Member
***
Messages: 1296


Voir le profil
« Réponse #4 : 19 Novembre 2009, 07:46:46 »

J'espère quand même que le compilateur simplifie de lui même la constante en 96 - 8 = 88 ...
Journalisée
morukutsu Hors ligne
Sr. Member
****
Messages: 499


Voir le profil
Noctambule

« Réponse #5 : 19 Novembre 2009, 13:11:20 »

Code
(cpp):
vaisseau.y = (192/2)-(16/2);
C'est fait dans un souci de simplicité. Là on voit bien que c'est centré avec les divisions par 2. Le décalage binaire est peut pas évidant à comprendre pour les débutants. J'aurai pu mettre la constante directement aussi.
Journalisée
smartis Hors ligne
Newser
*****
Messages: 179


Voir le profil
« Réponse #6 : 19 Novembre 2009, 15:17:14 »

Bonjour,

Bon, la programmation, ce n'est clairement pas mon domaine de compétence, mais je ne peux que te féliciter pour ce tutorial !.

C'est très bien écris et clair, cela complète bien le précédent tuto sur le développement d'un casse brique.

Il faudrait même trouver un moyen de mettre les tutoriaux plus en valeur sur Dev-fr je pense.

Une idée du prochain tutoriaux ? un Puzzle Game peut-être ?


En tout cas, merci de faire partager tes connaissances avec les autre membres de Dev-fr !

à bientôt

Cordialement Adrien



« Dernière édition: 19 Novembre 2009, 15:25:02 par smartis » Journalisée
morukutsu Hors ligne
Sr. Member
****
Messages: 499


Voir le profil
Noctambule

« Réponse #7 : 19 Novembre 2009, 15:30:57 »

Merci Smiley

Citation
Il faudrait même trouver un moyen de mettre les tutoriaux plus en valeur sur Dev-fr je pense.
Ouais certainement, mais je ne vois pas trop de quelle façon.

Citation
Une idée du prochain tutoriaux ? un Puzzle Game peut-être ?
Je ne sais pas trop, ça dépendra de mon temps libre surtout  police
Journalisée
Bitcrushr Hors ligne
Newbie
*
Messages: 1


Voir le profil
« Réponse #8 : 18 Mars 2010, 09:27:54 »

Permets tu une adaptation pour le XNA ?  Azn

Ton code est fort bien commenté, il explique clairement les tenants et aboutissants... merci encore.
Journalisée
morukutsu Hors ligne
Sr. Member
****
Messages: 499


Voir le profil
Noctambule

« Réponse #9 : 18 Mars 2010, 10:41:06 »

Pas de soucis pour l'adaptation XNA, au contraire, j'encourage à diffuser ça de quelque façon que ce soit smiley

Citation
on code est fort bien commenté, il explique clairement les tenants et aboutissants... merci encore.
De rien Langue
Journalisée
Ludo6431 Hors ligne
Administrateur
*****
Messages: 903


Voir le profil WWW
It flies !

« Réponse #10 : 19 Mars 2010, 00:38:48 »

Oh tient, j'l'avais pas vu ce sujet Smiley
Bien sympa
[/commentaire inutile]
Journalisée

Mon matériel : DS Lite blanche flashée v8 | DSi noire | SCLite | SCDS ONE v2 | SCDS TWO | DSerial EDGE | MK-R6 gold | rumble pack | R4(r4ds.cn) | M3i Zero | Acekard 2i | iTouch DS | CycloPS' iEvolution
thoduv Hors ligne
Sr. Member
****
Messages: 364


Voir le profil WWW
Hello world...

« Réponse #11 : 19 Mars 2010, 17:48:12 »

Petit truc qui pique les yeux :
Code:
vaisseau.y = (192/2)-(16/2);

Utilise le décalage binaire ici aussi Azn
Bon, c'est vrai que c'est pas dans une boucle de jeu donc ça bouffera pas des masses, mais on peut quand même éviter la division  angel
Pas du tout ! C'est des constantes tout ça, donc tu pense bien que c'est calculé lors de la compilation : y'a aucun problème ! Wink
Journalisée

jasonsmith092 Hors ligne
Newbie
*
Messages: 1


Voir le profil
« Réponse #12 : 11 Mai 2010, 05:29:48 »

Its really Amazing.....
Sounds good to me.
Journalisée

A lot of information here  aboutmcsa exams
for study
pmp course
and for information
project management professional
Pages: [1]   Haut de page
Imprimer

Aller à: