Jeux Libres
       
           

» Les Tutoriels » Création d'un jeu vidéo » Améliorations à apporter au jeu

Améliorations à apporter au jeu


Le projet commence à prendre de l'ampleur et tout n'est pas parfait. Ce chapitre va faire la transition entre notre projet actuel et ce dont on doit obtenir pour continuer sur de bonnes bases. Nous allons parler de tout ce dont je n'ai pas pu aborder plus tôt et des améliorations qu'il est temps d'effectuer pour continuer sur une base propre.





Chapitre précédent     Sommaire     Chapitre suivant


Déplacement du personnage


La partie réseau commence à approcher. J'ai commencé à réfléchir au protocole de communication et par soucis de simplicité, il sévère utile de réécrire la méthode avancer Personnage::avancer() pour prendre en compte la direction du déplacement.

Le nouveau prototype de la méthode devient alors :
1
void Personnage::avancer(float16 distance, float16 direction, bool8 entourage[8]);

Le nouveau paramètre direction représente l'angle vers lequel se déplace le personnage par rapport à son azimut. Si direction vaut 0.0, le personnage se déplace droit devant lui. Pour une valeur de 90.0, le personnage se déplace vers la gauche et pour -90.0, vers la droite.

La direction finale vers laquelle se déplace le personnage est alors direction + this->angleHorizontal. On fait donc la somme des deux angles puis on affecte le résultat à notre variable direction. Inutile de créer une nouvelle variable.
1
direction += this->angleHorizontal;

Notre direction qui était this->angleHorizontal devient alors direction. On remplace donc l'attribut angleHorizontal par notre nouvelle variable direction partout dans la méthode, ce qui devrait faire l'affaire.

Notre méthode ne sert donc plus à avancer mais, de façon générale, à se déplacer. On peut donc l'appeler Personnage::deplacer(). Les méthodes Personnage::gauche() et Personnage::droite() peuvent maintenant être supprimées.

Toutes ces modifications nous poussent à revoir notre méthode Scene::animer() que j'ai redéfinie comme ceci, je vous laisse comprendre ce code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
void Scene::animer(void)
{
   // Lecture de l'tat des touches
   int nombreDeTouches;
   Uint8* pTouches = SDL_GetKeyState(&nombreDeTouches);
   Uint8* touches = new Uint8[nombreDeTouches];
   memcpy(touches, pTouches, nombreDeTouches);
 
   // Evite les deplacements absurdes
   if (touches[SDLK_w] == touches[SDLK_s]) // Avancer et reculer
   {
       touches[SDLK_w] = 0u; // Ici, on travaille sur
       touches[SDLK_s] = 0u; // une copie des touches
   }
   if (touches[SDLK_a] == touches[SDLK_d]) // Gauche et droite
   {
       touches[SDLK_a] = 0u;
       touches[SDLK_d] = 0u;
   }
 
   float16 direction = 0.0;
   bool8 deplacement = FALSE;
 
   if (touches[SDLK_w]) // Touche Z
   {
       // Avancer
 
       deplacement = TRUE;
 
       // En diagonale
       if (touches[SDLK_a]) direction = 45.0;
       else if (touches[SDLK_d]) direction = -45.0;
 
       // Droit
       else direction = 0.0;
   }
   else if (touches[SDLK_s]) // Touche S
   {
       // Reculer
 
       deplacement = TRUE;
 
       // En diagonale
       if (touches[SDLK_a]) direction = 135.0;
       else if (touches[SDLK_d]) direction = -135.0;
 
       // Droit
       else direction = 180.0;
   }
 
   if(FALSE == deplacement)
   {
       if (touches[SDLK_a]) // Touche Q
       {
           // Gauche
           direction = 90.0;
           deplacement = TRUE;
       }
       else if (touches[SDLK_d]) // Touche D
       {
           // Droite
           direction = -90.0;
           deplacement = TRUE;
       }
   }
 
   delete[] touches;
 
   // Si un deplacement est demande
   if (deplacement)
   {
       #define VITESSE_DEPLACEMENT (2.0f)
 
       // Calcule de la distance parcourir
       float16 distance = (float)tempsDernierPas * VITESSE_DEPLACEMENT / 1000.0f;
 
       // Recuperation de l'environnement
       sint32 positionCarteX = 0, positionCarteY = 0;
       bool8 entouragePersonnage[8];
       this->personnage->positionSurLaCarte(&positionCarteX, &positionCarteY);
       this->carte->entourage(positionCarteY, positionCarteX, entouragePersonnage);
 
       // Deplacement du personnage dans la direction demande
       this->personnage->deplacer(distance, direction, entouragePersonnage);
   }
}

Comme vous pouvez le voir, le code est maintenant beaucoup plus propre, plus rapide à l'exécution notamment dans le cas d'un déplacement en diagonale, et il nous simplifiera le développement lorsqu'il s'agira d'envoyer un message sur le réseau pour indiquer la direction du déplacement du personnage.


Dans la peau du personnage


Jusqu'à maintenant, on déplace la caméra manuellement directement dans le code. Ce que je vous propose de faire maintenant, c'est de placer la caméra dans la peau du personnage puis d'arrêter de dessiner notre personnage.

Lorsqu'on déplacera le personnage, ce qui est déjà fait, on se verra bouger avec la camera comme si nous vivions dans la peau du personnage.

Placer la caméra dans le personnage



Je vous propose donc de créer une méthode Personnage::regarder() qui aura pour effet de mettre la camera au niveau du champ de vision du personnage.

Il s'agit donc de faire appel à la fonction gluLookAt() qui prend 3 vecteurs : la position de l'oeil, le point regardé, puis le vecteur de la verticale. Pour la verticale, ce sera 0,0,1 car nous voulons la verticale en Z+. Pour la position de l'oeil, il s'agit de la position (X;Y) de notre personnage. On choisira 0.8 pour la valeur de Z. Pour le point regardé, il faudra calculer un point situé devant le personnage et indiquer à la fonction gluLookAt() de regarder ce point.

Pour l'instant, si on code la partie facile de la méthode, on obtient ceci :
1
2
3
4
void Personnage::regarder(void)
{
   gluLookAt(this->positionX, this->positionY, 0.5, this->positionX-1, this->positionY, 0.5, 0, 0, 1);
}

La caméra est positionnée dans le personnage et regarde au nord du personnage.

Dans Scene::dessiner(), on remplace notre gluLookAt() par notre nouvelle méthode puis on supprime l'appel à la methode this->personnage->dessiner().

On teste :


Le test a l'air convaincant.

Calcule du point regardé



Pour calculer la position du point regardé (figurant au centre de l'écran), on fait quelques calcules de trigonométrie.


On obtient alors :
1
2
3
4
5
6
7
8
9
10
11
12
#define RADIANS_PAR_DEGRES 0.0174532925199
 
// Position de l'oeil
gluLookAt(positionX, positionY, 0.8,
 
// Point regarde
positionX - cos(-angleHorizontal * RADIANS_PAR_DEGRES),
positionY + sin(-angleHorizontal * RADIANS_PAR_DEGRES),
0.8,
 
// Verticale en Z+
0,0,1);

Attention : L'angle que prennent les fonctions sin() et cos() de math.h est en radian.


Test de la vue à la première personne



Vous devez maintenant être dans la peau du personnage. Vous pouvez tourner sur vous même, vous déplacer... Je ne fais pas de vidéo, le résultat est déjà sous vos yeux.

Lever et baisser la tête



Pour le moment, l'?il est à 0.8 et regarde à 0.8 de hauteur. Je vous propose de faire en sorte que le personnage puis lever et baisser la tête en ajoutant un attribut angleVertical à la classe Personnage, qu'on initialisera à 0.0 dans le constructeur. Cette attribut prendra ses valeurs dans l'intervalle [-45.0;+45.0] représentant l'angle en degrés. 0° regarde devant lui, +45° vers le haut et -45° vers le bas.

Tout à l'heure, nous avons choisi H = 1. Le personnage regarde le point situé à une unité devant lui. Pour faire bouger la tête à la verticale, il suffira de déplacer ce point le long d'une droite verticale situé à une unité devant le personnage. Cela se passe donc au niveau de l'appel à la fonction gluLookAt().


On ajoute une méthode Personnage::tournerVerticalement(float16 angle) qui prend en paramètre l'angle à ajouter à l'angle actuel en n'oubliant pas de brider l'angle à l'intervalle [-45.0;+45.0]. On ajout alors le nécessaire à notre méthode de gestion des évènements pour permettre au joueur d'agir sur cet attribut en fonction du déplacement relatif de la souris en Y.

Pour cette partie, je ne fournirai pas de code. Si vous avez besoin d'un coup de main, n'hésitez pas à vous aider du forum.

Voici le résultat que vous devez obtenir :



MIP mapping


  Le MIP mapping est une technique d'application de textures, les MIP maps, qui permet d'améliorer la qualité de l'affichage.

Le but du MIP mapping est d'éviter la pixelisation lorsqu'on s'éloigne d'une texture.

Le niveau de détail des textures est adapté à la distance de l'objet. Ainsi, un objet proche affichera des textures en haute résolution tandis qu'un objet lointain se verra attribuer une texture de faible taille.
      Wikipedia


Pour améliorer le rendu des objets lointains, nous allons faire du MIP mapping. La technique du MIP mapping est très simple à comprendre, je n'étendrais pas le cours sur ce sujet. Nous nous contenterons de la mettre en place, très simplement.

Pour cela, retournons dans la méthode ConteneurTextures::chargerTexture() à la ligne ou s'effectue le chargement de la texture dans la carte graphique.
1
2
glTexImage2D( GL_TEXTURE_2D, 0, octetsParPixel, surface->w, surface->h, 0,
             formatTexture, GL_UNSIGNED_BYTE, surface->pixels );

Création des MIP maps



Pour créer les MIP maps, remplacez cette instruction par :
1
gluBuild2DMipmaps(GL_TEXTURE_2D, octetsParPixel, surface->w, surface->h, formatTexture,GL_UNSIGNED_BYTE, surface->pixels);

Cette fonction a le même effet que la fonction précédente mais crée des textures supplémentaires de faible résolution pour les rendus lointains. Cette technique engendre un rendu plus rapide et plus agréable. En contrepartie, les textures occupent plus de place.

Activation du MIP mapping



Pour activer le MIP mapping, faite simplement un appel à la fonction suivante :
1
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

Sans MIP maps




Avec MIP maps




La différence se fait surtout sentir au niveau de l'herbe au loin.


Percevoir la scène en relief


Cette partie bonus va vous permettre faire en sorte d'afficher votre scène 3D en relief grâce à des lunettes 3D. Mon avis sur cette technique : c'est amusant mais loin d'être agréable. On va donc voir comment faire pour rendre la scène en relief mais je ne conserverais sans doute pas cette implémentation pour la suite.

Se procurer des lunettes 3D



La première étape difficile, ça va être de se procurer des lunettes 3D anaglyphique. Si vous en avez déjà, c'est parfait. Sinon, vous pouvez peut-être en trouver dans un magasine ou sur Internet. Personnellement j'ai acheté les miennes sur www.panosphere-store.com (15,90 ? + frais de port) et j'en suis content. J'ai pris des rouge-cyan et des vert-magenta en carton pour essayer les deux. Les moyens de paiement sont nombreux et les livraisons sont rapides.

Si vous connaissez un autre site, merci de le faire savoir au reste de la communauté à l'aide du forum. Je vous en remercie d'avance.

Des lunettes 3D anaglyphiques, c'est quoi ?



Une paire de lunette 3D anaglyphique est une paire de lunette composé de 2 filtres optiques. Chacun de ces filtres ne laisse passer que certaines lumières.


Un filtre rouge ne laisse passer que la lumière rouge.
Un filtre vert ne laisse passer que la lumière verte.
Un filtre bleu ne laisse passer que la lumière bleue.

Un filtre jaune ne laisse passer que les lumières rouge et verte.
Un filtre cyan ne laisse passer que les lumières verte et bleue.
Un filtre magenta ne laisse passer que les lumières bleue et rouge.

Ce n'est pas très compliqué à comprendre. Si vous n'avez pas compris immédiatement, je vous demanderai de faire un petit effort.

Pour ceux qui serait tenté de faire leurs lunettes 3D avec des papiers de bonbon, sachez que le résultat sera très mauvais. En effet, un papier de bonbon bleu est loin de ne laisser passer que la lumière bleue. Il laissera passer passer trop de vert et de rouge et provoquera beaucoup d'image fantôme. On appelle "image fantôme" les images visibles qui devrait théoriquement être masquées par le filtre. Le relief n'est alors pas visible.


Le relief, c'est quoi ?



Le relief est l'interprétation de profondeur que l'on fait à partir des différences de deux images : celle de l'?il gauche et celle de l'?il droit.


L'?il gauche vois plus la face A que l'?il droit

La différence des deux images observées par chaque ?il est interprété ainsi : "l'arête verticale du milieu est plus à droite pour l'?il gauche que pour l'?il droit, cette arrêt est donc plus près de moi que les autre, (...) , c'est un cube !".

Comment créer du relief ?



Pour créer du relief, nous devrons dessiner deux images : celle de l'?il gauche et celle de l'?il droit. Ces deux images différentes doivent être affichées à l'écran.


?il gauche                                                                    ?il droite

Le problème, c'est qu'on ne peut par dire à chaque ?il : "toi, tu regarde ça ! " et "toi, tu tu regarde ça !". De plus, il est difficile de faire regarder quelque chose à un ?il et quelque chose d'autre à l'autre ?il.

La "solution" c'est de mettre les deux images au même endroit. Un nouveau problème intervient. Si deux images sont au même endroit, les deux yeux risquent de voir la même chose : l'ensemble des deux images. Il va donc falloir se débrouiller pour faire apparaitre les deux images au même endroit mais que l'une des deux images soit visible par un seul ?il et l'autre image par l'autre ?il. C'est là qu'interviennent les filtres. Nous allons nous en servir pour masquer l'une des deux images.

Par "convention", le filtre gauche des lunettes rouge-cyan est rouge. L'autre est cyan, bien entendu. Si on porte ces lunettes, l'?il gauche ne vois que la lumière rouge tandis que l'?il droite vois la lumière verte et la lumière bleue. L'astuce permettant de ne pas rendre visible l'image prévu pour l'?il droite dans l'?il gauche, c'est de supprimer la composante rouge de l'image prévu pour l'?il droite de telle sorte que l'?il gauche la voit noire (absence totale de lumière).


La composante rouge de l'image droite a été supprimée. Elle ne contient plus que du vert et du bleu.

Le masquage de la composante rouge de chacun des pixels de l'image a été rendu possible grâce à l'appel à la fonction suivante avant le dessin de la scène :
1
glColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_FALSE); // Paramtres : Rouge, Vert, Bleu, Alpha

On peut voir que la lumière émise par l'image passe parfaitement au travers du filtre cyan à droite et est complètement masquée par le filtre rouge à gauche.


Explication



L'image n'est constituée que de bleu et de vert. Elle ne contient pas de rouge. Le filtre rouge n'a donc pas de lumière à laisser passer, on vois noir, tandis que le filtre cyan, au contraire, n'a rien à masquer. En effet, le filtre cyan laisse passer la lumière verte et la lumière bleue. C'est pourquoi l'image verte/bleue passe parfaitement au travers du filtre cyan et pas du tout au travers du filtre rouge. L'image est donc destiné à être visible uniquement et parfaitement par l'?il droite.

On fait la même chose avec l'image de gauche en ne gardant que sa composante rouge.



Comme vous pouvez le voir, le filtre cyan ne filtre pas complètement la lumière rouge émise par mon écran. Ça ferra quand même l'affaire mais j'observerai des images fantômes. C'est assez désagréable. Pour palier à ce problème, il est possible d'accentuer le filtrage en superposant une deuxième paire de lunettes.


Nous savons maintenant créer nos deux images différentes avec les composantes correspondantes à l'?il.


Maintenant, la difficulté va consister à faire apparaitre les deux images différentes au même endroit pour obtenir un beau rendu en relief. Mettez vos lunettes et admirez.


Votre jeu en relief

Comment programmer tout ça ?



Pour mettre en place le relief, on va devoir refaire la méthode Personnage::regarder(). Voici comment je m'y suis pris.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void Personnage::regarder(void)
{
   #define RADIANS_PAR_DEGRES 0.0174532925199
   #define HAUTEUR_OEIL_PERSONNAGE 0.8
 
   #define VR 0.05
   static float RELIEF = VR;
   if (RELIEF > 0)
   {
       RELIEF = -VR;
       glColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_FALSE);
   }
   else
   {
       RELIEF = VR;
       glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_FALSE);
   }
 
 
   gluLookAt(
       // Position de l'oeil
       positionX + (cos (-this->angleHorizontal * RADIANS_PAR_DEGRES) * RELIEF),
       positionY + (sin(-this->angleHorizontal * RADIANS_PAR_DEGRES) * RELIEF),
       HAUTEUR_OEIL_PERSONNAGE,
 
       // Point vise
       this->positionX - cos(-angleHorizontal * RADIANS_PAR_DEGRES),
       this->positionY + sin(-this->angleHorizontal * RADIANS_PAR_DEGRES),
       HAUTEUR_OEIL_PERSONNAGE + tan(this->angleVertical * RADIANS_PAR_DEGRES),
 
       // La verticale est en Z
       0,0,1);
}

Comme vous pouvez le voir, j'alterne entre l'?il droit et l'?il gauche à l'aide d'une variable statique. Le calcul de la position de l'?il a dû être revue. Pour afficher l'image de chaque ?il simultanément, nous devons appeler deux fois la méthode Scene::dessiner() dans la boucle d'exécution de la scène.
1
2
        dessiner();
       dessiner();

La fonction Scene::dessiner() ne doit plus vider l'écran si nous voulons garder l'image du premier ?il (?il droite) avant de dessiner l'image du deuxième ?il (?il gauche). Par contre, on maintient le vidage du buffer de profondeur pour redessiner tout les objets une seconde fois.
1
2
    // Vidage de l'cran
   glClear(/*GL_COLOR_BUFFER_BIT |*/ GL_DEPTH_BUFFER_BIT);

La totalité de la zone visible est redessinée à chaque image. Finalement, seul le buffer de profondeur nécessite d'être vidé, même si vous ne faites pas de relief.


Je n'étendrais pas davantage les explications. En revanche, vous pouvez télécharger la totalité du projet (avec relief).

Voici une courte vidéo du résultat final :





Voilà que notre FPS commence à avoir de la gueule !

Nous allons nous préparer progressivement au développement de la partie réseau. Mais pour cela, nous aurons besoin d'un menu de connexion. Je vous propose de voir comment créer un menu dès le prochain chapitre.

Afin de partir sur une base commune du projet, je vous invite à télécharger la version actuelle du projet, sans le relief.



Chapitre précédent     Sommaire     Chapitre suivant



Rédigé par David
Consulté 15093 fois



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


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

Page générée en 0.52 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.