Sunday, January 12, 2020

un p'tit pas de plus pour LEDS

 Bien. J'ai donc un éditeur de niveau capable de faire l'affichage des niveaux même quand j'utilise le "nouveau" format. Y compris pour les blocs spéciaux. J'ai encore un peu de travail à faire dessus (notamment faire en sorte que les boutons à droite de l'éditeur ne s'incrustent pas dans le niveau chaque fois qu'on les invoque), mais c'est plutôt bien parti.

Avec tout ça, et l'introduction de nouveaux types de pente, je me dis qu'il deviendra bien utile de pouvoir faire des cas de tests sur des micros-niveaux, avec un système capable de détecter qu'on arrive bien à passer les "obstacles" quelques soient les variations de conditions initiales (légèrement plus à l'ouest, vitesse qui n'est pas un multiple entier de pixels, etc.) sans pour autant entrer en contact avec les blocs supposés en dehors du trajet.

If I want to be effective with introducing more complexity in the sloped-ground engine code, I think I'll have to add some more unit-testing. I've learnt the hard way that sub-pixel initial position, interaction with step-motion and animation quirks can lead to broken slopes code, and that debugging that takes lots of time because it needs parts in-engine breakpoints and parts in-debugger breakpoints.

A micro-level with a few simple slope scenarios, easy control on the initial state that can be automated should help a lot. And help is welcome. Plus, it makes support for those slopes independent from Level editor updates, which is welcome as well.


Si ça peut sembler une perte de temps, vu que je n'ai encore aucun moyen d'ajouter des nouveaux types de pentes dans LEDS, ça ne serait peut-être pas si mal. En plus, dans un niveau comme celui de *deline, il est assez inconfortable de vouloir vérifier si la logique du code de gestion des pentes fonctionne bien comme prévu, parce qu'on est en permanence interrompu par ce que les autres "personnages" du jeu font.

Ça pourrait aussi être l'occasion d'introduire un truc qui me titille depuis un moment: une série alternatives de constructeurs pour les objets principaux du moteur de jeux (déclarateurs de blocs spéciaux, états du comportement des personnages, zones de collisions, etc) qui ne passent pas forcément par une analyse de bloc de texte ... et seraient donc un premier pas vers la possibilité de "compiler" du gobscript en code C++ (ou assembleur 68000 ?)

tempting to use that as an excuse to introduce an alternate set of constructors that could build objects without necessarily having to rely on text parsing. Like state.Using<GobWalkerController>("") rather than relying on "using walker;" processed by GobState::parse(). That would be handy for GBA or 16-bits ports of the engine, but it isn't easy to achieve with the current codebase, unfortunately. The core reason being that classes for the controllers are encap~insulated into a separate .o that was meant for 'dynamic linking' with the engine 'happily ignoring they even exist', except through a std::map of factories that can be invoked by controller names.

Thursday, January 09, 2020

Pentes et JumpThru

Je me rends compte qu'il y a un truc à propos des pentes que je n'ai apparemment pas convenablement expliqué sur ce blog. Voyez donc cette "capture d'écran" de l'éditeur de niveau avec les types de blocs qui sont révélés en bleu transparent. Il est assez facile d'y voir les zones considérées comme solides, et même la petite pente au niveau de la souche d'arbre (allez, je vous la marque en vert flashy). Puis il y a un troisième élément qui revient assez souvent, ce sont ces lignes horizontales, notamment dans la branche (je vous les marque en rose ou en jaune, petits veinards ;)

There's something I seem to have missed to explain about my implementation of slopes. Let's check this screenshot of the level editor, with the tile types revealed. Some areas are obviously solid (covered with fully-painted overlay squares). Some are slope (okay, I'll paint them in flashy green), and then there's a third recurrent item, identifiable with horizontal stripes (like the one painted in pink, on the tree branch).

Those striped tiles are 'jump-through' platformes, helping the player to climb up structures in the level. They are simply implemented by changing the "world flags" that we're looking for depending on whether we're moving up or down, by acting like ground if we fall down, and like air if we jump through them.


Elles correspondent au type "jump-thru", qui a la particularité de laisser passer ce qui monte mais pas ce qui descend. Aucune magie là-dedans: c'est simplement le "GravityController" qui ajuste le test effectué selon qu'on monte ou qu'on descend. Ce type agit aussi comme du sol sans agir comme un mur: si on arrive dans la branche par le bas avec une vitesse horizontale, on la conserve.

Là où ça devient intéressant (ou louche comme une passoire sans trous), c'est pour les blocs "jump thru" qui sont marqués en jaune. Non, il n'y a pas de passage secret, et on est jamais sensé sortir de la souche.

Now, there is another location with yellow jump-through blocks. This is no sweet secret passage. This is directly linked with the slope nearby. You see, depending on the controller used for a given state, we can decide to check for CAN_MOVE_THROUGH or CAN_FALL_THROUGH. And while we're walking along a slope, we don't try to fall.

Imaginons un instant qu'on ait un personnage occupé à monter cette pente. Il a un point de contact (en vert flashy) qui doit rester sur la pente, et une zone de collision (en bleu). Pour qu'un déplacement soit valide, l'ensemble des blocs sur lequel la zone de collision arrive doit posséder les bonnes propriétés -- par exemple "on peut passer à travers" ou "on peut nager dedans". C'est le principe de la fonction cando() dans mon moteur de jeu.

Mais dans le cas d'une pente, on voit clairement que la zone bleue peut "déborder" sur les blocs juste à-côté des blocs-pente. Si on les rends solides comme des murs, le personnage restera bloqué en bas de la pente... d'où la réutilisation des blocs au-travers desquels on sait tout faire sauf tomber.

(C) The UnDisbeliever
why we need more than slopes & blocks.
If we had instead a solid block at the end of the slope, the character would get stuck. It was perfectly detailed in the Undisbeliever's recent blog post on his SNES engine tile collision description. I tried to show the same with the zoomed screenshot, with a 'hot spot' in green, the 'cando(MOVE_THROUGH)' area in blue and the intersection of the jump-through tiles and that area with some pink-ish pixels.

I guess we could also have just used empty tiles for most of the rest of the slope (but the top, that is). I suppose I grew the habit of using jump-through tiles under slopes so that monsters work properly, especially those with check-points. If not, when they'll check a few pixels away from their hot spot whether there is still ground to walk on, they would wrongly consider they should turn back.

Ils permettront aussi qu'un personnage dirigé par une "IA" qui sonde la map autour de lui ne pense qu'il arrive au bord d'une plate-forme en trouvant un tile "creux" quelques pixels sous son point de contact.

Voilà, j'espère que c'était intéressant ;)


Wednesday, January 08, 2020

L'encodage de la physique du sol

Bon, j'ai pris un peu le temps de cogiter à ce dont j'ai besoin pour les nouveaux types de sols dans mon moteur de jeu. Je sais que je veux plus te types de pentes et plus de types de physiques, mais en relisant mes notes sur les pentes pour comparer ça à celles de Ishisoft, je suis tombé sur un os:

Si je veux des tapis roulants qu'on ne sait pas traverser, il me faut des tiles "physiques" avec la propriété "IS_WALL". Ou plutôt sans F_PLAYER_THRU. Cette propriété va être testée sur l'ensemble de la zone que le joueur (ou un ennemi si on regarde F_MONSTER_THRU) voudrait occuper après son mouvement.

I took some time to make a list of those 'special grounds' I'd like to have in my game engine. More slopes, that was an easy one. But while I was comparing my plan to Ishinsoft's tigsource post, I noticed a potential issue.

If I want to have conveyer belts that act as solid structures, I'll need physics-changing tiles that also have some IS_WALL property. Well, actually, that doesn't have F_PLAYER_THRU, since the 'cando' system allow move to some area only if a set of properties is true for all the tiles covered by this area.


Mais si je veux des plate-formes glissantes en jump-thru, il me faut des tiles "physique du sol modifiée" sans la propriété "IS_WALL" (donc avec F_PLAYER_THRU, cette fois-ci). Jusqu'ici, comme la physique "glace glissante" et "tapis roulants" sont dissociés, je pourrais m'en sortir en définissant aussi les propriétés de collision pour chaque type de physique.

I also want slippery jump-through ground, which requires physics-modifier tiles that act as plain air if you jump through them (so with F_PLAYER_THRU set). Hopefully, slippery ice and conveyer belts are distinct things, so I could just have per-physics-tile properties set and we'd be done.

Les ennuis commencent si je veux un mur solide avec des pentes et une physique particulière: à l'intérieur de la structure, il faut que des tiles avec 'F_PLAYER_THRU' pour qu'il n'y ait pas de murs cachés dans la pente. (Un vieux truc mais toujours indispensable avec 'cando' et ses flags) et sur les bords de la structures, il faut que je repousse les assauts des sprites trop entreprenant pour les obliger à attendre le sommet avant de s'installer.

ça me consommera 2 types de tiles physique. Ce n'est pas forcément un cas fréquent et on aura pas nécessairement besoin de dédoubler tous les types de physique.

Things really get nasty when you consider structures like Aladdin's sandfalls (we should have some in some Sonic games too). There are physics tiles (pushing the player to the left) involved and slope tiles involved. Some physics tiles will have to let the player thru, else they will get stuck into the slopes (that mostly happens in the middle of the structure), and other physics tiles will have to act as walls at the edge of the structure, so that aggressive speedrunners don't get through. I don't see shortcuts to workaround the situation: we'll need two separate tiles type here, despite they'll have the same effect on physics (push player to the left).


Du coup 1) il faudra regarder sous une pente pour avoir ses paramètres de physique et 2) ce sont les contrôleurs qui conserveront les informations dont ils ont besoins. Le coefficient de friction dans le contrôleur 'momentum' qui traduit les impulsions en vitesse et 'stopper', le fait d'être poussé dans un sens ou dans l'autre dans 'walker' et 'needground' etc. (on y reviendra).

Et pour afficher tous ces types (pas loin de 250 en tout), il me faudra plus que mes 16+n petits tiles utilisés actuellement. Je n'ai pas la place pour mettre tout ça dans la "zone libre" de caractères prévues pour les widgets (128 caractères, juste après les codes ASCII): je tombe dans l'espace prévu pour les écrans. Mais j'ai assez de place après les écrans. (oui, ça n'a rien avoir, mais j'avais besoin de le garder sous la main).

Thursday, January 02, 2020

Kirby's Dreamland

Permettez-moi de revenir en 1993. Jeune ado qui vient de commencer à envisager de laisser tomber son projet "Calimero" pour quelque chose de plus ambitieux avec pour personnage une boule bleue: Bilou. Puis mon frangin revient de chez notre jeune voisin avec un ovni pour Gameboy: Kirby's dreamland.

Fan de jeu de plate-forme, je fais d'abord la grimace devant le fait que le personnage peut voler à volonté: ça va tuer complètement l'intérêt du jeu. Puis je vois l'écran-titre, et je suis scotché par l'animation du personnage.

Pourtant le nombre d'images en lui-même n'est pas si énorme. Si on fait abstraction des sprites gonflés, c'est même assez standard pour l'époque. Mais avec un dosage parfait des versions "applaties" sur les murs et les petites étoiles qui soulignent les chocs au sol, cette petite variante ou Kirby (je dois vraiment me concentrer pour ne pas écrire "Bilou") plonge vers le bas quand il tombe depuis trop longtemps ... la magie prend. Et les petites "cutscenes" ajoutent juste ce qu'il faut pour que les temps de chargement deviennent une partie amusante de l'expérience.

Le jeu est court. 4 mondes, 4 boss. Pas de nette séparation de chaque monde en 'niveaux', mais plutôt une séquence de 'salles' entre-coupées par des portes ou des petites animations (faisant généralement intervenir une 'warp star' et un peu d'humour). Vient ensuite une sorte de "boss rush" précédé de salles "remixées" du niveau de chacun de ces boss avant d'affronter enfin sur son ring King Dedede -- qui pour une fois semble responsable des maux dont on l'accuse.

C'est le premier jeu Nintendo qu'on termine (heureusement, vu nos 15 ans?) et nous découvrons alors le mode "extra" que je n'ai toujours pas fini 25 ans plus tard. Si j'ai joué au maximum de 'vrais Kirbys' que j'ai pu rencontrer depuis (dédaignant les épisodes basés sur des pinceaux et des bouts de ficelle -- peut-être à tort), Kirby's dreamland a sans aucun doute gardé une place spéciale dans mon coeur et il est souvent arrivé que je remette la cartouche dans ce qui me tenait lieu de game boy pour me refaire une petite partie.

Si ça n'a pas été facile de faire apprécier le jeu à J.l.n (qui préfère quand Kirby peut attraper des pouvoirs) ou à *deline, les choses ont changé quand il s'est vu mis à l'honneur lors de la compétition de speedrunning francophone l'an dernier: l'ultime décathlon 7 que les enfants suivent maintenant avec avidité.

Voilà. Ca faisait bientôt 10 ans que je voulais prendre le temps de parler de ce petit jeu. C'est chose faite. Et si je m'y mets enfin, c'est qu'en voyant J.l.n aller jusqu'à Lololo/Lalala hier, je me suis dit qu'il y avait quelque-chose d'intéressant dans la formule "Dream Land", et que je devrais peut-être bien m'en inspirer pour faire un Bilou Dream Land, techniquement moins ambitieux que "Infinite Pyramid" et probablement faisable en 2 ans si je m'y prends bien.

J'ai donc repris les maps du jeu entre deux motiliums pour voir un peu dans quelle catégorie on joue.

Green Greens : 26 écrans, avec des zones relativement grandes et des environnements simples et ouverts

Lololo Castle : environ 30 écrans. Nombreuses petites zones inter-connectées. Principalement à l'intérieur avec une tendance à désorienter le joueur.

Floating Islands : 36 écrans plus quelques salles bonus et des cutscenes. Mélange intérieur/extérieur tout en restant dans le thème 'île aux pirates'

Bubbly Clouds : probablement aux alentours de 42 écrans. avec une progression de "pays des nuages" vers "monde des étoiles" en passant par le panthéon grec perché sur les nuages.

Si mes derniers chiffres sur le nombre d'écrans est douteux, c'est que pour les petites salles, les gars des studios HAL ne se sont pas fixés sur des nombres d'écrans entiers. On se retrouve ainsi dans le Lololo castle avec des salles de taille 256x192, 320x192 et 384x128 pas franchement alignée sur l'écran 160x128 du jeu quand il tourne sur game boy.

L'idée pour la version Bilou serait de recycler donner vie à un maximum de niveaux du jeu "Bilou's Adventure" prévu à l'origine sans entrer dans les niveaux "souterrains". Pas encore de temple perdu, de zone des insectes-de-chantier ou d'océan musical donc. Je garde la zone du désert vu les récents travaux sur les monstres en guise de "floating islands", et il me reste à trouver un bon environnement (probablement montagnard ... l'influence de Céleste) pour jouer le rôle de zone finale.

Il faut aussi que je décide ce que je fais pour les Boss. J.l.n voudra des boss à affronter, mais ça va clairement augmenter le temps de développement. Le jeu perdra tout intérêt si les enfants ont passé l'âge limite pour jouer à Fortnite d'ici qu'il soit fini.

Wednesday, January 01, 2020

TileMap

I had a remark in my notebook, wrote a few months ago, where I felt it weird that some parts of the game engine would require a SpriteSet where they actually needed information about the level map, and that the SpriteSet class was actually the only object capturing the map pointer, as well as its dimensions.

I then realised there was several places in the code where you'd load a map from a file, and it was tricky to use only one of them because one is within SpriteSet and the other (in the level editor) has no use of such a SpriteSet. It was time to introduce a new class with informations about a loaded map. Refactoring things went pretty fine and I could even repair a few things thanks to the 'unit-testing' tools (and thanks to the 'watchpoint' feature of ddd).

Well, I realise that doesn't mean a lot. That's all I could craft since I'm on holiday, I'm afraid. Happy new year.

edit: now, with only one class doing .map - to - memory conversions, I can make a new unit-testing tool that operates on level maps. I stuck to the idea of having some 'generic' tool invoked in scripts to define test cases based on reference data, but I pushed it on step further than 'cmdck' I wrote in early 2019: 'mapdo' handles a stack of level maps in the way a RPN arithmetic evaluator handles a stack of numbers. And you can quite easily define new 'operators' that act on that stack. It will help for things like cropping, splitting, patching etc. and can also help for tests such as "load level1.map save test1.map load test1.map eq" to check the level hasn't changed after being saved-and-reloaded (e.g. on-the-fly newmeta conversion produces the same contents as PHYS chunk loading).
  • by just passing "show" as the constructor argument to CommandHandler, this handler will be invoked whenever we see "show" on the command line
  • CommandContext.maps is the stack of level maps,
  • CommandContext.next() provides one token from the command line, for both commands-to-handlers lookup or for command arguments.
  • it could be further simplified into a void run(cc) since we no longer need to communicate the amount of command line tokens we consumed.

I love how both the core loop and the handlers are elegant, although I'd have loved a more in-line syntax like case "show": or show=>sub { ... } as well, but they wouldn't have worked in C++, would they ?

Friday, December 27, 2019

Leilani slopes

The tweet popped while I was doing mental checks on my revised tiled engine, and especially how it would handle more slopes than the previous one: Ishi(soft) finally decided to introduce slopes in Leilani's Island. And while I was looking for convenient ways to encode more than one array of tile_height[type][offset], I was surprised that his approach wasn't not requiring any such array.

Bon, le tweet est tombé comme un pavé dans la mare pendant que je réfléchissais aux problèmes que le mélange des pentes et des blocs à la physique modifiée risquait de poser: on peut aussi faire des pentes sans avoir pour autant besoin de modifier son moteur de jeu pour y mettre des fonctions du genre "doslopes". C'est ce que nous propose Ishi dans sa prose décrivant le moteur de jeu de Leilani's Island. Et que faire d'autre si on ne veut pas simplement faire un clone de Mario 1? Eh bien par exemple générer à la volée des "ascenseurs" sur lesquels les personnages vont se déplacer et dont la vitesse verticale donne l'illusion qu'on suit bel et bien la pente.

(C) Ishi
Instead, the world-collision engine would detect sloped ground and generate sort of "lift" objects that are tied to a specific X location, but adjust their Y location to the X position of the character that triggered them. It allowed enough flexibility to make me think twice before dismissing the approach. One of the benefit of the approach is that the code running the is-there-ground check still happily believes the character have solid ground all around them:

This fixes the problem with enemies turning around on slopes. When they do their collision check, they find solid ground in front of them - because as far as they're concerned, they're on a flat surface.

There was one major restriction, though: Ishi decided to go for a single type of slope (22.5°) where X-to-Y offsets function can easily be computed by shifts and additions. But I personally want more. More angles and some curved ground as well. We could still have that in Ishi's engine, but it would require picking a tile_height[offset] array according to the slope type and pass it to the "lift" object.


C'est séduisant, c'est sûr. Mais ce n'est pas allé sans mal pour autant, et à la lecture de tous les "corner cases" qu'il a dû prendre en compte, je ne suis pas certain d'avoir envie de jeter mon implémentation actuelle pour suivre son exemple. Entre autres, une des choses qui permet à Ishi d'éviter de devoir utiliser plusieurs types de pentes, c'est qu'il s'est limité à un seul angle (22.5°) pour lequel on peut facilement déduire la position verticale de l'ascenseur à partir de la position horizontale du personnage à l'intérieur du bloc. Mais moi je veux plus de variété. Il faudrait donc du coup plusieurs comportement d'ascenseurs et un code qui produit le bon à partir du type de pente que l'on a rencontré.

And the approach apparently has its own corner cases that need to be addressed, sometimes with special edge-of-slope types. The approach I'm using so far has its own corner cases, which were mostly resolved by using jump-through tiles (which are also walk-through) next to sloped tiles to avoid introducing artificial walls within the slope. That's precisely what needed more thinking when introducing "physics modifiers" tiles.

Well, anyway, don't miss the full post on Ishi's tigsource thread. It's very detailed and informative.

PS: it feels a bit odd to write a post about someone else's work here, but I've kept re-visiting the browser tab with the forum with the hope that I wouldn't forget it if there's anything important to find there ... for one week or so.

Tuesday, December 24, 2019

Ori and the difficulty curve

If you played Ori and the Blind Forest, I bet you've immediately recognized this place. If you haven't welcome to "how a simple puzzle can be as hard as beating a boss". It's not that the riddle is hard to figure out: there's a corner you'd like to reach locked with a door that you can only open by relaying some shot from the opposite corner of the room. You have the ability to redirect the shot, and there are corner-shaped things you've encountered before that redirect the shot as well.

Eh bien, elle m'aura donné du fil à retordre, cette salle de Ori and the Blind Forest!Pas tant que l'énigme soit difficile, mais l'exécution doit être impeccable. Si passer du premier au deuxième point de "rendez-vous" n'est pas trop tendu vu la vitesse (lente) du projectile qu'on relaie, le deuxième est déjà plus délicat: les sauts sont plus longs et s'attacher aux "lanternes" demande d'abord un double saut. Bref, il aura fallu que je maîtrise la nouvelle compétence du 'BASH' pour y parvenir. D'abord en prenant conscience que je n'étais pas obligé de me trouver au-dessus du projectile pour l'envoyer vers le haut. Je peux me positionner tout simplement au sol, là où il est possible de bien voir le moment où je dois dévier le projectile.

No, what makes it tricky is that it requires precise (perfect?) execution with proper timing despite that nothing in the level layout provides you grids or guides to time or aim things. And you've got to be quick because every time you move from one spot to another, you're racing against the same shot, all over the room. In other words, you enter the room with knowledge of how the BASH mechanics work, and you only leave the room when you have mastered the mechanics.

Through my quest to mastery, I discovered that (1) I wasn't required to be mid-air over the shot to throw it up, and (2) that I wasn't forced to use the analog stick to redirect the shot.. Standing still and pressing the BASH button with the right timing does the job. And the 'dpad' of the left joycon provides higher precision under stressful conditions (like re-doing it for the 20th time), when all you need to do is directing something vertically or horizontally. For the rest of the game, having an analog stick is precious, but puts too much stress on the dexterity of the player.


I was expecting some relief after such a boss-time but actually no! It's even getting worse: I got trapped in an escape event that requires full mastery of almost all the mechanics we've been taught so far. And I failed. A lot. Like 30 to 40 times, split along a whole week. I started believing that the BASH mechanics lacked proper tutorials and that I'd have preferred to face a "GAME OVER" and start the game afresh to find secrets that might bring me there better prepared.