Skyrim:Unterschiede zur vorherigen Scripterstellung
Inhaltsverzeichnis
Was ist neu?
Papyrus, die neue Scriptsprache, ähnelt in vieler Hinsicht der alten Scriptsprache, die in den Spielen von Bethesda Game Studios. Bedingt durch die Einführung erweiterter Funktionalität und flexibilität gibt es jedoch viele signifikante Änderungen in Syntax und Workflow sowie gelegentlich in der Funktionalität. Diese Seite ist für Leute, die mit den Konzepten eine Scriptsprache vertraut sind und ein bischen darüber wissen, was Pypyrus ist. Falls das für dich nicht zutrifft, lies bitte erst die Einführung in Papyrus.
Eine technisch und detailierte Erkläfung der neuen Scriptsprache bietet die Papyrus Sprachreferenz.
Neue Konzepte
Die neue Scriptsprache bietet mehr möglichkeiten. Dies bedeutet, dass die Menge dessen, was du über die neue Scriptsprache wissen musst, anwachsen wird. Auf der Habenseite steht, dass bestimmte Ergänzungen der Scriptsprache dafür sorgen, dass Dinge viel schneller und leichter zu machen sind. Unter diesen neuen Ergänzungen sind es eigene Funktionen zu machen, die Möglichkeit, Timer zu nutzen, Schleifenlogik und Zustand, die Möglichkeiten zur Vereinfachung des Lebens. Ich selbst habe es als so empfunden, dass es beim Scripten einfacher ist, geradlinig zu denken. Es gibt viel weniger Rück- und Vorwärtssprünge zwischen GameMode und anderen Ereignissen. Ich finde, das ich ebenfalls weniger Variablen verfolgen und enable/disablen muss.
Ersetzen des GameModes
Timer
Im alten System bedeutete ein Event (Ereignis) ablaufen zu lassen, dass das gesamte Script sofort und vollständig durchlaufen wurde. Wenn man nicht wollte, dass Teile des Scripts durchlaufen wurden, wurden If-Anweisungen gebraucht, mit deren Bedingungen dann Teile des Scripts ausgeklammert wurden. Wenn du Teile des Scripts später laufen lassen wolltest, musstest du mit einer Bedingung einen Timer GameMode-Block „anstellen“ getSecondsPassed laufen lassen, bis dann ein anderer Abschnitt des Scripts laufen konnte.
Im neuen Systen kannst du latente Funktionen nutzen (so wie die Wait() Funktion), um das Scipt zeitweise pausieren zu lassen. Wenn die Pause vorbei ist (das wird üblicherweise irgndewon von dir definiert) wird das Script wieder anfangen zu laufen und an genau an der unterbrochenen Stelle weitermachen.
Einige latente Funktionen: <papyrusscript>
- wartet 5.1 Sekunden
Wait( 5.1 )
</papyrusscript>
<papyrusscript>
- Dies spielt eine Animation und wartet dann auf ein Animations-Event um fortzusetzen
- Dies Animations-Event wird durch die Havok-Verhaltensweise des Akteurs ausgelöst.
PlayAnimationAndWait( "AnimationName", "EventName" ) </papyrusscript>
Beachte bitte, dass du die Utility-Library am Anfang deines Scripts importieren musst, <papyrusscript> import utility </papyrusscript> wenn du Wait() benutzen willst.
Ersatzweise kannst du
<papyrusscript> utility.Wait(5.1) </papyrusscript>
ohne etwas zu importieren eingeben.
♥ und danke an unsere Freunde bei Bethesda für solch ein wunderbares Spiel und beeindruckendes Creation Kit!
• Hinweis: du kannst Libraries Game and Debug ebenfalls laden, so dass du zum Beispiel nur getplayer() und messageBox("happy msg") statt game.getPlayer() und debug.messageBox eingeben musst.
Schleifen
Im alten System konntest du eine Schleife konstruieren, indem du eine Reihen von IF-Anweisungen machtest und einen Zähler inkrementell änderst, bis eine bestimmte Bedingung (üblicherweise, indem der Zähler gebraucht wird) nicht mehr länger wahr ist.. Im neuen System kann man Schleifen-Anweisungen innerhalb jedes gewünschten Events nutzen (bisher ist ein „While“ implementiert). Solange der dem While folgende Ausdruck wahr ist, läuft die Schleifen.
<papyrusscript> While counter <= 100 ;mache etwas 100 mal
... counter += 1 ;Kurzform für "counter = counter + 1"
EndWhile </papyrusscript>
OnUpdate
OnUpdate ist ein Event, dass an deine „Form“ gesendet wird, wenn und nur dann wenn es angefragt ist. Es wird in regelmäßigen Abständen gesendet, die von dir definiert werden (vergleichbar wie der Gamemode-Block bei Quests mit der Delay-Option arbeitet). Beachte, dass jedes Script, welches mit der Form verbunden ist, Update-Events empfängt. Wenn z.B. dein Queststage-Fragment für Updates angemeldet ist, empfängt dein Questscript ebenfalls die Update-Events. Damit deine Form beginnt, Update-Events zu empfangen, muss es dafür eingetragen werden.
<papyrusscript> RegisterForUpdate(10.0) ;frage alle 10 Sekunden nach Updates </papyrusscript> Wenn du nicht länger Updates empfangen möchtest, melde dich ab: <papyrusscript> UnregisterForUpdate() </papyrusscript> Wenn du ändern möchtest, wie oft dir Updates gesendet werden, melde dich einfach mit der neuen Zeitspanne neu an.
Bitte schau in der OnUpdate-Beschreibung nach, denn dort befinden sich einige Warnungen, von denen ein Scripter Kenntnis haben sollte.
Ein Script durchläuft mehrere Instanzen des selben Events
Events, wie OnActivate, liefen in einem einzigen Frame. Nach der Einführung von Multithreading, Schleifen und latenten Funktionen wie Wait(), kann das Abarbeiten eines einzelnen Events eine längere Zeit dauern. Das führt zu der Frage: „was passiert, wenn ich ein OnTriggerEnter-Event auslöse, bevor die Abarbeitung des vorherigen OnTriggerEnter-Event erledigt ist?“ Es stellt sich heraus, dass es einem einzigen Script möglich ist, mehrfache Instanzen eines einzelnen Events abzuarbeiten. Um dies zu vermeiden, kannst du Status- oder Control-Variablen benutzen, um zu ändern oder abzuschalten, was passiert, wenn ein Event aufgerufen wird, bevor die Abarbeitung einer anderen Instanz des selben Events abgearbeitet ist.
Keywords
Es gibt ein paar Keywords (Schlüsselwörter), die aus dem einen oder anderen Grund sinnvoll sind.
- None - dies kennzeichnet eine Referenz, die im Prinzip leer ist.
<papyrusscript> refVariable01 = GetLinkedRef() if refVariable01 != None
;mache etwas mit refVariable01 weil GetLinkedRef() etwas zurück gegeben hat
endif </papyrusscript>
- Self - eine Variable, die auf die derzeitige Instanz des Scripts verweist. Es ist mit der getself-Funktion aus der alten Scriptsprache vergleichbar nur gibt es die Referenz des Scripts und nicht die Referenz des Objekts, auf dem das Script läuft.
- Parent - ein besonderes Keyword, das man beim Aufruf einer Funktion benutzen kann, um sicher zu stellen, dass du die Funktion aufrufst, die als Funktion in einem Parent-Script definiert ist und nicht die Funktion im lokalen Script.
- Hidden - verbirgt ein Script oder eine Property vor dem Editor
- Conditional - markiert ein Script oder eine Variable als sichtbar für das Conditional-System. Beachte, dass das Script für alle Variablen innerhalb als conditional markiert sein muss, damit es sichtbar ist. Du darfst nur ein Conditional-Script zur Zeit mit einem Objekt verbinden.
- True - wird für Boolesche Werte anstelle von 1 benutzt
- False - wird für Boolesche Werte anstelle von 0 benutzt
Verschiedenes
- Scriptvariablen können mit einem Anfangswert mit folgender Syntax initialisiert werden: "= <value>"
Beispiel: <papyrusscript> int myVar = 20 </papyrusscript>
- Funktionsvariablen können mit einem kompletten mathematischen Ausdruck anstatt nur eines einfachen Wertes initialisiert werden.
- Scripts sind Typen – also wenn du eine besondere Scriptfunktion aufrufen willst, oder auf eine Property des Scripts zugreifen willst, musst du zuerst das Objekt, das du im Script hast, auf das Script casten, das du erwartest.
- Casting zwischen Typen nutzen die "<genericType> as <specificType>" Syntax. Wenn du versuchst, eine Variable in etwas zu casten, was sie nicht ist, gibt es einen "None"-Wert.
Beispiel: <papyrusscript> Function DoFunnyDance(Actor target)
FunnyDanceScript targetScript = target as FunnyDanceScript if targetScript != None targetScript.doingFunnyDance = 1 targetScript.DoFunnyDance() else Print("kann keinen funny dance machen, weil der Actor nicht das richtige Script hat") endIf
endFunction </papyrusscript>
- Alles kann als Boolean behandelt oder gecastet werden.
- Int and float: Nicht-Null-Werte sind true, Null ist false.
- Strings: nicht-leere Strings sind true, leere Strings sind false.
- Objects: nicht-none Objects sind true, none-Objecse sind false.
- Arrays: nicht-none-Arrays oder Arrays mit einem Element oder mehr sind true (sogar wenn die Elemente selbst false sind). None-Arrays oder Arrays mit 0 Elementen sind false.
- Alles kann als String gecastet werden. Zahlen werden in eine Stringdarstellung konvertiert, boolesche Variablen werden einfach zu einem "true" oder "false"-Text und Objects werden zu einem lesbaren Format, das im Detail den Scriptnamen und das aktuelle zugeordnete Ingame-Objekt enthält.
- Strings können als Zahlen gecastet werden, wenn der String eine Zahl repräsentiert.
- Wenn der Compiler automatisch und ohne die Möglichkeit eines Fehlers casten kann, wird er das tun. Die folgenden Casts werden automatisch gemacht.
- Int -> Float
- Anything -> String
- Anything -> Bool
- Object -> Parent Object
- Alles kann als Boolean behandelt oder gecastet werden.
- Ein Object kann nur als ein Child Object gecastet werden, wenn es gerade eines ist. Mit anderen Worten, jede Form, die auf diese Weise castet, benötigt ein angehängtes Script, das den Formeditor nutzt.
- Du kannst den "+"-Operator zum Zusammenfügen von Zeichenketten benutzen
Beispiel: <papyrusscript> string Hello = "Hello" string World = "World" Trace(Hello + " " + World) ; schreibt "Hello World" in das Log </papyrusscript>
- Der "!"-Operator wurde hinzugefügt und steht für "not". Der Ausdruck "!value" ist true wenn und nur wenn value den Wert false hat.
- Hexadezimal-Zahlen beginnen mit "0x" – üblicherweise sind dies FormIDs. Wenn du z.B. die FormID 00012EC7 möchtest, ist 0x00012EC7 zu benutzen (die führenden Nullen sind nicht erforderlich)
- Operatoren wie "+=" and "-=" wurden hinzugefügt. Sie funktionieren folgendermaßen:
<papyrusscript> myNumber += 1 myFloat -= 2 + 4 * player.getAV("repair") </papyrusscript> Ist gleichbedeutend mit: <papyrusscript> myNumber = myNumber + 1 myFloat = myFloat - (2 + 4 * player.getAV("repair")) </papyrusscript> Alle mathematischen Operatoren haben vergleichbare Versionen: +=, -=, *=, /=, and %=
Syntax-Änderungen
Funktionsparameter werden nun in Klammern gesetzt.
PlayGroup Forward 1
wird nun <papyrusscript> PlayAnimation(Forward) </papyrusscript> Diese Syntax-Änderung sollte einiges etwas klarer machen. Es sollte auch den Gebrauch von Nutzer-Definierten Funktionen weniger verwirrend machen.
Zuweisungs-Syntax
set variable01 to 25
wird nun <papyrusscript> variable01 = 25 </papyrusscript> Diese neue Syntax ist ein gebräuchlicher Weg von Zuweisungen.
Event Declaration Syntax
Begin OnTriggerEnter Player ... End
wird nun <papyrusscript> Event OnTriggerEnter( Actor akTriggerer )
...
EndEvent </papyrusscript> Wieder ist dies ein klarerer Weg, Dinge zu tun. "Event" macht es klarer, dass du ein Event startest und "EndEvent" macht es klarer, dass du das Event beendest. Events sind im Grunde das Gleiche wie benutzerdefinierte Funktionen, außer dass etwas, das im Spiel passiert, auslösen kann, dass das Event erfolgt.
Object References benötigen Properties
BrotherVerulusRef.GetActorValue Health
wird nun <papyrusscript> Actor Property akBrotherVerulus Auto
...
akBrotherVerulus.GetActorValue("Health") </papyrusscript> Wobei akBrotherVerulus durch den Property Manager innerhalb des CK gesetzt wird. Zu mehr über Properties siehe unten.
Änderung der Funktionalität
Performance
Es gibt ein paar Dinge die man im Gedächtnis behalten sollte, wenn man an die Performance denkt. Mit der alten Scriptsprache konnte man mit ineffizienten Scripts eventuell die Framerate des Spiels runter drücken. Mit der neuen Sprache hat jedes Script eine zugeteilte Menge an Verarbeitungszeit. Nachdem diese Zeit verbraucht ist, wird das Script pausieren, bis es mehr Verarbeitungszeit bewilligt bekommt. Diese Verarbeitungszeit wird durch Funktionsaufrufe und Logik durch die Scriptsprache gemessen. Du wirst mit einem schlecht geschriebenen Script wahrscheinlich eher alle Scripts ausbremsen als die Framerate des Spiels runter zu drücken.
Timing
Mit der neuen voll Sprache, mit der Threads unabhängig voneinander ablaufen können, ist die Zeitabstimmung nicht so zuverlässig wie sie im vorigen System war. Vorher konnte ein Script sagen „ich möchte dies sofort tun“ und das Verwaltungssystem hatte keine andere Wahl als zuzustimmen. Ein Script in der neuen Sprache kann sagen „ich möchte dies sofort tun“ und der Scriptverwalter wird im Wesentlichen sagen, „ziehe eine Nummer und warte in der Reihe“. Einem Script zu sagen, es möge 0,5 Sekunden warten, bedeutet in Wirklichkeit, dass das Script für 0,5 Sekunden wartet und zusätzlich die Zeit, die es für das Script dauert, die zugeteilte Nummer angesagt zu bekommen. Dieser Zeitzuschlag ist üblicherweise ziemlich klein, es könnte aber Bedeutung für Scripts haben, wo das Timing sehr präzise sein muss.
Setup
Zeugs mit dem Editor anhängen
Gegenwärtig können Scripts an Referenzen, Quests, Aktivatoren und andere Typen im Editor angehängt werden. Öffne das Edit Window. Du entdeckst entweder einen „Scripts“-Tab oder eine Scriptliste im Hauptdialog. Dann klicke den „Add Script“ Button, um eine aus der Liste von Scripts auszuwählen und dem Object hinzu zu fügen. Du kannst einmal auf das Script doppelklicken, sobald es hinzugefügt wurde (oder klicke auf den Properties-Button), um das Properties Window zu öffnen. Properties sind im Grunde Variablen, die im Editor durch eine Instanz des Scripts auf die Referenz gesetzt werden können. Mehrfache Scripts können dem selben Object hinzu gefügt werden. Wenn du ein Script einem Base Object hinzu fügst, werden alle Referenzen des Objects jene Scripts erhalten sowie dessen Properties. Man kann jedoch diese Einstellungen an genau der bestimmten Referenz einfach überschreiben, wenn man möchte, das die ein bisschen anders sein sollte als die anderen.
Properties
Bevor direkt in das Thema eingestiegen wird, sollte bemerkt werden, dass in der neuen Scriptsprache Variablen, die in einem Script deklarierte werden, „private“ sind. Das bedeutet, dass man, wenn man nicht innerhalb des Scriptes ist, diese Variable nicht setzen kann. Man kann nicht einmal sehen, welche Werte sie haben. Properties sind im Grunde Variablen, außer dass auf sie von außerhalb zugegriffen werden kann. Sie können sogar im Editor von jeder Referenz gelesen und beschrieben werden. Ziemlich nett, wenn man mich fragt.
<papyrusscript> int property NumShots auto </papyrusscript>
Dies ist ein Beispiel wie eine Property in einem Script deklariert werden kann. NumShots ist der Name der Property, es ist ein integer und der "auto"-Zusatz ist einfach eine schnelle Möglichkeit, eine Property zu definieren. "Auto" lässt dich den Wert von außen sehen und ändern. Und man kann es innerhalb des Editors setzen, in dem man auf das Reference Window geht, dort auf Scripts Tab-> Script Name-> Properties, was einem die Propertiy zeigt und es ermöglicht jeden von gewünschte Int-Wert einzutragen. Man kann das Property-Manager-Window ebenfalls erreichen, wenn man auf ein Script doppelklickt, das mit dem Object verbunden ist, oder durch Rechtsklicken auf das Object und Auswahl von Edit Properties Wenn man eine Property mit einem Objekt-Typ hast und die so nennst, wie ein bestimmtes Object im CK, wird das CK automatisch es automatisch füllen, wenn es das Script an ein Objekt angehängt wird. Wenn Objekt später hinzu gefügt wird, kann das CK es immer noch selbst füllen, indem der Auto-Fill-Button im Property Manager gedrückt wird. Es ist ein längerer Weg, auch Properties zu deklarieren aber das ist sinnvoll, wenn man spezielle Dinge tun will, also andere Scripte die Property sehen aber nicht ändern lassen oder Gültigkeitsgrenzen für die Eingabe definieren oder aber beim lesenden oder schreibenden Zugriff ein Stück Script durchlaufen lassen
<papyrusscript>
int shotCount = 20 ;dies ist eine interne Variable. Sie kann von außen nicht gesehen werden.
; Voreinstellungen sind so gesetzt.
int Property NumShots ;Das wird extern gesehen
int Function get() return shotCount endFunction Function set(int value) if (value > 50) ;Wert zu groß, es wird auf 50 reduziert ;und eine Warnung in den Trace geschrieben. debug.trace ( "Versuch, NumShots auf über 50 zu setzen. Es wurde auf 50 reduziert." ) shotCount = 50 elseIf (value < 1) ; Der Wert kann nicht kleiner eins sein debug.trace ( "Der Wert kann nicht kleiner eins sein. Der Wert wird nicht verändert." ) else ;alles ok, es wird nur shotCount auf den Wert gesetzt shotCount = value endIf endFunction
endProperty </papyrusscript>
Links
- Differences from Previous Scripting (das englische Original des obigen Artikels)