Saturday, January 10, 2015

Keen: ClipToWalls()

Pour éviter que Keen ne passe à travers les murs ou ne reste bloqué en bas d'une pente, les niveaux attribuent à chaque bloc de 16x16 pixels un ensemble de propriétés. C'est la technique des "tiles" bien connue de la plupart des lecteurs. Ce qui est particulier dans le moteur d'ID software, c'est la possibilité de définir séparément l'animation, la pente de sol, le comportement en tant que bloc spécial (valeur du bonus, couleur de la clé, etc.) et la polarité du mur.
Plus fastidieux à définir que le simple remplissage en tant que bloc solide de mon éditeur de niveau, mais heureusement, dans Keen, il y a presque systématiquement correspondance entre graphisme et propriétés, et l'éditeur de niveau peut automatiser tout ce qui n'est pas passages secrets.


Levels in Commander Keen are build out of tiles -- 16x16 pixels squares combining a graphic content and some in-game properties. In ID's engine, the tile number (*map) is used to index a set of arrays that are present in game definition, and read at game startup into tinf. NORTHWALL, in the snippet above, is the offset to the array holding information about how the tile behave as a floor. Is it sloped ? can we move through if we're sliding down a pole ? tinf[NORTHWALL+*map] tells you that. And only that. If you'd like to know whether there's a bonus of some value, you should definitely check another "slice" of the tinf array.

Interestingly, the behaviour routines won't immediately react to wall/floor/ceiling collision. Oh, well, character will immediately be _clipped_ to that wall. Aligned, if you prefer, so that it doesn't enter a rigid structure. But the alignments that were performed are saved into four boolean variables: hitnorth (floors), hitsouth (ceilings), hiteast and hitwest (walls). Behaviour routines like KeenAirReact use this information to adapt the current state (stop falling when reaching the ground, for instance).


La notion de "polarité des murs" est intéressante. Lorsque le personnage se retrouve partiellement dans un mur, c'est cette polarité qui décide si Keen est repoussé vers la gauche ou vers la droite. On aura donc jamais de situation comparable au "zipping" des jeux NES où Megaman se met à traverser le niveau à vitesse super-sonique lorsqu'il rentre dans un mur simplement parce que les programmeurs se sont contenté de "si on est dans un bloc solide, on repousse Megaman sur le tile suivant". Evidemment, ça suppose que les murs font toujours au moins deux tiles de large.
A Zipping technique in Megaman. Propelled at 1 tile/frame or so.

Another interesting thing is that walls have polarity. They're either left wall or right walls. And they're not plain ground either: you could fall through a wall if there were no floor on its top to prevent you from entering there. And it's actually more resilient against zipping through walls. Unlike Megaman, Keen wouldn't be propelled to the (arbitrary coder decision) right if he ends up into the wall from the left while heading left. A wall on Tuberia knows whether it is an east wall, no matter the direction, speed and age of the Commander. And if it's an East wall, it will push you to the East, back where you're supposed to belong to. That makes ID software's collision handling the perfect opposite to Epic Megagames' cando() function.

C'est intéressant de voir que ID software a choisi ici une technique radicalement opposée à celle d'Epic Megagames. Pour Jill of the Jungle et Xargon, Epic faisait en sorte que le personnage n'entre jamais dans des tiles qu'il ne peut pas traverser (grâce à la fonction "cando"). Pour Keen, think() peut potentiellement faire rentrer un personnage dans un mur, mais le déplacement via ClipToWalls() le repoussera automatiquement dans la bonne direction, sans risquer de se faire piéger par un demi-tour de dernière minute comme dans Mario Bros.

No comments: