Sunday, December 28, 2014

Keen ClipToSprite()

This is the code snippet for the ClipToSprite() action that results in pushing monster/keen interaction when the monster stands on the left. The code adapts characters expected motion (which differs from its computed speed and is transient) so that intersections between characters hitboxes remains empty.

The condition highlighted with yellow puzzled me at first. In fact, we'll note that leftinto == xmove (the difference between characters speeds) when they were exactly side by side at the previous tick. Would there be 1 pixel distance between them at the previous tick, we'd end up with leftinto == xmove -1. So what it really implies is that we won't get pushed at high speed if we fall down just on the left of a carrot running to the right with a large leftinto value (e.g. if we fall on carrot's head and move down through it).

Une seule fonction -- ClipToSprite -- permet à Keen de s'aligner sur d'autres objets en définissant un nouveau mouvement qui s'exécute en plus du mouvement prévu par la fonction think(). Le principe de base est de calculer de combien de pixels les deux personnage s'interpénètre et de repousser celui qui n'est pas solide par un déplacement opposé.

Il est important pour comprendre ce code de se souvenir que le moteur de jeu travaille en plusieurs passes et que tous les personnages ont déjà leur nouvelles coordonnées quand les tests de collision ont lieu. Les valeurs originales de xmove et ymove ont donc déjà été appliquées. Par la "condition jaune" (leftinto <= delta_xmove), on s'assure que Keen ne sera repoussé par le bord d'un objet que s'il n'avait pas déjà dépassé ce bord à l'étape précédente (mais sans que la collision n'ait lieu parce qu'il était au-dessus du chariot, par exemple).

The ID keen engine is multi-pass. That means when ClipToSprite() is invoked as result of keen/courier contact, setting keen.ymove=0 doesn't prevent keen from falling, because he already moved down in the previous step. Instead, it prevents him from moving down again as result of the carrot collision. This design avoids issues with moving platforms without requiring a re-ordering of the objects evaluation.
This is also made possible because objects have a separate react() function to align against level structures.


Pour les plate-formes, des tests verticaux font suite aux tests horizontaux également présents dans les "monstres pousseurs" comme la carotte véloce. S'il est surprenant que le déplacement horizontal de Keen soit exactement celui de sa plate-forme (et pas Keen.xspeed + Cart.xmove comme le voudrait la physique), c'est qu'un premier mouvement a déjà eu lieu. Il ne reste plus qu'à appliquer le mouvement de la plate-forme à Keen, le repousser vers le haut et surtout s'assurer qu'au moment de l'appel à KeenAirReact, la logique du jeu pense qu'il y avait un bloc solide sous Keen. C'est le rôle de la variable hitnorth qui conserve le type de tile qui a interrompu le personnage lors de la phase de déplacement.

Characters that push you but you can't ride ends after those horizontal tests. For carts, platforms and Keen4e's bouncing red ball, you'll also process the "bottominto" test. Note that since we already moved Keen according to the direction keys, setting keen.xmove=cart.xmove and invoking ClipToWalls() again -- which includes applying (xmove,ymove) to (x,y) coordinates of Keen -- combines cart's move with Keen's own move as expected. Hacking the hitnorth member will make KeenAirReact() function believe that some solid ground was found during move/clip step.

2 comments:

Cyborgjeff said...

C'est un peu magique de vivre Keen de cette manière ;)

PypeBros said...

Grâce à une levée de fonds, visiblement ^_^