Les AI

Les gardes portant des torches
Patrouilles et Navmesh
Comment montrer si un AI est inconscient ou mort
Personnalisation d'AI – Exemple d'un Elément de feu (Fire Elemental)

Personnalisation d'AI – Exemple d'un Elément de feu (Fire Elemental)

( tr : Stefan Key)

Ce tutorial a pour but de vous montrer comment ajouter un nouveau type d'IA au bestiaire de Deadly Shadows. On va prendre l'exemple de création d'un élément de feu, les méthodes sont les mêmes pour n'importe quel créature (du moment que les caractéristiques voulues ne dépassent pas les limites du moteur du jeu). Tous les sujets ne sont pas couverts ici. On ne va pas non plus ajouter de nouveaux meshes, des textures ni des animations. Au lieu de cela, on va plutôt se concentrer sur ce qui est déjà disponible ; ce qui est déjà pas mal.

Ce tutorial est pour les débutants. Il présente la recette complète pour faire un élément de feu qui marche, son but principal est de montrer de quoi est fait un AI.

Le design

Si vous avez déjà quelque expérience avec le design de vos propres modifications, ce conseil va vous paraître très évident mais il est si important qu'il devrait être dit à chaque fois : ne commencez jamais en ouvrant en premier l'éditeur. Prenez plutôt un crayon et une feuille de papier, asseyez-vous et répondez à quelques questions (L'ordre n'est pas obligatoire) :

1. A quoi devrait ressembler votre créature ? Essayez de le dessiner ou d'écrire une petite description (pas la peine de faire un chef d'œuvre, c'est seulement pour vous).
2. Quel est son rôle dans votre mission ou votre campagne ? Est-ce un ennemi, un commerçant ou un piéton ? ou alors peut-être le boss super méchant de fin de niveau ?
3. Décrivez-le en quelques mots ? Est-il dangereux ? Mystérieux ? Fait-il peur ? Amusant ? Surnaturel ? Intelligent ou abruti ? Est-il intellectuel ou instinctif ? Agressif ou lâche ? Bon ou méchant ? (ou peut-être comme Garrett, quelque part entre les deux)
4. Si c'est un ennemi, quelles sont ses qualités ? En d'autres termes, quels dangers représente-t-il pour le personnage-joueur ?
5. Et quels sont ses points faibles ? Comment le joueur devra-t-il le vaincre ? Ou alors on ne peut le vaincre de manière normale ? Est-il facile ou difficile de s'en défaire ?
6. Qu'est-ce qui rend cette créature différente des autres ? Rappelez-vous, vous avez l'entier réglage à votre disposition. Si vous avez besoin d'une bande de fanatiques religieux incorruptibles alors vous n'aurez pas besoin de créer un nouveau groupe. Les Marteleurs feront l'affaire.
7. Est-ce que cela correspond au contexte ? Par exemple, les références directes à des lieux, personnages, événements réels sont déplacées. Alors que des extrapolations sur un ordre religieux sont habituellement plus logiques.

Voilà des questions importantes qui vous forcent à prendre des décisions, mais ne vous y habituez pas trop. Pendant la réalisation, il se peut que quelque chose ne marche pas comme prévu ou même pas du tout. Le moteur ne supporte pas beaucoup de choses et d'autres requièrent trop d'efforts. En général, on doit séparer les conditions (les questions posées au dessus) à la réalisation (Sujet de ce tutoriel). Il peut être aussi bien de prendre en compte les limites techniques pendant la phase de réalisation. Par exemple, si vous n'avez pas les moyens de créer un nouveau mesh, alors votre créature est sûrement de type humanoïde.

 

Nous allons mettre en pratique ce que nous avons écrit pour notre IA personnalisé. Nous avons choisi un élément de Feu, remplissons les autres détails.

1. En tant qu'élément de feu, il est fait exclusivement de… feu. Comme nous n'utilisons pas de mesh personnalisé, nous savons qu'il a une vague apparence humanoïde. Il diffuse de la lumière orangée, il émet des sons de craquements et de feu. Des sons additionnels sont émis quand il est en colère ou lorsqu'il remarque quelque chose d'inhabituel. Quand il entre ou se prépare au combat, un son plus fort encore se fait entendre. Pendant le combat, il diffuse plus de lumière que d'habitude ; c'est un signe d'agitation.
2. Il n'y a pas beaucoup d'éléments de feu dans la Cité. Ils sont violents, détestent négocier et il est déconseillé de s'allier avec eux. Ils ne sont pas doués de conscience, ni sociaux, ils sont plus puissants que les autres créatures, il ne faut donc pas en mettre beaucoup. On veut aussi que le joueur les combatte un par un. L'élément de feu peut être un bon gardien de trésor ou surveiller un point de passage important (comme un pont unique au dessus d'une rivière de lave)
3. Ils sont agressifs et sans conscience. Ils sont effrayants car ils peuvent brûler un humain se tenant à côté d'eux. Ce sont des créatures mystérieuses. Elles sont surnaturelles et de ce fait, liées à des affaires surnaturelles. Elles sont le témoin de quelque chose de magique aux alentours et aussi d'un potentiel de richesses (au moins pour quelqu'un).
4. Les éléments de feu attaquent avec leurs "membres" mais ce qui les rend dangereux est leur température. Ils n'ont pas besoin de toucher Garrett pour lui faire du mal, le simple fait d'être près d'eux est fatal. Ils ont leur propre source de lumière, il est difficile de se cacher.
5. Les éléments de feu sont invulnérables à la plupart des armes, ils ne peuvent pas être assommés mais on peut les blesser avec de l'eau ou des flèches à eau. Le joueur devra donc combattre ce genre de créatures à distance à cause de leur capacité à projeter de la lumière et à brûler. On rendra l'élément de feu plus lent que les autres créatures ainsi le joueur aura la possibilité de le contourner.
6. Notre nouveau IA a un panel de vulnérabilités différent des humains et demande une autre méthode de furtivité de la part du joueur (du genre "Je vais me placer ici et le sniper avec une de mes flèches" ou alors "Je vais trouver un petit chemin et le contourner"). Il faut aussi le placer dans un contexte spécifique : personne ne pourrait voir un garde ordinaire en train de surveiller une cité antique en ruines complètement oubliée.
7. Les éléments de feu ont fait leur première apparition dans Dark Project. Ils font partie de la mythologie du jeu.

Bien, maintenant on peut poser notre crayon, ranger la feuille et ouvrir l'éditeur. Vous pouvez arrêter de lire pour le moment et construire une petite mission-test. Il devrait y avoir une pièce avec un Player Start, une chambre partiellement éclairée, deux couloirs les reliant : un petit couloir bien éclairé et un grand sombre, bien large.

Structure des données

Cela ne sert à rien d'avoir beaucoup d'IA si on ne peut pas les contrôler. Nous aurons besoin d'une structure de données, à la manière d'une table des matières ou un index.
On ne pense jamais à la table des matières quand on lit un roman, mais imaginez seulement comment on pourrait trouver la description de la réunion du conseil de Rivendell si "Le Seigneur des Anneaux" n'était pas divisé en chapitres !

Ouvrez Actor Class Browser (Dans le menu View). Sur la gauche, il y a une arborescence avec les noms de toutes les classes disponibles. Chaque classe définit exactement une sorte d'objet et chaque objet que vous créez appartient à une classe. Cliquez sur un nom. Sur la droite, vous verrez les propriétés de la classe choisie (Sinon vous devriez voir un menu Properties dans la rangée la plus basse avec un [+], cliquez sur ce [+]). Tous les objets de cette classe, quand vous les créez, auront les mêmes propriétés que la classe à laquelle ils appartiennent. Vous pouvez changer les propriétés plus tard et même en rajouter de nouvelles, mais vous ne pouvez pas effacer celles qui existent déjà (Si vous les effacez quand même, on aura l'impression que l'objet aura perdu des propriétés, mais en réalité, elles sont toujours présentes, cachées à moins que vous mettiez une coche dans la fenêtre des propriétés des objets Show non-Local Props.)

Vous vous demandez peut-être pourquoi il y a une arborescence de classes plutôt qu'une simple liste. Car il y a des classes qui sont basées sur d'autres. Par ex, la classe Pawn est basée sur celle de Actor. On dit que Actor est la classe de base (ou le "parent") de la classe Pawn. Pawn est la classe "enfant" de Actor (on dit aussi que Pawn "hérite" d'Actor). La relation entre la classe de base et la classe "enfant" est similaire entre la classe et l'objet. A chaque fois que vous créez une nouvelle classe, elle est une copie exacte de sa classe "parent".
Puis vous pouvez (devriez même !) changer quelques-unes de ses propriétés ou en ajouter d'autres. Vous devriez toujours choisir une classe de base qui sera la même et qui aura les mêmes choix que celle créée. Par exemple, vous voulez faire une torche (hand torch) qui donne une lumière violette, il est bon de chercher d'abord une torche normale, créez une classe "enfant" à partir de là et changez seulement la couleur de la lumière (light color) en violet (purple). Ceci vous fera économiser du temps et votre structure de données sera bien ordonnée (ce qui est très important à la longue).

Vous n'êtes pas obligés de créer une nouvelle classe à chaque fois que vous avez besoin d'une nouvelle sorte d'objet. Si vous n'avez besoin que d'une seule et unique torche violette, créez simplement une torche normale puis changez ses propriétés. Aussi, souvenez-vous que les changements faits aux objets ne s'appliquent qu'à eux alors que faire des changements sur une classe change également tous les objets de cette classe. Si vous voulez beaucoup de torches violettes, il est mieux de créer une nouvelle classe ce qui vous évite de modifier chaque torche manuellement.

Créons une classe pour notre élément de feu ! Toutes les classes d'IA de Deadly Shadows sont basées sur le T3AIPawn > AIPawn > Pawn. Regardez un peu à tous ses "enfants" (et leurs "enfants", ainsi de suite…). Leurs noms sont plus ou moins assez évidents. C'est un bon entraînement car si vous cherchez, par exemple, la classe torche (torch class), vous n'êtes pas obligés de scanner toutes les propriétés des classes ; il vous suffit simplement de chercher la classe nommée torch.
Nous sommes plus intéressés par T3AIPawnCreature qui est la classe-parent de tous les IA non-humains. C'est là que notre élément de feu sera le mieux classé.
Faites un clic droit sur T3AIPawnCreature et choisissez New. Tapez FireElemental au lieu de MyT3AIPawnCreature. Vous pouvez bien sûr choisir un nom différent, mais gardez en tête que certains caractères sont interdits (les espaces, en particulier). Il est mieux aussi de mettre un nom en anglais, qui est accepté par tous comme étant la langue informatique. Sans contexte, vous ne devineriez jamais ce qu'est un "ZywiolakOgnia", n'est-ce pas ?

N'oublions pas de faire un clic droit sur la nouvelle classe et de choisir Placeable/UnPlaceable. Un astérisque apparaîtra à côté du nom de la classe qui indique qu'à partir de maintenant, l'éditeur vous laisse placer les objets de FireElemental dans vos maps. Ne le faites pas pour l'instant. Votre classe n'a pas encore toutes les propriétés requises. On y vient.

Les propriétés

Les propriétés stockent une très grande partie des informations des classes et des objets. Certaines montrent l'état actuel de l'objet et ses modifications durant le jeu. D'autres décrivent l'état original (initial) de l'objet. Regardez les propriétés de FireElemental. Il y a une propriété avec une catégorie appelée Health (Santé). Il y a aussi 2 sous-catégories : HealthState et MainHealth. Leurs valeurs sont déjà réglées. MainHealth est en bleu pâle ce qui signifie que FireElemental a la valeur de son "parent". Si vous changez un des champs de MainHealth, sa couleur changera ce qui indique que sa valeur a été modifiée localement.

Ne vous laissez pas intimider par le nombre des propriétés. C'est très logique et sensé. Les meilleurs moyens d'en apprendre plus sur les propriétés sont :
- De lire leur nom (Sérieusement, beaucoup oublient de le faire !),
- De lire les notes des développeurs,
- De parcourir les classes existantes,
- De regarder d'un peu plus près les missions originales et de voir comment elles fonctionnent,
- De tester vos projets (Ne vous inquiétez pas, tant que vous travaillez sur les objets et non sur les classes et que vous n'effacez rien, vous ne risquez pas de détruire quelque chose).

Dans ce tutorial, nous allons modifier et ajouter quelques propriétés. Commençons avec Actor > VoiceTag

Cette propriété définit les voix de réponses que les IA vont utiliser. La valeur de FireElemental hérite de T3AIPawnCreature qui est T3CityGuard. Ce n'est pas approprié car nous ne voulons pas que notre créature parle comme un humain et on ne veut sûrement pas qu'il se plaigne de ses chaussures ou du capitaine. Aucune des voix de DS ne correspond car aucune des créatures des missions originales de DS n'a de voix "de feu". On ne va pas non plus créer notre propre son. On verra ça un peu plus tard. Pour l'instant, mettez Actor > Voice Tag à null (en fait, laissez le champ vide sans aucun caractère). Ceci indique au jeu que nous ne voulons pas donner de voix à notre AI.

Astuce : Si vous aimez une voix d'une des créatures des missions originales et souhaitez la donner à votre Ai, vous pouvez trouver la classe de la créature dans l'arborescence T3AIPawn, copiez la valeur Actor > VoiceTag et collez-la à la valeur Actor > VoiceTag de votre Ai. La même méthode peut s'appliquer dans quasiment tous les cas, tant que vous savez à quoi correspond la propriété que vous cherchez. Certains comportements sont définis par plus d'une propriété.

Sound > CharacterType
Sound > SoundAmbient

La classe FireElemental n'a pas ces propriétés, nous devons les ajouter manuellement. Faites un clic droit sur n'importe quelle catégorie de propriété et choisissez Add Property. Vous devriez voir une fenêtre avec une liste de catégories sur la gauche. Choisissez Sound de la liste. Une autre liste apparaît sur la droite, ce sont les propriétés assignées à la catégorie Sound. Choisissez CharacterType et SoundAmbient (Vous pouvez faire une sélection multiple en faisant un clic gauche et en maintenant la touche CTRL). Cliquez sur Add Property. Les deux devraient maintenant apparaître sur la liste des propriétés de FireElemental dans la catégorie Sound.

CharacterType définit le son des pas. On ne veut pas que l'élément de feu fasse des bruits de pas car il n'a pas de pieds, mettez cette propriété à null. Ce que nous voulons entendre, c'est le bruit du feu quand il est à proximité. Choisissez SoundAmbient et cliquez sur le bouton avec 3 points. Allez vous préparer un petit café, car les Schemas sont une panoplie de plus de 15000 petits fichiers textes et leur chargement peut prendre plusieurs minutes. Vous devriez maintenant voir une nouvelle fenêtre. C'est le browser des schemas sons. Ca n'a l'air de rien à la première impression mais les schemas sons sont une bonne invention. Chacun d'entre eux définit comment le son (ou le groupe de sons) devraient être joués. Donc "jouer un schema" est presque la même chose que "jouer un son", même si les schemas sont de simples scripts (texte) (Tellement simple, que l'on va en écrire un à la fin du tutoriel).

Allez dans le Schema Browser, ouvrez le répertoire de sons puis dans schemas_sfx. Trouvez fireplace_lp et sélectionnez-le. Réduisez le browser et retournez dans la fenêtre des propriétés de votre FireElemental. Allez dans Sound > SoundAmbient encore une fois mais là cliquez sur Use. La valeur de la propriété devrait changer en fireplace_lp.

AI > AnimationSpeedMultiplier
Comme on l'a dit avant, nous voulons ralentir le FireElemental. Mettons la propriété à la même valeur que celle des zombies : 0.65. Ce qui signifie le FireElemental sera 35 % plus lent que les autres créatures qui ont un AI > AnimationSpeedMultiplier réglé à 1 (dont les gardes de la ville). Garrett devrait être capable de leur échapper sans le moindre souci.

AIPawn > CombatModelClass
AIPawn > MovementModelClass
AIPawn > BehaviorModelClass
AIPawn > FactionModelClass

Toutes les propriétés de la catégorie AIPawn (avec l'exception de DiagnosticsOn) pointent vers d'autres classes. On peut les trouver dans l'arborescence Actor > MetaProperty > AIModel.
Jetons un coup d'oeil sur une des classes de modèle de combat, appelée ZombieCombatModel. Il y a 3 catégories de propriétés intéressantes : AICombat, AIMeleeCombat et AIRangedCombat. Tout ceci paramètre le comportement du IA au combat. Vous pouvez toujours créer une nouvelle classe et régler les valeurs des propriétés à celles que vous voulez. Toutes les classes de modèles peuvent être modifiées de la même façon. Nous allons en faire une pour notre FireElemental maintenant car nous avons oublié un léger détail : les FireElemental ne doivent pas glisser sur les flaques d'huile. On ne peut donc pas utiliser un modèle de comportement existant, car de manière générale, toutes les créatures ayant des jambes sont sensibles à ces saletés visqueuses.

Trouvez T3HumanBehaviorModel et créez son "enfant". Appelez-le FireElementalBehaviorModel. Regardez ses propriétés. Une des catégories est appelée AIStateFlags et contient beaucoup de propriétés dont le nom commence par StateEnabled.
Le nombre d'états est limité et l'IA est toujours dans l'un d'eux. Son comportement général est défini par les passages d'un état à un autre, constamment. En règle générale, il y a un état pour chaque action possible (et sinon, cela signifie que l'IA ne peut faire pas d'action ou qu'il doit faire quelque chose avant).
Malheureusement, les Ai ne connaissent rien de l'effet glissant des huiles, il faut donc mettre StateEnabled_Slipping à 0. L'Ai ne pourra plus entrer dans l'état "Slipping" (glissant) et sera invulnérable à l'huile. Appliquez ce changement maintenant.

Revenons à la classe FireElemental et à ses propriétés AIPawn. Réglez BehaviorModelClass à FireElementalBehaviorModel. Pour le combat, nous voulons qu'il soit agressif et sans conscience comme les zombies. Réglez CombatModelClass à ZombieCombatModel, FactionModelClass à UndeadFactionModel et enfin SensoryModelClass à UndeadSensoryModel.

Il y a une propriété de classe de modèle en plus dans cette catégorie : MovementModelClass. Sa valeur ne dépend que de vos goûts car elle définit les paramètres d'animations que l'AI va utiliser. Par exemple, si vous utilisez ZombieMovementModel, votre FireElemental aura des bras raides mais si vous choisissez StatueMovementModel, il aura l'air plus musclé et plus effrayant. Parfois, il peut être difficile de voir la différence car…

Render > SkeletalTag
Render > MaterialSkin
Render > CastShadows
Render > Transparency

…nous allons rendre notre Ai invisible. Plus précisément, son mesh sera invisible. Les éléments de feu ont besoin d'avoir un mesh car sans cela, il leur serait impossible de bouger dans votre niveau. D'un autre côté, pas de mesh (la géométrie) ni de skin (les textures) ressemble à un élément de feu. La solution est de les avoir mais de convaincre l'engin de ne pas les dessiner. Pour ce faire, il faut ajouter la propriété Render > Transparency et mettre une valeur à 1.
Ajoutez aussi Render > CastShadows et mettez la valeur ShadowCastingFalse. Le FireElemental n'aura pas d'ombre.
Ajoutez Render > SkeletalTag et mettez la valeur GuardSkinny.
La dernière propriété à régler est Render > MaterialSkin. Sa valeur est sans importance car le corps du FireElemental est invisible, mais nous avons déjà pris pas mal de choses des morts-vivants, on peut donc utiliser un de leurs skins (comme Zombie_01)

Les links (ou liens)

Nous allons faire une simple astuce : le corps du FireElemental est invisible mais nous allons lui attacher des flammes qui sont visibles. Quand le FireElemental bouge, les flammes suivent son mouvement. Le joueur aura l'impression que le FireElemental est fait exclusivement de feu.

Nous avons besoin de links. Le link est le lien entre 2 objets. Quand un de ces objets change, l'autre peut changer aussi, d'une façon qui dépend du type du link (qu'on appelle aussi flavor).
Par exemple, le link RigidAttachment permet à un objet d'en suivre un autre dans l'espace.
Quand on fait un link entre un humain et un objet avec RigidAttachment, l'objet est porté par l'humain. L'exemple le plus clair de cette technique est celui du garde qui porte une torche mais quand on y regarde de plus près, beaucoup d'IA ont les links RigidAttachment pour les vêtements, les cheveux, les dents et les yeux.

Les links font référence aux objets mais vous pouvez les assigner à des classes aussi. Quand un objet d'une classe linked (liée) est créé, tous les objets nécessaires pour remplir les links sont créés automatiquement.
Par exemple, la classe GenWallTorch a des liens (links) vers 2 autres classes. A chaque fois que vous placez une torche murale sur votre map, l'éditeur ajoute automatiquement une flamme et de la fumée, bien que ce soit 2 objets séparés.

Remarque : les classes "enfant" héritent des links de leurs parents. Si vous faites une classe "enfant" de GenWallTorch, elle aura les 2 mêmes links, même s'ils n'apparaissent pas sur la liste.

Trouvez la classe FireElemental dans l'Actor Browser, faites un clic droit dessus et choisissez Show Links. Sans surprise, notre Ai n'a pas de link. Cliquez sur Add Link. Vous devriez voir une longue liste avec tous les types de links.
Choisissez RigidAttachment. Il y a 2 autres champs dont un déjà rempli avec le nom de la classe que vous avez choisi. Il faut remplir le deuxième également, mais nous ne pouvons choisir le nom à partir d'une liste malheureusement. Cliquez dans le champ vide et tapez TorchFlame. C'est le nom de classe de l'emitter qui produit l'effet visuel d'une flamme de torche.

Remarque : les emitters sont des objets relativement complexes avec un simple but : ils créent constamment des sprites. Les sprites sont des petites images plates. Les emitters contrôlent leurs tailles, couleurs, textures, durées de vie, mouvements. On peut les utiliser pour produire des flammes, de la fumée, des étincelles, de la pluie, de la neige, etc. Tous ces effets ajoutent du spectaculaire mais ils peuvent consommer beaucoup de ressources. Utilisez-les avec modération.

Cliquez sur OK. Nous avons un link unique sur notre liste mais il n'est pas encore pleinement configuré. Sélectionnez-le et cliquez sur Edit Selected Link. Comme vous pouvez le constater, les links ont des propriétés aussi.
Choisissez Attachment > m_parentBone et cliquez sur le bouton avec les 3 points. Une liste de chaînes étranges apparaît, nous en avons besoin pour le mesh. Chaque mesh d'IA a une série de hardpoints prédéfinis auxquels les objets peuvent être attachés. Les points qui nous intéressent commencent par HP et M 1. Ils concernent les parties du corps.
Choisissez HP_LeftHand et cliquez sur OK. A partir de maintenant, chacun de vos FireElementals aura une flamme de torche attachée à sa main gauche invisible. Ne fermez pas encore la fenêtre de données link (link data).
Allez dans la propriété Link > m_name et réglez sur Flames. C'est un nom personnalisé, vous pouvez choisir n'importe quelle chaîne de caractères. Ecrivez-la quelque part car nous en aurons besoin pour écrire les scripts. Vous pouvez cliquer sur Done. Félicitations, vous avez paramétré un link !

Et pour les autres alors ? Répétez les mêmes actions mais choisissez à chaque fois un autre "hardpoint" :

- HP_RightHand
- HP_LeftHip
- HP_RightHip
- HP_RightKnee
- HP_LeftKnee
- M 1 L_Toe0
- M 1 R_Toe0
Et ce n'est pas tout. Créez 2 autres links mais utilisez cette fois FireplaceFlame au lieu de TorchFlame. Mettez m_parentBone à :
- HP_FrontWaist
- HP_Head

Il y a encore deux autres choses à faire avec ces links. Cliquez Add link encore une fois. Choisissez Vulnerability et tapez FireVulnerability dans le champ TO. Cliquez sur OK puis sur Done. FireVulnerability est un "enfant" de MetaProperty > VulnerabilityObject et il définit les sortes de dommages qu'un objet peut subir. Si on ne fait pas de link à un VulnerabilityObject pour notre FireElemental, il deviendra indestructible. Comme il est fait de feu, il devrait avoir les mêmes vulnérabilités que le feu. Bien sûr, vous pouvez définir votre propre "enfant" de VulnerabilityObject, et par exemple, faire qu'un garde lourdement protégé ait une résistance aux attaques physiques de 50 %.

En avant pour le dernier link. On avait prévu d'équiper notre IA avec sa propre source de lumière, n'est-ce pas ?
Allez dans Actor Browser puis dans Actor > WorldObj > Lights_ > FlameLight > TorchLight. Créez un "enfant" de TorchLight et nommez-le InvisibleHandTorch. Faites-le placeable.
Effacez tous les scripts et faites Scripts > bEnableInheritScripts à False (les torches sont contrôlées par des scripts mais nous n'en avons pas besoin, nous voulons seulement une bête source de lumière ressemblant à une torche.)
Changez RenderType > DrawType à DT_Sprite pour informer l'éditeur que cet objet n'a pas de mesh. Maintenant retournez dans la classe FireElemental et ouvrez encore une fois la fenêtre des links. (Clic droit, Show Links)
Ajoutez le link RigidAttachment FROM FireElemental TO InvisibleHandTorch. Mettez sa propriété m_parentBone à HP_LeftHand et m_name à Light Source (Comme dans le cas de Flames, c'est un nom personnalisé).

Encore une étape et notre IA sera prêt pour le test.

Les armes

Le développement de jeu est un art d'illusions. Saviez-vous que les will-o-wisps (Petites boules de lumières flottantes et inoffensives) des Païens de DS ne sont pas des IA ? Ce sont des ascenseurs. Si de telles choses existent, alors il n'est pas étonnant qu'on doit assigner une arme à notre FireElemental, même s'il n'en utilise pas.

Allez dans le actor class browser, allez dans Actor > WorldObj > SetDressing > AIParts > AIFakeMeleeWeapon. C'est l'objet qu'on donne à un IA quand il se bat à mains nues. C'est presque parfait mais quand l'IA touche quelque chose, cela fait des sons complètement différents de ceux de "feu".
Créez son "enfant" et appelez-le AIInaudibleFakeMeleeWeapon. Réglez sa propriété PhysicsSound > PhysSoundTag à null, et Weapon > WeaponStimulus à StimulusType_Fire. Cette fausse arme ne fera pas de son et fera des dommages de feu.

Trouvez FireElemental, faites un clic droit dessus et choisissez Edit Weapon Loadout. Sélectionnez LeftHip. Cliquez Make Local. Cliquez sur Add Item to Slot.
Vous verrez un sous-niveau de l'arborescence de la classe actor : ce sont les objets qui peuvent être placés sur le corps d'un IA.
Ils ont une seule chose en commun : la propriété Weapon > WeaponStance
Son nom est un peu trompeur car il peut s'agir aussi d'objets personnels comme des bourses, des clés (de tels objets doivent avoir le réglage WeaponStance à eWS_ITEM).
Choisissez AIInaudibleFakeMeleeWeapon (il devrait être en bas de l'arborescence). Cliquez OK et Done.

Le test

Si tout s'est bien passé, le FireElemental devrait être fonctionnel. Mettez-le quelque part sur votre essai de map. Il devrait être placé sur un point où l'on puisse l'approcher directement ou se faufiler derrière lui.

Remarque : L'éditeur vous demandera si vous voulez sauvegarder votre gamesys personnalisé. Nous avons modifié le gamesys quand nous avons ajouté des classes personnalisées, cliquez sur "Yes". L'éditeur crée des sauvegardes des gamesys antérieurs automatiquement.

Mais quelque chose manque. Le FireElemental est trop silencieux et la lumière ne change pas du tout de brillance. Si vous tuez l'IA avec des flèches à eau, il va rester immobile même mort au lieu de faire quelque chose de spectaculaire. Enlevez-le de votre map. Nous devons enrichir notre création avec quelques scripts.

Faire des scripts

Un script est une séquence de commandes. On les utilise pour mélanger de simples actions telles que allumer une lumière ou envoyer un IA en un lieu précis, avec des comportements complexes. Les scripts contiennent même des conditions qui doivent être remplies avant que les actions soient entreprises. Ce qui veut dire qu'on utilise les scripts pour apprendre aux IA comment réagir à des événements.

Ouvrez le script browser en cliquant sur Trigger Scripts dans le menu View. Sur la droite, il y a une arborescence de noms de scripts. Les noms avec des icônes de livres sont des groupes (groups). Cela n'a aucune importance quel script va dans quel groupe, mais on peut le trouver plus rapidement. Par exemple, vous pouvez créer un groupe MyScripts et y mettre tous vos scripts. Ou alors créer un groupe séparé pour chaque mission ou IA que vous faites. Un groupe appelé Test est un bon endroit pour les scripts expérimentaux que vous n'utilisez pas dans de vraies missions.

Cliquez sur New. Une fenêtre avec 6 boutons et 2 champs apparaît. Le plus petit champ contient le nom du script. Il a un nom très commun DefaultName. Changez-le en FireElementalChangeState. Vous pouvez mettre un nom différent mais comme toujours, mieux vaut mettre un nom qui décrive bien la fonction. Celui-ci sera exécuté à chaque fois que le FireElemental passe d'un état d'Ai à un autre.

Le moteur du jeu garde toutes les traces des événements importants qui apparaissent dans votre map et il détecte ceux qui remplissent une ou plusieurs conditions de scripts. Lorsque les conditions sont là, toutes les actions du script approprié sont exécutées. Votre tâche est de définir QUAND l'exécution d'un script devrait commencer et COMMENT elle doit se dérouler. Il y a donc 2 sortes de commandes : Conditions (quand / si) et actions (quoi).

Cliquez sur Condition. Comme vous pouvez le constater, il y a beaucoup de conditions disponibles. Vous ne pouvez pas en créer de nouvelles car il faudrait modifier les fichiers exécutables du jeu, mais ce n'est pas grave car il y a de nombreuses conditions prédéfinies et il y a peu de choses que vous ne pouvez pas faire (certaines ne sont pas compatibles avec le moteur…). On ne va pas les expliquer ici car il faudrait un livre entier, et leurs noms sont assez clairs pour deviner leurs fonctions.
Prenez le temps de parcourir l'arborescence pour voir les choix à votre disposition.

Trouvez le groupe AI (il devrait être tout en haut), ouvrez-le et sélectionnez When my behavior state changes to [Enum:EStateType]. Quand il y a me, cela signifie que le script de l'objet y est attaché. En général, les scripts sont toujours exécutés dans le contexte de leurs possesseurs. Vous pouvez seulement faire référence à un autre objet dans votre script lorsque l'objet auquel vous l'avez rattaché et l'autre objet sont linked ensemble (avec une exception, qui sera expliqué plus tard).
Les strings (chaînes de caractères) entre crochets comme [Enum:EStateType], sont des variables que vous devez réglées manuellement. Si vous avez déjà sélectionné la condition mentionnée au-dessus, cliquez sur OK. Elle sera ajoutée à la liste de conditions de votre script. Le string [Enum:EStateType] devrait être écrit en bleu pâle. Cliquez dessus. Une liste de valeurs correctes de cette variable devrait apparaître. Dans ce cas, chaque valeur fait référence à un des états que l'IA peut entrer. Remarquez que nous avons empêché notre FireElemental d'entrer dans l'état "glissant" (slipping state) donc si vous choisissez STATE_SLIP_AND_FALL, la condition ne sera jamais atteinte et le script n'exécutera jamais ses actions. Choisissez ANY à la place. La condition devrait être la suivante maintenant : When my behavior state changes to [ANY]. Elle sera atteinte quand l'objet changera son état à un autre.

On peut mettre plusieurs conditions mais il en faut une seule pour notre script, cliquez sur le bouton Action. Les actions sont organisées comme les conditions. Ouvrez le groupe Sounds et sélectionnez Play sound schema [SoundSchema] at my location. Cliquez sur OK, puis sur [SoundSchema], cela ouvre le catalogue de schemas sons (sound schema browser). Trouvez fire_short_burst, sélectionnez-le et réduisez le browser. [SoundSchema] devrait changer en [fire_short_burts]. Si ça ne le fait pas, cliquez dessus et choisissez fire_short_burst du catalogue de schemas encore une fois.

L'action que vous avez ajoutée va faire exactement ce qu'elle définit : elle va jouer un son schema.
Remarque : C'est un schema qu'on appelle "non-looping". Il ne va être entendu qu'une seule fois. Il y a aussi des schemas "looping". On les entend indéfiniment, à moins d'être interrompu par un événement (comme une fin de jeu) ou un script. On peut reconnaître les schemas looping facilement par leurs noms. Ils se terminent habituellement par le suffixe _lp (ceci ne concerne pas les sons d'ambiance utilisés pour la "musique" de fond dans les missions originales).

Il y a un problème avec notre script : il ne va s'exécuter qu'une seule fois.
Règle commune : si nous voulons qu'un script s'exécute plusieurs fois, il faut le dire.
Cliquez sur Action, mais cette fois-ci ouvrez Scripts et sélectionnez la dernière action de cette catégorie : Reset script conditions and actions.
Cliquez sur OK. Lorsque l'exécution du script atteint cette action, elle retournera au début du script et attendra que les conditions soient à nouveau remplies.
Remarque : La séquence des actions est importante. Si vous ajoutez Reset… avant Play [SoundSchema]… votre script n'atteindra jamais la dernière action. Vous pouvez faire un cliquer-déposer sur une action ou une condition pour changer l'ordre de la séquence. Mais vous ne pouvez pas mélanger des conditions et des actions ensemble.

Votre script a un nom, une condition et deux actions. C'est tout ce qu'il faut. Fermez la fenêtre en cliquant sur OK. Si vous voulez le modifier plus tard, il suffira de le sélectionner du browser et de cliquer sur Edit. Votre script est quasiment près à être utilisé. Nous devons l'attacher à la classe FireElemental, mais nous le ferons après avoir écrit d'autres scripts.

FireElementalAngry (Notre Ai est en colère)

Dans la première partie de ce tutoriel, nous avons dit que nous voulions plus de sons pour notre FireElemental, qu'il devienne plus bruyant et qu'il soit plus lumineux quand il entre au combat. On va s'y atteler dès maintenant.
Créez un nouveau script et nommez-le FireElementalAngry. Ajoutez la condition when linked AI [LinkFlavor] enters combat. Cliquez sur [LinkFlavor]. Nous avons déjà vu une liste ressemblante, n'est-ce pas ? Ce sont les mêmes link flavors que nous avons choisis quand nous avons attachés des flammes au mesh du FireElemental. Il y a quelques entrées que nous n'avons pas encore vues. Pour chaque nom de link, il y a sa "copie" avec une tilde (~) au début. C'est ce qu'on appelle des return links. Quand vous créez un link ordinaire d'un objet A à un objet B, un return link de B à A est créé automatiquement. Ils ne font rien tout seul mais cela donne un accès plus facile aux données à l'éditeur (et à vous).
Il y a aussi 2 types spéciaux de flavors : MYSELF et PLAYER. Ce ne sont pas vraiment des links. MYSELF pointe toujours vers le script de l'objet auquel il est attaché, et PLAYER pointe toujours vers Garrett (vous pouvez donc faire référence à lui, même s'il n'y a pas de link entre lui et votre objet). Choisissez MYSELF et cliquez sur OK. Votre condition devrait indiquer when linked AI [MYSELF] enters combat.

Ajoutez 3 actions Play sound schema [SoundSchema] at my location, et assignez-leur 3 schemas : furnaceflameburst, hit_arrowfire_lo, et furnaceflame_lp. Les deux premiers sont des schemas non-looping. Quand ils sont mixés, on peut entendre un rugissement féroce et terrifiant. Le dernier schema fait référence à un son en boucle d'une flamme de fournaise qui est plus fort qu'un feu de cheminée. Le FireElemental aura ce son jusqu'à ce qu'on lui "dise" d'arrêter.

Ajoutez l'action Set [Property] (Int/Float/Byte property) to [Value] on linked objects of [LinkFlavor], changing it over period of [Float] seconds" de la catégorie "Properties". C'est vraiment une fonction utile dans bon nombre de situations car ça vous permet de changer les propriétés des objets dynamiquement.
Nous avons déjà attaché une torche invisible à notre IA. Maintenant, nous allons changer son rayon de lumière. Notre script sera attaché au FireElemental, la torche est liée grâce à RigidAttachment.
Cliquez sur [LinkFlavor]. Choisissez RigidAttachment de la liste mais ne fermez pas la fenêtre. Il y a un champ texte en bas. Souvenez-vous, nous avons mis le nom Light Source à m_name quand nous avons créé des links entre le FireElemental et la torche.
Si nous ne le faisions pas, il n'y aurait pas de communication entre le link RigidAttachment et les autres, et notre script appliquerait le changement de propriété à tous les objets qui sont liés grâce au RigidAttachment (dont les "flames").
Cliquez dans le champ texte et écrivez Light Source (ou la valeur que vous avez assignée à m_name, si vous en avez choisi une autre). Cliquez sur OK. Au lieu de [LinkFlavor], il devrait y avoir [RigidAttachment:Light Source]. Mettez les autres valeurs de cette action à LighShape.
LightRadius, 64 et 1.0, respectivement. 64 est deux fois la portée habituelle de la lumière d'une torche et nous voulons qu'elle change sur une durée d'une seconde, car le changement instantané aurait un mauvais effet.
Ajoutez Reset conditions and actions, surtout mettez-le à la fin du script. Cliquez sur OK pour retourner sur le script browser.

FireElementalEndCombat

Ce script est l'inverse de FireElementalAngry. Essayez de comprendre tout seul comment il fonctionne.

Conditions :
· Si AI[MYSELF] est linké, il sort du combat.
· Chercher si [HealthState] est [Equal_To] [HealthState_Alive] sur tous les objets linkés de [MYSELF].

Actions :
· Arrêter de jouer [furnaceflame_lp] sur mon objet
· Jouer le schema son [furnaceflame_ext] sur mon emplacement.
· Mettre [LightShape.LightRadius] (Propriété de Int/float/Byte) à [32] sur les objets linkés de [RigidAttachment:Light Source], le faire changer sur une durée de [5.00] secondes.
· Redémarrer les conditions de script et les actions. (Reset script conditions and actions)

Vous vous demandez peut-être pourquoi on vérifie si notre IA est vivant (alive) : c'est parce que la mort de l'IA est une condition spéciale de sortie du combat et nous voulons nous en occuper à part entière.

FireElementalKilled

Dans Dark Project, quand un élément de feu est atteint par une flèche à eau, les deux objets disparaissent comme si le feu s'était soudainement éteint.
Nous allons éteindre notre élément également puis nous détruirons son corps invisible pour qu'il ne se transforme pas en obstacle gênant pour le joueur et les autres IA.

Conditions :
· Si mon état de santé (Health State) change de [ANY] à [HealthState_Dead].

Actions :
· Mettre [LightShape.LightRadius] (propriété de Int/float/Byte) à [0] sur les objets linkés de [RigidAttachment:Light Source], le changer sur une durée de [2.0] secondes.
· Finir de jouer [fireplace_lp] rapidement (ne pas finir la boucle du son en cours) sur mon objet.
· Finir de jouer [furnaceflame_lp] rapidement (ne pas finir la boucle du son en cours) sur mon objet.
· Jouer le son schema [furnaceflame_ext] sur mon emplacement.
· Retarder le GAME de [1.00] secondes. (Delay)
· Mettre le [EmitterSpawnState] à [EDS_NoNewPartices] sur les objets linkés de [RigidAttachment:Flames].
· Retarder le GAME de [2.00] secondes. (Delay)
· Détruire les objets linkés [RigidAttachment].
· Détruire les objets linkés [MYSELF].

Les deux dernières actions détruisent l'élément, les flammes et la torche. Nous n'avons pas besoin d'indiquer le nom du link car nous voulons détruire tous les objets liés ("linkés") à RigidAttachment (torche et flammes). Quand on détruit l'élément de feu, tous ses sons sont arrêtés ; le script doit se mettre en attente jusqu'à ce que le schema furnace_ext se termine (d'où les actions Delay).

Quand on met le EmitterSpawnState à EDS_NoNewParticles, les flammes arrêtent de produire de nouveaux sprites mais les anciens sprites ne s'estomperont qu'à la fin de leur durée de vie.
Nous n'avons pas besoin de rajouter "Reset script conditions and actions" car chaque élément de feu ne meure qu'une fois.

FireElementalHit

C'est le dernier script que nous avons besoin de faire. La partie la plus difficile est celle du delay (retardement). Cela signifie que le script doit attendre 2 secondes avant de commencer. Cela évite au jeu de jouer des copies multiples de schema quand l'élément de feu est touché par plusieurs ennemis à la fois.

Conditions :
· Si je prends des dommages (health damage) [Greater_Than] [0.00] de [ANY] à [ANY].

Actions :
· Jouer le son schema [furnaceflameburst] à mon emplacement.
· Jouer le son schema [hit_arrowfire_lo] à mon emplacement.
· Faire un delay GAME de [2.00] secondes.
· Redémarrer les conditions de script et actions. (Reset scripts conditions and actions)

C'est tout ou presque. Retournez sur le script browser, cliquez OK et allez vous preparer une tasse de café.
Vous devez cliquer su OK à chaque fois que vous faites ou changez un script, pour que l'éditeur remarque le changement. Evitez de fermer le browser dans d'autres cas, minimisez-le à la place.
Lorsque l'éditeur a terminé, trouvez le FireElemental dans le actor class browser (Catalogue de la classe actor) et ajoutez la propriété Scripts > TriggerScripts à ses propriétés.
Cliquez sur Add puis cliquez sur le [+] à côté du TriggerScripts puis sur none et enfin sur le bouton avec les 3 points. Sélectionnez un des scripts de votre FireElemental dans votre Script Browser puis cliquez sur Use dans l'Actor Class Browser. Le None devrait changer et avoir le nom du script sélectionné. Vous venez d'attacher ce script à votre classe. Faites de même pour les autres scripts que vous avez réalisés.

Notre FireElemental est presque fini. Mettez-le dans votre map-test.
Ajoutez aussi quelques flèches à eau. Enervez votre IA, fuyez-le, tuez-le avec les flèches à eau. Ceci vous permettra de savoir si vos scripts fonctionnent correctement.

Les sons schemas

Un petit détail ennuyeux subsiste : les volumes des sons sont mal définis. On entend les flammes d'un feu de cheminée quand on est très près, mais les explosions de flammes sont si bruyantes qu'on peut les entendre de n'importe quel endroit de la map. C'est la faute du schema. Une de leurs fonctions est de délimiter la distance à partir de laquelle un son donné peut être audible.

Ouvrez un explorateur de documents, allez dans le répertoire où est installé TDS (le répertoire pour l'éditeur). Ouvrez CONTENT\T3\Sounds\. Les schemas que vous voyez dans le schema browser sont en fait des fichiers textes et ils sont tous ici.
Ouvrez le répertoire schemas_sfx et trouvez fireplace_lp.sch.
Vous reconnaissez le nom ? C'est le son schema de notre cheminée.
Faites une copie du fichier et nommez-la fireplace_alt_lp.sch. L'autre schema dont nous avons besoin est fire_short_burst.sch. Créez également une copie que vous nommerez fire_short_burst_alt.sch.
Maintenant ouvrez ces deux copies avec n'importe quel éditeur de texte et trouvez une ligne appelée radii. Il y a deux paramètres séparés avec des virgules :

· 1 and 25 in case of fireplace_alt_lp,
· 1 and 150 in case of fire_short_burst_alt.

Le premier paramètre de chaque ligne est la distance maximum que chaque son doit jouer au volume maximum. Le second paramètre est la distance totale du son.
Remarquez que le fire burst (explosion de feu) a une distance six fois plus longue que le fireplace (feu de cheminée).
Changez 25 et 150 à 50. Sauvegardez les fichiers (assurez-vous qu'ils gardent l'extension en .sch) et retournez sur T3Ed.
Ouvrez le sound schema browser et regardez si vos nouveaux schemas sont sur la liste. S'ils n'y sont pas, sauvegardez tout et redémarrez l'éditeur.

La dernière étape est de remettre les anciens schemas avec leurs versions modifiées dans les propriétés et les scripts de votre FireElemental. Il faut changer la propriété Sounds > SoundAmbient et les deux scripts : FireElementalChangeState et FireElementalKilled.

A suivre

Ce tutorial prend fin ici, mais vous pouvez toujours encore mieux améliorer l'Ai. Par exemple, vous pouvez utiliser d'autres emitters et d'autres sons pour créer différentes sortes d'Elementals. Ou vous pouvez faire apparaître une flèche de feu quand l'Ai meurt.
Vous pouvez effacer les links entre les jambes et les flammes du FireElemental, on aura l'impression qu'il flotte à un mètre du sol. Vous pouvez également modifier les paramètres de combat et d'autres propriétés : peut-être qu'une seule flèche à eau suffira pour tuer le monstre ?
Ne craignez pas d'expérimenter des choses, c'est le meilleur moyen d'apprendre comment cela fonctionne.

 

Tutoriels sur T3ed
t3ed.tutoriel.free.fr