Tuesday, September 01, 2026

CompoundGob::RefreshPage

I guess you can hardly tell what's going on on the picture below, and I can't blame you for that. It's supposed to be a stomped scorpeye shell, but since I've added crawling animation for the scorpeye, we see that glitchy mess of sprite parts instead.


Ah oui, je m'étais fait un joli scorpion qui marche, mais si vous parvenez à l'assommer, tout à coup, il ne ressemble plus qu'à un tas de pixels complètement glitché. Comme j'envisage de passer refaire un coucou au "gaming club" de vendredi, ça ne serait pas mal de corriger un peu ça.

La cause du problème, je la connais: l'animation de la marche est construite avec 5 sprites hardware: un "large" pour la carapace du scorpeye et un carré pour chaque "patte". En revanche, les animations "carapace seule" et "carapace qui tourne parce qu'on l'a lancée" sont toujours inchangées et n'utilisent que 3 sprites hardware. Et le couac, c'est qu'en plus, le sprite large n'est pas sur le même d'une animation à l'autre. Il était donc temps que je me gratte un peu la tête et que je retrouve les fonctions-clé pour gérer ça, en particulier loadAnim() dans CompoundGob et setupOAM() qui peut redéfinir les tailles et aspects des sprites.

What happens is that you have dedicated bits in the hardware sprite entries to indicate whether you want a square, tall or wide sprite and of what size. So far, in my engine, those properties are defined once when you allocate hardware sprites for an object and then preserved as we just update coordinates, VRAM location and optionally palette slot of each sprite as we animate them. But by mixing the new crawl and the old spinning animations, I'm breaking an old habit of sharing the same structure for all animations of a given game object. So I need to extend the game engine with the following function:

  void refreshPages(const GobAnim *ani) {
    unsigned nlimbs = ani->getnlimbs();
    unsigned i;
    pages = ani->getpages();
    for (i = 0; i < nlimbs; i++) {
      if (oam[i]==NO_OAM) continue;
      pages[i]->setupOAM(sprites + oam[i], 0 /*?*/);
    }
    for (; i < nboam; i++) {
      if (oam[i]==NO_OAM) continue;
      sprites[oam[i]].attribute[0] = ATTR0_DISABLED;
    }
    nboam = nlimbs;
  } 

Je dois dire qu'au départ, je m'attendais à plus compliqué, mais la petite fonction ci-dessus et une brave ligne de plus, les animations se sont réparées presque d'elles-même. à utiliser avec prudence tout de même: le système ne se déclenche que si les deux animations ont un nombre différent de sprites.

To be honest, I was expecting it to be harder to code. It was a bit tedious to locate where to act in the code (state transition? animation loading ?) and I spent a significant part of a holiday afternoon in ddd setting conditional breakpoints to figure it out. Then I got puzzled by the update/setup/allocate functions manipulating hardware sprites through the "SpritePage" class: the one that is used at every frame to update what we see on screen keeps aspect ratio and size of the sprite unchanged... but after all, it was just a matter of a small function called when we detect that the current number of OAMs (aka hardware sprites) is different from the number of limbs the animation uses. Pretty and straightforward.