IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Viadeo Twitter Facebook Share on Google+   
Logo Documentation Qt ·  Page d'accueil  ·  Toutes les classes  ·  Toutes les fonctions  ·  Vues d'ensemble  · 

Tutoriel QML avancé, quatrième partie - finalisation

Fichiers :

Ajouter du dynamisme

Maintenant, on va ajouter deux choses pour égayer le jeu : animer les blocs et ajouter une sauvegarde des meilleurs scores.

Avant tout, on a nettoyé la structure des dossiers pour les fichiers du jeu : on a maintenant une multitude de fichiers. Tous les fichiers JavaScript et QML autres que samegame.qml ont été déplacés dans un nouveau sous-dossier content.

En prévision des animations de blocs, le fichier Block.qml est maintenant nommé BoomBlock.qml.

Animer le mouvement des blocs

Premièrement, on va animer les blocs pour qu'ils se déplacent de manière fluide. QML possède plusieurs méthodes pour ajouter un mouvement fluide et, dans ce cas, on va utiliser l'élément Behavior pour ajouter une SpringAnimation. Dans le fichier BoomBlock.qml, on applique une SpringAnimation sur les propriétés x et y afin que les blocs aient un sautillement dans la direction spécifiée (ces valeurs seront définies dans le fichier samegame.js). Voici le code ajouté dans le fichier BoomBlock.qml :

     property bool spawned: false
 
     Behavior on x {
         enabled: spawned;
         SpringAnimation{ spring: 2; damping: 0.2 }
     }
     Behavior on y {
         SpringAnimation{ spring: 2; damping: 0.2 }
     }

Les valeurs spring et damping permettent de modifier l'effet de sautillement de l'animation.

Le paramètre enabled: spawned se réfère à la valeur spawned, définie dans la fonction createBlock() du fichier samegame.js. Cela permet de s'assurer que la SpringAnimation sur la propriété x n'est activée que lors du positionnement des blocs dans la fonction createBlock(). Sinon, les blocs s'étaleraient en partant du coin (0,0) au démarrage du jeu, au lieu de tomber en ligne. (Commentez enabled: spawned et voyez la différence par vous-même).

Animation du changement d'opacité des blocs

Ensuite, on va ajouter une animation pour la destruction des blocs. Pour cela, on utilise un élément Behavior, permettant de spécifier une animation par défaut lors d'un changement de propriété. Dans ce cas, lorsque l'opacité opacity d'un bloc change, on anime cette valeur afin qu'elle apparaisse et disparaisse progressivement, au lieu d'avoir un changement brut entre visible et invisible. Ainsi, on applique le Behavior sur la propriété opacity de l'élément Image dans le fichier BoomBlock.qml :

     Image {
         id: img
 
         anchors.fill: parent
         source: {
             if (type == 0)
                 return "../../shared/pics/redStone.png";
             else if (type == 1)
                 return "../../shared/pics/blueStone.png";
             else
                 return "../../shared/pics/greenStone.png";
         }
         opacity: 0
 
         Behavior on opacity {
             NumberAnimation { properties:"opacity"; duration: 200 }
         }
     }

Notez le opacity: 0 signifie que le bloc est transparent lorsqu'il est créé. On pouvait définir l'opacité dans le fichier samegame.js lors de la création et destruction des blocs, mais on utilise des états, car on en aura aussi besoin pour la suite. Initialement, on ajoute les états suivants à l'élément racine de BoomBlock.qml :

 property bool dying: false
 states: [
     State{ name: "AliveState"; when: spawned == true && dying == false
         PropertyChanges { target: img; opacity: 1 }
     },
     State{ name: "DeathState"; when: dying == true
         PropertyChanges { target: img; opacity: 0 }
     }
 ]

Maintenant, les blocs vont apparaître doucement, car on a déjà défini spawned à true lors de l'implémentation de l'animation des blocs. Pour faire disparaître les blocs, on définit dying à true à la place de définir l'opacité à 0 lorsqu'un bloc est détruit dans la fonction floodFill().

Ajouter un effet de particules

Finalement, on ajoute un magnifique effet de particules aux blocs lorsqu'ils sont détruits. Pour cela, on commence par ajouter un élément Particles dans le fichier BoomBlock.qml, comme ceci :

     Particles {
         id: particles
 
         width: 1; height: 1
         anchors.centerIn: parent
 
         emissionRate: 0
         lifeSpan: 700; lifeSpanDeviation: 600
         angle: 0; angleDeviation: 360;
         velocity: 100; velocityDeviation: 30
         source: {
             if (type == 0)
                 return "../../shared/pics/redStar.png";
             else if (type == 1)
                 return "../../shared/pics/blueStar.png";
             else
                 return "../../shared/pics/greenStar.png";
         }
     }

Pour entièrement comprendre ce code, il est nécessaire de lire la documentation des particules, mais il est tout de même à noter que le taux d'émission emissionRate est défini à zéro afin que les particules ne soient normalement pas émises. Aussi, on étend l'état dying pour créer une explosion de particules en appelant la méthode burst()sur l'élément des particules particles. Le code des états est maintenant le suivant :

     states: [
         State {
             name: "AliveState"
             when: spawned == true && dying == false
             PropertyChanges { target: img; opacity: 1 }
         },
 
         State {
             name: "DeathState"
             when: dying == true
             StateChangeScript { script: particles.burst(50); }
             PropertyChanges { target: img; opacity: 0 }
             StateChangeScript { script: block.destroy(1000); }
         }
     ]

Maintenant, le jeu est superbement animé, avec de (peut-être pas) subtiles animations ajoutées pour toutes les actions du joueur. Le résultat final est présenté ci-dessous, avec un ensemble différent d'images pour montrer les bases d'un système de thèmes :

image

Ici, le changement de thème est simplement effectué en remplaçant toutes les images des blocs. Cela peut être fait à l'exécution en changeant la propriété source de Image ; donc, pour plus de challenge, on peut ajouter un bouton qui alterne les thèmes en utilisant différentes images.

Garder un tableau des meilleurs scores

Une autre fonctionnalité que l'on souhaite ajouter au jeu est une méthode pour garder et retrouver les meilleurs scores.

Pour ce faire, on affiche une boîte de dialogue lorsque le jeu est terminé afin de demander le nom du joueur pour l'ajouter au tableau des meilleurs scores. Cela requiert quelques changements au fichier Dialog.qml. En plus de l'élément Text, la boîte de dialogue possède maintenant un élément enfant TextInput afin de recevoir les évènements du clavier :

 Rectangle {
     id: container
     ...
     TextInput {
         id: textInput
         anchors { verticalCenter: parent.verticalCenter; left: dialogText.right }
         width: 80
         text: ""
 
         onAccepted: container.hide()
     }
     ...
 }

On ajoute aussi une fonction showWithInput(). L'interface pour insérer du texte ne sera visible que si cette fonction est appelée au lieu de show(). Lorsque la boîte de dialogue est fermée, elle envoie un signal closed() pour que les autres éléments puissent récupérer le texte entré par l'utilisateur grâce à la propriété inputText :

 Rectangle {
     id: container
     property string inputText: textInput.text
     signal closed
 
     function show(text) {
         dialogText.text = text;
         container.opacity = 1;
         textInput.opacity = 0;
     }
 
     function showWithInput(text) {
         show(text);
         textInput.opacity = 1;
         textInput.focus = true;
         textInput.text = ""
     }
 
     function hide() {
         textInput.focus = false;
         container.opacity = 0;
         container.closed();
     }
     ...
 }

La boîte de dialogue est maintenant utilisable dans samegame.qml :

     Dialog {
         id: nameInputDialog
         anchors.centerIn: parent
         z: 100
 
         onClosed: {
             if (nameInputDialog.inputText != "")
                 SameGame.saveHighScore(nameInputDialog.inputText);
         }
     }

Lorsque la boîte de dialogue émet le signal closed, on appelle la nouvelle fonction saveHighScore() du fichier samegame.js, qui sauvegarde le score dans une base de données SQL locale et qui l'envoie, si possible, en ligne.

La nameInputDialog est activée dans la fonction victoryCheck() de samegame.js :

 function victoryCheck() {
     ...
     // Vérifie si la partie est terminée
     if (deservesBonus || !(floodMoveCheck(0, maxRow - 1, -1))) {
         gameDuration = new Date() - gameDuration;
         nameInputDialog.showWithInput("You won! Please enter your name: ");
     }
 }

Sauvegarder les scores hors ligne

On doit maintenant implémenter la fonctionnalité de sauvegarde du tableau des meilleurs scores.

Voici la fonction saveHighScore() du fichier samegame.js :

 function saveHighScore(name) {
     if (scoresURL != "")
         sendHighScore(name);
 
     var db = openDatabaseSync("SameGameScores", "1.0", "Local SameGame High Scores", 100);
     var dataStr = "INSERT INTO Scores VALUES(?, ?, ?, ?)";
     var data = [name, gameCanvas.score, maxColumn + "x" + maxRow, Math.floor(gameDuration / 1000)];
     db.transaction(function(tx) {
         tx.executeSql('CREATE TABLE IF NOT EXISTS Scores(name TEXT, score NUMBER, gridSize TEXT, time NUMBER)');
         tx.executeSql(dataStr, data);
 
         var rs = tx.executeSql('SELECT * FROM Scores WHERE gridSize = "12x17" ORDER BY score desc LIMIT 10');
         var r = "\nHIGH SCORES for a standard sized grid\n\n"
         for (var i = 0; i < rs.rows.length; i++) {
             r += (i + 1) + ". " + rs.rows.item(i).name + ' got ' + rs.rows.item(i).score + ' points in ' + rs.rows.item(i).time + ' seconds.\n';
         }
         dialog.show(r);
     });
 }

Tout d'abord, on appelle la fonction sendHighScore() (expliquée dans la section ci-dessous) pour voir s'il est possible d'envoyer les meilleurs scores dans une base de données en ligne.

Ensuite, on utilise la bibliothèque de sauvegarde hors ligne pour maintenir une base de données SQL unique et persistante pour cette application. On crée une base de données hors ligne pour les meilleurs scores en utilisant la fonction openDatabase() et prépare les données et la requête SQL que l'on veut utiliser pour sauvegarder les scores. La bibliothèque de sauvegarde hors ligne utilise les requêtes SQL pour la manipulation et le retrait des données. Dans l'appel à db.transaction(), on utilise trois requêtes SQL pour initialiser la base de données (si nécessaire) et ajouter les scores. Afin d'utiliser les données renvoyées, on convertit le résultat en une chaîne de caractères ayant une ligne par ligne de résultat de la base de données et on affiche cette chaîne dans une boîte de dialogue.

C'est l'une des méthodes pour sauvegarder et afficher les meilleurs scores localement mais certainement pas la seule. Une autre solution plus complexe serait de créer une boîte de dialogue pour les scores et de lui passer le résultat afin de le traiter et de l'afficher (au lieu de réutiliser Dialog). Cela rendrait cette boîte de dialogue plus personnalisable et présenterait mieux les scores. Si le QML est l'interface utilisateur d'une application C++, on peut aussi passer les scores à une fonction C++ pour les sauvegarder localement de différentes façons, dont la sauvegarde dans un format simple sans SQL ou dans une autre base de données SQL.

Sauvegarder les scores en ligne

On a vu comment sauvegarder les scores localement mais il est aussi simple d'intégrer une sauvegarde des scores sur Internet dans une application QML. L'implémentation pour ce jeu est très simple : les données des scores sont envoyées à un script PHP sur un serveur et ce serveur les sauvegarde et les affiche aux visiteurs. On peut aussi récupérer un fichier XML ou QML à partir de ce serveur, qui contient les scores, mais cela dépasse le contexte de ce tutoriel. Le script PHP utilisé ici est disponible dans le dossier examples.

Si le joueur a entré son nom, on peut envoyer les données au service Web en utilisant le code du fichier samegame.js :

 function sendHighScore(name) {
     var postman = new XMLHttpRequest()
         var postData = "name=" + name + "&score=" + gameCanvas.score + "&gridSize=" + maxColumn + "x" + maxRow + "&time=" + Math.floor(gameDuration / 1000);
     postman.open("POST", scoresURL, true);
     postman.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
     postman.onreadystatechange = function() {
         if (postman.readyState == postman.DONE) {
             dialog.show("Your score has been uploaded.");
         }
     }
     postman.send(postData);
 }

Le XMLHttpRequest de ce code est le même que le XMLHttpRequest() que l'on peut trouver dans un navigateur standard JavaScript et on peut l'utiliser de la même façon pour récupérer un fichier XML ou QML à partir d'un service Web pour afficher les scores. On ne s'inquiète pas pour la réponse dans ce cas, on poste juste les scores au serveur. S'il avait retourné un fichier QML (ou une URL pointant sur un fichier QML), on aurait pu l'instancier de la même façon qu'avec les blocs.

Une méthode alternative pour accéder et soumettre des données sur Internet aurait été d'utiliser un élément QML conçu dans ce but. XmlListModel rend très facile la récupération et l'affichage de données basées sur XML tels que RSS dans une application QML (voir la démo Flickr, par exemple).

C'est tout !

En suivant ce tutoriel, vous avez vu comment écrire une application complète et fonctionnelle en QML à travers les points suivants :

Il y a tant de choses à apprendre sur QML que l'on n'a pas explorée dans ce tutoriel. Farfouillez dans les démos et exemples et la documentation pour voir toutes les choses que vous pouvez faire avec le QML !

[ Précédent : tutoriel QML avancé, troisième partie - implémenter la logique du jeu ]

Remerciements

Merci à Alexandre Laurent pour la traduction ainsi qu'à Thibaut Cuvelier, Jonathan Courtois et Claude Leloup pour leur relecture !

Cette page est une traduction d'une page de la documentation de Qt, écrite par Nokia Corporation and/or its subsidiary(-ies). Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia. Qt 4.7
Copyright © 2021 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon, vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts. Cette page est déposée à la SACD.
Vous avez déniché une erreur, une redirection cassée ou tout autre problème, quel qu'il soit ? Ou bien vous désirez participer à ce projet de traduction ? N'hésitez pas à nous contacter ou par MP !