

-----------------------------------

Mike Werewolf

Jeudi 28 Avril 2005 20:52


06. Les bases de la programmation (suite)

-----------------------------------

LES EXPRESSIONS

Eh oui, ça faisait longtemps ! ^^
Attention, y a pas mal de choses à dire (et donc à lire) !!!
C'est très long, mais pratiquement indispensable pour créer des persos.

Présentation
Si les states sont le moteur des personnages, les expressions sont indéniablement le carburant. Les expressions alimentent les fonctions de Mugen, qu'ils s'agissent des triggers ou des sctrl. Soyons clair : c'est de votre capacité à les créer que dépend essentiellement votre capacité à créer des mouvements.

Créer des expressions est relativement simple. Tout dépend de votre compatibilité avec les mathématiques, en fait. De même, ce n'est pas parce qu'on a à faire à des expressions très longues qu'elles sont forcément très complexes.

Mugen est plutôt complet à ce niveau, ce qui permet une très grande souplesse dans leur construction, et autorise pas mal de possibilités. C'est pourquoi il y a pas mal de choses à dire à ce sujet. Il n'est pas vital de tout connaître, mais l'idéal est quand même de connaître certaines bases mathématiques, plus les éléments principaux des expressions (ou sinon, d'avoir une doc sur les expressions à disposition).

Notez quand même que cette partie ne fait globalement que reprendre la doc "exp.txt" que j'ai traduite, en la présentant de façon différente. N'hésitez donc pas à consulter cette doc pour compléter vos connaissances, ou peut-être éclaircir un point qui vous semblerait obscur.

Petite définition
Mais qu'est-ce qu'une expression ? En gros, c'est tout ce qu'on peut évaluer comme valeur numérique. Ce "tout" peut revêtir plusieurs formes : un nombre unique - c'est la forme la plus simple - ou une série de plusieurs "mini-expressions" imbriquées, en passant par des opérations logiques, arithmétiques ou de comparaison, ou encore par des fonctions trigger.

La "forme" numérique de l'expression fait qu'elle ne peut pas être utilisée pour tous les paramètres. Il convient donc de ne pas confondre "valeur" et "expression". Une expression est une valeur, mais une valeur n'est pas forcément une expression. De façon globale, on ne peut pas utiliser d'expressions pour les paramètres dont la valeur est une chaîne de caractères.

Ainsi, on ne peut mettre :
trigger1 = command = IfElse(Time=1,"saut","vol")
Car la valeur du trigger "command" doit être une chaîne de caractère. On ne peut donc pas mettre d'expression ici. Idem pour, par exemple, les paramètres "physics" ou "movetype" du StateDef, ou encore pour les paramètres "hitflag" ou "attr" d'un HitDef.

Il existe également quelques triggers anciens qui, bien qu'ils prennent des paramètres numériques, n'acceptent pas d'expression (ou réagissent mal). Nous les verrons à la fin.

I - LES OPERATIONS

Les notions d'entiers et de décimaux (ou flottants)
Mugen fait une distinction relativement rigoureuse entre les nombres entiers (sans partie décimale) et les nombres décimaux (comportant une partie entière et une partie décimale). Les chiffres suivants sont des entiers : 1, 10, 2345. Les chiffres suivants sont des flottants : 1.0, 3.5, 34542.004.

La différence entre 10.0 et 10 peut sembler négigeable, mais ceci peut avoir une grande incidence sur la valeur de l'expression, notamment si pour les paramètres qui attendent une valeur d'un certain type (par ex. le paramètre "anim" du StateDef ne prend pas de valeur décimale ; anim = 10 est correct, mais anim = 10.0 ne l'est pas). De même, le résultat de certaines opérations peut être complètement différent selon que l'on utilise des arguments flottants ou entiers. Ainsi :
7.0/2.0=3.5
En revanche :
7/2=3

Les opérateurs arithmétiques
Les opérations arithmétiques, ce sont celles que l'on emploie traditionnellement : addition, soustraction, multiplication et division. Leurs "symboles" respectifs sont : + - * /. Il en existe une cinquième que nous verrons ensuite. Pour ces opérations "classiques", les règles de priorité s'appliquent d'elles-mêmes : la division et la multiplication sont prioritaires sur l'addition et la soustraction (ex : 8-2*3 donne 2 et non 18 ; 8-4/2 donne 6 et non 2).

On appelle "arguments" les nombres (ou expressions) utilisés dans les opérations. Ainsi, dans 8-2*3, les nombres 2 et 3 sont les arguments de la multiplication ; et la soustraction, elle, a pour arguments le nombre 8 et l'expression (2*3).

On peut également rétablir des règles de priorité en utilisant des parenthèses : 8-2*3=2, mais (8-2)*3=18. On met autant de paires de parenthèses que nécessaire (on ne passe pas par des crochets ou des accolades) : ((8-2)*3)/2=9.

Le type de résultat, entier ou flottant, dépend du types des arguments utilisés. Si les deux arguments ont le même type, alors le résultat aura ce type. Si les deux arguments ont des types différents, c'est le type décimal qui prend le dessus (l'argument de type entier est "converti" en flottant avant que l'opération ne soit évaluée).

Le cinquième opérateur arithmétique est l'opérateur modal, qui a pour symbole %. Cet opérateur retourne le reste d'une division, et ne peut donc retourner qu'une valeur entière. Ainsi, 8%5=3 et 10%3=1. On s'en sert principalement avec le trigger "Time", pour déclencher un sctrl tous les x ticks. Ex :  trigger1=Time%10=0 se déclenchera tous les 10 ticks. On ne peut utiliser cet opérateur qu'avec des nombres entiers. Tout comme la division et la multiplication, le modal est prioritaire sur l'addition et la soustraction.

Les opérateurs de comparaison
Le résultat d'une comparaison est toujours un entier, qui ne peut être que 0 (si la comparaison est fausse) ou 1 si la comparaison est vraie (le résultate est un entier dans les deux cas). Les opérateurs de comparaison sont : < > = != >= <= (inférieur strict, supérieur strict, égal, différent, supérieur ou égal, inférieur ou égal).

Si les deux arguments n'ont pas le même type (entier ou flottant), alors l'argument de type entier sera "converti" en décimal avant la comparaison (mais le résultat reste un entier dans tous les cas : 1.0 = 1.0 donne 1 et non 1.0).

Quelques exemples :
1=(2-1) donne 1
3=(4*2) donne 0
3<(2+1) donne 0
3<(3+1) donne 1
3>(2+1) donne 0
4>(2+1) donne 1
3!=(6/2) donne 0
3!=(3+1) donne 1
3<=(2+1) donne 1
3>=(2+1) donne 1
3<=(1+1) donne 0
3>=(4+0) donne 0

Les opérateurs d'égalité (= et !=) sont parfois utilisés avec les intervalles (voir plus loin).

Les opérateurs logiques
Les opérateurs logiques permettent de combiner plusieurs opérations. En général, on s'en sert pour combiner plusieurs comparaisons. Le résultat d'une opération logique, comme pour les opérations de comparaison, ne peut être que de 1 ou 0 (entier). Il existe trois opérateurs principaux :

&& : AND (ET).
L'expression ne peut valoir 1 que si et seulement si les deux arguments du AND sont différents de 0. Ainsi :
(1=1) && (2<4) donne 1 car l'argument 1=1 est vrai et donne donc 1, et le second argument 2<4 est également vrai et vaut aussi 1, ce qui fait que l'expression devient 1 && 1. Les deux arguments sont différents de 0 donc l'expression donnera 1.

Si au moins un des arguments vaut 0, l'expression vaudra 0.

|| : OR (OU).
L'expression ne peut valoir 0 que si et seulement si les deux arguments du OR valent O. Ainsi : (1<1) || (2>4) donne 0 car l'argument 1<1 est faux et vaut donc 0. Le second argument 2>4 est également faux et vaut donc aussi 0. Les deux éléments valant 0, l'expression se résume donc à 0 || 0 et vaut donc 0.

Si au moins un des arguments vaut 1, l'expression vaudra 1.

^^ : XOR (OU exclusif).
L'expression ne peut valoir 1 que si un et un seul des arguments vaut 1. Ainsi : (1=1) ^^ (2<1) donnera 1 car le premier argument 1=1 est vrai et vaut 1, et le second argument 2<1 est faux et vaut donc 0. L'expression devient donc 1 ^^ 0 et vaut donc 1.

Si les deux arguments sont différents de 0, ou si les deux arguments valent 0, dans ce cas, l'expression vaudra 0.

! : NOT (Différent)
Cet opérateur ne prend qu'un seul argument : !x. Il retourne 0 si x est différent de 0 et 1 si x vaut 0. On s'en sert couramment à la place de "= 0". En effet, les deux expressions suivantes sont identiques :
trigger1 = NumHelper = 0
trigger1 = !NumHelper

Si NumHelper vaut 0, dans le premier trigger, l'égalité NumHelper = 0 est vrai, donc trigger1 = 1. Dans le second trigger, comme NumHelper vaut 0, alors !0 vaut 1, et là aussi, trigger1 = 1.
Si NumHelper vaut 1, dans le premier trigger, l'égalité NumHelper = 0 est fausse, donc trigger1 = 0. Dans le second trigger, comme NumHelper est différent de zéro, !NumHelper = 0, donc là aussi, trigger1 = 0.

Notions diverses (intervalles, puissances...)
* Les intervalles : On ne les utilise qu'avec les comparateurs d'égalité (= ou !=). On utilise deux symboles pour définir un intervalle : les parenthèses (,) et les crochets Les puissances : On utilise le symbole ** pour indiquer la mise en exposant. Ainsi, 2**2 donne 4 et 2**3 donne 8. Les arguments utilisés ne peuvent être que des nombres positifs. Si l'un des arguments est un décimal, les deux sont convertis en décimaux.

* Les priorités : On a vu plus haut que la multiplication et la division sont prioritaire sur l'addition et la soustraction, mais il existe d'autres priorité entre les opérateurs (notamment pour les opérateurs de comparaison et les opérateurs logiques). Voici la liste des priorités (en tête de liste, les opérateurs qui sont prioritaires, et en bas, ceux qui sont traités en dernier) :
! ~ -  (Opérateurs unitaires)
**
* / %
+ -
> >= < <=
= != intervalles
:=
&
^
|
&&
^^
||
Si deux opérateurs ont la même priorité, ils seront traités dans leur ordre d'apparition (lecture de la gauche vers la droite). Notez quand même que souvent, l'ordre dans lequel on traite les opérations dont les opérateurs ont la même priorité n'a pas d'incidence sur le résultat. Ex : 3*4/2. Si on traite la multiplication en premier, on obtient :
3*4/2 = 12/2 = 6
Et si on traite la division en premier, on obtient :
3*4/2 = 3*2 = 6
La plupart du temps, l'ordre de priorité est plutôt intuitif. Si vous avez un doute, n'hésitez pas à utiliser des parenthèses pour "forcer" une priorité.

* Les parenthèses : Elles servent à donner une priorité supérieure à des opérations. Les opérations placées en priorité sont traitées en priorité par rapport aux autres :
2**3+2*2 = 8+2*2 = 8+4 = 12
2**(3+2)*2 = 2**5*2 = 32*2 = 64
2**(3+2*2) = 2**(3+4) = 2**7 = 128
Mettre des parenthèses n'a pas toujours d'incidence sur le résultat :
2**3+(2*2) = 2**3+4 = 8+4 = 12
Ici, la seconde partie de l'expression devient prioritaire (calculée avant la puissance) mais le résultat est identique, qu'il y ait des parenthèses ou non.
Si l'on imbrique des parenthèses, ce sont les plus "profondes" qui seront calculées en premier (en gras, les parenthèses qui sont traitées :
((2+(3-(1+1)/2))/4)**2 =
((2+(3-2/2))/4)**2 =
((2+(3-1))/4)**2 =
((2+2)/4)**2 =
(4/4)**2 =
1**2 =
1

Les autres opérateurs
Il existe d'autres opérateurs que ceux présentés. Je n'ai détaillé que les plus courants.

Cependant, il en existe d'autres. En voici un résumé succinct.

* Les opérateurs binaires
Ils reposent sur des nombres binaires (composés exclusivement de 1 et de 0). Ils sont extrêmement peu utilisés, et je vous renvoie au exp.txt pour plus de détails. Opérateurs : & | ^ ~

* L'opérateur d'assignation de variable
Permet d'assigner une valeur à une variable dans une expression. D'après la doc d'Elecbyte, cette assignation peut présenter une légère différence par rapport au sctrl VarSet.

Les valeurs incorrectes
Il existe un certain nombre de cas où les expressions retournent une valeur incorrecte. L'exemple le plus parlant est certainement la division par 0. Tout le monde sait qu'il est impossible de diviser par 0. Alors, que se passe-t-il quand Mugen est amené à faire une telle opération ?

Eh bien Mugen ne plante pas (fort heureusement !), mais donne à l'expression ce qu'on appelle une "valeur SFalse". Les SFalse sont des entiers. En gros, l'expression, quelle qu'elle soit, qui retourne un SFalse donne un chiffre entier qui ne correspond strictement à rien, ou qui vaut 0. Et en gros, toute expression utilisant un SFalse donnera un SFalse en résultat, même si l'expression est correctement construite.
4 + (1/0)*2 =
4 + SFalse*2 =
4 + SFalse =
SFalse

Quelques cas retournant un SFalse :
* La division par 0, donc.
* La redirection de trigger sur un helper si aucun helper n'existe (dans ce cas, le SFalse vaut 0).
* L'utilisation de valeurs entières sur des paramètres utilisant des valeurs flottantes (pas l'inverse).
* Les cas référencés dans la doc sur les triggers.

II - UTILISATION DES TRIGGERS DANS LES EXPRESSIONS

Le principe
Vous l'avez peut-être constaté si vous avez survolé la doc des triggers : la plupart des fonctions triggers retournent une valeur numérique. Et c'est là que les expressions prennent toute leur profondeur : il est possible d'utiliser ces triggers dans les expressions, pour former des expressions dont la valeur varie selon la valeur de ce trigger. D'ailleurs, les triggers eux-mêmes sont des expressions.

Ainsi, par exemple :
Time = Les exceptions
Comme vu au tout début, on ne peut pas utiliser d'expression pour les paramètres ou les triggers qui ne prennent pas de valeurs numériques. Ainsi :
trigger1 = command = IfElse(var(1)=1,"saut","vol")
attr = StateType, NA
sont des constructions incorrectes.

Voici les triggers qui prennent des valeurs non numériques :
Command, AuthorName, P1Name, P2Name, P3Name, P4Name, StateType, P2StateType, MoveType, P2MoveType.

Il y a également certains triggers qui prennent des valeurs numériques mais qui n'acceptent pas d'expression. Par exemple, mettre :
trigger1 = AnimElem = 5 +4
est une erreur, car Mugen ne va pas traiter AnimElem = 9, mais (AnimElem=5)+4

Voici les triggers qui souffrent de ce problème :
AnimElem (remplacé par AnimElemTime), TimeMod (remplacé par l'opérateur %), ProjHit, ProjContact, ProjGuarded (remplacés par ProjHitTime), ProjContactTime, et ProjGuardedTime.

Quelques utilisations particulières
On peut quand même utiliser ces triggers de façon différente. Par exemple, on peut imaginer ce state :
value = 100+(command = "HoldFwd)+2*(command="HoldBack")
Le paramètre "value" vaudra 100 si aucune commande n'est activée, 101 si on appuie sur "avant" et 102 si on appuie sur "arrière".

III - IMBRICATION D'EXPRESSIONS ENTRE ELLES

En fait, partant du principe qu'une expression est une valeur numérique, c'est quelque chose que l'on a déjà vu. Cependant, on va essayer de préciser.

Il est en effet possible de coupler certaines expressions. Ca ralonge l'expression, mais permet d'obtenir plus de valeurs différentes, voire de limiter le code à un seul sctrl plutôt que d'en mettre plusieurs pour obtenir le même résultat.

Un exemple simple :
[State 1000]
type = ChangeState
trigger1 = command = "AB" || command = "AC"
value = 1000+10*(command = "AB")+20*(command = "AC")

est une version raccourcie de ceci :
[State 1000]
type = ChangeState
trigger1 = command = "AB"
value = 1010

[State 1000]
type = ChangeState
trigger1 = command = "AC"
value = 1020

Mais on peut trouver bien plus complexe :
[State 18100, Pal]
type = PalFX
trigger1 = anim != 18000
trigger1 = var(3)
time = 1
add = 100+(100*(anim=18002)), -30*(anim=18002)-100*(anim=18001), -100-(100*(anim=18003))

La valeur de add sera ainsi :
* si anim=18001, add = 100, -100, -100
* si anim=18002, add = 200, -30, -100
* si anim=18003, add = 100, 0, -200

Ceci permet d'appliquer 3 couleurs de PalFX différents, et ce avec un seul sctrl. Ca permet aussi d'éviter l'utilisation d'un trigger IfElse, parfois lourd. Le principe se résume ainsi :
valeur = valeur de base + x*(condition1) + y*(condition2)...

Exemple : si on veut changer de state selon la direction appuyée, selon ce principe :
pas de direction = state 40 : ça sera notre valeur de base
direction haut = state 41 : condition 1
direction bas = state 42 : condition 2
direction avant = state 43 : condition 3
direction arrière = state 44 : condition 4

Ca nous donne un sctrl ChangeState, dont le paramètre "value" se présente ainsi :
value = 40+(command="holdup")+2*(command="holddown")+3*(command="holdfwd")+4*(command="holdback")

Pour comparaison, voici la même chose mais en passant par un IfElse :
value = IfElse(command="holdup",41,IfElse(command="holddown",42,IfElse(command="holdfwd",43,IfElse(command="holdback",44,40))))


IV - QUELQUES PETITES FICELLES SUR L'UTILISATION DES EXPRESSIONS

Simplifier les expressions - quelques pistes
Il n'y a pas vraiment de secrets, c'est à force de manipuler les triggers et les opérateurs que l'on finit par trouver certaines combines (soit en planchant dessus, soit par hasard). Cependant, il y a quelques petites choses que l'on peut facilement simplifier.

Par exemple, au départ, on a souvent tendance à recourir au IfElse, dès qu'on veut faire changer la valeur d'un paramètre en fonction de la valeur d'un trigger. Avec l'expérience, on s'aperçoit que les constructions sans IfElse, mais en recourant aux imbrications d'expressions (telles que présentées avant) sont souvent plus lisibles (on n'est jamais à l'abri d'oublier une parenthèses en imbriquant des IfElse...) et plus simples. Ainsi, on remplacera avantageusement :
anim = IfElse(ctrl=1,201,200)
Par l'expression suivante :
anim = 200+Ctrl

Tant que vous savez ce que vous faites, vous pouvez toujours complexifier des expressions, si c'est pour réduire le nombre de triggers ou sctrls utilisés pour le même résultat. 

Pensez notamment à utiliser les intervalles. Par exemple :
trigger1 = StateNo != 15000
trigger1 = MoveContact
trigger1 = StateNo != 15001
trigger1 = MoveContact
trigger1 = StateNo != 15002
trigger1 = MoveContact

Se simplifie en :
trigger1 = StateNo != [15000,15002]
trigger1 = MoveContact

Pensez également à utiliser les valeurs de certaines fonctions comme éléments d'expression et non comme simple élément "décisionnel". Par exemple :
[State -1]
type = ChangeState
trigger1 = command = "toto"
trigger1 = var(1) = 0
value = 1000

[State -1]
type = ChangeState
trigger1 = command = "toto"
trigger1 = var(1) = 1
value = 1010

[State -1]
type = ChangeState
trigger1 = command = "toto"
trigger1 = var(1) = 2
value = 1020

Se simplifie avantageusement en :
[State -1]
type = ChangeState
trigger1 = command = "toto"
value = 1000+10*(var(1))

Ou à la rigueur, si votre var peut prendre d'autres valeurs :
[State -1]
type = ChangeState
trigger1 = command = "toto"
trigger1 = var(1) = [0,2]
value = 1000+10*(var(1))

Cependant, attention à ne pas tomber dans le travers opposé, à savoir, vouloir tout réduire à tout prix. Ainsi, ce code :
[State 8310]
type = ChangeState
trigger1 = NumHelper(1000) = 1
trigger1 = Helper(1000), MoveHit
value = 8312

On pourrait imaginer le simplifier en :
[State 8310]
type = ChangeState
trigger1 = (NumHelper(1000) = 1) && (Helper(1000), MoveHit)
value = 8312

Pourtant, c'est une mauvaise idée : dans le deuxième cas, si vous affichez le debug en cours de combat, vous verrez la plupart du temps tout une file de message du style : "PlayerXX has no helper 1000 in state XX". Alors que dans le premier cas, non. La raison est simple, Mugen stoppe l'évalutation des triggers dès que l'un d'eux est faux (cf. le topic précédent), donc dans le premier cas, Mugen trouve que "NumHelper(1000) = 1" est faux et il s'arrête là, sans regarder le trigger1,2.

Dans le second cas, en revanche, même si "NumHelper(1000) = 1" retourne 0, Mugen n'a pas fini d'évaluer son expression, et il va donc regarder le second argument de l'opérateur &&, à savoir "Helper(1000), MoveHit". Mais la plupart du temps (sauf cas exceptionnel d'un helper présent tout le long du match), votre perso n'aura pas d'helper(1000) actif, et donc la redirection de trigger ne se fera pas, ce qui engendre le message d'erreur.



Quelques petits rappels mathématiques
Ce sont des petites choses toutes bêtes, mais qui sont parfois bien utiles pour nous simplifier la vie :
* Multiplier deux chiffres négatifs entre eux donne un résultat positif. C'est assez utile si vous voulez faire alterner des vitesses (vers l'avant, vers l'arrière, vers l'avant...). Il suffit juste de multiplier la vitesse à un instant T par -1. Ex, si on part de 3, on alternera entre 3 et -3.

* Soustraire un chiffre, c'est additionner un négatif (3-1 = 3+(-1)). Pensez-y si vous utiliser une variable qui change de signe...

* Dans le même style, diviser par un nombre, c'est multiplier par l'inverse de ce nombre (3/4 = 3*(1/4)).

* Principe de "capitalisation" : si on multiplie une variable x par un coef fixe supérieur à 1, tel que x devient le résultat de cette multiplication, alors la valeur de x augmente de façon exponentielle. 
1*1.1 = 1.1 (+ 0.1)
1.1*1.1 = 1.21 (+ 0.11)
1.21*1.1 = 1.331 (+ 0.121)
Ceci, appliqué avec un Velset, permet par exemple de "simuler" une accélération.

* Inversement si on le multiplie par un coef inférieur à 1, la baisse entre chaque valeur de x est de plus en plus faible :
1*.09 = .9 (- .1)
.9*.9 = .81 (- .09)
.81*.9 = .729 (- .081)

* N'oubliez pas, dans Mugen, la distinction entre entier et flottant peut être déterminante. Ainsi :
var(1)/2 donnera un nombre entier, forcément, même si var(1) est impair. Si vous voulez obtenir un nombre flottant, vous devrez procéder ainsi :
var(1)/2.0 (la valeur entière de var(1) sera convertie en décimal avant l'opération).
De même, si vous voulez obtenir un résultat entier en travaillant sur les fvar, vous devrez utiliser les triggers Ceil et Floor qui vous permettent de faire des arrondis supérieurs et inférieurs.

* Dans tous les cas, gardez à l'esprit le type demandé par les paramètres. Par exemple, si vous devez assigner une animation en fonction d'une fvar, vous devrez utiliser les triggers Ceil et Floor, même si le résultat de votre opération donne un résultat dont la décimale est nulle (10.0 par exemple).

Les petits pièges à éviter
* Vérifiez bien les types des triggers ; par exemple, contrairement à ce qu'on pourrait croire, le trigger Pos retourne une valeur flottante, et non entière, et ce, bien que l'on parle de pixels.

* N'oubliez pas de rediriger les triggers dans states d'helper, si vous voulez les appliquer au perso et non à l'helper. Ceci est notamment vrai pour les variables (eh oui, les helpers ont leurs propres variables, qui diffèrent de celles du persos). Ainsi, dans un helper, "parent, var(1)" n'a pas la même signification que "var(1)". Idem, faites attention à bien utiliser des ParentVarSet et non des VarSet...

* Essayez de voir comment va se comporter votre expression selon la valeur des triggers utilisés. Exemple :
velset = 4,0
anim = 1000


Ici, le sctrl ne se déclenchera pas au bout de 10 ticks ; en effet, si on regarde le trigger, quand Time vaut 0 (entrée du perso dans le state), le trigger1 est vrai, et du coup, le VelSet se déclenche, annulant le velset = 4,0 du StateDef.

* Ca a déjà été dit, mais l'ordre des sctrl a une importance qui peut se révéler capitale. Voici un extrait du Nayru's love de Link. Il s'agit du code de l'helper qui affiche le prisme qui tourne autour de Link.
[State 18100, VarCheck-Add]
type = VarAdd
trigger1 = Anim = [18001,18003]
var(4) = 1

[State 18100, Change]
type = VarSet
trigger1 = abs(ParentDist X) > 50
trigger1 = var(4) > 10
var(2) = -1*var(2)

[State 18100, VarCheck-Reset]
type = VarSet
trigger1 = abs(ParentDist X) > 50
var(4) = 0
Sachant que :
- Le déplacement devant se faire par rapport à Link, j'utilise un BindToParent pour lier la position du prisme à Link,
- A chaque tick, j'utilise un PosSet pour faire varier la distance entre Link et le prisme,
- Les anims 18001, 18002, 18003 correspondent aux différentes anims du prisme selon le power de Link,
- var(2) vaut toujours 1 ou -1, selon que je fais s'éloigner ou se rapprocher le prisme,
- var(4) est le nombre de fois où le prisme est déplacé par rapport à l'axe de Link depuis le dernier changement de sens. Il faut que ce nombre soit au moins supérieur à 10 (chiffre arbitraire) pour que l'on puisse changer de sens (c'est juste une sécurité pour éviter de bloquer
le prisme, cf. le topic de WIP sur Link où j'explique ce bug),

Sur le code lui-même :
- le 1er sctrl augmente de 1 la valeur de var(4) à tous les ticks
- le 2ème sctrl fait changer le sens si var(4) est > 10 (c'est la sécurité évoquée avant) et si la distance entre Link et le prisme est supérieure à 50 (pour que le prisme n'aille pas trop loin).
- le dernier sctrl réinitialise la var(4) à 0 à chaque changement de sens.

Ce code fonctionne sans problème. Mais que se passe-t-il si on inverse les second et troisième sctrls ? Le code n'a absolument pas changé, c'est juste l'ordre des sctrl qui est modifié. Ce petit détail entraîne cependant une grande conséquence : la var(4) est remise à 0 avant que le changement de sens ait eu lieu. Du coup, le sctrl censé opérer le changement de sens ne peut se déclencher, car le trigger1,2 (var(4)>10) est faux. Comme le sens n'est pas changé, le prisme continue à s'éloigner de Link indéfiniment.

Bon, je pense qu'on va s'arrêter là pour les expressions ; ça fait déjà un bon gros morceau à digérer. Si vous voulez vous exercer, reprenez les states de différents persos en essayant de comprendre ce que font les diverses expressions (et notamment KFM-Master, il est fait pour ça : tous les states des nouveaux mouvements sont commentés en français).

Mike Werewolf.

