Friday, May 22, 2009

Moon walk ?

As seen in former post: Scripting Sunday:

TODO: moving Bilou through the animation isn't the right way to go. Maybe "delay x" in the animation sequence to have the next frame triggered only when we move to the next pixel would be better
Faire marcher un personnage est loin d'être une tâche facile et en particulier, ça demande une synchronisation parfaite entre le déplacement du sprite et son animation. Le moindre décalage et le joueur aura l'impression que le personnage "glisse" sur le sol au lieu de marcher. En clair, le pied en contact avec le sol doit rester au même endroit par rapport au sol jusqu'à ce qu'il se lève. Ce genre de défaut était assez fréquent dans les jeux de mon enfance (hein, Eric ;). Une manière assez simple d'y remédier est évidemment de faire courir le personnage, de préférence "à la supermario"

La solution que je pensais y apporter était relativement simple: intégrer les déplacements dans l'animation à coup de "move x y" entre deux images. C'est comme ça que je déplace le wooworm et ça réussit plutôt bien. Le hic, c'est de combiner ça avec une vitesse éventuellement variable et le test des collisions qui a été ramené dans le contrôleur (qui ignore tout de l'animation en cours).

I've never been satisfied by typical game-making animation tool that just let you define a constant moving speed (e.g. one pixel per frame) and an walking animation that you try to match that. It always gave me the feeling that the hero is "sliperring" on the ground or walks with rollers. I initially planned to fix this by integrating moves to the animation itself (i.e. 'show frame A, then wait for 2 frames, move by 2 pixels horizontally and show frame B'). That's how woodworm works, but i couldn't simply extended to Bilou's walk... Not until a friend of mine suggested that i could also delay the animation _until sufficient movement has been accumulated to 'hop' two pixels away_. Here comes the specific code that does that (only when the animation is "self-moving", which would be typical from ladder climbing, walking and other "friction-based" moves.

C'est Pierrick qui m'a donné la solution a mon problème en racontant comment du temps du CPC il faisait faire des sauts "réalistes" à son petit bilou en modifiant la durée d'affichage à chaque emplacement vu qu'il lui était impossible de placer le bilou (eh oui, c'était lui) entre deux tiles (pas de sprites en CPC basic ?). Plutôt que de chercher midi à 14 heures (du genre "modifier la vitesse d'écoulement du temps pour que Bilou coure plus vite"), j'ai juste changé l'interprétation de "move x y" en "ne passe à l'étape d'animation suivante qu'une fois que le contrôleur aura 'accumulé' le décalage suffisant. Traduit en code, ça donne :

inline bool trymove(int dx, int dy) {
    forcechecks=false;
    if (selfmove) {
        int cflags  = cast==HERO?F_PLAYERTHRU:F_MONSTERTHRU;
        bool notyet = (dx>0 && cdata[4]<dx) || (dx<0 && cdata[4]>dx)
                   || (dy>0 && cdata[5]<dy) || (dy<0 && cdata[5]>dy);
        if (notyet) return false;
        if (cando(dx,dy, cflags)==cflags)  {
            x+=dx; cdata[4]-=dx;
            y+=dy; cdata[5]-=dy;
            return true;
        } else {
            cdata[4]=0; cdata[5]=0;
            return setstate(state->dochecks(x>>8,y>>8,world, cdata));
        }
    } else {
        x+=dx;
        y+=dy;
        return true;
    }
}
  • selfmove est défini par état : true pour monter à l'échelle, false pour tomber, etc.
  • cdata[STEPX] et [STEPY] accumule les valeurs de cdata[XSPEED] et cdata[YSPEED] (vitesses définies par le contrôleur) en mode "selfmove" (normalement, on a directement x+=cdata[0])
  • trymove(dx,dy) est appelé lorsqu'une étape d'animation utilse la commande "MOVE"
  • condloop et check permettent de vérifier les testpoints sur des frames données (p.ex. quand le personnage a de nouveau les pieds au sol).
Voilà. J'avais envie de démystifier ça. J'espère que ça sera utile à l'un ou l'autre.

A pair of per-object variables (cdata[])will thus be used to accumulate some intended move until that "step" size becomes large enough for the move x y instruction found in the animation list. The trymove(dx, dy) tells whether such a move is possible right now or must wait until more motion has been accumulated.

edit: De manière étonnante, Miyamoto avait lui fait le choix délibéré, dès Donkey Kong, de casser le lien entre animation et déplacement parce qu'il jugeait qu'une animation de marche réaliste "ne collait pas à l'action frénétique d'un jeu vidéo" (l'Histoire de Mario, p.240)

2024 #choice reality check: It is still there, and I like how it makes many character feel like they're in contact with the ground. But let's be honest, it makes the code a bit more complicated every year. It makes it possible to have characters whose motion accelerates and decelerates over one step with a constant average speed, but offseting the graphics for a truly-constant-speed could achieve something similar, and that wouldn't even be complicated for a compound sprite. So there I am. Maybe this was not a good choice, but I haven't replaced it yet.

1 comment:

PypeBros said...

Juste un hic: c'est incompatible avec la gestion des pentes. Il me faudra modifier la commande "move x,y" des animations de manière à permettre "move 1,*" (déplace d'un seul pixel en X, et d'autant que tu peux en Y) qui conduirait à un appel du genre:

trymove(command.x, cdata[5])