
gfx
Dessiner en pixels sous le texte de l'écran : formes, texte, sprites et défilement.
La bibliothèque gfx dessine des pixels sur l'écran de l'ordinateur : points, lignes, rectangles, cercles, triangles, texte dans une petite police en pixels, sprites écrits en chiffres hexadécimaux, et un défilement qui fait glisser toute l'image. C'est avec elle que l'on construit des cadrans, des graphiques, des plans de réseau ferré, des panneaux de commande avec de vrais boutons, et des jeux.
Le dessin se trouve sous le texte de l'écran. Ce que vous affichez avec print reste lisible par-dessus : un programme peut dessiner une image et continuer à se servir de print, term.write et read comme d'habitude.
local t = gfx.size()
gfx.clear("light_blue") -- le ciel
gfx.circle(262, 34, 16, "yellow", true) -- le soleil
gfx.rect(1, 128, t.w, t.h - 127, "green", true) -- l'herbe
gfx.rect(104, 30, 12, 30, "gray", true) -- une cheminée
gfx.rect(36, 72, 96, 56, "brown", true) -- l'atelier
gfx.triangle(28, 72, 84, 38, 140, 72, "red", true) -- son toit
gfx.rect(72, 98, 24, 30, "black", true) -- la porte
for i = 0, 7 do
local a = i * math.pi / 4
gfx.pixel(206 + math.cos(a) * 30, 96 + math.sin(a) * 30, "orange", 9)
end
gfx.circle(206, 96, 28, "orange", true) -- une roue dentée
gfx.circle(206, 96, 9, "light_gray", true)
gfx.text(8, 8, "ATELIER DE LAITON", "white", 2)
term.set_cursor(2, 18)
term.write("Le texte de term.write reste dessus")
gfx.size() | La taille du dessin en pixels : la taille de la grille de texte multipliée par 6 et par 9. |
gfx.clear([color]) | Efface le dessin, ou le remplit entièrement d'une couleur. |
gfx.get_pixel(x, y) | La couleur d'un pixel du dessin, sous forme de nombre, même s'il a été dessiné avec un nom. Elle vaut nil là où rien n'est dessiné, hors de l'écran, et partout avant le premier dessin. |
gfx.pixel(x, y, color [, size]) | Colorie un pixel, ou un carré de size × size pixels centré dessus. |
gfx.line(x1, y1, x2, y2, color [, thickness]) | Une ligne droite de (x1, y1) à (x2, y2), les deux bouts compris. |
gfx.rect(x, y, w, h, color [, fill]) | Un rectangle de w pixels de large et h pixels de haut, son coin en haut à gauche en (x, y). |
gfx.circle(x, y, radius, color [, fill]) | Un cercle autour de (x, y), ou un disque, ou un anneau. |
gfx.triangle(x1, y1, x2, y2, x3, y3, color [, fill]) | Un triangle passant par trois coins, donnés dans n'importe quel ordre. |
gfx.fill(x, y, color) | Le pot de peinture : repeint toute la zone de la même couleur que le pixel en (x, y). |
gfx.text(x, y, text, color [, scale]) | Écrit du texte avec une minuscule police de 3 × 5 pixels, à n'importe quel pixel et à n'importe quelle taille. Renvoie la largeur occupée. |
gfx.image(x, y, rows [, scale [, flip]]) | Dessine un sprite : une petite image écrite comme du texte, un caractère par pixel. |
gfx.scroll(dx, dy [, x, y, w, h]) | Fait glisser le dessin de (dx, dy). Ce qui sort de la zone est perdu, et ce qui entre de l'autre côté est vide. |
La couche de pixels
Chaque case de caractère de l'écran fait 6 pixels de large et 9 pixels de haut : la taille du dessin suit donc celle de la grille de texte.
| Ordinateur | Texte | Pixels | Couleurs |
|---|---|---|---|
| Calculateur à tubes | 40 × 14 | pas de gfx | téléscripteur papier |
| Ordinateur à transistors | 51 × 19 | 306 × 171 | phosphore vert |
| Mini-ordinateur | 51 × 19 | 306 × 171 | phosphore ambre |
| Micro-ordinateur | 51 × 19 | 306 × 171 | 16 couleurs |
| Microcontrôleur | 40 × 12 | 240 × 108 | 16 couleurs |
| Ordinateur moderne | 64 × 24 | 384 × 216 | 16 couleurs |
Le Calculateur à tubes imprime sur du papier et ne dessine pas : chez lui, gfx vaut nil. Un programme prévu pour tous les ordinateurs peut tester if gfx then ... end.
Coordonnées
Comme pour term.set_cursor, les coordonnées partent de (1, 1) dans le coin en haut à gauche. x augmente vers la droite, y augmente vers le bas. Les coordonnées décimales sont arrondies vers le bas : vous pouvez passer directement le résultat de math.sin ou d'une division.
Rien n'est jamais hors limites : ce qui dépasse de l'écran est simplement coupé. Un cercle à moitié sorti du bord dessine sa moitié visible, et un pixel en (-5, 400) ne dessine rien, sans erreur.
Couleurs
Une couleur est un nom ("red") ou un nombre de 0 à 15 (term.colors.red, qui vaut 14). Le nom spécial "none" (ou le nombre -1) efface : le pixel redevient vide et laisse voir ce qu'il y a sous le dessin. Les 16 noms et leurs numéros sont dans Couleurs. Un nombre doit être entier, de -1 à 15 : sinon le programme s'arrête avec bad argument #3 to 'pixel' (colour (0..15 or a name) expected, got number).
Les noms sont en anglais. Un nom inconnu arrête le programme, et l'erreur donne la liste des bons :
gfx.rect(10, 10, 40, 20, "gris", true)snippet:1: bad argument #5 to 'rect' (unknown colou r 'gris': white, orange, magenta, light_blue, yello w, lime, pink, gray, light_gray, cyan, purple, blue , brown, green, red, black, none)
Les couches : fond, dessin, texte
L'écran est dessiné en trois couches, de bas en haut :
- la couleur de fond de chaque case de caractère (
term.set_bgpuisterm.clear, ouwrite) ; - le dessin de
gfx; - les caractères du texte.
Le texte s'affiche donc toujours par-dessus le dessin, mais la couleur de fond d'une case est cachée partout où quelque chose est dessiné. Les pixels vides laissent voir les fonds des cases.
Le dessin reste à l'écran quand le programme se termine. term.clear n'efface que le texte : pour retirer le dessin, appelez gfx.clear(). La commande clear du shell, l'arrêt d'un programme avec Ctrl+T et un redémarrage effacent les deux.
Coût
Dessiner n'est pas gratuit. Chaque appel qui change des pixels compte au programme une instruction par tranche de 64 pixels touchés, en plus de l'appel lui-même. Un Micro-ordinateur exécute 1 200 instructions par tick à pleine vitesse, et remplir tout son écran (306 × 171 = 52 326 pixels) en coûte 817. Le texte coûte une instruction par caractère, multipliée par le carré de l'échelle ; un sprite, une par ligne, plus ses pixels.
| Appel | Instructions |
|---|---|
gfx.pixel, gfx.line, gfx.rect, gfx.circle, gfx.triangle, gfx.fill, gfx.scroll, gfx.clear(couleur) | 1 pour 64 pixels |
gfx.text | nombre de caractères × échelle² |
gfx.image | 1 par ligne + 1 pour 64 pixels |
gfx.clear(), gfx.size, gfx.get_pixel | rien de plus que l'appel |
L'écran est envoyé aux joueurs qui le regardent au plus tous les 2 ticks (un réglage du serveur), et seule la bande de lignes de pixels qui a changé depuis le dernier envoi passe sur le réseau : du changement le plus haut au plus bas. Un petit cadran animé ne coûte presque rien ; redessiner tout l'écran à chaque tick renvoie toute l'image à chaque fois. Ne dessinez que ce qui change (voir les recettes à la fin de cette page).
Écrans monochromes
L'Ordinateur à transistors (vert) et le Mini-ordinateur (ambre) n'ont qu'une couleur de phosphore. Sur eux, toutes les couleurs sauf le noir s'allument dans la couleur du phosphore, et le noir est l'écran éteint. Un dessin prévu pour eux utilise le noir pour « éteint » et n'importe quelle autre couleur pour « allumé ». (Un Moniteur LCD branché sur eux montre les vraies couleurs : voir Écrans et moniteurs.)
local noms = {"white", "orange", "magenta", "light_blue", "yellow", "lime", "pink", "gray",
"light_gray", "cyan", "purple", "blue", "brown", "green", "red", "black"}
gfx.clear("black")
gfx.text(8, 8, "LES 16 COULEURS", "white", 2)
for i = 1, 16 do
local x = 8 + (i - 1) * 18
gfx.rect(x, 40, 16, 80, noms[i], true)
gfx.text(x + 2, 126, tostring(i - 1), "white")
end
gfx.rect(277, 39, 18, 82, "white") -- un cadre autour du noir
gfx.text(8, 150, "LE NOIR (15) EST LE SEUL ETEINT", "white")
Le même programme sur un Micro-ordinateur :
local noms = {"white", "orange", "magenta", "light_blue", "yellow", "lime", "pink", "gray",
"light_gray", "cyan", "purple", "blue", "brown", "green", "red", "black"}
gfx.clear("black")
gfx.text(8, 8, "LES 16 COULEURS", "white", 2)
for i = 1, 16 do
local x = 8 + (i - 1) * 18
gfx.rect(x, 40, 16, 80, noms[i], true)
gfx.text(x + 2, 126, tostring(i - 1), "white")
end
gfx.rect(277, 39, 18, 82, "white") -- un cadre autour du noir
gfx.text(8, 150, "LE NOIR (15) EST LE SEUL ETEINT", "white")
La taille du dessin en pixels : la taille de la grille de texte multipliée par 6 et par 9.
- table
{w = largeur, h = hauteur}en pixels
local t = gfx.size()
print(t.w, t.h)
local grille = term.get_size()
print(grille.w * 6, grille.h * 9)
Utilisez-la plutôt que des nombres fixes, et le même programme s'adapte à tous les écrans. Ce cadre suit les bords de l'écran, quel que soit l'ordinateur :
local t = gfx.size()
gfx.clear("black")
gfx.line(1, 1, t.w, t.h, "gray")
gfx.line(t.w, 1, 1, t.h, "gray")
gfx.rect(1, 1, t.w, t.h, "yellow", 2)
gfx.text(5, 5, "1,1", "white")
local coin = t.w .. "," .. t.h
gfx.text(t.w - #coin * 4 - 3, t.h - 9, coin, "white")
Le même code sur un Microcontrôleur, dont l'écran fait 240 × 108 :
local t = gfx.size()
gfx.clear("black")
gfx.line(1, 1, t.w, t.h, "gray")
gfx.line(t.w, 1, 1, t.h, "gray")
gfx.rect(1, 1, t.w, t.h, "yellow", 2)
gfx.text(5, 5, "1,1", "white")
local coin = t.w .. "," .. t.h
gfx.text(t.w - #coin * 4 - 3, t.h - 9, coin, "white")
Voir aussi term.get_size()
Efface le dessin, ou le remplit entièrement d'une couleur.
colorstring|number facultatif- une couleur qui remplit toute l'image ; sans elle (ou avec
"none"), le dessin est effacé
Sans couleur, tous les pixels redeviennent vides et les fonds des cases du texte réapparaissent. Cela ne coûte rien. Avec une couleur, toute l'image est peinte, ce qui coûte une instruction pour 64 pixels (817 sur un Micro-ordinateur).
gfx.clear ne touche pas au texte, et term.clear ne touche pas au dessin. Ici, les cases sont mises en gris avec term.set_bg, le dessin les recouvre de bleu, et un rectangle de "none" ouvre une fenêtre où le gris réapparaît :
term.set_bg(term.colors.gray)
term.clear() -- la grille de texte : cases grises
gfx.clear("blue") -- le dessin les recouvre
gfx.rect(84, 50, 144, 72, "none", true) -- pixels effacés : le gris revient
term.set_cursor(16, 9)
term.write("un trou dans le dessin")
term.set_cursor(2, 2)
term.write("Le texte est toujours au-dessus")
Un programme qui redessine son écran depuis zéro commence en général par les deux :
term.set_bg(term.colors.black)
term.clear()
gfx.clear()Voir aussi term.clear()
La couleur d'un pixel du dessin, sous forme de nombre, même s'il a été dessiné avec un nom. Elle vaut nil là où rien n'est dessiné, hors de l'écran, et partout avant le premier dessin.
xnumber- colonne du pixel, à partir de 1
ynumber- ligne du pixel, à partir de 1
- number|nil
- le numéro de la couleur (0 à 15), ou
nillà où rien n'est dessiné
gfx.rect(10, 10, 20, 20, "red", true)
print(gfx.get_pixel(15, 15))
print(gfx.get_pixel(15, 15) == term.colors.red)
print(gfx.get_pixel(100, 100))14 true nil
Comparez avec un nombre : gfx.get_pixel(x, y) == "red" est toujours faux. Utilisez term.colors.red ou 14.
Elle ne lit que le dessin, jamais le texte. Elle rend service dans les petits jeux, pour savoir ce qu'une pièce en mouvement va toucher : un mur dessiné en gris, une pièce d'or dessinée en jaune.
Points et lignes
Colorie un pixel, ou un carré de size × size pixels centré dessus.
xnumber- colonne, à partir de 1
ynumber- ligne, à partir de 1
colorstring|number- un nom ou un numéro de couleur,
"none"pour effacer sizenumber facultatif- le côté d'un carré centré sur (x, y), de 1 à 64 (1 par défaut)
Avec une taille impaire, (x, y) est pile au milieu. Avec une taille paire, la ligne et la colonne en trop vont à droite et en bas. Au-delà de 64, la taille est ramenée à 64. Les points rouges marquent le point donné :
gfx.clear("black")
local tailles = {1, 2, 3, 5, 9, 15, 25}
local x = 16
for i = 1, #tailles do
gfx.pixel(x, 60, "yellow", tailles[i])
gfx.pixel(x, 60, "red")
gfx.text(x - 2, 90, tostring(tailles[i]), "white")
x = x + 22 + tailles[i]
end
gfx.text(8, 8, "GFX.PIXEL(X, Y, COLOR, SIZE)", "white", 2)
gfx.text(8, 130, "LE POINT ROUGE EST LE POINT (X, Y)", "red")
Les points sont l'outil idéal pour les relevés : une mesure, un point. Ici une journée d'une courbe d'ensoleillement, un point tous les 4 pixels :
local t = gfx.size()
gfx.clear("black")
gfx.line(1, 150, t.w, 150, "gray")
for x = 1, t.w, 4 do
local y = 150 - math.max(0, math.sin((x - 20) / 270 * math.pi)) * 120
gfx.pixel(x, y, "yellow", 3)
end
gfx.text(4, 158, "6:00", "white")
gfx.text(t.w - 22, 158, "18:00", "white")
Voir aussi gfx.rect() gfx.get_pixel()
Une ligne droite de (x1, y1) à (x2, y2), les deux bouts compris.
x1number- colonne du départ
y1number- ligne du départ
x2number- colonne de l'arrivée
y2number- ligne de l'arrivée
colorstring|number- un nom ou un numéro de couleur,
"none"pour effacer thicknessnumber facultatif- épaisseur de 1 à 64 pixels (1 par défaut)
Une ligne fine fait un pixel de large et n'a pas de trous, même quand elle monte en pente raide. Une ligne épaisse tamponne un carré de thickness pixels à chaque point : ses bouts sont carrés, et elle coûte plus cher. Une ligne de 100 pixels épaisse de 8 touche 6 400 pixels.
gfx.clear("black")
for i = 1, 4 do
local e = 2 ^ (i - 1)
local y = 10 + i * 30
gfx.line(16, y + 12, 136, y - 8, "orange", e)
gfx.text(146, y, "EPAISSEUR " .. e, "white")
end
for degres = 0, 330, 30 do
local a = degres * math.pi / 180
gfx.line(254, 86, 254 + math.cos(a) * 44, 86 + math.sin(a) * 44, "yellow")
end
gfx.circle(254, 86, 10, "yellow", true)
Des lignes entre les mesures d'un capteur font un graphique. Chaque segment relie une valeur à la suivante :
local niveaux = {40, 52, 47, 70, 95, 88, 120, 112, 90, 64, 58, 75}
gfx.clear("black")
gfx.rect(10, 10, 286, 150, "gray")
for i = 1, #niveaux - 1 do
local x1 = 12 + (i - 1) * 25
local x2 = 12 + i * 25
gfx.line(x1, 158 - niveaux[i], x2, 158 - niveaux[i + 1], "lime", 2)
end
gfx.text(16, 16, "NIVEAU DU RESERVOIR, 12 DERNIERES HEURES", "white")
Voir aussi gfx.pixel() gfx.triangle()
Formes
Les formes prennent leur taille et leur couleur, puis un dernier argument facultatif qui dit comment les dessiner : sans lui, un contour d'un pixel ; true, pleine ; pour les rectangles et les cercles, un nombre donne l'épaisseur du bord.
Un rectangle de w pixels de large et h pixels de haut, son coin en haut à gauche en (x, y).
xnumber- colonne du coin en haut à gauche
ynumber- ligne du coin en haut à gauche
wnumber- largeur en pixels
hnumber- hauteur en pixels
colorstring|number- un nom ou un numéro de couleur,
"none"pour effacer fillboolean|number facultatiftruepour le remplir, ou l'épaisseur du bord (1 à 64) ; un contour d'un pixel sans lui
Le bord est dessiné à l'intérieur du rectangle : gfx.rect(10, 10, 50, 20, "red", 3) reste dans les mêmes 50 × 20 pixels que le rectangle plein, avec un trou au milieu. Quand le bord fait au moins la moitié du plus petit côté, le rectangle est tout simplement plein. Une largeur ou une hauteur nulle ou négative ne dessine rien.
gfx.clear("black")
gfx.rect(10, 20, 86, 50, "white")
gfx.rect(110, 20, 86, 50, "lime", true)
gfx.rect(210, 20, 86, 50, "orange", 6)
gfx.text(10, 76, "CONTOUR", "white")
gfx.text(110, 76, "TRUE : PLEIN", "white")
gfx.text(210, 76, "6 : BORD", "white")
-- une barre de progression : un cadre, puis la partie remplie
local fait = 0.7
gfx.rect(10, 110, 200, 22, "white")
gfx.rect(12, 112, math.floor(196 * fait), 18, "green", true)
gfx.text(220, 116, math.round(fait * 100) .. "% BROYE", "white")
Les rectangles pleins sont aussi le moyen le plus rapide d'effacer une partie de l'image avant de la redessiner : peignez-la de la couleur du fond, ou avec "none".
Voir aussi gfx.pixel() gfx.clear()
Un cercle autour de (x, y), ou un disque, ou un anneau.
xnumber- colonne du centre
ynumber- ligne du centre
radiusnumber- le rayon en pixels ; 0 dessine un seul pixel
colorstring|number- un nom ou un numéro de couleur,
"none"pour effacer fillboolean|number facultatiftruepour un disque, ou l'épaisseur de l'anneau (1 à 64) ; un contour d'un pixel sans lui
Le cercle fait 2 × radius + 1 pixels de diamètre. L'épaisseur du bord pousse vers l'intérieur, comme le bord d'un rectangle. Un rayon négatif ne dessine rien.
gfx.clear("black")
gfx.circle(40, 50, 28, "white")
gfx.circle(40, 120, 28, "light_blue", true)
gfx.circle(110, 85, 34, "orange", 6)
gfx.text(14, 160, "CONTOUR, DISQUE, ANNEAU", "white")
-- une Roue dentée de Create : d'abord les dents, puis la roue par-dessus
local cx, cy = 226, 85
for i = 0, 11 do
local a = i * math.pi / 6
gfx.pixel(cx + math.cos(a) * 52, cy + math.sin(a) * 52, "brown", 13)
end
gfx.circle(cx, cy, 50, "brown", true)
gfx.circle(cx, cy, 40, "orange", 3)
gfx.circle(cx, cy, 14, "light_gray", true)
gfx.circle(cx, cy, 5, "black", true)
Avec math.cos et math.sin, les cercles deviennent des voyants, des cadrans et des écrans radar. Une couronne de 8 voyants, allumés selon un motif :
local allumes = {true, true, false, true, false, false, true, true}
gfx.clear("black")
for i = 1, 8 do
local a = (i - 1) * math.pi / 4 - math.pi / 2
local x = 153 + math.cos(a) * 60
local y = 86 + math.sin(a) * 60
local couleur = "gray"
if allumes[i] then couleur = "lime" end
gfx.circle(x, y, 12, couleur, true)
gfx.circle(x, y, 12, "white")
gfx.text(x - 1, y - 2, tostring(i), "black")
end
Voir aussi gfx.rect() math.cos()
Un triangle passant par trois coins, donnés dans n'importe quel ordre.
x1number- colonne du premier coin
y1number- ligne du premier coin
x2number- colonne du deuxième coin
y2number- ligne du deuxième coin
x3number- colonne du troisième coin
y3number- ligne du troisième coin
colorstring|number- un nom ou un numéro de couleur,
"none"pour effacer fillboolean facultatiftruepour le remplir ; un contour d'un pixel sinon
Contrairement aux rectangles et aux cercles, le triangle n'a pas d'épaisseur de bord : le dernier argument vaut true pour un triangle plein, et tout le reste dessine le contour. Pour un contour épais, tracez trois lignes avec une épaisseur.
Les triangles font des toits, des montagnes, et surtout des flèches :
gfx.clear("black")
gfx.triangle(20, 80, 60, 16, 100, 80, "green", true)
gfx.triangle(120, 80, 160, 16, 200, 80, "white")
gfx.text(20, 88, "PLEIN", "white")
gfx.text(120, 88, "CONTOUR", "white")
-- des flèches de tendance à côté de deux stocks
gfx.triangle(20, 140, 30, 124, 40, 140, "lime", true)
gfx.text(48, 130, "FER +120/H", "lime")
gfx.triangle(150, 124, 160, 140, 170, 124, "red", true)
gfx.text(178, 130, "CHARBON -45/H", "red")
Voir aussi gfx.line()
Le pot de peinture : repeint toute la zone de la même couleur que le pixel en (x, y).
xnumber- colonne du pixel de départ
ynumber- ligne du pixel de départ
colorstring|number- la nouvelle couleur, ou
"none"pour effacer la zone
La zone s'étend depuis (x, y) aux voisins du dessus, du dessous, de gauche et de droite qui ont exactement la même couleur que le pixel de départ. Elle s'arrête sur toute autre couleur. Les contours tracés par gfx.line, gfx.circle et gfx.triangle sont fermés pour elle, même là où ils passent en diagonale : l'intérieur d'une forme se remplit sans fuir.
Les pixels vides comptent comme une couleur à part : sur une image effacée avec gfx.clear("black"), un trou fait avec "none" est un mur pour un remplissage qui part du noir.
Rien ne se passe quand le pixel a déjà la nouvelle couleur, ou quand (x, y) est hors de l'écran. Le remplissage s'arrête après 1 048 576 pixels, bien plus que le plus grand écran (82 944 pixels sur un Ordinateur moderne) : en pratique, il remplit toujours toute la zone, à une instruction pour 64 pixels.
Une forme fermée se remplit seulement à l'intérieur. Ouvrez une brèche d'un pixel, et la peinture s'échappe :
gfx.clear("black")
gfx.rect(4, 20, 146, 120, "gray")
gfx.rect(156, 20, 146, 120, "gray")
gfx.circle(77, 80, 40, "white")
gfx.circle(229, 80, 40, "white")
gfx.rect(266, 76, 6, 9, "black", true) -- une brèche dans le cercle de droite
gfx.fill(77, 80, "orange")
gfx.fill(229, 80, "orange")
gfx.text(30, 148, "FERME", "white")
gfx.text(170, 148, "UNE BRECHE : CA FUIT", "white")
L'exemple shapes de l'ordinateur s'en sert pour les clics : chaque clic peint la zone sous la souris (voir shapes).
Voir aussi gfx.get_pixel()
Texte et sprites
Écrit du texte avec une minuscule police de 3 × 5 pixels, à n'importe quel pixel et à n'importe quelle taille. Renvoie la largeur occupée.
xnumber- colonne du bord gauche de la première lettre
ynumber- ligne du haut des lettres
textstring- le texte (un nombre est changé en texte)
colorstring|number- un nom ou un numéro de couleur,
"none"pour effacer scalenumber facultatif- de 1 à 64, la taille de chaque point de la police (1 par défaut)
- number
- la largeur du texte dessiné, en pixels
Chaque lettre fait 3 points de large et 5 de haut, suivie d'une colonne vide : 4 pixels par caractère, si bien qu'une ligne de 76 caractères tient sur un Micro-ordinateur à l'échelle 1. À l'échelle 2, chaque point devient un carré de 2 × 2 pixels, et ainsi de suite.
La police n'a que des majuscules (les minuscules sont dessinées en majuscules), des chiffres et ces signes : espace . , : ; ! ? ' " - + = / ( ) < > % * _ # [ ]. Les accents sont retirés (É est dessiné E), et tout autre caractère devient ?.
gfx.clear("black")
gfx.text(4, 4, "ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789", "white")
gfx.text(4, 12, ".,:;!?'\"-+=/()<>%*_#[]", "white")
gfx.text(4, 22, "les minuscules deviennent des capitales", "yellow")
gfx.text(4, 30, "Déjà vu : les accents sont retirés", "yellow")
gfx.text(4, 38, "Sans dessin : @ & $ { } ~", "red")
gfx.text(4, 54, "ECHELLE 2", "lime", 2)
gfx.text(4, 72, "ECHELLE 3", "lime", 3)
gfx.text(4, 98, "ECHELLE 6", "orange", 6)
La largeur renvoyée vaut (4 × caractères - 1) × échelle : la dernière colonne vide n'est pas comptée.
print(gfx.text(1, 1, "SALUT", "white"))
print(gfx.text(1, 10, "SALUT", "white", 2))
print(gfx.text(1, 30, "", "white"))19 38 0
Comme la largeur est connue d'avance, centrer n'est qu'une soustraction. Gardez une petite fonction comme celle-ci en haut de vos programmes :
local t = gfx.size()
local function centrer(y, texte, couleur, echelle)
local largeur = (#texte * 4 - 1) * echelle
gfx.text((t.w - largeur) // 2 + 1, y, texte, couleur, echelle)
end
gfx.clear("blue")
gfx.rect(1, 1, t.w, 30, "black", true)
centrer(9, "GARE DU NORD", "yellow", 3)
centrer(60, "PROCHAIN TRAIN", "white", 2)
centrer(84, "2 MIN", "lime", 5)
centrer(140, "VOIE 3, DIRECTION LA MINE", "white", 1)
La largeur renvoyée sert aussi à placer le morceau de texte suivant, pour une étiquette suivie d'une valeur d'une autre couleur :
local etat = {
{"PRESSION", "OK", "lime"},
{"CHAUDIERE", "CHAUFFE", "yellow"},
{"STRESS", "SURCHARGE", "red"},
}
gfx.clear("black")
for i, ligne in ipairs(etat) do
local y = 12 + (i - 1) * 24
local x = 10 + gfx.text(10, y, ligne[1], "white", 2)
gfx.text(x + 8, y, ligne[2], ligne[3], 2)
end
Le texte de gfx.text fait partie du dessin : il est sous les caractères de print, il s'efface avec "none" et il se déplace avec gfx.scroll. Chaque caractère coûte des instructions (échelle² chacun) : un grand titre dessiné une fois ne coûte presque rien, mais redessiner à chaque tick une page entière de texte à l'échelle 3, si.
Voir aussi gfx.image() term.write()
Dessine un sprite : une petite image écrite comme du texte, un caractère par pixel.
xnumber- colonne du coin en haut à gauche
ynumber- ligne du coin en haut à gauche
rowstable- une liste de chaînes, une par ligne de pixels, un chiffre hexadécimal par pixel
scalenumber facultatif- de 1 à 64, chaque pixel du sprite devient un carré de cette taille (1 par défaut)
flipboolean facultatiftrueretourne le sprite de gauche à droite, comme dans un miroir
Chaque ligne est une chaîne. Chaque caractère est un chiffre hexadécimal, le numéro d'une couleur : 0 blanc, 1 orange... 9 cyan, a violet, b bleu, c marron, d vert, e rouge, f noir (les majuscules marchent aussi). Tout autre caractère, comme . ou une espace, est transparent : le pixel garde ce qu'il y avait avant. Le tableau complet est dans Couleurs.
gfx.clear("light_blue")
gfx.rect(1, 110, 306, 62, "green", true)
local creeper = {
"5d5dd5d5",
"d5ddd5dd",
"dffd5ffd",
"5ffddff5",
"dd5ffd5d",
"d5ffffdd",
"5dffffd5",
"ddfd5fdd",
}
gfx.image(10, 10, creeper)
gfx.image(30, 10, creeper, 4)
gfx.image(76, 10, creeper, 8)
gfx.text(10, 84, "ECHELLES 1, 4 ET 8", "black")
local fleche = {
"....4....",
"....44...",
"44444444.",
"444444444",
"44444444.",
"....44...",
"....4....",
}
gfx.image(170, 20, fleche, 4)
gfx.text(214, 32, "NORMAL", "black")
gfx.image(170, 96, fleche, 4, true) -- en miroir, à moitié sur l'herbe
gfx.text(214, 108, "MIROIR", "black")
scale fait de chaque pixel un carré, et flip retourne le sprite : un personnage qui marche à gauche et à droite n'a besoin que d'une image. Chaque ligne est retournée sur sa propre longueur : donnez la même longueur à toutes les lignes (complétez avec des .), sinon le sprite retourné se disloque.
Une ligne qui n'est pas une chaîne arrête le programme : bad argument #3 to 'image' (row 2 is not a string). Il n'y a pas de chiffre pour « effacer » : pour retirer un sprite, dessinez par-dessus un rectangle de la couleur du fond (ou de "none").
Voir aussi gfx.text() Couleurs
Défilement
Fait glisser le dessin de (dx, dy). Ce qui sort de la zone est perdu, et ce qui entre de l'autre côté est vide.
dxnumber- pixels vers la droite (négatif : vers la gauche)
dynumber- pixels vers le bas (négatif : vers le haut) ; 0 par défaut
xnumber facultatif- colonne du coin en haut à gauche de la zone à déplacer
ynumber facultatif- ligne du coin en haut à gauche de la zone
wnumber facultatif- largeur de la zone
hnumber facultatif- hauteur de la zone
Avec quatre nombres de plus, seul ce rectangle bouge et le reste de l'image ne change pas : une barre d'état peut rester en haut pendant que le paysage défile dessous. Donnez les quatre ou aucun. Le texte de l'écran ne bouge pas (c'est le rôle de term.scroll), et avant tout dessin, l'appel ne fait rien.
Un défilement coûte autant que de remplir sa zone une fois (une instruction pour 64 pixels), quel que soit ce qui y est dessiné : un paysage fait de centaines de formes se déplace pour le prix d'un rectangle, et seule la nouvelle bande au bord reste à dessiner. C'est ainsi que marchent les jeux à défilement horizontal et les graphiques en direct. Ici, les collines arrivent par la droite, 2 pixels par tick, sous une barre de titre qui ne bouge pas :
local t = gfx.size()
local HAUT = 20
gfx.clear("light_blue")
gfx.rect(1, 1, t.w, HAUT - 1, "gray", true)
gfx.text(4, 7, "COLLINES QUI DEFILENT", "white")
local pas = 0
while true do
pas = pas + 1
gfx.scroll(-2, 0, 1, HAUT, t.w, t.h - HAUT + 1) -- tout ce qui est sous la barre, 2 pixels à gauche
local sol = 120 + math.floor(18 * math.sin(pas / 9) + 7 * math.sin(pas / 3.7))
gfx.rect(t.w - 1, HAUT, 2, sol - HAUT, "light_blue", true)
gfx.rect(t.w - 1, sol, 2, 4, "lime", true)
gfx.rect(t.w - 1, sol + 4, 2, t.h - sol - 3, "brown", true)
sleep(0.05)
end
Voir aussi term.scroll()
Recettes
De petits programmes complets qui combinent les fonctions ci-dessus. Ils utilisent des valeurs d'exemple pour tourner partout : dans votre monde, les valeurs viennent de peripheral.wrap(), inventory.count(), kinetic.speed() et des autres appareils.
Un graphique en barres
Le stock d'un entrepôt, une barre par objet, à l'échelle pour que le plus grand remplisse le graphique :
local stock = {
{nom = "FER", nombre = 1450, couleur = "light_gray"},
{nom = "CUIVRE", nombre = 820, couleur = "orange"},
{nom = "ZINC", nombre = 610, couleur = "light_blue"},
{nom = "LAITON", nombre = 1980, couleur = "yellow"},
{nom = "ANDESITE", nombre = 2400, couleur = "gray"},
}
local t = gfx.size()
local BASE = t.h - 20 -- le bas des barres
local HAUTEUR_MAX = 110
local plus_grand = 0
for _, objet in ipairs(stock) do
plus_grand = math.max(plus_grand, objet.nombre)
end
local function centrer(x, l, y, texte, couleur)
gfx.text(x + (l - (#texte * 4 - 1)) // 2, y, texte, couleur)
end
gfx.clear("black")
gfx.text(6, 6, "STOCK DE L'ENTREPOT", "white", 2)
gfx.line(4, BASE + 1, t.w - 4, BASE + 1, "gray")
for i, objet in ipairs(stock) do
local x = 14 + (i - 1) * 58
local h = math.max(1, math.floor(objet.nombre / plus_grand * HAUTEUR_MAX))
gfx.rect(x, BASE - h + 1, 40, h, objet.couleur, true)
centrer(x, 40, BASE - h - 7, tostring(objet.nombre), "white")
centrer(x, 40, BASE + 6, objet.nom, objet.couleur)
end
Un cadran
Un compte-tours pour un arbre de Create : un demi-cercle de points colorés, une graduation tous les 32 tr/min, et une aiguille. L'angle va de π (à gauche, 0 tr/min) à 2π (à droite, 256 tr/min). Le sinus de ces angles est négatif et y augmente vers le bas de l'écran : l'arc monte donc au-dessus de son centre.
local CX, CY, R = 153, 124, 84
local vitesse = 192
gfx.clear("black")
for degres = 0, 180, 3 do
local valeur = degres / 180 * 256
local couleur = "lime"
if valeur > 224 then
couleur = "red"
elseif valeur > 160 then
couleur = "yellow"
end
local a = math.pi + degres * math.pi / 180
gfx.pixel(CX + math.cos(a) * R, CY + math.sin(a) * R, couleur, 5)
end
for v = 0, 256, 32 do
local a = math.pi + v / 256 * math.pi
gfx.line(CX + math.cos(a) * (R - 16), CY + math.sin(a) * (R - 16),
CX + math.cos(a) * (R - 8), CY + math.sin(a) * (R - 8), "white")
end
local a = math.pi + vitesse / 256 * math.pi
gfx.line(CX, CY, CX + math.cos(a) * (R - 22), CY + math.sin(a) * (R - 22), "red", 3)
gfx.circle(CX, CY, 7, "light_gray", true)
local texte = vitesse .. " TR/MIN"
gfx.text(CX - (#texte * 4 - 1), CY + 18, texte, "white", 2)
Quand la vitesse change, inutile de redessiner le cadran : redessinez l'ancienne aiguille en noir, puis la nouvelle.
Animer sans scintillement
L'écran parvient aux joueurs tous les 2 ticks. Si une image demande plus d'un tick d'instructions (un effacement complet suivi d'un redessin y arrive facilement), le programme se met en pause au milieu et les joueurs peuvent voir une image à moitié dessinée : l'écran scintille. Deux habitudes l'évitent :
- N'effacez pas tout l'écran à chaque image. Effacez seulement ce qui bouge (un rectangle de la couleur du fond sur son ancienne place), puis dessinez-le à sa nouvelle place.
- **Faites glisser ce qui est déjà dessiné avec
gfx.scroll**, puis ne dessinez que la nouvelle partie. L'image n'est jamais vide, et chaque étape coûte une fraction d'un redessin.
Un graphique en direct du stress d'une usine fonctionne ainsi : à chaque tick, le graphique glisse de 2 pixels vers la gauche et un seul nouveau segment est tracé au bord droit.
local X, Y, L, H = 10, 30, 286, 120 -- la zone du graphique
gfx.clear("black")
gfx.text(10, 10, "STRESS", "white", 2)
gfx.rect(X - 1, Y - 1, L + 2, H + 2, "gray")
local avant = Y + H // 2
local n = 0
while true do
n = n + 1
-- une mesure d'exemple de 0 à 1 ; dans un monde : stress / capacité d'un appareil cinétique
local charge = 0.5 + 0.35 * math.sin(n / 8) + 0.1 * math.sin(n / 2.3)
local y = Y + H - 1 - math.floor(charge * (H - 1))
gfx.scroll(-2, 0, X, Y, L, H)
local couleur = "lime"
if charge > 0.8 then couleur = "red" end
gfx.line(X + L - 3, avant, X + L - 1, y, couleur)
avant = y
sleep(0.05)
end
Une animation de sprite
Une petite locomotive roule sur ses rails. Elle a deux images dont les roues diffèrent, montrées tour à tour, et elle est retournée quand elle fait demi-tour. Chaque étape efface l'ancienne place avec un rectangle couleur ciel : le reste de l'image n'est jamais redessiné.
local IMAGES = {
{
".........7777...",
".cccc.....77....",
".c33c.....77....",
".cccceeeeeeeeeee",
"4cccceeeeeeeeeee",
"4cccceeeeeeeeeee",
"ffffffffffffffff",
"..f.f..f.f..f.f.",
"...8....8....8..",
"..f.f..f.f..f.f.",
},
{
".........7777...",
".cccc.....77....",
".c33c.....77....",
".cccceeeeeeeeeee",
"4cccceeeeeeeeeee",
"4cccceeeeeeeeeee",
"ffffffffffffffff",
"...f....f....f..",
"..f8f..f8f..f8f.",
"...f....f....f..",
},
}
local ECHELLE = 4
local Y = 80
local t = gfx.size()
gfx.clear("light_blue")
gfx.rect(1, Y + 40, t.w, 4, "gray", true) -- les rails
gfx.rect(1, Y + 44, t.w, t.h - Y - 43, "green", true)
gfx.text(8, 8, "LA NAVETTE DE LA MINE", "white", 2)
local x, dx, image = 10, 4, 1
while true do
gfx.rect(x, Y, 16 * ECHELLE, 10 * ECHELLE, "light_blue", true) -- efface l'ancienne place
x = x + dx
if x + 16 * ECHELLE > t.w or x < 1 then
dx = -dx -- demi-tour
x = x + 2 * dx
end
image = 3 - image -- 1, 2, 1, 2...
gfx.image(x, Y, IMAGES[image], ECHELLE, dx < 0) -- en miroir vers la gauche
sleep(0.1)
end
Des boutons en pixels
Des boutons de n'importe quelle taille, n'importe où, avec une étiquette dans la police en pixels. Un évènement de clic porte e.px et e.py, le pixel sous la souris (ou sous le doigt, sur un moniteur) : le bouton touché est celui dont le rectangle contient ce point. Voir Écrans et moniteurs pour les clics sur les moniteurs.
local boutons = {
{x = 12, y = 44, l = 88, h = 64, texte = "PRESSE", couleur = "orange", face = "left", actif = true},
{x = 109, y = 44, l = 88, h = 64, texte = "MIXEUR", couleur = "light_blue", face = "right", actif = false},
{x = 206, y = 44, l = 88, h = 64, texte = "VENTILO", couleur = "red", face = "top", actif = false},
}
local function dessiner(b)
local fond, encre = "gray", "white"
if b.actif then
fond, encre = b.couleur, "black"
end
gfx.rect(b.x, b.y, b.l, b.h, fond, true)
gfx.rect(b.x, b.y, b.l, b.h, "white", 2)
local largeur = (#b.texte * 4 - 1) * 2
gfx.text(b.x + (b.l - largeur) // 2, b.y + b.h // 2 - 4, b.texte, encre, 2)
end
local function dedans(b, px, py)
return px >= b.x and px < b.x + b.l and py >= b.y and py < b.y + b.h
end
gfx.clear("black")
gfx.text(12, 12, "COMMANDES LIGNE 2", "white", 2)
gfx.text(12, 130, "CLIQUEZ UN BOUTON POUR SA MACHINE", "light_gray")
for _, b in ipairs(boutons) do
dessiner(b)
end
while true do
local e = os.pull_event("click")
for _, b in ipairs(boutons) do
if dedans(b, e.px, e.py) then
b.actif = not b.actif
rs.set(b.face, b.actif)
dessiner(b) -- ne redessine que le bouton qui a changé
end
end
end
Le test lui-même n'est que de l'arithmétique : vous pouvez le vérifier sans cliquer.
local presse = {x = 12, y = 40, l = 88, h = 40}
local function dedans(b, px, py)
return px >= b.x and px < b.x + b.l and py >= b.y and py < b.y + b.h
end
print(dedans(presse, 50, 60))
print(dedans(presse, 100, 60))true false
px < b.x + b.l, avec un < strict, parce qu'un bouton de 88 pixels de large qui commence en 12 finit au pixel 99.