Description

Mise à jour à venir.

Back to ground zero.

Dans un jeu vidéo, en particulier ceux en 2D, les backgrounds ou arrière-plans ajoutent de la profondeur à l’environnement. L’effet est encore plus saisissant lorsqu’on utilise plusieurs backgrounds à la fois ou qu’on utilise un avant-plan.  Chaque plan peut se déplacer, être animé ou complètement statique. Ils campent le décor du jeu et de ce fait, ils sont quasi indispensables.

De puis que j’ai décidé d’utiliser OpenGL plutôt que les BufferedImage de java pour mes graphiques, j’ai eu un paquet de problèmes. En particulier en ce qui à trait au temps de chargement des images et à la taille permise des images à chargées. OpenGL étant limité à certaines valeurs. En plus, les dimensions des images doivent être un multiple de deux. Quoique cela, ça dépend des versions d’OpenGL. Mais lorsque l’on développe, il faut toujours penser que les usagers ne possèdent pas nécessairement les plus récentes technologies. Ces restrictions ont complètement démolie mon système de gestion des arrière-plans qui était basé sur les techniques de KGPIJ, jusqu’à maintenant.

Je ne pouvais tolérer un temps de chargement interminable. Donc, après quelques recherches sur le web, j’ai constaté qu’une bonne pratique concernant les arrière-plans consiste à découper l’image d’origine et de construire l’arrière-plan à partir de ces petites images. Un peu comme un casse-tête.  C’est la technique des tuiles ou briques.  J’ai donc élaboré une structure que j’ai nommé « Scene » pour représenter un avant ou un arrière-plan. La scène est d’abord décrite dans un fichier XML. Ce qui me permet de la modifier à volonté sans passer par le code source. De plus, je pourrais éventuellement utiliser les fichiers XML pour d’autres applications (web, documentation, etc.).  Les scènes sont gérées par la classe « SceneManager ». Cette  classe à la responsabilité de :
  1.  Charger les scènes 
  2. Modifier les scènes 
  3. Mettre à jour les scènes 
  4. Afficher les scènes

Le fait de déléguer ces tâches à un manager permet de contrôler plus facilement la gestion des scènes. Par exemple, selon la situation (la confrontation final à la fin d’un niveau – scène statique), on peut demander d’arrêter la mise à jour des scènes et la reprendre plus tard. Permettant ainsi à d’autres routines de faire mieux leur travail. Ou de booster le nombre d’éléments à l’écran (bullet hell). C’est un principe que j’utilise pour les autres groupes d’objets du jeu : le feu ennemi, les sprites ennemis, l’interface, la musique, etc.
Le chargement et la création des scènes est assez banal. Le manager lit le fichier XML et utilise les données convertit par le parseur XML pour créer les scènes. Ces données sont organisées en tableau de sorte que chaque cellule du tableau correspond à une scène à créer. La première donnée lu est le type de scène à créer. Les scènes sont construites par rapport à ce type, soit :
  1. TILED
  2.  SPRITED
  3. PLAIN
  4. GRADIENT_H
  5. GRADIENT_V

Seule la scène SPRITED reste à implémenter. À cette étape-ci dans le développement, je n’ai pas besoin de ce type de scène pour tester mes techniques. Mais éventuellement, c’est avec ces scènes que j’ajouterai de l’animation aux arrières et avant-plans.

Voici le résultat d’une scène TILED avec près de 10000 bullets à l’écran. Noter les données statistiques en haut de l’image.


D’abord on voit dans l’arrière plan le caractère répétitif du motif bleu turquoise. Cela démontre que la scène à été créée avec une petite image répétée. Ensuite, au niveau de la performance, même avec 9874 bullets à l’écran, j’obtiens un FPS de 49. Ce qui est très bien à mon avis. Remarque que le temps de chargement est encore assez long. Je devrai sûrement trouver une méthode plus efficace.
La mise à jour de la scène ce fait de la façon suivante :

SceneManager -->

  /**
     *  Mise à jour à intervalles de temps des scènes.
     */
    public void move(GamePadController.COMPASS cDirection){
        if (clMoveTimer > 0){
            if (ctLifetimeTimer.getTimer() > (clMoveStart + clMoveTimer)){
                // Mise à zéro du temps de mouvement.
                clMoveStart = ctLifetimeTimer.getTimer();

                // Bouge la sprite par la quantité du vecteur vélocité.
                if (cDirection == GamePadController.COMPASS.WEST) moveLeft();
                else if (cDirection == GamePadController.COMPASS.EAST) moveRight();
                else stayStill();
                update();
            }
        } else {    // Update at cpu clockspeed.
                if (cDirection == GamePadController.COMPASS.WEST) moveLeft();
                else if (cDirection == GamePadController.COMPASS.EAST) moveRight();
                else stayStill();
                update();
        } // Fin if.
    }

La direction du mouvement des scènes ce fait selon la direction lu sur le contrôleur de jeu. La mise à jour ce fait à chaque intervalle de temps clMoveTimer. Ainsi, on peut forcer que la mise à jour ce fasse 20 fois seconde, 60 fois seconde. C’est comme on veut. De façon général, je crois qu’il est moins pertinent de consacrer trop de ressources système aux arrière-plans. Il vaut mieux les mettre à jour 30 fois par seconde et mettre à jour les sprites 60 fois seconde. Ça devrait augmenter la qualité du jeu.

Voici ce qui se passe dans la classe « Scene » :

    /** Mise à jour des scènes. La mise à jour diffère selon le type de scènes. */
    public void update(){
        if (ciType == 1) updateGradientH();
        if (ciType == 5) updateTiledScene();
    }    // fin de la méthode.

    /** Dessine les scènes. Varie selon le type de scènes. */
    public void draw(){
        if (ciType == 1) displayGradientGL();
        if (ciType == 5) displayTiledGL();
    }    // fin de la méthode.

    /**
     * Incrémente la valeur de xSceneHead dépendamment des flags.
     * Elle peut varier entre -width et width(exclus), qui est la
     * largeur de l'image.<br />
     * Attention au transtypage, on peut perdre de la précision dans le
     * <code>xSceneHead</code>.
     *
     * Note: les méthodes sont public pour une mise à jour indépendante si souhaité.
     */
    public void updateTiledScene(){
        if (isMovingRight) fSceneHead = (fSceneHead + cvVelocity.getStepX()) % pixeledWidth;
        else if (isMovingLeft) fSceneHead =  (fSceneHead - cvVelocity.getStepX()) % pixeledWidth;
        xSceneHead = (int)fSceneHead;
    }    // fin de la méthode.

La mise à jour et le rendu diffère selon le type de scène. Dans updateTiledScene, la valeur xSceneHead est mise à jour selon la direction du mouvement de la scène. Cette technique et celle de l’affichage des tuiles est similaire à KGPIJ. Sauf qu’étant donné que je travail avec des floats, la mise à jour de xSceneHead était un problème. Comme cette valeur est un entier, la précision par rapport à un float limitait énormément les vitesses de déplacement que je pouvais utiliser. J’ai donc ajouté la variable globale fSceneHead qui est essentiellement la même chose que xSceneHead mais en floats. fSceneHead n’est jamais transtypé ce qui fait qu’elle ne perd pas sa précision. Sa valeur est copiée dans xSceneHead pour positionner correctement l’affichage des tuiles. Éventuellement, il faudra que je fasse une mise à jour prenant en compte les mouvements verticaux.

Dans le cas de la scène de type gradient :



La mise à jour ne contient aucunes actions. J’ai quand même expérimenté un peu et en faisant augmenter le rouge à chaque update, on obtient un bel effet de crépuscule à mesure que le temps passe. C’est une voie à explorer. L’affichage d’un gradient en OpenGL ce fait de cette façon :

    /**
     *  Affiche un arrière plan de type Gradient.
     */
    private void displayGradientGL(){
        GL11.glPushMatrix();

            // Si on utilise une texture blanche, la scène est beaucoup plus vive.
            // Sans textures, la scène est sombre. Comme-ci on peignait sur du noir.
            ctBasicRenderingTexture.bind();

            // translate to the right location and prepare to draw
            GL11.glTranslatef(0, 0, 0);

            GL11.glBegin(GL11.GL_QUADS);
                GL11.glColor3f(cfaColor1[0],cfaColor1[1],cfaColor1[2]);
                GL11.glVertex2f(0, 0);
                GL11.glVertex2f(playableWidth, 0);

                GL11.glColor3f(cfaColor2[0],cfaColor2[1],cfaColor2[2]);
                GL11.glVertex2f(playableWidth, playableHeight);// / 2);
                GL11.glVertex2f(0, playableHeight);// / 2);
            GL11.glEnd();
        GL11.glPopMatrix();
    }

Pour plus de renseignements sur les techniques d’OpenGL, voir le site de Neon Helium Productions

J’ai peint mon gradient sur une texture blanche de 32x32 dans un carré (GL_QUADS) de la dimension de la surface de jeu. Et l’effet est parfait. Si j’avais peint la scène sur une texture plus foncée, les couleurs auraient étés moins vives. De plus, comme elle est peinte sur une texture, rien ne m’empêche de jouer avec la transparence pour ajouter de l’effet. J’ai voulu faire ce type de scène pour mettre une couleur intéressante dans le fond et du fait même, faire ressortir les éléments ce trouvant à l’avant. Bon, je ne suis pas sûr de savoir comment je vais l’utiliser, mais si j’en ressens le besoin, je pourrai le faire. 

Je ne crois pas que ce type de scène doit « bouger », conséquemment les updates et les rendus peuvent ce faire moins souvent. 

Voilà où j’en suis. Le problème que j’ai en ce moment (un autre !) est que la création des scènes avant même qu’elles soient décrites dans le fichier XML, n’est pas une mince affaire. Je dois créer mes images dans Photoshop ou Gimp, les sectionner (Tiled) et construire la scène finale. Après cela, tout inscrire sur papier afin de transférer les informations dans le fichier XML adéquatement. Un jour, il me faudra un éditeur de niveau qui fera ces tâches pour moi. 

Commentaires