Showing posts with label tutoriel. Show all posts
Showing posts with label tutoriel. Show all posts

Saturday, April 19, 2025

I want to try your editors so much!

How could I resist to such a quote ?

Adeline Venture (hehe) in Shadow Of The Temple The puzzle platformer I really, really want to make some day.

I want to try your editors so much! But in order to finish, I will have to do it in an engine I already know well. Probably construct3 -- CastPixel

It made me want to pack a set of my dsgametools with contents of Castpixel's mockup pre-digested into a .spr file so that she could directly toy with the level editor ... perhaps also convert the tutogit scripts so that she'd get something playable!

So first thing to know is that SEDS works with 16x16 blocks. There is a perl script that can convert the mock-up into tiles but if used directly, it will be a complete mess as game assets.

  1. ensure things are aligned on the 16x16 grid as much as possible. That means the ladder half-way over pillar must be shifted. A few other things needs similar shifting on the mockup.
  2. SEDS shows you 64-pixels slices at once. The scripts *can* handle wider pictures, but at the moment, they'll scan e.g. first row of 256x16 pixels and output 4 64x16 rows in the first page, then another 4x64x16 chunk etc. The result is barely usable, again, because many objects bigger than 16x16 will be de-aligned and will need to be re-aligned in the DS editor.
  3. So instead, you may want to turn the mockup into a 64x* image where things are where you want. Todo: imlib2spr.pl should be able to scan the image in 64px-wide columns itself, so that this step becomes useless and the mockup remains readable.
  4. The script may remove the background color, but when it does so, it also deletes fully background blocks from the output file. This is nice to avoid redundant data, but again, it screws up alignment on such a mockup. You can scribble something on those blocks or you may turn off the background color by giving another one with --bgcolor=ff00ff. Todo: have the imlib2spr.pl tool inject empty slots on the sprite page when it encounters such full-background blocks, to preserve alignment
  5. B u t if I use --bgcolor=some-color, the tool will inject a fully-transparent block as block 0. This is required for proper operation of the game engine and the level editor, but this is useless on the sprite pages. And worse, having block 0 editable led to corrupted files in the past. todo: patch the tool so that the empty block is never used by the generated pages (and never allocated by the editor :P)

With all that taken into account, with a 64x* png file with the content of the mockup aligned on a 16x16 grid as often as possible, conversion is performed with 

SpriteEditor/bin/imlib2spr.pl cast-a-venture2.png venture.spr --nomap --nopack --blocksize=16

And the reverse operation to see how things went is

SpriteEditor/bin/spr2png.pl venture.spr /tmp/venture.png && display /tmp/venture.png

Once that looks fine, copy the venture.spr file on your SD card in the /moving/ directory, make sure you have SpriteEditor for DS at the root of your SD card and launch it.

  • once launched, press START to enter the FILE screen
  • click the |/_/ browse button to show the contents of /moving/
  • if venture.spr doesn't show up, hold L trigger and touch the "UV" button on the alphapad to filter the list of files. (holding L lets you enter the second letter of each "button").
  • touch the "venture.spr" name to load the file, then press START again to return to the edition grid.

 

Tuesday, February 04, 2025

back then ... geometry first.

Dumblador revisit post from October 2011 featured not-so-great book covers and "colour balance" post 20 days later featured them almost identical. Then 10 more days and they suddenly look like in School Rush. What happened in between ? pixel art Comment and Critique. "I think you're focusing way too much on using techniques right rather than the objects itself" said Elk, but I couldn't figure out what that meant. "The biggest problem with your art is bad texturing, at this point, I'll return with specific edit and critique." So Helm Returns is what happened. 

En triant un peu, je suis retombé sur un post du forum pixelation qui répond à une question que personne ne se posait. C'est qu'entre le relookage de Dumblador (Oct. 2011) et la recherche des couleurs idéales pour la School Zone (Nov. 2011), on est passé d'un livre pas très convaincant au design actuel utilisé dans School Rush (et qui restera certainement inchangé pour Dreamland). Dans ce post, Helm me donnait une série de conseils en retouchant lui-même le livre que j'avais dessiné.

1. is your book.
2. is the form broken down to its block shape.
3. is geometry, lit from above more or less. If a thing doesn't look good and identifiable on this stage, it won't look much better if you overrender it.
4. here I apply more detailed geometry, I still don't need more colors or fancy tricks, I don't need to fake texture, it's just stuff that the book can support. I also looked at reference here which you should always do, no matter how cartoony what you're trying to draw is. You can spot many differences from 3 to 4 that are related to the reference.
5.After the shapes are good, identifiable and the object has volume, you can go nuts with your new school colors and tints and whatever else, it's all embellishment from here and on.

Le jeu, dans ce cas-là, c'est d'essayer de réappliquer soi-même les conseils qu'on a reçu. A savoir ici "commence par la forme brute, puis fais en sorte que la géométrie soit compréhensible". Ne pas aller au-delà de l'étape 3 si quelque-chose va de travers. Ensuite, rafiner la géométrie mais ne pas texturer, ne pas partir dans des grands effets de couleur. Une excellente leçon, et je vais en avoir bien besoin pour m'attaquer au gros poing pour la pyramide ...

So I went on and try to apply the master's suggestions to get closer to what I wanted:

My attempt at having the right geometry with flat shading and minimal details, my step of putting more detailed geometry and shading before adding any texture, and the final take. The page lines have become too horizontal and dull, retrospectively, but the binding has been used almost as-is in the game.

It was a pleasure to read the master's comment:

Much, much better. Wouldn't surprise me to see this piece in any professional good looking mega drive game of the era.

Approach all real-life related items you render in a similar way, avoid 'noisy textures' for their own sakes and you will level up in your craft."

A lesson to remember as I'll have to work on more pixels for the upcoming game...

Monday, December 25, 2023

Petite pause HDMA

Yes! I managed to get some HDMA effect applied on my 3-rooms demo. It has absolutely no use in that particular scenery, but it did work. Yet, at first, I couldn't get anything, up to the point where I suspected that my emulator simply had no support for HDMA at all. But then I remembered seeing something very HDMA-esque in WarhawkDS (recently open-sourced). And then before I could even try whether the .nds would work in my emulator, Asie confirmed that she knew another open-source homebrew using HDMA: MegaZeux.

Hahaa! Nous y voilà! J'ai réussi à appliquer un effet "HDMA" dans ma démo de Bilou Dreamland! ça ne sert à rien, c'est d'une esthétique discutable, mais voilà: il y a presque 2 ans que j'ai des notes sur comment faire ce genre de chose dans mon carnet-agenda et que je passe par-dessus en me disant que "ouais, je tenterai ça un de ces 4. C'est complètement le genre de technique qui fait que je suis sur NDS et pas sur playdate ou androïd. Mais c'est McMartin avec son post sur le HDMA de la SNES qui m'a donné envie de bousculer un peu mon absence-de-planning de hobby-coding pour le mettre en oeuvre. Sauf que vous vous en doutez, au premier essai, rien ne marchait. J'avais pourtant quelque chose de quasi-identique à ce setup dans MegaZeux DS ou même warhawk DS.

  DMA1_CR         = 0;
  REG_BG0VOFS_SUB = scroll_table[0];
  DMA1_SRC        = (u32)(scroll_table + 1);
  DMA1_DEST       = (u32)&REG_BG0VOFS_SUB;
  DMA1_CR         = DMA_DST_FIX | DMA_SRC_INC | DMA_REPEAT | DMA_16_BIT |
                    DMA_START_HBL | DMA_ENABLE | 1;

The registers configuration is almost identical to the one I tried in my demo: 16-bit transfer with proper start and repeat setup, and a transfer size of 1 word per line. At that point I started suspecting that something else in my code would break the setup of the DMA transfer. After all we already have channel 0 used for 3D pipeline and channel 3 used by dmaCopy macros. So I picked up devkitpro simplest graphics demo and tried to bring it there instead.

Un petit passage dans les programmes d'exemple de devkitpro. Aucune ne fait du HDMA alors j'essaie d'injecter mon code dedans. Sauf que la première victime n'a aucun plan de décor (donc rien à faire onduler) et est écrit en C (contre du C++ pour mon code). Je m'adapte et je fais la bonne vieille rasterbar (impossible dans le code de Bilou parce que je travaille en mode 4096 couleurs par plan, ce qui veut dire que la palette est hors de la mémoire adressable). Et là, ça marche presque nickel. Il faut juste veiller à programmer la couleur pour la ligne 0 "à la main" avant de commencer la configuration du HDMA qui fera toute les autres lignes parce qu'il se déclenche *à la fin* de chaque ligne, mais pas pendant les lignes virtuelles du délai entre 2 images.

The first one (simple sprite) had no background to wave but it had single palette, so there (unlike with my demo), I could beam values into palette slot 0 and see it draw raster bars on screen. A bit of translation was needed because it was a C example rather than a C++ one, but there it is. changing the background colour like I was driving an Atari 2600 except the DMA does the waits and syncs, and not the CPU.

class HdmaEffect {
  static const size_t N=256;
  static const unsigned CHN = 1;
  s16 data[N];
  size_t offset;
public:
  HdmaEffect(); // intialize data[]
  ~HdmaEffect() {
    DMA_CR(CHN) = 0;
  }

  void Frame() {
    DMA_CR(CHN) = 0; // disable channel
    DMA_SRC(CHN) = (uint32)(data + offset);
    DMA_DEST(CHN) = (uint32) &REG_BG0HOFS_SUB; // mind the & to get register *address*
    DMA_DEST(CHN) = (uint32) BG_PALETTE_SUB;
    DMA_CR(CHN) = DMA_REPEAT | DMA_START_HBL | DMA_SRC_INC | DMA_ENABLE | DMA_DST_FIX | 1;
    offset = (offset + 1) & ((N / 4) - 1);
  }
};

void mainLoop() {
	HdmaEffect hdma;
	
	while(1) {
		swiWaitForVBlank();
		hdma.Frame();
		scanKeys();
		if (keysDown()&KEY_START) break;
	}
}

Mais mon code C++, lui, toujours rien. Il n'est pourtant pas si différent. Je continue à passer d'un exemple à l'autre et je tombe sur un avec un décor (qui refusera mordicus de bouger) et en C++. Toujours rien. Par contre, en reprenant et adaptant le code C, là, je parviendrai à faire onduler le texte de l'écran du bas. Moins sexy, mais c'est un début. Il m'aura fallu un bon réveillon familial et une petite nuit de sommeil pour comprendre la différence fondamentale entre les deux implémentations.

I picked another example featuring a background, this time in C++. I couldn't get any color changed with my C++ code, and I couldn't get the picture waving. But with the adapted C code, it could change colors and I could make the text on the bottom screen waving (although somehow weirdly). We were 24th of December, I had errands to run and a party to attend, so I accidentally shut down the computer and went doing something completely different. It's only when I woke up this morning that I got struck by the difference between the C++ class and the C code. The C++ class will have the source array in a member and it allocates the HdmaEffect object on the stack. But the stack is invisible to DMA operations. I've been tricked by that a good number of times in the past already. One more alloc/free pair was all I needed to get the working screen-waving effect you've seen above. Huzzah! Merry Christmas! May the source be with you all ;-)

La conversion rapide en C utilisait une grosse variable globale pour le tableau contenant les différentes valeurs à streamer dans le registre de scrolling. La version C++, plus propre sur elle, encapsulait ce tableau au sein de l'objet HdmaEffect, lequel pouvait être construit dans une variable locale pour gérer ses ressources (le canal DMA) Rabi-style. Sauf que "local", ça veut dire "sur la pile" et que la pile de la DS est 1) petite et 2) mappée sur de la mémoire plus rapide (type cache L1) logée au coeur du chip ARM ... et donc inaccessible depuis le bus système avec lequel travaille le contrôleur DMA. Eh oui. Un malloc plus tard, j'étais prêt à faire une démo qui marche ^^"

Thursday, June 01, 2023

Déveloper pour DS

Figurez-vous que le youtubeur Nathan Fallet vient de se lancer dans une série de vidéo "live coding" pour faire un petit jeu DS. Son idée est de prendre juste libnds et les outils de devkitpro et de faire une adaptation d'un petit jeu pour smartphone qu'il avait fait il y a quelques années. L'idée est assez proche de ce qu'aurait fait un nouveau venu sur un forum à l'époque, donc j'ai envie de suivre ça.

Son point de départ, c'est l'exemple 'animate simple.nds', où on peut promener un sprite sur chaque écran avec le DPAD en modifiant l'image utilisée par le sprite en fonction de la direction prise.

Pour ce programme, les développeurs ont choisi d'embarquer les images directement dans la section "données en lecture seules" du programme. Là où se trouveraient aussi les chaînes de texte si on avait un texte. Pas d'ouverture de fichier avec ce système-là, mais pas non plus la possibilité de prévoir de charger/décharger des images de la mémoire principale pour y mettre autre-chose. En même temps, comme la DS a 4 beaux gros Méga, on devrait être tranquilles pour un projet modeste.

Il y a un programme (que je n'ai pour ainsi dire jamais utilisé) qui prendra en charge la lecture du fichier .png pour le convertir en données brutes (des tiles et des palettes) exploitables directement par le hardware de la console. Par contre, ce programme ne voit qu'une seule image à la fois. Il peut ranger les couleurs dans la palette, éviter les doublons, etc. mais si vous lui donnez deux .png différents, chaque .png produira son lot de sprites supposant que sa première couleur est en position 1 dans la palette. Essayer d'afficher les deux à la fois donnera de mauvais résultats. Le programme d'exemple masque un peu ça avec son man.png et sa woman.png, parce que dans ce cas précis, chaque personnage est chargé dans la mémoire de son écran. L'un sur le chip "main", l'autre sur le chip "sub". Il ne sauront jamais se rencontrer.

Le programme d'exemple n'utilise que la fonction "sprites" du hardware 2D. Aucun des 4 plans de décor possible n'a été configuré. Mais ça ne veut pas dire qu'on soit condamné à errer dans le noir pour autant.

Comme dans le cas de la Super NES, chacun de ces plans serait scrollable indépendamment des autres et possèderait une "couleur transparente": l'entrée 0 dans la palette de couleur. Mais si aucun plan n'a donné de pixel à afficher, le hardware nous mettra un plan avec la couleur 0. Et cette couleur, elle est trouvée tout simplement dans la mémoire vidéo. Les fichiers d'en-tête fournis avec libnds nous donnent un symbole BG_PALETTE qui permet de lire et d'écrire dans la palette "décor" de l'écran principal.

Ce n'est pas la seule manière de faire, bien sûr. On aurait pu activer un plan de tiles, remplir un tile avec une autre couleur puis remplir la map avec ce tile. On aurait pu activer un plan bitmap et remplir 98K de mémoire vidéo avec la même valeur. On aurait pu activer le moteur 3D et mettre un gros polygone monochrome...

Déplacez le BG_PALETTE[0] dans la boucle principale, modifiez sa valeur d'une itération à l'autre, et vous aurez un fond "stroboscopique". Pour faire un dégradé, par contre, je pense qu'il faudra passer par le HDMA. Mais ça, je n'ai pas encore tenté.

Bon voyage, M. Nathan ;-)

Je note au passage que depuis le temps, libnds s'est dotée d'un mécanisme de gestion des ressources (allocation dynamique de mémoire vidéo pour les sprites) et d'une cache pour la mémoire vidéo contrôlant les sprites (permettant potentiellement de les mettre tous à jour pendant le vblank)

Monday, March 20, 2023

qmake (CMake and 'friends' pour une autre fois)

j'ai mis un tag 'tutoriel', mais considérez ceci comme juste un moyen pour moi de ne pas oublier les griffes que le chat m'a fait pendant que j'essayais d'apprendre à me servir d'un nouvel outil

Bon, des Makefiles, ça va faire plus de 20 ans que j'en mange. C'est plus ou moins la base des règles de compilation. Vous indiquez un nom de fichier à obtenir, puis les fichiers dont il dépend, puis les commandes à exécuter pour passer de l'un à l'autre.


Monfichier.o: Monfichier.c FichierImportant.h
	compiler Monfichier.c -o Monfichier.o
     
Et bien sûr, vous avez le droit de rendre ça plus générique:

%.o: %.c FichierImportant.h
	compiler $< -o $@

Bon, assez rapidement, ça devient plus complexe que ça, hein. Par exemple, il y a peu de chance que tout votre projet dépende de FichierImportant.h et seulement de ça. On risque plus d'avoir quelque-chose comme jeu.c qui dépend de jeu.h et sprite.h alors que sprite.c ne dépend que de sprite.h et menu.c dépend à la fois de menu.h sauvegardes.h et sprite.h, par exemple.

Si on a de la chance, le compilateur offre alors une option pour générer des fichiers de dépendances qui capturent ça (disons, jeu.dep, menu.dep et sprite.dep) et on s'en sort avec


%.o: %.c
	compiler $< -o $@ $(ET_GENERER_LES_DEPENDANCES)

-include *.dep

Mais restons-en là pour l'instant. Dès que le projet grossit, qu'il commence à dépendre de bibliothèque externes (pour les formats d'images, la gestion du son, etc.), et surtout, dès qu'on commence à vouloir le compiler pour plusieurs plate-formes différentes (linux et windows?), ça devient vite un beau gros sac de noeuds ... en particulier parce que si make est omniprésent dans le monde Linux, il est toujours boudé par Microsoft qui y va de son propre msbuild.exe qui manipule des fichiers XML déguisés en .vcxproj ... D'où l'intérêt de programmes qui vont générer des Makefiles à partir de descriptions plus haut-niveau du projet à compiler.

C'est le cas de qmake, notamment, qu'on va exécuter dans un répertoire vide en lui indiquant l'emplacement de nos sources et qui va créer un Makefile dédié à la compilation de nos sources dans ce répertoire-là en utilisant le fichier *.pro distribué avec les sources.


  mkdir build
  cd build
  qmake ../src
  
  • il y définit tous les programmes utilisés (compilateur, linkeur, etc.) dans des variables pour make;
  • il donne une règle indiquant quand re-générer le Makefile (est-ce que le fichier .pro a changé?) qui réappellera qmake;
  • il ajoute les règles pour préparer une distribution du programme, nettoyer les fichiers intermédiaires, etc.
  • et surtout, parce que qmake est lié au projet d'interface graphique Qt, il prend en charge tout ce qui concerne la génération de code pour l'interface graphique à l'aide de moc.

A travers son fichier .pro, qmake prend en charge le fait que les différents compilateurs ont des préférences différentes pour les macro-définitions (à ajouter dans DEFINES) ou les répertoires à explorer quand il tombe sur un #include (à ajouter dans INCLUDEPATH). Idem avec les bibliothèques à passer au linkeur. On trouve évidemment aussi SOURCES pour les fichiers à compiler et HEADERS pour les déclaration de classes contenant des annotations pour le système de signal/slot propre à Qt (qui en aura besoin pour générer du code et compléter les vtables).

Une autre grande différence entre make et qmake est que ce dernier se veut multi-plate-formes et indépendant du shell. ça se traduit notamment par une manière franchement confortable pour les éléments conditionnels, comme


linux {
    LIBS += pthread
    !isEmpty(ENABLE_GL) {
       SOURCES += backend/opengl.cpp
    }
}
windows {
    LIBS += direct3D
}

Et autres astuces du genre. Attention par contre: si il était possible de faire des Makefile qui incluent d'autre makefiles (notamment pour combiner les résultats de plusieurs répertoires), la chose qui s'en rapproche le plus avec qmake c'est le TEMPLATE = subdirs, mais qui à l'instar de makefiles récursifs, ne partage pas les variables d'un fichier .pro à l'autre. Oui, je sais, en lisant ça, ça semble évident, mais ça l'était nettement moins en écrivant le makefile. La solution, c'est de faire venir la variable de l'extérieur soit avec qmake ENABLE_GL=y sur la ligne de commande, soit avec Qt Creator


 Une approche pas toujours très confortable, cela dit, en particulier à cause d'une gestion des dépendances entre variables et fichiers .o pas toujours très claire ... je comprends pourquoi mes collègues lui ont préféré include (../projet.pri) dans les .pro-feuilles et qui contient les DEFINES += ENABLE_GL=yes et autre MODEL_EXPORTER_PATH=/usr/bin/export_to_gl

Tuesday, November 14, 2017

Qui est volontaire ?

Je voudrais pouvoir faire une série "tutorielle" pour introduire progressivement les concepts de mon moteur de jeu et fournir une base de travail à d'éventuels game-makers amateurs qui voudraient s'essayer sur DS. Je voudrais aussi qu'ils puissent avoir quelques graphismes de référence, y compris les personnages.

ç'aurait pu être la bestiole de Pipemare (RSD), mais j'ai du mal à me l'approprier. ça ne peut pas être Jack Boost, Badman, Spector, parce qu'ils ne m'appartiennent pas en tant que personnage. Je n'ai pas envie que ce soit Bouli ou Biokid parce que j'y suis trop attaché, alors qui ?

For ten years now, I've been developing a game engine for the Nintendo DS. It isn't perfect but it already allows to do interesting things. And while most of the homebrew we've seen in the golden era of NDS development were using static screen, with GEDS, we have the full power of large maps and parallax scrolling.

Yet so far, I have failed to offer a workable starter kit for the libgeds and dsgametools. Apple Assault source code is mostly obsolete. Discovery packs for LEDS and AnimEDS weren't really helping to kick into game development. And my tilesets are too complicated for a 8-years-old to start sketching levels.

I'd like to take some time to craft something better. A small tileset inspired by Kenneth Fejer mockups. A generic-enough-but-yet-inspiring character that could be sent onto a Commander Keen / Nicky Boom-like adventure.


J'ai envisagé de commencer par un exemple basé sur Out'm'Up, SeaFox ou Crazy Brix, mais aucun n'est vraiment pratique pour montrer ce dont le moteur GEDS est capable (scrolling multi-niveaux, animation modulaire, etc). Non, il me faut une aventure à la Nicky Boom ou Commander Keen.

Reste les personnages secondaires que j'ai développés. Nono le gamin malchanceux (une version anonymisée de Badman, en sorte), Mo le sorcier maladroit, Mol la taupe punk ou Mike Boost, le talentueux mais ténébreux. Ou alors affubler spector d'une tête de souris ?

Rien de complètement convaincant. Reste la possibilité de faire des pixels sur mesure. Voici donc "power kid", qui a l'avantage d'être un perso rigolo.

Of course, given the amount of games I have contributed to together with other PPP Team members, it may sound weird that I'm on a quest for a character to drop to the public domain. The thing is, most of them were designed by other members (Badman, Jack Boost, Biokid) or collectively... or were pastiche of copyrighted characters (Calimero, Xeen, Frogger). My best candidate would be the punk mole I inserted in Badman II and who was playable in my brother's "4 to save Toon Land", althoug -- as you can see from the sketches -- it is definitely not the only option.

Sunday, June 18, 2017

runme + assault = todo

I'd love to have the time to provide a real tutorial for people to start working with libgeds. So far, the best I have is a package with 8-bitifed graphics for AppleAssault and the corresponding game/character scripts... which -- thanks to some work I did a few weeks ago -- now also comes with a copy of RunMe that can run all of that. Maybe that will at some point make it more interesting to start working with the Game Engine for DS.

Of course, because runME is a tool primarily designed to transfer files, it will not exactly be easy to start a game there.


Bon, j'avoue, j'adorerais avoir le temps de travailler sur un vrai tutoriel pour le système lib geds, mais jusqu'ici, la seule chose qui s'en rapproche un petit peu, c'est une sorte de kit avec les graphismes de Apple Assault en version 8-bit et les script correspondant pour les personnages et pour le jeu. Et grâce au travail de ces dernières semaines,  tout ça i tourne maintenant avec une version récente de runme. youpi. Peut-être que ça rendra les choses plus faciles pour ceux qui veulent commencer à travailler avec le game engine for ds on peut toujours rêver...




click 'offline', then 'cmd'click 'A' to pick one of ASSAULT*.CMD, and then click the name you want to runpress now L+A to load the script into the memoryand now press L+Y to process that script to the end.

Évidemment, le programme 'runMe' est avant tout un outil de téléchargement. Donc il faut un peu chipoter pour pouvoir démarrer son programme:  passer la détection réseau , par exemple, puis choisir le fichier qu'on veut démarrer, et des commandes un peu barbare du genre La+À ou L+y pour démarrer la lecture du script ou pour l'interpréter sur la DS.

When I want to run this, I do it with desmume-cli, using --cflash-path=AA-efsroot (which is in the 'runMe.zip' archive) and --load-type=1. But that only works in Linux. For windows user, you'll have to go into config->slot2 and setup the directory manually (I just hope for Windows users that they can somehow save that configuration)
Il faudra aussi s'assurer que l'émulateur a accès au fichier du jeu qu'on veut essayer. Moi, je fais ça à la ligne de commande dans Linux, mais évidemment, les gens qui travaillent sous Windows devront passer par les menus de configuration de desmume pour avoir la même fonctionnalité ( voir l'image).

Voilà, avec tout ça vous avez la possibilité de tester le jeu que vous avez vous êtes en train de développer, mais malheureusement, s'il y a des erreurs dans le script, c'est encore très laborieux de les trouver et de les corriger. On peut faire mieux avec l'outil de test automatique que j'ai développé pour mettre School rush au point, mais c'est un truc qui doit être compilé à part. Et pour l'instant, il faut même le compiler à chaque fois qu'on veut essayer de traiter de nouveaux script pour un nouveau jeu, donc il faudrait que je fasse appel à l'équipe pour avoir une variante qui tourne sous Windows histoire que les jeunes puissent essayer de faire le même. Mais voilà, l'équipe, pour l'instant c'est juste bibi. alors soit vous vous enrolez, soit de vous patientez. Ciao.

All this makes you able to try the game you're developing, but when there are errors in the script, you just have a stop with the content of the offending line dumped.
Hopefully, It can now also be checked with 'testme', the unit-testing tool for current School Rush game. (which unfortunately still requires a rebuild for Windows everytime you change the scripts you want to check).

I could really use a helping hand to get that going somewhere. Someone who's used to do builds of Linux native projects in a Windows environment. Even then, it's unclear whether I can compete with a tool such as DSGameMaker, but I still think people should have the choice ^_^


Oh, et si vous allez jusque là, la présence du "log" deviendra vite gênante dès le niveau chargé avec succès. Rassurez-vous: il est tout à fait possible de le faire disparaître: il suffit pour ça de toucher le bouton 'log' sur l'écran du bas. Et si vous voulez retourner charger un autre niveau, le bouton "beam out" vous ramènera sur l'écran avec la liste de fichier et le "clavier virtuel" (qui fait les "lettres paires" quand on garde L enfoncé, soit dit en passant).

Et pendant que vous lisiez tout ça, j'ai ajusté la position des vagues d'encre pour le "niveau retour" de School Rush...

Thursday, February 21, 2013

cleaning up a git repository ...

That's just some misluck: your git push is taking ages to complete. By checking it out later on, you realise you have pushed videos that do not belong there. git rm Videos/* is merely placing a curtain over a broken window: videos are still in the repository, they still make any clone über-huge.
Take your TARDIS and go erase any evidence of your mistake before someone else clone your repository! But before doing that, a git pull is required so that you're sure you're operating on a fresh space-time.

gitk

with this tool, find the revision where your Videos come from. Then grab the commit-id (sha1 hash) of the commit just before. Let's say you messed up on commit deadbeef and that cafebabe was your commit just before.

the command we want to execute on every commit between cafebabe and now is

git rm --ignore-unmatch -f Videos/*

The way to travel through time on cafebabe..HEAD to enforce that is

git filter-branch --tree-filter "git rm --ignore-unmatch -f Videos/*" cafebabe..HEAD

I bet you want to update gitk's view and explore commits to check all signs of your videos are indeed gone. Good.

Now, let's really cover our tracks:

mv .git/refs/original /tmp/refs-original-git
git reflog expire --expire=now --all
git gc --prune=now
git gc --prune=now --aggressive

If you were lucky and realised your mistake before the push, you're now done. If you had it pushed to the master repository, you still need to push these fixes ahead:

git push -f

And if you have a second clone of the master (at home?) where the videos still exist and that you want it as clean as the master,

git pull --rebase --verbose

(ooh, I wish so hard it had a "--dry-run" mode. Maybe you're better to clone your @home repository, and give the command a shot on the clone first so that you can check it has no undesired side-effects. The man pages claim that pull --rebase is potentially dangerous as it may affect your history. This is precisely what I want, but if for some reason your control on the master repository is weaker (i.e. someone else could alter its state between push -f and pull --rebase), you may want to follow the manpage advice and read git-rebase manpage first.)

Tuesday, February 05, 2013

Rope it up!

Paul's tutorial on ladders was on my "readme" list for the Cybook last night. I liked how he motivated them as a way to introduce discontinuity in control mechanics and physics response. It sounded "Eureka" in my mind and I wanted to go further, so I asked Bilou and Bouli to illustrate the other concepts of the article.

In my game engine, such discontinuity can be handled naturally as I can attach different control chains to the different player states.
(Note that I will consider that ropes are just an visual alternative to ladders, that do not bring significant gameplay difference -- i.e. you can't swing ropes yet).


J'aime beaucoup la façon dont Paul Firth introduit les échelles dans son tutoriel. "Rompre la continuité du gameplay". Forcer le joueur à utiliser temporairement d'autres règles de déplacement que celles du platformer, et donc l'obliger à se défaire d'une part de son "confort" de marcheur. Rien à voir, donc, avec le grillage ou Mario se déplace librement dans le château d'Iggy.
La bonne nouvelle, c'est que ce genre de dualité est parfaitement prévue dans mon modèle de "machine d'états" pour la gestion des personnages. Pas besoin, donc, de rajouter des booléens dans tous les sens et des tests tous côtés.

En outre, dans un niveau largement fourni en "plateformes à sens unique" (p.ex. un empilement de livres ;), les cordes et échelles peuvent aussi être un moyen d'offrir localement au joueur la possibilité d'aller "à contre courant". Une seconde bonne raison de ne pas se contenter du moteur de jeu actuel, donc.

Another think that ladders/ropes bring is that they introduce platforms that can be "climbed down". Platforms that let you jump through but not fall through is pretty common in platformers, and they let you build levels where the player is denied to move backwards. A rope is an escape path in that way.

Now let's move to the technical part. The walk/climb transition is not so obvious to set. In Paul's approach, it involved the introduction of several sub-states and sub-tiles type to be processed properly. This is typically the kind of thing that I've learned to be bug-prone and that rather quickly limits the addition of new mechanics to your character.

Ideally, it must be possible to let the game engine align the character to the rope/ladder so that climbing animation works. As mentioned in a former post, this require the combination of the cando(F_LADDER) property on the tiles occupied by the character and dpad&KEY_UP guardian expression.


Il me faut donc un "micro-contrôleur" de personnage capable de s'aligner sur une échelle (align_flag), un capable de créer un évènement lorsque le personnage passe à un endroit où un nouveau mouvement est possible (detect_flag), et évidemment, un comportement pour monter/descendre à l'échelle. Le reste n'est plus qu'affaire de mettre le bon contrôleur au bon endroit, et à filtrer les évènement générés par detect_flag en fonction de la direction prise par le joueur.

It may also happen that you insist on your player to release DPAD left/right before he can climb. This is more likely to be the case to climb down: pressing DOWN will first force Bilou to duck. Only in that "duck" state, we check for ladders. The "align-to-[F_LADDER]" behaviour controller can then be affected to the "getting down" non-cancellable animation.




Saturday, September 08, 2012

VRAM_E_LCD

I previously messed up with the sources of "Tetris Attack" to gain experience with DS development. Among the things Sten used, there was multi-palette display, using an extra bank of VRAM instead of the "regular" PALETTE_BG and PALETTE_SPRITE.

 videoSetMode(MODE_0_2D |
DISPLAY_BG0_ACTIVE | /* the background */
// ... more settings
DISPLAY_BG_EXT_PALETTE);
// 128K
vramSetBankA(VRAM_A_MAIN_BG);
// 128K
vramSetBankD(VRAM_D_MAIN_BG_0x6020000);
vramSetBankB(VRAM_B_MAIN_SPRITE);

The video mode initialisation we have here is fairly standard (note the old-time code with VRAM addresses still exposed to the homebrew coder :P), except for DISPLAY_BG_EXT_PALETTE flag. You can turn extended palettes on/off separately for sprites/backgrounds, or for top/bottom screens, but if you enable it for background, it's used for *all 256 colors backgrounds*. Period.

  // 64K, can hold palettes for all four BG layers
// allocate for cpu access
vramSetBankE(VRAM_E_LCD);

// background
Decompress((void*)(0x6880000), background_pal_bin);

// blocks
Decompress((void*)(0x6880000 + 256*16*2), sprites_pal_bin);
// an alternate palette with colors dimmed so that we can
// easily dim the whole playfield when the game is over...
CreateShadedPalette((u16*)(0x6880000 + 256*17*2),
(u16*)(0x6880000 + 256*16*2));

This magic 0x6880000 memory address sits within the 'LCD-mapped' region and is where VRAM bank 'E' appears. Right then, it's "offscreen", somehow, just ready for setup by the CPU. Each palette is 256x2 bytes long, and each background will have access to its 16 palettes at fixed offset. In this game, we had the backdrop on BG0 (thus palette at 0x6880000 to 0x6881E00 = 0x6880000 + 512*15), "blocks" (or apples) on BG1 that has its first palette at offset 512*16 (0x6882000) and its second palette at offset 512*17 (0x6882200). That makes lot of room unused (15 unused palettes for BG0 and 14 unused palettes for BG1), but if you want them to use different palettes (e.g. being converted independently from each other), you don't really have a choice.

  // text
Decompress((void*)(0x6880000 + 256*16*2*2), font_pal_bin);
// more background...
Decompress((void*)(0x6880000 + 256*16*3*2), singlebackground_pal_bin);

Score and status are displayed with a tiled layer too, implying yet another palette is needed. He could have used a macro XPALETTE(bg,slot) defined as ((void*)(0x6880000 + 256*(16*bg+slot)*2), or (possibly), use bank F/G forcing BG 0 and 2 to share the same 16 slots, but ensuring that those slots in use by BG2 are never needed by BG0 and vice-versa.


// then allocate it for extended palette use
vramSetBankE(VRAM_E_BG_EXT_PALETTE);

Enough setup: we let the video hardware access those fancy colours ^_^

Tuesday, August 21, 2012

One git to rule them all ...

Noticed how apparently simple things tend to turn into giant mess if left unattended ? That seems to be the case with game art as well. Revision, recoveries, distributed development and project shifts have turned my 4 sprite files into a nightmare of name-collisioning fellows. Giiiit tooo the Rescuuue!

Petit rangement d'été grâce à Cyril qui maitrise mieux git que moi. A partir de tous les répertoires DATA (et des cartes-mémoire de mes DS), je voudrais avoir un endroit unique où il fait bon vivre pour les sprites et autres maps, où les duplicatas ne seraient plus un problème, pas plus que les redondances dans les noms.
Première chose: activer la syncrhonisation "git" sur un des répertoires 'DATA' d'une de mes machines.

cd DATA
git init
git add *.spr *.SPR
git commit -a
Bien. Maintenant, créer l'espace unique ... le paradis des sprites.
mkdir ../DATA.git ; cd ../DATA.git
git --bare init
cd - # goes back to DATA
git remote add origin /path/to/DATA.git/
git push origin master

Il sera désormais dénommé "master", hébergé sur la machine host. Depuis mon portable, je pourrai donc faire un "clone" de mon jeu de données pour y ajouter les fichiers détenus localement:
git clone host:/path/to/DATA.git/
cp ~/DS/*.spr
git add *.spr
git commit -a

Une fois le "git local" préparé, je synchronise la copie-maître avec git push, tout simplement. Le répertoire DATA d'origine peut aussi accepter les nouveaux-venus via git pull à condition d'avoir copié la section [branch "master"] du fichier .git/config

Reste à faire le ménage et à structurer ça par projet plutôt que par provenance ... sur une branche parallèle, sans aucun doute.

(à relire: https://www.jesuisundev.com/comprendre-git-en-7-minutes/ )
git log --pretty=oneline --graph --decorate --all --abbrev-committree view of "where am I"
git branch -ashow known branches

Tuesday, May 22, 2012

Dumblador turns back

Here it comes. Some final tweaks on dumblador's "walk to the right" animation last night and I now have a CompoundGob that turns back when it encounters a wall (how sweet ^_^) Dumblador avance et fait demi-tour. L'occasion de revenir sur le fonctionnement des personnages dans mon moteur de jeu. Accrochez-vous un peu: le 'gobscript' est conçu plus comme un modèle de bytecode en ASCII que comme un véritable langage de programmation, mais comme dumblador est tout simple, ça reste lisible (j'espère ^^") Please bear some GobScript since it's still a pretty simple and straightforward monster:

anim1 = spr:0
anim2 = spr:3
statL0 :anim1 {
using walker
selfmove
test 0 (2,4)-(8,14) 0001
area 0 (0,0)-(8,4) 000A
}
on commence par importer des animations depuis le jeu de sprites (il suffit de connaître leur numéro, ici n°0 pour avancer vers la gauche et n°3 pour avancer vers la droite) et on leur donne des identifiants locaux (anim1 et anim2, respectivement). Le comportement de dumblador va maintenant être construit comme une combinaison d'états réutilisant ces animations: L0 pour avancer vers la gauche et R1 pour avancer vers la droite.
statR1 :anim2 {
using walker
selfmove
test 0 (2,4)-(8,14) 0001
area 0 (0,0)-(8,4) 000A
}

statL0->statR1 on fail (100 :0)
statR1->statL0 on fail (100 ~ :0)
statL0->statR1 on hit0 (100 :0)
statR1->statL0 on hit0 (100 ~ :0)
end
The only thing you have to remember about your spritesheet is now the position in the animation slots where your things are. That's slots #0 (walk left) and #3 (walk right) for me. You give them animation identifiers for your monster (1 and 2, resp.) From that, you can define states walk left and walk right. Each state defines an animation being played and a behaviour. The combination of using walker and selfmove means that DumBlador will move forward at constant speed, according to what's defined in the animation, but not faster than the value defined in its internal variable v0 (x speed)
Vous pouvez penser à un "état" comme à une action que fait un personnage: s'il est dans l'état "avancer", il fait son animation "un pas en avant" et utilise un morceau de code C++ un (contrôleur) identifié via using walker qui reprend les tests "y-a-t'il du sol? dois-je suivre une pente?, etc.". Le mot-clé "selfmove" indique que c'est l'animation qui définit de combien de pixel on avance à la fois pour une meilleur synchronisation. On reste donc par exemple dans l'état "vers la gauche" tant que tout va bien. Le contrôleur génère un "fail" lorsque le personnage arrive dans un mur. À ce moment-là il va falloir choisir un nouvel état. Les commandes du types statdepuis -> statvers on fail [condition] (action) permettent de construire les transitions qui seront testées dans ce cas-là. Ici, l'action consiste simplement à écrire une valeur constante (100 ou -100) comme nouvelle vitesse horizontale (variable n° 0). "walker" is what's called a controller for the GOB. It can also report events and indicate when it failed to perform the desired movement. The two on fail statements indicate transitions from "walk left" to "walk right" and back when it's no longer possible to move forward. That could be due to a wall or the end of a platform. With this simple script, dumblador "has no way to figure out". Another part of the current behaviour is that dumblador turns back when Bilou jumps on his "head". This is achieved by the two on hit statements. It's still necessary to provide the (relative) coordinates of the hitboxes manually. Here the "hit" statement corresponds to a passive area in the state definition, which is seen in dark cyan in the Inspector widget. "000A" is the bit mask that corresponds to Bilou's stomping action or AppleAssault's attacks. I could have used "0002" to make DumBlador impervious to punch attacks and reacts only to stomps. Enfin, pour compléter le comportement décrit par chaque état, les instructions test|area n° (coin1)-(coin2) avecqui donnent les zones de collisions (rectangulaires) qui vont permettrent d'interagir avec les autres personnages. La partie un peu "magique" se situe dans le nombre hexa avecqui. Pour chaque "type" de collision, il faudra choisir un des 16 bits disponibles et faire en sorte qu'au moins deux zones de collisions (p.ex. le corps de blador et celui de Bilou) utilisent le même bit (0001) mais dans un test pour l'un et dans un area pour l'autre, pour refléter l'asymétrie frappeur-frappé du moteur de collisions. Finally, the curious (100 ~ :0) are GobExpressions. It's a minimalist encoding of "set var[0] to -100". It works as those RPN calculators, where you type "2 40 +" to compute "40 + 2". Only simple parts are used here: push the constant 100, negate it (I don't have negative constants so far ^^") and assign it to variable 0 (mnemonic: Pascal uses := for assignments). Reusing a constant here is required as the walker controller is allowed to alter the speed in the final steps towards the wall. Would you like to give it a try yourself ?

Tuesday, July 26, 2011

Sonic Physics Guide

ça faisait un moment que je n'étais plus tombé sur de la lecture chouette comme celle-là. "pentes & blocs à pousser", "collisions", "gestion de la camera", etc. Ca va me faire de la lecture ... Attendez-vous à ce que je vous en dise d'avantage dans les prochains jours.

A neat pick: the Sonic physics Guide starring handling of slopes, curves, camera movement ... knowledge likely generated from years of hacking the good'old Sonic 1 and Sonic 2 roms (for SEGA Genesis). Expect some more detailed posts when I'll be done with my readings. 

TODO: make a small executive summary of it. You may use https://twitter.com/bigevilboss/status/1164309035270754306 as a starting point.

Tuesday, July 19, 2011

update on BG colors.

So that's the tileset as you know it so far. Used in "greenwoods demo" and "Apple Assault". It took me a few more missed attempts while trying to blindly copy Eyecraft's colours but I finally got it working... And what made it work was to build up a mockup first (the small image nearby) with BG, FG and character tiles alltogether in such a way that contrast issues became obvious. Once in that setup, with a 32x32 area to focus on where all the important things were there, I could bring the BG colours down, almost to grey, although keeping the blues with a higher value (blues and purple = dark, cold shadows. That's how our bluesky world and cone-rod eyes work ^_^)
Ingrédients: un tileset pas bien coloré, un sprite de référence, un éditeur capable de cloner des couleurs et une pinte de bonne humeur

  1. Créer une nouvelle Sprite Page et y copier l'intégralité des tiles à recolorer
  2. Copier également les tiles qui doivent avoir un bon contraste et le sprite de référence (personnage principal du jeu).
  3. Superposer l'arrière-plan et l'avant-plan en utilisant sur-chargeant avec (B) en mode curseur plutôt qu'avec (A). Construire un mini-mockup avec les tiles ainsi obtenus.
  4. Sélectionner un des tiles d'arrière-plan, cliquer sur "SCAN"
  5. Clicker sur une zone de la palette encore vierge pour définir la destination des couleurs clonées, puis L-cliquer sur le bouton [+] du sélecteur de couleur (QuickPal). Clicker sur "SYNC" pour valider les nouvelles couleurs.
  6. Passer la grille en 32x32 et charger (R-déplacer le curseur-A) la zone entourant le personnage.
  7. Basculer en mode "édition de palette" (SELECT)
  8. Ramener les couleurs clonées vers du gris. Vérifier sur la zone de preview en gris que le contraste est suffisant.
  9. Clicker sur "okay" pour prévisualiser les modifications sur l'ensemble de la page, puis validez en cliquant sur "sure". Retourner à la grille en pressant à nouveau SELECT
  10. Si nécessaire, pressez "START" et utilisez les boutons "<<" et ">>" pour faire apparaître en vis-à-vis la page avant et après modification. Clicker sur un tile "d'avant" puis L-clicker sur un emplacement "d'après" pour effectuer le transport.

See that horizontal area between [+] and [-] under your spriting grid ? This is the QuickPal widget. when you click [SCAN], all the colours present on the grid are copied there. "cloning" the colors consist of copying those colours at an alternate location in the palette. You first pick that target location by touching the palette widget, then launch the cloning by clicking [+] while holding L shoulder button of your console. All the tiles on your working sheet (on screen) will then use the new colours. All the remaining tiles are unchanged, included the backup copy of your working sheet, meaning that you can reimport some old tiles if you need to do so.


PS: ce blog comprend maintenant exactement 600 posts (dont 55 brouillons :P)

Thursday, April 08, 2010

ATTR0_ROTSCALE : partie I

Tout comme le GBA et la SNES, la DS est capable de zoom et "rotations" sur un sprite. Une fonction quelque peu obscure, sans doute parce que liée à une implémentation hardware alors qu'on serait tenté de penser "transformations géométriques" (comme dans le cas d'OpenGL) ou "programme de rendu". Eh bin non. Puisque je projette de me servir de sprites aussi pour la visualisation des zones de collisions pour mon InspectorWidget, voilà un peu de bidouille pour identifier les quelques points importants à prendre en compte. Mon "sprite de base" est une sorte de grille/radiateur noir dans une zone de 16x16.


Engine::sprites[oam].attribute[0]=ATTR0_ROTSCALE|ATTR0_ROTSCALE_DOUBLE|
   ATTR0_TYPE_BLENDED|ATTR0_SQUARE|y;
Engine::sprites[oam].attribute[1]=ATTR1_ROTDATA(oarot)|ATTR1_SIZE_16|x;
pSpriteRotation sr = Engine::spriteRotations + oarot;
sr->hdx=0x100; sr->hdy=0x0; // 8.8 fixed point : 0x100 is 1.00 here
sr->vdx=0x0; sr->vdy=0x100;
ROTSCALE_DOUBLE affecte la manière dont les coordonnées sont interprétées. C'est la zone de visualisation -- de 32x32 ici, matérialisée par les # blancs -- qui aura ses coordonnées en (x,y). Le sprite proprement dit, lui, verra son centre coïncider avec le centre de cette zone.
Si je donne les paramètres (hdx,hdy,vdx,vdy) = (1,0,0,1) -- ce qui correspond à un affichage sans aucune transformation -- mon sprite apparaîtra donc w/2 pixels plus à droite et h/2 pixels plus bas que les coordonnées (x,y) que je lui fixe.

A noter que [hv]d[xy] indique "de combien avancer (en X et Y) dans la "texture" du sprite lorsqu'on avance d'1 pixel Horizontalement ou Verticalement sur l'écran. Du coup, un zoom grossissant s'obtient avec des valeurs de hdx plus petites que 1.

Si je zoome (0.5,0,0,0.5) puis (0.25,0,0,0.25), le centre de mon sprite continue effectivement à coïncider avec le centre du "viseur". En revanche, impossible de sortir de la "zone de visibilité" de 32x32, même avec un facteur de zoom (4x) qui devrait m'afficher un sprite de 64x64, je ne vois plus que le centre de l'image.

En clair, le hardware permettra sans problème de légère déformations, comme celles subies par Morton Koopa Jr., et n'importe qu'elle réduction de taille (jusqu'à 256 fois plus petit ;), mais pour un zoom prend-toi-ça-dans-les-dents tels qu'on en a vu dans le combat contre Bowser dans SMW ou dans Turtles in Time, il faut ruser...
  • soit en utilisant plus de sprites,
  • soit en prenant des sprites plus gros que ce qui est nécessaire (p.ex. 64x64), zoomés de manière logicielle et rapetissés artificiellement par le hardware -- probablement la solution pour mes zones de collision,
  • soit (à mon avis plus plausible) en utilisant un fond plutôt qu'un sprite pour le boss. Ca s'est régulièrement vu sur NES où les sprites avait des tailles ridicule (8x8 ou 8x16 :P)
PS: tests réalisés sous Desmume 0.9.4 (linux) et confirmés par le hardware.

Sunday, January 10, 2010

Touche finale sur les pentes

Totalement par hasard, alors que je m'installe un petit "cubicle" de 8m³ à la maison pour y poser mon portable, je retrouve ce tutoriel de Florian Hufsky sur les pentes (le site original a disparu). Je lui chippe donc une de ses illustrations pour vous parler d'une des petites choses qui me restent encore à régler par rapport à la gestion des pentes dans Bilou. L'algorithme doslopes permet de suivre une pente, mais les états "standing" et "falling" ignorent encore presque complètement l'existence des pentes, ce qui donne des effets parfois curieux.

It looks like i've sketched the UI for monsters placement on a lost print-out of Florian Hufsky's tutorial on slopes. I'll then reuse one of his pictures to illustrate a last problem I need to take care of : landing on a slope. So far, the GravityController has not been modified to be aware of slopes (only WalkingController follow slopes with the doslope algorithm), but when the update will come, I know I'll have to ensure I prevent sprites from digging in the slope without making the slope "magnetic" to the point it attract sprites on the ground as soon as they enter the tile.

Il faut être prudent dans ce genre de situation, à éviter de positionner trop tôt le personnage à son emplacement d'arrêt sur la pente s'il n'y est pas encore arrivé. Sans ça, les pentes vont avoir un effet "magnétique" plutôt désagréable qui donne l'impression d'un moteur de jeu amateur.

Bon, ça ne peut pas être pire que le RSD game maker de mon adolescence qui ignorait purement et simplement l'existence des pentes. Le seul moyen de les simuler consistait à mettre un tile en gravité négative ... une approximation pour le moins bancale puisque le personnage se mettait à "osciller" autour de la position souhaitée :P

Tuesday, September 29, 2009

IRQ_VCOUNT

Voilà exactement le genre de technique de programmation que j'ai toujours admiré sur les consoles (et les ordinateurs Commodore, qui d'une certaine façon, sont des consoles avec un clavier et un lecteur de disquette). Mettons que je veuille modifier au milieu d'une image les paramètres de l'affichage, comme la palette de couleur, la priorité et la position de tel ou tel plan du jeu, etc.

Pour un jeu vidéo, ça me permettrait par exemple de garder une zone de score fixe dans un jeu à scrolling, ou (*ze* cas d'école) de virer la palette dans les bleus à la hauteur du plan d'eau. Dans mon éditeur de niveau, ça permettrait aux boutons de rester affichés correctement malgré les choix d'afficher ou de masquer les différents "calques".

Bien sûr, ça peut être approché aussi sur les cartes VGA etc. à condition de bien synchroniser le timer avec les débuts et fin de ligne à l'écran. Et c'est là toute l'élégance de la "solution console": il y a une ligne d'interruption dédiée directement à cette synchronisation (donc plus besoin de timers), et dans le cas de la DS, on a carrément le luxe d'un registre supplémentaire qui indique si la ligne courante correspond ou non à une ligne indiquée.

void vcountirq() {

if (REG_VCOUNT<192) {
BG_PALETTE[0]=RGB15(0,0,31);
REG_DISPSTAT = (REG_DISPSTAT&0xff)|(192<<8);
} else {
BG_PALETTE[0]=RGB15(0,0,0);
REG_DISPSTAT = (REG_DISPSTAT&0xff)|(160<<8);
}
The 'vertical count matched' interrupt is a perfect explanation of why I prefer underground console programming against overworld PC games. It is a very simple mechanism that lets code be run when the graphic controller is about to process line Y, and yet it enables a wide panel of special effects in games. The most common "showcase" is for sure implementing water: at a given horizontal position, you swap the palette and provide a "bluer" one so that bottom of the screen looks sunken.
The above code snippet does just that in a "raw" fashion where only backdrop color is changed. Don't let yourself scared by those bit masking, combining and shifting. It is just another facet of computing that is *very* handy when dealing with hardware registers. If you think of your process as an "electronic process" taking place on an array of bits, it just becomes natural.

Une petite fonction toute simple donc, qui sera appelée "sur la bonne ligne" et qui change les couleurs (ici, une seule, et entre deux valeurs pré-calculées, mais c'est juste pour la démo). Astuce, pour pouvoir être invoquée sur plusieurs lignes (plutôt que toujours sur la même) et donc remettre la couleur à sa valeur précédente, je change le numéro de la ligne où l'interruption doit se produire pour lui donner la valeur suivante dans l'interruption elle même (c'est le code avec REG_DISPSTAT).

Bon, okay, le code avec les & et les << fait un peu peur: c'est des manipulations de portions d'entiers pour modifier un byte qui se trouve en réalité dans la moitié d'un registre de 16 bits. La programmation DS est remplie de bidouille de ce genre. C'est de l'informatique d'avant XML, qui exploite pleinement la structure binaire des nombres.

Bien sûr, pour que ça fonctionne, je dois enregistrer la fonction vcountirq via la libnds (il ne suffit pas de lui donner le bon nom pour que ça se fasse tout seul. Vous rêvez, les gars!)
irqSet(IRQ_VCOUNT, vcountirq);

REG_DISPSTAT = REG_DISPSTAT|(160<<8);
irqEnable(IRQ_VCOUNT);
Vous remarquerez qu'on retrouve ici aussi les "manipulations bizarres" sur DISPSTAT, pour l'initialisation. Heureusement, le web est plein de gens qui passent leur temps à expliquer "X&FF = garder les 8 bits de poids faibles de X", etc.

Et pour désactiver l'effet,
irqDisable(IRQ_VCOUNT);
tout simplement.
J'aurais pu vous bricoler tout ça juste pour le fun ou parce que je prépare en secret un nouveau niveau de Bilou avec de l'eau. Malheureusement, je n'en suis pas encore là. Je suis dans une impasse avec mon éditeur de niveau, tout simplement: les boutons qui me permettent de décider si je veux voir ou non le fond, le plan principal, les sprites, etc. disparaissent ou deviennent illisibles par suite des manipulations pour changer la visibilité des plans. Résultat, rien de tel qu'un bon "virq" pour forcer l'affichage à "revenir" dans le bon état le temps de m'afficher la barre d'outils qui doit se trouver en bas de l'écran tactile...

Well, i wish i was posting this message because i'm working on an impressive water level. Not yet. I'm simply stuck with the UI of my level editor, where i need more layers than the console actually provide, so I'm doing a dynamic "horizontal split" when the user press "down" to access layer visibility setup. The hardest part is to integrate this properly in the GuiEngine (as usual with OOP >_<)

... Reste à intégrer ça dans le GuiEngine ...

edit: i managed to integrate and test on real hardware. It works, despite the amount of time taken by the operations confirms just "dma'ing" a new palette wouldn't work. The actual code for my 'vcount' handler is only 8 lines long (50 instructions, involving 8 stores to video registers), but it takes two scanlines to be fully processed. Plus, the first scanline shows a "flickering" area of some 24 pixels wide, suggesting that even the "companion code" that brings us from the interrupt to the "virtual void online" snippet is also too long. Maybe pushing it to ITCM would help ... and having the interrupt taking place one line in advance and forcing a busy loop until we encounter a HBLANK.

update: Pour la petite info, c'est maintenant intégré et testé sur la DS elle-même. Et ça marche, même si je suis surpris de voir 8 modifications de registres (les données sont constantes: ça se voit dans l'assembleur généré par le compilo) prendre quand même deux lignes d'affichage pour s'exécuter ! Je devrais peut-être essayer de mettre les routines concernées en ITCM, histoire de m'assurer qu'elles soient toujours en cache, ou qqch comme ça (sur du C++, ça va être coton, tiens!).