Construction Set Morrowind-Skriptkurs - Lektion 5

Killfetzer

Super-Moderator
Teammitglied
The Elder Scrolls III: Morrowind - Scriptkurs


5. Bewegung und Rotation von Objekten - Teil 2

In der heutigen Lektion geht es zunächst um die Rotation von Gegenständen. Dies ist wenn man so will noch mal eine Wiederholung der Translation (also Bewegung) aus der letzten Lektion. Die Befehle sind genau gleich strukturiert. Da dies nur ein sehr kleiner Abschnitt ist, beschäftigen wir uns danach noch mit der Fernsteuerung von Objekten. Bisher musste ein Script immer auf dem Objekt liegen, um es zu bewegen. Tatsächlich ist es oft viel einfacher, das Script auf einem anderen Gegenstand laufen zu lassen, der den zu bewegenden gegenstand gewissermaßen fernsteuert.


Rotation von Gegenständen

Neben der Bewegung von Gegenständen entlang einer Achse, kann man sie auch um eine Achse rotieren lassen.

Scriptkurs_Koordinatensysteme1.gif


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.

Die Befehle für die Rotation sind (analog zur Bewegung):

Rotate
RotateWorld
GetAngle


Wieder bezeichnet hier Rotate eine Rotation um die relative Achse und RotateWorld um die absolute Achse. Als Zahl wird diesmal die Winkelgeschwindigkeit in Grad pro Sekunde angegeben. Im obigen Bild bedeutet ein RotateWorld, X, dass sich das Objekt um die schwarze mit X bezeichnete Achse dreht. Ein Rotate, X lässt das Objekt aber um die rote mit X' bezeichnete Achse rotieren.

Ein Beispiel für Rotationsscripte sind die Tresenklappen:

Code:
begin BarDoor

float timer
short on
short open

if ( MenuMode == 0 )
  if ( OnActivate == 1 )
    if ( on == 0 )
      set timer to 0
      set on to 1
      PlaySound3D, "Door Creaky Open"
    endif
  endif

  if ( on == 1 )
    set timer to timer + GetSecondsPassed

    if ( open == 0 )
      if ( timer < 1 )
        Rotate, X, 110
      elseif ( timer >= 1 )
        set on to 0
        set open to 1
      endif
    elseif ( open == 1 )
      if (timer < 1)
        Rotate, X, -110
      elseif ( timer >= 1 )
        SetatStart             ;nicht zwingend nötig, beseitigt in diesem Fall kleine Abweichungen, der Position, die durch die Bewegung entstehen können
        set on to 0
        set open to 0
      endif
    endif
  endif
endif

end

Um noch ein Beispiel für die Abfrage des Winkels zu liefern: Im obigen Bild mit den Koordinatensystemen würde GetAngle, Z den Wert 45 zurückliefert.


Bewegung von Gegenständen aus der Ferne

Bisher wurden immer nur die Gegenstände, auf denen das Script selbst lag, bewegt. Viel öfter möchte man aber ein anderes Script (zum Beispiel ein globales) einen Gegenstand bewegen (oder disablen/enablen) lassen. Deswegen ist es möglich in einem Script Funktionen so aufzurufen, dass sie andere Gegenstände beeinflussen.

Damit dies funktioniert, muss dieser andere Gegenstand aber einen Haken bei reference persistent haben. Was bedeutet das? Normalerweise werden in Morrowind nur die Gegenstände geladen, die sich in der aktuellen Zelle befinden. Ein Gegenstand der als persistent markiert ist, wird beim Spielstart geladen und bleibt immer geladen. Da dies natürlich das Spiel verlangsamt, sollte man möglichst sparsam mit dem Häkchen bei reference persistent umgehen. Bei NPCs und Kreaturen muss corpse persistent ausgewählt werden.
Zusätzlich muss ein Objekt, dass ferngesteuert werden soll, einmalig sein. Das heißt, es muss genau einmal existieren.

Scriptkurs_ref_pres.jpg


Der Activator aus dem ersten Script wurde hier als persistent markiert.

Wenn man nun also solch ein Objekt hat, kann man es aus einem anderen Script heraus manipulieren. Dies kann zum Beispiel ein Schalter sein, der ein Tor steuert. Die Struktur sieht wie folgt aus:

Code:
"Object_ID"->Disable
set "Object_ID".state to 1

---

"ex_gg_portcullis_01"->MoveWorld, Z, 70
set "skinkintreesshade".soulAshGhoul to 1

Der Pfeil (->) dient hierbei zum Aufruf einer Funktion. Das Objekt vor dem Pfeil ist das, was ich immer als aufrufendens Objekt bezeichne. Wird dort kein Objekt angegeben (wie wir bisher immer), wird dort das Objekt eingesetzt, auf dem das Script liegt. Ein Beispiel hierfür ist das Script GG_OpenGate1, dass auf den Schaltern der Geisterpforte liegt und das (persistent markierte) Gitter der Geisterpforte bewegt.

Mit dem Punkt (.) kann man auf eine lokale Variable eines Scripts auf dem angegebenen Gegenstand zugreifen und diese verändern oder abfragen. Diese Variable muss dazu auch existieren, weiterhin muss der Träger des Scriptes in den letztn 72 Stunden Spielzeit tatsächlich geladen worden sein!
Ein Beispiel hierfür ist das Script SkinkSoul1.


Aufgaben

  1. Warum wird im Beispiel der Tresenklappe die Funktion Rotate und nicht RotateWorld benutzt?
  2. Erstellt eine Tür aus einem Activator. Diese soll sich wie eine normale Tür verhalten.
  3. Erstellt einen Aufzug, der nach Betätigen eines Rufknopfs nach oben beziehungsweise unten fährt und dort bleibt, bis ein Rufknopf erneut betätigt wird.
  4. Bonusaufgabe: Erstellt einen Aufzug, der 5 verschiedene Haltepunkte besitzt. an jedem dieser Haltepunkte soll es einen Rufknopf geben, mit dem man den Aufzug auf diese Ebene rufen kann. Der Aufzug soll eine Bedientafel besitzen, an der man jede der Zieletagen auswählen kann. Der Aufzug soll von jeder Ebene zu jeder fahren können.


-
Im nächsten Teil beschäftigen wir uns mit der Teleportation von Objekten und der Bewegung relativ zu einem anderen Objekt. Aber erstmal zu diesem Teil. Ich bin mir sicher, dass es viele Fragen geben wird ;)
 
Habe mich jetzt mal drangesetzt...

Es wird "rotate" benutzt, weil "rotateWorld" das Objekt um die globale und nicht um die objekteigene Achse rotieren würde. Wenn die Klappe zum Beispiel schon vorher gedreht war, würde das unter Umständen ziemlich komisch aussehen.
In dem Punkt kann ich als OB-Scripter MW-Scripter um die vorhandene Auswahl nur beneiden.
Code:
Begin Activator_Tuer_SCRIPT

short open
short anim
float timer

if MenuMode
 return
endif

if onActivate
 if anim == 0
  set anim to 1
  set timer to 1
  playSound3D "Door Creaky Open"
 endif
endif

if anim
 if timer > 0
  set timer to (timer - getSecondsPassed)
  if open
   rotate z -90
  else
   rotate z 90
  endif
 else
  if open
   set open to 0
   setAtStart
  else
   set open to 1
  endif
  set anim to 0
 endif
endif

end
 
Natürlich. Lies dir einfach die bisherigen Lektionen durch. Es werden auch zu zurückliegenden Lektionen noch Fragen beantwortet. Die entsprechenden Themen sind ja noch alle offen. ;)
 
Oha, hab mich noch gar nicht an diese neue Lektion gemacht, weil total vergessen. Werd mich mal in den nächsten Tagen damit beschäftigen. Sorry, Killfetzer!
 
Dito, bei mir genau so. Sorry, Killfetzer, aber momentan weiss ich nicht, woher ich die Zeit dafür stehlen soll.
Scheint aber wohl auch mit der berühmten Wintersmüdigkeit zu tun zu haben, da kommt man nur schlecht raus.
 
Damit es hier doch noch weitergeht, habe ich mich zumindest mal an die ersten beiden Aufgaben gemacht:

Bei der Tresenklappe oder Türe darf RotateWorld nicht benutzt werden, da eine vorher erfolgte manuelle Ausrichtung des Activators nicht berücksichtigt wird und man immer bis zum gleichen Punkt rotiert, unabhängig durch die Position. Mit Rotate wird wird auf die manuelle Rotation ein zusätzlicher Wert addiert bzw subtrahiert.

PHP:
begin merc_eigenartige_Tuer


float timer
short open
short state

if    (MenuMode==0)
    if    (OnActivate==1)
        if    (state==0)
            set timer to 0
            set state to 1
            PlaySound3D, "Door Creaky Open"
        endif
    endif
    if    (state==1)
        set timer to timer + GetSecondsPassed

    if    (Open==0)
        if    (Timer < 1)
        Rotate, z, 90
        elseif    (timer>=1)
            set state to 0
            set open to 1
        endif
    elseif    (Open==1)
        if    (timer < 1)
            Rotate, z, -90
        elseif ( timer >= 1 )
        SetatStart
        set state to 0
        set open to 0
      endif
    endif
  endif
endif

end

Aufgabe 3 hab ich so eine Idee, aber da muss ich erstmal schauen. Da versuche ich mich im Laufe der nächsten Woche dran. Aufgabe 4 wird aber wohl richtig tricky werden.
 
Da hier wohl nichts mehr kommt, werde ich mal die Lösungen schreiben und den Kurs dann wohl leider beenden.
 
Ja, es ist schade, dass nicht mehr daraus geworden ist, aber es ist zumindest gut, dass du uns noch die Lösungen hierfür zur Verfügung stellst.
 
Tut mir leid, dass ich auf der Strecke geblieben bin. Danke, dass du die Lösungen noch reinstellst.

Aber ich muss sagen, dass mich der Scriptkurs auch so ein ganzes Stück weiter gebracht hat.
Auch hier nochmal ein Danke an alle Verantwortlichen.
 
Ich möchte mich den anderen anschließen.Die Zeit wollte nicht mitspielen.Danke das du die Lösungen noch reinstellst.

Ich denke die bisherigen Sachen sind ein guter Einstieg ins Scripten gewesen und auch eine gute Basis auf der man aufbauen kann und ich habe viel dabei gelernt.:)

Mfg MadDin
 
  • Like
Reaktionen: Killfetzer
Ich kann mach meinen Vorpostern nur anschliessen. Allgemein sind deine Werke sehr gut als Scriptstütze geeignet. Rat mal, in welche liste ich bei etwas Komlizierteren Scripten gerne mal reinschaue und in welchen scriptkurs bei einfacheren sachen.8)
 
  • Like
Reaktionen: Killfetzer
Leider konnten wir ja nur einen kleinen Teil des Scriptings abdecken.
Ich denke aber mal, dass das was bis jetzt schon gemkommen ist für's Modden ausreichend ist, und im Zweifelsfall gibts ja immer noch den Scriptkurs, für die richtig komplizierten Scripts. Leute wie ich sind da schon für die Grundkenntnisse dankbar, die sich beim Selbststudium nach verzweifelten tagen so wie so selbst in den Hintern begissen hätten.


Gruß: TakoTatsujin
 
Lösungen

  • Warum wird im Beispiel der Tresenklappe die Funktion Rotate und nicht RotateWorld benutzt?

    Da man nicht weiß, in welcher Orientierung die Klappe später im Spiel eingebaut wird, nimmt man diesen Befehl. Somit dreht sich die Klappe immer um ihre eigene Achse und zwar unabhängig davon wie verdreht auch immer sie im Spiel vorkommt.

  • Erstellt eine Tür aus einem Activator. Diese soll sich wie eine normale Tür verhalten.

    Zunächst einmal: Warum sollte man sowas machen? Der Vorteil einer solchen Tür ist ganz klar, dass sie auf der Karte nicht als gelbes Kästchen erscheint und sich somit perfekt für eine Geheimtür eignet.
    Code:
    begin kf_sk_activator_door_script
    
    short state
    float timer
    
    if ( MenuMode == 1 )
      return
    endif
    
    if ( state == 0 )				;Tür ist geschlossen
    
    	if ( OnActivate == 1 )
    		set state to 1
    	endif
    
    elseif ( state == 1 )		;Tür schwingt auf
    
    	set timer to timer + GetSecondsPassed
    
    	if ( timer < 1 )
    		Rotate, Z, 90
    	else
    		set timer to 0
    		set state to 2
    	endif
    
    elseif ( state == 2 )		;Tür ist offen
    
    	if ( OnActivate == 1 )
    		set state to 3
    	endif
    
    else							;Tür schwingt zu
    
    	set timer to timer + GetSecondsPassed
    
    	if ( timer < 1 )
    		Rotate, Z, -90
    	else
    		set timer to 0
    		set state to 0
    	endif
    
    endif
    
    end

  • Erstellt einen Aufzug, der nach Betätigen eines Rufknopfs nach oben beziehungsweise unten fährt und dort bleibt, bis ein Rufknopf erneut betätigt wird.

    Hier nehme ich direkt die Lösung des 3. Aufzuges aus der 4. Lektion. Ich muss nur zwei Zeilen ändern, damit es funktioniert. Das Script liegt in dem Fall auf dem Rufknopf. Der Aufzug selbst ist ein Objekt mit reference persistent und dem Namen kf_sk_act_aufzug.

    Code:
    begin kf_sk_aufzug_rufknopf_script
    
    short state
    float timer
    
    if ( MenuMode == 1 )
      return
    endif
    
    if ( state == 0 )				;Aufzug wartet auf Aktivierung
    
    	if ( OnActivate == 1 )
    		set state to 1
    	endif
    
    elseif ( state == 1 )		;Aufzug fährt hoch
    
    	if ( GetPos, Z < 300 )
    		[B]"kf_sk_aufzug"->[/B]MoveWorld, Z, 50
    	else
    		set state to 2
    	endif
    
    elseif ( state == 2 )		;Aufzug hält oben und wartet auf erneute Aktivierung
    
    	if ( OnActivate == 1 )
    		set state to 3
    	endif
    
    elseif ( state == 3 )		;Aufzug fährt runter
    
    	if ( GetPos, Z > 0 )
    		[B]"kf_sk_aufzug"->[/B]MoveWorld, Z, -50
    	else
    		set state to 0
    	endif
    
    endif
    
    end

  • Bonusaufgabe: Erstellt einen Aufzug, der 5 verschiedene Haltepunkte besitzt. an jedem dieser Haltepunkte soll es einen Rufknopf geben, mit dem man den Aufzug auf diese Ebene rufen kann. Der Aufzug soll eine Bedientafel besitzen, an der man jede der Zieletagen auswählen kann. Der Aufzug soll von jeder Ebene zu jeder fahren können.

    • Ihr benötigt für meine Version der Lösung 2 Scripte und eine globale Variable.
    • Das eine Script liegt auf dem Aufzug. Das andere liegt auf allen 5 Rufknöpfen.

    Diese Aufgabe benötigt eine etwas umfangreichere Lösung. Zunächst erkläre ich, wie der Aufzug allgemein funktioniert. Danach zeige ich die Scripts für meine Version, die man natürlich beliebig abändern kann, damit sie der eigenen Situation nutzen.

    Natürlich kann man einen solchen Aufzug nicht mehr mit einfachen verschachtelten if-Konstruktionen bauen. Es gibt einfach viel zu viele mögliche Kombinationen. Meine Idee war gewesen, dass der Aufzug weiß in welchem Stockwerk er sich befindet und man ihm nur von außen vorgibt, in welches Stockwerk er fahren soll. Dann habe ich mir überlegt, dass es eigentlich besser ist, wenn man gar nicht einschränkt ob es Stockwerke (mit gleicher Höhe) oder voneinander unabhängige Haltepunkte sind. Deshalb lege ich eine globale float-Variable an, die einfach die gewünschte Zielposition des Aufzugs speichert. Die Rufknöpfe machen nichts anderes als diese globale Variable auf ihre eigene Z-Position (GetPos, Z) zu setzen.
    Das Script des Aufzugs besteht aus 3 Teilen. Der erste Teil wird nur einmal ausgeführt und setzt die globale Variable auf die Startposition des Aufzuges. Sonst würde er sofort irgendwohin fahren. Der zweite Teil ist der Dialog, mit dem der Spieler das Zielstockwerk wählen kann. Auch dieser Dialog macht nichts anderes als die globale Variable auf einen neuen Zielwert zu setzen. Der wichtigste Teil ist der dritte. Hier überprüft der haltende Aufzug, ob ihm ein neues Ziel gegeben wurde. Wenn ja, fährt er zur neuen Zielposition. Sobald er diese Zielposition erreicht hat, wartet er wieder auf eine neue Zielposition.

    In meinem Beispiel heißt die globale Variable kf_sk_dwr_aufzug_global und sie ist vom float-Typ. Ihr Startwert ist egal.

    Dieses Script liegt auf den Rufknöpfen:
    Code:
    begin kf_sk_dwr_rufknof_script
    
    float zpos
    
    if ( OnActivate == 1 )
    	set zpos to GetPos, Z
    	set zpos to zpos - 208        ;bereinigt die Zielhöhe um die Höhe des Schalters über dem Fußboden
    	set kf_sk_dwr_aufzug_global to zpos
    endif
    
    end

    Dieses Script liegt auf dem Aufzug:
    Code:
    begin kf_sk_dwr_aufzug_script
    
    short question
    short doonce
    short button
    short state
    float ziel
    float zpos
    
    if ( doonce == 0 )
    	set doonce to 1
    	set zpos to GetPos, Z
    	set kf_sk_dwr_aufzug_global to zpos
    endif
    
    if ( MenuMode == 1 )
    	return
    endif
    
    if ( question == 0 )
    	if ( OnActivate == 1 )
    		set question to 1
    		MessageBox, "In welches Stockwerk wollt Ihr fahren?", "1", "2", "3", "4", "5", "Abbrechen"
    	endif
    else
    	set button to GetButtonPressed
    
    	if ( button == 5 )
    		set question to 0
    		return
    	elseif ( button >= 0 )
    		set question to 0
    		set zpos to button * 384
    		set zpos to zpos + 14000
    		set kf_sk_dwr_aufzug_global to zpos
    		return
    	endif
    endif	
    
    if ( state == 0 )										;Aufzug wartet auf neues Ziel
    	if ( ziel < kf_sk_dwr_aufzug_global )
    		set state to 1
    		set ziel to kf_sk_dwr_aufzug_global
    	elseif ( ziel > kf_sk_dwr_aufzug_global )
    		set state to 2
    		set ziel to kf_sk_dwr_aufzug_global
    	endif
    elseif ( state == 1 )								;Aufzug fährt nach oben
    	if ( GetPos, Z < ziel )
    		MoveWorld, Z, 100
    	else
    		SetPos, Z, ziel
    		set state to 0
    	endif
    elseif ( state == 2 )								;Aufzug fährt nach unten
    	if ( GetPos, Z > ziel )
    		MoveWorld, Z, -100
    	else
    		SetPos, Z, ziel
    		set state to 0
    	endif
    endif
    
    end
 
Zuletzt bearbeitet:
  • Like
Reaktionen: Zeitgeist