6. L'inventaire

Dans ce chapitre nous allons nous focaliser sur le fonctionnement de l'inventaire. Comme il va y avoir beaucoup de problème à couvrir, nous devrions aller directement à eux.

D'abord clarifions ce qu'est un inventaire. Un des aspect typique du jeu d'aventure est la possibilités de collecter divers saloperies qui trainent dans le monde virtuel et de les utiliser avec une autre afin que le joueur atteigne son but ultime, Sauvez le monde, etc… Aussi parfois vous pourriez vouloir vous venger et utiliser un de ces détritus (en parlant des articles de l'inventaire) sur quelque chose dans ce monde virtuel.

WME a un excellent support pour l'inventaire incluant les articles combinés et l'article stocké. WME supporte aussi plusieurs inventaires, au cas où vous auriez plus d'un acteur principal ou que vous vouliez intégrer du commerce dans votre jeu.

L'inventaire a deux principaux fichiers de définition:

  1. La définition de la boite d'inventaire - définit l'endroit où votre inventaire sera stocké. Il se compose de zones rectangulaires et de barres de défilement pour naviguer dans votre inventaire.
  2. La définition des articles de l'inventaire – définit les articles individuels et les scripts attachés pour que vous puissiez mettre en place des interactions nécessaires.

Ces deux fichiers sont attribué dans le gestionnaire de projet et nous en avons parlé précédemment. Mais pour vous rappeler, c'est le fichier inventory.def et le fichier items.items. Bien sûr vous pouvez les nommer comme vous voulez, mais restons pour le moment avec les éléments qu'on a sous la main.

Avant de commencer avec ça, copiez quelque part le contenu de la ressource pour le chapitre 5. Il se compose du projet vierge que nous avons vu précédemment et on va, éventuellement, construire un jeu simple sur la base de ces fichiers.

Commençons avec la définition de la boite d'inventaire. Nous allons baser notre interface sur l'image inventory.bmp qui se trouve dans le dossier data/interface. L'image ressemble à ça:

C'est très simple, néanmoins nous pouvons expliquer vaguement y expliquer la mécanique de l'inventaire. Avant de nous plongez dans le fichier de définition lui-même, nous devrions regarder l'image. Nous voyons deux rectangles rouge, que nous ne verrons pas dans notre jeu, car ils servent uniquement à réserver la place pour les touches utilisées dans l'inventaire, défiler vers la gauche ou vers la droite. Puis on vois 10 carré qui seront les emplacements pour nos articles.

Bon avec ça en tête, regardons le fichier inventory.def se trouvant dans le dossier de l'interface.

INVENTORY_BOX 
{ 
  ITEM_WIDTH = 65 
  ITEM_HEIGHT = 65

Ce sont les dimensions de chaque articles et elles correspondent au dimensions de chaque carré de la boite d'inventaire.

SPACING = 10

Ceci défini à quelle distance sont chaque carré (articles). Cette valeur est en pixels.

  SCROLL_BY = 1

Quand le joueur sur le bouton précédent ou suivant, les articles se deplace respectivement vers la gauche ou vers la droite. Cette valeur spécifie combien d'article seront déplacé (1 par 1, 2 par 2…)

  HIDE_SELECTED = TRUE

Si le joueur sélectionne un article de l'inventaire, il disparait de la boite d'inventaire (pour empêcher d'utiliser l'article sur lui même par exemple).

AREA { 30, 12, 770, 77 }

AREA définit la zone des articles de l'inventaire. C'est relatif à la fenêtre d'inventaire, pas à l'écran. Donc même si vous souhaitez afficher l'inventaire en bas, ces valeurs restent les mêmes. Ces valeurs sont les coordonnées X,Y du coin supérieur gauche et les coordonnées X,Y du coin inférieur droit de la fenêtre d'inventaire. En d'autres termes, les coordonnées (30,12) correspondent au coin supérieur gauche du premier article et (770,77) correspondent au coin inférieur droit du 10e article de l'inventaire.

  EXCLUSIVE = FALSE

Si c'est réglé sur TRUE, le joueur ne peut rien faire d'autre tant qu'il n'a pas fermé la fenêtre d'inventaire.

Maintenant arrive la définition de la fenêtre proprement dit. Notez que notre prochain chapitre traitera des fenêtres et des contrôles plus largement, mais nous allons présenter ici ce qui doit être expliqué.

  WINDOW 
  { 
    X = 0 
    Y = 0 
    WIDTH = 800 
    HEIGHT = 90

Nous commençons par définir la position et les dimensions de la fenêtre. Ces nombres sont absolus, donc contrairement à AREA, elle font référence à l'écran. Notre fenêtre sera positionnée en haut de l'écran et fera 800 pixels de large et 90 pixels de haut. Si nous avions voulu positionner cette fenêtre en bas de l’écran (800x600), nous aurions simplement passer Y à 510 (600-90).

IMAGE = "interface\inventory.bmp"

L'image de fond qui sera utilisée pour l'inventaire - Nous l'avons déjà vu plus-haut.

    BUTTON 
    { 
      TEMPLATE = "ui_elements\template\but.button" 
      NAME = "prev" 
      TEXT = "<" 
      X = 0 
      Y = 0 
      WIDTH = 30 
      HEIGHT = 90 
    } 
 
    BUTTON 
    { 
      TEMPLATE = "ui_elements\template\but.button" 
      NAME = "next" 
      TEXT = ">" 
      X = 770 
      Y = 0 
      WIDTH = 30 
      HEIGHT = 90 
    }

Arrive maintenant la définition des deux boutons. Pour que l'inventaire fonctionne correctement, ils doivent être nommé prev pour le défilement à gauche et next pour le défilement à droite dans la boite de l'inventaire.
TEXT est ce que nous allons écrire sur le bouton (dans notre cas < et >). X,Y,WIDTH et HEIGHT définissent les dimensions et la position, ils doivent couvrir les zones rouges. Notez que la position est à nouveau relative à la fenêtre parent.
TEMPLATE c'est le modèle de bouton. Nous verrons ça dans le chapitre des fenêtres, pour l'instant, utilisons celui par défaut.

  }

Nous terminons le bloc WINDOW.

}

Nous terminons le bloc INVENTORY_BOX

Ok, voyons à quoi ressemble notre inventaire. Ouvrez le fichier game.script et avant

Game.ChangeScene("scenes\Room\Room.scene");


mettez

Game.InventoryVisible = true;


Bien, c’était pas trop dur, n'est ce pas? Lancer le jeu et regardez l'inventaire en haut.

La prochaine étape dans la découverte de l'inventaire sera la définition des articles. Pour que les articles soit bien utilisés dans le jeu nous devons définir leurs bloc de données (datablock). Ces datablock apparaissent dans e fichier items.items et nous les avons dans le répertoire data/items. Jusqu'à présent notre fichier ressemble à ceci:

ITEM 
{ 
   CURSOR_COMBINED = TRUE // Défini si vous voyez pas seulement l'article mais aussi le pointeur standard quand vous avez sélectionnez l'article. 
 
   CAPTION = "WME User's Guide"  // Légende de l'article quand la souris passe par dessus. 
 
   NAME = "book"  // Nom dans le jeu. Vous ferez référence a celui-ci dans vos scripts 
 
   SPRITE = "items\book.bmp"  // Image ou sprite de l'article dans l'inventaire. 
 
   CURSOR = "items\book.bmp" // image pointeur (sprite) quand vous avez sélectionné l'article. 
 
   CURSOR_HOVER = "items\book_h.bmp"  // Défini une image à utiliser comme pointeur de souris quand cet article est sélectionné et est sur un hot-spot actif.. 
 
   SCRIPT = "items\book.script" // Script attaché à l'article qui contient les interactions. 
}

Nous pouvons évidemment définir d'autre paramètres pour l'article:

SPRITE_HOVER – Quand la souris passe au dessus de l'image de l'inventaire elle peut changer pour une autre.
ALPHA – Spécifie la transparence de l'image de l'article (0 = invisible, 255 = Complétement visible)
TALK - l'animation de parler pour cet article.
FONT - quelle police doit être utilisée pour les sous-titres et pour afficher la quantité.
AMOUNT - Quantité actuel d'articles
DISPLAY_AMOUNT – (TRUE / FALSE) si le la quantité actuelle d'article devrait être affichée?
AMOUNT_ALIGN - L'alignement de l’étiquette quantité ("left"(gauche), "right"(droite) ou "center"(centré))
AMOUNT_OFFSET_X - Le décalage en X en pixels de l’étiquette quantité relatif à la position de l'article.
AMOUNT_OFFSET_Y - Le décalage en Y en pixels de l’étiquette quantité relatif à la position de l'article.

Nous sommes, maintenant, capable de définir notre inventaire et chaque articles et regardons la façons de lier les articles avec le jeu actuel.

Comme je l'ai déjà écrit, il y a deux façon d'aborder l'inventaire - Globale (Un inventaire pour tout le jeu) et basé sur l'acteur - Chaque acteur a son propre inventaire.

Faisons e sorte que notre première interaction avec l'article arrive. Qu'allons nous faire là? - Nous plaçons un article (Livre) dans la scène. dès que qu'il y un clic gauche de notre souris sur cet article et qu'il est ramassé. L'article disparait de la scène et apparait dans l'inventaire.

Nous devrions avoir le resultat suivant :

Sauvegardez la scène et ouvrir le fichier appelé book.script dans votre répertoire data\scenes\Room\scr.

Écrivez dedans ce qui suit:

on "LeftClick" 
{ 
	actor.GoToObject(this); 
	Game.TakeItem("book");	 
}

Sauvegardez et test le jeu. Cool, Molly va vers le livre et le place dans l'inventaire! Le livre a également disparu. Mais pourquoi?

Si nous remplissons la valeur de Item avec le nom correspondant au fichier items.items, il devient liée donc à chaque fois que nous appelons la méthode TakeItem, il disparait et chaque fois que nous appelons DropItem il réapparaît. Modifions notre petit script pour démontrer cela:

on "LeftClick" 
{ 
	actor.GoToObject(this); 
	Game.TakeItem("book");	 
	actor.GoTo(100,100); 
	Game.DropItem("book"); 
}

Pas très logique cette façon de jouer, hein? Mais ça montre ce que je veux dire. Quelque fois c'est bien de détruire complétement l'article, par exemple si on veut combiner deux articles pour en faire un ou si nous utilisons l'article et que le joueur ne veuille pas avoir la possibilité de le récupérer. Pour cela nous avons en stock la méthode DeleteItem.

Avant de nous pencher un peu plus dans la manipulation d'objets, regardons l'élément dans l'inventaire. Nous savons, que dans items.items il y a un script lié à lui. C'est le fichier, qui prend en charge l'élément quand il arrive dans l'inventaire. Ce n'est pas limitée à une seule scène comme notre script précédent était et ainsi nous devrions compter sur le fait que le joueur voudrait essayer d'utiliser cet élément n'importe où dans le jeu. Ce serait vraiment pénible de créer des réponses individuelles pour chaque hot-spot donc en gros nous voulons avoir des réponses uniques pour des choses logiques et certaines génériques "ça n'a aucun sens d'utiliser ce livre ici." pour les autres hot-spots

Restaurer book.script à sa version originale (sans le lacher d'article) et ouvrez data\items\book.script. Et modifiez le contenu pour avoir:

on "LeftClick" 
{ 
    Game.SelectedItem = "book"; 
}

Cette simple ligne signifie, que nous assignons l'article de l'inventaire comme l'article actif et ainsi tout les clics de souris "utiliserons" cet article dans l'environnement du jeu. si SelectedItem est Null, Nous n'avons rien de selectionné et donc les actions du pointeur normal s'applique.

Testez le jeu et vous pouvez voir, que si nous sélectionnons le livre dans l'inventaire, nous pouvons l'utiliser ailleurs, mais nous devons nous débarrasser de lui. Comme nous allons maintenant construire l'action générique, nous allons décidé que si nous faisons un clic droit avec l'article sélectionné, nous remettons cet article dans la boîte de l'inventaire.

Alors comme c'est une action génériques pour l'ensemble du jeu, ouvrez scripts/game.script et ajouter la gestion RightClick, qui va s'occuper de ce problème.

on "RightClick" 
{ 
  if (Game.SelectedItem != null){ 
    Game.SelectedItem = null; 
  } 
}

En fait, nous vérifions seulement si nous avons choisi un objet et si oui, nous le retournons dans l'inventaire.

Jusqu'ici tout va bien et maintenant nous allons présenter le gros changement à notre salle. Nous allons introduire le nouveau personnage, qui s'appelle Sally. Elles sont jumelles donc elles partage les mêmes graphiques. :P

Aller dans le répertoire des acteurs et copiez le fichier molly.actor en sally.actor et molly.script en sally.script.

Dans molly.actor changez le début pour que ça ressemble à :

  NAME = "molly" 
  CAPTION="Molly" 
  SCALABLE = TRUE 
  INTERACTIVE = TRUE 
  X = 100 
  Y = 100 
  SCRIPT="actors\molly\molly.script"

Dans sally.actor changez le début pour que ça ressemble à :

  NAME = "sally" 
  CAPTION="Sally" 
  SCALABLE = TRUE 
  INTERACTIVE = TRUE 
  X = 460 
  Y = 400 
  SCRIPT="actors\molly\sally.script"

Ouvrez data\scripts\base.inc et ajoutez les lignes

global molly; 
global sally;

Ensuite ouvrez le fichier game.script et ajoutez un nouvel acteur juste après avoir chargé le premier:

// load our main actor 
molly = Game.LoadActor("actors\molly\molly.actor"); 
sally = Game.LoadActor("actors\molly\sally.actor"); 
actor = molly; 
Game.MainObject = actor;

Enfin ouvrez data/scenes/Room/scr/scene_init.script et modifiez le début pour que ça ressemble à ça:

#include "scripts\base.inc" 
 
molly.SkipTo(400, 400); 
molly.Direction = DI_DOWN; 
molly.Active = true; 
 
sally.SkipTo(100, 400); 
sally.Direction = DI_DOWN; 
sally.Active = true;

Ce que nous venons de faire est que nous avons créé un autre acteur nommé Sally qui utilise les mêmes graphiques que Molly, mais c'est une personne entièrement différente. Nous avons sa référence stocké dans la variable globale sally et nous l'avons placé dans la salle. La chose suivante que nous allons faire est que nous allons mettre en œuvre un super changement personnage.

Si le joueur clic sur Molly ou sur Sally (sans avoir d'article de l'inventaire sélectionné) le jeu donne le contrôle au second acteur.

souvenez vous que tout ce qui est dans vos scripts est configurer pour utilisé la variable globale "actor". donc si nous voulons faire un changement de personnage, nous réglons tout simplement cette variable sur le personnage de notre souhait. Nous devons aussi changer Game.MainObject parce il gère les défilement d'écran et on veut qui maintienne bien cette fonctionnalité. Donc ouvrez le fichier molly.script dans le dossier acteur et ajouter y la gestion LeftClick:

on "LeftClick" 
{ 
	if (Game.MainObject == molly) actor.Talk("Je suis déjà sélectionnée!"); 
	else 
	{ 
		actor = molly; 
		Game.MainObject = actor; 
	} 
}

Par analogie nous allons modifier sally.script :

on "LeftClick" 
{ 
	if (Game.MainObject == sally) actor.Talk("e suis déjà sélectionnée!"); 
	else 
	{ 
		actor = sally; 
		Game.MainObject = actor; 
	}	 
}

Bien que nous aurions pu utiliser pour cela qu'un seul fichier, plus tard ça sera payant de les avoir séparé quand nous voulons effectuer des tâches différentes à chaque acteur.

Maintenant lancer le jeu et remarquez que nous pouvons intervertir entre les acteurs par un simple clic gauche dessus.

Ayant fait cela, nous allons revenir à notre inventaire et nous essayons quelques interactions. La première chose que nous devons faire est d'ouvrir notre fidèle game.script et réelement coder dans la logique de base des interactions d'article de l'inventaire. Ce que nous voulons réaliser, c'est que chaque fois que nous avons faisons un clic gauche avec un article sélectionné sur un hot-spot, nous essayons d'appeler la méthode correspondante nommé par un nom d'article. Donc, si on clique avec un livre, on essayait d'appeler une méthode de l'entité cible "Livre".

Ceci peut être facilement fait en modifiant l'événement Clic gauche pour avoir:

on "LeftClick" 
{ 
  var ActObj = Game.ActiveObject; 
  if(ActObj!=null) 
  { 
    if(Game.SelectedItem != null && Game.SelectedItem!=ActObj) 
    { 
      var Item = Game.SelectedItem; 
      if(ActObj.CanHandleEvent(Item.Name)) ActObj.ApplyEvent(Item.Name); 
    } 
    else 
	     ActObj.ApplyEvent("LeftClick"); 
  } 
  else 
  { 
    Scene.ApplyEvent("LeftClick"); 
  } 
}

Ce que nous avons programmé là est en fait, que si nous avons un article d'inventaire sélectionné et que nous ne cliquons pas avec cet article sur lui-même (Ce qui serait faisable si nous n'avions pas "cacher" l'article de l'inventaire lors de sa sélection), puis on test si le hot-spot cible peut gérer l’événement par le nom de notre article et si oui, l’événement est déclenché.

Prenez du temps pour réfléchir au sujet de ce concept et quand vous êtes sur de le comprendre, continuez.

A présent, dans notre cas nous avons deux objets actifs sur l'écran (à condition que vous ayez déjà pris le livre) - Molly et Sally. Donc, notre objectif est de les faire réagir à l'usage livre.

Commençons avec ce concept. Nous voulons ce qui suit: si Molly est active et lit un livre, elle devrait être capable de lire le livre. Si toutefois vous essayez de lire un livre avec Sally tandis que Molly est actif, Sally refuse. La même chose s'applique vice et versa.

Ouvrons molly.script d'abord et ajoutez le code suivant:

on "book" 
{	 
	if (actor == molly) actor.Talk("C'est toujours important de lire la doc de WME! Je vais le faire maintenant!"); 
	else 
	molly.Talk("Lit le toi même Sally!"); 
}

Puis ouvrez sally.script et ecrivez ce qui suit:

on "book" 
{	 
	if (actor == sally) actor.Talk("C'est toujours important de lire la doc de WME! Je vais le faire maintenant!"); 
	else 
	sally.Talk("Lit le toi même Molly!"); 
}

Là encore c'est une logique très simple. Si la variable actor contenant l'acteur actif est défini sur l'acteur qui est cliqué, nous liront la doc sinon nous diront l'autre ligne. L’événement "book" est appelé parce que nous avons modifié le game.script pour faire cela. Clair comme du jus de boudin?

Nous avons maintenant un chose pas logique. Ça ne se soucis pas de qui a pris le livre, les deux ont le livre dans leur inventaire. C'est une approche d'inventaire globale et c'est bon si nous avons qu'un acteur en scène. Mais comme nous avons introduit deux acteurs, nous devrions séparer leurs inventaires.

Nous allons essayer le scenario suivant. Quiconque prendra le livre le mettra dans son inventaire personnel. Si cette personne utilise le livre sur l'autre personnage, elle donne le livre à l'autre.

Nous allons introduire un nouvel attribut de Game qui s’appelle InventoryObject. Cet attribut accepte la variable acteur et nous allons modifier les scripts de l'acteur qui avec la permutation des acteur va aussi permuter les objets de inventaire.

Mais ce n'est pas assez. Nous n'allons plus utiliser Game.TakeItem (Game.DropItem, Game.DeleteItem, etc…)mais nous allons utiliser actor.TakeItem (actor.DropItem, actor.DeleteItem).

Donc notre premier arrêt est dans game.script à notre emplacement trop familier:

actor = molly; 
 
Game.MainObject = actor; 
Game.InventoryObject = actor;

Prochain arrêt est dans data\scenes\Room\scr\book.script:

on "LeftClick" 
{ 
	actor.GoToObject(this); 
	actor.TakeItem("book");	 
}

Et notre dernier arrêt pour le moment est le script de Molly et de Sally en ajoutant dans le gestionnaire de clic gauche des deux ces lignes :

Game.MainObject = actor; 
Game.InventoryObject = actor;

Maintenant quand vous testez le jeu, vous voyez que ces filles ont des inventaires séparés et d’ailleurs qui saisit le livre, l'a.

Maintenant occupons nous de donner le livre:

sally.script

on "book" 
{	 
	if (actor == sally) actor.Talk("C'est toujours important de lire la doc de WME! Je vais le faire maintenant!"); 
	else 
	{ 
		Game.SelectedItem = null; 
 		molly.DropItem("book"); 
		sally.TakeItem("book");	 
		sally.Talk("Merci"); 
	} 
}

Si nous cliquons avec Molly et le livre sur Sally, nous mettons d'abord l'article de coté (SelectedItem = null), puis Molly lâche le livre et Sally le prend. C'est la façon dont les items sont transférés. A la fin Sally remercie Molly pour le livre. Si nous n'avions pas appelé la méthode DropItem, les deux filles aurait eu le livre dans leurs inventaires.

le script de Molly est aussi le même:

on "book" 
{	 
	if (actor == molly) actor.Talk("C'est toujours important de lire la doc de WME! Je vais le faire maintenant!"); 
	else 
	{ 
		Game.SelectedItem = null; 
		sally.DropItem("book"); 
		molly.TakeItem("book");	 
		molly.Talk("Merci"); 
	} 
}

Le dernier couple de méthodes, pour clore ce chapitre, sont des méthodes supplémentaires pour les articles.

Game.IsItemTaken([item name]); retourne TRUE si n'importe qui a un article dans son inventaire.

Si nous voulons contrôler si un article est dans un inventaire spécifique, nous l'interrogeons grâce à sally.HasItem([item name]); et là encore cette méthode répond TRUE or FALSE
(Donc, en pratique Game.IsItemTaken("book"); ou sally.HasItem("book");)

C'est assez pour les articles sauf ce conseil important. Quelques fois vous n'avez pas besoin d'un acteur pour un NPC comme ça peut être, par exemple, seulement un ombre ou une tête accroché au mur. WME ne vous limite pas à cela et même des entités peuvent avoir leurs inventaire!

Liens vers la Documentation:
Contents→ Inside a game→ Working with the inventory - pour la description générale du fonctionnement de l'inventaire