Tuto creation d'un jeu : 1942Shooter

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.

  • Le joueur sera à gauche les ennemis arriverons de la droite.
  • Le joueur pourra bouger de bas en haut
  • Les ennemis bougeront de droite à gauche
  • Le joueur perds si un ennemis arrive jusqu’à lui ou si un ennemi le tue
  • Le joueur devra tirer sur les ennemis pour les arrêter

Prérequis

  • Avoir une Gamebuino META
  • Avoir fait l'atelier Installation de la Gamebuino META
  • Avoir un minimum de connaissance en programmation


But :

  • Structurer un programme pour avoir un code propre et réutilisable.
  • Utiliser la notion d’état pour passer d’un état à l’autre de notre jeu
  • Séparation logique /graphique


1 le squelette de l’application

Nous allons initialiser les différents états de notre jeu : 

  • Le spash screen  lors du démarrage
  • L’état in Game qui est le jeu à proprement parler


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


2 in game

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


3 IA (de ouf)

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

 4 Un peu de pixel art

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



Barricade

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 

  • polish du tuto 
  • Barriere de protection pour le joueur 
  • Balle ennemies
  • Ennemies plus "inteligent"
  • Vague d'ennemies
  • gestion de niveau
  • Ajout de graphisme