Maintenant, depuis le chapitre précédent, nous avons des entités, avec des propriétés, organisées en hiérarchie de classe. Ce système d'entité est suffisant pour décrire une scène statique assez complexe. Par contre, dès qu'on veut rajouter des éléments dynamiques, comme des animaux, ou des personnages non jouables, on se rend compte qu'il faut pouvoir modifier les entités à postériori, pour les animer, les faire se déplacer, etc. Il faudrait donc pouvoir définir des morceaux de programme qui codifient le comportement des entités.
Ce but sera atteint par l'utilisation de fonctions de rappel (callbacks), écrites dans un langage de script (javascript par défaut). Pourquoi des fonctions "de rappel" ? Tout simplement parce qu'elles sont appelées par le moteur de jeu lorsqu'un évènement particulier survient : attaque, collision, réponse à un dialogue, le but fixé a été atteint, etc. On peut faire une analogie avec le mode de fonctionnement des êtres vivants : quand ils reçoivent un stimuli (évènement), une information est envoyé au centre nerveux (la fonction de rappel est appelée), qui après analyse définit une réaction (modification de l'état de l'entité par la fonction).
Un ensemble de fonctions de rappels qui prend en compte tous les évèments possible est appelé un comportement. Chaque jeu peut proposer une liste de comportements standards ; les quêtes peuvent bien-sûr ajouter leurs propres comportements.
Les fonctions de rappel qui doivent être définies sont :
Si javascript est le langage de script utilisé, alors la définition d'un comportement se fait comme suit : on déclare toutes les fonctions de rappel (le nom de la fonction doit être préfixé du nom du comportement), puis on signale au moteur la création d'un nouveau comportement à l'aide de la fonction arkhart_RegisterCallback.
// Ici, on veut simuler le comportement d'un boulanger. Seules les déclarations // nécessaire à la création d'un comportement ont été inclues ; pour l'écriture // de l'IA, on attendra le prochain chapitre :) // Appelée à la création du boulanger function boulanger_onCreate () { // ... Reste à écrire le code ... } // Appelée lorsque le but a été atteint. function boulanger_onGoalReached () {} // Appelée lorsqu'une alarme s'est déclenchée. function boulanger_onTimer (id) {} // Appelée lorsque 'entite' dit 'message' au boulanger. function boulanger_onTell (entite, message) {} // Ajoute le comportement 'boulanger' à la liste des comportements. arkhart_RegisterCallback ("boulanger");
Finalement, pour indiquer au moteur quel comportement doit utiliser l'entité, on assigne au membre 'callback' de celle-ci le nom du comportement. Par exemple, si on veut ajouter un boulanger dans une quête :
implements Personne, uses Humain { model = "{models}/boulanger.mdl"; position = {128.0, 0.0, 128.0}; // Signale que cette entité va avoir le comportement 'boulanger'. callback = "boulanger"; }
Maintenant survient un autre problème (éh oui, encore un!) : comment fournir des données spécifiques au comportement pour une entité. Par exemple, pour un boulanger, comment lui indiquer quelle est sa boulangerie, où il peut acheter de la farine, etc.
La réponse simple serait de rajouter de créer une classe Boulanger, dérivée de la classe Humain. Ca n'est pas acceptable, tout simplement parce qu'un boulanger peut-être un Daphy, par exemple : il faudrait créer une classe DaphyBoulanger, HumainBoulanger, et ainsi de suite pour toutes les classes qui peuvent devenir boulanger. Ce serait ingérable.
Le problème vient du fait que le comportement d'un personnage n'est aucunement lié à sa classe ; Pour chaque entité, il est donc nécessaire d'associer deux objets distincs. Un Objet de Caractéristique, qui contient une liste de propriétés inhérentes à cette entité (c'est ce dont nous avons traité dans le chapitre précédent), et un Objet de Comportement, qui contient une liste de propriétés liées au comportement de cette entité. La distinction est de taille.
Le premier Objet reste presque inchangé au cours de la vie de l'entité, il correspond aux caractéristiques immuables, le deuxième donne des informations sur l'action / le comportement actuel de l'entité. On comprend encore plus l'importance de séparer les deux : en procédant de la sorte on on rend possible un changement de comportement au milieu de la vie de l'entité. Par exemple le boulanger (toujours lui!) peut passer d'un comportement d'artisan la journée à un comportement de père de famille la nuit.
Passons à la partie technique : comment spécifie-t'on les Objets de comportement ? La réponse est simple : exactement de la même manière que l'on fait pour les Objets de caractéristique.
Par exemple :
// {game}/scripts/callbacks.def // ... Callback { // Remarque : ce champ est indispensable. Contrairement à ce que // son nom pourrait laisser croire, "name" ne contient pas le nom de // l'entité à laquelle le comportement s'applique, mais son // nom-identifiant (shortname), qui est forcément unique. STRING name { desc = "[entity] Entité à laquelle ce comportement s'applique"; default = "None"; } } Boulanger inherits Callback { STRING boulangerie { desc = "[entity] Boulangerie dans laquelle travaille ce brave " "homme"; default = "None"; } } // ...
// entities.lst // ... implements Personne, uses Humain { shortname = "Boulanger0"; name = "Marcel le Boulanger"; model = "{models}/boulanger.mdl"; position = {128.0, 0.0, 128.0}; // Signale que cette entité va avoir le comportement 'boulanger'. callback = "boulanger"; } // ...
// callbacks.lst // ... implements Boulanger { name = "Boulanger0"; boulangerie = "Snattalang_Boulangerie18"; } // ...