Friday, June 07, 2013

walking on platforms

I was somehow 'home alone' Wednesday evening. But my eyes were stabbed by my laptop's backlit. Perfect mood for some thinking on game engine features with some pens & paper. Theme ? How to enable Bilou walking on platforms. I initially hoped that a GOB reference would be sufficient to "walk on a platform", but that would restrict us to "platforms that have just the shape of the object's bounding box, while there is much more I intend to do with a generic "dynamic path" abstraction!

Cette fois, je pense que je tiens enfin le bon bout. J'ai réussi à fusionner la notion de "chemin" modifié dynamiquement (pour les cordes qui se balancent, entre-autres), et de plate-formes.Tout ça en prenant le temps, pour une fois, d'y réfléchir en cherchant quelles sont les questions auxquelles il faut apporter une réponse plutôt que d'essayer de construire une solution en partant d'une page blanche. Un système efficace qu'il faudra que je réutilise plus souvent.

And for once, I didn't started building a solution, but rather by writing down the questions that need to be answered. Once again, it proved being a powerful exercise. The questions were:

  • Is the "path" object permanent ? sometimes
  • Is the "path" object redundant ? only in the basic case
  • Could a GOB walk on more than one path (at once) ? no
  • Who 'own' a path ? usually, a GOB.
  • How many path can be owned by an owner ? I don't want to limit.
  • Can we be both 'on path' and owning a path ?  Yes (see picture above)
  • How could the owner of a path force objects on that path to lose their reference ?
Now, it's time to be more precise. The path is redundant with collision box in the very case of an horizontal platform. Yet, collision boxes (GobAreas) are not easily accessed from a GameObject reference. For once, I recall of Super Mario World's slanted platforms in the Chocolate Zone. It's still linked to a GameObject, but it will require a dedicated y=f(x) function.

La clé, c'est d'arriver à la formule "l'objet GPath est redondant avec la GobArea -- les zones rectangulaires utilisées dans la détection de collisions entre sprites -- dans le cas précis où l'on veut que le chemin aille de (gob.x+area.left,gob.y+area.top) à (gob.x+area.right, gob.y+area.top)". En d'autre termes, GPath est une abstraction générique, et le GobArea contient les données nécessaires pour cette abstraction dans un cas précis, mais on ne souhaite pas que le code utilisateur soit exposé à ces données. L'exemple-type d'une classe implémentant une interface ^_^. Il me faudra bien sûr d'autres implémentations si je veux des plate-formes inclinées, rotatives, basculantes, et tout ce que NSMB a apporté aux jeux de plate-formes ... mais le code de base "marcher sur une surface" restera là.

I also think the "path" we walk on must be potentially dynamic, that is, manipulated by a controller, for swinging ropes (example of a vertical path) or those NSMB dancing giant mushrooms. Writing it down as "only in the basic case (GobArea), but as a function of what I already have" made the bell ringing: GobArea is a specific implementation of the GPath interface. Virtual methods and inheritance are what I need to avoid cluttering controller's code with interpretation of coordinates array.


It also nicely solves the "ownership" (and thus allocation) issues. I know that things like huge mushrooms would have their Path allocated with level-scope, and then manipulated through a controller. The Gob that runs that controller is likely to be permanent as well.

Path for a platform, on the opposite side, are required only as long as a character interacts with the platform. By making GobArea ISA GPath, and stating that all coordinates resolutions are relative to the owner's coordinate, it means that all the platforms in a current state may share the same "path" information (offsets to their local coordinate) and  function implementation. Nice. They stop being "transient/temporary" structures and get level-wide scope as well: the allocation problem disappears ^_^. It do mean, however, that the "gobs" that control movement of multi-segment, dynamic paths will not be able to be themselves on a path, as they'll need their "path" reference to define what path they're altering ... unless I find a better solution, I can live with that.


L'autre bonne nouvelle, c'est que celà règle également les problème de "responsabilité" entre objets. Je trouve cette notion de responsabilité fondamentale en C++: une fois qu'on a pu définir qui est le seul et unique responsable d'un élément, alors on peut gérer beaucoup plus facilement son allocation, surtout lorsque les durées de vie coïncident. "Un chemin pour marcher sur une plate-forme", à priori ça a une durée de vie assez brève: du moment où Bilou entre en collision avec la plate-forme jusqu'au moment où Bilou s'en détache (de son chef ou de celui de la plate-forme, d'ailleurs). Un vrai cauchemar, surtout si on commence à se dire qu'il serait sympa qu'un autre ennemi arrive à la rencontre de Bilou sur cette même plate-forme!

En revanche, dès que GPath n'est qu'une façon de regarder la zone de collision (GobArea), tout s'arrange. Les "GobAreas" ont une durée de vie identique à celle du niveau et sont sous la responsabilité des états des machines d'état (et donc partagées par tous les objets d'un même type). C'est tout à fait satisfaisant si on considère qu'elles définissent des fonctions y=f(x) dans des coordonnées relatives à l'objet auquel Bilou est attaché.


All I still need in addition to the existing Attach, Detach and AttachToPath 'interaction opcodes' in GobExpressions is a 'IsAttached' test that will allow me to craft "force-detach" collision boxes that won't detach objects from other platforms ^^".

No comments: