430 lines
20 KiB
Text
430 lines
20 KiB
Text
[#_lesson_23_models_and_light]
|
|
== Lektion 23: Modelle und Licht
|
|
|
|
image::learn/23-models.png[Die fertige Lektion, 480]
|
|
|
|
Mit Kisten und Kugeln kommst du weit, aber früher oder später will das Spiel einen Drachen. Ein
|
|
*Modell* ist eine Form, die jemand anders in einem Modellierprogramm gebaut hat: Hunderte von
|
|
Dreiecken, eigene Farben und Oberflächen, manchmal ein Skelett und Bewegungen dazu, alles in
|
|
einer Datei. Eins zu laden sind zwei Zeilen. Dass es nach etwas aussieht, ist eine Frage des
|
|
Lichts, und Licht ist der größte Teil dieser Lektion.
|
|
|
|
Du brauchst keine Grafiken und keine Downloads. Als du Singe zum allerersten Mal gestartet hast,
|
|
hat es einen Ordner namens `Singe` in deinen Arbeitsordner entpackt, und darin liegen zwei
|
|
Modelle: `DragonModel.glb`, der Drache aus dem eigenen Titelbildschirm der Engine, und
|
|
`SingeText.glb`, der Schriftzug darunter. Diese Lektion benutzt den Drachen.
|
|
|
|
[#_the_scene_around_it]
|
|
=== Die Szene drumherum
|
|
|
|
Ein Modell braucht einen Platz zum Stehen. Leg einen Ordner an, leg darin `dragon.singe` an und
|
|
fang mit einem Boden und einer Kamera an. Wenn du Lektion zweiundzwanzig gemacht hast, ist nichts
|
|
davon neu; wenn nicht, ist es eine eingeschaltete Szene, eine flache Platte zum Draufstehen und
|
|
ein Punkt, von dem aus man schaut.
|
|
|
|
[source,lua]
|
|
----
|
|
sceneEnable(true)
|
|
sceneSetBackground(10, 12, 20)
|
|
sceneSetAmbient(28, 30, 38)
|
|
|
|
local stone = materialNew()
|
|
materialSetColor(stone, 105, 105, 115)
|
|
materialSetRoughness(stone, 0.9)
|
|
|
|
local floor = nodeNew()
|
|
nodeSetMesh(floor, meshBox(24, 0.4, 24), stone)
|
|
nodeSetPosition(floor, 0, -0.2, 0)
|
|
|
|
local camera = nodeNew()
|
|
nodeSetPosition(camera, 0, 2.6, 9)
|
|
nodeLookAt(camera, 0, 1.7, 0)
|
|
cameraSet(camera)
|
|
cameraSetPerspective(55, 0.1, 200)
|
|
|
|
|
|
function onOverlayUpdate()
|
|
return OVERLAY_UPDATED
|
|
end
|
|
----
|
|
|
|
Starte es mit `Singe -R dragon`. Eine graue Platte, leicht von oben gesehen, vor einem
|
|
dunkelblauen Hintergrund. Die Platte ist vierundzwanzig Einheiten breit und etwas weniger als
|
|
eine halbe Einheit dick, und wenn du sie auf `y = -0.2` setzt, liegt ihre Oberseite genau bei
|
|
`y = 0`, und damit ist alles, was du später platzierst, leicht zu durchschauen: Der Boden ist
|
|
null.
|
|
|
|
[#_loading_the_dragon]
|
|
=== Den Drachen laden
|
|
|
|
Zwei Zeilen.
|
|
|
|
[source,lua]
|
|
----
|
|
local dragonModel = modelLoad("Singe/DragonModel.glb")
|
|
local dragon = modelInstance(dragonModel)
|
|
----
|
|
|
|
Setz sie hinter den Boden und speichere. Ein Drache erscheint, und er ist viel zu groß für das
|
|
Bild -- du schaust auf seine Mitte.
|
|
|
|
[#_what_just_happened_19]
|
|
=== Was gerade passiert ist
|
|
|
|
Die beiden Zeilen haben verschiedene Aufgaben, und der Unterschied ist wichtig.
|
|
|
|
`modelLoad` liest die Datei, gibt die Formen und ihre Oberflächen an die Grafikkarte und hält das
|
|
Ganze bereit. Das ist der langsame Aufruf. Mach ihn einmal, wenn das Spiel startet, und nie
|
|
mitten im Spiel.
|
|
|
|
`modelInstance` stellt eine Kopie in die Szene und gibt dir einen Knoten zurück -- einen
|
|
gewöhnlichen Knoten, dieselbe Art, die dir `nodeNew` gibt, den du mit den Aufrufen, die du schon
|
|
kennst, platzierst, drehst, skalierst und löschst. Instanziieren ist der billige Aufruf. Lade
|
|
einen Baum, instanziiere ihn vierzigmal, und du hast einen Wald, mit einer Kopie der Formen auf
|
|
der Grafikkarte und vierzig Stellen, an denen sie gezeichnet werden.
|
|
|
|
Mit dem Modell kommt alles, was in der Datei war: die Formen, die Oberflächen und ihre Farben,
|
|
die Anordnung der Teile, ein Skelett, falls es eins gibt, und Bewegungen, falls es welche gibt.
|
|
Für ein geladenes Modell baust du keine Materialien; es hat seine eigenen mitgebracht.
|
|
|
|
[#_scale_and_the_first_thing_that_goes_wrong]
|
|
=== Größe, und das Erste, was schiefgeht
|
|
|
|
Der Drache ist ungefähr siebeneinhalb Einheiten hoch. Deine Kamera steht neun Einheiten entfernt.
|
|
Darum füllt er das Fenster.
|
|
|
|
[source,lua]
|
|
----
|
|
nodeSetScale(dragon, 0.5)
|
|
----
|
|
|
|
Füg das unter der Zeile mit `modelInstance` ein. Jetzt ist er keine vier Einheiten hoch, steht
|
|
auf dem Boden und passt ins Bild.
|
|
|
|
Das ist kein Detail; es ist das häufigste erste Problem mit einem Modell, und es hat zwei
|
|
Gesichter. Ein Modell, das in Zentimetern gebaut wurde, kommt hundertmal zu groß an, und du
|
|
siehst eine riesige Fläche, die den Bildschirm füllt, was meistens nach „meine Szene ist kaputt“
|
|
aussieht statt nach „ich stecke im Hals des Drachen“. Ein Modell, das in irgendeiner anderen
|
|
Einheit gebaut wurde, kommt so klein an, dass es ein Punkt ist oder vor dem Hintergrund
|
|
unsichtbar, und du nimmst an, dass die Datei nicht geladen wurde.
|
|
|
|
Die Lösung ist immer dieselbe: eine Skalierung auf dem Knoten der Instanz. Nicht auf den Teilen,
|
|
nicht im Modellierprogramm, wenn es sich vermeiden lässt, sondern ein `nodeSetScale` auf dem
|
|
Knoten, den dir `modelInstance` gegeben hat, gewählt, indem du eine Zahl ausprobierst und
|
|
hinsiehst. `0.01` und `100` sind beides völlig gewöhnliche Antworten.
|
|
|
|
Wenn du nicht sagen kannst, wie groß etwas ist, verschaff dir ein Lineal. Stell eine Kiste von
|
|
einer Einheit daneben:
|
|
|
|
[source,lua]
|
|
----
|
|
local ruler = nodeNew()
|
|
nodeSetMesh(ruler, meshBox(1, 1, 1), stone)
|
|
nodeSetPosition(ruler, 3, 0.5, 0)
|
|
----
|
|
|
|
Ein Ding in Kistengröße neben dem Modell sagt dir auf einen Blick, ob das Modell ein Drache oder
|
|
ein Berg ist. Nimm das Lineal wieder heraus, wenn du fertig bist.
|
|
|
|
Wenn du ihn schon platzierst, dreh ihn so, dass er dich ansieht:
|
|
|
|
[source,lua]
|
|
----
|
|
nodeSetRotation(dragon, 0, -60, 0)
|
|
----
|
|
|
|
Der Drache wurde mit Blick entlang des positiven X modelliert, also nach rechts, darum steht er
|
|
am Anfang im Profil. Eine Drehung um Y bringt ihn herum zur Kamera. Die „Vorderseite“ eines
|
|
Modells ist die Richtung, die die Person gewählt hat, die es gebaut hat, und um sie
|
|
herauszufinden, musst du es drehen und hinsehen.
|
|
|
|
[#_light]
|
|
=== Licht
|
|
|
|
Starte, was du jetzt hast. Der Drache ist da, und er ist trostlos: eine flache, trübe Form ohne
|
|
Glanzlichter und ohne Schatten. Das liegt daran, dass das einzige Licht in deiner Szene das
|
|
Umgebungslicht ist, das schwache Leuchten von überall, das `sceneSetAmbient` einstellt.
|
|
|
|
Das ist die allerhäufigste Enttäuschung in 3D, und sie passiert jedem: Das Modell wurde
|
|
einwandfrei geladen, und die Szene ist schwarz oder fast schwarz, weil nichts darauf scheint.
|
|
Bevor du die Datei verdächtigst, stell ein Licht hinein.
|
|
|
|
Es gibt drei Arten, und jede ist ein Knoten, also platzierst du sie und richtest sie aus mit
|
|
`nodeSetPosition` und `nodeLookAt`, wie alles andere.
|
|
|
|
[source,lua]
|
|
----
|
|
local sun = lightNew(LIGHT_DIRECTIONAL)
|
|
nodeSetPosition(sun, -6, 9, 7)
|
|
nodeLookAt(sun, 0, 1.5, 0)
|
|
lightSetColor(sun, 255, 244, 224)
|
|
lightSetIntensity(sun, 1.8)
|
|
lightSetShadow(sun, true)
|
|
----
|
|
|
|
`LIGHT_DIRECTIONAL` ist eine Sonne. Sie wird behandelt, als wäre sie unendlich weit weg, also
|
|
zählt nur die Richtung, in die sie scheint, und alles in der Szene bekommt gleich viel davon.
|
|
Ihre Position ist dazu da, dass `nodeLookAt` sie damit ausrichten kann. Das ist das Licht, das
|
|
einer Szene ihre Hauptform gibt, und die meisten Szenen wollen genau eins.
|
|
|
|
`lightSetIntensity` ist die Helligkeit. Bei einem Richtungslicht macht `1` eine weiße Fläche, die
|
|
ihm direkt zugewandt ist, ganz weiß, also ist `1.8` ein heller Tag. `lightSetColor` färbt es: ein
|
|
leicht warmes Weiß für die Sonne, ein kaltes Blau für Mondlicht und der Abschnitt Licht in der
|
|
Referenz für den Rest.
|
|
|
|
`lightSetShadow(sun, true)` sorgt dafür, dass der Drache auf dem Boden sitzt, statt darüber zu
|
|
schweben. Schatten sind standardmäßig aus, weil jedes Licht, das Schatten wirft, in jedem Frame
|
|
einen weiteren Durchgang über die ganze Szene kostet. Schalte sie für das eine Licht ein, auf das
|
|
es ankommt, und lass sie bei den anderen aus; ein kleiner Rechner wird es dir danken.
|
|
|
|
[source,lua]
|
|
----
|
|
local torch = lightNew(LIGHT_POINT)
|
|
nodeSetPosition(torch, 3.5, 1.2, 3.0)
|
|
lightSetColor(torch, 255, 150, 70)
|
|
lightSetIntensity(torch, 14)
|
|
lightSetRange(torch, 12)
|
|
----
|
|
|
|
`LIGHT_POINT` ist eine Glühbirne. Sie sitzt an einer Stelle, scheint in alle Richtungen und wird
|
|
mit der Entfernung schwächer. Dieses Schwächerwerden ist der Grund, warum die Zahl `14` ist und
|
|
nicht `1`: Die Intensität eines Punktlichts ist seine Helligkeit direkt am Licht, und wenn es ein
|
|
paar Einheiten weit gekommen ist, ist nicht mehr viel übrig. Eine Glühbirne, die einen Raum
|
|
beleuchtet, will meistens etwas im zweistelligen Bereich. Wenn ein Punktlicht scheinbar nichts
|
|
tut, ist das fast immer der Grund.
|
|
|
|
`lightSetRange` sagt, wie weit es reicht, bevor es ganz aufhört, und das verhindert, dass eine
|
|
Lampe in einem Raum den nächsten schwach mitbeleuchtet, und kostet nichts.
|
|
|
|
Die dritte Art ist `LIGHT_SPOT`, ein Kegel, der entlang des eigenen negativen Z des Knotens
|
|
zeigt, mit `lightSetCone` für den Winkel der hellen Mitte und des weichen Rands: eine
|
|
Taschenlampe, ein Scheinwerfer, ein Suchscheinwerfer. Bis zu acht Lichter leuchten gleichzeitig,
|
|
und das ist mehr, als die meisten Szenen brauchen. Wenn du mehr willst, willst du eigentlich
|
|
weniger.
|
|
|
|
Starte es jetzt. Eine fast weiße Sonne von links, ein warmes orangefarbenes Leuchten von rechts,
|
|
ein Schatten auf der Platte und ein Drache mit einer Vorder- und einer Rückseite.
|
|
|
|
[#_materials_at_the_level_you_need_today]
|
|
=== Materialien, so weit du sie heute brauchst
|
|
|
|
Der Drache hat seine eigenen Materialien mitgebracht, und es sind drei: ein dunkles Metall, ein
|
|
helleres champagnerfarbenes Metall und ein blasses Glas, in das man ein Stückchen hineinsehen
|
|
kann. Die meisten seiner Teile sind aus mehr als einem davon geschnitten. Du musst nichts davon
|
|
anfassen, und meistens solltest du es auch nicht.
|
|
|
|
Wenn du doch ändern willst, wie ein Teil eines Modells aussieht, machst du ein Material wie in
|
|
Lektion zweiundzwanzig und legst es auf das Teil:
|
|
|
|
[source,lua]
|
|
----
|
|
local red = materialNew()
|
|
materialSetColor(red, 220, 50, 40)
|
|
materialSetRoughness(red, 0.4)
|
|
nodeSetMaterial(nodeFind("crest", dragon), red)
|
|
----
|
|
|
|
`nodeFind(name, root)` durchsucht eine Modellinstanz nach einem Teil mit dem Namen, den es in der
|
|
Datei hatte, und gibt seinen Knoten zurück, oder `nil`, wenn es keinen solchen Namen gibt. So
|
|
greifst du in ein Modell hinein: ein Geschützturm, ein Rad, eine Hand, an die man eine Fackel
|
|
hängt, oder der Kamm des Drachen.
|
|
|
|
Der Kamm ist ein Stück aus einem Material, also trägt sein Knoten die Form selbst, und die Zeile
|
|
oben malt ihn neu an. Ein Teil, das aus mehreren Materialien gebaut ist, wird als Knoten mit
|
|
einem Kind pro Stück gespeichert, und ein Material auf dem Elternknoten ändert nichts, was man
|
|
sieht. Wenn ein `nodeSetMaterial` scheinbar nichts tut, ist das der Grund, und `nodeGetChildren`
|
|
zeigt dir, was darunter liegt.
|
|
|
|
Vier Zahlen beschreiben die meisten Oberflächen. Die Farbe, aus `materialSetColor`.
|
|
`materialSetRoughness`, von `0` für einen Spiegel bis `1` für Kreide. `materialSetMetallic`, das
|
|
für fast alles `0` ist -- Farbe, Holz, Plastik, Haut -- und `1` für echtes Metall, und die Werte
|
|
dazwischen braucht man kaum. Und `materialSetEmissive`, das eine Oberfläche in ihrer eigenen
|
|
Farbe leuchten lässt, für Bildschirme, Lampen und glühendes Eisen, ohne etwas drumherum zu
|
|
beleuchten.
|
|
|
|
Eins solltest du über Metall wissen: Eine Metalloberfläche zeigt dir ihre Spiegelungen statt
|
|
einer eigenen Farbe, also sieht sie in einer Szene, in der es nichts zu spiegeln gibt, dunkel und
|
|
stumpf aus. Darum braucht das dunkle Metall des Drachen die Sonne, um lebendig zu werden. Die
|
|
richtige Lösung ist ein Himmel (`sceneSetSky`), der die Szene in ein Panorama hüllt, das hinter
|
|
allem zu sehen ist und sie zugleich beleuchtet; „Das Aussehen des Frames“ in der Referenz
|
|
behandelt das.
|
|
|
|
[#_movement_the_model_brings_with_it]
|
|
=== Bewegung, die das Modell mitbringt
|
|
|
|
Viele Modelle bringen Animationen mit: ein Gehen, ein Rennen, ein Herumstehen, eine Tür, die
|
|
aufgeht. Sie sind in der Datei gespeichert, und eine abzuspielen ist ein einziger Aufruf auf dem
|
|
Wurzelknoten der Instanz.
|
|
|
|
[source,lua]
|
|
----
|
|
animationPlay(dragon, "Walk", true)
|
|
----
|
|
|
|
Das zweite Argument ist der Name des Clips, wie die Datei ihn speichert, oder seine Nummer.
|
|
`true` heißt: endlos in einer Schleife. Zwei weitere, optionale Zahlen setzen die Geschwindigkeit
|
|
und die Überblendung: `animationPlay(hero, "Run", true, 1.2, 0.4)` spielt das Rennen ein Fünftel
|
|
schneller ab und blendet über vier Zehntelsekunden hinein, sodass aus einem Gehen ohne Ruck ein
|
|
Rennen wird. `animationStop`, `animationPause` und `animationIsPlaying` tun, was ihre Namen
|
|
sagen.
|
|
|
|
Diese Zeile kannst du noch nicht ausführen, und hier ist der ehrliche Grund: Der Drache in deinem
|
|
Ordner `Singe` hat keine Animationen. Er ist ein Modell, keine Vorstellung. Nach einem Clip zu
|
|
fragen, der nicht da ist, hält das Spiel mit einem Fehler an, also nimm nie etwas an -- frag:
|
|
|
|
[source,lua]
|
|
----
|
|
local clips = modelGetAnimations(dragonModel)
|
|
|
|
if #clips > 0 then
|
|
animationPlay(dragon, 1, true)
|
|
else
|
|
debugPrint("DragonModel.glb carries no animation clips. The wings are turned by hand.")
|
|
end
|
|
----
|
|
|
|
`modelGetAnimations` gibt eine Tabelle mit den Namen der Clips in der Datei zurück, der Reihe
|
|
nach, also ist `clips[1]` der erste und `#clips` die Anzahl -- die Tabellen-Aufrufe aus Lektion
|
|
sechs, auf einer Tabelle, die die Engine gefüllt hat. Starte es und beobachte die Konsole: Die
|
|
Meldung erscheint, weil diese Anzahl null ist.
|
|
|
|
Gewöhn dir diese Prüfung bei jedem Modell an, das du nicht selbst gemacht hast. Sie kostet drei
|
|
Zeilen und macht aus „das Spiel ist beim Start gestorben“ einen Satz im Log.
|
|
|
|
[#_moving_the_parts_yourself]
|
|
=== Die Teile selbst bewegen
|
|
|
|
Ein Modell ohne Animationen ist keine Statue. Seine Teile sind Knoten, und Knoten drehen sich.
|
|
|
|
[source,lua]
|
|
----
|
|
local wingLeft = nodeFind("wingL", dragon)
|
|
local wingRight = nodeFind("wingR", dragon)
|
|
----
|
|
|
|
[source,lua]
|
|
----
|
|
function onOverlayUpdate()
|
|
local flap = math.sin(singeGetTicks() / 260) * 22
|
|
|
|
nodeSetRotation(wingLeft, flap, 0, 0)
|
|
nodeSetRotation(wingRight, -flap, 0, 0)
|
|
nodeRotate(dragon, 0, 0.25, 0)
|
|
|
|
overlayClear()
|
|
overlayPrint(2, 2, "Singe/DragonModel.glb")
|
|
overlayPrint(2, 4, "animation clips in the file: " .. #clips)
|
|
return OVERLAY_UPDATED
|
|
end
|
|
----
|
|
|
|
Speichere. Die Flügel schlagen, der Drache dreht sich langsam auf der Stelle, und der Schatten
|
|
auf dem Boden schlägt mit.
|
|
|
|
`singeGetTicks` ist die Anzahl der Millisekunden, seit die Engine gestartet ist, also steigt sie
|
|
für immer gleichmäßig an. `math.sin` nimmt diese steigende Zahl und gibt einen Wert zurück, der
|
|
gleichmäßig von `-1` bis `1` und wieder zurück gleitet, ein voller Flügelschlag ungefähr alle
|
|
anderthalb Sekunden bei diesem Teiler. Die Multiplikation mit `22` macht daraus einen Winkel von
|
|
zweiundzwanzig Grad in jede Richtung. Teilst du durch eine größere Zahl, wird der Schlag
|
|
langsamer; multiplizierst du mit einer größeren Zahl, wird er weiter. Diese eine Zeile ist die
|
|
billigste Animation, die es gibt, und es lohnt sich, sie in der Tasche zu behalten.
|
|
|
|
Die Flügel drehen sich um ihre eigene X-Achse, und zwar um die Schulter statt um die Mitte des
|
|
Tiers, weil der, der die Datei gebaut hat, den Knoten jedes Teils dorthin gesetzt hat, wo das
|
|
Gelenk hingehört. Ein Modell, das nicht so gebaut wurde, schwenkt seine Teile stattdessen um die
|
|
Mitte des Modells, und die Lösung ist, den Knoten des Teils zu verschieben; der Abschnitt Knoten
|
|
in der Referenz behandelt das.
|
|
|
|
Achte darauf, dass das Drehen des ganzen Drachen dem Drehen der Flügel nicht in die Quere kommt.
|
|
Die Teile hängen unter der Wurzel der Instanz, also nimmt eine Drehung der Wurzel alles mit, und
|
|
die eigene Drehung jedes Flügels kommt obendrauf. Diese Anordnung -- ein Ding in einem Ding in
|
|
einem Ding, und jedes bewegt sich in der Welt seines Elternknotens -- ist das, wofür ein
|
|
Szenenbaum da ist.
|
|
|
|
[#_your_own_models]
|
|
=== Deine eigenen Modelle
|
|
|
|
Wenn du eigene machst, exportiere aus Blender -- oder Maya, 3ds Max oder irgendetwas anderem
|
|
Modernen -- als *glTF 2.0 binary*, also als `.glb`-Datei. Das ist die Standardwahl von Blender
|
|
und das einzige Modellformat, das Singe liest.
|
|
|
|
Die eine Regel ist, dass die Datei in sich geschlossen sein muss: die Formen, die Bilder darauf
|
|
und die Bewegungen, alles in der einen `.glb`. Eine Datei, die auf eine `.bin` oder eine `.png`
|
|
daneben verweist, wird abgelehnt, mit einer Meldung, die nennt, was sie wollte. Der Exporter von
|
|
Blender macht mit seinen Standardeinstellungen das Richtige.
|
|
|
|
Nicht alles, was du bauen kannst, übersteht die Reise. Der Abschnitt „Modelle und Animation“ in der
|
|
Referenz, im Kapitel 3D-Szenen, ist die Liste: was gelesen wird, was stillschweigend ignoriert
|
|
wird, und die Grenzen (ein Skelett darf zum Beispiel bis zu hundertachtundzwanzig Gelenke haben).
|
|
Lies ihn einmal, bevor du ein Wochenende in ein Modell steckst, nicht danach.
|
|
|
|
[#_try_it_23]
|
|
=== Probier es aus
|
|
|
|
. *Mach das Licht aus.* Kommentier den ganzen `sun`-Block aus, indem du `--` an den Anfang jeder
|
|
Zeile setzt, und starte es. Dann mach dasselbe mit `sceneSetAmbient`. Merk dir, wie das
|
|
aussieht, denn eines Tages passiert es, ohne dass du es wolltest.
|
|
. *Beweg die Sonne.* Ändere `nodeLookAt(sun, 0, 1.5, 0)` in `nodeLookAt(sun, 0, 20, 0)`, sodass
|
|
die Sonne über den Kopf des Drachen zielt. Sieh zu, wie der Schatten und die Glanzlichter
|
|
verschwinden. Ein Licht auszurichten ist keine Verzierung.
|
|
. *Heiz die Fackel an.* Erhöh die Intensität des Punktlichts von `14` auf `60` und senk seine
|
|
Reichweite auf `4`. Dann setz es auf `(0, 6, 0)`, über den Drachen. Ein Punktlicht ist eine
|
|
Glühbirne, und du entscheidest, wo die Glühbirne hängt.
|
|
. *Instanziiere ihn zweimal.* Ruf `modelInstance(dragonModel)` ein zweites Mal auf, bewahr das
|
|
Ergebnis in einer anderen Variablen auf, skalier es und setz es auf `(-4, 0, -3)`. Einmal
|
|
geladen, zwei Drachen. Dann versuch stattdessen, das Modell zweimal zu laden, und versteh,
|
|
warum du das nicht tun solltest.
|
|
. *Finde ein anderes Teil.* Die Teile des Drachen heißen `head`, `jaw`, `neck`, `tail`, `body`, `crest`,
|
|
`hornL`, `hornR`, `wingL`, `wingR`, `foreLegL`, `foreLegR`, `hindLegL` und `hindLegR`. Lass den
|
|
Schwanz mit seinem eigenen `math.sin` in einer anderen Geschwindigkeit schwingen als die
|
|
Flügel. Dann probier `nodeFind("wings", dragon)` und sieh, was ein falscher Name anrichtet,
|
|
wenn du das `nil` direkt an `nodeSetRotation` weitergibst.
|
|
|
|
[#_break_it_on_purpose_22]
|
|
=== Mach es absichtlich kaputt
|
|
|
|
Ändere den Dateinamen in `Singe/Dragonmodel.glb` -- ein einziger Großbuchstabe Unterschied -- und
|
|
starte es. Singe hält an, bevor das Spiel startet:
|
|
|
|
----
|
|
13:modelLoad: Unable to read Singe/Dragonmodel.glb.
|
|
----
|
|
|
|
Die Zeile, der Aufruf und die Beschwerde, in dieser Reihenfolge. „Unable to read“ heißt, dass die
|
|
Engine nachgesehen und nichts gefunden hat, und das ist fast immer eins von drei Dingen: Der Name
|
|
ist falsch geschrieben, die Groß- und Kleinschreibung stimmt nicht, oder die Datei liegt nicht
|
|
da, wo du gesagt hast. Unter Windows wäre die Groß- und Kleinschreibung verziehen worden, und dein
|
|
Spiel würde dann bei allen unter Linux scheitern, und das ist ein schlimmerer Fehler als dieser.
|
|
Tipp Namen genau.
|
|
|
|
Dieselbe Meldung mit einem richtigen Dateinamen heißt, dass die Datei nicht dort liegt, wo die
|
|
Engine nachgesehen hat. Der Ordner `Singe` wird in dem Ordner angelegt, *aus* dem du die Engine startest, also
|
|
zeigt `Singe/DragonModel.glb` ebenfalls woandershin, wenn du Singe von woanders gestartet hast.
|
|
|
|
[#_what_you_learned_23]
|
|
=== Was du gelernt hast
|
|
|
|
* `modelLoad` liest eine `.glb` einmal; `modelInstance` setzt so oft, wie du willst, Kopien davon
|
|
in die Szene.
|
|
* Die Wurzel einer Instanz ist ein gewöhnlicher Knoten: Platzier, dreh, skalier und lösch ihn wie
|
|
jeden anderen.
|
|
* Ein Modell bringt seine eigenen Formen und Materialien mit.
|
|
* Ein Modell kommt in der Größe an, die sein Autor benutzt hat, und darum gehört ein
|
|
`nodeSetScale` auf der Instanz zum Platzieren dazu.
|
|
* Eine Szene ohne Licht ist schwarz oder fast schwarz, und das ist das Erste, was du prüfst, wenn
|
|
ein Modell falsch aussieht.
|
|
* Ein Richtungslicht ist eine Sonne, ein Punktlicht ist eine Glühbirne, die mit der Entfernung
|
|
schwächer wird und eine viel größere Intensität braucht, und ein Spotlicht ist ein Kegel.
|
|
* Schatten sind aus, bis `lightSetShadow` sie einschaltet, und jeder kostet etwas.
|
|
* `nodeFind` erreicht ein Teil eines Modells über seinen Namen aus der Datei.
|
|
* `modelGetAnimations` sagt, was ein Modell abspielen kann, und nach einem Clip zu fragen, der
|
|
nicht da ist, ist ein Fehler.
|
|
* Ein Modell ohne Animationen lässt sich trotzdem Teil für Teil bewegen.
|
|
|
|
[#_next_time_23]
|
|
=== Beim nächsten Mal
|
|
|
|
Alles in dieser Szene bewegt sich, weil du es ihm gesagt hast, Frame für Frame. Die nächste
|
|
Lektion gibt diese Arbeit an eine Physik-Engine ab: Kisten, die fallen, rollen, sich stapeln und
|
|
sich gegenseitig umwerfen, ohne dass du eine Zeile Rechnerei schreibst -- und die schwierigere
|
|
Frage, wann du sie das tun lassen solltest. Das ist Lektion vierundzwanzig.
|