[#_lesson_10_sound] == Lektion 10: Sound image::learn/10-sound.png[Die fertige Lektion, 480] Ein Spiel ohne Sound fühlt sich kaputt an, und zwar auf eine Art, die man schwer benennen kann. Die Schüsse fliegen los, und nichts passiert. Die Felsen zerbrechen, und nichts passiert. Leg auf jedes davon ein Geräusch von einer halben Sekunde, und dasselbe Spiel fühlt sich plötzlich an, als wäre es aus etwas gemacht. Diese Lektion gibt deinem Spiel einen Schuss, eine Explosion und Musik darunter. Unterwegs stellt sie dir deinen fünften Callback vor: den, mit dem die Engine dir sagt, dass ein Sound fertig abgespielt ist. [#_one_noise] === Ein Geräusch Im Grafik-Kit, das du in Lektion neun kopiert hast, stecken zwei Sounds, `shoot.wav` und `boom.wav`, und die Explosion ist von den beiden leichter zu hören. Leg neben deinem Ordner `art` eine neue Datei an und tipp das ein. [source,lua] ---- dofile("Singe/Framework.singe") local boom = soundLoad(DIR .. "art/boom.wav") function onInputPressed(what) if what == SWITCH_BUTTON1 then soundPlay(boom) end end function onOverlayUpdate() overlayClear() overlayPrint(2, 2, "Press space.") return OVERLAY_UPDATED end function onShutdown() soundUnload(boom) end ---- Starte es und tipp drei- oder viermal schnell auf die Leertaste. Die Knalle türmen sich übereinander, statt einander abzuschneiden. Hältst du die Leertaste gedrückt, bekommst du genau einen Knall, keinen ganzen Strom davon. Das ist dasselbe Verhalten, das du in Lektion drei kennengelernt hast: Eine gehaltene Taste ist ein Drücken, einmal gemeldet, und die Engine wirft die Wiederholungen weg, die deine Tastatur schickt. Darum braucht das Lenken `onInputReleased` und eine Variable, statt Tastendrücke zu zählen. [#_what_just_happened_6] === Was gerade passiert ist Die Form ist dieselbe wie in Lektion neun: einmal oben laden, das Handle benutzen, am Ende zurückgeben. [source,lua] ---- local boom = soundLoad(DIR .. "art/boom.wav") ---- `soundLoad` liest eine Audiodatei, behält sie im Speicher und gibt ein Handle zurück -- dieselbe Idee wie ein Sprite-Handle, eine Zahl, die für die Engine „diesen Clip da“ bedeutet. `DIR` steht aus demselben Grund da wie in Lektion neun: Der Sound wohnt im Ordner deines Spiels, und das Spiel muss ihn finden können, egal von wo aus Singe gestartet wurde. Alles, was Lektion neun übers Laden gesagt hat, gilt hier Wort für Wort. Lade oben in der Datei, einmal. Ein `soundLoad` in `onOverlayUpdate` lädt den Clip sechzigmal pro Sekunde und gibt nie einen zurück, und ein paar Minuten später stirbt das Spiel daran. [source,lua] ---- soundPlay(boom) ---- Spiel ihn ab. Mehr gehört zum Abspielen eines Soundeffekts nicht, und um den Rest kümmert sich die Engine: Der Clip fängt an, er hört auf, und er kommt deinem Programm nie in die Quere. Aber `soundPlay` gibt auch etwas zurück, und das ist *nicht* dieselbe Zahl, die du hineingegeben hast. Ändere die Zeile und sieh hin: [source,lua] ---- local channel = soundPlay(boom) debugPrint("clip " .. boom .. " on channel " .. channel) ---- Tipp ein halbes Dutzend Mal schnell auf die Leertaste und beobachte die beiden Zahlen. Die Clip-Nummer ändert sich nie. Die Kanalnummer steigt -- 0, dann 1, dann 2 -- und sobald die früheren Knalle vorbei sind, fällt sie zurück und fängt an, die kleinen Nummern wiederzuverwenden. Das ist die eine Sache beim Sound, über die jeder stolpert, also lohnt es sich, sie langsam zu sagen. *Hier sind zwei verschiedene Arten von Zahlen im Spiel.* Das *Clip-Handle* von `soundLoad` ist die Aufnahme. Davon gibt es eins. Es ändert sich nicht, es spielt weder, noch spielt es nicht, es ist der Sound, der im Speicher sitzt und darauf wartet, benutzt zu werden. Der *Kanal* von `soundPlay` ist eine Aufführung dieser Aufnahme. Singe hat sechzehn davon, nummeriert von `0` bis `15`, und jeder Sound, den man gerade hört, belegt einen. Darum überlappen sich die Knalle: Das zweite Drücken hat das erste nicht unterbrochen, es hat eine zweite Aufführung auf dem nächsten freien Kanal gestartet. Alles, was mit einem *Sound, den du hörst*, etwas tut, nimmt einen Kanal: `soundStop`, `soundIsPlaying`, `soundPause`, `soundResume`. Alles, was mit der *Aufnahme* etwas tut, nimmt das Handle: `soundPlay` und `soundUnload`. Gib das eine dort hinein, wo das andere hingehört, und du bekommst nicht immer einen Fehler, denn `3` ist eine gültige Kanalnummer und auch ein völlig brauchbares Clip-Handle -- du bekommst Stille, oder der falsche Sound hört auf, und das ist viel schwerer zu finden. Sechzehn ist viel, aber nicht endlos. Wenn alle Kanäle belegt sind, spielt nichts, und `soundPlay` gibt statt einer Kanalnummer `-1` zurück. Ein Spiel, das sehr viele Sounds auf einmal abfeuert, sollte damit rechnen, und gleich schreibst du eins. [#_volume] === Lautstärke Es gibt drei Lautstärken, und sie werden miteinander multipliziert. Die *Gesamtlautstärke* der Effekte gilt für alles. `soundGetVolume` liest sie, und `soundSetVolume` setzt sie, auf einer Skala von `0` bis `63`. [source,lua] ---- soundSetVolume(40) ---- Lass es. Diese Zahl gehört dem Spieler, nicht dir: Sie startet bei dem, was er beim Starten des Spiels verlangt hat, und ein Spiel, das sie überschreibt, ist ein Spiel, das in irgendjemandes stillem Zimmer zu laut ist. Ändere die Gesamtlautstärke nur, wenn der Spieler dich darum bittet, über einen Optionsbildschirm. `soundGetVolume` gibt es, damit ein Optionsbildschirm zeigen kann, wie hoch sie gerade ist, statt zu raten. Wonach du greifen solltest, ist die *Kanallautstärke*, die unter der Gesamtlautstärke sitzt. Gib `soundPlay` ein drittes Argument, und diese eine Aufführung ist leiser: [source,lua] ---- soundPlay(shootClip, 0, 35) ---- Die `0` in der Mitte gibt an, wie oft wiederholt wird, und die musst du angeben, weil die Lautstärke danach kommt. `0` heißt: einmal abspielen. `-1` heißt: in Schleife, bis etwas es anhält, und so würdest du ein Motorbrummen oder Wind machen. Der Eintrag zu `soundPlay` in der Referenz listet alle drei Argumente auf. Die dritte Lautstärke ist die Entfernung, für Sounds, die in einer 3D-Welt platziert sind. Die kommt in Teil vier dran, wenn das Spiel in drei Dimensionen umzieht. Musik hat eine eigene Lautstärke, und die hat eine andere Skala. Gleich mehr dazu. [#_stopping_and_not_stopping] === Anhalten und Nicht-Anhalten `soundStop` nimmt einen Kanal und schneidet ihn dort ab, wo er gerade ist: [source,lua] ---- soundStop(engineChannel) engineChannel = -1 ---- Beachte die zweite Zeile. Sobald ein Kanal aufhört, darf die Engine genau diese Nummer an das nächste `soundPlay` vergeben, also gehört eine Kanalnummer, die du noch festhältst, jetzt vielleicht zu einem völlig anderen Sound. Wirf sie weg, sobald du ihn anhältst. `-1` ist das Nützliche, was du dort hineinschreibst, denn das ist derselbe Wert, den dir `soundPlay` gibt, wenn es gar nichts abspielen konnte, und so deckt ein einziger Test beides ab: `if channel >= 0 then`. `soundStop` ist für einen Sound, der jetzt falsch ist -- das Motorgeräusch, wenn der Motor ausgeht, der Alarm, wenn jemand auf den Alarm reagiert hat. Für einen kurzen Effekt ist es das falsche Werkzeug. Eine halbsekündige Explosion, die nach einer Zehntelsekunde abgehackt wird, klingt wie ein Fehler, weil sie einer ist. Also tu meistens einfach gar nichts. Lass ihn zu Ende spielen. Ein Soundeffekt, den du startest und an den du nie wieder denkst, hört von selbst auf und gibt seinen Kanal von selbst frei, und genau das willst du. Das gilt sogar am Ende. `soundUnload` schneidet einen Clip, der noch spielt, nicht ab: Die Engine hält den Ton am Leben, bis der letzte Kanal, der ihn benutzt, aufgehört hat. Du kannst in `onShutdown` entladen, ohne dir Sorgen zu machen, dass du etwas zum Schweigen gebracht hast. `soundIsPlaying(channel)` beantwortet, ob ein Kanal gerade Geräusche macht. Wofür man es hauptsächlich braucht, ist nicht Neugier, sondern Zurückhaltung: ein Alarm, der einmal ertönen soll und nicht jedes Mal neu anfängt, wenn das Spiel merkt, dass die Gefahr immer noch da ist. [source,lua] ---- function raiseAlarm() if alarmChannel < 0 or not soundIsPlaying(alarmChannel) then alarmChannel = soundPlay(alarmClip) end end ---- Lies die Bedingung laut vor: Spiel den Alarm ab, wenn es keinen Alarmkanal gibt oder wenn der, den du hast, aufgehört hat. Sonst lass ihn in Ruhe. [#_the_sound_that_stutters] === Der Sound, der stottert Jetzt kommt der Fehler. Jeder schreibt ihn, meistens innerhalb einer Woche, nachdem er Sound entdeckt hat. Angenommen, du willst, dass das Schiff weiterfeuert, solange der Knopf gedrückt ist, statt einmal pro Drücken. Du würdest es so machen, wie du lenkst: eine Variable `firing`, die in `onInputPressed` auf `true` gesetzt wird und in `onInputReleased` zurück auf `false`, und der Schuss wird in `onOverlayUpdate` erzeugt, solange sie wahr ist. Das ist ein völlig vernünftiger Wunsch, und das Geräusch sieht aus, als gehörte es genau dorthin, zum Schuss: [source,lua] ---- function onOverlayUpdate() if firing then shots[#shots + 1] = { x = shipX, y = shipY } soundPlay(shootClip) end ... ---- Was herauskommt, ist kein Schuss. Es ist ein surrendes Rasseln oder ein harter, flacher Ton oder ein Geräusch, als ginge eine kleine Maschine kaputt. `onOverlayUpdate` läuft etwa sechzigmal pro Sekunde. Halt den Knopf eine Sekunde lang gedrückt, und du hast sechzig Aufführungen eines Clips verlangt, der ungefähr eine Fünftelsekunde dauert, und jede fängt über der letzten an. In jedem Moment spielen etwa elf davon, jede einen Frame hinter der vorigen, und was du hörst, sind sechzig Kopien desselben Anfangs, übereinandergestapelt. Die Lösung ist kein schlauerer Sound. Sie besteht darin, den Sound dort abzuspielen, wo das *Ereignis* passiert, und nicht dort, wo der *Zustand* wahr ist: [source,lua] ---- function onInputPressed(what) if what == SWITCH_BUTTON1 then soundPlay(shootClip, 0, 35) end end ---- `onInputPressed` passiert einmal, wenn der Knopf runtergeht. Ein Drücken, ein Schuss, ein Geräusch. Wenn du wirklich Dauerfeuer willst, ist die Antwort dieselbe, nur in anderer Form: Mach das Feuern selbst zu einem Ereignis. Zähl Frames, genau wie der Laufzyklus sie in Lektion neun gezählt hat, und erzeuge nur jeden achten Frame einen Schuss. Das Geräusch gehört zum Schuss, also spielt es sieben- oder achtmal pro Sekunde statt sechzigmal, und das ist ein Maschinengewehr statt eines Rasselns. Das ist die Regel, und es lohnt sich, sie aus dieser Lektion mitzunehmen: *Sounds gehören zu Ereignissen, nicht zu Bedingungen*. Ein Drücken, ein Treffer, ein Tod, ein aufgesammeltes Ding -- jedes davon passiert einmal und verdient einen Sound. „Der Knopf ist unten“ und „das Schiff bewegt sich“ sind keine Ereignisse; sie sind Hunderte Frames hintereinander wahr, und alles, was du von dort aus abspielst, spielt hundertfach. Gehört ein Sound wirklich zu einem Zustand, der andauert -- ein Motor, Wind, eine Sirene -- dann starte ihn einmal mit `-1` als Schleifenzahl, behalt den Kanal und halt ihn an, wenn der Zustand endet. [#_the_fifth_callback] === Der fünfte Callback Manchmal musst du tatsächlich wissen, wann ein Sound fertig ist. Schreib diese Funktion, und die Engine ruft sie auf: [source,lua] ---- function onSoundCompleted(channel) end ---- Das ist dein fünfter Callback, nach `onOverlayUpdate` in Lektion eins, den beiden Eingabe-Callbacks in Lektion drei und `onShutdown` in Lektion neun, und er funktioniert genauso wie sie alle: Du schreibst ihn, du rufst ihn nie auf, die Engine ruft dich. Der `channel`, den er dir übergibt, ist der Kanal, der gerade still geworden ist -- die Nummer, die dir `soundPlay` gegeben hat, als du ihn gestartet hast. Er wird ausgelöst, wenn ein Clip zu Ende ist, wenn die letzte Wiederholung einer Schleife fertig ist, *und* wenn du einen Kanal selbst mit `soundStop` anhältst. Das Letzte überrascht Leute, die mit dem Callback einen Sound an den nächsten hängen: Auch ein absichtlich angehaltener Kanal wird als fertig gemeldet, und wenn du nicht aufpasst, startet der nächste Sound in der Kette trotzdem. Das Spiel benutzt ihn gleich für etwas, das sich lohnt. Wenn das letzte Leben weg ist, spielt die Explosion des Schiffs, und das Spiel hält still -- kein GAME OVER, kein Ausblenden der Musik -- bis diese Explosion wirklich vorbei ist. Dann hört alles auf einmal auf. Das sind eine Handvoll Zeilen, und sie machen den Unterschied zwischen einem Ende und einem Schnitt. [#_music_is_not_a_sound_effect] === Musik ist kein Soundeffekt Du könntest ein dreiminütiges Stück mit `soundLoad` laden und mit `soundPlay` abspielen. Tu es nicht. Musik bekommt eine eigene Familie von Aufrufen, und zwar aus Gründen, die zählen: * Musik hat ihre eigene Lautstärke, getrennt von den Effekten, also kann ein Spieler die Musik leiser drehen und die Schüsse trotzdem hören. * Es gibt keine Grenze von sechzehn Kanälen und keine Kanalnummer. Ein Handle ist ein Musikstück, und du hältst es über dieses Handle an, pausierst es und setzt es fort. * `musicStop` kann ein Stück über ein, zwei Sekunden ausblenden, statt es abzuschneiden. Die Aufrufe spiegeln die, die du schon kennst: [source,lua] ---- local theme = musicLoad("Singe/menuIntro.flac") musicSetVolume(50) musicPlay(theme, -1) ---- `musicLoad` gibt ein Handle. `musicPlay` startet es, mit derselben Schleifenzahl wie `soundPlay`: `-1` für immer. `musicStop(theme)` beendet es, und `musicStop(theme, 1500)` nimmt sich anderthalb Sekunden, um es auszublenden. `musicUnload` gibt es zurück. Jetzt die Falle. *Die Musiklautstärke reicht von 0 bis 128, nicht von 0 bis 63.* Soundeffekte benutzen eine Skala und Musik eine andere, doppelt so lange, aus historischen Gründen, die überhaupt kein Trost sind, wenn deine Musik mit der halben Lautstärke herauskommt, die du gemeint hast. Wenn du mit Musik arbeitest, verdopple die Zahl, die du für einen Effekt im Kopf hattest. Das Stück, das dort geladen wird, ist die eigene Menümusik der Engine, aus dem Ordner `Singe`, den die Engine bei deinem ersten Start in deinen Arbeitsordner entpackt hat. Es ist zehn Sekunden lang und wurde so geschrieben, dass es am Ende ausblendet, also klingt es in Schleife genau wie eine zehnsekündige Schleife, die immer wieder ausblendet. Zum Lernen der Aufrufe ist das in Ordnung, und für ein fertiges Spiel taugt es überhaupt nicht, denn das liefert ein längeres eigenes Stück mit. Jedes der üblichen Formate geht: Der Abschnitt Musik in der Referenz listet sie auf, und Ogg Vorbis oder MP3 sind die vernünftige Wahl, weil ein paar Minuten Musik als WAV eine riesige Datei sind. [#_the_game_gets_a_soundtrack] === Das Spiel bekommt einen Soundtrack Öffne das Spiel aus Lektion neun. Alles hier kommt dazu; nichts fliegt raus. Vier Dinge zum Laden, oben, bei den Sprites: [source,lua] ---- local shootClip = soundLoad(DIR .. "art/shoot.wav") local boomClip = soundLoad(DIR .. "art/boom.wav") local clickClip = soundLoad("Singe/click.wav") local theme = musicLoad("Singe/menuIntro.flac") ---- Die ersten beiden haben ein `DIR` davor und die letzten beiden nicht, und das ist Absicht. `DIR` bedeutet den Ordner *deines* Spiels. Ein Name, der mit `Singe/` anfängt, bedeutet stattdessen den eigenen Ordner der Engine, wo auch immer der ist -- und darum funktioniert `dofile("Singe/Framework.singe")` seit Lektion sieben ebenfalls ohne `DIR`. Deine eigenen Dateien brauchen `DIR`; die der Engine nie. Zwei neue Variablen, neben den anderen: [source,lua] ---- local dying = false local deathChannel = -1 ---- `dying` ist der Moment zwischen dem Verlust des letzten Lebens und dem Ende des Spiels: Die Explosion klingt noch, und auf dem Bildschirm bewegt sich nichts. Der Schuss bekommt sein Geräusch in `onInputPressed`, genau dort, wo der Schuss selbst erzeugt wird, und der Neustart bekommt einen Klick, damit sich das Drücken von `1` anfühlt, als hätte es etwas getan: [source,lua] ---- function onInputPressed(what) if over then if what == SWITCH_START1 then soundPlay(clickClip) startGame() end return end if what == SWITCH_LEFT then goLeft = true elseif what == SWITCH_RIGHT then goRight = true elseif what == SWITCH_BUTTON1 then shots[#shots + 1] = { x = shipX + shipWidth / 2 - shotWidth / 2, y = shipY } soundPlay(shootClip, 0, 35) end end ---- Ein Felsen, der getroffen wird, knallt, und ein Felsen, der dich trifft, auch. In `updateShots`, neben den Punkten: [source,lua] ---- newRock(rock) score = score + HIT_SCORE gone = true soundPlay(boomClip, 0, 45) ---- Und in `updateRocks`, wo das Leben verloren geht, der interessante Teil: [source,lua] ---- if overlapping(shipX, shipY, shipWidth, shipHeight, rock.x, rock.y, rockWidth, rockHeight) then local channel = soundPlay(boomClip) newRock(rock) lives = lives - 1 if lives <= 0 then dying = true deathChannel = channel if channel < 0 then endGame() end end end ---- Wenn das letzte Leben weg ist, spielt die Explosion, und ihr Kanal wird gemerkt. Das Spiel ist jetzt `dying`, und das hält alle Bewegung an, sagt aber noch nicht GAME OVER. Das `if channel < 0` ist die Grenze von sechzehn Kanälen, erledigt. Falls genau in diesem Moment zufällig alle Kanäle belegt waren, gibt es keine Explosion und keinen Kanal, auf den man warten könnte, und ohne diese beiden Zeilen säße das Spiel für immer in `dying` fest und wartete auf einen Sound, der nie gespielt wurde. Auf etwas zu warten, das nicht kommen kann, ist einer der einfachsten Wege, ein Programm aufzuhängen, und die Absicherung ist immer billiger als der Fehler. Jetzt der neue Callback und die kleine Funktion, die er sich mit dieser Absicherung teilt: [source,lua] ---- function endGame() dying = false deathChannel = -1 over = true musicStop(theme, 1500) end function onSoundCompleted(channel) if channel == deathChannel then endGame() end end ---- Die Engine ruft `onSoundCompleted` für *jeden* Kanal auf, der aufhört, also prüft er als Erstes, ob das der Kanal ist, der ihn interessiert. Alles andere -- jeder Schuss, jeder Felsen -- läuft durch und wird ignoriert. `onOverlayUpdate` muss stillhalten, solange die Explosion läuft, und das ist ein Wort: [source,lua] ---- if not over and not dying then updateShip() updateShots() updateRocks() end ---- `startGame` setzt die beiden neuen Variablen zurück und startet die Musik von vorn: [source,lua] ---- dying = false deathChannel = -1 musicSetVolume(50) musicPlay(theme, -1) ---- Und `onShutdown` gibt zurück, was geladen wurde: [source,lua] ---- function onShutdown() spriteUnload(shipSprite) spriteUnload(rockSprite) spriteUnload(shotSprite) spriteUnload(starSprite) soundUnload(shootClip) soundUnload(boomClip) soundUnload(clickClip) musicUnload(theme) end ---- Starte es. Schieß auf die Felsen, lass dich dreimal von einem treffen und hör dir das Ende an: Der letzte Knall spielt vollständig aus, und erst wenn er weg ist, fängt die Musik an auszublenden, und die Worte erscheinen. Das fertige Skript liegt im Ordner `learn` als `10-sound.singe`, falls du vergleichen willst. [#_try_it_10] === Probier es aus . *Mach den Schuss noch leiser.* Ändere die `35` im `soundPlay` des Schusses in `10`, dann in `63`. Finde die Zahl, bei der er unter den Explosionen liegt statt obendrauf. . *Dreh die Musik leiser, ohne die Effekte anzufassen.* Ändere `musicSetVolume(50)` in `musicSetVolume(15)`. Überzeug dich, dass die Knalle genauso laut sind wie vorher, und genau deshalb ist Musik ein eigenes System. . *Schneid das Ende ab.* Ändere in `endGame` das `musicStop(theme, 1500)` in `musicStop(theme)`. Hör dir beide Enden zweimal an. Der Unterschied ist eine Zahl in einem Argument, und er ist kein kleiner Unterschied. . *Gib dem Schiff einen Motor.* Starte `soundPlay(clickClip, -1, 12)` in `startGame`, heb den Kanal, den es dir gibt, in einer eigenen Variablen auf und halt ihn in `endGame` an. Der Klick dauert eine Fünfzigstelsekunde, also ist er in Schleife ein Brummen, und näher kommt dieses Kit an einen Motor nicht heran. Dann nimm das `soundStop` wieder heraus, spiel drei Spiele hintereinander und hör dir an, was du gebaut hast. . *Zähl die Kanäle.* Gib jedes Mal, wenn ein Felsen explodiert, die Kanalnummer aus. Dann erhöh `ROCK_COUNT`, bis mehrere auf einmal explodieren, und erhöh weiter, bis du eine `-1` vorbeikommen siehst. [#_break_it_on_purpose_9] === Mach es absichtlich kaputt Du hast jetzt zwei Lautstärkeskalen im Kopf, und früher oder später kommt die falsche aus deinen Fingern. Füg diese Zeile für sich allein ein, direkt unter den vier Zeilen mit `soundLoad` und `musicLoad`: [source,lua] ---- soundSetVolume(100) ---- Das Spiel weigert sich zu starten: ---- 20:soundSetVolume: Invalid sound volume value: 100 ---- Die Zeilennummer, die du bekommst, ist die Zeile, in die du es tatsächlich getippt hast, deine ist also vielleicht nicht 20. Dann kommt die Funktion, die sich beschwert hat, und der Wert, über den sie sich beschwert hat. Die Meldung sagt dir nicht, wie groß der Bereich ist -- dafür ist der Eintrag zu `soundSetVolume` in der Referenz da -- aber sie sagt dir die Zahl, die sie nicht angenommen hat, und sobald du weißt, dass Effekte bei 63 aufhören, ist die Korrektur eine einzige Zahl. `100` ist eine verdächtige Zahl für eine Lautstärke, und es lohnt sich zu wissen, warum du sie getippt hast. Fast sicher hast du dir Lautstärke als Prozentwert gedacht, weil jeder Lautstärkeregler, den du je benutzt hast, bis 100 geht. Der von Singe nicht. Effekte gehen von `0` bis `63` und Musik von `0` bis `128`, und keins von beiden ist ein Prozentsatz von irgendetwas. Das ist die allgemeine Form eines Engine-Fehlers, im Gegensatz zu den Lua-Fehlern in Lektion eins: eine Zeilennummer, der Name der Engine-Funktion, die du aufgerufen hast, und eine Beschwerde über die Werte, die du ihr übergeben hast. Wenn du so einen siehst, ist der Eintrag zu dieser Funktion in der Referenz das Nächste, was du liest. [#_what_you_learned_10] === Was du gelernt hast * `soundLoad` liest einen Clip und gibt ein Handle zurück. Lade einmal, oben in der Datei. * `soundPlay` startet einen Clip und gibt einen *Kanal* zurück, und das ist eine andere Art von Zahl als das Handle. * Singe mischt sechzehn Kanäle. Wenn alle belegt sind, gibt `soundPlay` `-1` zurück, und nichts spielt. * Kanalnummern werden wiederverwendet. Vergiss eine, sobald du ihren Kanal angehalten hast. * Kurze Effekte lässt du in Ruhe zu Ende spielen; `soundStop` ist für Sounds, die falsch geworden sind. * `soundUnload` schneidet einen Clip, der noch spielt, nicht ab. * Spiel einen Sound dort ab, wo das Ereignis passiert, nicht dort, wo eine Bedingung wahr ist, sonst spielt er sechzigmal pro Sekunde. * `onSoundCompleted(channel)` ist ein Callback, den die Engine jedes Mal aufruft, wenn ein Kanal still wird, egal aus welchem Grund. * Musik ist ein eigenes System mit eigenen Handles, eigener Lautstärke und ohne Kanalgrenze. * Die Effektlautstärke geht von `0` bis `63`. Die Musiklautstärke geht von `0` bis `128`. Das sind keine Prozentwerte. [#_next_time_10] === Beim nächsten Mal Deine Felsen werden getroffen, wenn sich ihre Rechtecke überlappen, und darum zählt ein Schuss auch dann, wenn er durch eine Ecke des Himmels fliegt. In Lektion elf bekommst du die eigenen Kollisionstests der Engine, lernst, was eine Hitbox ist, und findest heraus, warum das Rechteck um einen runden Felsen die falsche Form hat.