4. Bewegung von Objekten - Teil 1
Heute beschäftigen wir uns zum ersten Mal mit der Bewegung von Objekten. Dies ist natürlich ein riesiges Thema, deswegen gibt es heute nur den ersten Teil.
Man kann mit der Bewegung von Gegenständen eine größere Interaktivität in Modifikationen bringen. Statt einer Teleporttür kann man auch ein sich bewegendes Fallgitter oder statt einer Treppe einen Lift benutzen. Um genau diese Dinge wird es heute gehen.
Tatsächlicher Ablauf von Scripten
Zunächst einmal müssen wir ein wenig Theorie über Scripte in Morrowind machen. Für die bisherigen Scripte war es nicht wichtig, wie genau Morrowind Scripte behandelt, jetzt wird dies mit eine entscheidende Bedeutung bekommen.
Morrowind lässt jedes Script, dass sich auf irgendeinem Gegenstand in der aktuellen Zelle befindet in jedem Frame (fps = Frames per Second, also (Halb-)Bilder pro Sekunde) einmal durchlaufen. Auf den meisten Rechnern wird also jedes Script einige Dutzend Male pro Sekunde ausgeführt!
Was ziehen wir daraus für Schlussfolgerungen? Zum Ersten muss ein Script nicht erst aktiviert werden, es läuft automatisch, sobald man die Zelle des entsprechenden Objekts betritt. Das ist normalerweise sehr praktisch, wie wir gleich sehen werden. Zum Zweiten muss man aber auch sicherstellen, dass Aktionen nur einmal ausgeführt werden und nicht in jedem Frame erneut beziehungsweise das genaue Gegenteil, nämlich eine Funktion jeden Frame aufzurufen.
Bewegung von Gegenständen
Nur Gegenstände erscheinen und verschwinden zu lassen, wird natürlich schnell langweilig, man möchte sie auch bewegen. Wie funktioniert Bewegung in Morrowind? Genau genommen gibt es keine echte Bewegung. Objekte werden in jedem Frame an eine leicht veränderte Position gesetzt. Da das menschliche Auge aber nur etwa 20 Bilder pro Sekunde sehen kann (und wir hoffentlich eine viel höhere Framerate haben), nehmen wir diese vielen kleinen Sprünge als kontinuierliche Bewegung wahr.
Da Morrowind seine Scripte sowieso jeden Frame ausführt, nutzen wir dies bei Bewegungen, wir lassen die Bewegungsfunktion einfach in jedem Durchlauf des Scripts eine kleine Bewegung machen. Allerdings gibt es auch immer Situationen, in denen man keine Bewegung haben möchte. Zum Beispiel pausieren die normalen Animationen, wenn man sich im Inventar befindet, Scripte nicht. Also müssen wir dieses Pausieren per Hand erzeugen.
MenuMode
Dazu gibt es die äußerst praktische Funktion MenuMode. Diese liefert immer dann eine 1, wenn irgendein Menü geöffnet ist. Ob dies nun das Spielmenü (ESC), das Inventar, ein Dialog oder eine Messagebox ist, macht keinen Unterschied. Mit folgender Konstruktion kann man die Ausführung eines Scripts unterbrechen, wenn ein Menü geöffnet wird:
Dies ist einer der wichtigsten Codebrocken in Morrowind Script. In vielen Scripten ist dies der erste Block, der nach der Variablendeklaration steht.
Move
MoveWorld
Die Bewegung in Morrowind findet in einem dreidimensionalen, kartesischen Koordinatensystem statt (ja, das ist Mathe 😉). Die Achsen dieses Koordinatensystems werden mit X, Y und Z bezeichnet. X läuft dabei von West nach Ost (in Außenzellen), Y von Süd nach Nord (in Außenzellen) und Z von unten nach oben.
Außerdem unterscheidet Morrowind zwischen einem absoluten und einem relativen Koordinatensystem. Das absolute Koordinatensystem ist das der Welt mit den Eigenschaften, die oben beschrieben sind. Für jedes Objekt existiert aber auch ein eigenes relatives Koordinatensystem. In diesem sind die Achsen entsprechend der Drehung des Objekts in der Welt mitgedreht.
Vergleich des absoulten Welt-Koordinatensystems (schwarz) und des relativen Koordinatensystems eines Gegenstands (rot).
Das relative Koordinatensystem mit X' und Y' ist aus einer Drehung des Gegenstandes um die Z-Achse um 45° entstanden.
Wenn das Objekt nicht gedreht ist (also alle Winkel gleich 0 sind), sind das absolute und das relative Koordinatensystem gleich.
Die beiden Funktionen Move und MoveWorld werden so aufgerufen:
Diese Funktionen bewegen das aufrufende Objekt mit der in value angegebenen Geschwindigkeit bezüglich der in axis angegebenen Achse. Für axis werden die Buchstaben X, Y und Z akzeptiert. value verarbeitet float-Werte, es können aber auch beliebige andere Werte angegeben werden. Die Werte werden als Editoreinheiten pro Sekunde interpretiert. Tatsächlich wird bei jedem Aufruf nur der entsprechende Bruchteil der Bewegung ausgeführt, die der Zeit seit dem letzten Frame entspricht. Um eine kontinuierliche Bewegung zu erreichen, muss die Funktion also jeden Frame aufgerufen werden. value akzeptiert (leider) keine Variablen!
Die Funktionen haben keine Auswirkungen auf NPCs, Kreaturen oder den Spieler.
Move bewegt das Objekt entlang der relativen Achse. Diese Funktion kann zum Beispiel dafür benutzt werden, dass sich ein Pfeil immer in die Richtung seiner Spitze bewegt, egal in welche Richtung er gedreht ist.
MoveWorld bewegt das Objekt entlang der absoluten Achse. Diese Funktion wird weitaus häufiger benutzt werden und sorgt zum Beispiel dafür, dass sich ein Objekt immer nach oben bewegt, unabhängig davon wie es gedreht ist.
Im obigen Beispiel ist das Objekt um 45° um die Z-Achse gedreht worden. Ein Move, X, 100 würde das Objekt entlang der rotmarkierten X'-Achse bewegen. Die Funktion MoveWorld, X, 100 hingegen bewegt das Objekt entlang der schwarzen X-Achse.
Um ein Gefühl für die Geschwindigkeit zu bekommen, muss man wissen, dass eine Editoreinheit 1,42 cm entspricht. Also entsprechen etwa 20 Editoreinheiten pro Sekunde einem km/h. Geschwindigkeiten über 400 Editoreinheiten pro Sekunde (20 km/h) sollten vermieden werden, weil sie meistens abgehackt aussehen, da die Abstände zwischen den einzelnen Sprungpositionen zu groß werden.
Nun können wir also eine Bewegung starten, wir können sie sogar pausieren, wenn ein Menü geöffnet wird, aber so wird das Objekt eine endlose Bewegung ausführen und irgendwann im Nichts verschwinden. Wie können wir die Bewegung also aufhalten?
Dazu stehen uns grundsätzlich zwei Optionen zur Verfügung. Entweder stoppen wir die Bewegung, wenn das Objekt einen bestimmten Punkt erreicht hat oder wir lassen die Bewegung nur eine bestimmte Zeit lang laufen.
GetPos
Mit dieser Funktion kann man die Position des aufrufenden Objekts bezüglich der angegebenen Achse abfragen:
Statt dem X kann man natürlich auch wieder Y und Z abfragen. Der Rückgabewert der Funktion ist eine float-Größe (also eine Dezimalkomma-Zahl). Über eine if-Abfrage kann man nun prüfen, ob das Objekt bereits an seinem Zielpunkt angelangt ist (natürlich funktioniert hier auch ein while, aber das will ich heute nicht auch noch einführen). Hier ein Beispielscript:
Statt der Konstruktion mit zpos könnte man natürlich auch direkt die Koordinaten aus dem CS einsetzen, die man sich als Zielpunkte ausgesucht hat. Diese Formulierung ist aber universell anwendbar, da ich ja nicht weiß, wo in der Spielwelt ihr das Script ausprobieren wollt.
GetSecondsPassed
Die zweite Möglichkeit, um Bewegung zu begrenzen, liegt in der Zeitmessung. Bisher kennen wir dafür die globale Variable GameHour, hiermit ist es aber nicht möglich wirklich kurze Zeitspannen genau zu messen. Dafür gibt es die Funktion GetSecondsPassed. Diese liefert die vergangenen Sekunden(-bruchteile) seit dem letzten Frame zurück. Dies stellt die einzige Scriptmöglichkeit dar, kurze Zeitspannen zu messen. Hier noch einmal ein Script mit der gleichen Funktion wie oben, diesmal mit GetSecondsPassed implementiert:
Aufgaben
Lösungen
-
Im nächsten Teil beschäftigen wir uns mit der Rotation von Gegenständen. Dies ist quasi nur eine Wiederholung der Bewegungsfunktionen, da sie prinzipiell identisch funktionieren. Außerdem werden wir Gegenstände aus der Ferne bewegen beziehungsweise die Bewegung eines Objekts von einem anderen steuern lassen.
Heute beschäftigen wir uns zum ersten Mal mit der Bewegung von Objekten. Dies ist natürlich ein riesiges Thema, deswegen gibt es heute nur den ersten Teil.
Man kann mit der Bewegung von Gegenständen eine größere Interaktivität in Modifikationen bringen. Statt einer Teleporttür kann man auch ein sich bewegendes Fallgitter oder statt einer Treppe einen Lift benutzen. Um genau diese Dinge wird es heute gehen.
Tatsächlicher Ablauf von Scripten
Zunächst einmal müssen wir ein wenig Theorie über Scripte in Morrowind machen. Für die bisherigen Scripte war es nicht wichtig, wie genau Morrowind Scripte behandelt, jetzt wird dies mit eine entscheidende Bedeutung bekommen.
Morrowind lässt jedes Script, dass sich auf irgendeinem Gegenstand in der aktuellen Zelle befindet in jedem Frame (fps = Frames per Second, also (Halb-)Bilder pro Sekunde) einmal durchlaufen. Auf den meisten Rechnern wird also jedes Script einige Dutzend Male pro Sekunde ausgeführt!
Was ziehen wir daraus für Schlussfolgerungen? Zum Ersten muss ein Script nicht erst aktiviert werden, es läuft automatisch, sobald man die Zelle des entsprechenden Objekts betritt. Das ist normalerweise sehr praktisch, wie wir gleich sehen werden. Zum Zweiten muss man aber auch sicherstellen, dass Aktionen nur einmal ausgeführt werden und nicht in jedem Frame erneut beziehungsweise das genaue Gegenteil, nämlich eine Funktion jeden Frame aufzurufen.
Bewegung von Gegenständen
Nur Gegenstände erscheinen und verschwinden zu lassen, wird natürlich schnell langweilig, man möchte sie auch bewegen. Wie funktioniert Bewegung in Morrowind? Genau genommen gibt es keine echte Bewegung. Objekte werden in jedem Frame an eine leicht veränderte Position gesetzt. Da das menschliche Auge aber nur etwa 20 Bilder pro Sekunde sehen kann (und wir hoffentlich eine viel höhere Framerate haben), nehmen wir diese vielen kleinen Sprünge als kontinuierliche Bewegung wahr.
Da Morrowind seine Scripte sowieso jeden Frame ausführt, nutzen wir dies bei Bewegungen, wir lassen die Bewegungsfunktion einfach in jedem Durchlauf des Scripts eine kleine Bewegung machen. Allerdings gibt es auch immer Situationen, in denen man keine Bewegung haben möchte. Zum Beispiel pausieren die normalen Animationen, wenn man sich im Inventar befindet, Scripte nicht. Also müssen wir dieses Pausieren per Hand erzeugen.
MenuMode
Dazu gibt es die äußerst praktische Funktion MenuMode. Diese liefert immer dann eine 1, wenn irgendein Menü geöffnet ist. Ob dies nun das Spielmenü (ESC), das Inventar, ein Dialog oder eine Messagebox ist, macht keinen Unterschied. Mit folgender Konstruktion kann man die Ausführung eines Scripts unterbrechen, wenn ein Menü geöffnet wird:
Code:
if ( MenuMode == 1 )
return
endif
Dies ist einer der wichtigsten Codebrocken in Morrowind Script. In vielen Scripten ist dies der erste Block, der nach der Variablendeklaration steht.
Move
MoveWorld
Die Bewegung in Morrowind findet in einem dreidimensionalen, kartesischen Koordinatensystem statt (ja, das ist Mathe 😉). Die Achsen dieses Koordinatensystems werden mit X, Y und Z bezeichnet. X läuft dabei von West nach Ost (in Außenzellen), Y von Süd nach Nord (in Außenzellen) und Z von unten nach oben.
Außerdem unterscheidet Morrowind zwischen einem absoluten und einem relativen Koordinatensystem. Das absolute Koordinatensystem ist das der Welt mit den Eigenschaften, die oben beschrieben sind. Für jedes Objekt existiert aber auch ein eigenes relatives Koordinatensystem. In diesem sind die Achsen entsprechend der Drehung des Objekts in der Welt mitgedreht.
Vergleich des absoulten Welt-Koordinatensystems (schwarz) und des relativen Koordinatensystems eines Gegenstands (rot).
Das relative Koordinatensystem mit X' und Y' ist aus einer Drehung des Gegenstandes um die Z-Achse um 45° entstanden.
Wenn das Objekt nicht gedreht ist (also alle Winkel gleich 0 sind), sind das absolute und das relative Koordinatensystem gleich.
Die beiden Funktionen Move und MoveWorld werden so aufgerufen:
Code:
Move, axis, value
MoveWorld, axis, value
---
Move, Z, 100
MoveWorld, X, -200
Diese Funktionen bewegen das aufrufende Objekt mit der in value angegebenen Geschwindigkeit bezüglich der in axis angegebenen Achse. Für axis werden die Buchstaben X, Y und Z akzeptiert. value verarbeitet float-Werte, es können aber auch beliebige andere Werte angegeben werden. Die Werte werden als Editoreinheiten pro Sekunde interpretiert. Tatsächlich wird bei jedem Aufruf nur der entsprechende Bruchteil der Bewegung ausgeführt, die der Zeit seit dem letzten Frame entspricht. Um eine kontinuierliche Bewegung zu erreichen, muss die Funktion also jeden Frame aufgerufen werden. value akzeptiert (leider) keine Variablen!
Die Funktionen haben keine Auswirkungen auf NPCs, Kreaturen oder den Spieler.
Move bewegt das Objekt entlang der relativen Achse. Diese Funktion kann zum Beispiel dafür benutzt werden, dass sich ein Pfeil immer in die Richtung seiner Spitze bewegt, egal in welche Richtung er gedreht ist.
MoveWorld bewegt das Objekt entlang der absoluten Achse. Diese Funktion wird weitaus häufiger benutzt werden und sorgt zum Beispiel dafür, dass sich ein Objekt immer nach oben bewegt, unabhängig davon wie es gedreht ist.
Im obigen Beispiel ist das Objekt um 45° um die Z-Achse gedreht worden. Ein Move, X, 100 würde das Objekt entlang der rotmarkierten X'-Achse bewegen. Die Funktion MoveWorld, X, 100 hingegen bewegt das Objekt entlang der schwarzen X-Achse.
Um ein Gefühl für die Geschwindigkeit zu bekommen, muss man wissen, dass eine Editoreinheit 1,42 cm entspricht. Also entsprechen etwa 20 Editoreinheiten pro Sekunde einem km/h. Geschwindigkeiten über 400 Editoreinheiten pro Sekunde (20 km/h) sollten vermieden werden, weil sie meistens abgehackt aussehen, da die Abstände zwischen den einzelnen Sprungpositionen zu groß werden.
Nun können wir also eine Bewegung starten, wir können sie sogar pausieren, wenn ein Menü geöffnet wird, aber so wird das Objekt eine endlose Bewegung ausführen und irgendwann im Nichts verschwinden. Wie können wir die Bewegung also aufhalten?
Dazu stehen uns grundsätzlich zwei Optionen zur Verfügung. Entweder stoppen wir die Bewegung, wenn das Objekt einen bestimmten Punkt erreicht hat oder wir lassen die Bewegung nur eine bestimmte Zeit lang laufen.
GetPos
Mit dieser Funktion kann man die Position des aufrufenden Objekts bezüglich der angegebenen Achse abfragen:
Code:
float xpos
set xpos to GetPos, X
Statt dem X kann man natürlich auch wieder Y und Z abfragen. Der Rückgabewert der Funktion ist eine float-Größe (also eine Dezimalkomma-Zahl). Über eine if-Abfrage kann man nun prüfen, ob das Objekt bereits an seinem Zielpunkt angelangt ist (natürlich funktioniert hier auch ein while, aber das will ich heute nicht auch noch einführen). Hier ein Beispielscript:
Code:
begin kf_sk_bewegung_bsp1_script
short state
float zpos
if ( MenuMode == 1 )
return
endif
if ( state == 0 )
if ( OnActivate == 1 )
MessageBox, "Nach oben!"
set zpos to GetPos, Z ;berechne eine obere Schranke für die Bewegung
set zpos to zpos + 300
set state to 1
endif
elseif ( state == 1 )
if ( GetPos, Z <= zpos )
MoveWorld, Z, 100
else
set state to 2 ;sorgt dafür, dass man nur einmal nach oben fahren kann
endif
endif
end
Statt der Konstruktion mit zpos könnte man natürlich auch direkt die Koordinaten aus dem CS einsetzen, die man sich als Zielpunkte ausgesucht hat. Diese Formulierung ist aber universell anwendbar, da ich ja nicht weiß, wo in der Spielwelt ihr das Script ausprobieren wollt.
GetSecondsPassed
Die zweite Möglichkeit, um Bewegung zu begrenzen, liegt in der Zeitmessung. Bisher kennen wir dafür die globale Variable GameHour, hiermit ist es aber nicht möglich wirklich kurze Zeitspannen genau zu messen. Dafür gibt es die Funktion GetSecondsPassed. Diese liefert die vergangenen Sekunden(-bruchteile) seit dem letzten Frame zurück. Dies stellt die einzige Scriptmöglichkeit dar, kurze Zeitspannen zu messen. Hier noch einmal ein Script mit der gleichen Funktion wie oben, diesmal mit GetSecondsPassed implementiert:
Code:
begin kf_sk_bewegung_bsp2_script
short state
float timer
if ( MenuMode == 1 )
return
endif
if ( state == 0 )
if ( OnActivate == 1 )
MessageBox, "Nach oben!"
set state to 1
endif
elseif ( state == 1 )
set timer to timer + GetSecondsPassed
MoveWorld, Z, 100
if ( timer >= 3 ) ;3 Sekunden mal 100 Einheiten pro Sekunde = 300 Einheiten nach oben
set state to 2 ;sorgt dafür, dass man nur einmal nach oben fahren kann
endif
endif
end
Aufgaben
- Probiert die Beispielscripte aus und versteht, wie sie funktionieren.
- Erstellt einen einfachen Aufzug, der ständig hoch und runter fährt und an jedem Ende jeweils kurz anhält.
- Probiert, was passiert, wenn ihr die MenuMode-Abfrage weglasst und auf dem Aufzug das Inventar öffnet.
- Warum muss timer im zweiten Beispielscript eine float-Variable sein?
- Erstellt einen Activator, der euch beim Aktivieren die aktuelle Framerate (fps) ausgibt.
- Erstellt einen Aufzug, der anfangs still steht. Wenn er aktiviert wird, bewegt sich der Aufzug nach oben, wartet dort einen Moment und fährt wieder runter.
- Erstellt einen Aufzug, der nach Aktivieren nach oben beziehungsweise unten fährt und dort bleibt, bis er erneut aktiviert wird. Danach fährt er wieder in seine andere Position und wartet dort.
Lösungen
-
Im nächsten Teil beschäftigen wir uns mit der Rotation von Gegenständen. Dies ist quasi nur eine Wiederholung der Bewegungsfunktionen, da sie prinzipiell identisch funktionieren. Außerdem werden wir Gegenstände aus der Ferne bewegen beziehungsweise die Bewegung eines Objekts von einem anderen steuern lassen.
Zuletzt bearbeitet: