Showing posts with label OAM. Show all posts
Showing posts with label OAM. Show all posts

Tuesday, April 19, 2022

des sprites 32x16 ?

Le support des sprites rectangulaires, c'est pas encore une grande réussite dans GEDS. En chipotant un peu, on parvient à avoir une planche de sprites verticaux ou horizontaux dans SEDS. Ils sont quand-même édités sur une grille carrée, mais soit.

Les choses sont déjà un peu plus louches dans AnimEDS, où de curieux résidus viennent se placer par-dessous un sprite 32x16. ça gâche un peu l'effet, mais disons qu'on s'en serait accommodé le temps de d'un ennemi ou deux.

There is somehow way to introduce non-square sprites in my SpriteEditor (with those 'new W(ide)' and 'new H(igh)' buttons), and the NDS hardware definitely has support for them, but let's face it: none of the game engine or Animation Editor was half ready for a 32x16 scorpeye 'shell'

Mais le moteur de jeu a l'air de perdre complètement les pédales, lui.


Une petite réactivation de l'afficheur-de-sprites finit par me pointer le problème: je suis déjà au bout des 1024 sprites possibles dans ce nouvel environnement... enfin, pas exactement. Je n'ai pas encore saturé les 128K de mémoire disponibles pour les sprites, mais seulement les identifiants disponibles.

J'ai déjà joué avec ça pour l'éditeur de sprites, mais le moteur de jeu, visiblement, n'était pas prêt non-plus pour ça ... ça va faire un gros commit ...

You see: it really doesn't make sense. We get parts of Bilou heads instead of getting our expected shell, and the shape isn't even rectangular. Part of it is because the engine cannot change the shape of a 'limb' for a compound character while playing (changing states doesn't let you break that rule). If you're rectangular on your frame one, you're rectangular all your life long. Another part of it is because I'm hitting the tile number limit. I thought SchoolRush was already using 128K of VRAM for sprites, but it seems I was wrong and everything used to fit the default 64K. Only the background tiles had been increased. Anyway, the Makefiles didn't like it, but it is now expanded to 128K. Anything else ? Yep. size = 16 + mode = wide gives you 32x8 sprites. Not 32x16. That explains the lone stripe I had in the final attempts.

Un autre soucis visible sur l'animation: les tailles de gobs pour une page donnée ne peuvent pas être changées en passant d'une animation à l'autre. Et j'avais gardé la première version 32x32 pour l'animation "immobile". Du coup, les autres animations tentaient aussi d'utiliser du 32x32 même quand les images à charger étaient en 32x16.

Ah, et j'ai probablement sous-estimé l'existence des sprites 32x8, ce qui fait qu'encore après tous ces patches, il me manque la moitié de ma carapace ...

Saturday, September 07, 2019

272 sprite pixels / line.


Si faire une image sur DS c'est un peu comme monter une bibliothèque-étagère contre un mur, alors faire une image sur Super Nintendo, c'est pareil. Sauf que le toit est en pente, qu'il y a un conduit de cheminée qui dépasse, des poutres de toiture apparentes et une tablette d'appui de fenêtre dans le chemin.

Bref, il y a des contraintes à gogo et il faut tenir compte de toutes ces contraintes-là en même temps. Une qui m'a particulièrement perturbé, c'est la limite à 34x8 pixels de sprites par ligne horizontale en plus de la contrainte d'un maximum de 32 sprites par ligne (sur un total de 128 sprites, quand-même. ouf.)

En clair, la console a beau être capable de faire le rendu d'un sprite de 16x16 ou 32x32 pixels d'un coup, ça ne lui donne pas plus la possibilité d'utiliser les sprites pour faire une ligne de vagues couvrant tout l'écran (256 pixels) avec 8 sprites de 32x32. Enfin, si, mais vous n'aurez plus droit qu'à deux personnages sur la même ligne, et pas 24 autres sprites.

Je me suis donc mis à essayer de comprendre comment la structure du chip graphique de la Super Nintendo pouvait bien expliquer ça. L'idée de base, c'est que la console n'a qu'une bande passante limitée à sa mémoire graphique, tout juste suffisante pour aller chercher assez d'informations pour construire une ligne d'image sur le temps qu'une ligne de graphisme est tracée à l'écran par le canon à électrons. J'avais déjà appris que c'était vrai pour les décors -- c'est ce qui explique qu'on doive descendre à 4 couleurs par plans si on veut ajouter plus de plans -- reste à comprendre ce que ça implique pour les sprites.

Nintendo DS and Super Nintendo graphic chips are quite similar in the way they build images. Multiple layers of tiled background plus independently-positioned sprites. But the SNES comes with way more constraints than the DS, and I don't mean resolution or colour count constraint. I mean the 'sprites per scan line' constraint and the 'sprite tiles per scan line' constraint. The NES had only one width for sprite: 8 pixels (one tile). SNES introduced the ability to use 16x16 and even 32x32 pixels sprites but there was still some part of the hardware that could only prefetch a fixed number of sprite tiles per line to do its job.

See, for every pixel on-screen, the graphic chip will have to decide whether one of the sprite's graphics should be shown, or whether background pixel should show through. But the graphics chip has limited bandwidth to the video memory, and most of the time, it uses it nearly to its maximum. That's why we have to drop colours per pixel if we want more background planes, for instance.

Pour chaque pixel que le contrôleur du signal d'écran produit, il va falloir tester les sprites actifs et pour certains (peut-être chacun) d'entre-eux, il faudra regarder si le pixel correspondant est transparent ou opaque. Le premier pixel opaque dans l'ordre de priorité défini par le hardware gagne le match et les autres ne seront pas pris en compte. Ce genre de chose peut être réalisée efficacement avec un bus à collecteur ouvert et un circuit de contrôle des sorties de chaque unité "pixel pour le sprite S" dépendant du résultat des circuits précédents. Mais ça suppose qu'on ait, pendant toute cette phase de génération d'image, accès à l'ensemble des pixels de tous les sprites pour la ligne en cours.

La bande passante de la mémoire n'est pas suffisante pour aller regarder chacune de ces valeurs à chaque pixel, il faut donc supposer qu'il y a à l'intérieur du chip graphique un cache de 8 pixels de large (d'où le x8) pour chaque comparateur et que ce cache est rempli une et une seule fois lors du traîtement de la ligne.

Il me restait à comprendre comment ce genre de préparation est compatible avec des sprites dont on peut modifier la position en cours d'image. Et là, surprise: selon Upsilandre,

[...] Tu peux pas modifier la [Sprite Attributes Table] pendant l'affichage, ça veut dire pas de multiplexage software possible contrairement à la megadrive.

Le principe des sprites au raster c'est que les 34 patterns sont chargé en amont pendant le Hblank dans des registres internes. Ensuite c'est un jeu de priorité cablé dans le hardware qui va fait que tel pixel de tel registre aura la priorité sur les autres.

En arcade c'est mieux, t'as un (double) line buffer donc ils peuvent charger les pattern pendant toute la durée de la scanline précédente (et pas juste pendant le Hblank) c'est ce qui leur donne ces performances bien supérieur

J'aurais cru que la technique de multiplexage -- classique sur C64 ou NES et possible sur NDS -- était omniprésente sur console, et sur la 16-bit de Nintendo aussi. Visiblement non.

The best re-design I can come from assumes that we have an 8-pixels-wide cache of sprite data for each comparator block we put in the chip. Video memory is still laid out as 'bitplanes' in the SNES and accessed 8-bit at the time from commodity RAM chips. Each unit we read gives us data for 8 pixels and reading more of them gives us more bit-per-pixel. What I couldn't come up with was how to make that work with on-the-fly modifiable sprites table. Chatting with Upsilandre killed that fantasy: unlike the NES, the Mega Drive, the NDS and even the Commodore 64, you cannot change the contents of the 'OAM table' describing sprites while the SuperNES is repainting the screen.

Et pour creuser un peu plus loin, les chiffres sont sur http://folk.uio.no/sigurdkn/snes/timing.txt. C'est un peu difficile d'en tirer un nombre d'instructions possibles par ligne retracée à l'écran, mais les chiffres annoncés me portent à croire que le 65xxx dont la Super NES est équipée garde une architecture interne très proche de celle du 6502, avec un bus unique entre les différentes unités internes et du "microcode" qui s'exécute à la fréquence de la master clock de la clock processeur mais qui peut nécessiter un nombre de cycles d'horloge variable (de 3 à 6) pour exécuter une instruction, ce qui donne entre 200 et 500 50 et 73 instructions par ligne retracée à l'écran.

On comprend mieux du coup les ruses de sioux du style "on ne va pas tester toutes les collisions à chaque images" qui régnaient en maître à l'époque..

Saturday, March 24, 2018

Tutoriel libgeds - jour 3

Un des éléments les plus importants dans un jeu vidéo (2D), ce sont sans doute les sprites. Rien à voir avec une boisson pétillante, il s'agit du terme consacré pour les objets graphiques librement déplaçable à l'écran, y compris le personnage principal, les adversaires, mais aussi certains power-ups ou des effets spéciaux.

Les sprites ont besoin de données graphiques (des valeurs de couleurs) tout comme les décors, et ces données sont de nouveau constituées de "tiles". On va utiliser surtout des blocs de 16x16 pixels ici (et donc constitués de 2x2 tiles) mais la DS peut en réalité utiliser des sprites de 8x8 à 64x64 pisxels (avec quelques restrictions quand-même).

Mettre à jour la mémoire vidéo pour que les sprites s'affichent à l'écran (quels graphismes, quels réglages, etc) est délégué à la classe SpritePage dans la libgeds, et la fonction "sync()" de GuiEngine fera le nécessaire pour mettre à jour les coordonnées en respectant les timings de la puce vidéo (et faire en sorte que ça ne clignote pas, par exemple).

The English version of these tutorials sit at https://gbatemp.net/threads/tutorials-for-my-game-engine.497728/, where they are hopefully more likely to connect to potentially interested users. I once had the French version hosted on a forum too, but that forum is now defunct, so I'd rather bring them back home before the forum goes broken.

Les SpritePages ajoute un niveau de correspondance entre les identifiants logiques des graphismes (ceux que le développeur utilise, "c'est sur la page 3, deuxième ligne, première colonne) et les emplacement "physiques" dans la mémoire vidéo (42eme bloc sur 256 en mémoire). Bref, le contenu de la SpriteRam est généralement un joyeux chaos résultant des créations et supressions de blocs alors que chaque SpritePage est organisée exactement comme l'utilisateur le décide.

le brol de la SpriteRam la SpritePage, aussi organisé que vous

Code:
class Hero : private UsingSprites {
  NOCOPY(Hero);
  const SpritePage *page;
  unsigned x, y;
  oamno_t oam;
public:
  Hero(const SpritePage *pg) : page(pg), x(128), y(96),
         oam((oamno_t) gResources->allocate(RES_OAM))
  {
    page->setOAM((blockno_t)2, sprites);

  }

  void move(int dx, int dy) {
    x += dx; y += dy;
    iprintf("(%u,%u)", x, y);
    page->changeOAM(sprites + oam, x, y, (blockno_t) 4);
  }
};
Notre petite classe 'Hero' montre tout ce qu'il faut pour avoir un objet mobile:
- se souvenir des coordonnées (le bon mot pour parler de la position à l'écran) actuelles;
- une SpritePage qui sait comment mettre à jour les entrées de la "MAO" (ou Mémoire des Attributs d'Objets -- Object Attribute Memory -- le terme de Nintendo pour parler des blocs de contrôle des sprites);
- avoir réservé une entrée de MAO et en retenir le numéro.

En disant que notre classe dérive de "UsingSprites", on obtient l'accès au tableau des MAOs (le tableau 'sprites[]') que le moteur de jeu va utiliser pendant la fonction sync(), mais aussi au gestionnaire de ressources (`gResources`) qui nous donne notre numéro de MAO. Du coup, la SpritePage a tout ce qu'il lui faut pour faire son travail.

Code:
void setactive() {
   SpriteRam myRam(WIDGETS_CHARSET(512));
   SpriteSet mySet(&myRam, BG_PALETTE);

   mySet.Load("efs:/bg.spr");
/*+*/ sprSet.Load("efs:/hero.spr");

  // rest of MetaWindow::setActive() as defined in previous tutorial
/*+*/ hero = new Hero(sprSet.getpage(PAGE0));
}
Charger les graphismes des sprites en mémoire vidéo ressemble très fort à ce qu'on a déjà fait pour les décors dans le tutoriel précédent. Sauf que cette fois, il faut s'assurer que les objets "SpriteSet" et "SpriteRam" resteront 'en vie' au moins aussi longtemps que notre Hero (qui les utilisera à travers la SpritePage). Du coup, on en fait des membres de la MetaWindow, plutôt que des variables temporaires. Ça signifie aussi qu'il nous faut attendre que les données soient chargées avant de pouvoir créer notre Héro.

Puis il n'y a plus qu'à tester l'état de la croix directionnelle (et les transmettre par des appels à la fonction 'move()') pour pouvoir déplacer le personnage en réaction aux mouvements imprimés par le joueur. Bon, ne vous attendez pas à quelque-chose de fluide ici: j'utilise toujours l'interface utilisateur prévue pour les éditeurs du projet "GEDS" qui ne produit un 'évèment DPAD' que lorsqu'on appuie sur une nouvelle direction. On arrangera ça la semaine prochaine avec les Animators.

Code:
if (keys & KEY_UP) {
  hero->move(0, -4);
}
if (keys & KEY_DOWN) {
  hero->move(0, 4);
}
if (keys & KEY_LEFT) {
  hero->move(-4, 0);
}
if (keys & KEY_RIGHT) {
  hero->move(4, 0);
}
Les détails sont sur github, comme toujours.

Wednesday, October 04, 2017

OBJ-WINDOW: Look through the ink

The DS video is made of multiple layers -- this should be no news for you. One of the video registers define which of these layers should be shown on screen, but that register also enables and disable a more obscure feature of the NDS: the windows -- that is, the ability to define multiple regions on screen and give them different set of layers to be shown. When the ink raise in School Rush, I'm not simply painting black squares over the scenery: I actually reduce the window through which the scenery can be seen (with a setup where nothing at all can be seen out of that window).

Petit tour d'horizon dans le document "gbatek" pour voir comment fonctionnent les sprites/fenêtres, une petite particularité de la console DS qui permet de basculer entre deux réglages de visibilité des différentes couches graphiques du jeu. Jusque là, je l'utilisais d'une manière assez simpliste pour dessiner l'encre ou les "rideaux" qui ferment la scène à la fin d'un niveau.

Un simple rectangle dans ce cas-là, qui permet de voir toutes les couches à l'intérieur de la fenêtre et aucune à l'extérieur.

The use I have so far of the "window" feature is pretty basic, but a recent talk with Adrian (GBA developer) made me realise that it would be just perfect to allow the game to give us a hint on where Bilou is when he's swimming up the ink to safety. The trick is that some sprites can be used to create another kind of window. Rather than being rectangular, it can have any shape ... and will be much easier to use than reprogramming the size of the window as we get horizontal interrupts amiga-style.

Pour que l'on puisse voir Bilou nager dans l'encre, il me faudrait donc une deuxième configuration qui laisse voir une partie du niveau tout en gardant assez de noir. Il devrait suffire pour ça de changer un simple bit dans la configuration.

J'hésite un peu quand à la manière d'implémenter ça ... Plus précisément, sur la manière d'indiquer depuis les scripts du jeu que l'on souhaite passer un certain sprite dans le mode "fenêtre". L'idéal serait probablement que l'information soit retenue dans les animations elles-même, mais sans nécessiter de modification de AnimEDS.

En même temps, l'utilisation d'AnimEDS n'est nécessaire que si j'essaie de micro-optimiser et d'éviter un sprite 32x32.

There are a few implementation details I'd like to sort out before implementing that. I already figured out that it should be under the responsibility of *Gob + GobAnim classes, the *Gob being the only class that can manipulate OAM entries (including the OBJ_WINDOW_MODE bits) and GobAnim being a natural place to setup flags indicating that "this appearance of the sprite should be a window (or alpha-blended, or rotated)". I might give it a try with a single 32x32 sprite and dedicated "flags xxx" script entry before I go for something more integrated that could replace the Flicker command currently in use by e.g. inkjets... 

edit: got it working. (by Oct. 14th) Some pixels to edit, now.

Thursday, August 04, 2016

Si j'étais graphiste ...

Si j'étais graphiste, mes sprites auraient plutôt cette trogne-là. Je saurais qu'une bonne animation, c'est une animation où on a pas peur de casser les cadres et j'arriverais à l'appliquer. Du coup, le meilleur environnement pour faire du bon travail, ce serait quelque-chose qui soit le plus souple possible. avoir suffisamment d'images disponibles simultanément pour les copier-coller et les comparaison. Bref, un outil comme SEDS ne me semblerait probablement jamais franchement pratique.

Let's be honest: I'm far from getting any close to what a real pixel artist produces. If I was, my spritesheets would look like those featured in Titus's Prehistorik II: I would not let canvas restrict myself and I would apply squash/stretch so that animations work fine. There would be significant pixels variation between frames, too. In a word, the SEDS+AnimEDS would be very unlikely to suit me. I'd need something more flexible, and in return, my game engine would need something to easily convert such flat sheets into usable assets.

Mais pour mettre des graphismes dans un jeu, faire des graphismes ne suffit pas: il faut aussi qu'ils puissent être intégré au jeu. Il faut que l'artiste puisse le plus librement possible retoucher son animation, y compris faire grandir ou rétrécir certaines images.

L'idéal pour ça, comme l'expliquait hier Eric Zmiro -- concepteur avec plus de 20 ans de métier -- c'est d'avoir le système le plus automatisé possible. Voici donc ce que j'ai compris du système qu'il décrit.

 I had the opportunity to compare the tools used in my own "School Rush" with the kind of tools and techniques that Eric Zmiro (now at Magic Pocket, and formerly developer of Prehistorik II at Titus). The core idea is to make the spritesheet-to-game data conversion as automated as possible. There cannot be any "great hotspot positioning tool" because there shouldn't be any human doing such task in first place.

From his explanations, I started to think about such automated framing tool, which is mostly a matter of separating lines that contain pixels from lines that contain none -- and having some conventions such as "an animation is bottom-aligned horizontally". Of course, there is still need to identify a reference position for each frame, so that e.g. the character's nose stays at the same position on screen while scrolling at constant speed, but a mere line of lone pixels could do the trick.


Sur base de la première planche de sprites, le premier algorithme à faire tourner est un système qui identifie les zones dans lesquelles il y a des images des zones vides. Une ou plusieurs lignes horizontale ne contenant aucun pixel (gris foncé): c'est une nouvelle animation. Une ou plusieurs lignes verticales ne contenant aucun pixels (gris clair) à l'intérieur d'une tranche d'animation: c'est une séparation entre deux étapes d'animation. On peut ensuite raffiner pour rétrécir verticalement la zone active d'un sprite.

1) on découpe les animations par planche (1 planche = 1 animation)
2) on a un outil qui capture les sprites, les convertie en bloc

3) quand un bloc existe déjà dans la banque des bloc, il ne le rajoute pas.
4) pour chaque sprite indique : [x, y, flipXY, color_index, bloc_number, size] (j'espère n'avoir rien oublié)


Si deux sprites sont identiques, ils seront partagés. Mieux, certains blocs peuvent être partagé même si ils appartiennent a des sprites différents
Encore mieux, des blocs peuvent être identique apres flip, ou être identique a une transposition de palette prêt..
Si elle me semble contre-productive, la décision "une animation par fichier" trouve tout son sens dans une équipe soumise à une date de sortie: sur une planche "prehistorik-man.png", on peut effectivement laisser le logiciel découper les bandes d'animations et faire référence à prun = pman[0][*]; pcrawl = pman[1][*]; pswim = pman[2][*]; pjump=pman[3][*]; etc. Mais imaginons qu'au terme de la production on décide finalement de laisser tomber le niveau aquatique. L'animation de nage tombe aussi et pjump devient maintenant pman[2][*]. Tous les autres animations doivent également être re-numérotée. On s'en passerait bien vu qu'il faut livrer la ROM fin de semaine pour que le jeu soit prêt début décembre...

But my understanding was yet one productivity-step below the Zmiro approach: by enforcing only one animation per sprites file, we now have a strong filename-to-game asset relationship. If the file is named pcrawl.png, then we can gather all the corresponding sprites as pcrawl[*]. When instead the 'crawl' animation is simply refered as the 5th one on pman.png, then we become vulnerable to the removal of e.g. 4th or 3rd animation, for instance because "well, the water level doesn't work, we drop the swim animation".

Yet, having the art split into multiple bitmaps doesn't mean that the tool importing them all cannot identify shared frames (or blocks) across the whole dataset. The final touch, for every frame the auto-cropper found, will be to isolate solid pixels into a minimum set of hardware-compatible blocks and pack the pixels of those blocks into tiles. The DS video chip, for instance, will only work with 8*W x 8*H blocks with W/H ratios of 0.5, 1 or 2 and a maximum size of 64*64 pixels. Even with a simple greedy algorithm, we can easily shrunk the amount of video memory required for a sprite to 1/3rd of the brute-force 64x64 sprite approach. Each batch of tiles has a companion set of OAM data that can configure hardware sprites' size, ratio position and indexes so that your 7 sprites actually look like a single, seamless cro-magnon protagonist to the player.


Reste à faire manger ça par le hardware d'une console comme la DS, qui ne fonctionne qu'avec des sprites hardware de 8x8, 16x8, 8x16, 16x16, 16x32, 32x16, 32x32, etc. jusqu'à 64x64. Sa mémoire est limitée, aussi donc avec un seul bloc de 64x64, le nombre de sprites affichables simultanément est sérieusement réduit. Ici, p.ex., on a un cro-magnon de 37x49 pixels qui prendrait 64x64. Avec un simpe algorithme de découpe "glouton", on peut le couvrir avec 4 blocs de 16x16 et 3 blocs de 8x16. Soit 6/16 de la mémoire nécessaire pour l'afficher d'un seul bloc.

AnimEDS travaille par petites éditions successives. Mais AnimEDS suppose que le contenu de la mémoire vidéo est constant. Ici, il n'y a aucun intérêt à garder un morceau de cro-magnon en mémoire si cette image-là n'est pas à l'écran. Du coup, tout ce qu'il faut c'est allouer un bloc de N tiles de sprites -- avec N assez grand pour la plus grosse étape d'animation -- et assez de blocs 'OAM' pour les contrôler. Pendant la synchronisation vidéo, c'est un nouvel ensemble "VRAM+OAM" qui est transféré simultanément pour changer les pixels que la console peut afficher et l'information sur quoi afficher où (les OAMs).

Then you need to have a game engine that can make the best use of these list of OAM datas. A real animator like those who work with Eric will easily saturate the 128K of sprite memory the DS has, so every new frame is likely to see new sprites tiles updated in VRAM together with the OAM updates. More on that in a future post.

Mais je ne suis pas graphiste. Il y aura encore pas mal d'eau coulant sous des ponts avant que je n'accouche d'un personnage pareil ... donc je vais garder SEDS et AnimEDS encore un petit moment. Par contre, si mon moteur de jeu évolue dans le bon sens, on pourrait envisager un outil d'importation des .png en .spr qui ferait ce genre de travail pour faciliter l'importation de graphismes non-SEDS dans un jeu tournant grâce à libgeds.

Saturday, July 28, 2012

OAM priority issues

I'm about to introduce significant complexity in the game engine for the purpose of displaying some compound animations properly. I've took the time to think about it (while drilling staircase for a kid-safety-barrier, if you ask :P) and figured out how to sort all the needed details. I just missed one point: this is absolutely not the only place where I need OAM depth-sorting.

Remember of Apple Assault's frenzy, and how you could end up not seeing that appleman that runs towards you because it's actually sneaking behind a row of stunned applemen ? That's never been fixed to date because I had no way to instruct the game engine that "those stunned applemen should be moved to the background".

Then I remembered of that "talk" by Rafael Baptista about resource management on the GBA. Before he entered the core meat of his talk, he suggested that copying OAM entries (you can think of them as sprite descriptors) could be the right time to follow a Z-order linked list and keep the "hardware" OAM entries and their "shadow" counterpart. Nice move, although I still wonder how he manages the scaling/rotation matrices along the way...

I used to have a large, one-chunk DMA transfer of all the sprites & rotation information. I would have to split that in 3-u16 slices. That sounds like I'll have to pay sverx's report on memory copies performance benchmark a second - and more careful - read... and possibly opt for an intermediate - sorted, but in-cache - version out of the vblank period and then DMA that into VRAM when the vblank is hit.

A final consideration: there is little chance that I need a fine-grained Z-sorting. low/normal/high priority should be enough in 99% of the cases, and the remaining case could likely be "sorted" out at GOB instanciation by internally sorting the OAMs the GOB received.

edit: I asked myself the right question: 'is that move out of DMA sync'ing gonna cost me framerate or not ?'. From that point on, I hacked a modified version of my game engine, that uses CPU-driven copy with merging of OAM and rotations into one single table (as the DS hardware expects) and started Apple Assault with aggressivity setting turned at max (so many apples that you'll experience "ghost" berry bats and shots). Good news: it still works flawlessly (that is, I can't observe any slowdown). Next: the depth-re-ordering...


edit++: just applied silly reverse-priority and order-enforcement-at-sync() in an AppleAssault check: it proves the concept: now Bilou can sneak behind stunned applemen. Useless, but you can't deny it's no longer the default z-order that is applied.

edit+++: checked that a (Simple)Gob set on zlist[0] appears in front of (Compound)Gobs set on zlist[1]... now I've got to figure out how to use that best to implement the pullmask for my "roll jump" animation ...

Sunday, July 22, 2012

pullmask

Je vous disais que l'animation de Bilou avait été fortement inspirée par Fury of the Furries ... Ça m'a donc semblé normal d'utiliser le "roulé-boulé-en-l'air" comme cas de test des "animations de transitions". Entre "je tombe" et "j'ai rebondi", par exemple.
Après avoir dessiné quelques sprites de plus pour Bilou lui-même, je me suis retrouvé tout bête dans AnimEDS: pas moyen d'avoir les pieds de Bilou qui passent devant puis derrière son corps, vu que l'ordre d'affichage reste fixe.

Bilou has Sonic and Fury-of-the-Furries influences, and one of them is rolling in the air. I wanted to use that as a test for transanims, and I started pixel'ing more rolling frame for Bilou's body. But when I opened AnimEDS to convert it into a full-blown animation, I just went ^^" : it's notwasn't built for animations where the relative order of limbs change over time. In short, the order you add limbs in the 'skeletton' also define which limbs obscures which other one. To do a roll, Bilou's feet need to be shown in front of his body in some frame, and behind his body on other frames. That just was impossible before AnimEDS revision 999 which features a "pull" button that let some limbs belong to a "second pass" rendering.

Bon, maintenant, il faut que je trouve le moyen de faire ça *aussi* dans le moteur de jeu.

The trick is easy to do in the animation editor: I can just use hardware OAM priorities and set ATTR2_PRIORITY(0) on those limbs that are on the "pulled layer". Unfortunately, that won't work in the game engine. I could end up with some limbs randomly appearing in secret passages or with other limbs randomly disapperaring when moving in front of complex background elements. The only viable option here is to re-order limbs in the Engine::sprites[] array. Let's see which is the best way to do it ...

Saturday, September 03, 2011

OAM Alpha

Résumons un peu:
- si je veux un niveau de transparence variable pour un sprite, le seul moyen est d'utiliser un sprite en mode bitmap/direct color ((Attr 0, Bit10-11) to a value of 3) et d'utiliser le champ prévu pour le no de palette pour "personnaliser" le niveau de transparence.
- pour les sprites à palette, ce sont les mêmes registres "BLDCNT" et "BLDALPHA" qui régulent la transparence des sprites depuis le GBA.

REG_BLDCNT_SUB = BLEND_DST_BG0|BLEND_DST_BG3|    BLEND_SRC_BG2|BLEND_SRC_SPRITE|BLEND_ALPHA; permet donc d'afficher des sprites et le calque 2 transparents par-dessus BG0 et BG3 (pour autant que ces deux calques soient effectivement derrière eux, sans autre chose pour faire obstruction).

Mais comment faire pour que seuls certains sprites soient transparents?

OBJs that are defined as 'Semi-Transparent' in OAM memory are always selected as 1st Target (regardless of BLDCNT Bit 4), and are always using Alpha Blending mode (regardless of BLDCNT Bit 6-7).
nous apprend-on sur gbatek.

C'est donc le rôle de ATTR0_TYPE_BLENDED (à utiliser à la place de ATTR0_TYPE_NORMAL) qui sélectionne le sprite en question comme "source d'alpha-blending" indépendamment de la valeur de BLEND_SRC_SPRITE dans le registre BLDCNT.

Cool. Je suis prêt à attaquer l'implémentation de "l'onion skin" dans AnimEDS ^_^

I need 'onion skin' preview of the previous/next frame in AnimEDS, which suppose some sort of alpha-blending on the frame editor, which is made of sprites. I've got a DS alpha-blending tutorial around for some years now, but my last attempt to have that working in the level editor was rather disappointing. So here's a recap:
  • You can make all the sprites have some alpha-blending by turning on BLEND_SRC_SPRITE in REG_BLDCNT_SUB ... not that useful. You're more likely to want a BG translucent that let sprites show through as well. That's with REG_BLDCNT_SUB|=BLEND_DST_SPRITE
  • You can selectively turn on blending of some sprites by using ATTR0_TYPE_BLENDED. That's what I need in my editors, but the blending level will be the same for all sprites (and layers).c
  • bitmap-type sprites use direct colors (bypassing the palettes), so the ATTR2_PALETTE() field of GBA sprites is useless and has been recycled into ATTR2_ALPHA. I guess you still depend on REG_BLDCNT to tell *what* you're transparent against... Oh, and don't forget to set REG_BLDALPHA to some value, of course.

Wednesday, April 14, 2010

oamstack from XeO³

Bion, ajouter des sprites dans tous les sens, c'est sympa. Continuer à avoir des sprites dans le 2eme niveau, c'est mieux. L'ennui, c'est que le GuiEngine, comme son nom l'indique, a été au départ conçu pour gérer des interfaces graphiques (SEDS et LEDS), et pas des jeux, ce qui signifie qu'il n'y a pas la possibilité à ce niveau de libérer des sprites -- ou plus précisément, les OAM, c.à.d. les zones en mémoire vidéo qui décrivent l'emplacement et les propriétés des sprites. Qu'à celà ne tienne: m'inspirant du "stack allocator" pour 6502 du projet XeO³, je rajoute une petite surcouche ...

I was making sure that sprites could be reclaimed and that you keep seeing little stars even if you got hit > 60 times ... Just one problem here : Engine::allocate is the only way to inform the GuiEngine of how much sprites it should sync to the DS video memory ... As the level is reset, that number is reset to 0, but since all the Gobs of the level have just been "pushed" in the oamstack ... well ... Engine::allocate() is never called and thus no sprite show up ... at all. Trivial to fix, but reminds me that you should always think twice when you alter the behaviour of something ... And that was a nice occasion to mention Dailly, Kekule & Russel's work on XeO3 : the Ultimate shoot'm'up for Commodore Plus/4 (which has some armalyte taste, if you ask me) and how this code gets inspiration from the 6502 "stack allocator" for bullets in that game.

Ca n'a pas tout à fait marché, mais presque: le GuiEngine retient le nombre de sprites qu'il a effectivement alloué et ne copiera en VRAM que ceux-là. Or, lorsque le niveau recommence, tous les OAMs précédemment restent dans la "pile de sprites recyclés" alors que le GuiEngine pense qu'il n'y en a aucun en service. Le résultat ? Bin le jeu tourne, mais plus aucun sprite ne s'affiche. C'est plutôt bête comme chou à régler, donc je vais aller règler ça pendant que vous découvrez en détail cet étonnant projet XeO³ mené par Mike Dailly, un (ancien ?) programmeur de chez DMA design dont je suis le blog depuis un moment.

edit: Btw, yes, this implies u8 oamstack[128] because there is at most 128 OAMs per screen and that the game is only on one screen. I can't think of another allocation scheme that would save me more and don't disturb run-time operations. I'd love to extend the mechanism to GameObject structure themselves (which have higher allocation overhead)

edit++: funny, they seem to use the same kind of "script-encoded-into-asm-constants" approach than I used in 2000 for Out'm'UP :).

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.

Saturday, December 31, 2005

OAM (t.a.g.)

You already know sprites are movable images that can be freely placed over the background image in a computer game. Now that you've read all of the OAM tags, you also know that a sprite as we see it in-game is often built of multiple hardware sprites which usually have quite restricted size (e.g. 8x{8; 16} for NES, 8x8 to 64x64 for DS, but not e.g. 24x16).

VRAM alone doesn't make a sprite.
Each hardware sprite is controlled by an entry in the graphic chip's descriptors, giving its position and other attributes (like flipping bits) and that completes the sprite tiles storage (spritesheet) in either ROM or video RAM.

On the nintendo consoles, these are named Object Attribute Memory entries. I use that 'OAM' term when I truly mean "a hardware sprite", not a Game Object's rendition on screen (sprite).