Jeux Libres
       
           

» Les Tutoriels » Création d'un jeu vidéo » Création de la scène

Création de la scène


Nous allons maintenant prendre le temps de réfléchir un peu sur la façon de structurer le jeu, puis nous modéliserons la scène.





Chapitre précédent     Sommaire     Chapitre suivant


Phase de réflexion


Une scène, c'est quoi ?



La première chose qu'on peut se demander c'est : "qu'est ce qu'une scène ?".

Dans notre cas, une scène sera une succession de dessins qui représentent une manche du jeu au fil du temps. Pour que la scène évolue au fil de temps, des intervenants extérieurs doivent pouvoir modifier le cours de la scène. Par exemple, à tout moment, le joueur peut choisir de faire avancer son personnage ou l'arrêter. Il va donc falloir gérer les évènements extérieurs à la scène (clavier, souris...) et les prendre en compte dans l'évolution de la scène. Ensuite, il va falloir animer la scène : la faire évoluer d'un échantillon de temps par rapport à la scène à l'instant précédent. Une gestion du temps devras donc être mise en place.

En quoi consiste l'exécution de la scène ?



A priori, l'exécution de la scène consiste donc à effectuer les étapes suivantes, dans l'ordre :

  • Lecture des évènements (souris, clavier... ex: le joueur choisi d'avancer).
  • Animation de la scène (ex: le personnage avance un petit peu).
  • Dessin de la scène actuelle (Une fois animé, la scène est figée et dessinée).
  • Affichage de la scène dessinée.

Ces étapes doivent se faire en boucle et très rapidement.


On se rend bien compte que le temps doit être discrétisé (échantillonné). Notre scène n'évoluera pas de façon continue. Nous allons quand même essayer de créer 60 images par secondes afin que l'animation de la scène paraisse fluide. En effet, à 60 images par secondes (60 FPS), nous ne percevons pas le changement "brutal" d'une image à l'autre car les images sont très similaires et la durée d'une image est très courte. De plus, la plupart des écrans se contentent de n'afficher que 60 images par secondes. Si on en construisait 120, l'écran n'en afficherait qu'une sur deux, quoique vous fassiez. J'attire votre attention sur le fait qu'à 60 Hz, nous n'avons que 16 ms (0,016 seconde) pour construire l'image ! Il n'est pas question d'aller récupérer une texture sur le disque dur en pleine création d'image. C'est pourquoi nous chargerons tout le nécessaire avant de commencer l'exécution de la scène.



A 60 Hz, une image est affichée toutes les 16 ms.

La construction d'une image (évènements, animation, dessin) s'effectue entre l'affichage de deux images. Ainsi, l'image dessinée sera prête à l'heure prévue.


Construction de l'image 415.

L'image doit être dessinée et prête à être affichée avant l'heure d'affichage. Le temps de construction de l'image étant variable, un temps de repos variable permet de maintenir un nombre constant d'images par seconde.

Le temps de repos est géré par la fonction d'affichage SDL_GL_SwapBuffers(). Si, au niveau du pilote graphique, la synchronisation verticale est activée, cette fonction attend l'heure avant d'afficher réellement l'image. Si cette fonctionnalité n'est pas activée, l'affichage sera "effectué" immédiatement après la construction. Ce temps de construction étant peu variable, nous n'aurons pas à nous en préoccuper, la succession des images restera fluide malgré tout.


Prenons du recule !



Si on considère la scène comme un objet, la seule chose qu'on pourrait en faire, c'est l'exécuter. A priori, ce sera la seule méthode publique.

Entrons maintenant dans la scène



La scène est constituée d'une carte et d'un ou plusieurs personnages. Elle peut charger des textures, gérer les évènements, faire évoluer la scène...


Modélisation de la scène


La classe



Pour modéliser notre scène, nous allons créer un fichier scene.h et un fichier scene.cpp. Notre classe sera de type Scene.

Les méthodes publiques



Notre classe comportera un constructeur et un destructeur pour le chargement et la libération des ressources de la scène. On ferra attention à ne pas laisser de fuite de mémoire. La seule méthode publique qu'elle n'a besoin d'avoir est la méthode executer().

Les méthodes privés



Pour ce qui est des méthodes privés évidentes, il y aurra : gererEvenements(), animer(), dessiner() et afficher(). Pour le reste, nous verrons après.

Déclaration de la classe


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef SCENE_H_INCLUDED
#define SCENE_H_INCLUDED
 
class Scene
{
   private:
       void gererEvenements(void);
       void animer(void);
       void dessiner(void);
       void afficher(void);
 
   public:
       Scene();
       ~Scene();
       void executer();
};
 
#endif // SCENE_H_INCLUDED

Définition de la classe



La méthode executer()



C'est dans la méthode executer() que "tout" va se passer, on y a déjà réfléchi plus haut. Voici le code de la méthode :
1
2
3
4
5
6
7
8
9
10
11
12
void Scene::executer()
{
   this->continuer = TRUE;
 
   while(this->continuer)
   {
       gererEvenements();
       animer();
       dessiner();
       afficher();
   }
}

Afin de pouvoir arrêter la boucle, j'ai dû ajouter un attribut booléen que j'ai appelé continuer. Je l'initialise à TRUE avant de commencer ma boucle. Je le repasserai à FALSE lorsque l'utilisateur demandera l'arrêt du programme.

La méthode gererEvenements()


1
2
3
4
5
6
7
8
9
10
11
12
13
void Scene::gererEvenements(void)
{
   SDL_Event evenement;
 
   while (SDL_PollEvent(&evenement))
   {
       switch(evenement.type)
       {
           case SDL_QUIT:
               this->continuer = FALSE;
       }
   }
}

Pour le moment, on ne va gérer que l'évènement SDL_QUIT demandant l'arrêt du programme. L'entête #include <SDL/SDL.h> est nécessaire.

Il n'est pas question d'attendre un évènement de l'utilisateur pour redonner la main, même si l'utilisateur ne fait rien, la scène doit évoluer et faire le tour de la boucle de la méthode executer(). J'utilise donc la fonction SDL_PollEvent() pour rendre la main, quoiqu'il arrive.


La fonction main()



Très grossièrement, notre programme ne fera qu'instancier une scène et l'exécuter. Bien entendu, SDL et OpenGL doivent être initialisés.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "init.h"
#include "scene.h"
 
int main( int argc, char* argv[] )
{
   // Initialisation de la SDL et d'OpenGL
   initSDL();
   initOpenGL();
 
   // Execution de la scene
   Scene scene;
   scene.executer();
 
   // Arrt de la SDL
   SDL_Quit();
 
   return 0;
}

La méthode animer()



Pour l'instant, il n'y a rien à animer.

La méthode afficher()



On l'a vu dans le chapitre précédent, pour afficher le dessin à l'écran, on fait appel à deux fonctions.
1
2
3
4
5
6
void Scene::afficher(void)
{
   glFlush();
 
   SDL_GL_SwapBuffers();
}

La méthode dessiner()



Pour l'instant, nous ne dessinons rien. Dans la partie qui suit, je vous propose de charger une texture et de la dessiner.


Dessiner de l'herbe


Nous allons maintenant dessiner de l'herbe dans la scène : ce sera le sol. Cette partie n'est pas très réfléchie, mais va nous permettre de nous familiariser avec la classe std::map de la bibliothèque STL, les textures OpenGL et le repère à 3 dimensions.

Chargement des textures



"Tableau" de textures



Il pourrait y avoir plusieurs textures à charger. On va donc créer une méthode void Scene::chargerTextures(void) qui aura pour but de faire remonter les textures à la carte graphique.

Pourquoi en carte graphique ?


La carte graphique est beaucoup plus rapide pour faire le traitement d'image numérique : c'est son rôle. On va donc l'utiliser beaucoup avec OpenGL. Quelque soit le point de vue de la scène, les textures utilisées seront toujours les mêmes, on ne les chargeras donc qu'une fois. Autant les mettre directement là où elle seront utilisées.


Lorsqu'on place une texture en carte graphique, OpenGL nous retourne un nombre entier supérieur ou égal à 1. C'est l'identifiant de la texture.

Un nombre, ce n'est pas très parlant. On va donc faire une correspondance entre le numéro de la texture et un nom de texture plus parlant. La solution que je vous propose est d'utiliser un type permettant de faire la correspondance entre un GLuint, identifiant de texture OpenGL, et un std::string, identifiant plus parlant. La classe std::map fait très bien ce genre de chose.

On va donc redéfinir ce type en Textures directement au sein de la classe Scene.
1
2
3
4
5
6
7
8
class Scene
{
   private:
       typedef std::map<std::string, GLuint> Textures;
 
 
       // Les mthodes dj dclares...
};

Puis on déclare un attribut de type Textures :
1
2
3
4
5
6
7
8
9
10
11
class Scene
{
   private:
       typedef std::map<std::string, GLuint> Textures;
 
       bool8 continuer;
       Textures textures;
 
 
       // Les mthodes dj dclarres...
};

L'objet std::map agit comme un tableau de taille dynamique. Ses cases sont créées au moment où on les sollicite. Dans notre cas, l'index de la case est un std::string et sa valeur est un GLuint.


Charger une texture



Pour ne pas se compliquer la vie, le plus simple est de récupérer les fichiers de ce tutorial qui, après quelques adaptations, donne ceci.

Il y a beaucoup de fonctions, nous ne nous servirons pas de tout.

Après avoir ajouté ces fichiers au projet, nous pourrons directement faire appel à la fonction suivante :
1
GLuint loadTexture(const char* filename, bool useMipMap = true);

Elle prend en paramètre le nom du fichier BMP à charger et retourne le numéro d'identification de la texture chargée dans la carte graphique.


Le second paramètre permet d'utiliser le niveau de détail multiple (MIP map). Je recommande de l'utiliser. La texture prendra le double de place en mémoire mais sera rendu plus rapidement, notamment lorsqu'elle est dessinée profondément dans la scène. Il est initialisé par défaut à true, on va donc le laisser tel quel.


Pour les geeks : Nativement, OpenGL ne sait pas charger une texture depuis un fichier, mais SDL sait construire une SDL_Surface à partir d'un fichier BMP. Si vous êtes curieux, vous pouvez jeter un coup d'?il à la fonction et comprendre ce qu'il s'y passe. Dans un prochain chapitre, nous recréerons la fonction de chargement des textures et nous verrons ce qu'il s'y cache réellement.


Charger la texture de l'herbe



Nous avons maintenant tout ce qu'il faut pour écrire notre méthode chargerTextures().
1
2
3
4
void Scene::chargerTextures(void)
{
   this->textures["herbe"] = loadTexture("herbe.bmp");
}

C'est aussi simple que ça ! Le numéro de la texture sera enregistré dans la case "herbe".

Je vous donne la texture d'herbe que j'ai utilisée :


Si vous récupérez cette texture, pensez à la ré-enregistrer en BMP !


Ajoutez un appel à la méthode précédemment créé dans le constructeur pour charger les textures à l'instanciation de la scène.

N'exécutez pas le programme comme tel ! Si vous l'exécutez, la texture chargée en carte graphique ne sera jamais libérée. Un redémarrage de l'ordinateur deviendra nécessaire.


On va donc parcourir le "tableau" de textures et les libérer une par une lors de la destruction de la scène. Pour l'instant il n'y a qu'une texture, mais si à l'avenir on souhaite en ajouter une autre, sa déstruction sera déjà prise en compte.

La fonction qui permet de détruire une texture est glDeleteTextures().

Cette fonction prend 2 paramètres : le nombre de textures à libérer et un pointeur sur le tableau de numéros de textures à libérer.

Dans notre cas, nous n'en libèrerons qu'une à la fois. On fournira alors l'adresse du numéro de la texture à libérer.
1
2
3
4
5
6
7
8
9
Scene::~Scene()
{
   // Suppression des textures charges
   for (Textures::iterator element = this->textures.begin(); element != textures.end(); element++)
   {
       glDeleteTextures(1, &element->second);
       element->second = 0;
   }
}

Remarquez la façon de parcourir le tableau qui est assez atypique. Après avoir libéré la texture, j'injecte la valeur 0 dans case, synonyme de "texture invalide".
Dans la boucle, element->first fait référence à l'index du tableau et element->second au contenu de la case.

Pour bien comprendre le fonctionnement de l'objet std::map, je vous recommande cette doc.

Nous savons maintenant charger une texture, ranger son identifiant dans un tableau, et libérer les textures.

Dessin de la texture dans la scène



Je vous propose de créer, au sein de la classe, une méthode dessinerTerrain() qui dessinera le terrain dans la scène.

Rappelez-vous, avant de dessiner vous devez vider la scène et y placer la caméra.


Dans la méthode dessiner() on va donc vider la scène, placer la caméra et dessiner le terrain.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void Scene::dessiner(void)
{
   // Vidage de l'cran
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
   // Place la camera
   glMatrixMode( GL_MODELVIEW );
   glLoadIdentity();
   gluLookAt(3,4,2,0,0,0,0,0,1);
 
 
   // Dessin du terrain
   this->dessinerTerrain();
}

Avant de dessiner une texture, on doit s'assurer que le texturage est activé. On sélectionne ensuite la texture "herbe" pour dessiner un quadrilatère. Puis on dessine la texture en tournant dans le sens trigo. Et pour finir, on appelle : glEnd().

Voici le code de la méthode dessinerTerrain() :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void Scene::dessinerTerrain(void)
{
   // Activation des textures
   glEnable(GL_TEXTURE_2D);
   
   // Slection de la texture
   glBindTexture(GL_TEXTURE_2D, textures["herbe"]);
 
   // On va dessiner un quadrilatre, sans teinte
   glBegin(GL_QUADS);
   glColor3ub(255,255,255);
 
   // Position des sommets de la texture dans la scene
   glTexCoord2d(0, 1); glVertex3d(-2, -2, 0);
   glTexCoord2d(0, 0); glVertex3d(2, -2, 0);
   glTexCoord2d(1, 0); glVertex3d(2, 2, 0);
   glTexCoord2d(1, 1); glVertex3d(-2, 2, 0);
 
   // Fin de la "srie" de quadrilatres
   glEnd();
}

Les coordonnées des textures 2D chargées sont différentes des surfaces SDL. Le (0;0) est en bas à gauche, les X vers la droite et les Y vers le haut.
Et pour ce qui est du repère à 3 dimensions, souvenez-vous qu'on a choisi les Z vers le haut, les X vers nous et les Y vers la droite.


Vous pouvez maintenant lancer l'exécution, vous devriez obtenir ceci :





Nous venons de voir comment sera structuré la scène. Tout au long du cours nous allons lui ajouter des choses.

Dans le chapitre suivant nous allons ajouter une skybox pour créer le ciel.

Téléchargement : le projet actuel



Chapitre précédent     Sommaire     Chapitre suivant



Rédigé par David
Consulté 35972 fois



Hébergeur du site : David
Version PHP : 5.4.45-0+deb7u2
Uptime : 16 jours 6 heures 45 minutes
Espace libre : 1599 Mo
Dernière sauvegarde : 09/12/2018
Taille de la sauvegarde : 1109 Mo


4958010 pages ont été consultées sur le site !
Dont 2295 pages pendant les 24 dernières heures.

Page générée en 0.996 secondes


Nos sites préférés
- Création d'un jeu de plateforme de A à Z avec SDL
- Zelda ROTH : Jeux amateurs sur le thème de Zelda
- Zeste de Savoir : la connaissance pour tous et sans pépins
- YunoHost : s'héberger soi-même en toute simplicité
- Site de Fvirtman : recueil de projets et de codes en C et C++
- Par ici la sortie : le site des idées de sorties


  © 2005-2018 linor.fr - Toute reproduction totale ou partielle du contenu de ce site est strictement interdite.