Indholdsfortegnelse:

Scratch 3.0 -udvidelser: 8 trin
Scratch 3.0 -udvidelser: 8 trin

Video: Scratch 3.0 -udvidelser: 8 trin

Video: Scratch 3.0 -udvidelser: 8 trin
Video: Scratch! 2024, Juli
Anonim
Scratch 3.0 -udvidelser
Scratch 3.0 -udvidelser

Scratch -udvidelser er stykker af Javascript -kode, der tilføjer nye blokke til Scratch. Mens Scratch er bundtet med en masse officielle udvidelser, er der ikke en officiel mekanisme til tilføjelse af brugerfremstillede udvidelser.

Da jeg lavede min Minecraft -kontroludvidelse til Scratch 3.0, fandt jeg det svært at komme i gang. Denne instruktør indsamler oplysninger fra forskellige kilder (især dette) plus et par ting, jeg selv fandt ud af.

Du skal vide, hvordan du programmerer i Javascript, og hvordan du hoster dit Javascript på et websted. Til sidstnævnte anbefaler jeg GitHub Pages.

Hovedtricket er at bruge SheepTesters mod af Scratch, som lader dig indlæse udvidelser og plugins.

Denne instruks vil guide dig gennem to udvidelser:

  • Hent: indlæsning af data fra en URL og udtrækning af JSON -tags, f.eks. Til indlæsning af vejrdata
  • SimpleGamepad: ved hjælp af en spilcontroller i Scratch (en mere sofistikeret version er her).

Trin 1: To typer udvidelser

Der er to typer udvidelser, som jeg vil kalde "usandboxed" og "sandboxed". Udvidelser med sandkasse kører som webarbejdere, og har som følge heraf betydelige begrænsninger:

  • Webarbejdere kan ikke få adgang til globaler i vindueobjektet (i stedet har de et globalt selvobjekt, som er meget mere begrænset), så du kan ikke bruge dem til ting som adgang til gamepad.
  • Udvidelser med sandkasse har ikke adgang til objektet Scratch runtime.
  • Udvidelser med sandkasse er meget langsommere.
  • Javascript -konsolfejlmeddelelser for udvidelser med sandkasse er mere kryptiske i Chrome.

På den anden side:

  • Det er sikrere at bruge andres sandkasseudvidelser.
  • Sandboxed -udvidelser fungerer mere sandsynligt med enhver eventuel officiel understøttelse af indlæsning af udvidelser.
  • Udvidelser med sandkasse kan testes uden at uploades til en webserver ved at kode til en data: // URL.

De officielle udvidelser (f.eks. Musik, pen osv.) Er alle ikke i sandboks. Konstruktøren til udvidelsen får runtime -objektet fra Scratch, og vinduet er fuldt tilgængeligt.

Fetch -udvidelsen er sandkasse, men Gamepad -en har brug for navigatorobjektet fra vinduet.

Trin 2: Skrivning af en sandkasseudvidelse: Del I

For at lave en udvidelse opretter du en klasse, der koder for oplysninger om den, og tilføjer derefter en smule kode for at registrere udvidelsen.

Det vigtigste i udvidelsesklassen er en getInfo () -metode, der returnerer et objekt med de nødvendige felter:

  • id: det interne navn på udvidelsen skal være unikt for hver udvidelse
  • navn: udvidelsens venlige navn, der vises på Scratchs liste over blokke
  • blokke: en liste over objekter, der beskriver den nye brugerdefinerede blok.

Og der er et valgfrit menufelt, der ikke bliver brugt i Fetch, men vil blive brugt i Gamepad.

Så her er den grundlæggende skabelon til Hent:

