Jass Einführung (Mueslirocker)

[nocontentad]

Jass Einführung


zum Anfang


1. Einleitung

Jass ist eine Scriptsprache.
Mit ihrer Hilfe formuliert, speichert und interpretiert Warcraft 3 seine Auslöser.
Der User bekommt normalerweise gar nichts von diesem Script mit, da er Auslöser oder Trigger über eine Maske, das GUI (Graphical User Interface), erstellen kann.
Dies ist eine Möglichkeit, seine Trigger einfacher zu formulieren.
Beim Speichern der Map werden die über das GUI erstellten Trigger jedoch in ein großes Jass-Script formatiert.


zum Anfang


2. Einen Trigger umwandeln


Hier erstmal ein Trigger, wie ihn jeder kennen sollte, mit Ereigniss, Bedingun und Aktionen.
Den Trigger markiert können wir ihn im Menü unter Bearbeiten, Zu eigenem Text umwandeln direkt in Jasscode umwandeln.

Hier sei erwähnt, dass ein Trigger, den man von dem GUI gelöst hat, nicht wieder zurück gewandelt werden kann.
Also wenn ihr euch nicht sicher seid, ob ihr den Trigger mit GUI noch braucht, macht ein Backup, in dem ihr von dem Trigger einfach eine Kopie anfertigt und diese deaktiviert.

zum Anfang


3. Jass besteht aus…

Als erstes fällt auf, dass Jass im Prinzip nur aus Funktionen besteht – der Kenner merkt, dass Jass ein Stück weit an eine Programmiersprache wie Java angelehnt ist.

Eine Funktion kann aufgerufen werden und dabei Parameter übergeben bekommen und einen Rückgabewert zurück liefern.
Innerhalb einer Funktion können Variablen deklariert und gesetzt werden, Schleifen durchlaufen werden, If-The-Else-Verzweigungen beschrieben und wieder andere Funktionen aufgerufen werden.
Dabei werden Aktionen (und auch Funktionen wie zB Ereignisreaktionen) wie wir sie per GUI kennen durch Aufrufe von vordefinierten Funktionen beschrieben.

Wer mehr über Funktionen bzw funktionale Programmierung erfahren möchte, sollte ein bisschen googeln gehen.

Es gibt viele Tutorials über das Programmieren – sie enthalten neben Funktionen auch Prozeduren, welche einfach keinen Rückgabewert haben. Die sollen uns aber hier nicht interessieren.

zum Anfang


4. Variablen

Weiter geht es zu den Variablen.
Das sind Platzhalter im Speicher, welche mit einer bestimmten Art von Wert gefüllt werden können, so dass man später wieder auf sie zugreifen kann.

Was für Werte man speichern kann, bestimmt ihr Typ, der bei der Deklaration fesgelegt wird.
Es gibt primitive Datentypen, wozu zum Beispiel die ganzzahligen Zahlen vom Typ Integer, die Fließkommazahlen vom Typ Real und boolsche Ausdrücke vom Typ Wahrheitswert (Boolean – Werte: wahr (true) oder falsch (false)) gehören.
Aber es gibt auch die Möglichkeit, Objekte zu speichern.

So zum Beispiel Einheiten, Spezialeffekte, Sounds, Gebiete,…
Diese Objekte bestehen im Endeffekt wieder auf primitiven Typen.
Eine Einheit definiert sich über ihren Typ (was im Endeffekt ein Integer ist), ihren Namen (String), ihre Position (2 Reals), ihre derzeitige Animation (String),… woraus ein Objekt besteht ist für uns aber wiedermal fürs Erste uninteressant.
Noch ein Hinweis: diese Objekte stellen manchmal einen Speicherleck dar, da in vielen Fällen Objekte erstellt aber niemals wieder gelöscht werden. Wenn man zB einen Punkt (Typ location) braucht, wo eine Einheit hinlaufen soll, wird dieser Punkt erstellt, aber man hat später nichtmal die Möglichkeit, auf ihn zuzugreifen, da man ihn nirgends gespeichert hat.
Lösung wäre, den Punkt erst in einer Variable zu speichern und diese Variable dann in dem move-Befehl zu benutzen.

So kann man das Punkt-Objekt danach wieder löschen.

Einige kennen bisher nur die Variablen, die man in dem GUI beim gelben X deklarieren kann.
Diese werden außerhalb der Trigger im Script deklariert.

Sie sind global und somit von überall aus errreichbar.
Damit ihr Name einmalig ist, packt das GUI noch ein Präfix namens udg_ dazu.
Eine globale Variable, die ihr meineVariable genannt habt, heißt also eigentlich udg_meineVariable.
Mit dem GUI kriegt man natürlich auch davon nichts mit, während man nun im Jass Script das udg_ nicht vergessen darf.

Die Variablen, die man in einer Funktion deklariert, sind lokal.

Sie sind nur von dieser einen Funktion aus zugänglich und bringen somit Vor- und Nachteile mit sich. Dazu später mehr in einem anderen Tutorial.


zum Anfang


5. Elemente einer Funktion

Schauen wir uns nun die Syntax einer Funktion an.

function foo takes nothing returns nothing
endfunction

Das ist eine leere Funktion.
Die Befehle einer Funktion werden normalerweise von oben nach unten von function bis endfunction abgearbeitet.

Funktionen kann man mit dem Befehl call aufrufen.

call foo()

zum Anfang


5.1. Parameter und Rückgabewert

Hinter takes kann man Parameter, die der Funktion übergeben werden müssen, angeben.

Diese sind dann bei Aufruf der Funktion wie lokale Variablen zu behandeln.

Das return gibt den Rückgabewert der Funktion an.

Wenn eine Funktion zB etwas berechnen soll, kann man so das Ergebnis zurück geben – und zwar an die Funktion, die jene Funktion aufgerufen hat.

function bar takes integer i returns integer
    return i + 1
endfunction

Die Funktion benutzt nun einen Parameter vom Typ Integer und gibt diesen um eins erhöht wieder.

Nach einem return wird die Funktion unweigerlich abbrechen.
Aktionen, die nach einem return stehen, machen also nur Sinn, wenn das return nur bedingt erreicht wird, weil es z.B. in einem If-Zweig verschachtelt ist.

Eine Funktion mit Rückgabewert muss auf jeden Fall (bei allen möglichen Verzweigungen) irgendwann auf ein return treffen.

Eine Funktion ohne Rückgabewert kann auch ein return beinhalten.
Dahinter muss nichts mehr stehen.
Dadurch wird nur die Funktion vor Erreichen von endfunction abgebrochen.
Dieses „leere return“ benutzt das GUI, bei der Aktion Skip remaining actions.

Will man auf den Rückgabewert zugreifen, kann man den call-Befehl nicht mehr benutzen.

Stattdessen benutzt man die Funktion wie eine Variable vom Typ ihres Rückgabewertes.
Ist x eine globale Variable vom Typ Integer, kann man so an den Rückgabewert kommen:

set udg_x = bar(5)

Somit würde 5+1 = 6 in x gespeichert werden.
Nun habe ich schon den nächsten Befehl genannt.
set wird wie call benutzt, nur dass es nicht vor dem Aufruf einer Funktion sondern vor dem Setzen einer Variable steht.

Nach meinen Erfahrungen stürzt der WE ab, wenn man das set vergisst und dann versucht zu speichern.

Anmerkung zur Syntax:

Eine Funktion kann nur Funktionen aufrufen, die im Script über ihr liegen!

zum Anfang


5.2. Deklaration von Variablen

Die Deklaration von Variablen ein einer Funktion muss als erstes in der Funktion geschehen.

