Showing posts with label level data. Show all posts
Showing posts with label level data. Show all posts

Thursday, April 24, 2025

"Why am I doing this?"

It's not unusual to ask yourself the question among indiedev. What struck me is that the question was raised this time by someone who's been interviewed by Pix'n'Love and makes games with his son: Dale Coop.

There are moments when I wonder, “Why am I doing all this?” It takes so much time, few people care, and it brings nothing in return. The majority of people don’t even know it exists. Should I just move on to something that makes more sense for my life and family? That’s where I am today 😔 -- Dale Coop

I know that feeling. I relate it to those time where you feel the urge to eat, but nothing you eat seems to quiet it. Because actually, I need to eat something different (sometimes more fruity, sometimes more crunchy, ...) Sometimes gamedev is satisfying, sometimes we need something else.

What tend to make me wonder "why am I ..." is the overall dsgametools approach. 20 years past the introduction of the NintendoDS, and despite my first game on the system is now about 15 years old, none of my relatives have really picked up the tools and start making their own PPP games with them. It is heartwarming to read veteran artist say "your tools are perfect and tailored to your needs", yet

  • basic *EDS tools apparently feel repulsive to friends and nephew and will be lacking to professionals
  • conversion tools are so tricky to use, you'd better consider them inexistant
  • I've got nice IRL chat sessions with my son about the game design overall, but pencil drawing is none of his hobbies so I'm left with all the job anyway
  • mixing script and C++ code in the engine doesn't feel that useful when I'm the only developer anyway. It makes many debugging sessions more complicated that they should because you can't really single-step the GobExpressions

I've got something running, but it is far from perfect and it gives me the feeling that I've made my own life overcomplicated. Nobody would use scripted state machines in a platformer. Or if you really want editable behaviour, you'd go for something like Fury of the Furries

Nobody ?

Well, I've encountered some github project to decompile the original Rayman title on PSX. I was looking for the code of that tricky-to-beat bandlands bug, and I got intrigued by some of the GO_SPEED, GO_LEFT, etc. you could find in the "top" function DO_MIT_COMMAND. No reference to that anywhere else in the codebase. I was also puzzled by the "skipToLabel();" calls. Digging through references, I end up with just one big "load" function filling all the commands together with other loads for the level... So they're not just some data part of the game engine.

I couldn't find anything like "Rayman Level Format" as I hoped, but I found Ray1Map ... the website that pretends to be a Rayman 1 level editor, and that's nearly as good as far as I'm concerned.

And there, we can view the commands for each individual enemy in the game. Some of them will be delivered to per-enemy handler (like LEFT % and RIGHT %), many will affect variables that those handlers can check and manipulate (especially enemy state), but there's also a whole set of flow control instructions for loops, branches, and even subroutine calls. And as you can see, the programs are already quite sophisticated. But that was used, and not only by that game (and its numerous ports), not only for other Rayman games (like the designer kit or the Junior series), but for dozens of unrelated platformers until the GBA era... or so does the Readme.md file suggests.

So well ... let's say that for 32-bit consoles, that approach makes sense, and recreating a game engine that deals with such abstract code at runtime isn't a bad idea. I just need to give myself better tools to address the debugging aspects... like presenting the whole thing as the debugger for a virtual machine that does not exists ?

Saturday, March 15, 2025

Dans les .DAT de Fury

J'aurais préféré pouvoir dire "dans le code", mais à défaut,  moddingwiki sur shikadi.net a pas mal d'info sur la structure des niveaux d'un de mes jeux fétiches: Fury of the Furries. On y apprend pas mal de choses qui différencient le code du genre de code que moi j'aurais écrit. Comme ce tableau de 5 entrées pour "une zone d'eau" puis 5 autres "téléporteurs". Les concepteurs du niveau devront se débrouiller dans ces limites. Les zones sont forcément rectangulaires, aussi. De mon côté, n'importe quelle zone de 8x8 pixels du niveau peut devenir de l'eau, mais ça aura demandé pas mal d'efforts de développement.

C'est presque encore plus contraignant que les lignes de code BASIC que j'écrivais dans les années '90. Mais avec cette structure statique, on *peut* se faire un éditeur de niveaux et éviter de devoir lancer le jeu après recompilation pour vérifier que les coordonnées correspondent bien à ce qui est prévu. Et le nombre de zones reste suffisamment faible pour ne pas ralentir l'exécution ... même probablement plus efficace qu'avec une liste dynamique puisque les emplacement en mémoire de cette liste sont connus dès la compilation.

There are days where I'm proud of the game engine I've crafted for Bilou on DS and days where I can't help feeling ashamed for how much overhead it carries for things that would have been made so much simpler in the 16-bit age. So from times to times, I go for another hunt on "how 16-bit games stored their levels" in hope that it would teach me how the engine manipulates them.

I knew Fury of the Furries had a level editor. We even have some icons and UI elements in an LBM file shipped with the game. For years, that's been all we had, but now the moddingwiki also features a description of .BIN files for each level in the DAT/ directory. You can see there that there had been some agreement between coders and designers that 5-{anything}-ought-to-be-enough-for-everybody. 5 exits. 5 teleporters. 5 regions where Green's rope wouldn't attach. 5 pools of water ... within each entry, you just find coordinates of a rectangular area. 

I used to have IF XX > 150 AND XX < 270 AND YY > 160 THEN die() in my QBasic edition of Bilou ... well, this is more or less the same. Except you don't get a script line, but you do get a tool that can adjust those coordinates with the mouse and have the engine load them precisely where the game logic will scan them. In comparison, my current engine will instead provide a get_tile_type(x,y) function that looks up a value in a phys_level_map[WIDTH/8][HEIGHT/8] and then let the state machine adapt from that ... where Fury.c could have if in_water_area(furry.x, furry.y) swim(furry);

Of course, that puts a strong limit on how many things you may have in every level, and thus on the size of those levels. It works fine with Fury because levels are quite small, although you may stay longer in some of them than in a Super Mario Bros level. This approach wouldn't have worked for Badman II-styled level, nor for a metroïdvania level design ... But for the maps my brother had drawn for Bilou's Adventures, even the 10 ennemies per level limit could have worked.

Un autre élément intéressant: la façon dont les blocs destructibles sont gérés. Vu la palette réduite, il n'y a pas de "portion partagée du tileset" ici.

Certain tiles in the image file have special meaning.

The tile at 0,1 is a coin. If the player touches this tile in the level, they will gain a coin, and the tile will be swapped out for the tile at 0,0 which is always empty.

The tile at 0,2 is an extra life. If the player touches this tile in the level, they will gain an extra life, and the tile will be swapped out for the tile at 0,0.

The tile at 0,3 is a time extension. If the player touches this tile in the level, they will gain an extra 30 seconds on the clock, and the tile will be swapped out for the tile at 0,0. 

All the remaining tiles on rows 0, 1 and 2 in the map are destructible tiles.

Par contre, certains objets doivent systématiquement être à certains emplacements, comme la pièce (coordonnées 0,1), l'image à utiliser à sa place (0,0), les animations à afficher entre les deux. On a droit à tout une ligne d'images que Fury Rouge pourra grignoter, et les deux lignes en-dessous serviront pour les blocs un peu abîmés et très abîmés. Tellement plus simple que mes "mapanim" ^^"

They also had an interesting approach for animated tiles: the top rows of the tileset pictures (7 or 8 of them) have special meaning, as well as a few more on the leftmost column. You'll see the details in the quote above. An interesting alternative to the hard-coded crumbling floor logic of 8-bit titles like Manic Miner, if you ask me. It requires a good pre-production brainstorming to decide what sort of things we want/don't want in the level, but at least the level designer and the graphist are free to make anything crumbling / chunked by Red as they see fit without having to bother the coders with One More Change.

Mais ce qui m'a le plus surpris, c'est le nombre si faible de "sprites" autorisés dans le niveau combiné à la quantité d'informations associée à chaque sprite. Imaginez: seulement 10 ennemis par niveau !? Mais la raison, c'est qu'il n'y a pas ici "d'ennemi partagé" non plus: pour chaque ennemi, on définit les zones qui le déclenchent, les flots qu'il active/désactive (les interrupteurs et les ennemis sont donc gérés de la même manière). ça explique comment on se retrouve avec des comportements si différents pour le même visuel de monstre ... mais rien qui ne se comporte comme un koopa (avec un déplacement identique d'un bout à l'autre du jeu).

One even more interesting thing is the way sprites are handled. There are few of them per level, but they are given over 1600 bytes each. All the switches and doors and falling blocks that you push and then move into place, all this is achieved with those 10 sprites that can have up to 10 state each in the level. 

Et ce que vous voyez là ne fait même pas directement partie de la structure "sprite", c'est un des 10 états associés à un des sprites, avec leur propre vitesse, leur propre type de déplacement, 


10 states is rather few (pendats in SchoolRush have nearly twice that amount) but what makes it brilliant here is that these are no koopa-like sprites. Their state machine is level-specific and instance-specific. You don't craft a mummy that will turn back at the edge of any platform, you have an editor that let you say *this* mummy will cycle horizontally between *here* and *there*. Same for the falling bricks. or the doors.

RSD Game-make state machine could only have one "next state" for each state. Pretty limited. Here there are at least 8 state transition conditions (most of them being triggers), some about "when that position is reached", other "when that water area reached that level", etc. And yup, you can "name" a water area or another sprite, or even a "current" area (flowing air or water) and activate/deactivate that when your sprite reaches a give state. 


Even the elaborate level 2 of the Pyramid could be explained with pre-defined path alternating "move horizontally" and "falls & bounce", with triggers so that the rocks start falling... there's not even need for a multi-state orange box as I depicted: there is no "shooting" of rocks, just a stock of 3 rocks rolling left and 3 rocks rolling right, and each could unlock the next one when crossing some predefined trigger.  

(PS: j'en profite pour introduire le tag "level data", et attendez-vous à ce que j'essaie de trouver l'équivalent pour d'autres jeux)

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)

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."

Sunday, January 10, 2016

100 bytes for a level

Il m'aura fallu me promener dans le code désassemblé de Super Mario Bros 1 pour finir par y croire. Oui, sur cette cartouche de 40KB, les niveaux sont extrêmement compacts. Oui, la technique pour en faire le rendu est à la base de ce que j'avais observé avec l'éditeur pour NSMB: bien que le hardware travaille avec des "tiles", le niveau est décrit par une série de commandes (un byte de coordonnées, un byte indiquant le type de commande et éventuellement une taille). On aura ainsi des "un tuyau de 3 blocs de haut" puis "une rangée de 5 briques" et "un escalier de hauteur 3". Voire même "un trou dans le sol de largeur 4". Pas de "patterns extensibles" derrière ces codes, mais directement du code assembleur!
Les buissons, nuages, le sol sont eux décrits sur une autre couche qui est utilisée en premier lieu pour remplir la zone mémoire sur laquelle le niveau sera ensuite dessiné.

Thanks fly to Daniel Turner who teased my interest for how SMB1 encoded its levels. The cartridge was incredibly small (40KB) and indeed encoded levels as a list of "painters" operation, each giving a coordinate on the current screen, a type and possibly a size. Painters are simply dedicated assembly routine, and thus they could have something as complex as "add a staircase of 9 steps".

What I beleived to be an original technique in NSMB was thus a good old recipe. Would it be useful to shrunk those 64K levels of Bilou : School Rush ? Well, hardly, because compared to SMB1 I have much more variety in the graphics of the level, and I used those "alternate tiles" to make books, folders and pencils looks as unique as I could and avoid giving the feeling of a world built by copy-pasting. That's a bit sad, because SMB approach definitely makes editing the level much easier.

L'équipe RD4 aura profité au maximum du fait que le jeu ne fait pas marche arrière: le niveau est encodé écran par écran, et les coordonnées des objets sont données à l'intérieur de l'écran en cours. Un marqueur est ajouté aux objets qui se trouvent sur un nouvel écran par rapport à leur prédécesseur. (I AM ERROR) Il faudra attendre que SMB3 embarque une RAM additionnelle dans sa cartouche pour que le jeu puisse se souvenir des blocs déjà cassés et des pièces collectées, rendant ainsi possible une navigation plus libre dans le niveau (/I AM ERROR).

Un monde de différence, donc, avec le stockage brutal d'un tableau de MxN éléments que j'utilise dans LEDS (avec des niveaux de 32 ou 64KB. Plus gros que l'entièreté du jeu SMB1, donc), mais qui me permet en contre-partie une souplesse totale pour que les livres ne soient pas tous exactement les même. Allez, apparemment, une simple compression .zip pourrait réduire les niveaux à une taille d'environ 8KB.

Saturday, June 15, 2013

NSMB Editor !

The fact that I've just beaten the game without using a Mega Mushroom against final Bowser on *deline's request shouldn't let me forget that new Super Mario Bros on Nintendo DS is now 5... omg! 7 years old... Much enough to allow level editors to have been created. And I'm really glad that NSMBe works fine on my linux, since it's a mono-friendly Csharp program ^_^. Much easier than running Lunar on Wine.

Cette fois j'ai battu Bowser sans utiliser de Mega Mushroom (interdiction édictée par *deline). Et je réalise tout à coup que "new" Super Mario Bros a ... 7 ans. Bien assez pour que les éditeurs de niveaux aient fleuri, et j'ai même réussi a faire un peu tourner NSMBe un tout petit peu sous Linux. J'avoue que je n'ai pas l'intention de créer mes propres niveaux à NSMB. C'est un exercice qui ne m'a jamais vraiment attiré. Par contre, je suis curieux de voir ce que je peux apprendre sur la structure du jeu.

As you guess, I'm not that interested into crafting alternate levels for nsmb, although thanks to my ds linkers, I could even play the modified ROM on the Real Thing. But I'm deeply curious to discover how the thing was built. As I had expected, many of the special platforms you can walk on are "sprites" (either made of OAM or using 3D hardware ? I still couldn't tell). Obviously, not all level may use all the sprites, but what is interesting is that they have been organised in swappable "banks". You can't have e.g. giant Bowser and Nessie starred in the same level because they both belong to bank 5. But you're free to chose wheter you complement them with Hammer Bros. or Chomp Chomp, with mushrooms or tornados or moving platforms, etc. Spawn points, door/pipe entrance are also managed as "sprites".

Comme on pouvait s'y attendre, un grand nombre de plate-formes sont en réalité des "sprites" (soit avec des OAM, soit avec la couche 3D. Difficile à dire), même celles qui ne sont pas forcément mobiles, mais dont le comportement est suffisamment différent du sol. Les portes, les début de niveaux, les entrées/sorties par des tuyaux, sont eux aussi gérés avec des sprites.

Les sprites sont organisés en "bancs" que l'on peut utiliser ou non dans le niveau, certains bancs en excluant d'autres (pas de bowser et de nessie ensemble: ils sont tous les deux pour le banc n°5, par exemple). Par contre, on pourra les compléter avec des Hammer Bros ou des chomp chomps, comme on préfère.

Coins and blocks belong to the "ground" layer. Even item-providing blocks. I trust that to be made of good old tiles, as there can be only one layer of 3D objects and that sprites already use it... yet it remains to be proved. One reason that make me hesitate is that you edit it in terms of "objects" and not merely by laying out tiles on a grid. Each object can be individually selected, moved along, dropped, resized, etc. It is very efficient, compared to what LEDS currently offer, so I'm not surprised. It leads to funny things such as many different patterns of 'coins' so that you could have vertical, horizontal or dithered coins arrangement with a single 'object'.

Les pièces d'or et les bloc-question font partie de la couche "sol". Idem pour les blocs-à-power-up. Ils sont sans aucun doute fait à partir de bons vieux "tiles". Chose assez inhabituelle, en revanche: on les édite et tant "qu'objets", pas juste en plaçant des pavés sur une grande grille. Chaque objet peut être sélectionné, déplacé, redimensionné individuellement, ce qui s'avère très efficace pour la conception rapide de niveau comparé à ce que LEDS propose. Du coup, on retrouve des éléments assez curieux comme des "pointillés de pièces d'or" qui permettent d'avoir un "damier de pièces d'or en diagonale" avec un seul objet.

Tout ça n'a pas l'air d'être juste un choix de fonctionnement de l'éditeur: les niveaux eux-même contiennent ce genre d'objets: si on clique sur un "bloc" du dessus d'une plate-forme, on sélectionne directement l'entièreté du sommet de la plate-forme. Je présume que c'est une technique pour réduire l'espace sur la cartouche: le contenu en mémoire du niveau sera reconstitué en appliquant des commandes du type "remplir (10,10)-(20,20) avec du sol" puis "remplir (10,9)-(20,9) avec de l'herbe", etc. J'y crois d'autant plus fort quand je vois des motifs "effaceurs" qui n'auraient aucun sens dans un système utilisant des textures et des polygones.

And that doesn't seem to be a mere side-effect of the editor: the level has those objects, truly. If you click on the top edge of a platform that's already part of the design, you select the whole top. I can only imagine that this helps storing levels using less memory. Given that there's a lot of empty space and repetitions in NSMB maps, storing the level as "paint commands" (fill x1,y1 - x2,y2 with object #12) where object12=[start at tile 42, width=3, height=4] is much more compact than storing the whole map, and allows easier edition cycles. That would also explain the presence of "erasors" patterns that wouldn't make any sense if the level was 3D-rendered (afaik). When the level is loaded, the painting list is streamed from the ROM card (unlike previous Nintendo console, the DS doesn't have direct access to game ROM through its bus, iirc), a large chunk of RAM is allocated, and the commands are applied to know what to map on-screen. This in-RAM map would then be dynamically updated as coins are taken, blocks are used and bricks are destroyed... it all make sense.

Alternatively, you could just 'unpack' part of the level by applying a clip mask on the "paint commands", and have a level-sized bitmap for "interactive tiles".  Both coins, bricks and blocks are binary (original/used) so you'd mix that information with a paint command and still be able to retrieve the start of the level as the player left it although it wasn't permanently available as a tile map in RAM.

Wednesday, June 24, 2009

Je vous parle d'un temps que les moins de 20 ans...

Bien avant Internet, du temps où les diskettes étaient molles et que les écrans étaient le plus souvent monochrome, je programmais déjà. Petit retour sur la version "BASIC" de Bilou en image, stimulé par le fait que j'ai utilisé la "distribution" de Bilou pour QEMU comme .torrent de test dans les TPs de mes étudiants (pas de téléchargement proposé, parce que je me suis rendu compte que certains paramètres y étaient encore incorrects :P)

There was a time were the network wasn't interconnected yet and had to travel the real world enclosed in sectors of floppy disks that were actually floppy. Screens had usually no colors but I was already coding. Here are a few shots of those coding sessions, with the BASIC version of Bilou's adventure.

Tout d'abord, construire le niveau lui-même. A grand coup de "Data" qui sont lues séquentiellement à partir d'un autre point du programme. Une sorte "d'Embedded File System" avec ses avantages et ses inconvénients, l'inconvénient majeur étant sans doute l'impossibilité de construire son niveau autrement qu'au clavier.

Vous reconnaissez la scène dont la capture d'écran est sur ce blog depuis des mois ?

Venait ensuite la 'routine de gestion de l'écran', quelque-part au coeur d'une énorme boucle du genre:

  • lire les DATA et les afficher
  • select CASE ecran
  • ajuster la position de Bilou et boucler

A level was built out of 'DATA' lines that encoded pixels or tile arrangement for a room. But that wasn't quite enough. Over the background layer, I had small chunk of code that placed interactive graphics, and a loop that animated every object on the room, tested for collisions with bonuses, bumpers, etc. and "doors" area towards the next screen. Yes, every room with its own game loop. Never do that again, kids.

Cette "routine" va dessiner tout ce qui n'est pas décor (ennemis, bonus, dégradés, etc) avant de traiter en boucle le comportement de ces objets avec des sous-routines (ouf. Il y en a quand-même) genre "move" pour Bilou, "pomme" pour tester si bilou touche une pomme, "gold" pour tester s'il touche une pièce d'or, etc. Vous avez vu cet horrible hack ? répéter n fois ce qui ne dépend que de Bilou avant de passer au test de timer histoire que Bilou puisse effectivement se déplacer plus vite que le petit ver. On a échappé au 'FOR I = 1 TO 1000 : NEXT', ceci dit.
Des choses aussi élémentaires que "suis-je mort" ou "faut-il passer à l'écran suivant" devaient être répétées pour chaque écran. Wéééééé. :P

Allez, un autre petit écran. Je me suis demandé en reparcourant le code "mais enfin, pourquoi diable ce "if ... then mort". Bin parce que sinon, Bilou pouvait se promener allègrement dans les pics dessinés un peu plus haut. J'aurais pu trouver autre-chose... une couleur mortelle, par exemple.

Au passage, il n'y avait plus de sprites dans le QuickBasic (pas plus qu'en EP-BASIC). Tout fonctionnait par copier-coller entre l'écran et des variables tableau (ce que les amigaistes appelaient sans doute le "blitting", mais qui en l'occurence était du PUT et GET. Concept farfelu, ces "copies" pouvaient faire appel à un opérateur booléen pour combiner les pixels existant et ceux du buffer. Je suis parti de jeux monochrome où j'effaçais le personnage du fond noir en le "XOR"ant avec lui-même pour finir par utiliser un système de masque en deux passes (AND/OR). En l'occurence, dans ce jeu-ci, j'avais droit à 15 couleurs de fond (respectant le pattern xxxx1111) et 15 couleurs de sprites (0000yyyy) de sorte qu'un "AND" bit-à-bit entre un sprite et le fond affichait le sprite, et une couleur automatiquement transparente (255). De tous mes hacks, je crois que ça reste le plus tordu. Toute tentative de programmer sa propre routine d'affichage en BASIC étant évidemment inimaginable, car d'une lenteur affligeante.

Figurez-vous que même les tests étaient lents. Je veux dire les "if-then-else" Du coup, j'essayais dans mes routines de comportement (appelées plusieurs dizaines de fois par seconde quand tout allait bien) d'en utiliser le moins possible. En témoigne ce genre d'expression optimisée -- surréaliste aujourd'hui -- qui utilisait le signe de la différence de position pour ajuster la vitesse du "Bubble Bat". Il m'en reste encore des séquelles aujourd'hui (cf. Coding Funky Funghi).

Friday, March 27, 2009

vgmaps tool for RDS Game-Maker


bbl2map.pl

Et voilà. L'outil ultime : conversion des fichiers .MAP en allant rechercher les animations des monstres dans les .MON. Je suis encore loin d'avoir tout compris aux .mon (déplacement, etc.), mais au moins, j'ai de quoi me faire un joli musée de niveaux de nos jeux RSD Game-Maker. Pour ce qui est de faire une conversion .gam -> .nds, par contre, ce n'est toujours pas gagné. Si la plupart des maps se "compressent" assez bien sur des tiles de 8x8 (pour Badman II, en tous cas), la gestion des animations serait un vrai cauchemar dans un environnement pareil ...

Good news if you used to design games on RSD Game-Maker: i managed to build a fully-featured RSD .MAP reader that lookups .MON files (at least animations) to render a full map of your levels, with monsters depicted... Oh, it could still use the information from .GAM files to show you entry/exit points as well, i admit.

I guess anyone fluent in PERL could reuse the code to write a RSD->XXX converter given that you already know the map format for XXX. Unfortunately, that doesn't really bring me much closer to the "RSD->NDS" conversion/replay tool due to the silly 20x20 block size in .BBL files. Not much of a concern for handling the map (at least, in Badman II, most of the maps use quite few blocks and a plain conversion to 8x8 tiles does the trick), but animating such 20x20 blocks that are split over different tiles would likely turn into a nightmare. And trying to update a bitmap rendering of the level might be tedious (and boring) to implement, not mentioning the limited bandwidth to the screen's backbuffer , that would hit the framerate quite badly.
.

Thursday, March 26, 2009

BBL metadata.

Voilà. Une fois qu'on dispose de tout un jeu de .BBL avec les tileset correspondant, c'est bien plus simple de reconstituer le rôle des 20 bytes de contrôle ... C'est ce que fait de son mieux ce petit outil "bblmeta.pl" que je vous offre également.

Chaque ligne rappelle le numéro du bloc (en hexa) et sa position dans l'image générée par bbl2png (le premier, par exemple, r02 c12 correspond au premier canon, tourné vers la gauche sur la 3eme ligne de l'image). Suit ensuite les "flags" tels que lus dans le fichier .BBL puis l'interprétation de ces flags par le script bblmeta.pl

J'ai repris la technique des répertoires UNIX: un '-' chaque fois qu'il n'y a rien à signaler, une lettre sinon. Ainsi "B--a-K" serait un bloc solide (B) animé qui interagit avec une clé (probablement une porte). "--g-+-" est un bonus qui rapporte uniquement des points, etc. Pour être exhaustif, je dirais que le programme reconnait:

  1. Bloc, Floor, Wall ou ciel (-)
  2. Pickme : l'objet peut être ramassé pour l'inventaire
  3. g, l ou r : gravité normale, vers la gauche ou la droite. Les autres combinaisons ne sont pas reconnues.
  4. a : bloc animé,
  5. t: réagit au contact
  6. bonus/malus : H=hitpoints, x=kills, + : score
  7. L = 1UP, K=clé/porte.
En plus de ces "flags", certaines valeurs sont indiquées dans une liste entre parenthèse, comme la succession des blocs pour une animation, le nombre de hitpoints ou de clés, ou de points modifiés lors d'un contact , le numéro du monstre tiré (pour les blocs qui tirent des monstres), etc.

Here comes a second funny PERL script. It extracts meta-data from .BBL (Background BLocks), .CBL (Character BLocks) and .MBL (Monster BLocks) produced by RSD Game-Maker. If you used to write games with that editor, feel free to use that tool to retrieve all meaningful elements and port your old funware to some new platform. If you do so (or just plan to do so), please share the joy and post a comment.

This tool is a kind of "preliminary" reverse-engineering tool. It can tell you roughly what are the properties of individual blocks but detailed information may need to be looked up in the 40-digits hex string. Each line describes a block. Right to left you'll have the following information :

1c (r01 c13): 400100000000000000ffff1b64001c0400010000  --ga-x-  (a1b,100)(h-1)
^    coords   |--- complete hex dump of meta-data ---|  |flags|  |---details--|
+- block number
On the right side, "flags" tell you roughly what are the properties of your block. here you see an Animated block that has downwards Gravity and that hurts the player (x) on contact. Details tell you that it animates to block 1b after 100 ticks and that hitpoints are reduced by 1 on contact. On the left, you have block number (block #x pixels are located at bytes x*420..x*420+399 in xBL file) and the row/column coordinates on the picture generated by my former bbl2png.pl script.
Bref, ce n'est certainement pas aussi souple que l'éditeur du game-maker (et d'ailleurs, ce n'est pas un éditeur, juste un outil d'inspection), mais ça capturera la plus grande partie des cas que l'on retrouve dans un jeu de plates-forme comme les Badman.

Hope it Helps ^_^
PS: 1st byte is the index of the generated monster (0x40 = no monster generated), thus byte 2 is likely the delay between two such generations.

Wednesday, May 09, 2007

He's the Terror who makes Errors ...

Retour en 1994 ou quelque-part par là à l'age d'or de PPP Team Software (moi, mon frère, Pierrick et quelques autres dont Pascal). A l'aide du fabuleux Game-Maker de Recreational Software Design (RSD), nous enchaînions niveau sur niveau puis jeu sur jeu les mercredi après-midi.

Back in '94, in the golden age of PPP Team Software, where we were spending our lunchtime creating games with the RSD Game Maker. Badman has quickly become one of our "best-known" funny platformer hero, and is starring 3 games while he was initially meant to be just a mid-level boss for Bilou's Quest. "Badman 1" was one of our "real" game produced with RSD tools, where the "terror-who-makes-errors" crosses manatthan roofs, japanese waterfalls, nazi jails and haunted mansions looking for clues about a terrorist take over to take place during the football world cup. Since my bros came up with a collection of .mod songs, it's very tempting to revive the 'best' 4 levels of the game on the Nintendo DS for an "anniversary edition" ...

Parmi la collection de personnages loufoques dont nous avons fait nos héros, il y avait Badman, sans conteste un de ceux pour lequel il y a eu le plus d'efforts investis. Les images reprises ici sont des captures d'écran de Badman 1, un de nos premiers "vrais" jeux, même s'il était encore entaché de pas mal de blocs venant des jeux "démo" livrés avec le Game Maker... Puisque Piet a (par la suite) écrit des musiques qui colleraient à merveille avec certains de ses niveaux, la tentation est énorme de reprendre les niveaux les mieux aboutis (4 en fait) et d'en faire une version "anniversaire" sur la DS. J'ai un modplayer, donc c'est ok pour les musiques ... les maps et l'IA des ennemis était excessivement simple au point qu'un week-end devrait suffire à tout récupérer.

Le gros problème, ce sont les images. Là où toutes les consoles travaillent avec des tiles 8x8, le GameMaker fonctionnait avec des blocs 20x20, ce qui va terriblement compliquer la composition des graphismes, etc. Pour des éléments comme les blocs de terre ou les briques d'arrière plan, je pourrais à la rigueur m'en tirer en passant d'un bloc 20x20 à 25 tiles (pour reconstruire un méta-bloc 40x40), mais ça va clairement coincer dès qu'on voudra avoir un seul bloc plutôt qu'un mur entier (voir la cascade).

Pas question non-plus de 'zoomer' tous les persos en 16x16. Badman ne ressemblerait plus à rien (et il faudrait retoucher tous les sprites). Pas question (toujours) de devoir prendre en compte toutes les combinaisons de blocs voisins pour construire les tiles "de jonction". Il me reste tout de même un espoir, jouer sur les plans multiples (un bloc sur le plan 1, le suivant par transparence sur le plan 2, puis retour au plan 1, etc).

There are a couple of technical issues to achieve this, mostly due to the mismatch between Nintendo console tiles (8x8 pixels) and RSD tiles (20x20). Plus, RSD game maker tileset and maps are stored in a proprietary format that isn't documented anywhere. Palettes and blocks (BBL, MBL files) aren't very sophisticated, I know how to display them on a DOS screen since '97. But .map files (levels) are still a mystery. I'll give them a reverse-engineering try this lunchtime. Let's open a hex editor and have a look ...

Reste à briser les secrets des fichiers .MAP ... je jette un rapide coup d'oeil aux premiers niveau de Badman ... Toutes les map sont scindées en 2 parties, et la 2ème partie commence toujours à la position 20000, ce qui suggère une map de 100x100 (avec 2 bytes par bloc)

Presque tous les blocs possèdent un code c8xx, sauf un petit nombre, et les premiers bytes sont tous différents, e.g.

SONI.MAP: 000a@(8,13) 0105@(8,33) 0200@(33,21)
0306@(33,36) 040d@(33,51) 050d@(67,46)

facts:

  • .maps file look to have two distinct parts, and the first one is 20,000 bytes long, which suggests a 100x100 map with 2 bytes worth of data per tile
  • tiles where there is no monsters have the form 0xC8yy and tiles where there is a monster have the form 0xzzyy, where zz is a relatively small number, that is unique over the map. That suggests "C8" means "no monster" and that zz is the monster's index in some other table.
  • The second part has 5 words (16-bit) per entry, where "0040 0000 ffff ffff 0000" seems to be a filler for "no monster here".
  • words 2 and 3 in that list seems to be X and Y coordinates of the monster on the map, while word #1 could be the ID of the monster to be used (that is, in the .MON file).

Pour le fun, j'ai noté les coordonnées auxquelles ces blocs apparaissent, en plus de leur valeur.

(SONI.MAP)

0004e20 000e 0000 000d 0008 0000:000e 0000 0021
0004e30 0008 0000:000e 0000 0018 0021 0000:000e
0004e40 0000 0024 0021 0000:000e 0000 0033 0021
0004e50 0000:000e 0000 0049 0021 0000:0040 0000
0004e60 ffff ffff 0000 0040 0000 ffff ffff 0000
0004e70 0040 0000 ffff ffff 0000 0040 0000 ffff


Répétition de blocs "0040 0000 ffff ffff 0000" une fois un certain point passé... Dans la map Soni, il y a juste 6 entrées avant ce "remplissage", ce qui correspond au nombre de blocs "spéciaux" trouvés.


000e 0000 000d 0008 0000 (8,13)
000e 0000 0021 0008 0000 (8,33)
000e 0000 0018 0021 0000 ...

Les mots 2 et 3 correspondent aux coordonnées où sont enregistrés les monstres sur la map. Il serait logique de supposer qu'il y a une 'liste de monstre' et que le premier des 2 bytes encode le numéro du monstre dans la liste (pour le retrouver facilement lors de l'affichage du niveau). Le premier mot serait logiquement le numéro du monstre à utiliser, quant aux deux autres valeurs (toujours à 0), aucune idée, ma foi.

En tout, il y a 2000 bytes de monstres (la 2eme partie) dans chaque map... Soit 200 monstres possibles... C8 est le numéro 200, le monstre inexistant ^_^

Pour info, j'ai déjà un convertisseur de blocs & palettes vers le format SEDS.
Dernières news, j'ai aussi reverse-engineeré le format des fichiers .GAM du game-maker RSD.