LSL Scripting Teil 3 - Alles über Events 
Samstag, den 18. Oktober 2008 um 14:00 Uhr
Da sind wir wieder mit Teil 3 unseres LSL Scripting Tutorials. Willkommen zurück!
Hiermit führen wir das Tutorial nach Teil 2 fort. Wir lernen heute etwas über Events - welche Events es gibt und was man damit so alles anstellen kann.

Events sind, nach den States, so ziemlich das Wichtigste in einem LSL Script. Okay, eigentlich ist alles wichtig, denn auch ohne Funktionen oder Variablen läuft in einem LSL Script nichts. Aber Events sind ein besonderer Teil eines Scripts. Es sind die "Funktionen", die auf eine Aktion reagieren können und je nach Aktion eine entsprechende Information zurück geben.

Ausserdem haben wir in Teil 1 gelernt, dass sich ein Script in die States aufteilt, diese dann die Events enthalten, welche dann wiederrum die Funktionen beinhalten.

Wir haben gelernt, dass das Event "state_entry()" den Eingang eines States darstellt, der immer aufgerufen wird, wenn ein Script startet, beziehungsweise der State (in unseren Beispielen State "default") gestartet wird, der das Event enthält.

Schauen wir uns noch einmal die grundlegende Struktur eines Scripts an: 

default
{
        state_entry ()
        {
                llSay(0, "Hello, Avatar!");
         }
        touch_start(integer total_number)
        {
                llSay(0, "Touched: "+(string)total_number);
        }
}
Wir haben hier also ein ganz simples Script, das den State "default" enthält, der immer vorhanden sein muss. In diesem State sind zwei Events enthalten, nämlich "state_entry()", welcher sofort aufgerufen wird, wenn das Script aktiv wird und "touch_start()", welcher aufgerufen wird, wenn jemand auf das Objekt klickt, das das Script enthält.

Es gibt zwei Kategorien von Events:
  1. Events die durch eine Aktion von Außen aufgerufen werden
  2. Events, die durch Funktionen innerhalb des Scripts aufgerufen werden
Bei Ersteren werden die Events gestartet, wenn eine Interaktion mit dem Objekt von Außen stattfindet, also zum Beispiel auf das Objekt geklickt wird oder jemand gegen das Objekt läuft (collision). Bei Zweiteren handelt es sich um Events, die durch Funktionen innerhalb des Scripts aufgerufen werden. So wird "state_entry()" durch den Start des Scripts beziehungsweise des States aufgerufen und ist somit ein Event der 2. Kategorie.

Was für Events gibt es?
Ich will hier sicher nicht alle aufzählen und beschreiben. Ich werde mich hier nur auf die nützlichsten und am häufigsten verwendeten Events beschränken.

Fangen wir einmal mit Events aus der 1. Kategorie an, also jene, die durch Aktionen von Außen gestartet werden:
  • touch_start() - Wird gestartet, sobald jemand mit der Maus auf das Objekt klickt, das das Script enthält.
  • collision_start() - Wird gestartet, sobald jemand gegen das Objekt läuft, es also mit dem Avatar berührt. Auch andere Objekte können eine Kollision mit dem Objekt auslösen.
  • land_collision_start() - Wird aufgerufen, sobald das Objekt das das Script enthält mit Linden-Land in Berührung kommt.
  • money() - Wird aufgerufen, sobald jemand dem Objekt Geld gibt, es also bezahlt.
Events aus der 2. Kategorie sind nicht so einfach zu beschreiben. Ich versuche es dennoch einmal:
  • state_entry() - Wird gestartet, sobald das Script den State "betritt", der dieses Event enthält.
  • dataserver() - Ein sehr umfangreiches Event, das auf verschiedene Funktionen reagiert. So kann man damit das Alter eines Avatars erfragen oder auch den Online-Status.
  • email() - Ja, auch email kann ein Objekt empfangen und senden. Jedes Objekt und jeder Avatar in SL hat eine ganz eigene email-Adresse, die sich aus der UUID des Objekts/Avatars und der Domain "@lsl.secondlife.com" zusammensetzt.
  • http_response() - Man kann über eine Funktion eine externe Webseite aufrufen und bekommt dann den Quelltext der Webseite über dieses Event wieder zurück.
  • listen() - Dieses Event wird durch eine Funktion initialisiert (llListen()), reagiert dann allerdings auf Chat-Befehle von Außen. Text, der im Chat geschrieben wird, wird in diesem Event an das Script weitergegeben.
  • on_rez() - Ein sehr nützliches Event, um das Script automatisch zu resetten, wenn das Objekt gerezzt wird. Es wird gestartet, sobald das Objekt gerezzt wird und führt somit darin enthaltene Funktionen in dem Moment aus, wenn das Objekt gerezzt wird.
  • sensor() - Ebenfalls ein sehr umfangreiches Event. Es dient dazu, andere Avatare oder Objekte in der Umgebung (bis max. 96m) zu finden, deren Namen und Keys herauszubekommen oder andere Sachen damit anzustellen.
  • timer() - Ein sehr nützliches Event und sehr oft gebraucht. Dieses Event wird in einem, über "llSetTimerEvent()" definierten Zeitabstand immer wieder aufgerufen.
Das waren erst einmal die wichtigsten und am häufigsten verwendeten Events. Weitere Informationen über Events, welche es gibt und was man damit alles anstellen kann, gibt es hier.

Was können wir nun mit diesen Events anstellen?

So ziemlich alles. Man kann mit diesen Events so ziemlich alles machen, was man sich in SL nur vorstellen kann. Von einfachen Dingen wie die Stoppuhr, die wir in Teil 2 begonnen haben, bis hin zu komplexen Geräten wie ein steuerbares Fahrzeug (Auto/Flugzeug/Boot) oder automatisch einem Pfad folgende Fahrzeuge wie Eisenbahnen. Wir wollen es hier aber nicht übertreiben, jedenfalls noch nicht. Bleiben wir doch erstmal bei unserer Stoppuhr. Diese eignet sich nämlich hervorragend, um die verschiedenen Events zu demonstrieren.
Rufen wir uns den aktuellen Stand des Stoppuhr Scriptes nochmal in Erinnerung:
integer status = 0
integer start;
integer stopp;
default
{
        state_entry()
        {}
        collision_start(integer num_detected)
        {
                if (status == 0)
                {
                        status = 1;
                        start = llGetUnixTime();
                        llSay(0, "Stoppuhr gestartet!");

                }
                else
                {
                        status = 0;
                        stopp = llGetUnixTime();
                        integer timediff = stopp - start;
                        llSay(0, "Zeit: "+(string)timediff+" Sekunden.");
                }
        }
}

Wir haben hier also die bekannte Struktur mit einem "collision_start()" Event, der aufgerufen wird, sobald jemand das Objekt berührt.

Wollen wir das doch mal so umbauen, dass man das Objekt anklicken muss, um die Stoppuhr zu starten und zu stoppen:

integer status = 0
integer start;
integer stopp;
default
{
        state_entry()
        {}
        touch_start(integer total_number)
        {
                if (status == 0)
                {
                        status = 1;
                        start = llGetUnixTime();
                        llSay(0, "Stoppuhr gestartet!");

                }
                else
                {
                        status = 0;
                        stopp = llGetUnixTime();
                        integer timediff = stopp - start;
                        llSay(0, "Zeit: "+(string)timediff+" Sekunden.");
                }
        }
}

Ihr seht richtig. Das Einzige, was sich verändert hat, ist der Name des Events und dessen Parameter. Aus "collision_start" wird ganz einfach "touch_start". Den Parameter hätte man nicht verändern müssen, ich will da aber konform mit dem lslwiki bleiben, damit ihr bessere Vergleichsmöglichkeiten habt.

Probiert das Script einfach einmal aus, ihr werdet sehen, dass jetzt ein Dagegen-Rennen keine Auswirkungen mehr hat. Stattdessen muss man das Objekt anklicken, um den Timer zu starten und nochmal anklicken, um ihn wieder zu stoppen.

Das nächste Beispiel zeigt den Einsatz eines Listeners, der auf einen Chatbefehl reagiert, um die Stoppuhr zu starten und zu stoppen.

Da das allerdings etwas aufwendiger ist, machen wir das in kleineren Schritten und mit Erklärungen:

Zuerst müssen wir einen Channel definieren, in dem wir der Stoppuhr die Befehle geben. Der Listen Event kann auf jedem beliebigen Channel hören, so wie es in SL ja auch (fast) beliebig viele Channels gibt. Channel 0 ist der offene, public Chat, den jeder lesen kann. Alle anderen Channels sind ohne Hilfsmittel von anderen nicht lesbar.

Definieren wir also ganz oben eine neue Ganzzahl-Variable (integer)  mit dem Namen "commandchannel" mit der Channelnummer, auf der wir die Befehle senden wollen. Benutzen wir hier einfach einmal Channel 1:

integer commandchannel = 1
integer status = 0
integer start;
integer stopp;
default
{
        state_entry()
        {}
        touch_start(integer total_number)
        {
                if (status == 0)
                {
                        status = 1;
                        start = llGetUnixTime();
                        llSay(0, "Stoppuhr gestartet!");

                }
                else
                {
                        status = 0;
                        stopp = llGetUnixTime();
                        integer timediff = stopp - start;
                        llSay(0, "Zeit: "+(string)timediff+" Sekunden.");
                }
        }
}

Als Nächstes müssen wir den Listener initialisieren. Das Listen Event ist ein Event der 2. Kategorie, obwohl es genauso gut auch in die erste Kategorie passen würde. Das Event muss zuerst initialisiert werden, bevor es dann anfängt zu lauschen und zu reagieren. Dazu gibt es die Funktion "llListen()".
Da wir wollen, dass das Script sofort nach Starten des Scripts auch anfängt, auf Befehle zu warten, muss die Funktion, die das Event initialisiert, in das Event hineingeschrieben werden, das beim Starten des Scripts aufgerufen wird. Das ist, wie wir wissen, das "state_entry()" Event:

integer commandchannel = 1
integer status = 0
integer start;
integer stopp;
default
{
        state_entry()
        {
                 llListen(commandchannel, "", NULL_KEY, "");
        }
        touch_start(integer total_number)
        {
                if (status == 0)
                {
                        status = 1;
                        start = llGetUnixTime();
                        llSay(0, "Stoppuhr gestartet!");

                }
                else
                {
                        status = 0;
                        stopp = llGetUnixTime();
                        integer timediff = stopp - start;
                        llSay(0, "Zeit: "+(string)timediff+" Sekunden.");
                }
        }
}

Die llListen() Funktion erwartet insgesamt vier Parameter:

  1. channel - Der Channel, auf dem das Listen-Event nach Befehlen lauschen soll
  2. name - Ein String, der den Namen des Avatars oder Objekts enthält, auf den das Listen Event hören soll. Steht hier nichts drin, hört es auf alle.
  3. key - Eine UUID des Avatars oder Objekts, auf den das Listen Event hören soll. Steht hier NULL_KEY, also eine Konstante, die einen leeren Key enthält, hört es auf alle.
  4. message - Ein String mit dem erwarteten Befehl, auf den es hören soll. Steht hier nichts drin, kommt jeglicher Text an.

In unserem Fall also übergeben wir der Funktion die ganz oben definierte Variable "commandchannel", die die Channel Nummer enthält, dann einen leeren Namen, einen leeren Key und eine leere Message. Demnach hört das listen Event, das wir gleich einfügen, nur noch auf den Channel 1 und zwar auf alle Avatare und Objekte und gibt jeden Text weiter, der auf Channel 1 gesendet wird.

Fügen wir nun noch das Listen Event hinzu:

integer commandchannel = 1
integer status = 0
integer start;
integer stopp;
default
{
        state_entry()
        {
                 llListen(commandchannel, "", NULL_KEY, "");
        }
        listen(integer channel, string name, key id, string msg)
        {
                if (status == 0)
                {
                        status = 1;
                        start = llGetUnixTime();
                        llSay(0, "Stoppuhr gestartet!");

                }
                else
                {
                        status = 0;
                        stopp = llGetUnixTime();
                        integer timediff = stopp - start;
                        llSay(0, "Zeit: "+(string)timediff+" Sekunden.");
                }
        }
}

Wir verändern einfach das "touch_start" Event zum "listen" Event mit den dazugehörigen Parametern.

So würde es schon funktionieren.

Und zwar würde die Stoppuhr nun, ganz egal was auf Channel 1 gesendet wird, beim ersten Mal den Timer starten und beim zweiten Text den Timer stoppen - wie gesagt, ganz egal, welchen Text wir auf Channel 1 senden. Das ist natürlich nicht Sinn der Sache.

Wir werden das Listen Event beziehungsweise dessen Inhalt nun noch dahingehend abändern, dass die Stoppuhr nur auf zwei bestimmte Befehle hört, nämlich auf "Start", um die Stoppuhr zu starten und auf "Stop", um die Stoppuhr wieder zu stoppen.

Dazu entfernen wir zuerst eine Variable, die wir nicht mehr benötigen, die Variable "status". Stattdessen benutzen wir dann die if-Abfrage, um auf die Befehle zu reagieren:

integer commandchannel = 1
integer start;
integer stopp;
default
{
        state_entry()
        {
                 llListen(commandchannel, "", NULL_KEY, "");
        }
        listen(integer channel, string name, key id, string msg)
        {
                if (msg == "Start")
                {
                        start = llGetUnixTime();
                        llSay(0, "Stoppuhr gestartet!");

                }
                else if (msg == "Stop")
                {
                        stopp = llGetUnixTime();
                        integer timediff = stopp - start;
                        llSay(0, "Zeit: "+(string)timediff+" Sekunden.");
                }
        }
}

Und damit haben wir die Stoppuhr fertig umgebaut, so dass sie auf unsere Befehle auf Channel 1 hört und reagiert.

Wenn ihr jetzt das Script in ein Objekt packt, dann könnt ihr durch Eingabe von "/1 Start" in die Chatzeile Eures SL-Viewers die Stoppuhr starten und durch Eingabe von "/1 Stop" wieder stoppen. 

So gibt es noch viele weitere Events, die man genauso für die Stoppuhr benutzen kann. Je nach Funktion des Events macht es natürlich mehr oder weniger Sinn, das jeweilige Event ausgerechnet für eine Stoppuhr zu verwenden. ;)


Andere Artikel der LSL-Serie:

LSL Scripting Teil 1 - Die Grundlagen

LSL Scripting Teil 2 - Hello World


 

Bitte Einloggen oder Registrieren um Kommentare zu schreiben.

Panorama 

Neue Artikel:
rss-005

Metaversen 

Neue Artikel:
VWI RSS

IT / Tech 

Neue Artikel:
VWI RSS

Second Life 

Neue Artikel:
rss-005

Mitmach-News 

Neue Artikel:
rss-005