Monday, April 27, 2009

Coding Funky Funghi

J'avais envie de mettre côte à côte "comment je me représente le comportement de Funky Funghi" et "comment je réalise ce comportement avec mes outils actuels.

state12 :anim15 {
using stopper
}

state13 :anim6 {
using gravity(24,1536);
testpoint off (16,32)
}

state12->state13 on done [t] (v4 0 = 200 * 500 + ~ :1)
state13->state12 on fail [t] (v4 1 + 4 % :4 0 :1)
The gamescript for funky funghi is a good example of what i called "more sophisticated monster behaviour" in my earlier post about how to write the game logic. Eventually, such behaviours will be edited *on the DS* with a mostly-graphical tool, but the logic is already there: Funghi has two states (jumping and on the ground) to which i can attach different animations and "core behaviours" such as 'affected by gravity' or 'just stay in place'. It also has internal variables (loaded with v4 and stored back with :4, for instance) that both manipulates its internal speed (:1 sets vertical speed, for instance) and can be used as monster-specific state.
I reused the UML state machine model where each state transition can have a predicate that indicates whether the transition can occur (between square brackets) and an action that alters the object before it enters its new state.

Voilà donc à la fois un "mockup" futuriste de l'éditeur de monstres sur DS et le genre de script que je bricole en attendant pour quand-même avoir des monstres qui se balladent dans mes niveaux. Histoire que vous suiviez :
  • Funghi a deux états: au sol (12) ou en saut (13). Ca me permet notamment d'avoir une animation spécifique à l'état "au sol" et synchroniser la "déformation" du sprite avec la collision au sol (chose dont le RSD Game-Maker était incapable).
  • Comme tous les objets que j'ai jusqu'à présent, Funky Funghi respecte la convention que v0 est la vitesse horizontale et v1 la vitesse verticale. A priori, ces valeurs sont gérées par le contrôleur et utilisées par la classe SimpleGob pour mettre les coordonnées à jour. Elles sont aussi accessibles dans la machine d'état (:1 signifie "écrire la valeur dans v1)
  • Chaque "gob" possède 12 compteurs sous son entier contrôle. Funghi utilise le premier d'entre-eux (v4) pour compter ses rebonds. "v4 1 + 4 % :4" est une expression post-fixée (pensez 'calculatrice HP' qui doit se lire "prend v4, ajoute 1, calcule le reste de la division par 4 et stocke le résultat dans v4 à nouveau". Ce calcul est effectué chaque fois que Funghi entre en contact avec le sol.
The above script is a bit complicated because i decided to rule out flow control when i 'designed' the language (something i learnt in my thesis), yet i need here that Funky Funghi jumps higher every 4 bops. Plus, it's a bit ugly because i used post-fixed arithmetic (2 + 3 is thus written 2 3 +, as on a HP calculator). Hopefully, the existence of predicates allow me to compensate the lack of conditional expressions in actions by multiple transitions, each with a predicate and a separate action. See the code below, it's actually clearer (sort of) with similar efficiency.

A la fin de l'animation "déformation", la nouvelle vitesse verticale est définie. "500 ~ :1" donnerait à chaque fois la même implusion (~ est utilisé ici pour prendre l'opposé d'un nombre, donc v1 reçoit -500 ... eh non, je n'ai pas implémenté les constantes négatives. Sue me).
Pas d'expressions conditionnelles genre "si(test, réussi, échec)" comme on en trouverait dans Excel ici (ou test?réussi:échec pour les programmeurs); c'est une restriction délibérée, et sans doute la plus contraignante de mon évaluateur d'expressions jusqu'ici. Or, je dois décider de fixer la vitesse après rebond à 700 ou 500 selon qu'on est au 4eme saut ou pas. Ma première approche était de tenir compte du fait que "v4 0 =" teste la valeur du compteur de rebonds et laisse 0 ou 1 sur la pile selon le résultat, et de jouer sur la multiplication absorbante : "t*200+500" vaut 700 quand t=1 et 500 quand t=0. D'où "v4 0 = 200 * 500 + ~ :1".
En fait, il y a plus simple puisqu'à défaut d'expressions conditionnelles, j'ai séparé prédicats et actions. Rien ne m'empèche donc de réécrire la machine d'état sous la forme suivante:
state12->state13 on done [v4 0 =] (700 ~ :1)
state12->state13 on done [t] (500 ~ :1)
state13->state12 on fail [t] (v4 1 + 4 % :4 0 :1)
Alors oui, bien sûr, c'est horrible à relire. L'idée n'est évidemment pas d'obliger l'utilisateur final de GEDS à écrire des expressions de ce genre-là, mais plutôt de se donner un "langage intermédiaire" (un bytecode, quoi) comme résultat d'un éditeur de monstres plus graphique.

J'aurai donc quand-même mis un an entre "l'idée sur papier" et le protoype qui tient la route pour la définition des contrôleurs (bon, j'ai fait d'autres trucs entre-temps, évidemment). Et l'intégration des "test de possibilité de déplacements" dans le contrôleur (reléguant les testpoints au rang de sophistication optionnelle) n'est pas la moindre des décisions.

No comments: