Der Skript-Editor
Um deine Spiel-Skripte zu bearbeiten, klicke auf den Link "Mod (API) Skripte" auf der Spieldetails-Seite für dein Spiel (dort, wo sich auch Optionen wie "Chat-Protokoll" und "Spiel kopieren/erweitern" befinden). Du wirst ein paar Funktionen sehen:
- Eine Liste von Registerkarten oben. Dein Spiel kann mehrere Skripte haben, damit du es besser organisieren kannst. Beachte, dass alle Skripte immer noch im gleichen Kontext laufen. Das heißt, du solltest nicht mehrere Skripte gleichzeitig laufen lassen, die versuchen, die gleichen Werte zu überschreiben, sonst könntest du ungewollte Ergebnisse bekommen.
- Ein Skriptcode-Editor. Du kannst diesen Editor benutzen oder deine Skripte in einem externen Editor deiner Wahl bearbeiten und sie dann hier einfügen.
- Eine "Mod (API) Konsole" befindet sich unten (siehe unten).
Wenn du auf den „Skripte speichern“-Knopf klickst, wird der Sandkasten für dein Spiel neu gestartet (alle Daten im Speicher, die nicht im state-Objekt oder in Roll20-Objekten gespeichert wurden, gehen dabei verloren). Das gilt auch, wenn du ein neues Skript hinzufügst, ein Skript löschst oder ein Skript aktivierst/deaktivierst.
Die Mod (API) Konsole
Die Mod (API) Konsole ist das "Fenster" zu deinen Skripten. Da Mod (API) Skripte in einem Sandkasten laufen, hast du keinen direkten Zugriff auf sie, während sie laufen, um Informationen zu den Ergebnissen oder Fehlern des Skripts anzuzeigen. Die Mod (API) Konsole zeigt diese Informationen außerhalb des Sandkastens an, damit du sie anzeigen kannst, während du deine Skripte bearbeitest. Alle log() Befehle werden hier angezeigt, sowie alle Fehler, die während der Ausführung deiner Skripte auftreten. Mehr Infos findest du den Artikel zum Debuggen von Skripten.
Reaktive Skripte: Auf Ereignisse reagieren, Objekte ändern
Der erste (und einfachste Typ) der Mod (API) Verwendung ist, auf Änderungen am Spieltisch zu reagieren und dann mit zusätzlicher Funktionalität auf die geänderten Objekte zu reagieren. Diese Art von Skript besteht aus mehreren Funktionen, die auf Ereignisse im Spiel lauschen. Dann verändert es die Objekte, die bei diesen Ereignissen übergeben werden; das wirkt sich darauf aus, was auf dem Spieltisch passiert.
Ein einfaches Skript, das einen Spielmarker um weitere 5 Fuß verschiebt (bei Standard-Seiteneinstellungen), würde so aussehen:
on("change:graphic", function(obj) {
obj.set({
left: obj.get("left") + 70
});
});
Wie du siehst, haben wir eine einfache Funktion erstellt, die immer dann ausgeführt wird, wenn das Ereignis „change:graphic“ auftritt. Die Funktion bekommt das Grafikobjekt obj übergeben. Um eine Änderung vorzunehmen, passen wir obj mit der set-Funktion an – welche Eigenschaften wir auch ändern, sie werden erkannt und auf dem Spieltisch übernommen.
set und get verwenden, um aktuelle Werte an Objekten zu setzen und abzurufen, sonst werden deine Änderungen nicht gespeichert. (Unten findest du eine Liste der Objekttypen und ihrer Eigenschaften sowie eine Liste aller Ereignisse und der Argumente, die jedes Ereignis übergibt.)Ein Hinweis zu den Werkzeugfunktionen
Klar, das vorherige Beispiel ist nicht besonders hilfreich, weil es immer 70 Pixel zur Position des Spielmarkers hinzufügt. Aber was ist, wenn der Nutzer seine Skalierung so geändert hat, dass 5 Fuß 140 Pixel sind? Die Roll20-API bietet mehrere praktische Werkzeugfunktionen, die bei diesem (und anderen) häufigen Szenarien helfen. Lass uns unser vorheriges Beispiel so anpassen, dass wir die Funktion distanceToPixels verwenden. Sie sagt uns, wie viele Pixel „fünf Fuß“ (oder Zoll, Meter oder welcher Entfernungstyp auch immer eingestellt wurde) auf dem Spieltisch sind.
on("change:graphic", function(obj) {
obj.set({
left: obj.get("left") + distanceToPixels(5);
});
});
Wenn die aktuelle Seite so eingestellt ist, dass sie die Standard-Rastergröße verwendet, gibt distanceToPixels(5); immer noch 70 Pixel zurück; wenn die Seite jedoch auf den doppelten Maßstab eingestellt ist, wären es 140.
Es ist immer eine gute Idee, Werkzeugfunktionen zu nutzen, wenn sie verfügbar sind, damit dein Skript nicht kaputtgeht, wenn sich die Einstellungen einer Seite oder eines Spielmarkers ändern.
Proaktive Skripte: Dinge ohne Benutzereingriff erledigen
Du kannst nicht nur auf Benutzerereignisse reagieren, sondern mit der API auch automatisch Dinge ausführen, die nicht an ein bestimmtes Ereignis der Spieler gebunden sind. Nehmen wir zum Beispiel einen Spielmarker, der auf der Karte hin und her patrouilliert.
Hinweis: Obwohl dieser Skripttyp nicht von Benutzerinteraktion abhängt, werden die Mod (API) Skripte für dein Spiel nur ausgeführt, wenn mindestens eine Person mit deinem Spiel verbunden ist.
on("ready", function() {
//Warte, bis das Ready-Ereignis ausgelöst wird, damit wir wissen, dass das Spiel komplett geladen ist.
//Hol dir eine Referenz auf unseren patrouillierenden Spielmarker.
var patroltoken = findObjs({_type: "graphic", name: "Guard A"})[0]; //Wir wissen, dass es im Spiel einen Spielmarker namens „Guard A“ gibt.
var direction = -1*distanceToPixels(5); //70 Pixel nach links gehen.
var stepstaken = 0; //Wie viele Schritte haben wir in der aktuellen Richtung gemacht?
setInterval(function() {
if(stepstaken > 3) {
//Richtung wechseln!
direction = direction * -1; //dreht die Laufrichtung um
stepstaken = 0; //Setzt die Schritte auf 0 zurück.
}
patroltoken.set("left", patroltoken.get("left") + direction); //laufen!
stepstaken++;
}, 5000); //alle 5 Sekunden eine Aktion ausführen
});
Eine Abhandlung über asynchrone Funktionen
Eine asynchrone Funktion gibt die Kontrolle sofort an den aufrufenden Bereich zurück und macht im Hintergrund weiter. Hier ist ein sehr einfaches und leicht verständliches Beispiel, das du in einem Mod (API) Skripte-Tab einfügen kannst:
on('load',function(){
log('Übergeordneter Bereich – Vor dem Aufruf der asynchronen Funktion.');
setTimeout(function(){
log('Bereich der asynchronen Funktion – Ausführung der asynchronen Funktion.');
},10 /* 10 Millisekunden */);
log('Übergeordneter Bereich – nach dem Aufruf der asynchronen Funktion.');
});
Im Mod (API) Protokoll siehst du etwas wie das hier:
„Parent Scope – Vor dem Aufruf der asynchronen Funktion.“ „Parent Scope – nach Aufruf einer asynchronen Funktion.“ Asynchroner Funktionsbereich – Die asynchrone Funktion ausführen.
Wenn du dir diesen Code anschaust, denkst du: „Klar, dass das später passiert, du hast ihm gesagt, dass er in 10 Millisekunden laufen soll, na klar!“ Hier ist ein weniger offensichtliches Beispiel, das die gleichen Protokollmeldungen hat:
on('load',function(){
log('Übergeordneter Bereich – Vor dem Aufruf der asynchronen Funktion.');
sendChat('Async Function','Evaluate this: [[1d6]]',function(msg){
log('Asynchronous Function Scope - Doing the Asynchronous function work.');
});
log('Parent Scope - after call to asynchronous function.');
Asynchrone Funktionen sind wichtig, damit die API nicht ständig hängen bleibt. Wenn jeder Würfelwurf synchron abgewickelt würde, wäre die API extrem langsam und würde nicht mehr richtig reagieren. Fast jede Funktion, die du siehst und die einen Rückruf braucht, läuft asynchron. (Ausnahme für einige der Funktionen _.map, _.reduce usw. Das sind Beispiele für funktionale Programmierung im Gegensatz zur prozeduralen Programmierung, die die meisten Leute gewohnt sind.)