422 lines
18 KiB
Text
422 lines
18 KiB
Text
[#_lesson_22_into_3d]
|
|
== Lektion 22: Hinein in 3D
|
|
|
|
image::learn/22-3d.png[Die fertige Lektion, 480]
|
|
|
|
Alles, was du bisher gezeichnet hast, war flach. Sprites, Text, Kästen und Linien leben alle auf
|
|
dem Overlay, einer Glasscheibe vorn im Fenster mit denselben Koordinaten wie in Lektion eins.
|
|
Diese Lektion stellt eine Welt hinter diese Scheibe: einen Ort mit Tiefe, an dem ein Ding näher
|
|
oder weiter weg sein kann und an dem eine Kamera entscheidet, was du davon siehst.
|
|
|
|
Am Ende hast du eine Kiste, die im Raum schwebt und die du mit den Pfeiltasten drehen kannst, und
|
|
die Zahlen für ihre Winkel stehen in der Ecke, ausgegeben mit demselben `overlayPrint`, das du an
|
|
deinem allerersten Tag benutzt hast. Dieser letzte Teil ist wichtiger, als er klingt, und es gibt
|
|
einen Abschnitt dazu.
|
|
|
|
[#_a_scene_a_box_and_a_light]
|
|
=== Eine Szene, eine Kiste und ein Licht
|
|
|
|
Leg einen Ordner an, leg eine Datei namens `spin.singe` hinein und tipp das ein.
|
|
|
|
[source,lua]
|
|
----
|
|
sceneEnable(true)
|
|
sceneSetBackground(18, 20, 34)
|
|
sceneSetAmbient(35, 35, 45)
|
|
|
|
local paint = materialNew()
|
|
materialSetColor(paint, 215, 95, 60)
|
|
materialSetRoughness(paint, 0.6)
|
|
|
|
local box = nodeNew()
|
|
nodeSetMesh(box, meshBox(2, 2, 2), paint)
|
|
|
|
local sun = lightNew(LIGHT_DIRECTIONAL)
|
|
nodeSetPosition(sun, 4, 6, 5)
|
|
nodeLookAt(sun, 0, 0, 0)
|
|
|
|
|
|
function onOverlayUpdate()
|
|
nodeRotate(box, 0, 0.5, 0)
|
|
return OVERLAY_UPDATED
|
|
end
|
|
----
|
|
|
|
Starte es mit `Singe -R spin`. Eine orangefarbene Kiste dreht sich langsam vor einem
|
|
dunkelblauen Hintergrund, beleuchtet von oben rechts, mit einer hellen Seite, während die anderen
|
|
in den Schatten abfallen.
|
|
|
|
In diesem Skript gibt es keine Kamera. Singe hat dich auf `(0, 0, 5)` gesetzt, mit Blick auf die
|
|
Mitte der Welt, weil eine Szene ohne Kamera ja von irgendwoher gesehen werden muss. Gleich
|
|
ersetzt du das durch deine eigene Kamera.
|
|
|
|
Wenn das Fenster schwarz bleibt, lies die Konsole. Auf einem Rechner, dessen Grafik keine
|
|
3D-Szene zeichnen kann, hält `sceneEnable` das Spiel mit einer Meldung an, die das sagt, und
|
|
dagegen kannst du nichts tun, außer die Lektion woanders zu starten. 2D-Spiele funktionieren auf
|
|
so einem Rechner weiter; 3D nicht.
|
|
|
|
[#_what_just_happened_18]
|
|
=== Was gerade passiert ist
|
|
|
|
Die drei Aufrufe ganz oben richten die Welt als Ganzes ein.
|
|
|
|
[source,lua]
|
|
----
|
|
sceneEnable(true)
|
|
sceneSetBackground(18, 20, 34)
|
|
sceneSetAmbient(35, 35, 45)
|
|
----
|
|
|
|
`sceneEnable(true)` schaltet die 3D-Ebene ein. Das ist der eine Aufruf, den jedes 3D-Spiel
|
|
macht, und bis du ihn machst, wird nichts gezeichnet, was du in der Szene baust.
|
|
|
|
`sceneSetBackground` ist die Farbe, mit der die Szene in jedem Frame gelöscht wird, Rot, Grün und
|
|
Blau von `0` bis `255`, so wie Farben seit Lektion neun funktionieren. Du kannst ihm eine vierte
|
|
Zahl für die Transparenz geben, und so stellt ein Spiel, das Video abspielt, ein 3D-Objekt vor
|
|
den Film. Lässt du sie weg, ist der Hintergrund undurchsichtig.
|
|
|
|
`sceneSetAmbient` ist ein bisschen Licht, das aus allen Richtungen gleichzeitig kommt. Seine
|
|
Aufgabe ist, die Seiten, die keine Lampe erreicht, davor zu bewahren, rein schwarz zu sein. Dreh
|
|
es später auf `0, 0, 0` herunter und sieh, wie hart das Bild wird.
|
|
|
|
[source,lua]
|
|
----
|
|
local paint = materialNew()
|
|
materialSetColor(paint, 215, 95, 60)
|
|
materialSetRoughness(paint, 0.6)
|
|
----
|
|
|
|
Ein *Material* ist, wie eine Oberfläche aussieht: ihre Farbe, wie glänzend sie ist, ob ein Bild
|
|
darauf ist. `materialNew` erzeugt eins und gibt eine Zahl zurück, die dafür steht, dieselbe Art
|
|
von Handle, die dir `spriteLoad` in Lektion neun gegeben hat. Bewahr es in einer Variablen auf,
|
|
denn jeder spätere Aufruf braucht es.
|
|
|
|
`materialSetRoughness` reicht von `0`, einem Spiegel mit einem winzigen, harten Glanzpunkt, bis
|
|
`1`, einer kreidigen, matten Oberfläche ganz ohne Glanzpunkt. `0.6` ist Farbe auf Holz. Es gibt
|
|
noch ein Dutzend weitere `materialSet`-Aufrufe, und die nächste Lektion benutzt ein paar davon;
|
|
der Abschnitt Material in der Referenz listet sie alle auf.
|
|
|
|
[source,lua]
|
|
----
|
|
local box = nodeNew()
|
|
nodeSetMesh(box, meshBox(2, 2, 2), paint)
|
|
----
|
|
|
|
Das ist der wichtige Teil. Ein *Knoten* ist ein Ort in der Welt. Er hat eine Position, eine
|
|
Drehung und eine Skalierung, und für sich allein zeichnet er überhaupt nichts: Er ist eine
|
|
Stelle, an die man Dinge hängen kann. Alles in einer 3D-Szene ist ein Knoten, auch die Lichter
|
|
und die Kamera.
|
|
|
|
`nodeNew` erzeugt einen leeren Knoten in der Mitte der Welt. `nodeSetMesh` hängt zwei Dinge
|
|
daran: ein *Mesh*, das ist die Form, und das Material, das ist, wie diese Form aussieht.
|
|
`meshBox(2, 2, 2)` baut eine Kiste, zwei Einheiten breit, zwei hoch und zwei tief, und gibt ein
|
|
Handle darauf zurück, so wie `materialNew` es getan hat.
|
|
|
|
Meshes und Materialien lohnt es sich zu teilen. Ein `meshBox`-Handle kann von hundert Knoten
|
|
benutzt werden, und so zeichnest du hundert Kisten billig. Hundert einzelne Kisten zu bauen, die
|
|
zufällig gleich groß sind, ist der Anfängerweg, eine Szene langsam zu machen.
|
|
|
|
[source,lua]
|
|
----
|
|
local sun = lightNew(LIGHT_DIRECTIONAL)
|
|
nodeSetPosition(sun, 4, 6, 5)
|
|
nodeLookAt(sun, 0, 0, 0)
|
|
----
|
|
|
|
`lightNew` erzeugt ebenfalls einen Knoten -- einen, der ein Licht trägt -- und gibt sein
|
|
Knoten-Handle zurück, also stellst du es mit denselben Aufrufen auf und richtest es aus, die du
|
|
für alles andere benutzt. `LIGHT_DIRECTIONAL` ist eine Sonne: Sie ist so weit weg, dass nur die
|
|
Richtung zählt, in die sie scheint, und darum ist die Position hier nur insofern wichtig, als
|
|
`nodeLookAt` aus ihr die Richtung ausrechnet.
|
|
|
|
Nimm diese drei Zeilen heraus und starte es noch einmal. Die Kiste wird fast schwarz, weil nur
|
|
noch das schwache Umgebungslicht übrig ist. Um Licht geht es in der ganzen Lektion dreiundzwanzig,
|
|
und das hier ist ein erster Vorgeschmack auf die häufigste Enttäuschung in 3D: Das Modell wurde
|
|
einwandfrei geladen, und die Szene ist schwarz, weil nichts darauf scheint.
|
|
|
|
[source,lua]
|
|
----
|
|
nodeRotate(box, 0, 0.5, 0)
|
|
----
|
|
|
|
`nodeRotate` dreht einen Knoten um so und so viele Grad um jede seiner eigenen drei Achsen,
|
|
zusätzlich zu der Drehung, die er schon hatte. Ein halbes Grad pro Frame, bei etwa sechzig Frames
|
|
pro Sekunde, ist eine Umdrehung alle zwölf Sekunden. Das ist derselbe Trick wie in Lektion zwei:
|
|
Eine kleine Änderung, in jedem Frame angewandt, wirkt wie Bewegung.
|
|
|
|
[#_which_way_is_which]
|
|
=== Welche Richtung ist welche
|
|
|
|
Drei Zahlen beschreiben einen Ort in der Szene, und du musst wissen, was jede davon bedeutet,
|
|
bevor du irgendetwas irgendwohin stellen kannst.
|
|
|
|
* *X* geht nach rechts. Größeres X ist weiter rechts.
|
|
* *Y* geht nach oben. Größeres Y ist höher. Der Boden einer Szene ist meistens `y = 0`.
|
|
* *Z* geht auf dich zu, aus dem Bildschirm heraus. Größeres Z ist näher an der Standardkamera,
|
|
und *negatives Z ist die Richtung, in die die Kamera schaut*.
|
|
|
|
Das Letzte erwischt jeden. Dinge, die vor der Kamera sein sollen, kommen auf negatives Z. Das ist
|
|
keine Marotte von Singe: glTF und Blender benutzen dieselbe Anordnung, also kommt ein Modell, das
|
|
aus Blender exportiert wurde, so ausgerichtet an, wie Singe es erwartet.
|
|
|
|
Glaub mir nichts davon einfach so. Füg diese Zeile nach der Zeile mit `nodeSetMesh` ein,
|
|
speichere und sieh hin:
|
|
|
|
[source,lua]
|
|
----
|
|
nodeSetPosition(box, 3, 1, 0)
|
|
----
|
|
|
|
Die Kiste springt nach rechts und nach oben. `nodeSetPosition` setzt einen Knoten an eine genaue
|
|
Stelle und ersetzt die Position, die er vorher hatte. Passend dazu gibt es
|
|
`nodeMove(node, dx, dy, dz)`, das einen Knoten stattdessen um einen Betrag verschiebt, entlang
|
|
seiner eigenen Achsen, also fährt `nodeMove(ship, 0, 0, -0.1)` ein Schiff vorwärts, egal in
|
|
welche Richtung es gerade zeigt.
|
|
|
|
Probier als Nächstes `nodeSetPosition(box, 0, 0, -20)`. Die Kiste schrumpft in die Ferne, und
|
|
das ist negatives Z, das seine Arbeit macht. Dann probier `(0, 0, 20)`: Die Kiste verschwindet,
|
|
weil du sie hinter die Kamera gestellt hast.
|
|
|
|
Zwei weitere Aufrufe machen den Satz komplett.
|
|
|
|
[source,lua]
|
|
----
|
|
nodeSetRotation(box, 0, 45, 0)
|
|
nodeSetScale(box, 0.5)
|
|
----
|
|
|
|
`nodeSetRotation` setzt die Drehung direkt in Grad, während `nodeRotate` etwas dazuzählt. Die
|
|
drei Zahlen sind um X, um Y und um Z: Eine Drehung um Y macht ein Mensch, der auf dem Boden steht
|
|
und sich in eine andere Richtung dreht, und die wirst du am häufigsten benutzen. `nodeSetScale`
|
|
macht den Knoten größer oder kleiner, entweder mit einer Zahl für alle drei Achsen oder mit
|
|
dreien, einer für jede.
|
|
|
|
Nimm beide Experimente wieder heraus, bevor du weitermachst. Der Rest der Lektion geht von dem
|
|
Skript aus, wie du es zuerst getippt hast.
|
|
|
|
[#_your_own_camera]
|
|
=== Deine eigene Kamera
|
|
|
|
Die Standardansicht ist eine Höflichkeit, keine Funktion. Stell eine Kamera hinein.
|
|
|
|
[source,lua]
|
|
----
|
|
local camera = nodeNew()
|
|
nodeSetPosition(camera, 0, 2, 7)
|
|
nodeLookAt(camera, 0, 0, 0)
|
|
cameraSet(camera)
|
|
cameraSetPerspective(60, 0.1, 100)
|
|
----
|
|
|
|
Setz das unter das Licht, vor dein `onOverlayUpdate`, und starte es. Jetzt schaust du leicht von
|
|
oben auf die Kiste, aus zwei Einheiten Höhe und sieben Einheiten Abstand.
|
|
|
|
Eine Kamera ist ein Knoten wie jeder andere. `cameraSet` sagt der Engine, aus welchem Knoten sie
|
|
herausschauen soll, und die Szene wird von der Position dieses Knotens aus gezeichnet, mit Blick
|
|
entlang seines eigenen negativen Z. Eine Kamera von Hand mit `nodeSetRotation` auszurichten wäre
|
|
eine Qual, also erledigt `nodeLookAt` das für dich: Gib ihm einen Punkt in der Welt, und es dreht
|
|
den Knoten so, dass er dorthin schaut.
|
|
|
|
`cameraSetPerspective` ist das Objektiv. Die erste Zahl ist das Sichtfeld in Grad, von oben nach
|
|
unten: wie viel von der Welt in die Höhe des Fensters gequetscht wird. Eine kleine Zahl wie `35`
|
|
ist ein Teleobjektiv, das alles flach drückt; eine große wie `90` ist ein Weitwinkel, der den
|
|
Raum riesig aussehen lässt und die Ecken ausbeult. `60` ist ein angenehmer Standardwert. Die
|
|
anderen beiden Zahlen sind die kleinste und die größte Entfernung, die gezeichnet wird, und Dinge
|
|
außerhalb dieses Bereichs werden überhaupt nicht gezeichnet.
|
|
|
|
Weil die Kamera ein gewöhnlicher Knoten ist, funktioniert alles, was du über Knoten gelernt hast,
|
|
auch bei ihr. Später hängst du eine Kamera unter ein fahrendes Auto und bekommst eine
|
|
Verfolgerkamera geschenkt.
|
|
|
|
[#_turning_it_with_the_arrow_keys]
|
|
=== Mit den Pfeiltasten drehen
|
|
|
|
Das Koordinatensystem von einer Buchseite abzulesen ist ein schlechter Weg, es zu lernen. Etwas
|
|
darin herumzubewegen ist ein guter. Ersetz dein `onOverlayUpdate` hiermit und füg die beiden
|
|
neuen Callbacks und die vier Variablen darüber ein.
|
|
|
|
[source,lua]
|
|
----
|
|
local turnLeft = false
|
|
local turnRight = false
|
|
local tiltUp = false
|
|
local tiltDown = false
|
|
----
|
|
|
|
Die kommen ganz oben in die Datei, über alles andere.
|
|
|
|
[source,lua]
|
|
----
|
|
function onInputPressed(what)
|
|
if what == SWITCH_LEFT then
|
|
turnLeft = true
|
|
elseif what == SWITCH_RIGHT then
|
|
turnRight = true
|
|
elseif what == SWITCH_UP then
|
|
tiltUp = true
|
|
elseif what == SWITCH_DOWN then
|
|
tiltDown = true
|
|
end
|
|
end
|
|
|
|
|
|
function onInputReleased(what)
|
|
if what == SWITCH_LEFT then
|
|
turnLeft = false
|
|
elseif what == SWITCH_RIGHT then
|
|
turnRight = false
|
|
elseif what == SWITCH_UP then
|
|
tiltUp = false
|
|
elseif what == SWITCH_DOWN then
|
|
tiltDown = false
|
|
end
|
|
end
|
|
|
|
|
|
function onOverlayUpdate()
|
|
if turnLeft then
|
|
nodeRotate(box, 0, -1.5, 0)
|
|
end
|
|
if turnRight then
|
|
nodeRotate(box, 0, 1.5, 0)
|
|
end
|
|
if tiltUp then
|
|
nodeRotate(box, -1.5, 0, 0)
|
|
end
|
|
if tiltDown then
|
|
nodeRotate(box, 1.5, 0, 0)
|
|
end
|
|
|
|
return OVERLAY_UPDATED
|
|
end
|
|
----
|
|
|
|
`onInputPressed` und `onInputReleased` sind Callbacks wie `onOverlayUpdate`: Du schreibst sie,
|
|
und Singe ruft sie auf, wenn ein Steuerelement gedrückt oder wieder losgelassen wird. Ein Druck
|
|
kommt einmal an, nicht immer wieder, solange die Taste gehalten wird, und darum setzt jeder eine
|
|
Variable, und der Zeichen-Callback erledigt die Arbeit. Dieses Muster -- der Druck setzt ein
|
|
Flag, der Frame liest das Flag -- ist der Weg, aus einer Engine, die dir nur Änderungen meldet,
|
|
ein „solange die Taste gehalten wird“ herauszuholen.
|
|
|
|
Die Namen mit `SWITCH_` kommen von der Engine selbst, also muss nichts geladen werden, um sie zu
|
|
benutzen. Sie sind auch keine Tasten. Sie sind das, was ein Steuerelement *bedeutet*, und
|
|
`controls.cfg` entscheidet, welche Taste, welcher Knopf oder welche Stick-Richtung jeden davon
|
|
erzeugt. Deine Pfeiltasten schicken heute `SWITCH_LEFT` und seine Freunde; der Stick eines
|
|
Gamepads schickt dasselbe, und dein Skript merkt den Unterschied nie.
|
|
|
|
Die Vorzeichen sind so gewählt, dass jeder Pfeil die Seite, auf die du schaust, in die Richtung
|
|
bewegt, die du gedrückt hast. Eine Drehung um Y um einen positiven Winkel schwenkt diese vordere
|
|
Seite nach rechts, also verlangt der linke Pfeil einen negativen. Wenn dir das verkehrt herum
|
|
vorkommt, tausch die Vorzeichen; es ist deine Steuerung, nicht die der Engine.
|
|
|
|
[#_the_overlay_is_still_on_top]
|
|
=== Das Overlay liegt immer noch oben
|
|
|
|
Füg diese Zeilen in `onOverlayUpdate` über dem `return` ein.
|
|
|
|
[source,lua]
|
|
----
|
|
local pitch, yaw = nodeGetRotation(box)
|
|
|
|
overlayClear()
|
|
overlayPrint(2, 2, "Arrow keys turn the box.")
|
|
overlayPrint(2, 4, "yaw " .. math.floor(yaw) .. " pitch " .. math.floor(pitch))
|
|
----
|
|
|
|
Neu ist daran nichts außer dem, worüber es ausgegeben wird. `overlayPrint` ist der Aufruf aus
|
|
Lektion eins, der in Zeichenzellen zählt und keine geladene Schriftart braucht, und er zeichnet
|
|
über eine 3D-Szene, ohne dass man ihm irgendetwas davon gesagt hat.
|
|
|
|
So ist die Anordnung, und es lohnt sich, es klar auszusprechen: Singe zeichnet zuerst das Video,
|
|
dann die 3D-Szene darüber, dann das Overlay darüber. Deine Punktzahl, deine Leben, dein Timer,
|
|
dein Fadenkreuz und deine Untertitel sind alles Arbeit fürs Overlay, genau wie in Teil zwei. Ein
|
|
3D-Spiel zeigt seine Punktzahl in der Ecke mit denselben fünf Zeilen, die ein 2D-Spiel benutzt.
|
|
|
|
`nodeGetRotation` gibt drei Zahlen zurück, die Winkel um X, Y und Z, und diese Zeile behält die
|
|
ersten beiden in `pitch` und `yaw`. Das sind die üblichen Namen dafür: Pitch ist die Nase, die
|
|
hoch- und runtergeht, Yaw ist das Drehen nach links und rechts. `math.floor` wirft die
|
|
Nachkommastellen weg, damit die Zahlen aufhören zu flackern.
|
|
|
|
Beobachte den Yaw, während du den linken Pfeil gedrückt hältst. Er zählt über `-180` hinaus
|
|
herunter und kommt von `180` wieder herum, weil die Winkel als die kleinste Drehung gemeldet
|
|
werden, die dorthin führt, und nicht als laufende Summe. Wenn dein Spiel wissen muss, wie oft
|
|
sich der Spieler gedreht hat, zähl das selbst mit.
|
|
|
|
[#_try_it_22]
|
|
=== Probier es aus
|
|
|
|
. *Beweg die Kamera.* Ändere `nodeSetPosition(camera, 0, 2, 7)` in `(0, 8, 7)`, dann in
|
|
`(7, 2, 7)`, dann in `(0, 0.2, 7)`. Jedes Mal hält `nodeLookAt` die Kiste in der Mitte, also
|
|
bekommst du für eine einzige Zahl einen anderen Blick auf dieselbe Szene.
|
|
. *Wechsle das Objektiv.* Probier `cameraSetPerspective(30, 0.1, 100)`, dann
|
|
`(90, 0.1, 100)`, ohne die Kamera zu bewegen. Ein Spiel fühlt sich mit einem weiten Sichtfeld
|
|
schnell an und mit einem engen gemessen, und das ist der billigste Stimmungsregler, den es
|
|
gibt.
|
|
. *Stell eine zweite Kiste hinein.* Kopier die zwei Zeilen, die die Kiste gemacht haben, benenn
|
|
die Variable in `mark` um und gib ihr `nodeSetPosition(mark, 3, 0, 0)` und
|
|
`nodeSetScale(mark, 0.3)`. Jetzt hast du einen Orientierungspunkt, an dem du die erste Kiste
|
|
messen kannst. Stell sie nacheinander auf `(0, 3, 0)` und `(0, 0, -3)` und sag laut, welche
|
|
Achse welche ist.
|
|
. *Teil das Mesh.* Statt `meshBox` zweimal aufzurufen, bewahr das erste in einer Variablen auf
|
|
und gib beiden Knoten dasselbe Handle. Nichts sieht anders aus. Genau darum geht es: eine Form,
|
|
zwei Orte.
|
|
. *Häng die Kamera an die Kiste.* Behalte die zweite Kiste aus dem letzten Schritt, sonst bleibt
|
|
nichts übrig, woran man die Bewegung sehen kann. Dann schlag `nodeSetParent` in der Referenz
|
|
nach und häng die Kamera unter die erste Kiste. Halte eine Pfeiltaste gedrückt: Die Kamera
|
|
fährt die Drehung der Kiste mit, also steht die Kiste still, und der Rest der Szene schwenkt
|
|
vorbei. Das ist eine Zeile von einer Ego-Perspektive entfernt, und so findest du auch heraus,
|
|
dass eine Verfolgerkamera einen Elternknoten will statt Rechnerei.
|
|
|
|
[#_break_it_on_purpose_21]
|
|
=== Mach es absichtlich kaputt
|
|
|
|
Vertipp dich beim Namen des Materials. Ändere die Zeile mit `nodeSetMesh` so, dass sie `pain`
|
|
statt `paint` benutzt:
|
|
|
|
[source,lua]
|
|
----
|
|
nodeSetMesh(box, meshBox(2, 2, 2), pain)
|
|
----
|
|
|
|
Singe hält an, bevor das Spiel startet, und gibt etwas aus wie:
|
|
|
|
----
|
|
15:nodeSetMesh: Argument 3 must be a number.
|
|
----
|
|
|
|
Das ist eine andere Form von Fehler als die in Lektion eins, und er kommt von der Engine statt
|
|
von Lua. Die Zahl vorn ist die Zeile. Der Name danach ist der Aufruf, der sich beschwert hat. Die
|
|
Beschwerde selbst ist, dass das dritte Ding, das du ihm gegeben hast, keine Zahl war.
|
|
|
|
Es lohnt sich, dem Grund nachzugehen, denn er erklärt eine ganze Familie von Fehlern. `pain` ist
|
|
eine Variable, die nie einen Wert bekommen hat, und in Lua ist eine leere Variable `nil` -- der
|
|
Wert, der „hier ist nichts“ bedeutet. Wenn du `nil` an einen Aufruf übergibst, der ein
|
|
Material-Handle will, bekommst du genau das. Wenn du von einem Engine-Aufruf „must be a number“
|
|
siehst, such in dieser Zeile nach einem falsch geschriebenen Variablennamen oder nach einem
|
|
Handle, das du vergessen hast aufzubewahren.
|
|
|
|
[#_what_you_learned_22]
|
|
=== Was du gelernt hast
|
|
|
|
* `sceneEnable(true)` schaltet die 3D-Ebene ein, und in 3D wird nichts gezeichnet, bis du es
|
|
aufrufst.
|
|
* Ein Knoten ist ein Ort in der Welt mit einer Position, einer Drehung und einer Skalierung.
|
|
Meshes, Lichter und Kameras hängen alle an Knoten.
|
|
* Ein Mesh ist die Form und ein Material das Aussehen, und jedes davon kann von beliebig vielen
|
|
Knoten geteilt werden.
|
|
* X ist rechts, Y ist oben, und negatives Z ist die Richtung, in die die Kamera schaut.
|
|
* `nodeSetPosition` und `nodeSetRotation` setzen einen Wert direkt; `nodeMove` und `nodeRotate`
|
|
ändern ihn um einen Betrag.
|
|
* Jeder Knoten kann die Kamera sein. `nodeLookAt` richtet sie aus, und `cameraSet` wählt sie.
|
|
* Das Sichtfeld in `cameraSetPerspective` ändert, wie sich die Szene anfühlt, ohne etwas zu
|
|
bewegen.
|
|
* Eine Szene ohne Licht ist schwarz, und das ist das Erste, was du prüfst, wenn nichts erscheint.
|
|
* Das Overlay ist immer noch da, immer noch oben und immer noch der Ort für deine Punktzahl.
|
|
|
|
[#_next_time_22]
|
|
=== Beim nächsten Mal
|
|
|
|
Eine Kiste ist ein armseliger Drache. Die nächste Lektion lädt ein echtes Modell -- eines, das
|
|
die Engine schon in deinen Arbeitsordner entpackt hat, also gibt es nichts herunterzuladen und
|
|
nichts zu zeichnen -- stellt es auf den Boden und beleuchtet es richtig. Das ist Lektion
|
|
dreiundzwanzig.
|