klasse ScratchFetch {

constructor () {} getInfo () {return {"id": "Hent", "navn": "Hent", "blokke": [/* tilføj senere * /]}} / * tilføj metoder til blokke * /} Scratch.extensions.register (ny ScratchFetch ())

Trin 3: Skrivning af en sandkasseudvidelse: Del II

Nu skal vi oprette listen over blokke i getInfo () objekt. Hver blok har brug for mindst disse fire felter:

  • opcode: dette er navnet på den metode, der kaldes til at udføre blokens arbejde
  • blockType: dette er bloktypen; de mest almindelige for udvidelser er:

    • "kommando": gør noget, men returnerer ikke en værdi
    • "reporter": returnerer en streng eller et tal
    • "Boolean": returnerer en boolean (bemærk store bogstaver)
    • "hat": begivenhedsfangende blok; hvis din Scratch -kode bruger denne blok, undersøger Scratch -runtime regelmæssigt den tilknyttede metode, der returnerer en boolean for at sige, om hændelsen er sket
  • tekst: dette er en venlig beskrivelse af blokken med argumenterne i parentes, f.eks. "hent data fra "
  • argumenter: dette er et objekt med et felt for hvert argument (f.eks. "url" i ovenstående eksempel); dette objekt har til gengæld disse felter:

    • type: enten "streng" eller "nummer"
    • defaultValue: standardværdien, der skal udfyldes på forhånd.

Her er for eksempel blokkefeltet i min Hent -udvidelse:

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

Her definerede vi to blokke: fetchURL og jsonExtract. Begge er journalister. Den første trækker data fra en URL og returnerer den, og den anden udtrækker et felt fra JSON -data.

Endelig skal du inkludere metoderne for to blokke. Hver metode tager et objekt som et argument, hvor objektet indeholder felter for alle argumenterne. Du kan afkode disse ved hjælp af krøllede seler i argumenterne. For eksempel er her 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") return t? 1: 0 return JSON.stringify (out)} else {return ""}}

Koden trækker navnefeltet fra JSON -dataene. Hvis feltet indeholder en streng, et tal eller en boolean, returnerer vi det. Ellers re-JSONify vi feltet. Og vi returnerer en tom streng, hvis navnet mangler i JSON.

Nogle gange kan du dog lave en blok, der bruger en asynkron API. Metoden fetchURL () bruger fetch API, som er asynkron. I et sådant tilfælde skal du returnere et løfte fra din metode, der gør arbejdet. For eksempel:

fetchURL ({url}) {

returner hentning (url). derefter (respons => svar.tekst ())}

Det er det. Den fulde udvidelse er her.

Trin 4: Brug af en sandkasseudvidelse

Brug af en sandkasseudvidelse
Brug af en sandkasseudvidelse
Brug af en sandkasseudvidelse
Brug af en sandkasseudvidelse
Brug af en sandkasseudvidelse
Brug af en sandkasseudvidelse

Der er to måder at bruge sandboxed extension. Først kan du uploade det til en webserver og derefter indlæse det i SheepTesters Scratch -mod. For det andet kan du kode det ind i en data -URL og indlæse det i Scratch -moden. Jeg bruger faktisk den anden metode ganske lidt til test, da den undgår bekymringer om ældre versioner af udvidelsen, der bliver cachelagret af serveren. Bemærk, at mens du kan være vært for javascript fra Github Pages, kan du ikke gøre det direkte fra et almindeligt github -lager.

Min fetch.js er hostet på https://arpruss.github.io/fetch.js. Eller du kan konvertere din udvidelse til en data -URL ved at uploade den her og derefter kopiere den til udklipsholderen. En data -URL er en kæmpe URL, der indeholder en hel fil i den.

Gå til SheepTester's Scratch mod. Klik på knappen Tilføj udvidelse i nederste venstre hjørne. Klik derefter på "Vælg en udvidelse", og indtast din webadresse (du kan indsætte hele den gigantiske data -URL, hvis du vil).

Hvis alt gik godt, har du en post til din udvidelse i venstre side af din Scratch-skærm. Hvis det ikke gik godt, skal du åbne din Javascript-konsol (shift-ctrl-J i Chrome) og prøve at fejlsøge problemet.

Ovenfor finder du nogle eksempler på kode, der henter og analyserer JSON -data fra KNYC (i New York) -stationen i US National Weather Service, og viser den, mens du vender spriten til ansigtet på samme måde, som vinden blæser. Den måde, jeg lavede det på, var ved at hente dataene i en webbrowser og derefter finde ud af koderne. Hvis du vil prøve en anden vejrstation, skal du indtaste et nærliggende postnummer i søgefeltet på weather.gov, og vejrsiden for din placering skal give dig en firebogstavsstationskode, som du kan bruge i stedet for KNYC i kode.

Du kan også inkludere din sandkasseudvidelse direkte i URL'en til SheepTester's mod ved at tilføje et "? Url =" argument. For eksempel:

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

Trin 5: Skrivning af en ikke -sandboxet udvidelse: Introduktion

Konstruktøren af en ikke -sandboxet udvidelse får bestået et Runtime -objekt. Du kan ignorere det eller bruge det. En brug af Runtime -objektet er at bruge dets nuværende MSMS -egenskab til at synkronisere hændelser ("hat -blokke"). Så vidt jeg kan se, bliver alle hændelsesblok -opcodes undersøgt regelmæssigt, og hver runde i afstemningen har en enkelt currentMSecs -værdi. Hvis du har brug for Runtime -objektet, starter du sandsynligvis din udvidelse med:

klasse EXTENSIONCLASS {

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

Alle standardvindueobjekter kan bruges i udvidelsen uden sandkasse. Endelig skal din usandbox -udvidelse ende med denne magiske kode:

(funktion () {

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 skal udskifte EXTENSIONCLASS med din udvidelses klasse.

Trin 6: Skrivning af en udvidet udvidelse: Enkel gamepad

Lad os nu lave en simpel gamepad -udvidelse, der giver en enkelt hændelsesblok ("hat"), når der trykkes på en knap eller slippes.

Under hver afstemningscyklus for hændelsesblokke gemmer vi et tidsstempel fra runtime -objektet, og den tidligere og aktuelle gamepad -tilstand. Tidsstemplet bruges til at genkende, om vi har en ny afstemningscyklus. Så starter vi med:

klasse ScratchSimpleGamepad {

constructor (runtime) {this.runtime = runtime this.currentMSecs = -1 this.previousButtons = this.currentButtons = }…} Vi vil have en hændelsesblok med to indgange-et knapnummer og en menu til at vælge, om vi vil have hændelsen til at udløses ved tryk eller slip. Så her er vores metode

få information() {

return {"id": "SimpleGamepad", "name": "SimpleGamepad", "blocks": [{"opcode": "buttonPressedReleased", "blockType": "hat", "text": "knap [eventType] "," argumenter ": {" b ": {" type ":" nummer "," defaultValue ":" 0 "}," eventType ": {" type ":" nummer "," defaultValue ":" 1 "," menu ":" pressReleaseMenu "},},},]," menuer ": {" pressReleaseMenu ": [{tekst:" press ", værdi: 1}, {text:" release ", værdi: 0}],}}; } Jeg tror, at værdierne i rullemenuen stadig overføres til opcode-funktionen som strenge, på trods af at de er erklæret som tal. Så sammenlign dem eksplicit med de værdier, der er angivet i menuen efter behov. Vi skriver nu en metode, der opdaterer knaptilstandene, når der sker en ny afstemningscyklus for hændelser

opdater () {

hvis (this.runtime.currentMSecs == this.currentMSecs) returnerer // ikke en ny pollingcyklus 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) { // forskelligt antal 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)} Endelig kan vi implementere vores hændelsesblok ved at kalde metoden update () og derefter kontrollere, om den nødvendige knap lige er blevet trykket eller frigivet ved at sammenligne nuværende og tidligere knaptilstande

buttonPressedReleased ({b, eventType}) {

this.update () if (b <this.currentButtons.length) {if (eventType == 1) {// note: dette vil være en streng, så det er bedre at sammenligne det med 1 end at behandle det som en booleske hvis (this.currentButtons &&! this.previousButtons ) {return true}} else {if (! this.currentButtons && this.previousButtons ) {return true}}} return false { Og endelig tilføjer vi vores magiske udvidelsesregistreringskode efter at have defineret klassen

(funktion () {

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

Du kan få den fulde kode her.

Trin 7: Brug af en ikke -sandboxet udvidelse

Brug af en ikke -sandboxet udvidelse
Brug af en ikke -sandboxet udvidelse

Igen, vært din udvidelse et sted, og denne gang indlæse den med load_plugin = frem for url = argument til SheepTester's Scratch mod. For eksempel, for min enkle Gamepad -mod, skal du gå til:

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

(Forresten, hvis du vil have en mere sofistikeret gamepad, skal du bare fjerne "simple" fra ovenstående URL, så får du rumlen og analog akseunderstøttelse.)

Igen skal udvidelsen vises i venstre side af din Scratch -editor. Ovenfor er et meget simpelt Scratch -program, der siger "hej", når du trykker på knap 0 og "farvel", når du slipper det.

Trin 8: Dual-kompatibilitet og hastighed

Jeg har lagt mærke til, at udvidelsesblokke kører en størrelsesorden hurtigere ved hjælp af den indlæsningsmetode, jeg brugte til udvidelser uden sandkasse. Så medmindre du bekymrer dig om sikkerhedsfordelene ved at køre i en Web Worker -sandkasse, vil din kode have fordel af at blive indlæst med argumentet? Load_plugin = URL til SheepTesters mod.

Du kan gøre en sandkasseudvidelse kompatibel med begge indlæsningsmetoder ved at bruge følgende kode efter at have defineret udvidelsesklassen (skift CLASSNAME til navnet på din udvidelsesklasse):

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

Anbefalede: