Creation Kit : Scripts introduction
Le but de cet article est de vous familiariser avec des exemples au système de scripting du Creation Kit.
Il n'abordera pas les différents éditeurs existants pour les scripts ainsi que leurs spécificités ainsi que les fragments de scripts, spécifiques aux dialogues et STAGES de quêtes.
Un script, c'est quoi?
Un script est un ensemble d'instructions, de fonctions, de propriétés et de variables destiné à ajouter, modifier certaines fonctionnalités du jeu. Ils sont attachés à un referencealias, un actor ou tout autre objet les acceptant.
Pour réaliser un script, nous éditons un fichier appelé source. Ce fichier source est celui que nous modifions à l'aide de nos petites mains à l'aide de l'éditeur de notre choix. Il est stocké dans DATA\scripts\source et possède l'extension *.psc.
Ce fichier source n'est pas utilisé tel quel par le jeu. Il a besoin d'un script compilé. Quand nous sauvegardons un script, l'éditeur le compile et génère un fichier compilé. C'est ce fichier qui sera chargé par le jeu. Il a une extension en *.pex et sera stocké dans DATA|scripts. C'est le seul fichier nécessaire pour faire fonctionner un mod. Par contre le fichier source est nécessaire pour l'éditer.
Les instructions IMPORT
Celles-ci permettent d'aérer un peu le code source de votre script en vous évitant des saisies supplémentaires. Par exemple :
miscobject property alphaproperty auto miscobject property alphaproperty auto event oninit() int toto = game.getplayer().getitemcount(alphaproperty) int toto1 = game.getplayer().getitemcount(alphaproperty1) endevent
deviendra :
miscobject property alphaproperty auto miscobject property alphaproperty auto Import game event oninit() int toto = getplayer().getitemcount(alphaproperty) int toto1 = getplayer().getitemcount(alphaproperty1) endevent
Le fait d'utiliser la fonction import dans la source n'agit en rien sur le fichier compilé. En effet, le compilateur sera celui qui importera l'ensemble de fonction game pour réalisé le fichier compilé. En clair, la fonction import n'a aucune influence sur le fichier compilé. Elle sert juste à améliorer la lisibilité du script et diminuer la saisie.
Les objets que nous importons via la commande import ne sont rien d'autre que des fichiers *.pex (scripts compilé) contenant les instruction qui s'y rapportent.
2. Optimisation - remplacement des getplayer()
Les fonctions getplayer() sont des fonctions assez lentes si on les compare à l'utilisation de propriétés ou de variables. Nous allons optimiser un peu notre exemple précédent, nous obtenons :
miscobject property alphaproperty auto miscobject property alphaproperty auto actor property playerref auto Import game event oninit() int toto = playerref.getitemcount(alphaproperty) int toto1 = playerref.getitemcount(alphaproperty1) endevent
Le script réalise exactement la même fonction mais de manière plus rapide. Il ne faut cependant pas oublier de "remplir" la propriété.
3. Le nommage des propriétés
Dans l'exemple précédent, nous avons nommé notre propriété de type actor playerref. Le choix n'a pas été anodin car il permet de remplir de manière automatique la propriété du script. Il suffit d'ouvrir la fenêtre des propriétés et de cliquer sur "auto-fill all". Une fenêtre apparaitra précisant le nombre de propriétés remplies. Il suffit donc de nommer la propriété de la même manière que l'objet du même type que l'on veut y mettre dedans pour qu le CK remplisse les champs tout seul après. Pratique quand on travaille avec beaucoup de propriétés.
4. Les définitions de variables
miscobject property alphaproperty auto miscobject property alphaproperty auto actor property playerref auto Import game event oninit() int toto = playerref.getitemcount(alphaproperty) int toto1 = playerref.getitemcount(alphaproperty1) endevent
Le code précédant définit deux variables de type int appelées toto et toto1. Cependant, on remarquera avec ce type de déclaration que la variable n'est pas partagée par toutes les instances du même script. Pour pouvoir utiliser ces variables en dehors de l'event oninit(), il faut les déclarer avant.
Le code devient donc :
miscobject property alphaproperty auto miscobject property alphaproperty auto actor property playerref auto int toto int toto1 Import game event oninit() toto = playerref.getitemcount(alphaproperty) toto1 = playerref.getitemcount(alphaproperty1) endevent
Les variables toto et toto1 peuvent donc être utilisées par tous les event de ce script mais toujours pas par les autres.
5. Variables globales
Pour le besoin de l'exemple, nous avons besoin de partager une variable avec plusieurs scripts, toto2. Il faut d'abord créer la variable globale dans le CK (dans la catégorie miscellaneous). Ensuite il faudra la déclarer en tant que propriété.
miscobject property alphaproperty auto miscobject property alphaproperty auto actor property playerref auto globalvariable property toto2 auto int toto int toto1 Import game event oninit() if toto2 == 1 toto = playerref.getitemcount(alphaproperty) toto1 = playerref.getitemcount(alphaproperty1) endif endevent
Ce script refusera de compiler car toto2 n'est pas une variable à proprement parler mais un objet nommé contenant une valeur avec une ID. Il existe des fonctions pour lire ou écrire des valeurs dans ces variables globales. Le script devient :
actor property playerref auto miscobject property alphaproperty auto miscobject property alphaproperty auto globalvariable property toto2 auto int toto int toto1 Import game event oninit() if toto2.getvalueint() == 1 toto = playerref.getitemcount(alphaproperty) toto1 = playerref.getitemcount(alphaproperty1) endif endevent
En clair, si la valeur de la variable toto2 est égale à 1 au moment de l'initialisation du script, les variables de ce script toto et toto1 seront remplies avec le nombre d'objets alphaproperty et alphaproperty1 détenus par le joueur.
6. Les events
Les events sont des évènement qui déclencheront la partie de script comprise entre le mot "event" et le "endevent". Par exemple, un event onitemadded() se déclenchera dès que l'objet sur lequel est attaché le script reçoit un objet. Cependant, si on tape ceci dans un script ça ne marchera pas :
Event onitemadded(gold001) debug.notification("Chouette de l'or!!!") endevent
Il faut mettre :
miscobject property gold001 auto ;propriété pour faire comprendre au script ce qu'est gold001
Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer) if akbaseitem == gold001 debug.notification("Chouette " + aiitemcount + " pièces d'or!!!") endif endevent
akbaseitem est le form de l'objet ajouté,
aiitemcount est le nombre d'objets ajouté
akitemreference est la référence de l'objet s'il en possède une
akSourcecontainer est la provenance de l'objet. None si ramassé par terre.
Donc ce bout de script se déclenche à l'ajout d'un objet et se termine à endevent. On peut placer plusieurs events dans un même script.
La liste des events et leur syntaxe est donnée ICI
Une variable définie dans un event ne sera valable que pour cet event.
Il faut veiller à la compatibilité entre l'event utilisé et ce qui suit le extends de la première ligne du script. En cas d'incompatibilité, l'event sera ignoré et le script ne fonctionnera pas.
Un même event peut être lancé plusieurs fois en même temps si l'évènement se reproduit alors que le script n'est pas terminé. Ce sont les joies du multithreading. Il faut donc prévoir cette éventualité pour certains scripts (comme ceux qui lancent des animations par exemple).
7. Les states ou l'art de proposer plusieurs modes de fonctionnement pour un script.
Qu'est ce qu'un state? C'est tout simplement un état.
Une explication : Prenons l'exemple d'un script ayant un état "attente" et un état "occupé". Il y aura dans l'état attente un event correspondant au fait que l'on active l'objet. Cet event déclenchera une série d'instructions. Si on active une deuxième fois l'objet, la série d'instruction sera lancée une nouvelle fois même si la première n'est pas finie. Pour éviter cela, on va basculer vers l'état "occupé" qui ne contiendra pas l'event d'activation donc ne lancera pas d'instructions. A la fin de la série d'instruction, il est nécessaire de revenir à l'état "attente" pour permettre une nouvelle activation.
En gros, l'architecture de ce script d'activateur ressemblerait à ça :
Nom du script et type (scriptname tartanpion extends objectreference par exemple)
Déclaration des propriétés et variables
event oninit() si besoin. fin d'event oninit()
Etat "Attente" par défaut - event onactivate() (portion de script se déclenchant à l'activation) - on passe en état "occupé" - on exécute les instructions - on repasse en état "attente" - fin d'event fin d'état "attente"
Etat "Occupé" Il n'y a pas d'event onactivate() dans cette portion donc il ne se passe rien en cas de double activation. Fin d'état "occupé"
On peut aussi utiliser les états pour compter un nombre d'activations dans un temps donné et ainsi permettre plusieurs fonctions différentes à l'activation d'un même objet.
Exemple :
event oninit() self.blockactivation(true) ;je bloque l'activation normale de l'objet pour la remplacer par celle du script endevent
auto state dep; je déclare l'état "attente" comme étant l'état par défaut.
Event OnCombatStateChanged(Actor akTarget, int aeCombatState) ;un event n'ayant rien à voir pour montrer qu'il peut y avoir autant d'events que l'on veut dans un state ;Il faut se souvenir que cet event ne se lancera que dans l'état "dep" car il n'est pas présent dans l'état "occupé". S'il doit être déclenché dans l'état "oneclick", il faut faire un copier-coller de cet event dans l'autre état. endevent
Event OnActivate(ObjectReference akActionRef) gotostate("oneclick") ; j'ai effectué mon premier clic donc à partir de maintenant, pour le temps choisi en dessous (0.5 secondes, je change d'état et compte les clics. Le changement d'état m'évite aussi une deuxième activation. clic = 1 ;j'ai appuyé une fois utility.wait(0.5) ;j'attend 0.5 secondes. Pendant ce temps c'est l'event de l'état "occupé" qui compte les clics. A la fin de la tempo, je regarde combien il y a eu de clics et déclenche le mode de fonctionnement désiré. if clic == 1 ;premier mode de fonctionnement elseif clic == 2 ;deuxième mode de fonctionnement else ;troisième mode de fonctionnement - ne pas mettre de condition sur le dernier au cas où le joueur fait plus de clics que nécessaire. endif clic = 0 gotostate("dep") ;j'ai fini, je revient donc à l'état "dep" endevent endstate ;fin d'état "dep"
state oneclick ; début d'état "oneclick" Event OnActivate(ObjectReference akActionRef) ;c'est cet event qui est déclenché dans l'état oneclick clic = clic + 1 ;je compte mes clics et rien d'autre endevent endstate
Attention, ne pas confondre un gotostate() (changement d'état) avec un goto style language basic.
Dans le cas d'un gotostate(), les instructions qui sont présentes dans l'event après cette instruction sont exécutées jusqu'à la fin de l'event.
Dans l'exemple ci-dessus, on change d'état mais on continue de dérouler le bout de script de l'event onactivate de l'état "dep".