Friday, May 24, 2013

Un grand pas en avant ...

Pas évident d'avoir des pentes correctement intégrées dans le déroulement du jeu.

Souvenez-vous: dans Apple Assault, si les pentes fonctionnaient presque correctement, il me restait une certaine probabilité que Bilou se "bloque" arrivé au sol, et ce n'est qu'avec la révision du moteur de jeu en Septembre dernier que le bug fut corrigé. Mais on était pas tiré d'affaire pour autant...

A force de refaire des tests pour m'assurer que Bilou ne puisse plus se bloquer dans les murs suite à un atterissage en catastrophe (en mode "pas à pas"), je finis par me rendre compte il y a quelques semaines que lorsque Bilou arrive au bord d'un livre (terminé par un tile pentu en bordure), il oscille quelques fois entre "marcher" et "attendre" avant de finalement se décider par sauter. C'est la plupart du temps presqu'imperceptible pour le joueur qui ne notera peut-être qu'un temps de retard, mais je me suis mis avec ce projet d'avoir un moteur de jeu irréprochable. Exit l'à-peu-près et les excuses bidon: si je ne peux pas être le moteur candidat pour Super Mario World 3 sur SNES, alors le côté "documentation de comment on aurait sans doute pu faire les choses avant de passer à la 3D" perd sa raison d'être.

My nephew had pointed out that Bilou could get stuck in the "School Zone" quite a while ago, and while I was checking that I fixed that properly, using step-by-step mode in Inspector Widget, I realised that Bilou would also temporarily "stick" to book corners before falling down. In stop motion, one could have noted that it flickers between walk and idle 2 or 3 times before actually jumping off the cliff. I want a rules-perfect engine, not a "we can play it out", so I invested several evenings figuring out what was actually wrong.

As in many engines, I handle slopes using a 'hotspot' pixel that must stay on the 'curve' defined by the slope. The rest of the character (the dark box) may enter slope tiles or be in the void, what it doesn't cando() is entering those black, solid tiles (which is checked separatedly). Making sure this looks nice is the level designer's problem, not the engine's. Within a tile, when the horizontal coordinate is changed by the 'walking' behaviour, we retrieve the ground height for the corresponding target pixel and align the hotspot there. With that approach, we may end up in a tile that has no ground at all (gh=0) when switching to the next (horizontal) tile. In that case, the alignment is performed on the tile just below.

Commençons par un rappel du fonctionnement des pentes dans mon environnement découpé en pavés (les "taïlze/tiles"). Bilou n'y est qu'un rectangle qui doit pouvoir naviguer sans rentrer dans les tiles solides (noirs). Il possède en plus un point de référence (hot spot, en rouge vif sur l'image) qui doit rester en contact avec le sol lorsqu'on suit une pente. Pour avancer d'un pixel vers la gauche, le hot'spot passe d'abord dans un tiles complètement vide (gh=0) où il n'y a pas de sol à suivre. La fonction do_slopes détecte ça et teste du coup le tile situé juste en dessous. Son pixel le plus à droite correspond à une hauteur gh=-8. On se retrouve ré-aligné sur le prochain pixel ... tout va bien.

Le problème apparaît seulement lors d'une transition pente/trou, comme j'en ai ajouté sur le bord des livres. Ici, lorsque'on regarde sous le tile vide, il y a ... un tile vide. Pour do_slopes, celà signifie qu'il n'y a plus de pente à suivre.  Idéalement, on devrait donc juste se retrouver "le long de la ligne bleue" inférieure, qui empèche Bilou de tomber jusqu'à ce qu'il l'ait complètement quitté. Mais voilà, à ce moment-là, on est pas encore sur le sol! Du coup, lorsqu'on teste s'il est possible de tomber d'un pixel, la réponse est "oui" et le contrôleur envoie un FAIL.

Now, when we instead have a sloped edge, as with books, both tiles are AIR and have no "ground" to align on. The do_slopes function then consider there is no slope to follow and let the walker::think behaviour function to adapt accordingly. Walker's behaviour is then to walk as long as possible, testing whether Bilou cando a move downwards (meaning he can fall). That would be all fine if Bilou was aligned on the blue line by then, but he isn't. he's still one pixel above, because that's where the slope ended.

Would have it implied that Bilou started falling earlier, I wouldn't have minded. But that's not what the state machine decides. from its perspective, the backward testpoint is still on the ground and thus the FAIL even is translated into a transition to idle state rather than towards fall.

Seulement, voilà, l'échec du contrôleur n'implique pas forcément une chute. C'est à travers les transitions du comportement de Bilou que les choses vont maintenant se jouer. Tomber d'un pixel ne serait pas impeccable, mais celà nous conviendrait. Seulement, pour celà, il faudrait que le point-test placé au sol indique du vide ... et étant décalé sur la droite, il est toujours dans la pente! Du coup, c'est vers l'état "do_slopes. C'est ainsi qu'on parvient finalement à quitter le livre après ce qui semble être un instant d'hésitation.
à l'arrêt" (idle) que Bilou passe. Mais puisqu'on a toujours le DPAD incliné vers la gauche, on quitte dès l'image suivante cet état pour tenter à nouveau une marche... qui échoue encore. Heureusement, à chaque échec, on avance d'un quart/demi de pixel qui correspond au "mouvement entammé".

Une fois que l'Inspector Widget est devenu assez précis pour faire ce genre d'analyse, il devient assez évident que la solution est de forcer le personnage à s'aligner sur le sol quand on quitte une pente, et de ne considérer qu'on chute que s'il est possible de descendre alors que le personnage est déjà aligné sur ce qui devrait être du sol.

The actual solution is of course to ensure that we're aligned on ground when there's no slope. Only if we cando the move downwards *while being on a tile boundary*, we will claim it a FAIL move that should switch to some other state. But to realise that, significant upgrade on InspectorWidget's precision was required. I even was tempted to turn this post into another "Inspector Widget's novel" post, but the implication on the game engine and behaviour coding was too high, and I'd rather went for a tutorial shape.

Pour faire bonne mesure (et pour éviter que Bilou ne se retrouve en suspens sur un bord de livre parce qu'il n'a pas sauté assez haut), j'ajuste enfin le saut à travers une plate-forme à sens unique (les branches de Apple Assault): on ne peut plus atterir sur ce type de plate-forme que lorsqu'on vient d'au-dessus de la plate-forme.

No comments: