Showing posts with label tiled. Show all posts
Showing posts with label tiled. Show all posts

Saturday, August 23, 2025

Une pente qui tombe à l'eau

I had tiles that push you along a slope. They've been shown in the sands mostly, but more importantly, they've been tested in a setup where there's plain ground at the bottom of the slope.

Here, I'd like to use water falling down a slope as a way to implement a one-way region somewhere in a cavern of the green zone. But it didn't quite worked as intended. What makes Bilou slide down in such a setup is that a test-point queries the tile number under his feet and use that to look up a physics values array. But once you get down the hill, the hotspot gets in the air, and nothing finishes to push you out. 

Il y a sous l'arbre creux une sorte de pente rendue glissante par une cascade sous-terraine. Mon frère l'avait utilisée pour créer une sorte de sens interdit entre deux morceaux du niveau. Alors j'ai déjà des pentes, donc ça aurait dû être une simple formalité, mais comme vous l'aurez sûrement deviné par le titre, ça n'a pas été sans mal Premier couac: en voulant rendre ce "tapis roulant" plus rapide que les chutes de sable, je suis sorti des limites d'un tableau, ce qui a eu pour effet d'appliquer l'effet geyser à l'ensemble du niveau ^^".

Plus contrariant, une fois arrivé en bas de la pente ... rien. On ne tombe pas dans l'eau, on reste juste là, sur le rebord. Parce que c'est le pixel sous le *milieu* de Bilou (le "hotspot") qui détermine si on se trouve sur un tapis roulant mais qu'il faut qu'il n'y ait plus rien sous lui sur toute sa largeur pour qu'il commence à tomber. Je dois donc rajouter un autre type de tile qui pousse comme un tapis roulant tout en étant pas du sol.

So I added another tile that behaves like air and keeps pushing you as if you were on sliding ground and put them at the bottom of the slope, but even that wasn't enough to get it working. It would take you as far away from the slope as you want, but then, once your hotspot is in plain air, you'll stop, happily idling in the middle of nowhere.

The part I was still missing was the F_START_FALLING property to my new tile type, something introduced so that we could land in slopes properly by having slope tiles letting you keep falling into them (until the hot spot reaches the ground) but not start falling through them when you're walking.

Il restait un dernier soucis. Un couac lié à un changement plus récent et qui fait qu'après avoir bien emmené Bilou au milieu de nulle-part, il ne tombe toujours pas. tout ça parce que j'ai donné au nouveau tile possède bien la propriété F_FALLTHRU, utilisée pendant que Bilou tombe, mais pas F_START_FALLING utilisée pendant que Bilou est au sol pour voir s'il pourrait tomber ^^"

With that new bit set, we finally have it: Bilou keeps sliding until there's no solid ground, and there  it cando(START_FALLING) and ends up in the water :P

There's one odd thing going on with the way the water slide is made as a conveyer, though. Conveyers just move you and don't affect your speed, and that's on purpose. But that means that as you're running full-speed against the water flow (if you manage to) and decide to jump, you suddenly move in the air at top speed while you were almost idle on the ground.

Maybe I should mimmic Sonic's water slide and make a dedicated "woah! I'm sliding down!!" state for Bilou as well.

But even then, when Bilou is walking against a conveyer, it is natural that he's walking faster than he's moving ... against water, we all know there's a force slowing us down, so it'd make sense to see his animation slowed down and pixel-fitting its motion against the water ... which would mean acting on his speed rather than shifting his position ... Meditate on this I will. 

Saturday, July 12, 2025

Mario in Godot

Refaire Super Mario World dans le game-maker "Godot" ... j'ai envie de dire à la fois "tout ça" et "rien que ça". Pourtant, l'idée de Wye n'est ni de proposer un Mario World Studio ni son Mario World 3. L'idée, c'est de comprendre le fonctionnement du jeu d'origine en le reconstruisant dans un nouvel outil tout en apprenant l'outil lui-même. Et ça, ça me parle. C'est le genre de bouquin que je dévorerais mais ici, ce sont des vidéos youtube.

"In Super Mario World, Mario was considered fully underwater if both his head and body interaction points are touching water tiles. [...]

That's the kind of things you can learn by watching Wye's series on re-making Super Mario World in Godot, and learn how to use Godot in the process

I've been following the videos roughly since episode 1 or 2, but as Wye now address the water physics, I cannot just remain a silent watcher. I've spent too much time working on water physics myself, and I need to compare the approaches. Of course, Mario isn't using the "cando"  function. Instead, interaction with the world are guided by "interaction points" which remember the type of tile they're on. So for instance, 

"When Mario body is in the water but not his head, that means he is near the surface."

Du point de vue des collisions avec le niveau, Mario n'est pas une boîte. Il est ... une sorte de constellation de points qui ont chacun leur préférence sur le type de terrain 8 en tout, mais dont certains ne seront évalués que dans certaines conditions. Pour déterminer s'il faut nager ou non, ce sont par exemple ce sont les tests du milieu et de la tête qui entrent en ligne de compte. Et pour déterminer si on peut sauter hors de l'eau ? Bin il faut que le corps de Mario soit dans l'eau mais sa tête hors de l'eau. On évite en fait les complications du type "la surface est à la fois de l'air et de l'eau" dans lesquelles je me suis embarqué.

And only when Mario is near the surface is it possible to jump out of water ... and only if pressing UP in addition to pressing the JUMP button ...

The concept of interaction points comes from the disassembly of SMW itself and was discussed in an earlier video. They replace hitboxes when it comes to interacting with the world and there are 8 of them for Mario. But as usual with 16-bit games, not all points are tested on every frame. What is interesting is that rather than testing e.g. "left side" when moving left, the game tests "left side" only if the left side is "smaller" than the right side, as a way to detect "new" things, assuming that what covers most of Mario's box has been tested in the past. "When Mario is on the right side of a tile, the two points on the left are ignored" is possibly a better way to describe what happens, indeed ^^".

I'd be curious to find out to what extent that keeps working where your level grid isn't exactly one hero wide ...

edit: while you need to *press* the jump button to make Mario jump when he hits the ground from a fall, you just need to be *holding the button down* when reaching the water surface to trigger "jump out of water".  (from the "extra bits")

Thursday, February 13, 2025

Hang on!

It's one of the key mechanics I'd like to bring into my new game: being able to catch hanging things and hanging as well. It was a fun move in Commander Keen, it was amazingly expanded in Fury of the Furries and Mickey Magical quest ... And last week-end, I went on to try and get it prototyped.

I started with a summary of how Bilou can hang below spongebops in School Rush ... something we don't see a lot in the blog, mostly because Bilou is supposed to *ride* the sponges, not hang under them. Hanging was added as an "else" branch of another "else" branch:

when you hit the "HANDS" button mid-air, Bilou does a roll move trying to grab what's nearby. that is AIRGRAB state. The original idea was to test what was *below* him and either pick-up a dumblador or ride a spongebop... but the move shows the hand grabbing in front and rear directions as well ... so it would feel natural that Bilou could grab a sponge even if he's on the same horizontal line, like in the sketch on the left. Yet, snapping Bilou on top of the sponge from this location felt glitchy.

Bon, j'ai un p'tit Bilou suspendu sur une page de mon cahier-agenda depuis le début 2024 ... j'en ai un autre sur la bannière du blog ... je crois que l'appel du pied est assez clair: il serait temps de prototyper un peu ça, d'autant que cette fois ci, ce ne sont pas les coins de niveau où s'accrocher qui manquent dans le livre-secret-du-level-design.

Et pour "commencer simple", l'idée était de faire subir à Bilou le comportement des éponges. tout en réutilisant un des états codés pour l'interaction avec les éponges: rester suspendu (la feinte). Chose qui m'a tout de même demandé de me replonger dans pas mal de gobscript, parce qu'à la base, il est prévu que Bilou s'accroche *par-dessus* les éponges, et pas qu'il pende en-dessous. Le côté "pendu" n'a été rajouté que dans les derniers 20% du projet, entre deux séances de playtesting parce "bin, oui, c'est vrai au fait: pourquoi Bilou ne pourrait-il pas rester suspendu s'il était trop bas pour escalader l'éponge au moment ou la joueuse a appuyé sur (B) ?"

So instead, Bilou will enter AIRHOP move, getting an automatic double jump and automatically tracking the sponge's position. That feels fairly naturally when playing, that gives you a sort of "fair second chance" but it might fail anyway: you may fall below sponge's position again without having reached the "riding spot", with no hope of catching up. and *that* is the time when we finally switch to HANGING. 

There will be other objects to hang to in the game, especially bookmarks, vines, and the like. Some of them could be entities like spongebop (maybe moving a bit less), but sometimes it might also be nice to have tiles you can hook to. I have code to make some tiles react to collisions with the player, but that was not enough: the hitboxes used to detect spongebop explicitly search for baddies. And because I'm testing 3 areas over the course of the animation, I'm out of options to also test for friendly areas (which has been the case for all pick-ups so far ^^"). But hitting a tile can trigger an animation, and it can spawn an entity with proper hitboxes so that Bilou could hook to *that*. You couldn't really hook to a tile, anyway, because the BlockArea created during the collision test is immediately recycled, so you could not attach to it

Mon idée était ensuite d'utiliser un type de bloc spécial pour servir de point d'accroche, et d'aller "poker" quelques blocs de ce genre à des endroits bien sentis de la map "green" de la démo. ça non plus, ça n'a pas fonctionné comme j'espérais. Entre autres parce que quand Bilou cherche à s'accrocher à quelque-chose pendant sa pirouette-en-l'air, il cherche parmi la liste "EVIL" ... et les blocs interactifs, eux se présente automatiquement comme des "HERO".

Bon, ce n'est pas bien grave: puisque je voulais que Bilou s'attache au point d'ancrage, il fallait de toutes façons que j'introduise un GOB (game object) généré à partir du bloc spécial, un peu comme les scintillements qui suivent Bilou quand on ramasse un smiley bleu. Ce gob pourra être dans la liste que l'on veut. Pour éviter qu'on ne génère des quantités ridicules de gob-points-d'ancrage, on va (enfin) utiliser une animation de la map elle-même pour désactiver puis réactiver les propriétés du bloc pendant que le gob est présent. Exactement le comportement des blocs-question de SuperMario quand on les coup-de-boulise.

So the plan was the following: a new special block that would react to GRAB action. When that happens, it trigger the animation that actually leaves graphics untouched (with "spr0 ffff") but turns the physical tile into "plain fall-through" for 5 frames, and then restore the tile as "block 07" (thus interactive) again. Quite the kind of thing that SuperMario question blocks do on a daily basis, actually.

(It turned out meanwhile that using sprite 'ffff' did not work as intended, so I ended up adding a bbox statement to that animation -- effectively making it a special 8x8 tile rather than a 16x16 special block -- and fixed the code that plays mapanim so that "props" instruction is executed immediately rather than cached until the next "spr0" instruction)

anim9 spr:2 {
   spr0 7      # visuals for the "tile replacement",
   delay 5     # here just a Bilou hand ...
   done
}

state63 :anim9 {           # behaviour for that hand :
   using freemove          # just stay there, 
   area 0 (0,0)-(8,8) 1010 # accept GRAB hits.
}

state62 :anim9 {   # should have been the persistent state when Bilou hooked...
   using freemove
}

state63->state62 on found 0 [wc $1010 ?] (t) # detecting the actual hook-up hit
state63->nil on done                         # and disappear otherwise

using shgob(state63 is e:=1:6) as 6          # a special action to spawn the hooked hand
                                             # oh ... and it's an evil hooked hand.
block 06 {
    is gndhook "ff3c7cfebe2a2a00"  # the special tile 
    area (0,0)-(7,7) 1010          # will also detect GRAB hits
    on hit [t] (x6) :anim6         # and the 'x6' will invoke shgob registered 'as 6'
    props fc0
 }

That should have done it. I could see the hand showing up, and getting away, but I couldn't ever hook to it. I couldn't get an evidence of why it failed, but I believe it is rooted to the fact that bilou's own bbox must overlap the tile in order to trigger the "on hit" transition. but since all the hitboxes with the GRAB flag tend to be off-center, the second collision (with state63 entity) doesn't occur because there's no overlap.

Alors oui, je vous balance le script directement dans le browser. Parce que ça ne fonctionne pas. Enfin, après avoir corrigé la gestion de l'animation pour ne pas dépendre des changements d'affichage (qui expliquent le bout de terre manquant dans le screenshot là-haut), ajouté une "bbox" à l'animation (pour que le code sache qu'il ne doit manipuler que 8x8 et pas 16x16), je finis par avoir un objet qui est créé tandis que le bloc devient inactif puis se réactive ... le hic, c'est que il faut pour ça que le "corps" de Bilou soit en contact avec le bloc interactif alors que ce sont ses *mains* qui vont essayer de l'attraper. Et la zone à attraper est réduite à 8x8, de toutes façons mal positionnée... bref, je n'y arrive que 1 fois sur 5-6, donc le joueur n'y arrivera pas.

Il vaut bien mieux que je me décide à dessiner un graphisme dédié (une racine ?) et que je fasse juste un GOB dédié... il y aura des import/exports mercurial à faire ... 

At some point I thought "hmm. I'll just have the little tile be a 8x8 hitbox in the middle of a 24x24 special box, that will do the trick", but it wouldn't work either: that 24x24 region around the corner of a pillar (for instance) would have "jump-through ground" on one tile, and "air" everywhere else... how could a single "props" instruction fix that ? okay, I could introduce "props(-1,0) fc4" to change one specific tile of the block but that sounds like the coding equivalent of "just stab me now" ... 

So, likely, I should forget about that "hook to tile" thing, accept that there's very little hookspots in the Dreamland levels anyway and kickstart my NDS to draw some root sprite in green.spr ... 

Sunday, May 28, 2023

special properties

While thinking at 'how to implement conveyer belts', I end up investigating some currently incomplete code in special blocks handling. They have correct implementation of their collision areas, but since the migration to the new tile types engine, they can no longer act as 'doors' because they all have the same cando() properties.

There's some code in the parser to change that, and some code in 'FlatWorld' to return BlockInfo::props when we encounter one ...

Wait ... BlockInfo::props is precisely what this last `siscanf()` updates ... so ... that means the code is not incomplete at all ? it just needs to be tested ?

Saturday, January 29, 2022

Les bonus s'emmèlent...

J'étais tout content de voir que Bilou savait aller dans l'eau, et je ne me suis pas rendu compte qu'il y avait un soucis avec les bonus: on pouvait carrément marcher dessus. Quelque-chose lié à la nouvelle propriété F_START_FALLING, sans doute ... sauf que non. c'est surtout lié aux bytes 'regarde à côté'.

C'est que l'ancien moteur de jeu utilisait le numéro du 'bloc spécial' pour décider s'il devait être solide ou non, et la fonction qui indique la 'hauteur du sol' essayait toujours de faire comme ça. L'ennui, c'est que j'ai utillisé les codes 0xfc à 0xff pour les fameux 'regarde à côté' qui permettent aux blocs spéciaux d'être des blocs, et pas juste des pavés de 8x8.

Jan.24. Managed to have Bilou switch to 'swim' state when it gets in contact with F_WATER tiles. It's as swimple as $LFALL->$INWATER on fail [H WATER ?] ...

Jan.25. It uses the new H GobExpression 'operator' that puts type of the tile under character's hotspot on stack, where it can later be compared with a constant flag (WATER). That operator will expect an elevation value on the stack first. like [2 H WATER ?] to tell "check 2 pixels above the hotspot"

But this is not the point of this post. The point is, looking at the video I captured (bottom left on the toot panel below), I noticed I was walking on disappeared apple before going into the water.

Jan.29. When you pick up a bonus, a MapAnim is spawn, that will edit graphics to get that sprinkling animation. It should clear "properties" as well, as soon as you get in contact with the collectible.

Yet, the change to newmeta changed what 'cleared' means on the map. Now, the clear value 0 means 'lookup World::properties[0] to know what you can do'. Another trap was that, when checking ground height, you might find yourself on one of the new "lookup one tile left" or "lookup one tile up" that are used to make special *blocks* of adjustable size (not just special mini-squares of 8x8 pixels). If that happens, code was still lacking "locate the actual block defining corner" and then use that special blocks's properties set to decide whether it has ground or not.

Pas le choix, donc: ici aussi, il faut retrouver le 'coin actif' du bloc spécial et aller chercher ses propriétés dans le BlockInfo correspondant.

Deuxième farce (voir l'animation): une fois le bonus effacé, il a laissé derrière lui un bloc à travers lequel il est possible de continuer à tomber, mais aussi de continuer à marcher. La faute cette fois au tableau des propriétés pré-encodées pour les blocs à définition indirecte (prévus pour les physiques particulières, essentiellement).

edit: if you don't have a twitter account, here's what the videos looked like:

Wednesday, January 12, 2022

Slopes Landed.

I think I got it working. There was one major flaw in my earlier design: stating that you can F_FALLTRHU a slope implied that you could no longer walk on it, as the walking controller tests for solid ground by checking whether it would be possible to fall down from the current location. Oh, not much. Just one pixel is enough to claim that you cannot walk anymore.

Là, je pense que je le tiens. Il y avait un défaut majeur dans mes premiers essais: considérer qu'on peut tomber à travers une pente en lui donnant la propriété "F_FALLTHRU" implique automatiquement qu'on ne peut plus marcher dessus: le contrôleur "walker" s'assure que le sol est solide en testant s'il serait possible de tomber à travers ... pas beaucoup: juste quelques pixels, mais si ça marche, ça suffit pour indiquer que marcher n'est plus possible.

Mais la technique utilisée pour les collision avec le sol (le "cando()") part du principe que on ne peut faire un déplacement que si l'entièreté de la zone concernée permet ce type de déplacement. On ne peut nager qu'entièrement dans F_WATER, on ne peut grimper qu'entièrement sur F_LADDER, etc. Mais donc un tile de type "pente" doit à la fois nous permettre de tomber à travers lui (au moins jusqu'à atteindre l'altitude du sol) et nous empêcher de commencer à tomber. Le seul moyen de réconcilier les deux, c'est de dédoubler le flag: avoir un bit indiquant "autorise le début de chute" (F_START_FALLING) et un autre "autorise de continuer à tomber" (F_FALLTHRU). Un pour le contrôleur de marche (walker), l'autre pour le contrôleur de gravité. 

But the technique used for terrain collision detection -- cando() -- assumes that we only do a move if we can do it over all the tiles covered during the move. That means the slope tiles should both allow us to fall through them (until ground height, at least), and not allow to start falling through them. To get that solved, I had to split the flag, having one bit telling whether we can start falling (F_START_FALLING) and one telling whether we can keep falling. Walker controller tests one of these bits, gravity controller tests the other one.

Then I had another issue, not properly computing the ground distance to see whether the move we cando actually remained over the ground. Let Bouli explain that...

We were at old position (ox, oy) and will move our 'hot spot' (the one that is kept in contact with the ground on uneven grounds) to (hx,hy). In order to know whether we're find with slope-landing, we compare hy - oy with the 'ground distance', which is construct with an appropriate sum of tile.groundheight() calls.

But groundheight() gives us a value relative to the bottom of the corresponding tile. -8 means the whole tile is solid. -2 means the first 2 pixels of the tile are solid, the rest is air. 0 means the whole tile is air. That was quite quickly remembered and accounted for. The other part to take into account is that the start of the darkblue 'vertical motion vector' may be anywhere within the first tile. If we're in the 4th pixel of the tile and the last pixel of the tile is solid, then only hy - oy < 3 are uninterrupted moves.

Mais ce n'est pas de ça que Bouli veut vous parler. Une fois le problème des propriétés réglé, il reste à calculer correctement la distance jusqu'au sol. Supposons qu'on parte d'une position (ox, oy) et qu'on veut déplacer le "hotspot" (le point qui doit rester en contact avec le sol dans une pente) en (hx, hy). Déterminer si on a atterri sur une pente, c'est comparer la distance hy - oy avec la distance jusqu'au sol, à partir des résultats de tile.groundheight().

Seulement, ce nous donne une altitude par rapport au bas du tile. -8 pour un tile entièrement solide, -2 pour indiquer que les 2 pixels du bas sont solides et qu'il y a ensuite de l'air, 0 pour un tile constitué uniquement d'air. Une fois qu'on a relu un peu sa doc, c'est vite traité. Par contre, le début du vecteur-mouvement (en bleu foncé sur le schéma) peut se trouver n'importe où dans le tile de départ. Si on est au 4eme pixel d'un tile dont seul le dernier pixel est solide alors seuls les mouvements tels que hy - oy < 3 pourront se faire entièrement.

Sunday, January 09, 2022

Land on Slope

There are many things that got fixed over my holidays, and many things that remain to be done before I can claim the 3 rooms "done". I had to pick one for the last week-end ... it seemed wise to pick something that I'd likely not have enough focus to work on during the evenings of a regular week. "Land on slopes" seemed the right one to pick.

Il y a pas mal de choses qui restent à arranger avant de pouvoir cocher la case "démo avec les trois salles de test", et ça malgré toutes les choses qui ont pu être corrigées pendant les vacances. Alors pour ce week-end, pourquoi pas s'attaquer à l'atterrissage sur des pentes ?  

So far, only the "walk" (behaviour) controller is aware of slopes. For the rest of the code, the world is all made of square tiles, and the properties of a tiles are homogeneous over that square. The result is that if you try to land from a jump on sloped ground, you're very likely to end up floating over the ground until you start walking.

Jusque là, le seul bout de code qui s'intéresse au pentes, c'est celui qui contrôle le comportement "marcher". Pour le reste du moteur, une pente, ce sont des pavés carrés comme tout le reste. Du coup, si vous faites un p'tit saut pendant que vous étiez sur une pente, il y a de fortes chances que vous finissiez en flottant au-dessus du sol ^^". 

J'ai passé la journée d'hier à faire des va-et-viens entre le débuggueur et le compilateur pour essayer de comprendre ce qui se passait, mais c'est sans espoir tant qu'on ne saura pas tomber à travers les tiles qui constituent les pentes... A cogiter.

I gave it a first try yesterday, opening the debugger, seeing what happens when I'm entering such a tile, designing a patch, compiling it, trying it, discover that it wouldn't work, refine it, and repeating the cycle. Over and over.

It wouldn't work. First because the slope tiles couldn't be fallen through. There's no need to try working around it: they *must* be made fall-through. Second because slopes are currently complemented by walk-through-but-don't-fall-through blocks that ensure smooth walk, but interfere with falling. I had that discussed with the Undisbeliever in the past, as it was a difference between our implementations, and the solution will be to replace them with a 'sloped' ground that actually is square.

But the ultimate reason of my failure is that I was trying to provide a solution for 'fall on the sloped ground' as if we'd have the last row of tiles all made of F_SLOPE tiles. The reality is much more diverse: there are so many 'corner' cases that we can hardly call them 'corners' at all.

Mais même avec cette nouvelle propriété et même avec des blocs carrés qui disent que "oui, en vrai, on est des pentes, mais des pentes plates. haha", ça ne marche pas encore. La solution que j'avais prévue suppose que le sol sur lequel on veut atterrir est constitué d'une brave rangée de tiles de type "pente". Mais comme vous pouvez le voir sur mon petit croquis, il y a pas mal d'autres combinaisons qu'il faudra aussi prendre en compte. 

J'ai une idée pour remplacer ça ... il faudra que je la teste. Elle n'est pas sans me rappeler (ce que j'ai compris) du moteur de Sonic, d'ailleurs: considérer toute la colonne de tiles par-dessus la nouvelle position souhaitée pour le "hot spot" et calculer la "hauteur du sol" dans cette colonne.  

I have a replacement design sketched, which I'll give a try in the afternoon. Amusingly, it looks a lot like what (I've understood) happens in Sonic the Hedgehog engine: consider the whole column of tiles on top of the desired 'new hot spot position' and figure out the 'ground height' in that column. Then make sure the move planned so far doesn't work past the ground.

Do that every time. If done right, it doesn't matter how many sloped tiles were encountered when checking that we cando() the move.

Well, that was the plan, but for some reason, it is not yet quite working. And for some (possibly other) reason, it managed to break walk-on-slopes despite my care to avoid so.

edit: fixed

Saturday, February 13, 2021

Higher Order Blog ...

Of course, it's been a long time since I first read the guide to implementing 2D platformers on the Higher Order Fun blog. I believe it's been part of my research on how people implement slopes together with the MC Kids talk on implementing sophisticated platformers on NES. So somewhere around 2007.

Un tout bon blog, qui continue à nous proposer des conseils pour implémenter un jeu de plate-forme 2D depuis facilement 2007, époque où j'ai dû le rencontrer pour la première fois. Mais en repassant dessus il y a quelques mois (magie Twitter), je me rends compte qu'il y a encore un bon nombre de fonctionnalités discutées que je n'ai pas encore implémentées dans mon moteur à moi (échelles, plate-formes dont on peut redescendre), et d'autres qui sont en cours de révision (pentes, plate-formes uni-directionnelles). Je repasserai dessus dans les jours à venir (j'espère)

There's still a number of topics I haven't implemented in GEDS that are discussed in that post, including ladders, refined slopes and one-way platforms. So I finally printed it all so I could take the time to read it quietly and take notes on what was neat and what needs more thinking. That was over 6 months ago.

So unfortunately, the toughts aren't fresh in my mind anymore. I'll have to read them back *too* before I can talk about it more.

The blog post presented different ways to implement a platformer. Tiles-based, of course, as it was de-facto technique used on virtually all 8-bit and 16-bit consoles that used tiles-based hardware (and still an efficient technique on page-flipping hardware), but also vector-based levels (only mentioned, though) and bitmap-based. I wish there was more being said about the techniques used with bitmap-based level modeling, as I believe it was the technique used in Fury of the Furries. 

Mais une particularité de l'article de Rodrigo Monteiro, c'est qu'il ne se limite pas aux jeux basés sur des tiles: il discute aussi le cas des niveaux modélisés par des vecteurs (Braid), à positionnement forcé sur des tiles (Prince of Persia, Flashback) et basés directement sur un bitmap (Worms). Et là, je trouve qu'il a peut-être été un peu léger en oubliant de mentionner Lemmings. Bon, d'accord, l'article est bien plus ancien que le talk de Mike Dailly présentant le fonctionnement (et l'éditeur de niveau :P) de Lemmings 1 et 2. Bon, on est d'accord: ni Lemmings ni Worms ne sont à proprement parler des jeux de plate-formes, mais ils restent en vue latérale avec de la gravité, et à voir la physique du personnage dans Fury of the Furries, je dirais qu'on pourrait bien en avoir un troisième.

I also know for sure it was used in Lemmings for levels up to 5 screens-wide (that would be about half the conventional RAM available on an IBM-PC, btw). 

Since the storage space on floppies was limited, levels weren't stored as a large bitmap, though: they were stored as a set of 'paint brush A at location (X, Y)' plus a set of (deluxe paint) brushes -- think of them as 256-color stamps The level editor calls them 'rocks' and reports how much are used so far. Some brushes may even be used as 'clearers' instead of 'drawers', exactly like what is seen in the New Super Mario Bros. level editor.

A true puzzle for those who later had to port DMA Design's success title to tile-based consoles of the time, if you ask me. But it allowed complete freedom that was precious to design appealing and challenging levels. Lemmings 2 was later developed based on an editor that aligns everything on a grid to ease porting to Nintendo and Sega (?) consoles, and while it had charming new themes, I never felt them attractive the way the original Lemmings were to me.

A noter que traiter la map comme un gros bitmap n'implique pas forcément de stocker la map comme un gros bitmap. En fait, pour la majorité des machines d'avant le CD-ROM, ç'aurait été tout simplement impensable même avec des niveaux de taille modeste comme ceux de Lemmings (5 écrans). Pour faire tenir tout ça sur une diskette, les gens de DMA design ont construit leur niveaux sur base 1) d'une palette de 'brosses' (des bitmaps de taille quelconque provenant de Deluxe Paint) et une série de commande 'ajoute la brosse A aux coordonnées (X, Y)'. L'éditeur permettant d'ailleur de garder une idée du nombre de 'coups de pinceau' utilisés jusqu'ici dans le niveau.

Bon, vous l'aurez compris, cette technique bitmap pour les données d'un niveau marche surtout bien pour les jeux où l'environnement est complètement destructible (chose qui devenait moins vrai avec Lemmings 2, prévu pour consoles 16-bit) et à un style graphique plus proche d'un Rayman que d'un Mario. D'une façon assez amusante (mais foireuse), c'est aussi ce qui était utilisé pour la version BASIC de Bilou's Adventures ;-)

Saturday, February 01, 2020

DoSlopes

Ok. Ajouter des pentes dans un jeu de plate-forme, ça devrait ne pas être compliqué, mais dès qu'on modélise son niveau à l'aide de dalles pré-définies (toujours les bons vieux tiles), ça cesse d'être simple. J'en ai déjà beaucoup parlé (cf. le tag) mais sans jamais vraiment (reality check: si) présenter la solution que j'utilise depuis Apple Assault: la fonction doslopes.

L'algorithme derrière doslopes travaille uniquement avec le 'hotspot' des sprites, et veille à garder ce point en contact avec le sol tout en appliquant un déplacement horizontal donné (qui peut s'étendre sur plusieurs tiles). Il utilise aussi une fonction annexe -- groundheight(x,y) -- qui donne la hauteur du sol pour un tile du niveau à partir de la base de ce tile. On y reviendra.

L'algorithme est constitué d'une boucle optionnelle (en jaune) pour atteindre le tile de destination puis une phase d'affinage (en vert) qui calcule la position du sol pour la position horizontale voulue.
En sortie, on obtiendra un  déplacement vertical correspondant au déplacement horizontal donné. A chaque déplacement intermédiaire, doslopes s'assure que l'on est pas en train de s'envoyer dans un mur à l'aide de la fonction cando -- la base de la gestion des interactions sprites/niveau.


Le principe de base, c'est de regarder la hauteur du dernier pixel d'un tile pentu juste avant de le quitter. Il peut avoir une 'hauteur = -8' ce qui fait positionne le joueur juste au-dessus du tile (comme on le fait normalement pour du sol) et permet du coup de regarder le bon tile pour la suite de la pente au tour suivant.

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 ;)


Saturday, December 07, 2019

Rush to Flatworld

It took me some time to get all the special block types working again with the new collision engine for GEDS. Partly because there were places in the scripts that referred to some block identifiers which I had to squeeze from 8-bit to 6-bit. I managed to make it a lossless squeeze, but some bits had special meaning. 

Like all the blocks identified 1xxx or 3xxx triggering actions even when collided by a monster while 0xxx and 2xxx would require a HERO cast. Or like the second character of the id xXxx indicating whether the block would act as sky (x0xx), water, block (x2xx) or floor when not interacting.

Avec le recul, ça me fait un peu penser à un donjon Zelda où on viendrait de trouver l'objet-clé. J'ai un nouveau mécanisme pour gérer les types de blocs sur DS, conquis après une lutte opiniâtre contre les vilains bugs (pensez aux wall masters) et les émulateurs récalcitrants (grosses statues à déplacer vers une dalle-interrupteur au hasard dans la pièce).

Et du coup, je dois retrouver les différents endroits où je peux à présent reprendre le développement. Editeur de niveau amélioré ? Programme de conversion vers un nouveau format de map ? Plus de types de pentes ? chacune de ces "petites clés" supplémentaires seront nécessaires au final pour pouvoir faire une version jouable de la maquette "desert zone" que j'ai en tête, et un bon nombre est aussi utile pour débloquer une conversion correcte de la "green zone". Mais là, je suis dans la salle avec trois portes et je dois choisir la bonne.

But well, it finally works fine for level 1 of school rush, and adapting other levels should now become easy as I patched the "rules.gam" shared file instead of patching directly the level commands.

And while working on it, I also mapped the whole set of "tile properties" that were in use in School Rush. That will help for introducing the new tile types. In addition to the 'special' blocks, some will have direct flags for the 'cando' operations and some will have indirect flags (i.e. you have to lookup a table to know their properties). The idea is to reduce the comparison stress for the 'normal', 'empty' tiles. We may keep the 'monster cage' tiles, but they would be seldom tiles compared to plain 'air' tiles and allow an extra table lookup.

Well, that last part is not yet done. And to be honest, I feel so tired that I'll need to go back to my little "remember you had no blog" notebook to see what has unlocked with the newest achievement and which target should be my next goal. I can't even dare to do that on screen :-/


Friday, December 06, 2019

FlatWorld.artmap

Bon, bin j'ai finalement réussi à trouver les erreurs que j'avais glissées dans mon code et à les corriger. Je peux donc reprendre une des "maps" de School Rush et la convertir à la volée pour qu'elle tourne avec la nouvelle classe "monde" et ses données de collisions séparées des blocs de VRAM du fichier .map. Par contre, ce faisant, j'ai cassé la possibilité d'animer le contenu de l'écran en réaction à une collision avec un bloc spécial.

Je dois reconnaître que j'y vais à tous petits pas, là. Entre autres à cause de la faible quantité de temps libre qu'il reste après les transports et les opérations de maintenance domestiques ... et encore, je suis loin du compte.

Bref, on va commencer par ré-établir un lien entre la "méta-map" et les map-à-images puis il faudra ajuster les changeblock() et autres clearblock() pour que tout passe. Mais caser ça avant St-Nicolas ? Je dirais qu'on est à un niveau d'improbabilité de 3.14 ou pis.

Tuesday, November 26, 2019

FlatWorld : public iWorld

Bon, je laisse un instant les commentaires sur Ori et Link's Awakening de côté, les pixels studies et compagnie. Les enfants commencent à me demander s'ils peuvent essayer les niveaux dans la forêt de Bilou, et j'ai une idée un peu plus claire du moteur de jeu que je voudrais avoir pour pouvoir le réaliser.

La première étape va être de faire en sorte que les propriétés du niveau soient conservées dans un tableau séparé, plus dans l'emplacement réservé aux couleurs d'une des deux couches de graphismes (une des plus vieilles fausses-bonnes-idées du projet).

Il faudra adapter l'éditeur de niveaux, mais ce sera pour la deuxième étape. Avant ça, j'utiliserai un "extracteur automatique" pour les "vieilles" maps (comme celle de School Rush et Apple Assault).

Je vais essayer d'avoir un bon design de classe dès le départ ce coup-ci, avec un type pour les données du moteur physique, l'implémentation qui reste un détail et iWorld qui est le seul élément connu du reste du code.

It was about to be a post on how I'm refactoring the Map/World/Camera classes to support my new tile types plans. And then I wanted to quick-check what I had so far with School Rush and problems kicked-in. I couldn't get the debugger handling it properly on my old laptop, and when I tried a rebuild on my fairy's laptop with "latest" devkitPro, I just got a blue screen reminding me that I should have dropped EFS years ago as it is no longer supported by the devkit team. All the previous tricks I could remind of didn't help.

J'ai voulu réessayer de faire tourner ça dans SchoolRush pour voir, mais je n'arrive qu'à avoir un écran bleu quand je compile avec le "dernier" devkitpro. Un conflit avec EFS, semble-t-il ... ou bien j'ai fini par oublier comment je devais invoquer le programme, mais
  • ni --load-type=1 ne semble marcher
  • ni un build plus ancien
  • ni --cflash-path=SchoolTest (et recherche dans les fichiers)
Un premier problème que je parviens à identifier, c'est lseek(..., SEEK_END) qui renvoie -1. Pas bon, vu que le code d'efs va chercher à comparer la taille trouvée avec celle pré-enregistrée. Et si ça foire avec lseek, c'est apparemment parce que open("fat:/SchoolTest.nds") échoue avec un beau ENOENT ... pourtant ce nom provenait d'argv[0]. Bref, c'est de nouveau la saison des guru meditations...

Par contre, le commit précédent marchait sans problèmes sur 'grizzly', mon vieux laptop de 2007 qui utilise toujours un ancien devkitpro. Et mon nouveau code passe sans problème la découverte du système de fichiers embarqué quand il est compilé sur grizzly. Au point que j'en viens à me demander si j'ai jamais fait un build de SchoolRush sur le nouveau système... Fort peu probable, en fait vu que le système de highscores dépend du stylet (décembre 2018) alors que je n'ai corrigé le bug d'intégration du stylet avec le nouveau devkitpro que 6 mois plus tard.

Using the old desmume-cli on newest devkitpro and investigating with their libfatdir sample program, I'm tempted to say that --cflash-path=SchoolTest should have worked (it will work some hours laters for whatever reason -- typically meaning 'make clean' left some stuff dirty), and that the --gbaslot-rom thing is the one that is truly broken with the new devkitpro setup.

And I guess I was too tired to realise I was trying to remote-debug SchoolTest.nds using the LevelEditor.elf image ... So I'd better go to bed sooner tonight.

Je sens que je vais archiver le setup devkitpro de grizzly sur mon nuc-à-tout-faire, moi.
mais, en utilisant grizzly-desmume-cli --cflash-path=directory/to directory/to/SchoolTest.nds, je peux faire tourner le code (c'est l'implémentation pour "cartouche GBA" qui ne marche plus, apparemment).
et en débuggant ce code-là, il apparaît que le destructeur d'InfiniMap se voit appelé lors du premier getflags()... c'est supposé être un iWorld, mais il a une vtable de Vblcallback ...

Et si desmume sur grizzly ne se laissait pas débugger avec ddd c'est parce que ... J'avais chargé l'éditeur de niveau dans ddd pendant que l'émulateur tournait le moteur de jeu et SchoolRush. Eh oui. Je vous en prie, balancez votre vanne préférée sur la quarantaine dans les commentaires.

Wednesday, August 21, 2019

a + b = idea


+
+
= idea.

Let's just use flowing sand in desert zone as a way to test the new tile engine ideas ...

Background: there is a strong motivation that keeps me working on the GEDS project nowadays, and revising all the things I'm revising rather than just calling it done: I'd like to be able to make things like riverflows that push player around. when I say "motivating me strongly", I mean I considered replacing the banner of this blog to show Bilou pushed by such a river on a slope. As I mentioned back in 2009, I like that this picture I see every time I visit my own blog to act as a reminder of the goal I set, by showing me something I'd like to see working when I play the game.

I was a bit annoyed that the level where I'd need such rivers and the tileset / game I'm working out these days were disjoint, but seeing the 'sand rivers' tiles of Sandopolis made me realise that they could be the perfect match for trying things here already.


Il y a encore des choses dans l'étude des moteurs de jeux de plate-formes qui m'intéressent suffisamment pour me garder accroché au dévelopement de 'libgeds'. Des mouvements style "corde ninja". Des rebonds sur les murs. Toutes des choses que j'espère bien voir figurer dans 'infinite pyramid', mon prochain projet.

Il y avait par contre un truc qui ne collait pas avec le thème de la pyramide: les zones qui forcent Bilou à avancer dans une direction précise. C'est le cas avec la rivière que j'aimerais introduire dans la version revisitée du niveau 1-1 de Bilou, à la placedu tapis roulant que mon frère avait dessiné en guise de passage à sens unique. Parce que cette rivière justifie la révision du système de "propriétés des blocs", en cogitation depuis le début de l'année.

Mais c'est bon. Je me suis retrouvé avec les méga-tiles de Sonic juste à côté de Bouli expliquant le nouveau système et ça a fait "a + b = i * d" : je n'ai qu'à mettre des rivières de sables (comme tout le monde?).

Pas oublier non plus les escaliers-qui-deviennent-des-tobogans du chateau gadget. Mais à condition que ce soit fun!

Friday, March 22, 2019

Level Editor vs new engine

Mes cogitations pour rénover la gestion des maps dans mon moteur DS commence à porter ses fruits, au point que je peux commencer à réfléchir à l'impact que ça aura sur l'éditeur de niveaux. ça me tient à coeur parce qu'avec le système actuel, je ne vois de bonne solution pour réaliser les cours d'eau souterrains que mon frère avait mis dans les niveaux historiques de la Grande Aventure.

Je me dirige vers un système avec uniquement un byte par tile de 8x8 pixels, et quatre grande classes de blocs:
- les morceaux de blocs interactifs, provoquant des collisions auxquelles le personnage doit réagir (pics, bloc-question, bonus, portes ...)
- les zones non-solides mais succeptibles d'affecter le comportement de certains morceaux de code (eau, échelles, courant d'air ...), dites "medium"
- les blocs de "sol" (solides) qui encodent la forme des pentes
- les blocs solides qui encodent les propriétés physiques (friction, déplacement forcé, etc.)

With about 2 more months to think about a better tiled engine, I start having fewer questions and more plans. I'll migrate away from 4-bit-palette being used as tile type and opt for an 8-bit type information per 8x8 tile. I'd have fewer (64 instead of 256) unique codes for "special/interactive" blocks, but the code to handle them would scale to 24x24 or 32x32 pixels blocks easily. I would have 64 different slope-type identifiers and it would be much easier to write the slope-handling code than initially planned.

And to make sure I have required flexibility, I'll split physics-hinting tiles from slope-angle-tiles. Some part of the code will read the pixel immediately under the character's feet and find the angle, while other part of the code will read past the slope and figure out whether we're on ice, solid ground or mud.


ça signifie que la petite palette de blocs utilisée dans LEDS va devenir plus complexe, avec un bouton "montrer les autres options", et que je pourrais bien en avoir trois tranches plutôt qu'une seule pour éviter les opérations de navigation fastidieuses.

ça signifie aussi que c'en est fini de l'encodage de petits "0" et "2" dans la map pour former un code entre 1 et 256 en base 4: l'éditeur de niveau ne pourra plus proposer que les blocs qui auront été décrits, ce qui devrait au passage rendre les choses plus lisibles et plus faciles pour des gens qui auraient envie d'utiliser LEDS pour leur propres projets (on peut rêver, non?)

LEDS will have to adapt this, of course. I won't have to enter digits and cross fingers with hope I remember correctly the codes for a bumper or a spike. The editor will have to know them, now. And it will have to be able to set 'medium' flags individually or pick appropriate slope types. I don't know yet how I'll handle all that.

Autre truc intéressant: puisque l'information concernant un bloc spécial (mettons des briques cassables) n'est plus encodée que sur 1 tile, je vais avoir besoin de tiles qui disent "suite du bloc, cf. à droite" ou "suite du bloc, cf. en haut", etc. Chose qui devrait être plus simple à étendre à des blocs interactifs de 24x24 ou 32x32 si le besoin s'en fait sentir...

Thursday, February 28, 2019

Sandopolis Zone and Mega-tiles.

Ok, au départ je cherchais des références pour les couleurs et le style de la "desert/pyramid zone" de mon prochain jeu. Et je venais de découvrir l'existence de Sandopolis, région d'un jeu Sonic jusqu'à laquelle je ne suis probablement jamais allé.

At first, I was just trying to find colour references for the pyramid/desert level of my next game, and I just had discovered the Sandopolis zone in some Sonic game (where I never managed to have my red shoes around). The tileset was a big .zip where I should have found all the backgrounds, but instead of the expected 'tilesheet', I stumble upon a set of 128x128 pixels pictures.

Un beau gros .zip dans lequel était supposés se trouver tous les décors, mais au lieu d'une "tilesheet" habituelle, je tombe sur une collections d'images de 128x128 pixels. J'étais resté un peu intrigué mais sans plus. Puis en revoyant la vidéo "making of Sonic 2" de splashwave, le doute n'est plus possible: il y a un niveau intermédiaire de map, et ces blocs de 128x128 pixels sont bels et biens des morceaux de puzzle construit à la main pour servir ensuite de de bloc de construction dans un niveau. On va les appeler des "mega-tiles".

I then shifted from "puzzled" to "enlightened" while watching the Sonic 2 making of video by Splashwave. There was no more doubt to have: there is truly an intermediate level of design -- 128x128 pixels areas hand-crafted by the graphics people out of brainstorming proposals, and making best reuse of the 8x8 tiles. Level design people then use them as jigsaw pieces to get the desired chains of jumps, boosts and loops. Note how the in and out ground height is standardised over the mega-tiles, btw. The core idea of this technique is of course to improve space efficiency: levels in Sonic may get as large as 10000x2000 pixels and the machine only has 64K of RAM. A 100:1 compression ratio is more than welcome in this context.

Avec les mega-tiles, on peut atteindre ainsi un taux de compression proche de 100 pour 1. Les niveaux de Sonic avec leur taille surréaliste de 10,000 x 2,000 pixels tiennent en réalité dans moins de 64K de mémoire.
Et si on regarde un niveau construit avec suffisamment de recul, on peut commencer à trouver des correspondances. Commencer seulement, parce que c'est loin d'être complètement évident ... et j'aurai eu recours au mode "soustraction" de Gimp pour les mettre en évidence.

Yet, when you have a look at a sonic map, it isn't obvious that it is made of such mega-tiles. To see them,  I had to use the same trick I used to locate tiles in SuperFrog: duplicate the layer and turn it in "substraction" mode, so that tiled area suddenly turn as all-black squares when they overlay.

Et si on ne s'en rend pas compte en jouant, c'est grâce aux différents éléments qui eux ne font pas partie de ces blocs : les télévisions, les anneaux, les ennemis ... Et parce que contrairement à un jeu "type Mario", la disposition des blocs de taille 16x16 au sein du jeu a nettement moins d'importance. J'entends par là que Mario interagit avec un bloc de brique ou un bloc-question individuellement. Et le fait qu'un saut soit possible ou non se décide à un bloc près.

I see two possible reasons explaining why those mega-tiles remain hidden to the player. First, the level designers have an additional layer of diversity with rings, screenchests, spikes, bumpers and baddies. Second, unlike a Mario-styled game, 16x16 blocks layout within the level makes little practical sense: Mario interacts with individual blocks frequently, and a mere block may define whether you an clear a jump or not. This isn't the case in Sonic: the speed, not the layout, defines whether you make it through -- or whether you find the appropriate bumper to gain extra height/speed. And even when you aren't high enough, you can still make it through a less-impressive backup route.

Pas dans Sonic. Selon qu'on va plus ou moins vite, on passe ou non. Selon qu'on trouve le bon bumper ou non, on va assez haut. Et quand on n'est pas assez haut, il y a de toutes façons une route inférieure à suivre. 

edit: a nice document entitled 'a level map is assembled with 64 parts' shows up in interview. Note that the width of a meta-tile allowed one loop to fit one meta-tile (and that the loop is actually made of one background tile and one foreground tile)

Sunday, January 13, 2019

Tile Engine Revision

Bon, j'ai une grosse révision de mon moteur de jeu en cours. Jusqu'ici, les propriétés du niveau étaient conservées dans les bits "palette" du niveau, soit 4 bits par mini-bloc de 8x8 pixels (les tiles, pour les intimes. Prononcez avec de l'aï comme dans light). Pour "School Rush" et ses niveaux de 16 écrans de large pour 1 écran de haut, on parle de 8KiB de données. Un niveau de Commander Keen (mettons la machine infernale des Shikadis) avec ses 1200x1200 pixels nécessite 22500 de ces tiles.

Much of my hobby time has been spent in tileset engine investigation since SchoolRush release. The current one has a few shortcomings which proved annoying during the " finish him " phase. Among other things, I want to stop using the palette bits for metadata, but it is still unclear how many bits per tile I can afford.
Most levels in schoolRush need a mere 16 screens, and consume only 8KiB with the current 4-bit-per-tile implementation. A Commander Keen level in comparison is 1200x1200 pixels, which would require about 22K tiles.


Pourquoi tous ces chiffres ? parce qu'au coeur de la révision, il y a le besoin de faire sortir ça des bits de palette pour pouvoir utiliser librement les changements de couleurs si utiles dans School Rush tout en permettant de nouvelles mécaniques de jeu. Jusqu'ici, j'ai pu m'en sortir avec des blocs spéciaux (bonus, pics) qui utilisaient les "codes couleurs" des 4 tiles qu'ils couvraient pour encoder un numéro suffisamment large. Mais quand on commence à réfléchir à faire de l'eau, des ventilateurs ou des tapis roulant, ce système devient bancal.

Of course, one of the goals for the revised engine is to enable multi-palette edition on both tile layers, but I would also like to increase the number of unique materials in the game. My attempts at having ice blocks, conveyor belts and flowing water highlighted that special blocks are not enough for all purposes.
One reason that makes it misfit is that special blocks only work for 16x16 blocks, not for arbitrary layout of 8x8 tiles or for transparent areas.


En particulier, il était basé sur le fait qu'un groupe de 4 tiles sont liées par les numéros de tile (en mémoire vidéo) utilisé parce que l'éditeur de sprites fonctionne comme ça. Impossible donc de définir un bloc-bumper sur un graphisme qui serait fait d'une moitié de gomme et d'une moitié de lettre. ça, c'est plutôt un avantage. Impossible aussi de rendre une pointe de crayon blessante si elle est sur 'l'arrière-plan'.

So from those figures, and given that the NDS has 4MiB of RAM, upgrading to 8-bit per tile should be possible. Granted, it could be nice to include some decompression techniques or some 16-bit era fancy techniques, but none of this seems to be mandatory right now. That's one of the strengths of the DS, imbo, that you can already make interesting games while keeping the engine simple.
And yet I know that at some point I'll wonder how such function could be achieved on a much simpler system - let's say a GBA, a SNES or a Mega-Drive... And should I have need for e.g. virtual tilesets, splitting away physics (meta) information from remappable graphics information would help.



Faut-il donc que je continue avec ce système (données 4-bit) sachant que je travaillerai maintenant sur un tableau séparé ? Puis-je me permettre une extension à 8-bit par tile ? et si oui, comment organiser au mieux les données ? Est-ce que ça m'impose d'intégrer de la compression ?

A mon avis, avec 22Ko pour une map type "Commander Keen" et sur une bécane qui dispose de 4Mo de mémoire principale, ça reste parfaitement jouable. Oui, c'est loin des techniques de trapéziste de l'époque des consoles 16-bit, mais c'est justement une des raison de choisir la DS à mes yeux: elle permet de se concentrer sur le jeu lui-même, avec des contraintes, certes, mais des contraintes simples à apprivoiser.

Friday, September 07, 2018

Retro-game mechanics explain: golden!

I just went into a super video (link below) about how collision code can be exploited in Super Mario World to beat the game faster. I'm not much into speedrunning myself, but I love whatever can teach us how those old games where built and what is the logic behind their engine. And tool-assisted speedrunning quickly gets quite deep into those subjects. So let's go.

C'est du tout bon. Une vraie pépite. Une fois encore, "retro-game mechanics explained" confirme que même quand on a pas l'intention de pratiquer le speed-run intensif, les découvertes des speedrunners sont une mine d'or pour qui s'intéresse aux techniques de programmation utilisées par les anciens de l'ère 8/16-bit. Et cette fois, c'est Super Mario world qui s'y colle.

When a tile is activated, it is deleted, and a sprite version of the block will displayed in its place. When that sprite returns to its initial position, it is removed, and another tile is set in its place (usually a brown block).
Well, that was an expected one. And as he explains, it was already used in SMB3 (and probably also back in SMB1). It is something I'd like to put into my own game engine as well, although the closest I have so far is simply the "mapanim" to replace tiles of a specific location on the map along an animation triggered by a collision.

Si vous avez déjà un peu cogité la manière dont les consoles nintendo construisaient leurs images à base de mosaïques de "tiles" et des "sprites" par-dessus, vous aurez aussi deviné que pour faire sursauter un bloc-question quand on le touche, il faut effacer le bloc de décor (à base de tiles, donc) et le remplacer par un graphisme librement positionnable (un sprite) le temps de son sursaut, puis remettre à nouveau des tiles pour le bloc transformé. C'est sympa d'en avoir une confirmation depuis l'analyse du code (et oui, j'ajouterai ça dans ma todoux-liste un de ces quatres).

The next one is a bit more unexpected.

Which tile is activated during sprite/tile collision is determined by a point that is a mix (blue) between the the sprite's position (green) and its clipping box (red). If that point is not within the tile that was activated [...] there will be block duplication.
Of course, that duplication is the whole point in SMW - Level End Glitches video by RG Mech EX:  the location where to spawn the bopping block and set the new brown block won't match the original question block location, which will remain unchanged. Interrestingly, this is partly because sprites that are located inside a solid tile are ejected outwards so that they don't get stuck. I always thought that would be mario-specific, but actually no: it applies to all objects.

Un peu plus inattendu: en plus de leur zone active -- la hitbox, en rouge sur la carapace -- qui doit rester en-dehors des zones solides du jeu, les objets ont un point unique qui sert à déterminer quel bloc a été touché. Si on cogne un bloc-question avec une carapace lancée vers le haut, c'est d'abord le carré rouge qui va renseigner qu'il y a collision puis le point bleu servira à trouver avec quoi il y a collision. Chose intéressante, tous les objets subissent le traitement "repoussé par les murs" qui autorise Mario à contourner un bloc lors d'un saut plutôt que de s'y cogner méchamment comme une Giana sister.

En revanche, le fait d'avoir mis le point-test en dehors de la zone de collision m'intrigue. Quelle est la raison ? ou est-ce juste un bug ? et cette possibilité d'avoir changé de position entre les deux opérations du test de collision (boîte et point) trahit-elle une optimisation du genre "on ne teste les blocs-question qu'une frame sur 4" ?

A few wonders ...
- is there a good reason for that blue hot spot not being with the red box (other than saving computation cycles) ? It just sounds like a bug to my ears, since the red box is what triggers the collision.
- is that "ejected first but still triggering the initial block" linked to some lower rate for the collision code compared to the motion code ?


And did you know ?

In order to reduce the number of distinct objects, some power-up blocks have different contents depending on their X coordinate on screen.
The limit on the number of objects you can have in a game engine is a old opponent. I know him a bit too well myself. But still ... Thinking of editing your level and having to shift that key pick-up one block to the left or one to the right so that it actually contains a key feels just mind-blowing. Naturally, it might not have been a big deal for Miyamoto's team who already knew player needs wide enough areas to move their avatar around ... and possibly went for "aha! guess which of those 4 ?-blocks hold a key and which are mere coins ;-)". But still. That's pretty unexpected.

Mais il reste le plus croustillant. Le truc que explique qu'un bloc supposé contenir une clé peut tout d'un coup donner des ailes à yoshi. Visiblement, je ne suis pas le seul à avoir choisi trop peu d'information par bloc dans mon format de niveau, et pour pouvoir représenter tous les power-ups et bonus possibles dans Super Mario World, l'équipe de Myamoto a choisi d'utiliser la position du bloc au sein du niveau pour choisir quel objet serait offert au joueur. Pas la position absolue, hein, mais le fait qu'il soit sur un bloc pair, multiple de 4, impair, etc.

C'est à la fois génial et complètement déroutant. Dans un commander keen, je ne me serais jamais attendu à un truc du genre "si le bonus est au 2eme étage du building, alors c'est une glace à 2000 points. S'il est au 1er c'est un donuts à 1000 points et au 3eme un nounours à 5000". Mais ici, dans un contexte où les blocs-question sont souvents présentés alignés comme les gobelets d'un jeu de hasard, le truc prend tout son sens. Il reste à considérer le "numéro" stocké dans le niveau non plus comme un identifiant d'objet à créer mais plutôt comme l'identifiant du générateur d'objets correspondant. Au moins, ils ont évité les contraintes du genre "un niveau peut offrir soit les ailes, soit une clé, soit un ballon, mais jamais une combinaison de ceux-ci."