Créer son propre système de quêtes [partie 1/2]
{Tutoriel de script RPG Maker Ace}
Le tutoriel sur le Shifumi nous a appris les bases de la création de scènes et de fenêtres, dans cette publication, nous irons plus loin en créant un système de quêtes. Après avoir vu comment créer des scripts indépendants, nous allons nous concentrer sur l'intégration d'un système complexe dans un projet, sans pour autant créer des incompatibilités à gogo ! Voyez cet article comme une suite à celui sur le Shifumi et je partirai du principe que vous avez déjà lu ce dernier !
- Raho, Nuki, 03/10/2013 à 11h26.


Créer son propre système de quêtes [partie 1/2] - Raho, Nuki

Créer son propre système de quêtes [partie 1/2]


Sachez, chers lecteurs, que mon humour est exceptionnel... et j'espère qu'au-delà de l'apprentissage de la création d'un système sous RPG Maker VX Ace, vous allez apprendre à être aussi drôle que moi Smiley


icone Introduction


Dans l'excellent tutoriel sur le Shifumi vous avez été confrontés à la création de votre première scène et de vos premières fenêtres (je pars du principe que vous avez déjà lu cet article pour ne pas faire de la redondance d'information sur ce superbe site !).
C'est un bon début, mais cet article en apprend bien peu sur la création d'un véritable système. C'est pour ça que j'ai décidé (moi... Raho... a priori inconnu de vos personnes) de rédiger cet article qui vous apprendra à réaliser un véritable système qui s'ancrera correctement dans votre projet. Pour ce faire, j'ai choisi de raconter comment réaliser son propre système de quêtes ! Pourquoi un système de quêtes ? Parce que, au long de mes pérégrinations sur la toile, j'ai remarqué qu'il s'agissait d'un script que beaucoup de gens demandaient et que généralement, c'est lors du paramétrage du journal des quêtes que le bât blesse. Plutôt que de me lancer dans l'écriture d'un script de la mort qui tue qui s'adapte aux envies du maker, j'ai préféré écrire un article pour apprendre aux makers de la mort qui tuent (en l’occurrence, vous) comment réaliser leur propre système ! Attention... c'est parti !

icone Architecture du RGSS


Avant de démarrer l'implémentation de notre système, nous allons d'abord voir comment fonctionne le RGSS, sa structure générale, dans les grandes lignes. Il s'agit d'une partie un peu théorique mais qui est tout de même nécessaire pour bien comprendre ce que l'on fait. Et c'est en respectant cette structure que l'on va pouvoir faire des scripts super compatibles avec le reste du monde.


Pourquoi respecter l'architecture du RGSS ?


Dans l'absolu, pourquoi s'ennuyer à respecter une architecture ? La question semble légitime et je vais tâcher d'y répondre, pour ne pas provoquer le courroux de ce psychorigide de s4suk3... bien sûr que non Smiley, c'est pour plusieurs raisons :

• Le code sera plus lisible (généralement, dans sa structure), donc plus facile à maintenir.
• On maximise la compatibilité avec les autres scripts.
• En appliquant cette structure, le RGSS deviendra limpide !

Le troisième argument est un peu spéculatif, mais les deux premiers sont relativement importants. En effet, respecter cette structure est avant tout une manière de rendre son compte, structurellement, lisible et donc relativement bien ancré dans le RGSS. Car implémenter un script dans RPG Maker, c'est intégrer une application dans un écosystème déjà codé. Que ce soit sur RPG Maker ou plus tard, si ce tutoriel vous découvre une vocation de programmeur et que ça en devient votre métier, vous serez amené à respecter des structures de code. Alors autant commencer tout de suite !

Un système représenté en trois couches


Même si ce type de représentation ne s'applique pas à 100% des scripts, généralement un système est représenté par trois couches :

1.) Le modèle.
2.) Le contrôleur.
3.) La vue.

Comme se contenter d'une nomination de ces trois couches n'est peut-être pas vraiment explicite, nous allons les décrire une par une et donner un exemple concret avec le RGSS.

icone
Le modèle :
Le modèle est la couche de représentation statique. En gros, dans le cadre de RPG Maker, il s'agit des données telles que représentées dans la base de données. En effet, chaque champ de la base de données correspond à une instance de RPG::Quelquechose. Il s'agit d'une représentation statique car ses valeurs ne changeront pas au cours du jeu.
Pour voir un exemple de classe dite statique, rendez-vous dans l'aide de RPG Maker (F1 dans l'éditeur), allez dans l'index et choisissez n'importe quelle classe commençant par RPG::, par exemple RPG::Actor qui correspond à la représentation d'un acteur (héros) dans la base de données. Dans cette représentation, pas d'informations sur l'expérience car l'expérience est destinée à changer au fil du jeu. Ici, comme nous sommes dans la couche de représentation statique, on se contente de décrire un héros de manière brute.


icone
Le contrôleur :
Le contrôleur est la couche de représentation dynamique, elle va représenter dans le jeu une donnée statique. Pour reprendre l'exemple des héros, leur représentation statique est décrite dans la base de données au moyen de RPG::Actor, et leur couche de représentation dynamique est décrite par la classe Game_Actor (dans l'éditeur de script). Son rôle est de construire et de représenter un acteur, au fil de la partie. Ses valeurs initiales seront décrites au moyen de la couche de représentation statique mais contrairement à cette dernière, ses instances évolueront.


icone
La vue :
Maintenant que nous avons une représentation statique et une représentation dynamique, il nous faut une couche de représentation graphique. C'est le rôle de la vue. Elle aura pour rôle de représenter ces données dynamiques. En référence à nos héros, il existe plusieurs vues, il s'agit des scènes qui afficheront des données relatives à nos héros.


Voici, généralement, la représentation des systèmes complexes. Elle respecte plus ou moins ce schéma :



Les experts de la programmation (mais qui ne connaissent pas spécialement le RGSS) sont sûrement familiers avec ce genre d'architecture dite MVC (modèle-vue-contrôleur), et s'outrageront peut-être de la forme de mon schéma, cependant, je ne l'ai volontairement pas complexifié (en lui indiquant la notion d'injection de vue, ce qui ne serait pas très pertinent dans cet article). Retenons donc simplement que la vue, l'affichage, affiche des données dynamiques qui sont décrites sur bases du schéma établi dans la couche de représentation statique, à savoir la base de données.

Maintenant que nous avons une représentation imagée de ces trois couches, nous pouvons amener un autre intérêt à ce type d'architecture : il est facile de modifier la représentation de notre système sans changer "tout le script". Nous avons donc une indépendance sensible entre nos trois couches.

Smiley Est-ce que TOUS les scripts respectent cette architecture à trois couches?


Très bonne question. Non.
... Smiley
Cette architecture n'est à priori utile que lorsque l'on doit avoir une base de données. Si je voulais écrire un script qui calcule des ombres mouvantes, déployer le modèle à trois couches, ce serait un peu stupide.


Nous avons maintenant compris (enfin je l'espère Smiley) un exemple d'architecture particulièrement redondant dans le RGSS, nous allons maintenant le mettre en pratique en réalisant un système de quêtes. Go Go !

icone Réflexion sur le système


Avant de commencer, il est important de raisonner ce que fera notre système. Pourquoi? Et bien simplement parce que cela nous évitera de réécrire du code. Nous allons optimiser notre travail ! Alors nous allons essayer de voir ce que sera une quête. S4suk3 vous a parlé de la programmation orienté objet, de ce devoir de caractériser ce que l'on va manipuler. Nous allons donc caractériser une quête. Je vous propose cette structure :


A.) Id
B.) Name, Description
C.) Exp, Gold
D.) Items, Weapons, Armors


Je vais vous expliquer ce choix tout de suite, étape par étape.

icone
Id :
L'Id permettra de représenter la quête au moyen d'un numéro, c'est tout de même plus confortable que devoir gérer des noms. L'Id sera donc l'identifiant de la quête (merci Cap'tain Obvious).


icone
Name, Description :
Il s'agira du nom de la quête et de sa description. Par exemple "Sauver une souris", "Sauver la souris de la mère Michèle, prise en joug par le chat du père Lustucru (ahha WTF comment je réécris l'histoire).


icone
Exp, Gold :
Ces deux champs correspondront à l'expérience et à l'argent que rapportera une quête réussie (et donc achevée).


icone
Items, Weapons, Armors :
Ces trois champs correspondront à des listes d'entiers, qui seront les IDs des objets, armes, armures donnés en cas de réussite d'une quête. Pour faire gagner plusieurs fois le même objet, il suffira de répéter son ID dans la liste.


Maintenant que nous avons "le design" d'une quête dans sa représentation statique, nous allons pouvoir nous y mettre sérieusement, alors c'est parti pour la représentation de données statiques !

icone Mise en place de la couche statique


C'est ici que les romains s'empoignèrent. Dans un système du RGSS, on peut profiter de la base de données pour représenter nos quêtes. Malheureusement, ici, on ne peut pas étendre cette base de données (dans l'absolu c'est envisageable mais bon...). Alors comment allons-nous représenter nos quêtes ?
Une solution serait d'écrire un logiciel qui créerait notre base de données de quêtes mais ce tutoriel deviendrait alors beaucoup trop grand (et vous ne connaissez pas encore ma flemme légendaire... mais moi je la connais...).
Nous allons donc utiliser un script qui nous permettra de représenter des données statiquement.

L'Extend Database


Non, cette fois je ne vais pas faire l'apologie de l'Event Extender parce que ce script ne conviendrait pas... ah... si en fait, mais ce n'est pas grave, je vais présenter un autre script très cool, dont le nom n'est grammaticalement pas correct ... lol, le créateur de ce script, quel noob.


L'Extend Database est un petit script très mignon qui permet de créer sa propre base de données statique très facilement.
Pour l'installer, rendons-nous sur la page du script, ici, récupérons le script et installons-le dans notre éditeur de script. En-dessous, créons un emplacement Mapping dans lequel nous écrirons notre mapping de base de données:



C'est dans "Mapping" que nous écrirons notre structure de quête et que nous insérerons nos quêtes. Pour la structure il suffit de nous référer à ce que nous avions établi comme schéma dans la rubrique Réflexion sur le Système.

Définition statique des quêtes


Une fois que vous aurez relu la page sur l'Extend Database, la définition d'une table pour les quêtes devra vous sembler enfantin. Il suffit de faire :

Code (Ruby):
  1. # Définition de la structure statique des quêtes
  2. class Quests < Database::Table
  3. integer :id
  4. string :name
  5. string :description
  6. integer :exp
  7. integer :gold
  8. polymorphic :items
  9. polymorphic :weapons
  10. polymorphic :armors
  11. end


Les trois derniers champs sont polymorphiques car il s'agira de listes d'entiers (et que ce système préfère fourrer tous les champs dont le type n'est pas connu dans un champ typé "polymorphic"... lol). Concrètement, polymorphic sert à stocker toutes les données dont le type n'est pas connu. Comme, malheureusement, les listes d'entiers ne sont pas proposées dans le système de type de l'Extend Database, nous utilisons un type générique (qui ne subira aucune conversion à l'insertion).
Maintenant que nous avons une structure statique, insérer des quêtes devient un jeu d'enfant. Je vous propose ces quêtes pour essayer notre script :

Code (Ruby):
  1. Quests.insert(0, "Notre première Quête", "Sauver le monde", 10, 11, [],[],[])
  2. Quests.insert(1, "Notre seconde Quête", "Sauver le monde", 20, 12, [1],[2],[3])
  3. Quests.insert(2, "Notre troisième Quête", "Sauver le monde", 30, 13, [1,2],[1],[4,5,6])
  4. Quests.insert(3, "Notre quatrième Quête", "Sauver le monde", 40, 14, [],[],[])


Si vous n'êtes pas à l'aise avec les 3 derniers champs, voici un peu d'aide qui devrait clarifier la situation :


• La première quête n'offre rien comme objets/armes/armures.
• La seconde offre l'objet 1, l'arme 2, l'armure 3.
• La troisième offre l'objet 1 et 2, l'arme 1 et les armures 4, 5 et 6
• La quatrième n'offre rien comme objets/armes/armures.


Au moyen de ce script nous avons très vite écrit la structure statique de nos quêtes et l'insertion d'une quête est assez facile.
L'insertion des quêtes peut se faire n'importe où dans l'éditeur de script, après la définition de la table. Cependant, j'ai pris l'habitude de créer un emplacement réservé dans mon éditeur de script pour localiser toutes mes insertions.

Conclusion sur la couche statique


Nous en avons DÉJÀ fini avec la couche statique. Il aurait été possible de le faire de plein d'autres manières mais je trouvais ça cool de faire la promotion du script de mon pote Nuki qui, il faut le reconnaître, facilite grandement l'écriture de cette partie.
Nous pouvons passer à la suite, la couche dynamique !

icone Mise en place de la couche dynamique


Cette partie sera, hélas, un peu plus longue que la précédente et abordera plus de concepts. En effet, c'est ici que nous verrons concrètement comment intégrer correctement un système dans le RGSS. N'hésitez donc pas à relire cette partie plusieurs fois pour être sûr de bien la comprendre. N'hésitez pas non plus à poser des questions dans ce superbe module de commentaires. Que ce soit moi ou un autre, nous nous ferons un plaisir de répondre !


Nous avons brillamment programmé (au moyen de la base de données étendue de Nuki, c'est vrai, mais ça reste une réussite !) notre couche statique, il va falloir s'attaquer à la couche dynamique. Je vous propose de créer un nouvel emplacement script, à la suite des deux précédents, que nous nommerons Quests Dynamic (ahahha ce nom !) et dans lequel nous coderons la logique dynamique de notre système :



Nous pouvons d'ores et déjà y écrire ce code Ruby :

Code (Ruby):
  1. # Description dynamique d'une quête
  2. class Game_Quest
  3.  
  4. end


Et oui, nous avons créé une classe. Nous allons, avant de continuer, revoir certains points théoriques.

Smiley Qu'est ce qu'une classe


Le Ruby est un langage orienté objet. Dans le tutoriel sur le Shifumi, S4suk3 décrit la programmation orientée objet comme ceci :

Derrière cette appellation compliquée : "Orienté Objet", se cache un concept relativement facile à décrire. En effet, l'orienté objet consiste à dire que tout est descriptible. Donc que tous les éléments que je vais être amener à utiliser sont basés sur un "modèle" et le jeu du programmeur est de construire ces modèles et de les remplir. Le moule, le modèle, que l'on utilise pour décrire des éléments est appelé une classe. Dans la programmation orientée objet (plus spécifiquement orientée classe, mais ne nous arrêtons pas sur ce détail), on est amené à utiliser des classes déjà existantes et à créer nos propres classes, pour décrire des éléments.
Dans le jeu de la création de classe, il sera possible de créer des liens entre des classes : on appelle ces liens l'héritage. On va donc écrire une classe qui part avec toutes les caractéristiques d'une autre. C'est assez cool parce que ça évite de devoir réécrire inutilement du code.


Concrètement, une classe est une sorte de moule qui contient toutes les informations nécessaires pour créer des données qui répondent à la description que la classe impose. Dans notre exemple, nous allons écrire la classe Game_Quest qui, en fonction d'un Identifiant, créera une structure dynamique sur base de sa représentation statique. Nous lui ajouterons des méthodes, que l'on pourrait traduire en "actions" pour finir une quête.

Smiley Qu'est ce qu'un constructeur


Il s'agit d'une méthode particulière d'une classe, c'est la méthode qui sera appelée lorsque l'on instanciera un objet. Par exemple :

Code (Ruby):
  1. une_quete = Game_Quest.new(5)


Dans cet exemple, je place dans la variable "une_quete" un objet Game_Quest qui sera construit en fonction de la donnée statique 5. Le .new est le constructeur, c'est lui qui, en fonction des arguments, va construire une instance. Dans notre classe Game_Quest nous allons ajouter le constructeur qui s'appelle toujours initialize :

Code (Ruby):
  1. # Description dynamique d'une quête
  2. class Game_Quest
  3. # Constructeur
  4. def initialize(id)
  5. quest = Database.Quests[id]
  6. @id = id
  7. @name = quest.name
  8. @description = quest.description
  9. @exp = quest.exp
  10. @gold = quest.gold
  11. @items = quest.items
  12. @weapons = quest.weapons
  13. @armors = quest.armors
  14. end
  15. end


Ici, notre constructeur prend un argument, un ID, il correspond au 5 dans notre exemple précédent. Sur base de cet ID, il va charger la donnée statique de la quête référencée par son ID, c'est la ligne quest = Database.Quests[id]. Ensuite nous allons attribuer les données de la quête à des attributs (pour rappel, un attribut dans une classe est une variable dont le nom commence par "@" et est donc accessible de partout dans les méthodes de la classe), les valeurs de la quête.
Cependant, actuellement, notre classe Game_Quest est identique à sa représentation statique. Il lui manque un attribut, celui qui définit si la quête est finie ou pas. Par défaut, quand une quête est démarrée (donc quand elle est construite), elle n'est pas finie. On peut donc modifier notre constructeur de la sorte :

Code (Ruby):
  1. # Description dynamique d'une quête
  2. class Game_Quest
  3. # Constructeur
  4. def initialize(id)
  5. quest = Database.Quests[id]
  6. @id = id
  7. @name = quest.name
  8. @description = quest.description
  9. @exp = quest.exp
  10. @gold = quest.gold
  11. @items = quest.items
  12. @weapons = quest.weapons
  13. @armors = quest.armors
  14. @finished = false
  15. end
  16. end


Maintenant que nous avons développé (de manière complète) le constructeur de notre classe, nous allons pouvoir implémenter une méthode pour finir une quête. Le constructeur était le déclencheur d'une quête.

Implémenter la méthode pour terminer la quête


Maintenant que nous sommes capables de démarrer une quête (nous nous occuperons de l'intégrer dans "le jeu" dans une rubrique suivante) au moyen du constructeur. Typiquement, une quête est démarrée quand son @finished vaut false. Nous allons écrire une méthode qui permet de terminer une quête.
Car pour rappel, il s'agit ici de l'implémentation de la couche dynamique. Donc lorsque nous créerons un objet Game_Quest, c'est que nous lancerons une quête et donc, une fois lancée, elle ne sera pas finie (logique). Si une quête n'est pas lancée, alors elle n'est pas instanciée.
La première étape est de donner à l'attribut @finished la valeur true. Ensuite, il faudra distribuer l'expérience, l'or et donner les objets :

Code (Ruby):
  1. # Description dynamique d'une quête
  2. class Game_Quest
  3. # Constructeur
  4. def initialize(id)
  5. quest = Database.Quests[id]
  6. @id = id
  7. @name = quest.name
  8. @description = quest.description
  9. @exp = quest.exp
  10. @gold = quest.gold
  11. @items = quest.items
  12. @weapons = quest.weapons
  13. @armors = quest.armors
  14. @finished = false
  15. end
  16. # Termine une quête
  17. def finish
  18. @finished = true
  19. # Donne l'or à l'équipe
  20. $game_party.gain_gold(@gold)
  21. # Attribue l'expérience à chaque membre de l'équipe
  22. $game_party.members.each do |actor|
  23. actor.gain_exp(@exp)
  24. end
  25. # Donne les objets
  26. @items.each do |id|
  27. $game_party.gain_item($data_items[id], 1)
  28. end
  29. # Donne les armes
  30. @weapons.each do |id|
  31. $game_party.gain_item($data_weapons[id], 1)
  32. end
  33. # Donne les armures
  34. @armors.each do |id|
  35. $game_party.gain_item($data_armors[id], 1)
  36. end
  37. end
  38. end


Voyons ensemble ce que fait cette méthode. Premièrement, elle change l'attribut @finished pour qu'il soit égal à true, donc que la quête soit finie. Ensuite, nous donnons les récompenses. Pour donner l'or, il existe dans Game_Party une méthode qui permet de donner de l'or, nous l'utilisons !
Il existe une autre méthode qui permet de récupérer un tableau avec tous les membres de l'équipe, il s'agit de members.

Code (Ruby):
  1. # Attribuer l'expérience à chaque membre de l'équipe
  2. $game_party.members.each do |actor|
  3. actor.gain_exp(@exp)
  4. end

Cette partie est un petit peu complexe. Je vais vous l'expliquer dans les grandes lignes. Le rôle de la méthode each est de parcourir tous les éléments d'une structure complexe énumérable, dans cet exemple, il s'agit d'un tableau. Et de lui appliquer une fonction. Cette procédure, l'énumération de tous les éléments, s'appelle l'itération. Nous déclarons une variable entre pipes ( | ), et pour chaque élément de la structure de données cette variable prendra successivement sa valeur. Donc ici, nous appelons la méthode give_exp sur chaque élément du tableau $game_party.members qui est rempli d'instances de Game_Actor et nous leur donnons leur expérience en récompense ! Facile !

L'argument de each est un petit peu particulier, il s'agit d'un block, soit une fonction anonyme, mais ne nous arrêtons pas sur ce genre de détail Smiley


Le même procédé est utilisé pour donner à l'équipe les objets (objets/armes/armures). Cette fois on traverse les listes définies dans notre couche statique. Il serait possible d'optimiser cette fonction pour ne pas donner deux fois "une fois" les objets répétés dans la liste mais c'est du détail.

Smiley Comment trouver les méthodes qui nous sauvent ?


Dans la section précédente, nous avons utilisé plein de petites méthodes qui nous résolvent des problèmes. Pour donner l'or, pour donner l'expérience, pour donner les armes, comment découvre-t-on ces méthodes ?
Malheureusement, il n'y a pas de manière magique. La première chose est de bien comprendre le RGSS et de le lire. C'est en pratiquant que l'on devient pratiqueur Smiley.
Donc quand vous aurez fini la lecture de ce très didactique tutoriel, je vous invite à lire le RGSS en l'appréhendant avec ce que vous aurez appris (sans oublier de raisonner en fonction du système à trois couches) et vous trouverez quels sont les outils que vous pouvez déjà utiliser pour concevoir vos propres scripts ! Dingue non Smiley.

L'encapsulation


Quand je bois du coca, je décapsule ma bouteille !

En programmation orientée objet, l'encapsulation est une idée, qui consiste à protéger les attributs d'un objet (les variables qui commencent par @). Donc rendre ses attributs inaccessibles de l'extérieur. Donc pour pouvoir lire ou modifier ces attributs, il faudra créer des méthodes qui se chargeront de renvoyer la valeur d'un attribut ou d'en modifier sa valeur.

Smiley Mais quel est l'intérêt !? Pourquoi devrais-je m'ennuyer à écrire chacune des méthodes une par une ?


L'utilisation d'une méthode permet de vérifier les modifications d'attributs, vérifier qu'elles sont cohérentes. On représente souvent les objets comme des boîtes noires ayant leurs propres modes de fonctionnement et dont le développeur ne connaît que les actions spécifiques. En utilisant des méthodes de lecture et de modification, on renforce cette notion de boîte noire en séparant la spécification du comportement d'un objet et son paramétrage pratique.

Dans le futur, en développant notre système, nous aurons besoin d'afficher les attributs d'une quête (dans le journal des quêtes par exemple), donc pour ça, il faudra que nous puissions y accéder. Une solution triviale serait d'utiliser la définition statique... mais... ce ne serait pas très convivial. Une autre solution serait d'écrire toutes les méthodes qui se chargeront de renvoyer l'attribut. Un exemple pour name :

Code (Ruby):
  1. def name
  2. return @name
  3. end


L'instruction return a pour rôle d'interrompre une fonction et de renvoyer ce qu'on lui a passé en argument. Donc la création de cette méthode permettrait de récupérer le nom d'une quête. Cependant, ces méthodes peuvent être générées. Au moyen de la méthode attr_reader. Par exemple :

Code (Ruby):
  1. # Description dynamique d'une quête
  2. class Game_Quest
  3.  
  4. # Accesseurs
  5. attr_reader :id
  6. attr_reader :name
  7. attr_reader :description
  8. attr_reader :exp
  9. attr_reader :gold
  10. attr_reader :items
  11. attr_reader :weapons
  12. attr_reader :armors
  13.  
  14. # Constructeur
  15. def initialize(id)
  16. quest = Database.Quests[id]
  17. @id = id
  18. @name = quest.name
  19. @description = quest.description
  20. @exp = quest.exp
  21. @gold = quest.gold
  22. @items = quest.items
  23. @weapons = quest.weapons
  24. @armors = quest.armors
  25. @finished = false
  26. end
  27. # Renvoie l'état d'une quête
  28. def finished?
  29. return @finished
  30. end
  31. # Termine une quête
  32. def finish
  33. @finished = true
  34. # Donne l'or à l'équipe
  35. $game_party.gain_gold(@gold)
  36. # Attribue l'expérience à chaque membre de l'équipe
  37. $game_party.members.each do |actor|
  38. actor.gain_exp(@exp)
  39. end
  40. # Donne les objets
  41. @items.each do |id|
  42. $game_party.gain_item($data_items[id], 1)
  43. end
  44. # Donne les armes
  45. @weapons.each do |id|
  46. $game_party.gain_item($data_weapons[id], 1)
  47. end
  48. # Donne les armures
  49. @armors.each do |id|
  50. $game_party.gain_item($data_armors[id], 1)
  51. end
  52. end
  53. end


Ici, j'ai généré les méthodes d'accès à mes attributs, vous pouvez vous rendre compte que je n'ai pas généré celle de son état (@finished) parce que par convention, les méthodes qui renvoient vrai ou faux (un booléen), en ruby, se finissent par un ?, c'est pour cette raison que j'ai écrit manuellement la fonction finished?.

En gros, en faisant ça, il est possible d'accéder aux attributs de l'extérieur, par exemple :

Code (Ruby):
  1. quest = Game_Quest.new(1)
  2. print quest.name # affichera notre seconde quête
  3. print quest.finished? # affichera false
  4. quest.finish
  5. print quest.finished? # affichera true

attr_reader va générer les méthodes qui renvoient les attributs et il ne faudra donc pas les écrire manuellement. Il existe deux autres méthodes de ce style : attr_writer, qui permet de générer les méthodes de modification des attributs, et attr_accessor, qui combine les deux et qui génère les méthodes de lecture et de modification. Dans notre exemple, nous n'avons besoin que de la lecture des attributs, donc nous ne nous étendrons pas sur ces deux autres méthodes.


C'en est fini de ce survol trèèès sommaire de l'encapsulation, nous allons pouvoir maintenant spécifier quelque chose.

Trouver une quête via son ID


Cette rubrique doit vous paraître un peu étrange étant donné que nous avions déjà trouvé une quête au moyen de "Database.Quests[id]", cependant, cette manière que j'ai utilisée pour introduire le cours n'est pas bonne !

Pourquoi donc Smiley


En effet, lors de nos essais, cela fonctionnait parfaitement, alors pourquoi vous demanderais-je de changer ?
Eh bien c'est très simple, cette manière de récupérer une quête utilise l'ordre d'insertion et non le champ ID référencé dans notre table. Donc si nous supprimons un enregistrement en plein milieu de notre liste de définition des quêtes, tout l'ordre serait perturbé. De même qu'il aurait été obligatoire de respecter un ordre croissant démarrant de zéro.
Nous allons devoir effectuer une requête simple ! Je vous montre le code :

Code (Ruby):
  1. Database.Quests.find{|quest| quest.id == ID_DESIRE}


Comme vous pouvez le constater, ce code ressemble assez fort à ceux que nous avions utilisé pour distribuer les récompenses. Typiquement, voici ce que fait cette ligne :

Prend tous les éléments de Database.Quests et les passe tous en revue sous le nom de quest (ce nom est libre, j'aurais pu mettre "lapin" à la place) et trouve celui dont l'id est égal à ID_DESIRE


Pour votre culture générale, la partie {|quest| quest.id == ID_DESIRE} est appelé un prédicat, dans le jargon de ruby, c'est un block. On aurait pu remplacer les {} par do et end (donc :
Code (Ruby):
  1. Database.Quests.find do |lapin| lapin.id == ID_Desire end
)

Par convention j'ai l'habitude d'utiliser des accolades pour les prédicats qui ne prennent qu'une ligne et les do/end pour les plus grosses questions.
Nous verrons plus tard que nous pourrons nous servir de cette méthode find pour faire d'autres requêtes, avec plus de critères pris en compte, mais nous n'y sommes pas encore Smiley.
Comme nous savons comment récupérer correctement des quêtes en fonction de leur ID, nous pouvons réécrire notre classe et cette fois nous pouvons effectuer des insertions de manière totalement libre sans se soucier de l'ordre Smiley:

Code (Ruby):
  1. # Description dynamique d'une quête
  2. class Game_Quest
  3.  
  4. # Accesseurs
  5. attr_reader :id
  6. attr_reader :name
  7. attr_reader :description
  8. attr_reader :exp
  9. attr_reader :gold
  10. attr_reader :items
  11. attr_reader :weapons
  12. attr_reader :armors
  13.  
  14. # Constructeur
  15. def initialize(id)
  16. quest = Database.Quests.find{|quest| quest.id == id}
  17. # le id que je teste en égalité avec quest.id est celui
  18. # passé en argument au constructeur.
  19. @id = id
  20. @name = quest.name
  21. @description = quest.description
  22. @exp = quest.exp
  23. @gold = quest.gold
  24. @items = quest.items
  25. @weapons = quest.weapons
  26. @armors = quest.armors
  27. @finished = false
  28. end
  29. # Renvoie l'état d'une quête
  30. def finished?
  31. return @finished
  32. end
  33. # Termine une quête
  34. def finish
  35. @finished = true
  36. # Donne l'or à l'équipe
  37. $game_party.gain_gold(@gold)
  38. # Attribue l'expérience à chaque membre de l'équipe
  39. $game_party.members.each do |actor|
  40. actor.gain_exp(@exp)
  41. end
  42. # Donne les objets
  43. @items.each do |id|
  44. $game_party.gain_item($data_items[id], 1)
  45. end
  46. # Donne les armes
  47. @weapons.each do |id|
  48. $game_party.gain_item($data_weapons[id], 1)
  49. end
  50. # Donne les armures
  51. @armors.each do |id|
  52. $game_party.gain_item($data_armors[id], 1)
  53. end
  54. end
  55. end


Cette méthode find existe pour les objets énumérables de Ruby, donc on peut l'utiliser pour les tableaux, les dictionnaires etc. Nous allons voir dans la rubrique suivante comment s'en servir pour interroger la base de données.

Effectuer des requêtes



En récupérant une quête au moyen de son ID, nous avons déjà fait des requêtes. Nous allons voir qu'il est possible d'aller plus loin que ça.
Cette partie est un petit plus car nous n'en n'aurons pas réellement besoin pour la suite de ce tutoriel. Cependant, je suis intimement convaincu qu'il s'agit d'un apport, et sans cette fonctionnalité, l'Extend Database est un peu useless.


Nous avions donc énoncé que

Code (Ruby):
  1. Database.Quests.find{|quest| quest.id == 10}


est une requête, qui va chercher le premier enregistrement dont l'ID est égal à 10. Nous aurions pu évaluer un autre prédicat. Par exemple :

Code (Ruby):
  1. Database.Quests.find{|quest| quest.name == "je suis un nooom"}


Cette requête va chercher le premier enregistrement dont le nom est "je suis un nooom". == est un opérateur, il va vérifier l'égalité (en mathématiques, on utilise = pour représenter l'égalité, attention, en Ruby, le signe égal simple assigne une valeur. Donc x = 9 n'interrogera pas l'égalité entre x et 9 mais assignera à x la valeur 9 !). Il existe d'autres opérateurs que le ==. En voici quelques-uns :

icone
x == y
Renvoie true si x est égal à y et false si x est différent de y.

icone
x != y
Renvoie true si x est différent de y et false si x est égal à y.

icone
x > y
Renvoie true si x est plus grand que y et false sinon.

icone
x < y
Renvoie true si x est plus petit que y et false sinon.

icone
x >= y
Renvoie true si x est plus grand que ou égal à y et false sinon.

icone
x <= y
Renvoie true si x est plus petit que ou égal à y et false sinon.


Il est aussi possible de coupler les questions, au moyen des opérateurs logiques or et and (que l'on peut aussi écrire || et &&).

icone
x and y
Renvoie truesi les deux valeurs, x et y sont vraies.

icone
x or y
Renvoie truesi au moins une des deux valeurs, x ou y est vraie.


Il est évidemment possible de poser des questions plus grandes au moyen de parenthèses comme par exemple :
Code (Autre):
  1. (x and (y or z)) and (a or c)

Apprendre à lire des expressions booléennes est un exercice intéressant et utile qui vous permettra d'aller plus loin dans la création de vos scripts. Cependant, ce n'est pas l'objectif de ce tutoriel, donc je vais clôturer cette partie sur cet encart, qui n'était là que pour vous expliquer pourquoi on récupère une quête de cette manière.
J'ajoute tout de même un petit exemple de requête sur notre table de quêtes :

Code (Ruby):
  1. Database.Quests.find{|quest| quest.id < 10 and quest.id > 20}


Je vous invite à essayer de comprendre le rôle de cette requête (rien de très dur) et vous serez prêt à exploiter la base de données vous-même pour vos propres systèmes !

icone Intégration dans le jeu


A ce stade-ci, nous avons déjà une grosse partie de l'application qui est réalisée, nous allons donc pouvoir nous intéresser à l'intégration dans le jeu. C'est-à-dire, à faire en sorte que le joueur puisse accepter et finir des quêtes. C'est donc la dernière ligne droite de cette partie-ci du tutoriel.

Les classes sont ouvertes, le Monkeypatching


Une chose qu'il est très important de savoir en Ruby, c'est qu'il s'agit d'un langage interprété et donc que ses classes sont ouvertes. Ce qui veut dire qu'à tout moment, je peux étendre une classe et lui rajouter des méthodes. Donc si par exemple je fais ça dans un script :

Code (Ruby):
  1. class Fixnum
  2. def successeur
  3. return self + 1
  4. end
  5. end


(pour information, la classe Fixnum est la classe qui décrit les entiers (les nombres de l'ensemble Z)) eh bien je pourrai faire : un_nombre.successeur qui me renverra ce nombre + 1. Donc 8.successeur vaudra 9 !

Lorsque l'on étend une classe, il n'est pas nécessaire de re-spécifier ses liens d'héritage, ça n'a même pas de sens, donc dans ce cours, nous ne le ferons JAMAIS !


Grâce à cette "feature" du langage Ruby, nous allons donc pouvoir étendre une classe du RGSS qui se chargera de gérer l'accumulation des quêtes et leur logique. On appelle cette pratique le Monkeypatching.

Le Monkeypatching des méthodes


Comme, nous l'avons vu, il est possible d'ajouter des méthodes, une question naturelle devrait vous venir à l'esprit :

Que se passe-t-il si j'ajoute une méthode qui a le même nom qu'une méthode déjà existante ?


Eh bien c'est une catastrophe, en effet, la méthode ancienne sera écrasée par la nouvelle ! Par exemple si je fais ça :

Code (Ruby):
  1. class Fixnum
  2. def (-)(autre_entier)
  3. return self + autre_entier
  4. end
  5. end


J'aurai écrasé la méthode "-" et l'aurai transformé en "+", ça permet de faire des petites blagues très drôles mais ce n'est pas très intelligent.
Cependant, c'est en écrasant des méthodes que nous allons pouvoir leur faire faire plus que ce pour quoi elles étaient prévues !

Les alias


Admettons que je veuille modifier le comportement d'une méthode ? Lui faire faire quelque chose en plus ?

Fastoche, premièrement, je vais copier le code de la méthode source, ensuite, je la monkeypatch et je colle le contenu de la méthode source, puis j'ajoute ce que je veux !


C'est une manière de faire qui marcherait, mais qui créerait son lot d'incompatibilités. Admettons que vous fassiez ça, tous les scripts qui monkeypatcheraient ces méthodes ne seraient plus compatibles et ce que nous voulons faire, c'est justement un script qui minimise les incompatibilités !
Pour cela, nous allons utiliser des alias.

Un alias est une manière de donner un autre nom à une méthode, comme ça, lorsque l'on écrasera une méthode, l'ancienne version sera toujours accessible via le nom que l'on lui aura donné via l'aliasisation ! Je vous montre !

Pour notre système de quêtes, nous allons modifier la classe Game_Party pour lui ajouter un attribut qui contiendra la liste des quêtes, qui sera un tableau ! Il suffit juste de rajouter une ligne dans son constructeur, je vous montre puis je vous explique :
Dans un nouveau script (en-dessous des précédents), j'ai créé un espace "Intégration des quêtes" dans lequel je vais étendre Game_Party :

Code (Ruby):
  1. class Game_Party
  2. attr_accessor :quests
  3. alias quest_initialize initialize
  4. def initialize
  5. quest_initialize
  6. @quests = []
  7. end
  8. end


Concrètement, j'étend la classe Game_Party (sans re-spécifier son lien d'héritage parce que je suis modernement cool). Je rends l'attribut quests lisible et ensuite, je crée un alias sur le constructeur.
A ce stade, on peut accéder à la méthode initialize aussi au moyen de la méthode quest_initialize. Puis j'écrase la méthode initialize, j'appelle l'ancienne version (l'alias pointe sur la méthode au moment où l'alias est effectué, donc avant que l'on l'écrase) et j'ajoute mon attribut "quests" qui est un tableau ! Facile non ?

Game_Party correspond à l'état de jeu, c'est donc cette classe qui stockera les membres de l'équipe, les inventaires, l'équipement, c'est donc naturellement dans cette classe que j'ai décidé de stocker mes quêtes lancées (en cours ou finies). Maintenant que nous avons un attribut quests, nous allons pouvoir créer des méthodes pour Game_Party qui nous seront utiles !

Intégration de méthodes pour manipuler les quêtes


La classe Game_Party décrit une équipe dans sa totalité. Plus haut dans le tutoriel, nous avons vu qu'il était possible d'y accéder au moyen de la variable $game_party, nous allons donc créer ce que l'on appelle des méthodes d'interface.

La première que nous allons faire est la méthode qui permet de lancer une quête et que nous allons appeler launch_quest. Elle prendra en argument l'ID de la quête à lancer. Je vous donne le code et je vous explique :

Code (Ruby):
  1. class Game_Party
  2. attr_accessor :quests
  3. # Modification du constructeur
  4. alias quest_initialize initialize
  5. def initialize
  6. quest_initialize
  7. @quests = []
  8. end
  9. # Lance une quête
  10. def launch_quest(id)
  11. if !@quests.find{|quest| quest.id == id}
  12. @quests << Game_Quest.new(id)
  13. end
  14. end
  15. end


La première étape :

Code (Ruby):
  1. if !@quests.find{|quest| quest.id == id}


C'est pour n'ajouter une quête que si elle n'est pas déjà en cours, sinon, on aurait des doublons dans la liste de quêtes.

Code (Ruby):
  1. @quests << Game_Quest.new(id)


Cette ligne ajoute dans le tableau @quests la nouvelle quête (uniquement dans le cas ou elle n'était pas déjà présente).
Dans un appel de script, il est donc possible de faire simplement "$game_party.launch_quest(ID)" pour démarrer la quête définie par l'ID passé en argument.

Eh oui, il avance notre petit système !


Maintenant il va falloir finir une quête ! Pour finir une quête, il faut d'abord que la quête soit lancée (donc qu'elle existe dans la liste de quêtes) et qu'elle ne soit pas finie (pour éviter de gagner plusieurs fois les récompenses). Donc nous allons créer deux méthodes. Une première pour accéder à notre quête (qui nous permettra de modifier launch_quest, un tout petit peu) et une méthode pour finir une quête :

Code (Ruby):
  1. class Game_Party
  2. attr_accessor :quests
  3. # Modification du constructeur
  4. alias quest_initialize initialize
  5. def initialize
  6. quest_initialize
  7. @quests = []
  8. end
  9. # Renvoie une quête en fonction de son ID
  10. def quest(id)
  11. return @quests.find{|quest| quest.id == id}
  12. end
  13. # Lance une quête
  14. def launch_quest(id)
  15. if !quest(id)
  16. @quests << Game_Quest.new(id)
  17. end
  18. end
  19. # Finit une quête
  20. def finish_quest(id)
  21. if quest(id) and !quest(id).finished?
  22. quest(id).finish
  23. end
  24. end
  25. end


Et voilà ! Nous avons tous les éléments pour que notre système de quêtes soit complet. Je vous montre, dans des appels de scripts :

icone
Démarrer une quête
Code (Ruby):
  1. $game_party.launch_quest(ID)


icone
Terminer une quête
Code (Ruby):
  1. $game_party.finish_quest(ID)


icone
Une quête est en cours
Cette ligne renvoie un booléen :
Code (Ruby):
  1. $game_party.quest(ID) && !$game_party.quest(ID).finished?


icone
Une quête est finie
Cette ligne renvoie un booléen :
Code (Ruby):
  1. $game_party.quest(ID) && $game_party.quest(ID).finished?


Les deux dernières commandes sont un peu moches donc nous allons modifier Game_Party pour rendre ces deux dernières commandes plus faciles à lire :

Code (Ruby):
  1. class Game_Party
  2. attr_accessor :quests
  3. # Modification du constructeur
  4. alias quest_initialize initialize
  5. def initialize
  6. quest_initialize
  7. @quests = []
  8. end
  9. # Renvoie une quête en fonction de son ID
  10. def quest(id)
  11. return @quests.find{|quest| quest.id == id}
  12. end
  13. # Lance une quête
  14. def launch_quest(id)
  15. if !quest(id)
  16. @quests << Game_Quest.new(id)
  17. end
  18. end
  19. # Finit une quête
  20. def finish_quest(id)
  21. if quest_launched?(id)
  22. quest(id).finish
  23. end
  24. end
  25. # Vérifie si une quête est en cours
  26. def quest_launched?(id)
  27. return quest(id) and !quest(id).finished?
  28. end
  29. # Vérifie si une quête est finie
  30. def quest_finished?(id)
  31. return quest(id) and quest(id).finished?
  32. end
  33. end


Maintenant, voici un petit rappel des fonctions avec cet ajout :

icone
Démarrer une quête
Code (Ruby):
  1. $game_party.launch_quest(ID)


icone
Terminer une quête
Code (Ruby):
  1. $game_party.finish_quest(ID)


icone
Une quête est en cours
Cette ligne renvoie un booléen :
Code (Ruby):
  1. $game_party.quest_launched?(ID)


icone
Une quête est finie
Cette ligne renvoie un booléen :
Code (Ruby):
  1. $game_party.quest_finished?(ID)


Et avec ces commandes, vous avez toutes les cartes en main pour intégrer votre système de quête au moyen d'appels de scripts simples. C'est ici que je conclus cette première partie !

icone Conclusion de la première partie


Nous en avons fini avec la gestion logique de notre système ! Dans la partie 2 nous réaliserons étape par étape le journal des quêtes et nous l'intégrerons dans le menu. Cette partie fut assez importante car nous avons appris la construction d'un système qui maximise les compatibilités et qui n'écrase rien de manière arbitraire et non contrôlée. Si vous avez des questions, le système de commentaires est là. Je remercie Nuki qui m'a bien motivé sur la fin (il a même écrit une partie Smiley) et Hiino qui relira ma piteuse orthographe (fait Smiley) !
Bien à vous ! Raho !

Index

Tags

Annonces
Bilou Concept
BilouCorp
Découvertes
Event Extender
Jeux
RPG Maker
Script
Tutoriel

Auteurs

2cri
Grim
Hiino
Joke
Nuki
PypeBros
Raho
S4suk3

Suivez nous
Rss

Poster un commentaire



Commentaires

jcad
Excellent et absolument claire, si l’on s’en donne un tant soit peu la peine. C’est un excellent travail de vulgarisation et aucun point (définition) n’est laissé en suspens.
Merci beaucoup pour tout ce travail. Posté le : 08/03/2015 à 03h14
J_J42
la suite va-t-elle être bientôt publiée?^^'
en tout cas merci pour votre site :) Posté le : 23/02/2014 à 22h19
testament
Excellent comme d'hab. L'humour est bon. J'ai appris en me marrant. Bien que cela me soit inutile pour mon progay (reférence ironman2) je vais tenter de l'utilisé si j'ai un progay un peu plus sérieux. Posté le : 14/11/2013 à 01h34
Nemau
(au voleur ! xD) Posté le : 02/11/2013 à 21h44
Nemau
Je retire ce que j'ai dis, tout est parfait sur ce site merveilleux, ça pète la classe de partout et je vais arrêter de lire en diagonale dorénavant afin de vraiment apprécier la qualité de ce que vous faites ! : ) Posté le : 31/10/2013 à 16h37
Sylvanor
C'est dangereux quand même vot' site, on peut se faire passer pour n'importe qui. :F (là c'est encore Nemau) Bon allez j'arrête de flooder. ^^

J'ai lu ce tuto en diagonale mais ça a l'air bien puissant !

PS (je viens de le remarquer) : apparemment l'heure des commentaires retarde de 5h. Posté le : 31/10/2013 à 00h36
Nemau
Nuki > bah c'est bizarre tout est normal maintenant ! x) Surement un bug occasionnel venant de chez moi. Posté le : 31/10/2013 à 00h31
Joke
C'est juste un excellent tutoriel. : ) Posté le : 27/10/2013 à 17h19
Nuki
Merci Ixsui et Naruto pour vos retours.
Nemau, as tu un encodage forcé spécifique? Utilises tu un navigateur particulier? Merci d'avoir pris le temps de nous prévenir ! Posté le : 27/10/2013 à 10h31
Nemau
Chez moi les accents merdent total, sur absolument toute la page. :o (commentaires inclus) Posté le : 26/10/2013 à 23h37
Ixsuixwzone
Très bon tutoriel. A conseiller au scripteur débutant ou intermédiaire ! Posté le : 25/10/2013 à 16h33
n@rut0
lol c dla merd Posté le : 25/10/2013 à 12h38
Nuki
ll faudra attendre combien d'année pour la Partie 2? Posté le : 03/10/2013 à 15h43
Heaven
Superbe :0
Merci pour ce tutoriel =D


Posté le : 03/10/2013 à 11h31