Inhaltsverzeichnis:

Scratch 3.0-Erweiterungen - Gunook
Scratch 3.0-Erweiterungen - Gunook

Video: Scratch 3.0-Erweiterungen - Gunook

Video: Scratch 3.0-Erweiterungen - Gunook
Video: Scratch 3.0 - Extensions 2024, November
Anonim
Scratch 3.0-Erweiterungen
Scratch 3.0-Erweiterungen

Scratch-Erweiterungen sind Teile von Javascript-Code, die Scratch neue Blöcke hinzufügen. Während Scratch mit einer Reihe offizieller Erweiterungen gebündelt ist, gibt es keinen offiziellen Mechanismus zum Hinzufügen von benutzerdefinierten Erweiterungen.

Als ich meine Minecraft-Steuerungserweiterung für Scratch 3.0 erstellte, fiel mir der Einstieg schwer. Dieses Instructable sammelt Informationen aus verschiedenen Quellen (insbesondere diese) sowie ein paar Dinge, die ich selbst entdeckt habe.

Sie müssen wissen, wie Sie in Javascript programmieren und wie Sie Ihr Javascript auf einer Website hosten. Für letzteres empfehle ich GitHub Pages.

Der Haupttrick besteht darin, den Scratch-Mod von SheepTester zu verwenden, mit dem Sie Erweiterungen und Plugins laden können.

Dieses Instructable führt Sie durch die Erstellung von zwei Erweiterungen:

  • Fetch: Laden von Daten von einer URL und Extrahieren von JSON-Tags, zum Beispiel zum Laden von Wetterdaten
  • SimpleGamepad: Verwenden eines Gamecontrollers in Scratch (eine anspruchsvollere Version ist hier).

Schritt 1: Zwei Arten von Erweiterungen

Es gibt zwei Arten von Erweiterungen, die ich "unsandboxed" und "sandboxed" nenne. Sandkastenerweiterungen werden als Web Worker ausgeführt und weisen daher erhebliche Einschränkungen auf:

  • Web-Worker können nicht auf die Globals im Fensterobjekt zugreifen (stattdessen haben sie ein globales self-Objekt, das viel eingeschränkter ist), sodass Sie sie nicht für Dinge wie den Gamepad-Zugriff verwenden können.
  • Sandkastenerweiterungen haben keinen Zugriff auf das Scratch-Laufzeitobjekt.
  • Sandkastenerweiterungen sind viel langsamer.
  • Javascript-Konsolen-Fehlermeldungen für Sandbox-Erweiterungen sind in Chrome kryptischer.

Auf der anderen Seite:

  • Die Verwendung der Sandbox-Erweiterungen anderer Benutzer ist sicherer.
  • Sandbox-Erweiterungen funktionieren eher mit einer eventuellen offiziellen Unterstützung für das Laden von Erweiterungen.
  • Sandkastenerweiterungen können ohne Hochladen auf einen Webserver getestet werden, indem sie in eine data://-URL kodiert werden.

Die offiziellen Erweiterungen (wie Musik, Stift usw.) sind alle ohne Sandbox. Der Konstruktor für die Erweiterung ruft das Laufzeitobjekt von Scratch ab, und das Fenster ist vollständig zugänglich.

Die Fetch-Erweiterung ist Sandboxed, aber das Gamepad benötigt das Navigator-Objekt aus dem Fenster.

Schritt 2: Schreiben einer Sandbox-Erweiterung: Teil I

Um eine Erweiterung zu erstellen, erstellen Sie eine Klasse, die Informationen darüber codiert, und fügen dann ein wenig Code hinzu, um die Erweiterung zu registrieren.

Die Hauptsache in der Erweiterungsklasse ist eine getInfo()-Methode, die ein Objekt mit den erforderlichen Feldern zurückliefert:

  • id: der interne Name der Nebenstelle, muss für jede Nebenstelle eindeutig sein
  • name: der Anzeigename der Erweiterung, der in der Liste der Blöcke von Scratch angezeigt wird
  • Blöcke: eine Liste von Objekten, die den neuen benutzerdefinierten Block beschreiben.

Und es gibt ein optionales Menüfeld, das in Fetch nicht verwendet wird, aber im Gamepad verwendet wird.

Hier ist also die grundlegende Vorlage für Fetch:

Klasse ScratchFetch {

constructor() { } getInfo() { return { "id": "Fetch", "name": "Fetch", "blocks": [/* später hinzufügen */] } } /* Methoden für Blöcke hinzufügen */ } Scratch.extensions.register (neues ScratchFetch())

Schritt 3: Schreiben einer Sandbox-Erweiterung: Teil II

Jetzt müssen wir die Liste der Blöcke im Objekt von getInfo() erstellen. Jeder Block benötigt mindestens diese vier Felder:

  • opcode: Dies ist der Name der Methode, die aufgerufen wird, um die Arbeit des Blocks zu erledigen
  • blockType: Dies ist der Blocktyp; die gebräuchlichsten für Erweiterungen sind:

    • "Befehl": tut etwas, gibt aber keinen Wert zurück
    • "reporter": gibt einen String oder eine Zahl zurück
    • "Boolean": gibt einen booleschen Wert zurück (beachten Sie die Groß-/Kleinschreibung)
    • "hat": Ereignisfangblock; Wenn Ihr Scratch-Code diesen Block verwendet, fragt die Scratch-Laufzeit regelmäßig die zugehörige Methode ab, die einen booleschen Wert zurückgibt, um zu sagen, ob das Ereignis aufgetreten ist
  • text: Dies ist eine benutzerfreundliche Beschreibung des Blocks mit den Argumenten in Klammern, z. B. "Daten von abrufen"
  • arguments: Dies ist ein Objekt mit einem Feld für jedes Argument (z. B. "url" im obigen Beispiel); dieses Objekt hat wiederum diese Felder:

    • Typ: entweder "Zeichenfolge" oder "Zahl"
    • defaultValue: der Standardwert, der vorausgefüllt werden soll.

Hier ist zum Beispiel das Blockfeld in meiner Fetch-Erweiterung:

"Blöcke": [{ "opcode": "fetchURL", "blockType": "reporter", "text": "Daten von abrufen", "arguments": { "url": { "type": "string", "defaultValue ": "https://api.weather.gov/stations/KNYC/observations" }, } }, { "opcode": "jsonExtract", "blockType": "reporter", "text": "extract [Name] from [data]", "arguments": { "name": { "type": "string", "defaultValue": "temperature" }, "data": { "type": "string", "defaultValue": '{"Temperatur": 12.3}' }, } },]

Hier haben wir zwei Blöcke definiert: fetchURL und jsonExtract. Beide sind Reporter. Der erste ruft Daten von einer URL ab und gibt sie zurück, und der zweite extrahiert ein Feld aus JSON-Daten.

Schließlich müssen Sie die Methoden für zwei Blöcke einschließen. Jede Methode nimmt ein Objekt als Argument entgegen, wobei das Objekt Felder für alle Argumente enthält. Sie können diese mit geschweiften Klammern in den Argumenten decodieren. Hier zum Beispiel ein synchrones Beispiel:

jsonExtract({name, data}) {

var parsed = JSON.parse(data) if (name in parsed) { var out = parsed[name] var t = typeof(out) if (t == "string" || t == "number") return out if (t == "boolean") return t ? 1: 0 return JSON.stringify(out) } else { return "" } }

Der Code ruft das Namensfeld aus den JSON-Daten ab. Wenn das Feld einen String, eine Zahl oder einen Booleschen Wert enthält, geben wir diesen zurück. Andernfalls JSONify das Feld erneut. Und wir geben einen leeren String zurück, wenn der Name im JSON fehlt.

Manchmal möchten Sie jedoch möglicherweise einen Block erstellen, der eine asynchrone API verwendet. Die Methode fetchURL() verwendet die asynchrone fetch-API. In einem solchen Fall sollten Sie von Ihrer Methode, die die Arbeit erledigt, ein Versprechen zurückgeben. Zum Beispiel:

fetchURL({url}) {

return fetch(url).then(response => response.text()) }

Das ist es. Die vollständige Erweiterung ist hier.

Schritt 4: Verwenden einer Sandbox-Erweiterung

Verwenden einer Sandbox-Erweiterung
Verwenden einer Sandbox-Erweiterung
Verwenden einer Sandbox-Erweiterung
Verwenden einer Sandbox-Erweiterung
Verwenden einer Sandbox-Erweiterung
Verwenden einer Sandbox-Erweiterung

Es gibt zwei Möglichkeiten, die Sandbox-Erweiterung zu verwenden. Zuerst können Sie es auf einen Webserver hochladen und dann in den Scratch-Mod von SheepTester laden. Zweitens können Sie es in eine Daten-URL codieren und diese in den Scratch-Mod laden. Ich verwende die zweite Methode tatsächlich ziemlich oft zum Testen, da sie keine Sorgen darüber vermeidet, dass ältere Versionen der Erweiterung vom Server zwischengespeichert werden. Beachten Sie, dass Sie Javascript zwar von Github-Seiten hosten können, dies jedoch nicht direkt von einem gewöhnlichen Github-Repository aus.

Meine fetch.js wird unter https://arpruss.github.io/fetch.js gehostet. Oder Sie können Ihre Erweiterung in eine Daten-URL umwandeln, indem Sie sie hier hochladen und dann in die Zwischenablage kopieren. Eine Daten-URL ist eine riesige URL, die eine ganze Datei enthält.

Gehen Sie zum Scratch-Mod von SheepTester. Klicken Sie auf die Schaltfläche Erweiterung hinzufügen in der unteren linken Ecke. Klicken Sie dann auf "Wählen Sie eine Erweiterung" und geben Sie Ihre URL ein (Sie können die gesamte riesige Daten-URL einfügen, wenn Sie möchten).

Wenn alles gut gegangen ist, haben Sie auf der linken Seite Ihres Scratch-Bildschirms einen Eintrag für Ihre Nebenstelle. Wenn die Dinge nicht gut gelaufen sind, sollten Sie Ihre Javascript-Konsole öffnen (Shift-Strg-J in Chrome) und versuchen, das Problem zu beheben.

Oben finden Sie einige Beispielcodes, die JSON-Daten von der KNYC-Station (in New York) des US-amerikanischen National Weather Service abrufen und parsen und anzeigen, während das Sprite in die gleiche Richtung gedreht wird, in die der Wind weht. Ich habe es geschafft, indem ich die Daten in einen Webbrowser geholt und dann die Tags herausgefunden habe. Wenn Sie eine andere Wetterstation ausprobieren möchten, geben Sie eine Postleitzahl in der Nähe in das Suchfeld auf weather.gov ein. Die Wetterseite für Ihren Standort sollte Ihnen einen vierstelligen Stationscode anzeigen, den Sie anstelle von KNYC im Code.

Sie können Ihre Sandbox-Erweiterung auch direkt in die URL für die Mod von SheepTester einschließen, indem Sie ein "?url="-Argument hinzufügen. Zum Beispiel:

sheeptester.github.io/scratch-gui/?url=https://arpruss.github.io/fetch.js

Schritt 5: Schreiben einer Erweiterung ohne Sandbox: Einführung

Der Konstruktor einer Erweiterung ohne Sandbox erhält ein Runtime-Objekt. Sie können es ignorieren oder verwenden. Eine Verwendung des Runtime-Objekts besteht darin, seine currentMSecs-Eigenschaft zu verwenden, um Ereignisse ("Hatblöcke") zu synchronisieren. Soweit ich das beurteilen kann, werden alle Ereignisblock-Opcodes regelmäßig abgefragt, und jede Abfragerunde hat einen einzelnen currentMSecs-Wert. Wenn Sie das Runtime-Objekt benötigen, starten Sie Ihre Erweiterung wahrscheinlich mit:

Klasse EXTENSIONCLASS {

Konstruktor(Laufzeit) { this.runtime = Laufzeit … } … }

Alle Standard-Fensterobjekt-Dinge können in der Erweiterung ohne Sandbox verwendet werden. Schließlich sollte Ihre Erweiterung ohne Sandbox mit diesem magischen Code enden:

(Funktion() {

var extensionInstance = new EXTENSIONCLASS(window.vm.extensionManager.runtime) var serviceName = window.vm.extensionManager._registerInternalExtension(extensionInstance) window.vm.extensionManager._loadedExtensions.set(extensionInstance.getInfo().id, serviceName) })()

wobei Sie EXTENSIONCLASS durch die Klasse Ihrer Erweiterung ersetzen sollten.

Schritt 6: Schreiben einer Erweiterung ohne Sandbox: Einfaches Gamepad

Lassen Sie uns nun eine einfache Gamepad-Erweiterung erstellen, die einen einzelnen Ereignisblock ("Hut") bereitstellt, wenn eine Taste gedrückt oder losgelassen wird.

Während jedes Abfragezyklus des Ereignisblocks speichern wir einen Zeitstempel aus dem Laufzeitobjekt und den vorherigen und aktuellen Gamepad-Status. Der Zeitstempel wird verwendet, um zu erkennen, ob wir einen neuen Abfragezyklus haben. Wir beginnen also mit:

Klasse ScratchSimpleGamepad {

constructor(runtime) { this.runtime = Laufzeit this.currentMSecs = -1 this.vorherigeButtons = this.currentButtons = } … } Wir haben einen Ereignisblock mit zwei Eingängen - eine Tastennummer und ein Menü, um auszuwählen, ob das Ereignis beim Drücken oder Loslassen ausgelöst werden soll. Also, hier ist unsere Methode

Informationen bekommen() {

return { "id": "SimpleGamepad", "name": "SimpleGamepad", "blocks": [{ "opcode": "buttonPressedReleased", "blockType": "hat", "text": "button [eventType]", "arguments": { "b": { "type": "number", "defaultValue": "0" }, "eventType": { "type": "number", "defaultValue": "1 ", "menu": "pressReleaseMenu" }, }, },], "menus": { "pressReleaseMenu": [{text:"press", value:1}, {text:"release", value:0}], } }; } Ich denke, die Werte im Dropdown-Menü werden immer noch als Strings an die Opcode-Funktion übergeben, obwohl sie als Zahlen deklariert wurden. Vergleichen Sie sie daher bei Bedarf explizit mit den im Menü angegebenen Werten. Wir schreiben jetzt eine Methode, die die Schaltflächenzustände aktualisiert, wenn ein neuer Ereignisabfragezyklus stattfindet

aktualisieren() {

if (this.runtime.currentMSecs == this.currentMSecs) return // kein neuer Abfragezyklus this.currentMSecs = this.runtime.currentMSecs var gamepads = navigator.getGamepads() if (gamepads == null || gamepads.length = = 0 || gamepads[0] == null) { this.vorherigeButtons = this.currentButtons = Zurück } var gamepad = gamepads[0] if (gamepad.buttons.length != this.vorherigeButtons.length) { // andere Anzahl von Schaltflächen, also neues Gamepad this.vorherigeButtons = for (var i = 0; i < gamepad.buttons.length; i++) this.vorherigeButtons.push(false) } else { this.vorherigeButtons = this. currentButtons } this.currentButtons = for (var i = 0; i < gamepad.buttons.length; i++) this.currentButtons.push(gamepad.buttons.pressed) } Schließlich können wir unseren Ereignisblock implementieren, indem wir die Methode update() aufrufen und dann überprüfen, ob die erforderliche Schaltfläche gerade gedrückt oder losgelassen wurde, indem wir den aktuellen und vorherigen Schaltflächenstatus vergleichen

buttonPressedReleased({b, eventType}) {

this.update() if (b < this.currentButtons.length) { if (eventType == 1) { // Hinweis: Dies ist ein String, also besser mit 1 vergleichen, als als Boolean zu behandeln if (this.currentButtons && !this.vorherigeButtons) { return true } } else { if (!this.currentButtons && this.vorherigeButtons) { return true } } } return false } Und schließlich fügen wir unseren Registrierungscode für die magische Erweiterung hinzu, nachdem wir die Klasse definiert haben

(Funktion() {

var extensionInstance = new ScratchSimpleGamepad(window.vm.extensionManager.runtime) var serviceName = window.vm.extensionManager._registerInternalExtension(extensionInstance) window.vm.extensionManager._loadedExtensions.set(extensionInstance.getInfo().id, serviceName) })()

Den vollständigen Code erhalten Sie hier.

Schritt 7: Verwenden einer Erweiterung ohne Sandbox

Verwenden einer Erweiterung ohne Sandbox
Verwenden einer Erweiterung ohne Sandbox

Hosten Sie Ihre Erweiterung wieder irgendwo und laden Sie sie diesmal mit dem Argument load_plugin= statt url= in den Scratch-Mod von SheepTester. Gehe zum Beispiel für meinen einfachen Gamepad-Mod zu:

sheeptester.github.io/scratch-gui/?load_plugin=https://arpruss.github.io/simplegamepad.js

(Übrigens, wenn Sie ein anspruchsvolleres Gamepad wünschen, entfernen Sie einfach "einfach" aus der obigen URL, und Sie haben Rumble- und Analogachsenunterstützung.)

Auch hier sollte die Erweiterung auf der linken Seite Ihres Scratch-Editors erscheinen. Oben ist ein sehr einfaches Scratch-Programm, das "Hallo" sagt, wenn Sie die Taste 0 drücken, und "Auf Wiedersehen", wenn Sie sie loslassen.

Schritt 8: Dual-Kompatibilität und Geschwindigkeit

Ich habe festgestellt, dass Erweiterungsblöcke mit der Lademethode, die ich für Erweiterungen ohne Sandbox verwendet habe, um eine Größenordnung schneller ausgeführt werden. Wenn Sie sich also nicht für die Sicherheitsvorteile einer Ausführung in einer Web Worker-Sandbox interessieren, wird Ihr Code davon profitieren, wenn er mit dem ?load_plugin=URL-Argument in die Mod von SheepTester geladen wird.

Sie können eine Sandkastenerweiterung mit beiden Lademethoden kompatibel machen, indem Sie den folgenden Code verwenden, nachdem Sie die Erweiterungsklasse definiert haben (ändern Sie CLASSNAME in den Namen Ihrer Erweiterungsklasse):

(Funktion() {

var extensionClass = CLASSNAME if (typeof window === "undefined" || !window.vm) { Scratch.extensions.register(new extensionClass()) } else { var extensionInstance = new extensionClass(window.vm.extensionManager.runtime) var serviceName = window.vm.extensionManager._registerInternalExtension(extensionInstance) window.vm.extensionManager._loadedExtensions.set(extensionInstance.getInfo().id, serviceName) } })()

Empfohlen: