535 lines
22 KiB
Text
535 lines
22 KiB
Text
[#_lesson_19_text_that_looks_good]
|
|
== Lektion 19: Text, der gut aussieht
|
|
|
|
image::learn/19-text.png[Die fertige Lektion, 480]
|
|
|
|
Jedes Wort, das du seit Lektion eins auf den Bildschirm gebracht hast, kam aus
|
|
`overlayPrint`, und `overlayPrint` hat genau eine Schriftart: einen Pixelblock fester
|
|
Breite, der in die Engine eingebaut ist, für immer gleich groß, in welcher Farbe er gerade
|
|
eben ist. Für einen Frame-Zähler ist sie perfekt. Für einen Titel, eine Punktzahl, ein Menü
|
|
oder alles, was ein Spieler ansehen und nicht einfach überlesen soll, ist sie falsch.
|
|
|
|
Diese Lektion lädt eine echte Schriftart von der Festplatte, gibt Text mit ihr aus, färbt
|
|
ihn ein, misst ihn aus, damit du Dinge zentrieren kannst, und zeigt dir, was das Ausgeben
|
|
von Text wirklich kostet. Am Ende hast du einen Titelbildschirm, der gewollt aussieht statt
|
|
zufällig.
|
|
|
|
[#_set_the_overlay_to_a_size_you_can_think_in]
|
|
=== Gib dem Overlay eine Größe, in der du denken kannst
|
|
|
|
Fang eine neue Datei namens `text.singe` mit zwei Zeilen an:
|
|
|
|
[source,lua]
|
|
----
|
|
dofile("Singe/Framework.singe")
|
|
|
|
overlaySetResolution(discGetWidth(), discGetHeight())
|
|
----
|
|
|
|
Die erste Zeile hast du schon oft geschrieben; sie lädt das Framework, das `DIR`, die
|
|
Namen `SWITCH_*` und die Namen `FONT_QUALITY_*` definiert, die du gleich benutzt.
|
|
|
|
Die zweite ist wichtiger, als sie aussieht. Das Overlay -- die Fläche, auf der alles
|
|
landet, was du zeichnest -- ist am Anfang *halb* so groß wie die Welt. Ein Spiel ohne Video
|
|
hat eine Zeichenfläche von 720 mal 480, also ist das Overlay am Anfang 360 mal 240 groß,
|
|
und eine Schrift in 36 Punkt ist auf einem Overlay, das 360 Pixel breit ist, riesig.
|
|
`overlaySetResolution` ersetzt das Overlay durch eines in der Größe, um die du bittest, und
|
|
von da an liegt jede Koordinate, die du angibst, und alles, was `overlayGetWidth` und
|
|
`overlayGetHeight` dir sagen, in diesem Raum.
|
|
|
|
`discGetWidth()` und `discGetHeight()` antworten mit der Größe des Videos, wenn es eines
|
|
gibt, und mit der Größe der Zeichenfläche, wenn nicht, also bekommst du mit dieser einen
|
|
Zeile ein Overlay, das zur Welt passt, egal welche Art von Spiel das hier ist. Ruf es
|
|
einmal auf, am Anfang, bevor du irgendetwas zeichnest.
|
|
|
|
[#_load_a_font]
|
|
=== Lade eine Schriftart
|
|
|
|
Schreib darunter:
|
|
|
|
[source,lua]
|
|
----
|
|
fontQuality(FONT_QUALITY_BLENDED)
|
|
|
|
local titleFont = fontLoad("Singe/FreeSansBold.ttf", 48)
|
|
----
|
|
|
|
`fontLoad` öffnet eine TrueType-Schriftdatei und gibt dir eine Zahl zurück -- ein *Handle*
|
|
-- und die benutzt du immer, wenn du diese Schriftart willst. Das zweite Argument ist die
|
|
Punktgröße, und die steht fest, solange es das Handle gibt. Es gibt keine Möglichkeit,
|
|
die Größe einer geladenen Schriftart nachträglich zu ändern. Wenn du dieselbe Schrift in
|
|
drei Größen willst, rufst du `fontLoad` dreimal auf und behältst drei Handles.
|
|
|
|
`Singe/FreeSansBold.ttf` ist die Schriftart, die die Engine in den Ordner `Singe` in
|
|
deinem Arbeitsordner entpackt hat, als du zum ersten Mal irgendetwas gestartet hast. Jede
|
|
Kopie von Singe hat sie, also kannst du dich auf sie verlassen. Jede andere `.ttf`-Datei
|
|
funktioniert genauso; leg sie neben dein Skript und lade sie als `DIR .. "myfont.ttf"`.
|
|
|
|
Gib jetzt Text mit ihr aus:
|
|
|
|
[source,lua]
|
|
----
|
|
function onOverlayUpdate()
|
|
overlayClear()
|
|
fontPrint(24, 60, "ASTEROID PATROL")
|
|
return OVERLAY_UPDATED
|
|
end
|
|
----
|
|
|
|
Starte es. Große weiße Buchstaben oben links.
|
|
|
|
Achte darauf, was du *nicht* getan hast: Du hast `fontPrint` nie gesagt, welche
|
|
Schriftart es benutzen soll. `fontLoad` macht die Schriftart, die es gerade geladen hat,
|
|
zur ausgewählten, und `fontPrint` zeichnet immer mit der, die gerade ausgewählt ist. Wenn
|
|
mehr als eine Schriftart geladen ist, wählst du mit `fontSelect` zwischen ihnen, und das
|
|
machst du gleich.
|
|
|
|
Achte auch darauf, dass `24` und `60` Pixel sind, keine Zeichenzellen. Das ist der
|
|
Unterschied zwischen `fontPrint` und `overlayPrint`, über den die Leute stolpern:
|
|
`overlayPrint(2, 2, ...)` heißt zwei Buchstaben nach rechts und zwei Zeilen nach unten,
|
|
`fontPrint(2, 2, ...)` dagegen zwei Pixel nach rechts und zwei Pixel nach unten, also
|
|
eingequetscht in die Ecke. Das `y`, das du angibst, ist die *Oberkante* des Texts, nicht
|
|
die Grundlinie, auf der er steht.
|
|
|
|
[#_color]
|
|
=== Farbe
|
|
|
|
`fontPrint` nimmt drei Argumente, und keines davon ist eine Farbe. Die Farbe kommt von
|
|
woanders:
|
|
|
|
[source,lua]
|
|
----
|
|
colorForeground(255, 211, 90)
|
|
fontPrint(24, 60, "ASTEROID PATROL")
|
|
----
|
|
|
|
`colorForeground` setzt Rot, Grün und Blau von `0` bis `255` und, wenn du willst, eine
|
|
vierte Zahl für die Deckkraft. Es ist kein Argument für irgendetwas; es ist eine
|
|
Einstellung, die sich die Engine merkt. Alles, was danach gezeichnet wird, kommt in dieser
|
|
Farbe heraus, bis du sie wieder änderst, genau so, wie `overlayBox` und `overlayLine`
|
|
schon funktionieren. Das Muster ist also immer dasselbe: Setz die Farbe, dann zeichne.
|
|
|
|
Das ist das Erste, was die Leute bei Schriftarten falsch machen, und das Zweite kommt in
|
|
ein paar Seiten.
|
|
|
|
[#_quality]
|
|
=== Qualität
|
|
|
|
`fontQuality` entscheidet, wie die Buchstaben gerastert werden, also wie die Kurven eines
|
|
Buchstabens zu Pixeln werden. Es gibt drei Möglichkeiten. Probier jede aus, indem
|
|
du sie über die Zeile mit `fontLoad` setzt und dir das Ergebnis genau ansiehst.
|
|
|
|
`FONT_QUALITY_SOLID` ist die schnellste. Jedes Pixel gehört entweder zum Buchstaben oder
|
|
nicht, also kommen die Diagonalen und Kurven mit sichtbaren Treppenstufen heraus. Bei 48
|
|
Punkt sieht das billig aus. Bei 12 Punkt auf einem kleinen Overlay kann es *schärfer*
|
|
aussehen als die anderen, und manchmal willst du genau das.
|
|
|
|
`FONT_QUALITY_SHADED` glättet die Kanten, aber dazu mischt es sie mit der aktuellen
|
|
Hintergrundfarbe, und diesen Hintergrund zeichnet es als volles Rechteck hinter den ganzen
|
|
String. Du bekommst ein Etikett mit einem Kasten drumherum, ob du einen wolltest oder
|
|
nicht. Für ein Debug-Anzeigefeld über einem unruhigen Bild ist das die richtige Antwort und
|
|
für fast alles andere die falsche.
|
|
|
|
`FONT_QUALITY_BLENDED` glättet die Kanten und lässt sie durchsichtig, also sitzen die
|
|
Buchstaben auf dem, was gerade hinter ihnen ist. Sie kostet am meisten, und sie ist das,
|
|
was du fast immer willst. Nimm sie, außer du hast einen Grund dagegen.
|
|
|
|
Die Einstellung ist global, nicht pro Schriftart, und sie gilt ab dem Moment, in dem du
|
|
sie aufrufst. Sprites, die du schon gemacht hast, behalten die Qualität, mit der sie
|
|
gemacht wurden. Der Eintrag zu `fontQuality` in der Referenz nennt die drei Namen und ihre
|
|
Werte.
|
|
|
|
[#_measuring_so_you_can_center_things]
|
|
=== Ausmessen, damit du Dinge zentrieren kannst
|
|
|
|
Früher oder später -- meistens nach etwa zehn Minuten -- willst du den Titel in der Mitte
|
|
des Bildschirms haben. Du weißt, dass der Bildschirm `overlayGetWidth()` breit ist. Du
|
|
weißt nicht, wie breit der Text ist, und es gibt keinen Aufruf `fontMeasure`, den du fragen
|
|
könntest.
|
|
|
|
Was es stattdessen gibt, ist `fontToSprite`. Es rendert einen String mit der ausgewählten
|
|
Schriftart, der aktuellen Qualität und der aktuellen Farbe, und statt ihn auf den
|
|
Bildschirm zu bringen, gibt es dir ein *Sprite* zurück: dieselbe Art von Handle, die dir
|
|
`spriteLoad` gibt, und das heißt, jeder `sprite*`-Aufruf, den du in Lektion neun gelernt
|
|
hast, funktioniert damit. Auch die beiden, die es ausmessen.
|
|
|
|
[source,lua]
|
|
----
|
|
fontSelect(titleFont)
|
|
colorForeground(255, 211, 90)
|
|
local titleImage = fontToSprite("ASTEROID PATROL")
|
|
----
|
|
|
|
Und dann, in `onOverlayUpdate`:
|
|
|
|
[source,lua]
|
|
----
|
|
local x = (overlayGetWidth() - spriteGetWidth(titleImage)) / 2
|
|
spriteDraw(titleImage, x, 60)
|
|
----
|
|
|
|
Das ist Zentrieren, und das ist schon der ganze Trick. Nimm die Breite des Platzes, zieh
|
|
die Breite des Dings ab, und die Hälfte von dem, was übrig bleibt, ist der Abstand auf
|
|
jeder Seite.
|
|
|
|
Rechtsbündig ist dieselbe Idee mit weniger Rechnerei. So setzt du Text an den rechten
|
|
Rand:
|
|
|
|
[source,lua]
|
|
----
|
|
spriteDraw(scoreImage, overlayGetWidth() - MARGIN - spriteGetWidth(scoreImage), y)
|
|
----
|
|
|
|
`spriteGetHeight` misst die andere Richtung, um Zeilen zu stapeln oder senkrecht zu
|
|
zentrieren.
|
|
|
|
In diesem ersten Codeblock steckt ein Haken, und das ist das Zweite, was alle falsch
|
|
machen. *Die Farbe wird eingebacken, wenn das Sprite gemacht wird, nicht wenn es
|
|
gezeichnet wird.* `colorForeground` über `fontToSprite` ändert den Text. Setzt du es
|
|
stattdessen über `spriteDraw`, tut es überhaupt nichts, denn `colorForeground` ist eine
|
|
Einstellung für das Zeichnen von Text und Linien, und ein Sprite wird mit seinen eigenen
|
|
Pixeln gezeichnet. Wenn dein sorgfältig eingefärbter Titel weiß herauskommt, liegt es
|
|
daran.
|
|
|
|
Ein Sprite lässt sich umfärben, nachdem es gemacht wurde, aber mit einem Sprite-Aufruf,
|
|
nicht mit `colorForeground`. `spriteSetTint(image, red, green, blue)` multipliziert jedes
|
|
Pixel des Sprites bei jedem Zeichnen mit dieser Farbe, also kann Text, der in Weiß gerendert
|
|
wurde, heute gelb und morgen rot gezeichnet werden, ohne neu gemacht zu werden. Weiß ist
|
|
die Farbe, in der du renderst, wenn du einfärben willst: Alles, was mit Weiß multipliziert
|
|
wird, bleibt, was es war, also ist die Tönung genau die Farbe, um die du bittest.
|
|
|
|
[#_what_printing_costs]
|
|
=== Was Ausgeben kostet
|
|
|
|
Wenn `fontPrint` einen String zum ersten Mal sieht, leistet es echte Arbeit: Es rastert
|
|
jeden Buchstaben aus den Umrissen der Schriftart und baut ein Bild des ganzen Strings.
|
|
Dann behält es dieses Bild. Wenn du denselben String das nächste Mal ausgibst, mit
|
|
derselben Schriftart, Qualität und denselben Farben, überspringt es all das und kopiert
|
|
das Bild, das es schon hat, und das kostet ungefähr so viel wie das Zeichnen eines
|
|
Sprites. Ein String, den du nicht mehr ausgibst, wird eine Sekunde später losgelassen,
|
|
also häuft sich nichts an.
|
|
|
|
Das heißt, die naheliegende Art, einen Titelbildschirm zu schreiben, ist auch die
|
|
schnelle. `"ASTEROID PATROL"` sechzigmal pro Sekunde auszugeben rendert ihn einmal. Eine
|
|
Punktzahl, die sich zweimal pro Sekunde ändert, wird zweimal pro Sekunde gerendert, von
|
|
selbst, ohne dass du Buch führen musst.
|
|
|
|
Der eine Fall, der etwas kostet, ist Text, der in jedem Frame *anders* ist (ein Timer in
|
|
Millisekunden, ein Frame-Zähler), denn jeder Frame ist ein String, den `fontPrint` noch
|
|
nie gesehen hat. Auf einem Desktop-Rechner spielt das keine Rolle. Auf einem Raspberry Pi
|
|
oder einem Handheld spielt ein Bildschirm voll davon sehr wohl eine. Die Abhilfe ist, den
|
|
Text seltener zu ändern: Ein Timer in ganzen Sekunden ist einmal pro Sekunde ein neuer
|
|
String, nicht sechzigmal.
|
|
|
|
Warum hat diese Lektion dann überhaupt `fontToSprite` benutzt? Weil ein Sprite Dinge kann,
|
|
die ausgegebener Text nicht kann:
|
|
|
|
* *Es kann ausgemessen werden*, und das ist die einzige Möglichkeit, Text zu zentrieren
|
|
oder an der rechten Seite auszurichten.
|
|
* *Es kann skaliert und gedreht werden*, mit den `sprite*`-Aufrufen aus Lektion neun.
|
|
* *Es kann seine Schriftart überleben*, und das ist gleich wichtig.
|
|
|
|
Nimm `fontPrint` für Text und `fontToSprite`, wenn du eines davon brauchst.
|
|
|
|
Die Punktzahl in dieser Lektion braucht eines davon: Sie sitzt am rechten Rand, also muss
|
|
sie ausgemessen werden, also muss sie ein Sprite sein. Und ein Sprite einer Punktzahl
|
|
stimmt nur, bis sich die Punktzahl ändert. Wenn sie das tut, render ein neues Sprite und
|
|
entlade das alte -- vergisst du das, bleibt bei jeder Änderung ein Sprite als Leck zurück:
|
|
|
|
[source,lua]
|
|
----
|
|
local function renderScore()
|
|
if scoreImage ~= nil then
|
|
spriteUnload(scoreImage)
|
|
end
|
|
fontSelect(scoreFont)
|
|
colorForeground(255, 255, 255)
|
|
scoreImage = fontToSprite(string.format("%06d", score))
|
|
scoreShown = score
|
|
end
|
|
----
|
|
|
|
`scoreShown` merkt sich, welche Punktzahl dieses Sprite zeigt, damit der Zeichencode
|
|
erkennen kann, ob es noch stimmt:
|
|
|
|
[source,lua]
|
|
----
|
|
if score ~= scoreShown then
|
|
renderScore()
|
|
end
|
|
----
|
|
|
|
`string.format("%06d", score)` ist reines Lua, nicht Singe. Es macht aus einer Zahl einen
|
|
String, und `%06d` heißt „als ganze Zahl, mindestens sechs Stellen, mit Nullen
|
|
aufgefüllt“, und darum haben Arcade-Punktzahlen führende Nullen. Ändere es in `%d`, und
|
|
die Nullen verschwinden.
|
|
|
|
[#_putting_fonts_away]
|
|
=== Schriftarten wegräumen
|
|
|
|
`fontUnload` schließt eine Schriftart und macht ihr Handle ungültig. Sprites, die du mit
|
|
`fontToSprite` gemacht hast, sind davon nicht betroffen (ist ein Sprite einmal gerendert,
|
|
hat es mit der Schriftart nichts mehr zu tun), also kannst du eine Schriftart entladen
|
|
und weiter Text zeichnen, der aus ihr gemacht wurde.
|
|
|
|
Eine scharfe Kante: Wenn du die ausgewählte Schriftart entlädst, ist danach *keine*
|
|
Schriftart ausgewählt, und das nächste `fontPrint` beendet dein Spiel mit einem Fehler.
|
|
Wähl zuerst eine andere aus, wenn du weiter Text ausgeben willst.
|
|
|
|
Der ordentliche Platz für all das ist `onShutdown`, ein Callback, den die Engine einmal
|
|
aufruft, wenn das Spiel beendet wird:
|
|
|
|
[source,lua]
|
|
----
|
|
function onShutdown()
|
|
spriteUnload(titleImage)
|
|
fontUnload(titleFont)
|
|
end
|
|
----
|
|
|
|
Singe räumt hinter dir auf, wenn das Spiel endet, also fängt nichts Feuer, wenn du das
|
|
weglässt. Schreib es trotzdem. Ein Spiel, das freigibt, was es geladen hat, ist ein Spiel,
|
|
das du zweimal laden kannst.
|
|
|
|
[#_the_whole_script]
|
|
=== Das ganze Skript
|
|
|
|
[source,lua]
|
|
----
|
|
dofile("Singe/Framework.singe")
|
|
|
|
overlaySetResolution(discGetWidth(), discGetHeight())
|
|
|
|
local MARGIN = 24
|
|
local TITLE_Y = 60
|
|
local SCORE_Y = 170
|
|
local LABEL_Y = 186
|
|
local HINT_Y = 300
|
|
local CLOCK_Y = 430
|
|
|
|
local score = 0
|
|
local titleFont = nil
|
|
local scoreFont = nil
|
|
local smallFont = nil
|
|
local titleImage = nil
|
|
local hintImage = nil
|
|
local scoreImage = nil
|
|
local scoreShown = nil
|
|
|
|
|
|
local function centered(image)
|
|
return (overlayGetWidth() - spriteGetWidth(image)) / 2
|
|
end
|
|
|
|
|
|
local function renderScore()
|
|
if scoreImage ~= nil then
|
|
spriteUnload(scoreImage)
|
|
end
|
|
fontSelect(scoreFont)
|
|
colorForeground(255, 255, 255)
|
|
scoreImage = fontToSprite(string.format("%06d", score))
|
|
scoreShown = score
|
|
end
|
|
|
|
|
|
fontQuality(FONT_QUALITY_BLENDED)
|
|
|
|
titleFont = fontLoad("Singe/FreeSansBold.ttf", 48)
|
|
scoreFont = fontLoad("Singe/FreeSansBold.ttf", 36)
|
|
smallFont = fontLoad("Singe/FreeSansBold.ttf", 18)
|
|
|
|
fontSelect(titleFont)
|
|
colorForeground(255, 211, 90)
|
|
titleImage = fontToSprite("ASTEROID PATROL")
|
|
|
|
fontSelect(smallFont)
|
|
colorForeground(140, 200, 255)
|
|
hintImage = fontToSprite("Press the fire button to score")
|
|
|
|
renderScore()
|
|
|
|
|
|
function onInputPressed(what)
|
|
if what == SWITCH_BUTTON1 then
|
|
score = score + 125
|
|
end
|
|
end
|
|
|
|
|
|
function onOverlayUpdate()
|
|
overlayClear()
|
|
|
|
spriteDraw(titleImage, centered(titleImage), TITLE_Y)
|
|
|
|
if score ~= scoreShown then
|
|
renderScore()
|
|
end
|
|
spriteDraw(scoreImage, overlayGetWidth() - MARGIN - spriteGetWidth(scoreImage), SCORE_Y)
|
|
|
|
fontSelect(smallFont)
|
|
colorForeground(160, 160, 160)
|
|
fontPrint(MARGIN, LABEL_Y, "SCORE")
|
|
fontPrint(MARGIN, CLOCK_Y, "Running for " .. (singeGetTicks() // 1000) .. " seconds")
|
|
|
|
if (singeGetTicks() // 500) % 2 == 0 then
|
|
spriteDraw(hintImage, centered(hintImage), HINT_Y)
|
|
end
|
|
|
|
return OVERLAY_UPDATED
|
|
end
|
|
|
|
|
|
function onShutdown()
|
|
spriteUnload(titleImage)
|
|
spriteUnload(hintImage)
|
|
if scoreImage ~= nil then
|
|
spriteUnload(scoreImage)
|
|
end
|
|
fontUnload(titleFont)
|
|
fontUnload(scoreFont)
|
|
fontUnload(smallFont)
|
|
end
|
|
----
|
|
|
|
Starte es und halt den Feuerknopf gedrückt. Der Titel und der Hinweis sitzen zentriert,
|
|
die Punktzahl klettert am rechten Rand hoch und wird nur neu gerendert, wenn sie sich
|
|
ändert, der Hinweis blinkt, und das Etikett und die Uhr werden in jedem Frame mit
|
|
`fontPrint` ausgegeben -- die Uhr in ganzen Sekunden, also ist sie nur einmal pro Sekunde
|
|
ein neuer String.
|
|
|
|
[#_what_just_happened_15]
|
|
=== Was gerade passiert ist
|
|
|
|
[source,lua]
|
|
----
|
|
titleFont = fontLoad("Singe/FreeSansBold.ttf", 48)
|
|
scoreFont = fontLoad("Singe/FreeSansBold.ttf", 36)
|
|
smallFont = fontLoad("Singe/FreeSansBold.ttf", 18)
|
|
----
|
|
|
|
Eine Datei, drei Größen, drei Handles. Jedes kostet Speicher für seinen eigenen Satz
|
|
gerenderter Glyphen, also lade die Größen, die du benutzt, und keine weiteren. Drei sind
|
|
normal. Ein Dutzend riecht verdächtig.
|
|
|
|
Nach diesen drei Zeilen ist `smallFont` die ausgewählte Schriftart, denn Laden wählt aus.
|
|
Darum ruft der Code `fontSelect(titleFont)` auf, bevor er den Titel rendert: Das zuletzt
|
|
Geladene ist nicht das, was du zuerst willst.
|
|
|
|
[source,lua]
|
|
----
|
|
local function centered(image)
|
|
return (overlayGetWidth() - spriteGetWidth(image)) / 2
|
|
end
|
|
----
|
|
|
|
Einmal als Funktion geschrieben, weil es zweimal benutzt wird und weil du es beim
|
|
nächsten Mal, wenn du etwas zentriert haben willst, wieder brauchst. Gib der Rechnung
|
|
einen Namen, und du musst sie nie wieder lesen.
|
|
|
|
[source,lua]
|
|
----
|
|
if (singeGetTicks() // 500) % 2 == 0 then
|
|
spriteDraw(hintImage, centered(hintImage), HINT_Y)
|
|
end
|
|
----
|
|
|
|
`singeGetTicks()` ist die Zahl der Millisekunden seit dem Start der Engine. `//` ist eine
|
|
Division, die den Rest wegwirft, also zählt `singeGetTicks() // 500` halbe Sekunden, und
|
|
`% 2` ist der Rest nach dem Teilen durch zwei, also `0`, `1`, `0`, `1` ... Der Hinweis
|
|
wird also eine halbe Sekunde lang gezeichnet und eine halbe Sekunde lang ausgelassen.
|
|
Nichts zu zeichnen ist die Art, wie man etwas blinken lässt.
|
|
|
|
[source,lua]
|
|
----
|
|
fontSelect(smallFont)
|
|
colorForeground(160, 160, 160)
|
|
fontPrint(MARGIN, LABEL_Y, "SCORE")
|
|
----
|
|
|
|
Hier werden beide Einstellungen gebraucht, und es lohnt sich, klar zu sehen, warum. Ein
|
|
Sprite zu zeichnen ändert weder die ausgewählte Schriftart noch die Farbe, also lassen die
|
|
beiden `spriteDraw`-Aufrufe darüber beides genau so, wie sie es vorgefunden haben. Aber der
|
|
Code am Anfang hat nacheinander drei verschiedene Schriftarten ausgewählt, und
|
|
`renderScore` wählt jedes Mal eine weitere aus, wenn sich die Punktzahl bewegt, also ist
|
|
die Auswahl, wenn diese Zeile läuft, die, die zufällig zuletzt gewählt wurde. Versuch
|
|
nicht, darüber Buch zu führen. Wähl die Schriftart aus und setz die Farbe unmittelbar
|
|
bevor du etwas ausgibst, jedes Mal. Das sind zwei Zeilen, und sie ersparen dir eine ganze
|
|
Kategorie ratloser Abende.
|
|
|
|
[#_try_it_19]
|
|
=== Probier es aus
|
|
|
|
. *Ändere die Qualität.* Schreib `FONT_QUALITY_SOLID` in die Zeile mit `fontQuality` und
|
|
sieh dir die Kurve des `S` in `ASTEROID` an. Dann probier `FONT_QUALITY_SHADED` und
|
|
achte darauf, was hinter jedem String passiert.
|
|
. *Mach den Hinweis gelb.* Verschieb die Zeile `colorForeground(140, 200, 255)` von über
|
|
`fontToSprite` direkt über das `spriteDraw`, das `hintImage` zeichnet, und ändere die
|
|
Zahlen. Finde heraus, warum nichts passiert. Dann setz sie zurück, mach den Hinweis
|
|
stattdessen weiß und färb ihn mit `spriteSetTint` gelb, direkt bevor er gezeichnet wird.
|
|
. *Richte die Uhr rechtsbündig aus.* Die Uhr wird mit `fontPrint` gezeichnet, und das
|
|
lässt sich nicht ausmessen. Bring sie trotzdem an den rechten Rand. Es gibt nur einen
|
|
Weg, und ihn zu finden ist der Sinn der Sache.
|
|
. *Nimm die Prüfung von `scoreShown` heraus*, sodass `renderScore()` stattdessen in jedem
|
|
Frame läuft. Es sieht genauso aus, aber jetzt wird die Punktzahl sechzigmal pro Sekunde
|
|
gerendert statt dann, wenn sie sich ändert -- genau die Arbeit, die `fontPrint` dir
|
|
erspart, wenn du es lässt. Dann nimm auch das `spriteUnload` in `renderScore` heraus,
|
|
lass es eine Minute laufen und beobachte, wie viel Speicher dein Spiel benutzt.
|
|
. *Gib eine Uhr in Millisekunden aus.* Ändere die Zeile der Uhr so, dass sie
|
|
`singeGetTicks()` ohne das `// 1000` ausgibt. Es funktioniert immer noch; es ist jetzt
|
|
in jedem Frame ein String, den `fontPrint` noch nie gesehen hat. Auf einem langsamen
|
|
Rechner ist das die Art von Text, die etwas kostet.
|
|
. *Nimm deine eigene Schriftart.* Such dir eine `.ttf` auf deinem Rechner, kopier sie neben
|
|
dein Skript und lade sie mit `DIR .. "thatfont.ttf"`. Manche Schriftarten sehen bei 18
|
|
Punkt furchtbar aus und bei 48 gut. Das ist die Schuld der Schriftart, nicht deine.
|
|
|
|
[#_break_it_on_purpose_18]
|
|
=== Mach es absichtlich kaputt
|
|
|
|
Füg eine Zeile `fontPrint(24, 60, "SCORE")` direkt unter
|
|
`overlaySetResolution(discGetWidth(), discGetHeight())` ein, sodass sie läuft, bevor
|
|
irgendein `fontLoad` gelaufen ist. Singe startet und stirbt sofort:
|
|
|
|
----
|
|
4:fontPrint: No font selected.
|
|
----
|
|
|
|
Lies das wie jeden anderen Fehler. `4` ist die Zeile. `fontPrint` ist der Engine-Aufruf,
|
|
der sich beschwert hat -- Meldungen in dieser Form kommen aus dem Inneren der Engine und
|
|
nicht von Lua, darum nennen sie die Funktion statt der Datei. Der Rest ist die Beschwerde,
|
|
und hier ist sie genau: Du hast verlangt, etwas auszugeben, und es gab keine Schriftart,
|
|
mit der das ging.
|
|
|
|
Diesem Fehler wirst du in echt auf eine weniger offensichtliche Art begegnen. Entlade die
|
|
ausgewählte Schriftart, während das Spiel läuft, und das nächste `fontPrint` sagt genau
|
|
dasselbe, mehrere hundert Zeilen entfernt von dem `fontUnload`, das es verursacht hat.
|
|
|
|
[#_what_you_learned_19]
|
|
=== Was du gelernt hast
|
|
|
|
* `fontLoad` öffnet eine TrueType-Datei in einer festen Punktgröße und gibt ein Handle
|
|
zurück. Eine Größe, ein Handle.
|
|
* Eine Schriftart zu laden wählt sie aus; `fontSelect` wählt zwischen geladenen
|
|
Schriftarten.
|
|
* `fontPrint` zählt in Pixeln und `overlayPrint` in Zeichenzellen.
|
|
* Farbe ist kein Argument. `colorForeground` ist eine Einstellung; setz sie, dann zeichne.
|
|
* `fontQuality` bestimmt, wie Buchstaben gerastert werden, und `FONT_QUALITY_BLENDED` ist
|
|
die übliche Antwort.
|
|
* `fontPrint` behält, was es rendert, also ist es billig, in jedem Frame denselben Text
|
|
auszugeben; Text, der in jedem Frame anders ist, ist das, was kostet.
|
|
* `fontToSprite` rendert in ein Sprite, das `spriteGetWidth` ausmisst und die
|
|
`sprite*`-Aufrufe skalieren und drehen können. Nimm es dafür, nicht für Geschwindigkeit.
|
|
* Ausmessen ist die Art, wie du Text zentrierst oder rechtsbündig ausrichtest, denn sonst
|
|
kann dir nichts sagen, wie breit ein String wird.
|
|
* `colorForeground` erreicht Text, wenn er gemacht wird, nie ein Sprite, wenn es
|
|
gezeichnet wird; mit `spriteSetTint` färbst du ein Sprite nachträglich um.
|
|
* `fontUnload` gibt eine Schriftart frei; die Sprites, die sie gemacht hat, überleben sie.
|
|
|
|
[#_next_time_19]
|
|
=== Beim nächsten Mal
|
|
|
|
Text, der gut aussieht, ist noch weit entfernt von einem Menü, das funktioniert. Eine
|
|
Seite mit einem Titel, einer Liste, durch die du einen Cursor bewegst, einem Schieberegler
|
|
und einem Knopf sind hundert Zeilen Zeichnen und Cursor-Rechnerei, wenn du sie aus den
|
|
Aufrufen dieser Lektion baust. Lektion zwanzig baut sie überhaupt nicht aus diesen
|
|
Aufrufen.
|