Darüber dürfen nur Kommentare (durch ein // am Zeilenangfang gekennzeichnet) und andere Deklarationen stehen.

Die Deklaration einer Variable namens e vom Typ effect (Spezialeffekt) sieht also so aus:

function foo takes nothing returns nothing
    local effect e
    //...
    set e = ...
endfunction

Man kann der Variable auch direkt bei der Deklaration einen Wert zuweisen.

local integer i = 22

Der Wert kann natürlich auch wieder der Rückgabewert einer Funktion sein.

zum Anfang


5.3. Schleifen

Kommen wir zu den Schleifen.
Sie umrahmen Teile in einer Funktion mit den Tags loop und endloop.
Die Aktionen dazwischen, werden immer wieder ausgeführt.

Damit die Funktion auch mal fertig wird, sollte man noch eine Abbruchbedingung in die Schleife einführen.
Das funktioniert mit dem Befehlt exitwhen. Dahinter muss ein boolscher Ausdruck stehen.

Ist dieser Ausdruck wahr, wird die Schleife abbgebrochen.
Man kann beliebig viele exitwhen’s in eine Schleife bauen.

function foo takes integer i returns integer
    local integer j = 10

    local integer e = 0
    loop
        set e = e + i*j

        exitwhen i < 0
        set e = e + 1

        set j = j - 1
        set i = i - 1

        exitwhen j <= 0
    endloop
    return e
endfunction

Was diese Funktion im Endeffekt berechnet, ist nicht wirklich relevant.
Sie soll nur ein Beispiel für die Anwendung von Schleifen sein.
j <= 0 und/oder i < 0 werden irgendwann wahr sein, da i und j in jedem Schleifendurchlauf echt verkleinert werden.

Wir haben also keine Endlosschleife… das sollte man immer sicher stellen (und das ist nicht immer so einfach, wie in diesem Beispiel).


zum Anfang


5.4. If-Then-Else

Als letztes Element einer Funktion möchte ich If-Verzweigungen ansprechen.
Wenn man im Laufe eines Triggers auf einige Aktionen trifft, die nur ausgeführt werden sollen, falls eine bestimmte Bedingung erfüllt ist, kann man das mit einer solchen Verzweigung erreichen.

Man kann die If-Verzweigung in 3 Teile partitionieren.

  1. If-Bedingung

    Dort kommt die Bedingung hin, die gelten soll.
    Um mehrere Bedingungen zu verkünpfen, benutzt man die Ausdrücke and und or.

  2. Then-Aktionen
    Dort kommen die Aktionen hin, die ausgeführt werden sollen, wenn jene Bedinung erfüllt ist.
  3. Else-Aktionen
    Der Teil kann auch weg gelassen werden.

    Dort kommen die Aktionen hin, die ausgeführt werden sollen, wenn jene Bedingung nicht erfüllt ist.

function foo takes integer i returns boolean
    return i > 0
endfunction

function bar takes nothing returns nothing
    local boolean b = foo(5)
    local integer i = -1
    if (b or (i = 0)) and true then
        set i = 1
    else
        set i = -1
    endif
endfunction

Sieht etwas verwirrend und vor allem unnütz aus.

Ist es auch, aber ich will damit nur die Möglichkeiten aufzeigen, wie man syntaktisch richtige Ausdrücke erstellen kann.


zum Anfang


6. Aufbau eines Triggers

Nun haben wir die Eigenschaften einer Funktion geklärt und wollen uns einem Trigger widmen.

Wir identifizieren mehrere Arten von Funktionen bei unserem Trigger.

Ganz unten entdecken wir eine Funktion namens InitTrig_Triggername.

Als wir den Trigger mit dem GUI erstellt haben, wurde eine Variable namens gg_trg_Triggername erstellt und dem startenden Script wurde jene Init-Funktion zugeordnet.
Die Trigger Variable ist zunächst leer.
Erst wenn die Map gestartet wird, wird über diese Init-Funktion Ereignisse, Bedingungen und Aktionen hinzugefügt.

Dort wird nun als erstes ein neuer Trigger erstellt und in der Variable gespeichert.
set gg_trg_test_trigger = CreateTrigger()

Das -Ereigniss, dass wir per GUI eingefügt haben, wird mit der Funktion
TriggerRegisterEnterRectSimple()

dem Trigger hinzugefügt.

Parameter ist natürlich das Gebiet, aber auch der Trigger, welcher das Ereigniss bekommen soll.

Bedingungen und Aktionen werden dem Trigger etwas anders hinzugefügt.
Als Bedingungen gibt man Funktionen an, die als Rückgabewert einen Wahrheitswert liefern.
Darin werden dann die Bedingungen ausformuliert.

In unserem Fall ist die Bedingung in der Funktion Trig_test_trigger_Conditions.
Diese Bedingungs-Funktion wird dem Trigger in der Init-Funktion mit der Funktion TriggerAddCondition hinzugefügt.

Parameter ist auch hier der Trigger selbst und darüber hinaus die Funktion, welche den Wahrheitswert liefert.
(Das Condition() um die Bedingungsfunktion lassen wir mal außer Acht. Dort wird nur der Typ gecastet.)

Die Aktionen des Triggers werden ebenso wie die Bedingungen in Form von Aktionen hinzugefügt.

Die Hauptfunktion, die das GUI gebastelt hat, heißt in unserem Beispiel Trig_test_tirgger_Actions.
Wenn ihr aber ganze Trigger ohne GUI bastelt, könnt ihr eure Bedingungs- und Aktions-Funktionen natürlich beliebig benennen.
Das GUI macht das nur so, damit keine zwei Funktionen den gleichen Namen bekommen.

Nun sei noch erwähnt, dass das GUI bei der Umwandlung von mehreren Bedingungen, die u.U noch in einander verschachtelt sind, ein ziemliches Durcheinander baut.
Aus A and B wird da schonmal

function cond1 takes nothing returns boolean
    if not B then
        return false
    endif
    return A
endfunction

Und es geht noch schlimmer.
Das gleiche Problem existiert übrigens bei den Bedingungen einer If-Abfrage.
Statt i > 0 wird wieder eine neue Funktion gebaut, die nichts tut, als eben das möglichst kompliziert zu prüfen.

Zu umgehen ist diese Verwirrung in dem man sich die Funktionen, die man braucht, heraus sucht und die Funktionen für Bedingungen selbst erstellt.


zum Anfang


7. Der Header

Will man Funktionen in mehreren Triggern benutzen, kann man diese seit TFT in den Header der Map schreiben.
Auf diesen greift man wie auf einen Trigger zu; nur dass man im Triggereditor in der Baumstruktur links nicht auf einen Trigger klickt, sondern auf die Wurzel, welche den Namen der Karte trägt.


zum Anfang

8. Was man trotz Jass nicht sieht

Mit Programmen wie mpq2k oder winmpq kann man aus einer Karte das Trigger-Script extrahieren.

Das Script ist unter war3map.j oder Scripts\\war3map.j zu finden.

Ihr könnt auch einen Syntaxfehler (zB in den Header) einbauen – ein unsinniges Wort genügt – und in der Fehlermeldung das Script anschaun.

Das Script ist also wie folgt aufgebaut (Ich lasse die automatisch erstellten Kommentare weg):

Als erstes findet man die Deklaration der globalen Variablen – darunter auch die Trigger und Gebiete – alles eingebettet in den global– und endglobal-Tag.

Dann findet man die Funktion InitGlobals, welche Arrays bis zur gewünschten Größe und Objekte wie Timer oder Einheiten initalisiert.

Die Funktion CreateRegions initalisiert dann noch die Gebiete.

Als nächstes kommt der Scriptcode des Headers. Natürlich vor allen anderen Funktionen, damit man von überall darauf zugreifen kann.
Danach werden die Initalisierungs- Bedingungs- und Aktions-Funktionen aller Trigger ins Script gefügt.
Man könnte also auch Funktionen von einem Trigger über dem derzeitigen aufrufen. Allerdings ist der Header wohl dafür besser geeignet.
Als letztes kommen noch einige Initalisierungs-Funktionen, die die Teams festlegen, Startpositionen setzen und all son Zeug machen.
Uns interessiert nur noch eine dieser Funktion – nämlich der Anfang von allem: function main.

Von ihr werden – neben anderen Initalisierungen – die globalen Variablen durch den Aufruf call InitGlobals( ) intialisiert und die Trigger durch den Aufruf call InitCustomTriggers( ).


zum Anfang

9. RTFM ;)

Ein Problem, das man am Anfang hat, ist, sich die ganzen Schlüsselwörter (neben call, set, local,..) wie die Namen der vorgefertigen Funktionen, deren Parameter oder die Namen der Variablentypen zu merken.
Wusstet ihr zum Beispiel, dass die GUI mehr Variablentypen benutzt, als es wirklich gibt?

Einheiten-Typ oder Item-Type sind zwei Beispiele. Im Jasscode sind das nur noch Integers.
Um einen Überblick über die Möglichkeiten zu bekommen, ist das Manual sehr empfehlenswert.


zum Anfang


Schlusswort

Was ich erstaunlich finde, ist, dass man im Prinzip ein ganzes Script mit beliebig vielen Triggern ohne den Triggereditor erstellen.
Also, ich hoffe, nun wurde etwas Licht in die Welt Jass gebracht.

Vielleicht konnte ich den ein oder anderen ermutigen, sich etwas mehr mit Jass zuschäftigen.
Entdecke die Möglichkeiten! ;)

zum Anfang

  • 28.07.2008 um 23:34
Spells mit Jass Erstellen eines Save/Load Systems (alt)