Je m'excuse par avance pour ceux qui aurons les yeux qui saigne a cause de mes fautes de "frappes" ;) .
Niveau intermédiaire
Création d’un jeu de shoot.
Prérequis
But :
Nous allons initialiser les différents états de notre jeu :
Pour chaque etat de notre jeux nous allons créer un fichier ino qui gerera uniquement cet etat.
Chaque fichier porte l’initialisation/les mise à jour et le graphisme uniquement pour lui.
Dans chaque fichier on a une method init [NomDuFIchier] (),update [NomDuFIchier] (),draw [NomDuFIchier] ()
Ceci nous permet d’avoir un fichier qui gère un seul état et une méthode qui gère un seul comportement.
//appeler une fois pour initialiser les etat de ce fichier // par exemple si on est sur le joueur on vas le positionner sur le point de depart void init() {} //appeler a toutes les frame , gere le comportement de ce fichier //par exemple si on est sur le fichier du joueur c'est ici que nous allons detecter les pression sur les boutons // pour deplacer le personnage void update() {
}
//permet de dessiner notre objet //le fait de bien separer le comportement des graphisme me permet de commencer ace de simple rectagle // puis d evoluer sur des sprite plus complexe sans avoir a retoucher aux methodes de comportement // souvent plus complexe et plus dure a maintenir void draw() {
}
Source :
https://github.com/Clement83/1942Shooter/releases/tag/1_skeleton
https://github.com/Clement83/1942Shooter/tree/1_skeleton
Maintenant que le squelette est fait nous allons passer au jeu.
Pour commencer nous allons avoir besoins de trois nouveaux fichiers
Player.ino, ennemies.ino, et playerBullet.ino
Nous allons aussi déclarer les différentes structures dont nous aurons besoin et déclarer les variables globale dans 1942Shooter.ino
pour nos objets nous utilisons des stucture qui nous permettent de regrouper plusieur variable sous une meme variable
struct Player { int y; int life; boolean fire; };
Les ennemies, le joueur et , les balle serons utiliser à plusieurs endroit ils sont donc déclarer en globale
Dans inGame.ino on appelle les méthodes de nos nouveau fichiers (update/init /draw)
Comme énoncé plus haut pour les états , chacun de nos nouveau fichier n'aura qu'un type d'objet a géré. par exemple le fichier player.ino gerera les mouvements du players, l'affichage du player, et l'initialisation du player. et puis c est a peux pres tout :)
On ajoute dans inGame.ino la détection de la fin de la partie (soit le joueur a tué tout le monde soit un ennemi a franchi votre ligne) et le tour est joué.
chaque etat de notre jeux et le seul maitre de son evolution.
C est lui qui vas appeler les methodes init/update/draw des objet qu il gere
et c est lui qui sait a quel moment il vas changer d etat et dans quel etat il peux passer
par exemple pour l etat in game :
// une method nous permet de detecter la fin du jeux boolean isEndGame() { int dead = 0; for(int i = 0 ; i< NB_MAX_ENNEMIES; ++i) { if(ennemies[i].life>0) { if(ennemies[i].x< 2) { return true; } } else { ++dead; } } if(dead>=NB_MAX_ENNEMIES) { return true; } return false; }//dans l init il initialise tous les objets qu il manage void initInGame() { initPlayer(); initEnnemies(); initPlayerBullet(); } // de meme il update tous les objets void updateInGame() { updatePlayer(); updateEnnemies(); updatePlayerBullet(); if(isEndGame()) { //si la fin de parti est valider on passe sur l etat game over gameState = STATE_GAME_OVER; }
} //on dessie tous les objet lié void drawInGame() { drawPlayer(); drawEnnemies(); drawPlayerBullet(); }
Source :
https://github.com/Clement83/1942Shooter/releases/tag/2_in_game
https://github.com/Clement83/1942Shooter/tree/2_in_game
Nous allons rendre les ennemies moins prévisible et plus armé.
Premièrement nous créons un fichier ennemiesBullet qui gérera les balles ennemies
Ce fichier est un copier collé du fichier playerBullet, et nous verrons rapidement que beaucoup des méthodes de ces deux fichier sont identique. Nous les factoriserons plus tard. Pour le moment on renomme les player par des Ennemies et on inverse le sens des balles :
ennemyBullet[i].x -= VELOCITY_ENNEMY_BULLET;//go bullet go!
Bien voire le -= a la place du +=
Dans 1942Shooter nous ajoutons les variables de gestion des balles ennemies
#define NB_ENNEMY_BULLET 10 Bullet ennemyBullet[NB_ENNEMY_BULLET];
Petite modification aussi dans la methode createNewEnnemy Bullet.
Cette method prend en paramètre un ennemy pour pouvoir positionner la nouvelle balle
void createNewEnnemyBullet(Ennemies ennemy)
Dans le fichier ennemies nous allons ajouter une method actionEnnemy qui prend en parametre un pointeur sur un ennemy.
void actionEnnemy(Ennemies *ennemy)
Cette methode vas modifier l’instance de l’ennemy c’est pour cela que nous envoyons un pointer (aussi appelé une référence) plutôt qu'une valeur. (étant développer web c#/php/javascript je ne suis plus tres au fait des pointeurs en C, escusez moi si je raconte des truc plus ou moins faut)
Avec un random et deux if on gere les action de nos ennemies de façon aleatoir .
Attention pour modifier l ennemy nous utilison la syntaxe -> car nous somme sur un pointeur
ennemy->x
pour le moment j ai mis : à chaque frame on a 3% de tirer et 27 % de chance d avancer
il y a donc 70% de chance de rien faire.
A ce niveau-là nous avons une IA bien plus intéressante
Release : https://github.com/Clement83/1942Shooter/releases/tag/3_ia_enn
Code : https://github.com/Clement83/1942Shooter/tree/3_ia_enn
Ce soir un peu de « déco » pour le jeu
Je fais un personnage unique qui servira pour le moment aux ennemies et au joueur
Ce personnage devra être capable de rester sans bouger, de courir et de tirer. Je prends 2 frame par animation (une seul pour le sans bougé)
Dans la structure Player je veux savoir combien de temps dure mon tire
Pour cela je change mon boolean fire en int. Cela devient un compteur de frame
Je ne pourrais tirer que si le compteur est à zéro. Au moment du tire je l initialise a 3 pour faire un tour de mon animation.
Evidemment je met ce nombre dans une constante pour pouvoir le modifier facilement
#define NB_FRAME_FIRE_ANIM 3
Dans le updatePlayer je mets à jour ce compteur :
if (soldat.fire>0) { --soldat.fire; }
Ne pas oublier de modifier dans playerBullet/updatePlayerBullet
if(soldat.fire == NB_FRAME_FIRE_ANIM) { createNewSoldatBullet(); }
De même il faut que je sache si mon personnage est en mouvement ou non.
Dans un jeu avec un minimum de physique nous aurions pu nous baser sur les variables de vélocité du personnage. Ici nous n’en avons pas il faut donc ajouter une variable qui nous indique l’état de notre personnage.
struct Player { int y; int life; int fire; boolean isRun; };
Le boolean isRun nous permettra de savoir quel sprite afficher je le met a false dans l'init du player et en début d update , si j’appuis sur haut ou bas je le met à true
Enfin je draw tout ca :
void drawPlayer() { gb.display.setColor(GREEN);if(soldat.fire == NB_FRAME_FIRE_ANIM) { gb.lights.fill(YELLOW); } if(soldat.fire>0) { gb.display.drawImage( SOLDAT_X , soldat.y , soldatFire); } else if(soldat.isRun == true){ gb.display.drawImage( SOLDAT_X , soldat.y , soldatRun); } else { gb.display.drawImage( SOLDAT_X , soldat.y , soldatIdl); } }
Je fais pareil pour les ennemies en changeant les nom des image par ennemiyMonNomDiMage .
J’y ajouter une subtilité, les ennemies ne font rien pendant le tire
Pour voir les personnages il faut ajouter un fond au monde.
Nous allons donc ajouter un fichier ino world.ino avec la même structure que les précèdent.
Pour le moment seul le draw aura une action (peindre le fond d’une couleur, en vert par exemple) mais dans la venir on pourra imaginer des animations de fond ou autre qui viendront s’ajouter ici
J’ajoute les appels à initWorld/updateWorld/drawWorld dans inGame.ino
J’en profite pour rappeler que l’ordre dans lequel sont appelées les méthodes est important.
Mettre draw world en dernier et votre écran sera complètement recouvert de vert .
Source :
Release : https://github.com/Clement83/1942Shooter/releases/tag/4_player_sprite
Code : https://github.com/Clement83/1942Shooter/tree/4_player_sprite
Je vais passer aux barricades pour protéger notre soldat.
Ces barricade aurons un certain nombre de point de vie et donc on les verra ce dégrader au fur et à mesure qu’elles se font tirer dessus.
Je passerais rapidement sur l’implementation des baricade car nous allons suivre le shema habituel
Création d’un barricade.ino avec les methodes init/update/draw.
A ce point du tuto on commence a ressentire les limite de cette architecture.
Avec de la programmation orienté objet nous aurions pu faire une arborescence d’héritage qui nous aurais permis d’éviter pas mal de copier-coller. C’est un problème qu’essaye de résoudre par exemple @ZappedCow avec GBX.
Ceci étant dit on passe à la suite.
Lors de la création du fichier barricade ne pas oublier d’ajouter l’appel des méthodes init/update/draw dans inGame.ino
Attention l’ordre du draw a une importance, je mets le draw des barricades avant le draw des balles mais après celui des ennemies.
Plus haut je dis que nous voyons ici les limites de cette architecture.
Mais nous voyons aussi toute la puissance de bien organiser son code.
En 30 minutes et sans aucun risque que plus rien ne marche j’ai pu ajouter la gestion des barricades
release: https://github.com/Clement83/1942Shooter/releases/tag/5_barricade
code: https://github.com/Clement83/1942Shooter/tree/5_barricade
A venir