Inhoudsopgave:

Scratch 3.0-extensies: 8 stappen
Scratch 3.0-extensies: 8 stappen

Video: Scratch 3.0-extensies: 8 stappen

Video: Scratch 3.0-extensies: 8 stappen
Video: Scratch! 2024, Juli-
Anonim
Scratch 3.0-extensies
Scratch 3.0-extensies

Scratch-extensies zijn stukjes Javascript-code die nieuwe blokken aan Scratch toevoegen. Hoewel Scratch is gebundeld met een aantal officiële extensies, is er geen officieel mechanisme voor het toevoegen van door de gebruiker gemaakte extensies.

Toen ik mijn Minecraft-besturingsextensie voor Scratch 3.0 maakte, vond ik het moeilijk om te beginnen. Deze Instructable verzamelt informatie uit verschillende bronnen (vooral deze), plus een paar dingen die ik zelf heb ontdekt.

U moet weten hoe u in Javascript moet programmeren en hoe u uw Javascript op een website kunt hosten. Voor dat laatste raad ik GitHub Pages aan.

De belangrijkste truc is om Scratch-mod van SheepTester te gebruiken waarmee je extensies en plug-ins kunt laden.

Dit Instructable zal u begeleiden bij het maken van twee extensies:

  • Fetch: gegevens laden van een URL en JSON-tags extraheren, bijvoorbeeld voor het laden van weergegevens
  • SimpleGamepad: een gamecontroller gebruiken in Scratch (hier vind je een meer geavanceerde versie).

Stap 1: Twee soorten extensies

Er zijn twee soorten extensies die ik "unsandboxed" en "sandboxed" zal noemen. Sandbox-extensies worden uitgevoerd als Web Workers en hebben als gevolg daarvan aanzienlijke beperkingen:

  • Web Workers hebben geen toegang tot de globals in het window-object (in plaats daarvan hebben ze een global self-object, dat veel beperkter is), dus je kunt ze niet gebruiken voor zaken als gamepad-toegang.
  • Sandbox-extensies hebben geen toegang tot het Scratch runtime-object.
  • Sandbox-extensies zijn veel langzamer.
  • Javascript-consolefoutmeldingen voor sandbox-extensies zijn cryptischer in Chrome.

Anderzijds:

  • Het is veiliger om de sandbox-extensies van anderen te gebruiken.
  • Sandbox-extensies werken eerder met eventuele officiële ondersteuning voor het laden van extensies.
  • Sandbox-extensies kunnen worden getest zonder te uploaden naar een webserver door te coderen in een data://-URL.

De officiële extensies (zoals Music, Pen, etc.) zijn allemaal unsandboxed. De constructor voor de extensie haalt het runtime-object uit Scratch en het venster is volledig toegankelijk.

De Fetch-extensie is sandboxed, maar de Gamepad-extensie heeft het navigatorobject uit het venster nodig.

Stap 2: Een Sandbox-extensie schrijven: deel I

Om een extensie te maken, maakt u een klasse die informatie erover codeert en voegt u vervolgens een stukje code toe om de extensie te registreren.

Het belangrijkste in de extensieklasse is een methode getInfo() die een object retourneert met de vereiste velden:

  • id: de interne naam van de extensie, moet uniek zijn voor elke extensie
  • naam: de beschrijvende naam van de extensie, die wordt weergegeven in de lijst met blokken van Scratch
  • blokken: een lijst met objecten die het nieuwe aangepaste blok beschrijven.

En er is een optioneel menuveld dat niet wordt gebruikt in Fetch, maar zal worden gebruikt in Gamepad.

Dus, hier is de basissjabloon voor Fetch:

klasse ScratchFetch {

constructor() { } getInfo() { return { "id": "Fetch", "name": "Fetch", "blocks": [/* later toevoegen */] } } /* methoden voor blokken toevoegen */ } Scratch.extensions.register (nieuwe ScratchFetch())

Stap 3: Een sandbox-extensie schrijven: deel II

Nu moeten we de lijst met blokken in het object van getInfo() maken. Elk blok heeft minimaal deze vier velden nodig:

  • opcode: dit is de naam van de methode die wordt aangeroepen om het werk van het blok te doen
  • blockType: dit is het bloktype; de meest voorkomende voor extensies zijn:

    • "command": doet iets maar retourneert geen waarde
    • "reporter": geeft een tekenreeks of getal terug
    • "Boolean": geeft een boolean terug (let op het hoofdlettergebruik)
    • "hat": gebeurtenisvangblok; als je Scratch-code dit blok gebruikt, peilt de Scratch-runtime regelmatig de bijbehorende methode die een boolean retourneert om te zeggen of de gebeurtenis heeft plaatsgevonden
  • tekst: dit is een vriendelijke beschrijving van het blok, met de argumenten tussen haakjes, bijvoorbeeld "fetch data from "
  • argumenten: dit is een object met een veld voor elk argument (bijvoorbeeld "url" in het bovenstaande voorbeeld); dit object heeft op zijn beurt deze velden:

    • type: ofwel "string" of "nummer"
    • defaultValue: de standaardwaarde die vooraf moet worden ingevuld.

Hier is bijvoorbeeld het veld blokken in mijn Fetch-extensie:

"blokken": [{ "opcode": "fetchURL", "blockType": "reporter", "text": "haal gegevens op van ", "argumenten": { "url": { "type": "string", "defaultValue ": "https://api.weather.gov/stations/KNYC/observations" }, } }, { "opcode": "jsonExtract", "blockType": "reporter", "text": "extract [name] from [data]", "argumenten": { "name": { "type": "string", "defaultValue": "temperature" }, "data": { "type": "string", "defaultValue": '{"temperatuur": 12.3}' }, } },]

Hier hebben we twee blokken gedefinieerd: fetchURL en jsonExtract. Beiden zijn verslaggevers. De eerste haalt gegevens uit een URL en retourneert deze, en de tweede extraheert een veld uit JSON-gegevens.

Ten slotte moet u de methoden voor twee blokken opnemen. Elke methode neemt een object als argument, waarbij het object velden bevat voor alle argumenten. U kunt deze decoderen met accolades in de argumenten. Hier is bijvoorbeeld een synchroon voorbeeld:

jsonExtract({naam, gegevens}) {

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") retourneer t ? 1: 0 return JSON.stringify(out) } else { return "" } }

De code haalt het naamveld uit de JSON-gegevens. Als het veld een string, getal of boolean bevat, geven we die terug. Anders voeren we het veld opnieuw JSONify uit. En we retourneren een lege tekenreeks als de naam ontbreekt in de JSON.

Soms wil je echter misschien een blok maken dat een asynchrone API gebruikt. De methode fetchURL() gebruikt de fetch-API die asynchroon is. In zo'n geval moet u een belofte terugsturen van uw methode die het werk doet. Bijvoorbeeld:

fetchURL({url}) {

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

Dat is het. De volledige extensie staat hier.

Stap 4: Een sandbox-extensie gebruiken

Een sandbox-extensie gebruiken
Een sandbox-extensie gebruiken
Een sandbox-extensie gebruiken
Een sandbox-extensie gebruiken
Een sandbox-extensie gebruiken
Een sandbox-extensie gebruiken

Er zijn twee manieren om de sandbox-extensie te gebruiken. Ten eerste kun je het uploaden naar een webserver en het vervolgens in de Scratch-mod van SheepTester laden. Ten tweede kun je het coderen in een gegevens-URL en die in de Scratch-mod laden. Ik gebruik de tweede methode eigenlijk nogal wat om te testen, omdat het me geen zorgen hoeft te maken dat oudere versies van de extensie door de server in de cache worden opgeslagen. Merk op dat hoewel je javascript kunt hosten vanaf Github Pages, je dit niet rechtstreeks vanuit een gewone github-repository kunt doen.

Mijn fetch.js wordt gehost op https://arpruss.github.io/fetch.js. Of u kunt uw extensie converteren naar een gegevens-URL door deze hier te uploaden en vervolgens naar het klembord te kopiëren. Een data-URL is een gigantische URL die een heel bestand bevat.

Ga naar de Scratch-mod van SheepTester. Klik op de knop Extensie toevoegen in de linkerbenedenhoek. Klik vervolgens op "Kies een extensie" en voer uw URL in (u kunt desgewenst de hele gigantische gegevens-URL plakken).

Als alles goed is gegaan, heb je een vermelding voor je extensie aan de linkerkant van je Scratch-scherm. Als het niet goed is gegaan, moet u uw Javascript-console openen (shift-ctrl-J in Chrome) en proberen het probleem op te lossen.

Hierboven vindt u een voorbeeldcode die JSON-gegevens ophaalt en parseert van het KNYC (in New York) station van de Amerikaanse National Weather Service, en deze weergeeft, terwijl de sprite naar dezelfde kant wordt gedraaid als de wind waait. De manier waarop ik het maakte, was door de gegevens in een webbrowser op te halen en vervolgens de tags uit te zoeken. Als u een ander weerstation wilt proberen, voert u een postcode in de buurt in het zoekvak op weather.gov in, en de weerpagina voor uw locatie zou u een vierletterige stationscode moeten geven, die u kunt gebruiken in plaats van KNYC in de code.

U kunt uw sandbox-extensie ook rechtstreeks in de URL voor de modificatie van SheepTester opnemen door een "?url="-argument toe te voegen. Bijvoorbeeld:

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

Stap 5: Een Unsandboxed-extensie schrijven: Inleiding

De constructor van een extensie zonder sandbox krijgt een Runtime-object doorgegeven. Je kunt het negeren of gebruiken. Een gebruik van het Runtime-object is om de eigenschap currentMSecs ervan te gebruiken om gebeurtenissen ("hat-blokken") te synchroniseren. Voor zover ik weet, worden alle opcodes van gebeurtenisblokken regelmatig gepold, en elke ronde van de peiling heeft een enkele huidige MSecs-waarde. Als u het Runtime-object nodig heeft, begint u uw extensie waarschijnlijk met:

klasse UITBREIDINGSKLASSE {

constructor(runtime) { this.runtime = runtime … } … }

Alle standaard vensterobject-dingen kunnen worden gebruikt in de extensie zonder sandbox. Ten slotte zou je extensie zonder sandbox moeten eindigen met dit stukje magische code:

(functie() {

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

waar u EXTENSIONCLASS moet vervangen door de klasse van uw extensie.

Stap 6: Een extensie zonder sandbox schrijven: eenvoudige gamepad

Laten we nu een eenvoudige gamepad-extensie maken die een enkel gebeurtenis ("hat")-blok biedt voor wanneer een knop wordt ingedrukt of losgelaten.

Tijdens elke pollingcyclus voor gebeurtenisblokken slaan we een tijdstempel op van het runtime-object en de vorige en huidige gamepad-statussen. De tijdstempel wordt gebruikt om te herkennen of we een nieuwe pollingcyclus hebben. We beginnen dus met:

klasse ScratchSimpleGamepad {

constructor(runtime) { this.runtime = runtime this.currentMSecs = -1 this.previousButtons = this.currentButtons = } … } We zullen één gebeurtenisblok hebben, met twee ingangen - een knopnummer en een menu om te selecteren of we de gebeurtenis willen activeren bij het indrukken of loslaten. Dus, hier is onze methode:

informatie verkrijgen() {

return { "id": "SimpleGamepad", "name": "SimpleGamepad", "blocks": [{ "opcode": "buttonPressedReleased", "blockType": "hat", "text": "button [eventType]", "argumenten": { "b": { "type": "number", "defaultValue": "0" }, "eventType": { "type": "number", "defaultValue": "1 ", "menu": "pressReleaseMenu" }, }, },], "menus": { "pressReleaseMenu": [{text:"press", value:1}, {text:"release", value:0}], } }; } Ik denk dat de waarden in het vervolgkeuzemenu nog steeds als tekenreeksen aan de opcode-functie worden doorgegeven, ondanks dat ze als getallen zijn gedeclareerd. Vergelijk ze dus expliciet met de waarden die in het menu zijn gespecificeerd, indien nodig. We schrijven nu een methode die de knopstatussen bijwerkt wanneer er een nieuwe event polling-cyclus plaatsvindt

update() {

if (this.runtime.currentMSecs == this.currentMSecs) return // not a new polling cycle this.currentMSecs = this.runtime.currentMSecs var gamepads = navigator.getGamepads() if (gamepads == null || gamepads.length = = 0 || gamepads[0] == null) { this.previousButtons = this.currentButtons = return } var gamepad = gamepads[0] if (gamepad.buttons.length != this.previousButtons.length) { // ander aantal knoppen, dus nieuwe gamepad this.previousButtons = for (var i = 0; i < gamepad.buttons.length; i++) this.previousButtons.push(false) } else { this.previousButtons = this. currentButtons } this.currentButtons = for (var i = 0; i < gamepad.buttons.length; i++) this.currentButtons.push(gamepad.buttons.pressed) } Ten slotte kunnen we ons gebeurtenisblok implementeren door de update() -methode aan te roepen en vervolgens te controleren of de vereiste knop zojuist is ingedrukt of losgelaten, door de huidige en vorige knopstatussen te vergelijken

buttonPressedReleased({b, eventType}) {

this.update() if (b < this.currentButtons.length) { if (eventType == 1) { // opmerking: dit zal een string zijn, dus beter om het te vergelijken met 1 dan om het te behandelen als een Boolean if (this.currentButtons && ! this.previousButtons) { return true } } else { if (!this.currentButtons && this.previousButtons) { return true } } } return false } En tot slot voegen we onze magische extensie-registratiecode toe na het definiëren van de klasse

(functie() {

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

Je kunt de volledige code hier krijgen.

Stap 7: Een extensie zonder sandbox gebruiken

Een extensie zonder sandbox gebruiken
Een extensie zonder sandbox gebruiken

Host nogmaals je extensie ergens, en laad deze keer met het argument load_plugin= in plaats van url= naar de Scratch-mod van SheepTester. Ga bijvoorbeeld voor mijn eenvoudige Gamepad-mod naar:

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

(Trouwens, als je een meer geavanceerde gamepad wilt, verwijder dan gewoon "simpel" uit de bovenstaande URL en je hebt ondersteuning voor rumble en analoge assen.)

Nogmaals, de extensie zou aan de linkerkant van je Scratch-editor moeten verschijnen. Hierboven staat een heel eenvoudig Scratch-programma dat "hallo" zegt wanneer u op knop 0 drukt en "tot ziens" wanneer u deze loslaat.

Stap 8: Dubbele compatibiliteit en snelheid

Ik heb gemerkt dat extensieblokken een orde van grootte sneller werken met de laadmethode die ik gebruikte voor extensies zonder sandbox. Dus tenzij u geïnteresseerd bent in de beveiligingsvoordelen van het draaien in een Web Worker-sandbox, zal uw code er baat bij hebben dat deze wordt geladen met het argument ?load_plugin=URL naar de mod van SheepTester.

U kunt een sandbox-extensie compatibel maken met beide laadmethoden door de volgende code te gebruiken na het definiëren van de extensieklasse (wijzig CLASSNAME in de naam van uw extensieklasse):

(functie() {

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) } })()

Aanbevolen: