Tuesday, May 22, 2012

Dumblador turns back

Here it comes. Some final tweaks on dumblador's "walk to the right" animation last night and I now have a CompoundGob that turns back when it encounters a wall (how sweet ^_^) Dumblador avance et fait demi-tour. L'occasion de revenir sur le fonctionnement des personnages dans mon moteur de jeu. Accrochez-vous un peu: le 'gobscript' est conçu plus comme un modèle de bytecode en ASCII que comme un véritable langage de programmation, mais comme dumblador est tout simple, ça reste lisible (j'espère ^^") Please bear some GobScript since it's still a pretty simple and straightforward monster:

anim1 = spr:0
anim2 = spr:3
statL0 :anim1 {
using walker
selfmove
test 0 (2,4)-(8,14) 0001
area 0 (0,0)-(8,4) 000A
}
on commence par importer des animations depuis le jeu de sprites (il suffit de connaître leur numéro, ici n°0 pour avancer vers la gauche et n°3 pour avancer vers la droite) et on leur donne des identifiants locaux (anim1 et anim2, respectivement). Le comportement de dumblador va maintenant être construit comme une combinaison d'états réutilisant ces animations: L0 pour avancer vers la gauche et R1 pour avancer vers la droite.
statR1 :anim2 {
using walker
selfmove
test 0 (2,4)-(8,14) 0001
area 0 (0,0)-(8,4) 000A
}

statL0->statR1 on fail (100 :0)
statR1->statL0 on fail (100 ~ :0)
statL0->statR1 on hit0 (100 :0)
statR1->statL0 on hit0 (100 ~ :0)
end
The only thing you have to remember about your spritesheet is now the position in the animation slots where your things are. That's slots #0 (walk left) and #3 (walk right) for me. You give them animation identifiers for your monster (1 and 2, resp.) From that, you can define states walk left and walk right. Each state defines an animation being played and a behaviour. The combination of using walker and selfmove means that DumBlador will move forward at constant speed, according to what's defined in the animation, but not faster than the value defined in its internal variable v0 (x speed)
Vous pouvez penser à un "état" comme à une action que fait un personnage: s'il est dans l'état "avancer", il fait son animation "un pas en avant" et utilise un morceau de code C++ un (contrôleur) identifié via using walker qui reprend les tests "y-a-t'il du sol? dois-je suivre une pente?, etc.". Le mot-clé "selfmove" indique que c'est l'animation qui définit de combien de pixel on avance à la fois pour une meilleur synchronisation. On reste donc par exemple dans l'état "vers la gauche" tant que tout va bien. Le contrôleur génère un "fail" lorsque le personnage arrive dans un mur. À ce moment-là il va falloir choisir un nouvel état. Les commandes du types statdepuis -> statvers on fail [condition] (action) permettent de construire les transitions qui seront testées dans ce cas-là. Ici, l'action consiste simplement à écrire une valeur constante (100 ou -100) comme nouvelle vitesse horizontale (variable n° 0). "walker" is what's called a controller for the GOB. It can also report events and indicate when it failed to perform the desired movement. The two on fail statements indicate transitions from "walk left" to "walk right" and back when it's no longer possible to move forward. That could be due to a wall or the end of a platform. With this simple script, dumblador "has no way to figure out". Another part of the current behaviour is that dumblador turns back when Bilou jumps on his "head". This is achieved by the two on hit statements. It's still necessary to provide the (relative) coordinates of the hitboxes manually. Here the "hit" statement corresponds to a passive area in the state definition, which is seen in dark cyan in the Inspector widget. "000A" is the bit mask that corresponds to Bilou's stomping action or AppleAssault's attacks. I could have used "0002" to make DumBlador impervious to punch attacks and reacts only to stomps. Enfin, pour compléter le comportement décrit par chaque état, les instructions test|area (coin1)-(coin2) avecqui donnent les zones de collisions (rectangulaires) qui vont permettrent d'interagir avec les autres personnages. La partie un peu "magique" se situe dans le nombre hexa avecqui. Pour chaque "type" de collision, il faudra choisir un des 16 bits disponibles et faire en sorte qu'au moins deux zones de collisions (p.ex. le corps de blador et celui de Bilou) utilisent le même bit (0001) mais dans un test pour l'un et dans un area pour l'autre, pour refléter l'asymétrie frappeur-frappé du moteur de collisions. Finally, the curious (100 ~ :0) are GobExpressions. It's a minimalist encoding of "set var[0] to -100". It works as those RPN calculators, where you type "2 40 +" to compute "40 + 2". Only simple parts are used here: push the constant 100, negate it (I don't have negative constants so far ^^") and assign it to variable 0 (mnemonic: Pascal uses := for assignments). Reusing a constant here is required as the walker controller is allowed to alter the speed in the final steps towards the wall. Would you like to give it a try yourself ?

1 comment:

PypeBros said...

this simple "walkLeft->walkRight on fail ; walkRight->walkLeft on fail" approach has a drawbacks: the GOB can move as long as there's one pixel of ground beneath it. Then it's stuck, but swapping direction won't help: it will still be unable to walk *because it's already mid-air*.

Result is a monster that suddenly flips at every frame, staying stuck mid-air.

Solution is to use *test-points* at the edges of the "feet zone" that validate there's a ground to walk on.