Sommario:

Estensioni di Scratch 3.0: 8 passaggi
Estensioni di Scratch 3.0: 8 passaggi

Video: Estensioni di Scratch 3.0: 8 passaggi

Video: Estensioni di Scratch 3.0: 8 passaggi
Video: Scratch 3.0 #8: gesture con Makey Makey 2024, Luglio
Anonim
Estensioni di Scratch 3.0
Estensioni di Scratch 3.0

Le estensioni di Scratch sono parti di codice Javascript che aggiungono nuovi blocchi a Scratch. Sebbene Scratch sia in bundle con una serie di estensioni ufficiali, non esiste un meccanismo ufficiale per l'aggiunta di estensioni create dall'utente.

Quando stavo realizzando la mia estensione di controllo di Minecraft per Scratch 3.0, ho trovato difficile iniziare. Questo Instructable raccoglie informazioni da varie fonti (soprattutto questa), oltre ad alcune cose che ho scoperto da solo.

Devi sapere come programmare in Javascript e come ospitare il tuo Javascript su un sito web. Per quest'ultimo, consiglio GitHub Pages.

Il trucco principale è usare la mod di Scratch di SheepTester che ti permette di caricare estensioni e plugin.

Questo Instructable ti guiderà attraverso la creazione di due estensioni:

  • Recupera: caricamento di dati da un URL ed estrazione di tag JSON, ad esempio per il caricamento di dati meteo
  • SimpleGamepad: utilizzo di un controller di gioco in Scratch (una versione più sofisticata è qui).

Passaggio 1: due tipi di estensioni

Esistono due tipi di estensioni che chiamerò "unsandboxed" e "sandboxed". Le estensioni in modalità sandbox vengono eseguite come Web Worker e, di conseguenza, presentano limitazioni significative:

  • I Web Worker non possono accedere ai globali nell'oggetto finestra (invece, hanno un oggetto sé globale, che è molto più limitato), quindi non puoi usarli per cose come l'accesso al gamepad.
  • Le estensioni in modalità sandbox non hanno accesso all'oggetto runtime di Scratch.
  • Le estensioni in modalità sandbox sono molto più lente.
  • I messaggi di errore della console Javascript per le estensioni in modalità sandbox sono più criptici in Chrome.

D'altra parte:

  • L'utilizzo delle estensioni sandbox di altre persone è più sicuro.
  • È più probabile che le estensioni in modalità sandbox funzionino con qualsiasi eventuale supporto ufficiale per il caricamento delle estensioni.
  • Le estensioni in modalità sandbox possono essere testate senza caricarle su un server Web codificandole in un URL data://.

Le estensioni ufficiali (come Music, Pen, ecc.) sono tutte senza sandbox. Il costruttore per l'estensione ottiene l'oggetto runtime da Scratch e la finestra è completamente accessibile.

L'estensione Fetch è in modalità sandbox, ma quella Gamepad necessita dell'oggetto navigator dalla finestra.

Passaggio 2: scrittura di un'estensione in modalità sandbox: parte I

Per creare un'estensione, crei una classe che codifichi le informazioni su di essa, quindi aggiungi un po' di codice per registrare l'estensione.

La cosa principale nella classe di estensione è un metodo getInfo() che restituisce un oggetto con i campi richiesti:

  • id: il nome interno dell'estensione, deve essere univoco per ogni estensione
  • nome: il nome descrittivo dell'estensione, visualizzato nell'elenco dei blocchi di Scratch
  • blocchi: un elenco di oggetti che descrivono il nuovo blocco personalizzato.

E c'è un campo menu opzionale che non viene utilizzato in Fetch ma verrà utilizzato nel Gamepad.

Quindi, ecco il modello di base per Fetch:

class ScratchFetch {

constructor() { } getInfo() { return { "id": "Fetch", "name": "Fetch", "blocks": [/* aggiungi dopo */] } } /* aggiungi metodi per i blocchi */ } Scratch.extensions.register(nuovo ScratchFetch())

Passaggio 3: scrittura di un'estensione in modalità sandbox: parte II

Ora, dobbiamo creare l'elenco dei blocchi nell'oggetto di getInfo(). Ogni blocco ha bisogno di almeno questi quattro campi:

  • opcode: questo è il nome del metodo che viene chiamato per fare il lavoro del blocco
  • blockType: questo è il tipo di blocco; i più comuni per le estensioni sono:

    • "comando": fa qualcosa ma non restituisce un valore
    • "reporter": restituisce una stringa o un numero
    • "Boolean": restituisce un booleano (notare la maiuscola)
    • "cappello": blocco di cattura degli eventi; se il tuo codice Scratch utilizza questo blocco, il runtime di Scratch esegue regolarmente il polling del metodo associato che restituisce un booleano per dire se l'evento si è verificato
  • testo: questa è una descrizione amichevole del blocco, con gli argomenti tra parentesi, ad esempio "recupera dati da "
  • argomenti: questo è un oggetto che ha un campo per ogni argomento (es. "url" nell'esempio sopra); questo oggetto a sua volta ha questi campi:

    • tipo: "stringa" o "numero"
    • defaultValue: il valore predefinito da precompilare.

Ad esempio, ecco il campo dei blocchi nella mia estensione Fetch:

"blocchi": [{ "opcode": "fetchURL", "blockType": "reporter", "text": "recupera dati da ", "arguments": { "url": { "type": "string", "defaultValue ": "https://api.weather.gov/stations/KNYC/observations" }, } }, { "opcode": "jsonExtract", "blockType": "reporter", "text": "extract [nome] from [data]", "arguments": { "name": { "type": "string", "defaultValue": "temperature" }, "data": { "type": "string", "defaultValue": '{"temperatura": 12.3}' }, } },]

Qui abbiamo definito due blocchi: fetchURL e jsonExtract. Entrambi sono giornalisti. Il primo estrae i dati da un URL e li restituisce, mentre il secondo estrae un campo dai dati JSON.

Infine, è necessario includere i metodi per due blocchi. Ogni metodo accetta un oggetto come argomento, con l'oggetto che include i campi per tutti gli argomenti. Puoi decodificarli usando le parentesi graffe negli argomenti. Ad esempio, ecco un esempio sincrono:

jsonExtract({nome, dati}) {

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

Il codice estrae il campo del nome dai dati JSON. Se il campo contiene una stringa, un numero o un valore booleano, lo restituiamo. In caso contrario, ri-JSONify il campo. E restituiamo una stringa vuota se il nome non è presente nel JSON.

A volte, tuttavia, potresti voler creare un blocco che utilizzi un'API asincrona. Il metodo fetchURL() utilizza l'API fetch che è asincrona. In tal caso, dovresti restituire una promessa dal tuo metodo che fa il lavoro. Ad esempio:

fetchURL({url}) {

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

Questo è tutto. L'estensione completa è qui.

Passaggio 4: utilizzo di un'estensione in modalità sandbox

Utilizzo di un'estensione in modalità sandbox
Utilizzo di un'estensione in modalità sandbox
Utilizzo di un'estensione in modalità sandbox
Utilizzo di un'estensione in modalità sandbox
Utilizzo di un'estensione in modalità sandbox
Utilizzo di un'estensione in modalità sandbox

Esistono due modi per utilizzare l'estensione sandbox. Innanzitutto, puoi caricarlo su un server Web e quindi caricarlo nella mod Scratch di SheepTester. In secondo luogo, puoi codificarlo in un URL di dati e caricarlo nella mod di Scratch. In realtà uso un po' il secondo metodo per i test, poiché evita di preoccuparsi che le versioni precedenti dell'estensione vengano memorizzate nella cache dal server. Nota che mentre puoi ospitare javascript da Github Pages, non puoi farlo direttamente da un normale repository github.

Il mio fetch.js è ospitato su https://arpruss.github.io/fetch.js. Oppure puoi convertire la tua estensione in un URL di dati caricandola qui e poi copiandola negli appunti. Un URL di dati è un URL gigante che contiene un intero file.

Vai a SheepTester's Scratch mod. Fare clic sul pulsante Aggiungi estensione nell'angolo in basso a sinistra. Quindi fai clic su "Scegli un'estensione" e inserisci il tuo URL (puoi incollare l'intero URL dei dati giganti, se lo desideri).

Se tutto è andato bene, avrai una voce per la tua estensione sul lato sinistro della schermata di Scratch. Se le cose non sono andate bene, dovresti aprire la tua console Javascript (shift-ctrl-J in Chrome) e provare a eseguire il debug del problema.

Sopra troverai un codice di esempio che recupera e analizza i dati JSON dalla stazione KNYC (a New York) del National Weather Service degli Stati Uniti e li visualizza, mentre si gira lo sprite nello stesso modo in cui soffia il vento. Il modo in cui l'ho realizzato è stato recuperando i dati in un browser Web e quindi individuando i tag. Se vuoi provare una stazione meteorologica diversa, inserisci un codice postale nelle vicinanze nella casella di ricerca su weather.gov e la pagina meteo per la tua posizione dovrebbe darti un codice di stazione di quattro lettere, che puoi usare al posto di KNYC nel codice.

Puoi anche includere la tua estensione sandbox direttamente nell'URL per la mod di SheepTester aggiungendo un argomento "?url=". Ad esempio:

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

Passaggio 5: scrittura di un'estensione senza sandbox: introduzione

Al costruttore di un'estensione senza sandbox viene passato un oggetto Runtime. Puoi ignorarlo o usarlo. Un uso dell'oggetto Runtime consiste nell'utilizzare la sua proprietà currentMSecs per sincronizzare gli eventi ("blocchi cappello"). Per quanto ne so, tutti gli opcode dei blocchi di eventi vengono interrogati regolarmente e ogni round del polling ha un singolo valore currentMSecs. Se hai bisogno dell'oggetto Runtime, probabilmente inizierai la tua estensione con:

class EXTENSIONCLASS {

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

Tutte le cose standard dell'oggetto finestra possono essere utilizzate nell'estensione senza sandbox. Infine, la tua estensione senza sandbox dovrebbe terminare con questo codice magico:

(funzione() {

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

dove dovresti sostituire EXTENSIONCLASS con la classe della tua estensione.

Passaggio 6: scrittura di un'estensione senza sandbox: gamepad semplice

Creiamo ora una semplice estensione del gamepad che fornisca un singolo blocco evento ("cappello") per quando un pulsante viene premuto o rilasciato.

Durante ogni ciclo di polling del blocco di eventi, salveremo un timestamp dall'oggetto runtime e gli stati del gamepad precedente e corrente. Il timestamp viene utilizzato per riconoscere se abbiamo un nuovo ciclo di polling. Quindi, iniziamo con:

class ScratchSimpleGamepad {

costruttore(runtime) { this.runtime = runtime this.currentMSecs = -1 this.previousButtons = this.currentButtons = } … } Avremo un blocco di eventi, con due input: un numero di pulsante e un menu per selezionare se vogliamo che l'evento si attivi alla pressione o al rilascio. Quindi, ecco il nostro metodo

ottenere informazioni() {

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}], } }; } Penso che i valori nel menu a discesa vengano ancora passati alla funzione opcode come stringhe, nonostante siano dichiarati come numeri. Quindi confrontali esplicitamente con i valori specificati nel menu, se necessario. Ora scriviamo un metodo che aggiorna gli stati del pulsante ogni volta che si verifica un nuovo ciclo di polling dell'evento

aggiornare() {

if (this.runtime.currentMSecs == this.currentMSecs) return // non un nuovo ciclo di polling 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) { // diverso numero di pulsanti, quindi nuovo 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) } Infine, possiamo implementare il nostro blocco di eventi, chiamando il metodo update() e quindi controllando se il pulsante richiesto è stato appena premuto o rilasciato, confrontando gli stati del pulsante corrente e precedente

buttonPressedReleased({b, eventType}) {

this.update() if (b < this.currentButtons.length) { if (eventType == 1) { // nota: questa sarà una stringa, quindi è meglio confrontarla con 1 piuttosto che trattarla come un booleano if (this.currentButtons && ! this.previousButtons) { return true } } else { if (!this.currentButtons && this.previousButtons) { return true } } } return false } E infine aggiungiamo il nostro codice di registrazione dell'estensione magica dopo aver definito la classe

(funzione() {

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

Puoi ottenere il codice completo qui.

Passaggio 7: utilizzo di un'estensione senza sandbox

Utilizzo di un'estensione senza sandbox
Utilizzo di un'estensione senza sandbox

Ancora una volta, ospita la tua estensione da qualche parte e questa volta caricala con l'argomento load_plugin= anziché url= nel mod Scratch di SheepTester. Ad esempio, per la mia semplice mod Gamepad, vai a:

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

(A proposito, se vuoi un gamepad più sofisticato, rimuovi "semplice" dall'URL sopra e avrai il rombo e il supporto dell'asse analogico.)

Ancora una volta, l'estensione dovrebbe apparire sul lato sinistro del tuo editor di Scratch. Sopra c'è un programma Scratch molto semplice che dice "ciao" quando si preme il pulsante 0 e "arrivederci" quando lo si rilascia.

Passaggio 8: doppia compatibilità e velocità

Ho notato che i blocchi di estensioni vengono eseguiti un ordine di grandezza più velocemente utilizzando il metodo di caricamento che ho usato per le estensioni senza sandbox. Quindi, a meno che non ti interessino i vantaggi in termini di sicurezza dell'esecuzione in una sandbox di Web Worker, il tuo codice trarrà vantaggio dal caricamento con l'argomento ?load_plugin=URL nel mod di SheepTester.

Puoi rendere un'estensione sandbox compatibile con entrambi i metodi di caricamento utilizzando il seguente codice dopo aver definito la classe di estensione (cambia CLASSNAME con il nome della tua classe di estensione):

(funzione() {

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

Consigliato: