Innholdsfortegnelse:

Scratch 3.0 -utvidelser: 8 trinn
Scratch 3.0 -utvidelser: 8 trinn

Video: Scratch 3.0 -utvidelser: 8 trinn

Video: Scratch 3.0 -utvidelser: 8 trinn
Video: Astroprosjekt 8.trinn 2024, Juli
Anonim
Scratch 3.0 -utvidelser
Scratch 3.0 -utvidelser

Skrapeutvidelser er biter av Javascript -kode som legger til nye blokker i Scratch. Selv om Scratch er sammen med en haug med offisielle utvidelser, er det ikke en offisiell mekanisme for å legge til brukerfrie utvidelser.

Da jeg laget min Minecraft -kontrollutvidelse for Scratch 3.0, syntes jeg det var vanskelig å komme i gang. Denne instruksen samler informasjon fra forskjellige kilder (spesielt dette), pluss noen ting jeg oppdaget selv.

Du må vite hvordan du programmerer i Javascript og hvordan du hoster Javascript på et nettsted. For sistnevnte anbefaler jeg GitHub Pages.

Hovedtrikset er å bruke SheepTesters mod av Scratch som lar deg laste inn utvidelser og plugins.

Denne instruksen vil guide deg gjennom å lage to utvidelser:

  • Hent: laste inn data fra en URL og trekke ut JSON -koder, for eksempel for å laste værdata
  • SimpleGamepad: bruker en spillkontroller i Scratch (en mer sofistikert versjon er her).

Trinn 1: To typer utvidelser

Det er to typer utvidelser som jeg vil kalle "usandboxed" og "sandboxed". Sandkasseutvidelser kjøres som nettarbeidere, og har som følge av dette betydelige begrensninger:

  • Nettarbeidere har ikke tilgang til globaler i vinduobjektet (i stedet har de et globalt selvobjekt, som er mye mer begrenset), så du kan ikke bruke dem til ting som tilgang til gamepad.
  • Utvidelser med sandkasse har ikke tilgang til Scratch -kjøretidsobjektet.
  • Utvidelser med sandkasse er mye tregere.
  • Javascript -konsollfeilmeldinger for utvidelser med sandkasse er mer kryptiske i Chrome.

På den andre siden:

  • Å bruke andres utvidelser med sandkasse er tryggere.
  • Sandbox -utvidelser fungerer mer sannsynlig med eventuell offisiell støtte for lasting av utvidelser.
  • Utvidelser med sandkasse kan testes uten å lastes opp til en webserver ved å kode til en data: // URL.

De offisielle utvidelsene (for eksempel Musikk, penn, etc.) er alle uten sandkasse. Konstruktøren for utvidelsen får kjøretidsobjektet fra Scratch, og vinduet er fullt tilgjengelig.

Fetch -utvidelsen er sandkasse, men Gamepad -en trenger navigasjonsobjektet fra vinduet.

Trinn 2: Skrive en Sandboxed Extension: Del I

For å lage en utvidelse, oppretter du en klasse som koder informasjon om den, og legger deretter til en bit kode for å registrere utvidelsen.

Det viktigste i utvidelsesklassen er en getInfo () -metode som returnerer et objekt med de nødvendige feltene:

  • id: det interne navnet på utvidelsen, må være unikt for hver utvidelse
  • navn: det vennlige navnet på utvidelsen, som vises i Scratchs liste over blokker
  • blokker: en liste over objekter som beskriver den nye tilpassede blokken.

Og det er et valgfritt menyfelt som ikke blir brukt i Fetch, men som vil bli brukt i Gamepad.

Så her er den grunnleggende malen for Fetch:

class ScratchFetch {

constructor () {} getInfo () {return {"id": "Hent", "navn": "Hent", "blokker": [/* legg til senere * /]}} / * legg til metoder for blokker * /} Scratch.extensions.register (nytt ScratchFetch ())

Trinn 3: Skrive en Sandboxed Extension: Del II

Nå må vi lage listen over blokker i objektet til getInfo (). Hver blokk trenger minst disse fire feltene:

  • opcode: dette er navnet på metoden som kalles for å utføre blokkens arbeid
  • blockType: dette er blokktypen; de vanligste for utvidelser er:

    • "kommando": gjør noe, men returnerer ikke en verdi
    • "reporter": returnerer en streng eller et tall
    • "Boolean": returnerer en boolsk (vær oppmerksom på store bokstaver)
    • "hatt": fangstblokk for hendelser; hvis din Scratch -kode bruker denne blokken, undersøker Scratch -kjøretiden regelmessig den tilknyttede metoden som returnerer en boolsk for å si om hendelsen har skjedd
  • tekst: dette er en vennlig beskrivelse av blokken, med argumentene i parentes, f.eks. "hente data fra "
  • argumenter: dette er et objekt som har et felt for hvert argument (f.eks. "url" i eksemplet ovenfor); dette objektet har igjen disse feltene:

    • type: enten "streng" eller "nummer"
    • defaultValue: standardverdien som skal fylles ut på forhånd.

Her er for eksempel blokkeringsfeltet i Fetch -utvidelsen min:

"blokker": [{"opcode": "fetchURL", "blockType": "reporter", "text": "hente data fra ", "argument": {"url": {"type": "string", "defaultValue ":" https://api.weather.gov/stations/KNYC/observations "},}}, {" opcode ":" jsonExtract "," blockType ":" reporter "," text ":" extract [name] fra [data] "," argumenter ": {" navn ": {" type ":" streng "," standardValue ":" temperatur "}," data ": {" type ":" streng "," standardValue ": '{"temperatur": 12.3}'},}},]

Her definerte vi to blokker: fetchURL og jsonExtract. Begge er journalister. Den første henter data fra en URL og returnerer den, og den andre trekker ut et felt fra JSON -data.

Til slutt må du inkludere metodene for to blokker. Hver metode tar et objekt som et argument, med objektet inkludert felt for alle argumentene. Du kan dekode disse ved hjelp av krøllete seler i argumentene. For eksempel, her er et synkront eksempel:

jsonExtract ({navn, data}) {

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

Koden trekker navnefeltet fra JSON -dataene. Hvis feltet inneholder en streng, tall eller boolsk, returnerer vi det. Ellers re-JSONify vi feltet. Og vi returnerer en tom streng hvis navnet mangler i JSON.

Noen ganger kan det imidlertid være lurt å lage en blokk som bruker et asynkron API. Metoden fetchURL () bruker fetch API som er asynkron. I et slikt tilfelle bør du returnere et løfte fra din metode som gjør jobben. For eksempel:

fetchURL ({url}) {

retur hent (url). deretter (response => response.text ())}

Det er det. Hele utvidelsen er her.

Trinn 4: Bruke en Sandboxed Extension

Bruke en Sandboxed Extension
Bruke en Sandboxed Extension
Bruke en Sandboxed Extension
Bruke en Sandboxed Extension
Bruke en Sandboxed Extension
Bruke en Sandboxed Extension

Det er to måter å bruke utvidelse med sandkasse. Først kan du laste den opp til en webserver, og deretter laste den inn i SheepTesters Scratch -mod. For det andre kan du kode den inn i en data -URL og laste den inn i Scratch -moden. Jeg bruker faktisk den andre metoden ganske mye for testing, da den unngår bekymringer for at eldre versjoner av utvidelsen blir bufret av serveren. Vær oppmerksom på at mens du kan være vert for javascript fra Github Pages, kan du ikke gjøre det direkte fra et vanlig github -depot.

Min fetch.js ligger på https://arpruss.github.io/fetch.js. Eller du kan konvertere utvidelsen til en data -URL ved å laste den opp her og deretter kopiere den til utklippstavlen. En data -URL er en gigantisk URL som inneholder en hel fil i den.

Gå til SheepTester's Scratch mod. Klikk på knappen Legg til utvidelse i nedre venstre hjørne. Klikk deretter på "Velg en utvidelse", og skriv inn nettadressen din (du kan lime inn hele gigantiske data -URL -en hvis du vil).

Hvis alt gikk bra, vil du ha en oppføring for utvidelsen på venstre side av Scratch-skjermen. Hvis det ikke gikk bra, bør du åpne Javascript-konsollen (shift-ctrl-J i Chrome) og prøve å feilsøke problemet.

Over finner du noen eksempler på kode som henter og analyserer JSON -data fra KNYC (i New York) -stasjonen i US National Weather Service, og viser den, mens du snur spriten til ansiktet på samme måte som vinden blåser. Måten jeg lagde det på var ved å hente dataene i en nettleser og deretter finne ut taggene. Hvis du vil prøve en annen værstasjon, skriver du inn et postnummer i nærheten i søkefeltet på weather.gov, og værsiden for posisjonen din skal gi deg en stasjonsnummer på fire bokstaver, som du kan bruke i stedet for KNYC i kode.

Du kan også inkludere sandbox -utvidelsen din i URL -adressen til SheepTester's mod ved å legge til et "? Url =" argument. For eksempel:

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

Trinn 5: Skrive en usandbox -utvidelse: Introduksjon

Konstruktøren av en ikke -esket utvidelse får passert et Runtime -objekt. Du kan ignorere det eller bruke det. En bruk av Runtime -objektet er å bruke sin currentMSecs -egenskap for å synkronisere hendelser ("hatteblokker"). Så vidt jeg kan se, blir alle hendelsesblokk -opcodes spurt regelmessig, og hver runde i avstemningen har en enkelt currentMSecs -verdi. Hvis du trenger Runtime -objektet, vil du sannsynligvis starte utvidelsen din med:

klasse EXTENSIONCLASS {

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

Alle standardvinduobjektet kan brukes i utvidelsen som ikke er i esken. Til slutt, den usandede utvidelsen din skulle ende med denne magiske koden:

(funksjon () {

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

hvor du bør erstatte EXTENSIONCLASS med utvidelsens klasse.

Trinn 6: Skrive en utvidelse uten sandboks: Enkel gamepad

La oss nå lage en enkel gamepad -forlengelse som gir en enkelt hendelse ("hatt") blokk for når en knapp trykkes eller slippes.

Under hver avstemningssyklus for hendelsesblokker, lagrer vi et tidsstempel fra kjøretidsobjektet, og forrige og nåværende gamepad -tilstand. Tidsstempelet brukes til å gjenkjenne om vi har en ny avstemningssyklus. Så, vi starter med:

class ScratchSimpleGamepad {

constructor (runtime) {this.runtime = runtime this.currentMSecs = -1 this.previousButtons = this.currentButtons = }…} Vi vil ha en hendelsesblokk, med to innganger-et knappetall og en meny for å velge om vi vil at hendelsen skal utløses ved trykk eller slipp. Så, her er vår metode

få informasjon() {

return {"id": "SimpleGamepad", "name": "SimpleGamepad", "blocks": [{"opcode": "buttonPressedReleased", "blockType": "hat", "text": "knapp [eventType] "," argument ": {" b ": {" type ":" number "," defaultValue ":" 0 "}," eventType ": {" type ":" number "," defaultValue ":" 1 "," menu ":" pressReleaseMenu "},},},]," menyer ": {" pressReleaseMenu ": [{tekst:" press ", verdi: 1}, {text:" release ", verdi: 0}],}}; } Jeg tror at verdiene i rullegardinmenyen fortsatt overføres til opcode-funksjonen som strenger, til tross for at de er deklarert som tall. Så sammenlign dem eksplisitt med verdiene som er angitt i menyen etter behov. Vi skriver nå en metode som oppdaterer knappestatusene når en ny avstemningssyklus for hendelser skjer

Oppdater() {

if (this.runtime.currentMSecs == this.currentMSecs) returnerer // ikke en ny pollingsyklus 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) { // forskjellig antall knapper, så ny 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)} Til slutt kan vi implementere hendelsesblokken vår ved å kalle oppdateringsmetoden () og deretter kontrollere om den nødvendige knappen nettopp er trykket eller slippes, ved å sammenligne nåværende og tidligere knappestatuser

buttonPressedReleased ({b, eventType}) {

this.update () if (b <this.currentButtons.length) {if (eventType == 1) {// note: dette vil være en streng, så det er bedre å sammenligne det med 1 enn å behandle det som en booleske hvis (this.currentButtons &&! this.previousButtons ) {return true}} else {if (! this.currentButtons && this.previousButtons ) {return true}}} return false { Og til slutt legger vi til vår magiske forlengelse registreringskode etter å ha definert klassen

(funksjon () {

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

Du kan få hele koden her.

Trinn 7: Bruke en ikke -innbakt utvidelse

Bruke en ikke -innbakt utvidelse
Bruke en ikke -innbakt utvidelse

Igjen, vert utvidelsen et sted, og denne gangen laster du den inn med load_plugin = i stedet for url = argument til SheepTester's Scratch -mod. For eksempel, for min enkle Gamepad -mod, gå til:

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

(Forresten, hvis du vil ha en mer sofistikert gamepad, fjerner du "enkelt" fra URL -adressen ovenfor, så får du rumble og analog aksestøtte.)

Igjen, utvidelsen skal vises på venstre side av Scratch -editoren. Over er et veldig enkelt Scratch -program som sier "hei" når du trykker på knapp 0 og "farvel" når du slipper den.

Trinn 8: Dual-kompatibilitet og hastighet

Jeg har lagt merke til at utvidelsesblokker kjører en størrelsesorden raskere ved hjelp av lastemetoden jeg brukte for utvidelser uten eske. Så med mindre du bryr deg om sikkerhetsfordelene ved å kjøre i en Web Worker -sandkasse, vil koden din tjene på å bli lastet med argumentet? Load_plugin = URL til SheepTesters mod.

Du kan gjøre en sandkasseutvidelse kompatibel med begge lastemetodene ved å bruke følgende kode etter å ha definert utvidelsesklassen (endre CLASSNAME til navnet på utvidelsesklassen):

(funksjon () {

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

Anbefalt: