gfx : le graphisme¶
gfx, c'est tout ce qu'un jeu montre à l'écran : le bundle qui porte son art, les palettes et leurs variantes, les sprites de la display list, le pool qui garde les bitmaps résidents, et le fond, un monde de tuiles de 8×8 qui défile et qui sert aussi de mode texte.
Une règle gouverne l'ensemble, et elle mérite d'être répétée : l'ordre des appels à gfx.sprite() est l'ordre de peinture, la dernière entrée est devant. Il n'existe aucun autre mécanisme de priorité entre sprites. La page des règles donne celles qui valent pour toute l'API. Les acteurs et les animations, qui alimentent gfx.sprite par phy.sprite, vivent dans phy ; les constantes (gfx.FLIP_H, gfx.TILE_FONT8...) et les limites sont réunies dans constantes et limites.
Le bundle¶
Le dossier d'un jeu porte son art en groupes de palette ; les préfixes font les liens, il n'y a pas de manifeste :
| Fichiers | Ce que c'est |
|---|---|
<g>.pal (768 o), <g>.atlas (format Kivy, multi-pages), les PNG indexés que l'atlas nomme |
un groupe : une palette et les images quantifiées avec elle. Un jeu en a un ou plusieurs (game, niveau2...) |
<g>.<v>.pal |
une variante : mêmes index, d'autres couleurs, pour le jour/nuit ou un flash |
<g>-<f>.map (texte), <g>-<f>-tiles.png (au plus 704 tuiles de 8×8, en ordre de lecture, la tuile 0 = le trou) |
un fond : un tileset et sa carte, dans la palette du groupe |
Noms de groupe et de fond en [a-z0-9_]. L'éditeur de bundles de l'App fabrique ces fichiers : le .pal et l'.atlas à partir de PNG indexés, le tileset et la carte à partir d'une image de fond.
gfx.bundle() → ok, message¶
Installe le jeu entier : toutes les palettes et variantes, toutes les régions de tous les groupes dans la bibliothèque, les fontes système en tête (ids 0..127), puis l'art, nommé <g>/<région>, et tous les fonds, validés. La première palette (ordre des noms) et le premier fond deviennent actifs. Sur la carte : des secondes la première fois (la NOR de RP2 et de RP3 sont programmées), rien ensuite grâce au cache ; dans le simulateur, quelques millisecondes.
Tout est compté avant d'être installé : trop de sprites (1 024, fontes comprises), trop de palettes (12), trop de fonds (16), ou plus de 2 Mio d'art, et le bundle est refusé entier avec le chiffre ; rien n'a bougé. Un PNG en vraies couleurs, une carte mal formée, une région hors de sa page : idem, avec le nom du fichier et la ligne.
gfx.id(nom) → id | nil¶
L'id de bitmap de la région "groupe/région", ce que gfx.sprite prend. nil si elle n'existe pas : c'est au jeu de le dire, au chargement.
Palettes et couleurs¶
gfx.palette(nom) → true¶
gfx.fade(nom, trames) → true¶
La palette active, tout de suite ("game", "game.nuit") ; ou en glissant, une palette interpolée en RGB565 par trame, les modes de rangée basculant à la fin. Le jeu ne dessine que des images du groupe actif : c'est sa responsabilité. Un nom inconnu est une erreur (avec la liste des palettes du bundle), comme pour gfx.background.
Un appel relance le fondu de zéro
gfx.fade est un déclencheur, pas un état. Appelé à chaque trame, le fondu reste figé sur son premier pas. Appelez-le une fois, puis laissez les trames courir.
gfx.color(i, r, g, b) / gfx.color_get(i) → r, g, b / gfx.effect(i, mode)¶
Une couleur de la palette courante (i de 1 à 255, composantes sans écrêtage : (r & 0xF8) << 8 | (g & 0xFC) << 3 | b >> 3), relue requantifiée ; le mode de mélange d'un index (gfx.TRANSPARENT, OPAQUE, TRANSLUCENT, ADDITIVE, SHADOW, BEHIND). Chacun reconstruit la table de mélange : à réserver au chargement, pas à la trame.
gfx.backdrop(r, g, b) → rien¶
La couleur de ce qui n'est pas dessiné, un registre distinct de la palette, puisque l'index 0 est réservé des deux côtés : clé de couleur des sprites, trou du fond.
gfx.sky(i0, n) / gfx.sky()¶
Le ciel : n index consécutifs de la palette, à partir de i0, forment une rampe verticale que tout pixel de fond à 0 prend à la place du backdrop, la case vide comme le pixel transparent à l'intérieur d'une tuile ; la ligne d'écran y rend l'index i0 + y·n // 480. C'est une rampe écran (l'horizon ne défile pas avec la caméra), et ce sont de vrais index : les translucides, additifs et ombres se mélangent à la couleur locale du ciel, exactement ; et la rampe vivant dans la palette, gfx.fade fait le coucher de soleil tout seul. Les rangées « derrière le fond » traversent le ciel comme le vide : un sprite derrière reste visible. Sans argument, coupe : le vide revient au backdrop. Coût console : une rangée de la table de mélange recopiée par ligne (512 octets), rien par pixel. Les couleurs de la rampe viennent du bundle ou de gfx.color.
Les sprites¶
gfx.sprite(id, x, y [, flags]) → rien¶
Ajoute une entrée à la display list. L'ordre des appels est l'ordre de peinture. x et y sont des nombres, arrondis comme sur la carte (lroundf : au plus proche, le demi vers l'extérieur, 100,5 → 101, −0,5 → −1). flags : gfx.FLIP_H, gfx.FLIP_V.
Jamais une erreur. Un id inconnu, un sprite entièrement hors écran, une liste pleine (256 par trame) ou un bitmap pas encore dans le pool sont comptés, pas signalés : voir gfx.stats(). Le dernier cas est le plus important à connaître : le bitmap est chargé au premier usage et, si le lot de la trame est plein, il manque une trame ou deux (missing). Un jeu qui veut être sûr d'un sprite le demande à l'avance avec gfx.load.
Pour un acteur, phy.sprite rend exactement les trois valeurs que gfx.sprite prend, et gfx.sprite(phy.sprite(a)) dessine l'image courante de son animation : voir phy.
gfx.print(x, y, s [, taille]) → booléen¶
Du texte en sprites, par-dessus tout, à n'importe quel pixel : pour un HUD. La fonte système : taille 1 = 8×8, taille 2 = 16×16 ; codes 32..95, minuscules repliées en majuscules, autre caractère = le bloc plein ; encre = index 191. Une entrée de display list par caractère (l'espace n'en coûte pas), avance de 8 ou 16 px. false si une entrée a été perdue.
gfx.size(id) → w, h¶
Les dimensions du bitmap. Erreur si l'id est inconnu.
Le pool : l'art résident¶
Le pool est la SRAM de RP2 : 320 Kio, en pages de 64 octets, un cache de la bibliothèque. Un bitmap y entre au premier gfx.sprite, 16 ordres et 96 Kio par trame au plus, le reste attend la trame suivante, et en sort par éviction LRU quand le pool est plein, jamais s'il a été dessiné dans les trois dernières trames. Ce qui déborde, c'est donc le working set d'une trame, pas la bibliothèque : un jeu peut porter 2 Mio d'art tant qu'une trame n'en montre pas plus de 320 Kio. Au-delà, des sprites manquent, chaque trame.
gfx.load(id) → booléen¶
Charge sans dessiner : true si le bitmap est résident ou part avec le paquet de cette trame, false s'il attend (lot plein) ou ne peut pas tenir. À appeler au chargement d'une scène, ou une trame avant qu'un sprite apparaisse.
gfx.unload(id) / gfx.resident(id) → booléen¶
Décharge (les pages reviennent trois trames plus tard) ; dit s'il est là. Facultatif depuis le LRU : un conseil, pas une obligation.
gfx.pool() → utilisé, total, résidents¶
gfx.stats() →¶
Les octets de pages tenus, 327 680, le nombre de bitmaps résidents ; et les compteurs de gfx.sprite (used : entrées de la dernière trame ; les autres cumulent).
Le fond : un monde de tuiles¶
Le fond n'est pas une image : c'est un monde de tuiles de 8×8 qui défile, que RP3 rastérise depuis sa NOR. Il emploie la palette du groupe, la tuile 0 est le trou (le backdrop), les tuiles 704..1023 sont celles du système : deux fontes, les mêmes glyphes que gfx.print.
gfx.background(nom) → true¶
Le fond "game-niveau1" : son tileset et sa carte. Sur la carte, ~2 ms sans écran noir. Le premier fond du bundle est actif d'office. Un nom inconnu est une erreur (avec la liste des fonds du bundle) : le fond auto-chargé rendrait un false muet invisible, et un main.lua périmé afficherait « le bon » fond par accident. Même règle pour gfx.palette et gfx.fade.
gfx.camera(x, y) → rien¶
La caméra en pixels monde, entiers ; latchée en fin de trame, toute la trame suivante est rendue contre elle, avec les sprites de la même trame. En mode NOR, hors du monde on voit le trou ; en mode local, tout est modulo 1 024 × 512.
gfx.bg_local(on) → rien¶
Le mode texte. La fenêtre de 128 × 64 tuiles que RP3 tient devient le monde lui-même : torique, jamais rechargée depuis la NOR ; ce qu'elle contenait reste visible tant qu'on ne l'écrase pas. false : retour au monde du fond, fenêtre rechargée entière.
gfx.tile(x, y, e) / gfx.tiles(x, y, t) / gfx.fill(x, y, w, h, e) / gfx.text(x, y, s [, taille]) → booléen¶
Écrivent dans la carte, en tuiles (x, y modulo 128 × 64). Une entrée e est un id 0..1023 plus gfx.TILE_FLIP_H / gfx.TILE_FLIP_V ; les bits 12-15 sont réservés, et le simulateur les refuse avec la ligne, là où la carte lirait hors du tileset. gfx.text écrit la fonte système : taille 1 = 8×8 (une tuile par caractère, gfx.TILE_FONT8 + code − 32), taille 2 = 16×16 (quatre tuiles, deux colonnes par caractère).
Le canal est lent, et ça se voit : les écritures partent avec le paquet vidéo, 624 octets par trame, appliquées en fin de trame, visibles à la suivante. Une rangée de 128 tuiles est un run de 260 octets, une ligne de 80 caractères en fait 164 ; un plein écran de 80 × 60 tuiles prend 16 à 20 trames. Rendent false si la FIFO (24 Kio) a débordé. Hors mode local, les mêmes écritures sont des sur-impressions que le prochain déplacement de caméra efface.
C'est la voie du texte de HUD et des menus : aucune entrée de display list, aucun cycle du blitter. Un compteur qui change écrit quelques tuiles ; une ligne réécrite pleine largeur ne coûte qu'un run.