singe/docs/de/lessons/13-game-folder.adoc

382 lines
19 KiB
Text

[#_lesson_13_a_game_folder]
== Lektion 13: Ein Spielordner
image::learn/13-game-folder.png[Die fertige Lektion, 480]
Dein Spiel ist ein Skript in einem Ordner, dessen Namen du dir merkst, und du startest es, indem du
seinen Namen in ein Terminal tippst. Für dich ist das in Ordnung, für alle anderen nutzlos. Ein
Spieler sollte einen Ordner neben Singe kopieren, das Menü starten, dein Spiel mit seinem Titel und
seinen Grafiken in der Liste sehen und einen Knopf drücken können. Das Bild oben ist dieses Menü,
mit deinem Spiel darin.
Dafür braucht es eine neue Datei, etwa sechs Zeilen lang, und zwei Bilder, die du mit einer Taste
aufnehmen kannst. An deinem Skript ändert sich überhaupt nichts.
[#_give_the_game_a_name]
=== Gib dem Spiel einen Namen
Ein Spiel hat zwei Namen, einen für den Computer und einen für Menschen.
Der für den Computer ist der Ordner, und den hast du in Lektion neun ausgesucht: `rocks`, mit
`rocks.singe` darin. Ordner und Skript haben denselben Namen, so wie in jeder Lektion seit
Lektion eins, und darum findet `Singe rocks` das Skript allein über den Ordner. Halte diesen Namen
schlicht. Keine Leerzeichen, denn die liest ein Terminal als Lücke zwischen zwei Namen. Und
überall, wo du ihn tippst, genau dieselbe Groß- und Kleinschreibung: Auf den meisten Computern, die
nicht Windows sind, sind `rocks` und `Rocks` zwei verschiedene Ordner, und ein Spiel, das nur auf
deinem Rechner läuft, ist kein Spiel, das irgendjemand anderes haben kann.
Der Name für Menschen kann alles sein, was du willst, mit Großbuchstaben, Leerzeichen und allem,
und er wohnt nicht im Ordnernamen. Er wohnt in der Datei, um die es in dieser Lektion geht, damit
du nie einen Ordner umbenennen musst, wenn du das Spiel im Menü umbenennst.
[#_what_goes_in_the_folder]
=== Was in den Ordner kommt
Alles, was dein Spiel braucht, und sonst nichts. Am Ende dieser Lektion sieht er so aus:
----
rocks/
games.dat Was das Menü liest
rocks.singe Das Spiel
art/ Das Grafik-Kit aus Lektion neun
spaceship.png
asteroid.png
bullet.png
star.png
shoot.wav
boom.wav
...
cabinet.png Ein Bild, das das Menü neben der Liste zeigt
marquee.png Ein Bild, das das Menü darüber zeigt
----
Alles andere, was beim Lernen darin gelandet ist -- `walk.singe` aus Lektion neun, der kleine
Soundtest aus Lektion zehn -- gehört nicht zum Spiel. Verschieb es in einen eigenen Ordner neben
`rocks`. Im Grafik-Kit sind auch ein paar Dateien, die dieses Spiel nie lädt, und die schaden
vorerst nicht. In Lektion dreißig gehst du einen Spielordner durch und nimmst alles heraus, was
nicht zum Spiel gehört.
Die Regel hinter diesem Aufbau lautet: Ein Spiel greift nie über seinen eigenen Ordner hinaus.
Nicht für Grafiken, nicht für einen Sound und nicht für eine Bibliothek, die du auch in einem
anderen Spiel benutzt -- wenn du Code zwischen zwei Spielen teilst, behält jedes seine eigene
Kopie. Es gibt genau einen Ort außerhalb des Ordners, aus dem ein Spiel laden darf, und aus dem
lädst du schon: der Ordner `Singe` der Engine selbst, in dem `Singe/Framework.singe` liegt und seit
Lektion zehn auch der Klick und die Musik.
Der Spielordner liegt in deinem Arbeitsordner, neben der Engine:
----
singe/ Dein Arbeitsordner
Singe-v3.00-... Die Engine selbst, wie auch immer deine heißt
Menu.sh Startet das Menü. Unter Windows Menu.bat
Singe/ Hilfsdateien. Nicht deine; lass sie in Ruhe
data/ Alles, was irgendetwas schreibt, auch dein Spielstand
rocks/ Dein Spiel
----
Genau so sieht es auch auf dem Rechner eines Spielers aus. Die Engine hat `Singe`, `data` und
`Menu.sh` (`Menu.bat` unter Windows) geschrieben, als du in Lektion eins zum allerersten Mal etwas
gestartet hast. Lektion eins hat die ersten beiden erwähnt. Das dritte ist neu für dich, und diese
Lektion setzt es ein.
[#_paths_count_from_where_singe_started]
=== Pfade zählen ab dort, wo Singe gestartet wurde
Das machst du seit Lektion neun, also ist das hier eine Erinnerung und keine Änderung.
Jeder Name, den dein Spiel lädt -- `art/spaceship.png`, `art/boom.wav` -- wird ab dem Ordner
gesucht, in dem Singe *gestartet wurde*, nicht ab dem Ordner, in dem dein Skript liegt. Du startest
Singe in deinem Arbeitsordner, also würde ein nacktes `art/spaceship.png` in `singe/art` gesucht,
und das gibt es nicht. `DIR` ist der eigene Ordner deines Skripts, mit dem Schrägstrich schon am
Ende, also zeigt ein Name, der daran geklebt wird, auf deine Datei, egal von wo aus Singe gestartet
wurde:
[source,lua]
----
local shipSprite = spriteLoad(DIR .. "art/spaceship.png")
----
Das Menü startet Singe in dem Ordner, in dem `Menu.sh` liegt, und das ist wieder dein
Arbeitsordner, also tut `DIR` dort genau dasselbe wie bei dir. Das tut es auch weiterhin, wenn das
Spiel in Lektion dreißig in eine einzige Datei gepackt wird, wenn der Arbeitsordner von jemand
anderem ganz anders heißt und auf einem Rechner, den du nie sehen wirst. Ein Name, der mit
`Singe/` anfängt, ist der Ordner der Engine und braucht nie `DIR`; jeder Name, der dir gehört,
braucht es.
[#_games_dat]
=== games.dat
Mit `games.dat` stellt sich ein Spiel vor. Das Menü liest die `games.dat` jedes Spielordners neben
sich und listet auf, was es findet. Keine `games.dat`, kein Eintrag: Dein Ordner ist unsichtbar,
egal wie gut das Spiel darin ist.
Leg in deinem Ordner `rocks` eine neue Datei namens `games.dat` an. Das ist die kleinste, die
funktioniert:
[source,lua]
----
GAMES = {
{
TITLE = "Rocks",
SCRIPT = "rocks/rocks.singe"
}
}
----
Das ist Lua, und es ist eine Tabelle von der Art, die du in Lektion sechs kennengelernt hast:
`GAMES` ist eine Liste, und jedes Ding in der Liste ist ein Datensatz, der ein Spiel beschreibt.
Ein Ordner kann mehrere Spiele enthalten -- darum ist es eine Liste -- aber deiner enthält eins.
`SCRIPT` ist das Feld, das die Engine braucht, um das Spiel zu starten, und `TITLE` das, das das
Menü braucht, um es aufzulisten. Der Pfad des Skripts zählt ab dem Ordner *über* der `games.dat`, nicht ab dem Ordner, in dem die `games.dat` liegt. Eine `games.dat` in
`rocks` nennt ihr eigenes Skript also `rocks/rocks.singe`. Das sieht aus, als stünde `rocks`
zweimal da, und es ist richtig so. Jeder andere Pfad in der Datei funktioniert genauso. Machst du
es falsch, beschwert sich nichts, wenn du den Namen tippst: Der Eintrag passt einfach nie zu dem
Skript, das du gestartet hast.
`TITLE` ist der Name für Menschen vom Anfang dieser Lektion. Ihn zeigt das Menü an, und Spiele
werden nach Titel sortiert aufgelistet, also bestimmt ein Titel auch, wo dein Spiel in der Liste
steht.
[#_telling_the_menu_more]
=== Dem Menü mehr erzählen
Das Menü hat neben der Liste eine ganze Fläche zu füllen, und es füllt sie aus dem Rest des
Eintrags:
[source,lua]
----
GAMES = {
{
TITLE = "Rocks",
SCRIPT = "rocks/rocks.singe",
CANVAS_X = 720,
CANVAS_Y = 480,
YEAR = 2026,
GENRE = "Shooter",
DEVELOPER = "Your Name Here",
PUBLISHER = "Your Name Here",
DESCRIPTION = "Shoot the rocks before they reach your ship.",
CABINET = "rocks/cabinet.png",
MARQUEE = "rocks/marquee.png"
}
}
----
`CABINET` und `MARQUEE` sind gewöhnliche Bilddateien in deinem Spielordner, in jedem Format, das
Singe lesen kann. Das Automaten-Artwork ist das große Bild neben der Liste, und das Marquee ist der
Streifen darüber. Mach sie so groß, wie du willst; das Menü passt sie in ihre Flächen ein.
Du brauchst kein Zeichenprogramm, um sie zu machen, denn dein Spiel kann sein eigenes Bild
aufnehmen. Starte es, und wenn der Bildschirm so aussieht, wie die Welt ihn sehen soll, drück F12
(oder F11, weil manche Desktops F12 für sich behalten). Singe speichert ein Bild des Fensters im
Datenordner deines Spiels, `data/rocks`, mit dem Namen `singe000.png`, dann `singe001.png` und so
weiter. Nimm eins vom Titelbildschirm auf und eins mitten im Gefecht. Kopier sie in deinen Ordner
`rocks` und benenn sie um: Das Gefecht ist `cabinet.png`, der Titel ist `marquee.png`.
Ein echtes Marquee ist ein breiter Streifen mit dem Namen des Spiels in großen Buchstaben, und ein
ganzer Bildschirm, der hineingequetscht wird, sieht aus wie eine Briefmarke. Bis du etwas
Besseres hast, reicht es. Lass `MARQUEE` weg, und das Menü setzt stattdessen das eigene Ersatzbild
der Engine dorthin, ein rosa Schachbrett mit der Aufschrift MISSING, und das ist schlimmer.
`YEAR`, `GENRE`, `DEVELOPER`, `PUBLISHER` und `DESCRIPTION` sind der Text darunter, und die
Engine sieht sie sich nie an. Sie sind für den Menschen, der das Menü liest, und darum geht es ja.
`CANVAS_X` und `CANVAS_Y` sind die Größe der Welt deines Spiels, und 720 mal 480 ist der
Standard -- dieselbe Welt, in der du seit Lektion eins zeichnest. Setz sie nur, wenn dein Spiel
eine andere Form will. Ein Spiel ohne Feld `VIDEO` ist ein Spiel ohne Video, und das ist deins; ab Lektion
vierzehn fängt dieses Feld an, wichtig zu werden.
Der Abschnitt `games.dat` in der Referenz listet jedes Feld auf, das es gibt, darunter mehrere für
Hardware, die du noch nicht hast. Bei ein paar weiteren lohnt es sich, die Namen jetzt schon
zu kennen.
`REQUIRES` sagt, was ein Spiel über schlichtes 2D hinaus vom Rechner braucht: `"GUI"` für die
Menüs und Seiten aus Lektion zwanzig, `"SCENE"` für das 3D, das in Lektion zweiundzwanzig
anfängt, und noch ein paar mehr. Das Menü liest es, damit es einem Spieler sagen kann „dieser
Rechner kann das nicht starten“, statt das Spiel auf halbem Weg scheitern zu lassen. Rocks ist
schlichtes 2D, und genau das bedeutet es, wenn `REQUIRES` fehlt, also lass es weg. Die Liste musst
du selten selbst ausarbeiten, denn Singe merkt, was ein Spiel beim Laufen benutzt, und sagt es,
wenn der Eintrag es nicht aufführt. Ab Lektion zwanzig wirst du Zeilen wie diese sehen, egal ob das
Spiel überhaupt eine `games.dat` hat:
----
Warning: this game uses GUI, which its games.dat does not list in REQUIRES.
----
Das ist ein Rat, kein Fehler; das Spiel läuft so oder so. Die Abhilfe ist eine Zeile im Eintrag,
`REQUIRES = { "GUI" },`, und wenn du in Lektion dreißig ein Spiel packst, wird die Liste für dich
geschrieben, falls der Eintrag keine hat. Sein Partner `TIER`, eine Zahl von 0 bis 3, sagt, wie
viel Rechner ein Spiel braucht, und kann warten, bis du ein Spiel schreibst, das schwer genug ist,
dass es darauf ankommt.
`GAME_ID` ist eine eindeutige Kennung für dein Spiel, die die Online-Bestenlisten in Lektion
achtundzwanzig brauchen und sonst nichts. Und `RESOLUTION_X` und `RESOLUTION_Y` verlangen eine
bestimmte Fenstergröße. Ignorier den Rest, bis dich etwas danach suchen lässt.
[#_running_it_from_the_menu]
=== Aus dem Menü starten
Starte `Menu.sh` -- unter Windows `Menu.bat` -- aus deinem Arbeitsordner. Es findet das neueste
Singe daneben und startet das Menü.
Dein Spiel steht in der Liste, mit seinen Grafiken daneben und seiner Beschreibung darunter. Es ist
das Einzige in der Liste, weil `rocks` der einzige Ordner ist, dem du eine `games.dat` gegeben
hast; `hello`, `dodge` und der Rest sind für das Menü keine Spiele. Hoch und runter bewegen dich um
ein Spiel, links und rechts um eine Seite, und Start oder ein Feuerknopf spielt das ausgewählte.
Beende das Spiel mit Esc, und du bist zurück im Menü, beim selben Spiel, bereit für die nächste
Runde. Das Menü merkt sich auch zwischen zwei Starts, bei welchem Spiel du warst.
Mehr gehört, was die Engine angeht, zum Ausliefern eines Spiels nicht. Ein Spieler kopiert deinen
Ordner neben Singe, und es erscheint.
Den Namen zu tippen funktioniert immer noch, und zwar genauso:
----
Singe rocks
----
Wenn du ein Skript so von Hand startest, sucht Singe eine `games.dat` daneben -- und in bis zu drei
Ordnern darüber -- und wenn ein Eintrag das Skript nennt, das du gestartet hast, gelten die
Einstellungen dieses Eintrags dafür. Ein Spiel spielt sich also aus dem Terminal genauso wie aus
dem Menü, und das heißt, du kannst weiterarbeiten wie bisher, mit `-R` und einem Texteditor, und
dich trotzdem darauf verlassen, was ein Spieler bekommt. Es schreibt auch an dieselbe Stelle: Egal
auf welchem Weg gestartet, der Spielstand deines Spiels ist der in `data/rocks`.
[#_later_one_file]
=== Später: eine Datei
In einem Ordner lässt es sich leicht arbeiten, aber er ist etwas umständlich weiterzugeben: ein
Dutzend Dateien, ein Ordner in einem Ordner und ein Spieler, der alles an die falsche Stelle
entpackt. Ein fertiges Singe-Spiel lässt sich in eine einzige `.game`-Datei packen, die die Engine
direkt startet, mit Grafiken, Sounds und allem, und die ein Spieler neben Singe kopiert, und
fertig.
Dafür muss sich an deinem Spiel nichts ändern. Das ist Lektion dreißig, und es lohnt sich, darauf
zu warten: Pack ein Spiel, das nicht fertig ist, und du packst es morgen noch einmal.
[#_what_just_happened_9]
=== Was gerade passiert ist
[source,lua]
----
GAMES = {
{
----
Zwei geschweifte Klammern, weil es eine Liste von Datensätzen ist. Die äußere ist die Liste. Die
innere ist das erste Ding in der Liste, also der Datensatz für ein Spiel. Eine davon falsch zu
setzen ist der übliche Fehler, und Lua sagt dir die Zeile.
[source,lua]
----
SCRIPT = "rocks/rocks.singe",
----
Gezählt ab dem Ordner über der `games.dat`, und das ist dein Arbeitsordner. Es ist derselbe Name,
den du nach `Singe` tippen würdest, um das Skript von Hand zu starten, und so merkst du ihn dir am
leichtesten.
Das Komma am Ende der Zeile trennt ein Feld vom nächsten, genau wie in Lektion sechs. Nach dem
letzten Feld eines Datensatzes steht kein Komma -- Lua stört es nicht, wenn eins dasteht, aber
lass es weg, dann sieht deine Datei aus wie die von allen anderen. Ein Komma, das mittendrin
fehlt, ist eine andere Sache, und das ist der Fehler dieser Lektion.
[source,lua]
----
CABINET = "rocks/cabinet.png",
----
Ein Bild, das das Menü lädt und nicht dein Spiel, also wird es so benannt wie `SCRIPT`, ab dem
Ordner darüber, und nicht mit `DIR`. `DIR` gehört deinem Skript, während es läuft, und das Menü und
die Engine lesen `games.dat`, bevor dein Skript gestartet ist.
[#_try_it_13]
=== Probier es aus
. *Mach bessere Bilder.* Wenn du ein Zeichenprogramm hast, mach eine richtige `marquee.png`:
einen breiten Streifen, auf dem ROCKS in großen Buchstaben steht. Setz sie an die Stelle des
Screenshots und sieh dir das Menü noch einmal an.
. *Gib Dodge eine games.dat.* Schreib eine `games.dat` in deinen Ordner `dodge` aus Lektion sieben
und starte das Menü. Jetzt sind es zwei Spiele, nach Titel sortiert. Dann ändere den `TITLE` von
Rocks in `Asteroid Field` und sieh zu, wie es über Dodge rutscht: Die Liste ist nach Titel
sortiert, nicht nach Ordner.
. *Mach games.dat fürs Menü kaputt.* Lösch eine der schließenden geschweiften Klammern in der
`games.dat` in `rocks` und starte das Menü. Dein Spiel ist aus der Liste verschwunden, und das
Terminal sagt, warum. Setz sie wieder ein.
. *Mach SCRIPT falsch.* Setz `CANVAS_X` auf `1280` und `CANVAS_Y` auf `720` und starte
`Singe rocks`. Das Spielfeld ist breiter. Jetzt ändere `SCRIPT` in `rocks.singe` und starte es
noch einmal. Das Spiel hat wieder seine alte Größe, und nichts hat es dir gesagt: Der Eintrag
nennt nicht mehr das Skript, das du gestartet hast, also gilt er nicht. Setz beides zurück.
. *Finde deinen Spielstand.* Starte das Spiel aus dem Menü, stell einen Highscore auf, kehr ins
Menü zurück und beende das Menü. Starte `Singe rocks` und sieh dir den Titel an. Dann such in
`data/rocks` die Datei, die ihn sich gemerkt hat.
[#_break_it_on_purpose_12]
=== Mach es absichtlich kaputt
Nimm das Komma am Ende der Zeile mit `TITLE` weg, sodass der Anfang der Datei so lautet:
[source,lua]
----
GAMES = {
{
TITLE = "Rocks"
SCRIPT = "rocks/rocks.singe",
----
Starte `Singe rocks`. Das Spiel startet, und das Terminal sagt das hier:
----
Note: rocks/games.dat ignored: [string "rocks/games.dat"]:4: '}' expected (to close '{' at line 2) near 'SCRIPT'
----
Dein Spiel läuft trotzdem, weil ein Spiel, das über seinen Namen gestartet wird, seinen Eintrag
nicht braucht. Es läuft einfach ohne ihn. Starte aber das Menü, und dein Spiel ist weg. War es das
einzige, sagt die Liste `No games yet`, und das Terminal sagt dasselbe auf andere Art:
----
rocks/games.dat: rocks/games.dat:4: '}' expected (to close '{' at line 2) near 'SCRIPT' Skipped.
----
Lies ihn wie jeden anderen Lua-Fehler. Die Datei ist `rocks/games.dat`, und die Zeile ist 4. Aber
Zeile 4 ist die Zeile mit `SCRIPT`, und an der ist nichts falsch. Der Fehler steckt am Ende von
Zeile 3. Nach `TITLE = "Rocks"` hat Lua entweder ein Komma und ein weiteres Feld erwartet oder die
`}`, die den Datensatz schließt, der in Zeile 2 angefangen hat. Stattdessen hat es `SCRIPT`
gefunden, also ist Zeile 4 die Stelle, an der es den Fehler bemerkt hat. Sein Vorschlag zur
Korrektur -- eine `}` -- ist falsch; seine Zeile liegt um eins daneben. Wenn ein Fehler auf den
Anfang einer Zeile zeigt, sieh dir das Ende der Zeile davor an.
Setz das Komma wieder ein und starte das Menü noch einmal.
[#_what_you_learned_13]
=== Was du gelernt hast
* Ein Spiel ist ein Ordner: das Skript, seine Grafiken und eine `games.dat`, und nichts von
außerhalb außer dem eigenen Ordner `Singe` der Engine.
* Der Name des Ordners ist für den Computer, also halte ihn schlicht. Der Name für Menschen ist
`TITLE`.
* Namen, die dein Skript lädt, zählen ab dort, wo Singe gestartet wurde, und darum wird jeder davon
aus `DIR` zusammengebaut.
* `games.dat` ist eine Lua-Datei mit einer Liste `GAMES`, und jeder Eintrag ist ein Spiel.
* Ein Eintrag braucht `TITLE` und `SCRIPT` und sonst nichts; Pfade darin zählen ab dem Ordner über
der `games.dat`.
* `CABINET`, `MARQUEE`, `YEAR`, `GENRE`, `DEVELOPER`, `PUBLISHER` und `DESCRIPTION` sind das, was
das Menü dem Spieler zeigt, und F12 nimmt ein Bild auf, das du für die ersten beiden benutzen
kannst.
* `REQUIRES` sagt dem Menü, was ein Spiel über 2D hinaus braucht, und Singe warnt dich, wenn ein
Spiel etwas benutzt, das sein Eintrag nicht aufführt.
* `Menu.sh` oder `Menu.bat` startet das mitgelieferte Menü, das jeden Spielordner daneben
auflistet, nach Titel sortiert.
* Ein Spiel, das über seinen Namen gestartet wird, bekommt ebenfalls seinen Eintrag aus
`games.dat`, also verhält es sich auf beiden Wegen gleich, und eine `games.dat`, die sich nicht
laden lässt, wird gemeldet und ignoriert.
* Ein fertiges Spiel kann später in eine einzige Datei gepackt werden, in Lektion dreißig.
[#_next_time_13]
=== Beim nächsten Mal
Damit ist Teil zwei fertig: Du hast ein Spiel, mit Grafiken, Sound, Kollision, Punkten und einem
Weg, wie jemand anderes es starten kann. In Teil drei geht es um das, was fast keine andere Engine
kann -- Film als die Welt abspielen, in der das Spiel stattfindet -- und Lektion vierzehn fängt mit
der Disc, dem Frame und dem Warten an.