471 lines
20 KiB
Text
471 lines
20 KiB
Text
[#_lesson_14_playing_video]
|
|
== Lektion 14: Video abspielen
|
|
|
|
image::learn/14-video.png[Die fertige Lektion, 480]
|
|
|
|
Singe kann einen Film abspielen und dein Spiel darin laufen lassen. Das Bild wird zur Welt, und das
|
|
Overlay, auf dem du seit Lektion eins zeichnest, liegt obendrauf. In dieser Lektion startest du ein
|
|
Video, findest heraus, welcher Frame gerade zu sehen ist, hältst es an und springst zu jedem
|
|
beliebigen Moment.
|
|
|
|
Vor alldem muss eine Sache klar gesagt werden, denn sie entscheidet, ob du diesen Teil des Buchs
|
|
überhaupt lesen solltest.
|
|
|
|
[#_a_game_plays_video_only_if_it_asks_to]
|
|
=== Ein Spiel spielt nur dann Video ab, wenn es darum bittet
|
|
|
|
Singe ist eine Spiel-Engine. Alles, was du in Teil eins und zwei gebaut hast -- die Sprites, der
|
|
Sound, die Kollisionen, die Punkte, der Spielordner -- ist das ganze Singe, und nichts davon hat
|
|
mit Video zu tun. Was Teil drei hinzufügt, ist eine Zugabe: Ein Spiel kann darum bitten, dass
|
|
hinter ihm ein Film läuft, und dann ist der Film die Welt statt einer schwarzen Zeichenfläche.
|
|
|
|
Sonst ändert sich nichts. `onOverlayUpdate` wird immer noch sechzigmal pro Sekunde aufgerufen.
|
|
`overlayPrint` zählt immer noch in Zeichenzellen. Deine Sprites werden dort gezeichnet, wo sie
|
|
vorher gezeichnet wurden. Ein Spiel mit Video und ein Spiel ohne werden auf dieselbe Art
|
|
geschrieben, mit denselben Funktionen, und teilen sich dasselbe Buch.
|
|
|
|
Wenn das Spiel, das du machen willst, also kein Video hat, kannst du diesen Teil jetzt zuklappen
|
|
und zu Teil vier gehen. Nichts in Teil vier hängt von irgendetwas in Teil drei ab, und die einzigen
|
|
Lektionen, die diese hier brauchen, sind die vier, die nach ihr kommen.
|
|
|
|
Noch da? Gut.
|
|
|
|
[#_you_already_have_a_video]
|
|
=== Du hast schon ein Video
|
|
|
|
Du musst keins suchen, keins kaufen und keins machen. Als du Singe in Lektion eins zum ersten Mal
|
|
gestartet hast, ist in deinem Arbeitsordner ein Ordner namens `Singe` aufgetaucht. Öffne ihn und
|
|
sieh nach: Unter den Dateien, die die Engine entpackt hat, ist `menuBackground.mkv`. Das ist der
|
|
Hintergrund, den das eigene Menü der Engine benutzt, es ist 720 mal 480 Pixel groß und 420 Frames
|
|
lang -- etwa vierzehn Sekunden.
|
|
|
|
Das ist das Video für diese Lektion und die beiden danach.
|
|
|
|
Leg in deinem Arbeitsordner, neben den Ordnern aus den anderen Lektionen, einen Ordner namens
|
|
`movie` an und darin eine Datei namens `movie.singe`. Mehr musst du nicht vorbereiten. Du startest
|
|
Singe aus dem Arbeitsordner, wie seit Lektion eins, also liegt der Ordner `Singe` samt Video direkt
|
|
neben deinem Spiel, und `Singe/menuBackground.mkv` ist der Name, den du der Engine gleich gibst.
|
|
Kopieren musst du nichts.
|
|
|
|
[#_type_this_in_2]
|
|
=== Tipp das ein
|
|
|
|
[source,lua]
|
|
----
|
|
dofile("Singe/Framework.singe")
|
|
|
|
|
|
function onOverlayUpdate()
|
|
overlayClear()
|
|
overlayPrint(2, 2, "Frame " .. discGetFrame())
|
|
return OVERLAY_UPDATED
|
|
end
|
|
|
|
|
|
discPlay()
|
|
----
|
|
|
|
Speichere die Datei.
|
|
|
|
[#_run_it_2]
|
|
=== Starte es
|
|
|
|
Das Video gehört nicht zu deinem Skript, also lädst du es auch nicht in deinem Skript. Du gibst es
|
|
der Engine auf der Befehlszeile mit, mit `-v`:
|
|
|
|
----
|
|
Singe -R -v Singe/menuBackground.mkv movie
|
|
----
|
|
|
|
`-v` ist die Kurzform von `--framefile` und nennt das Video, das das Spiel abspielen soll. Der Name
|
|
deines Skripts kommt zuletzt, wie immer. `-R` ist dasselbe `-R`, das du seit Lektion eins benutzt,
|
|
und es startet das Spiel immer noch neu, wenn du speicherst.
|
|
|
|
Um das Bitten geht es bei `-v`. Wenn du eine Videodatei neben dein Skript legst und nichts sagst,
|
|
bemerkt die Engine sie, sagt das auf der Konsole und startet dein Spiel ohne sie. Ein Spiel ist nur
|
|
dann ein Videospiel, wenn jemand sagt, dass es eins ist.
|
|
|
|
Ein Fenster geht auf. Ein Ball aus orangefarbenem Feuer füllt es, aus dem Feuer formt sich ein
|
|
Drache, darunter erscheint das Wort `SINGE`, und dann rollt ein magentafarbenes Gitter unter einer
|
|
orangefarbenen Sonne davon. In der linken oberen Ecke, über allem, zählt eine Zahl hoch.
|
|
|
|
Sieh es dir bis zum Ende an. Dann sieh dir an, was am Ende passiert.
|
|
|
|
[#_what_just_happened_10]
|
|
=== Was gerade passiert ist
|
|
|
|
[source,lua]
|
|
----
|
|
discPlay()
|
|
----
|
|
|
|
Singe nennt das Video *die Disc*, und jede Funktion, die es steuert, fängt mit `disc` an. Der Name
|
|
ist historisch: Die Engine hat ihr Leben damit begonnen, Spiele abzuspielen, die ihren Film auf
|
|
einer Laserdisc hatten, und der Name ist an der Idee hängen geblieben. Es ist eine Videodatei auf
|
|
deiner Festplatte und sonst nichts.
|
|
|
|
Ein Spiel hat höchstens eine Disc, also nimmt keine der `disc`-Funktionen ein Argument, das sagt,
|
|
welche. Es gibt nur die eine.
|
|
|
|
Diese Zeile steht in keiner Funktion. Sie steht für sich allein unten in der Datei, und sie läuft
|
|
einmal, wenn Singe dein Skript lädt -- vor dem ersten `onOverlayUpdate`, vor allem anderen. Singe
|
|
hat keinen Callback `onStartup`, weil es keinen braucht: Ein Lua-Skript *ist* eine Liste von
|
|
Anweisungen, und alles, was du außerhalb einer Funktion schreibst, passiert, während die Datei
|
|
gelesen wird. Startcode kommt nach Konvention ganz unten hin, unter die Funktionen.
|
|
|
|
Du brauchst `discPlay()`, weil die Disc nicht von selbst startet. Wenn das Spiel beginnt, parkt die
|
|
Engine die Disc auf Frame eins und pausiert sie dort. Nichts bewegt sich, bis du darum bittest.
|
|
|
|
[source,lua]
|
|
----
|
|
overlayPrint(2, 2, "Frame " .. discGetFrame())
|
|
----
|
|
|
|
`discGetFrame` beantwortet eine Frage: Welcher Frame ist gerade zu sehen? Es nimmt keine Argumente
|
|
und gibt eine Zahl zurück.
|
|
|
|
Achte darauf, wo diese Zahl gezeichnet wird. Spalte 2, Zeile 2 -- genau dort, wo in Lektion eins
|
|
`Hello!` hinkam, und sie landet an derselben Stelle auf dem Bildschirm. Das Overlay, auf dem du
|
|
zeichnest, ist auf jeder Seite halb so groß wie das Video, und das ist bei einem Video von 720 mal
|
|
480 genau 360 mal 240, also genau die Größe, auf der du seit dreizehn Lektionen zeichnest. Deine Koordinaten haben sich nicht verschoben. Wenn du je ein Video in einer anderen
|
|
Größe abspielst, ändert das Overlay seine Größe mit, und `overlaySetResolution` stellt es auf jede
|
|
Größe, die du willst; der Eintrag in der Referenz hat die Einzelheiten.
|
|
|
|
Das ist die ganze Idee des Overlays. Das Video liegt darunter. Deine Zeichnung liegt obendrauf, und
|
|
überall, wo du nicht gezeichnet hast, scheint der Film durch. Darum ist `overlayClear` jetzt noch
|
|
wichtiger als bisher: Lösch das Overlay, und du siehst den Film.
|
|
|
|
[#_a_frame_is_an_address]
|
|
=== Ein Frame ist eine Adresse
|
|
|
|
Die Zahl, die `discGetFrame` zurückgibt, ist eine *Framenummer*. Ein Frame ist ein einzelnes
|
|
stehendes Bild. Dieses Video enthält 420 davon und zeigt etwa dreißig pro Sekunde, also dauert es
|
|
etwa vierzehn Sekunden.
|
|
|
|
Frames werden ab null gezählt, also hat dieses Video die Frames 0 bis 419. Die Anzahl und die
|
|
letzte Nummer sind nicht dasselbe, und darüber stolpert jeder einmal. `discGetFrameCount` sagt dir
|
|
die Anzahl -- 420 -- und der höchste Frame, nach dem du fragen kannst, ist eins weniger.
|
|
|
|
Das Wichtige an einer Framenummer ist, dass sie sich nie ändert und nie verschiebt. Frame 110 ist
|
|
der Moment, in dem der Drache Feuer speit, in dieser Kopie der Datei, auf diesem Rechner, heute und
|
|
nächstes Jahr. Sekunden verschieben sich, weil ein Video nicht immer genau mit der Rate läuft, die
|
|
du glaubst. Eine Framenummer ist die Adresse eines Moments, und aus Adressen ist ein Videospiel
|
|
gebaut: Du schreibst dir den Frame auf, in dem der Drache erscheint, und von da an kannst du den
|
|
Film dorthin schicken, wann immer du willst.
|
|
|
|
Diese Zahlen zu finden ist eine Arbeit, die du einmal pro Video machst, und das Skript, das du
|
|
geschrieben hast, ist das Werkzeug dafür: Lass den Film laufen, beobachte den Zähler, schreib dir
|
|
die Frames auf, in denen etwas passiert. Alles in Lektion sechzehn baut auf einer Liste von Zahlen
|
|
auf, die genau so gefunden wurden.
|
|
|
|
[#_what_happened_at_the_end]
|
|
=== Was am Ende passiert ist
|
|
|
|
Der Zähler hat 419 erreicht, und dann stand er wieder auf 0, und das Feuer war zurück.
|
|
|
|
Wenn die Disc über das Ende hinausläuft, startet die Engine sie wieder ab Frame null. Sie hält
|
|
nicht an, und sie sagt dir nichts. Ein echtes Spiel lässt es nie so weit kommen: Es beobachtet die
|
|
Framenummer und schickt die Disc woandershin, bevor das Ende kommt. Weil das jemand vergessen hat,
|
|
spielt ein halbfertiges Videospiel mitten in einem Kampf plötzlich seine eigene Titelsequenz ab.
|
|
|
|
[#_stopping_jumping_and_asking]
|
|
=== Anhalten, Springen und Fragen
|
|
|
|
Füg diese Funktion über `onOverlayUpdate` ein:
|
|
|
|
[source,lua]
|
|
----
|
|
function onInputPressed(what)
|
|
if what == SWITCH_BUTTON1 then
|
|
if discGetState() == DISC_PLAYING then
|
|
discPause()
|
|
else
|
|
discPlay()
|
|
end
|
|
elseif what == SWITCH_LEFT then
|
|
discSearch(0)
|
|
elseif what == SWITCH_RIGHT then
|
|
discSkipForward(30)
|
|
elseif what == SWITCH_START1 then
|
|
discStop()
|
|
end
|
|
end
|
|
----
|
|
|
|
Speichere und probier jede aus. Die Leertaste spielt und pausiert. Links geht zurück zum Anfang.
|
|
Rechts springt etwa eine Sekunde weiter. Die Taste `1` hält die Disc ganz an, und was du bekommst,
|
|
ist ein blauer Bildschirm -- die Farbe, die ein Laserdisc-Player zeigte, wenn er angehalten hatte,
|
|
und sie ist geblieben, weil sehr viele Spiele ihre Punktetabelle darauf gezeichnet haben.
|
|
|
|
Das sind fünf neue Aufrufe, und sie teilen sich in zwei Arten auf.
|
|
|
|
*Fragen.* `discGetState` gibt einen von vier Namen zurück, die die Engine schon für dich festgelegt
|
|
hat: `DISC_PLAYING`, `DISC_PAUSED`, `DISC_STOPPED` und `DISC_EJECTED`. Sie sind wie
|
|
`OVERLAY_UPDATED` -- keine Anführungszeichen, weil sie Namen für Werte sind und kein Text.
|
|
Vergleiche mit `==`, so wie du in Lektion drei Zahlen verglichen hast.
|
|
|
|
*Sagen.* `discPlay` startet oder setzt fort. `discPause` friert auf dem Frame ein, der gerade zu
|
|
sehen ist, und lässt ihn auf dem Bildschirm. `discStop` ist der schwerere: Das Bild wird durch den
|
|
blauen Bildschirm ersetzt, `discGetFrame` antwortet `0`, bis die Disc wieder läuft, und
|
|
`discPause` und die Sprungaufrufe werden ignoriert, solange sie angehalten ist. Jeder von
|
|
`discPlay`, `discSearch` oder `discSkipToFrame` hebt das Anhalten wieder auf.
|
|
|
|
Zwei Aufrufe schicken die Disc zu einem Frame, und der Unterschied zwischen ihnen ist das Einzige in
|
|
dieser Lektion, das du dir einprägen solltest:
|
|
|
|
* `discSearch(frame)` geht dorthin und *pausiert* dort.
|
|
* `discSkipToFrame(frame)` geht dorthin und *spielt* von dort ab.
|
|
|
|
Nimm Search, wenn du auf einem Bild parken und warten willst. Nimm Skip, wenn eine Szene anfangen
|
|
soll. `discSkipForward(30)` und `discSkipBackward(30)` sind dieselbe Idee, gemessen ab dort, wo du
|
|
bist, statt ab dem Anfang, und sie lassen die Disc spielen, wenn sie gespielt hat, und pausiert,
|
|
wenn sie pausiert war.
|
|
|
|
Es gibt auch `discPauseAtFrame`, das unter einem älteren Namen genau das tut, was `discSearch` tut.
|
|
Die Referenz kennzeichnet es als alten Alias. Du wirst es in den Skripten anderer Leute sehen;
|
|
schreib in deine eigenen `discSearch`.
|
|
|
|
[#_waiting_is_harder_than_it_sounds]
|
|
=== Warten ist schwerer, als es klingt
|
|
|
|
Das ist der Satz, aus dem jedes Videospiel besteht:
|
|
|
|
____
|
|
Spiel von Frame 0 bis Frame 120, dann stell dem Spieler eine Frage.
|
|
____
|
|
|
|
Versuch, ihn zu schreiben. Du kannst nicht „spiel, dann warte, dann frag“ schreiben, weil du keinen
|
|
Ort hast, an dem du das schreiben könntest. Du hast `onOverlayUpdate`, das Singe sechzigmal pro
|
|
Sekunde aufruft und das jedes einzelne Mal sofort zurückkehren muss. Eine Funktion, die dasäße und
|
|
wartete, würde die Engine abwürgen: kein Zeichnen, keine Eingabe, kein Video.
|
|
|
|
Du kannst also nicht „dann“ sagen. Du musst aus dem „dann“ eine Variable machen, die sich merkt,
|
|
wie weit du gekommen bist, und sie bei jedem dieser sechzig Aufrufe prüfen:
|
|
|
|
[source,lua]
|
|
----
|
|
local asking = false
|
|
|
|
|
|
function onOverlayUpdate()
|
|
overlayClear()
|
|
if not asking and discGetFrame() >= 120 then
|
|
discPause()
|
|
asking = true
|
|
end
|
|
if asking then
|
|
overlayPrint(2, 4, "Left or right?")
|
|
end
|
|
return OVERLAY_UPDATED
|
|
end
|
|
----
|
|
|
|
Das funktioniert. Lies es, und du siehst, dass es funktioniert. Aber achte darauf, was es gekostet
|
|
hat: eine Variable, deren einzige Aufgabe es ist, sich zu merken, in welchem Teil des Satzes du
|
|
bist, und zwei `if`-Anweisungen, die sie ehrlich halten.
|
|
|
|
Jetzt füg den Rest der Szene hinzu. Der Spieler antwortet, also spielt als Nächstes eins von zwei
|
|
Dingen. Jedes davon endet irgendwo und stellt eine weitere Frage. Ein bescheidenes verzweigtes Spiel
|
|
hat vierzig solcher Momente, und jeder davon will seine eigene Variable und sein eigenes `if`. Am
|
|
Ende hast du ein Skript, in dem die Geschichte nirgends aufgeschrieben steht -- sie ist über ein
|
|
Dutzend Flags verteilt, und um herauszufinden, was nach dem Drachen passiert, musst du sie alle
|
|
lesen.
|
|
|
|
Merk dir diesen Gedanken. Lektion fünfzehn ist nichts anderes als die Lösung dafür.
|
|
|
|
[#_when_there_is_no_video]
|
|
=== Wenn es kein Video gibt
|
|
|
|
Starte dein Skript wieder auf die schlichte Art, ohne `-v`:
|
|
|
|
----
|
|
Singe -R movie
|
|
----
|
|
|
|
Nichts stürzt ab. Du bekommst ein schwarzes Fenster mit `Frame 0` in der Ecke.
|
|
|
|
Das lohnt sich zu verstehen, denn es ist die beruhigende Hälfte des Versprechens vom Anfang dieser
|
|
Lektion. Ein Spiel ohne Disc führt trotzdem jeden `disc`-Aufruf aus, den du machst; sie tun nur
|
|
nichts Nützliches. Die Engine dokumentiert genau, was sie antworten, und das ändert sich nie:
|
|
`discGetFrame` und `discGetFrameCount` geben `0` zurück, `discGetState` gibt `DISC_EJECTED`
|
|
zurück, `discGetWidth` und `discGetHeight` geben die Größe der Zeichenfläche zurück, und
|
|
`discPlay`, `discPause`, `discSearch` und der Rest werden im Log vermerkt und ansonsten ignoriert.
|
|
|
|
Du kannst kein Skript kaputt machen, indem du eine Disc-Funktion aufrufst, wenn es keine Disc gibt.
|
|
|
|
Wenn ein Skript es *wissen* muss, fragt es `SINGE_DISC`. Das ist ein weiterer Name, den die Engine
|
|
festlegt, bevor dein Skript läuft, und er ist `true`, wenn das Spiel ein Video hat, und `false`,
|
|
wenn die Welt eine schlichte Zeichenfläche ist. So schreibt man richtig ein Spiel, das auf beide
|
|
Arten laufen kann:
|
|
|
|
[source,lua]
|
|
----
|
|
if not SINGE_DISC then
|
|
overlayPrint(2, 2, "This game has no video.")
|
|
overlayPrint(2, 4, "Run it again with -v Singe/menuBackground.mkv")
|
|
return OVERLAY_UPDATED
|
|
end
|
|
----
|
|
|
|
`not` dreht einen Wahr-oder-falsch-Wert um, wie in Lektion drei.
|
|
|
|
[#_the_whole_thing_7]
|
|
=== Das Ganze
|
|
|
|
Zusammengesetzt, mit einer kleinen Funktion, die den Zustand in Worte fasst, und einem Frame, an
|
|
dem angehalten wird, ist das hier das fertige Skript. Es ist ein Frame-Finder: das Werkzeug, das du
|
|
jedes Mal benutzen wirst, wenn du einem neuen Video begegnest.
|
|
|
|
[source,lua]
|
|
----
|
|
dofile("Singe/Framework.singe")
|
|
|
|
local stopAt = 400
|
|
local held = false
|
|
|
|
|
|
local function stateName()
|
|
local state = discGetState()
|
|
|
|
if state == DISC_PLAYING then
|
|
return "playing"
|
|
elseif state == DISC_PAUSED then
|
|
return "paused"
|
|
elseif state == DISC_STOPPED then
|
|
return "stopped"
|
|
end
|
|
return "not there"
|
|
end
|
|
|
|
|
|
function onInputPressed(what)
|
|
if what == SWITCH_BUTTON1 then
|
|
if discGetState() == DISC_PLAYING then
|
|
discPause()
|
|
else
|
|
discPlay()
|
|
end
|
|
elseif what == SWITCH_LEFT then
|
|
discSearch(0)
|
|
held = false
|
|
elseif what == SWITCH_RIGHT then
|
|
discSkipForward(30)
|
|
elseif what == SWITCH_START1 then
|
|
discStop()
|
|
held = false
|
|
end
|
|
end
|
|
|
|
|
|
function onOverlayUpdate()
|
|
local frame = discGetFrame()
|
|
|
|
overlayClear()
|
|
if not SINGE_DISC then
|
|
overlayPrint(2, 2, "This game has no video.")
|
|
overlayPrint(2, 4, "Run it again with -v Singe/menuBackground.mkv")
|
|
return OVERLAY_UPDATED
|
|
end
|
|
|
|
if not held and frame >= stopAt then
|
|
discPause()
|
|
held = true
|
|
end
|
|
|
|
overlayPrint(2, 2, "Frame " .. frame .. " of " .. discGetFrameCount())
|
|
overlayPrint(2, 4, "The disc is " .. stateName() .. ".")
|
|
overlayPrint(2, 6, "Space plays and pauses. Left rewinds.")
|
|
overlayPrint(2, 7, "Right jumps on a second. 1 stops the disc.")
|
|
if held then
|
|
overlayPrint(2, 9, "Holding at frame " .. stopAt .. ".")
|
|
end
|
|
|
|
return OVERLAY_UPDATED
|
|
end
|
|
|
|
|
|
discPlay()
|
|
----
|
|
|
|
`held` ist dieselbe Art von Variable wie `asking` oben, und sie ist aus einem Grund da, den du
|
|
testen kannst: Nimm sie heraus, lass `if frame >= stopAt then discPause()
|
|
end` stehen und versuch dann, mit der Leertaste über Frame 400 hinaus weiterzumachen. Es geht
|
|
nicht. Die Disc läuft eine Sechzigstelsekunde lang weiter, und das nächste `onOverlayUpdate`
|
|
pausiert sie wieder, für immer. Mit `held` sagst du „das habe ich schon einmal gemacht“.
|
|
|
|
[#_try_it_14]
|
|
=== Probier es aus
|
|
|
|
. *Finde den Drachen.* Starte es, beobachte den Zähler und schreib dir den Frame auf, in dem der
|
|
Drache zum ersten Mal erscheint, den Frame, in dem er Feuer speit, und den Frame, in dem das
|
|
Gitter den Bildschirm übernimmt. Diese drei Zahlen wirst du in Lektion sechzehn brauchen.
|
|
. *Geh es Schritt für Schritt durch.* Ändere `discSkipForward(30)` in `discSkipForward(1)` und
|
|
halt die Pfeiltaste nach rechts gedrückt. Jetzt ändere `stopAt` in einen Frame mitten im Drachen
|
|
und benutz `discSearch` und die Pfeiltaste, um genau darauf zu landen.
|
|
. *Lass es in Schleife laufen.* Ändere das Anhalten so, dass es statt bei Frame 400 zu pausieren
|
|
`discSkipToFrame(0)` aufruft. Du hast einen Attract-Modus gebaut: eine Szene, die für immer
|
|
läuft, bis jemand einen Knopf drückt.
|
|
. *Frag nach einem Frame, den es nicht gibt.* Ändere die Zeile mit `SWITCH_RIGHT` in
|
|
`discSkipToFrame(100000)`. Es stürzt nicht ab. Lies den Eintrag zu `discSkipToFrame` in der
|
|
Referenz und finde den Satz, der sagt, warum.
|
|
. *Spiel es ohne das Video.* Starte `Singe -R movie` und drück dann jede Taste. Nichts, was du
|
|
drückst, kann es kaputt machen.
|
|
|
|
[#_break_it_on_purpose_13]
|
|
=== Mach es absichtlich kaputt
|
|
|
|
Der Fehler, der hier am meisten Zeit kostet, ist kein Lua-Fehler. Starte das hier:
|
|
|
|
----
|
|
Singe -R -v menuBackground.mkv movie
|
|
----
|
|
|
|
Der Pfad ist falsch -- er zählt ab dem Arbeitsordner, und das Video liegt dort im Ordner `Singe`,
|
|
nicht lose herum -- und Singe startet nicht. Stattdessen gibt es seine ganze Liste von Befehlszeilenoptionen aus, was beim ersten
|
|
Mal erschreckend ist, und dann das hier als allerletzte Zeile:
|
|
|
|
----
|
|
Error: Unable to locate video.
|
|
----
|
|
|
|
So eine Wand aus Optionen bedeutet immer dasselbe: Die Engine ist nie bis zu deinem Spiel gekommen.
|
|
Ignorier die Wand und lies die letzte Zeile.
|
|
|
|
Der ist leicht. Die wenig hilfreiche Version davon ist, ganz ohne `-v` zu starten und zu vergessen,
|
|
dass du das getan hast. Es gibt keinen Fehler, weil ein Spiel ohne Video ein völlig gutes Spiel
|
|
ist. Du bekommst einen schwarzen Bildschirm und einen Zähler, der auf `0` festhängt, und suchst
|
|
zwanzig Minuten lang in deinem Skript nach dem Fehler.
|
|
|
|
Genau dafür ist der Block mit `SINGE_DISC` im fertigen Skript da. Drei Zeilen davon, und das Spiel
|
|
sagt dir, was nicht stimmt, statt zu schmollen. Bau so eine Prüfung in jedes Skript ein, das ein
|
|
Video braucht.
|
|
|
|
[#_what_you_learned_14]
|
|
=== Was du gelernt hast
|
|
|
|
* Ein Spiel spielt nur dann Video ab, wenn es darum bittet, und alles andere in Singe funktioniert
|
|
so oder so gleich.
|
|
* Singe nennt das Video die Disc. Ein Spiel hat eine, und die `disc`-Funktionen nehmen kein
|
|
Handle, weil es nichts gibt, zwischen dem man wählen müsste.
|
|
* Das Video kommt mit `-v` auf die Befehlszeile, nicht ins Skript.
|
|
* Das Overlay wird über das Bild gezeichnet, und der Film scheint überall durch, wo du nicht
|
|
gezeichnet hast.
|
|
* Ein Frame ist ein einzelnes stehendes Bild, ab null gezählt, und eine Framenummer ist die Adresse
|
|
eines Moments.
|
|
* `discGetFrame` fragt, wo die Disc ist; `discGetFrameCount` sagt, wie viele Frames es gibt;
|
|
`discGetState` sagt, was die Disc gerade tut.
|
|
* `discPlay`, `discPause` und `discStop` steuern sie. `discSearch` geht zu einem Frame und
|
|
pausiert; `discSkipToFrame` geht zu einem Frame und spielt.
|
|
* Eine Disc, die über das Ende hinausläuft, fängt wieder bei Frame null an, ohne es dir zu sagen.
|
|
* Ohne Disc ist jeder `disc`-Aufruf harmlos, und an `SINGE_DISC` erkennt ein Skript, in welcher
|
|
Art von Spiel es steckt.
|
|
* Du kannst in `onOverlayUpdate` nicht warten, also muss „dann“ als Variable ausgeschrieben
|
|
werden.
|
|
|
|
[#_next_time_14]
|
|
=== Beim nächsten Mal
|
|
|
|
Der letzte Punkt ist der wunde. Ein Videospiel ist eine Liste von Schritten -- spiel dies, frag
|
|
das, spiel jenes -- und du hast gerade gesehen, dass das Callback-Modell nicht „dann“ sagen kann
|
|
ohne ein Flag und ein `if` für jeden Schritt. Lektion fünfzehn zeigt dir die andere Art, wie Singe
|
|
dich ein Skript schreiben lässt, und darin ist „dann“ eine neue Zeile.
|