Spisu treści:

Rozszerzenia Scratch 3.0: 8 kroków
Rozszerzenia Scratch 3.0: 8 kroków

Wideo: Rozszerzenia Scratch 3.0: 8 kroków

Wideo: Rozszerzenia Scratch 3.0: 8 kroków
Wideo: Webinar "Scratch 3.0 - łatwiejszy niż myślisz” CodeWeek2020 | webinar dla klas 3-4 | 20.10.2020 2024, Lipiec
Anonim
Rozszerzenia Scratch 3.0
Rozszerzenia Scratch 3.0

Rozszerzenia Scratch to fragmenty kodu JavaScript, które dodają nowe bloki do Scratch. Chociaż Scratch jest dostarczany w pakiecie z kilkoma oficjalnymi rozszerzeniami, nie ma oficjalnego mechanizmu dodawania rozszerzeń stworzonych przez użytkowników.

Kiedy tworzyłem moje rozszerzenie sterujące Minecraft dla Scratch 3.0, trudno mi było zacząć. Ten Instructable zbiera informacje z różnych źródeł (zwłaszcza z tego), a także kilka rzeczy, które sam odkryłem.

Musisz wiedzieć, jak programować w JavaScript i jak hostować JavaScript na stronie internetowej. Dla tych ostatnich polecam GitHub Pages.

Główną sztuczką jest użycie modu Scratch firmy SheepTester, który umożliwia ładowanie rozszerzeń i wtyczek.

Ta instrukcja poprowadzi Cię przez tworzenie dwóch rozszerzeń:

  • Fetch: ładowanie danych z adresu URL i wyodrębnianie tagów JSON, na przykład do ładowania danych o pogodzie
  • SimpleGamepad: używanie kontrolera gier w Scratchu (bardziej zaawansowana wersja jest tutaj).

Krok 1: Dwa rodzaje rozszerzeń

Istnieją dwa rodzaje rozszerzeń, które nazywam „unsandboxed” i „sandboxed”. Rozszerzenia w trybie piaskownicy działają jako Web Workers, przez co mają istotne ograniczenia:

  • Web Workers nie mogą uzyskać dostępu do globalnych obiektów okna (zamiast tego mają globalny obiekt self, który jest znacznie bardziej ograniczony), więc nie można ich używać do rzeczy takich jak dostęp do gamepada.
  • Rozszerzenia w trybie piaskownicy nie mają dostępu do obiektu środowiska wykonawczego Scratch.
  • Rozszerzenia w trybie piaskownicy są znacznie wolniejsze.
  • Komunikaty o błędach konsoli JavaScript dla rozszerzeń piaskownicy są bardziej tajemnicze w Chrome.

Z drugiej strony:

  • Korzystanie z rozszerzeń piaskownicy innych osób jest bezpieczniejsze.
  • Rozszerzenia w trybie piaskownicy prawdopodobnie będą działać z ewentualną obsługą wczytywania rozszerzeń oficjalnych.
  • Rozszerzenia w trybie piaskownicy można testować bez przesyłania ich na serwer sieciowy, kodując je w adresie URL data://.

Oficjalne rozszerzenia (takie jak Music, Pen itp.) nie są dostępne w piaskownicy. Konstruktor rozszerzenia pobiera obiekt runtime ze Scratcha, a okno jest w pełni dostępne.

Rozszerzenie Fetch jest piaskownicą, ale Gamepad potrzebuje obiektu nawigatora z okna.

Krok 2: Pisanie rozszerzenia w trybie piaskownicy: część I

Aby zrobić rozszerzenie, tworzysz klasę, która koduje informacje o nim, a następnie dodajesz fragment kodu, aby zarejestrować rozszerzenie.

Najważniejszą rzeczą w klasie rozszerzenia jest metoda getInfo(), która zwraca obiekt z wymaganymi polami:

  • id: wewnętrzna nazwa rozszerzenia, musi być unikalna dla każdego rozszerzenia
  • name: przyjazna nazwa rozszerzenia, pojawiająca się na liście bloków Scratcha
  • bloki: lista obiektów opisujących nowy niestandardowy blok.

Jest też opcjonalne pole menu, które nie jest używane w aplikacji Fetch, ale będzie używane w Gamepadzie.

Oto podstawowy szablon dla Fetch:

class ScratchFetch {

konstruktor() { } getInfo() { return { "id": "Fetch", "name": "Fetch", "blocks": [/* dodaj później */] } } /* dodaj metody dla bloków */ } Scratch.extensions.register(nowy ScratchFetch())

Krok 3: Pisanie rozszerzenia w trybie piaskownicy: część II

Teraz musimy stworzyć listę bloków w obiekcie getInfo(). Każdy blok wymaga co najmniej tych czterech pól:

  • opcode: jest to nazwa metody, która jest wywoływana w celu wykonania pracy bloku
  • blockType: jest to typ bloku; najczęstsze dla rozszerzeń to:

    • "polecenie": robi coś, ale nie zwraca wartości
    • "reporter": zwraca ciąg lub liczbę
    • „Boolean”: zwraca wartość logiczną (zwróć uwagę na wielkość liter)
    • "kapelusz": blok łapania zdarzeń; jeśli twój kod Scratch używa tego bloku, środowisko wykonawcze Scratch regularnie odpytuje powiązaną metodę, która zwraca wartość logiczną, aby powiedzieć, czy zdarzenie miało miejsce
  • tekst: jest to przyjazny opis bloku, z argumentami w nawiasach, np. „pobierz dane z ”
  • arguments: jest to obiekt mający pole dla każdego argumentu (np. "url" w powyższym przykładzie); ten obiekt z kolei ma te pola:

    • typ: albo „ciąg” albo „liczba”
    • defaultValue: domyślna wartość do wstępnego wypełnienia.

Na przykład oto pole bloków w moim rozszerzeniu Fetch:

"Bloki": [{ "opcode": "fetchURL", "blockType": "reporter", "text": "pobierz dane z ", "arguments": { "url": { "type": "string", "defaultValue ": "https://api.weather.gov/stations/KNYC/observations" }, } }, { "opcode": "jsonExtract", "blockType": "reporter", "text": "extract [nazwa] from [data]", "arguments": { "name": { "type": "string", "defaultValue": "temperature" }, "data": { "type": "string", "defaultValue": '{"temperatura": 12.3}' }, } },]

Tutaj zdefiniowaliśmy dwa bloki: fetchURL i jsonExtract. Obaj są reporterami. Pierwszy pobiera dane z adresu URL i zwraca je, a drugi wyodrębnia pole z danych JSON.

Na koniec musisz uwzględnić metody dla dwóch bloków. Każda metoda przyjmuje obiekt jako argument, przy czym obiekt zawiera pola dla wszystkich argumentów. Możesz je zdekodować za pomocą nawiasów klamrowych w argumentach. Na przykład, oto jeden przykład synchroniczny:

jsonExtract({nazwa, dane}) {

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 "" } }

Kod pobiera pole nazwy z danych JSON. Jeśli pole zawiera ciąg, liczbę lub wartość logiczną, zwracamy to. W przeciwnym razie ponownie JSONify pole. I zwracamy pusty ciąg, jeśli w JSON brakuje nazwy.

Czasami jednak możesz chcieć utworzyć blok, który używa asynchronicznego interfejsu API. Metoda fetchURL() korzysta z asynchronicznego interfejsu API pobierania. W takim przypadku powinieneś zwrócić obietnicę swojej metody, która działa. Na przykład:

fetchURL({url}) {

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

Otóż to. Pełne rozszerzenie jest tutaj.

Krok 4: Korzystanie z rozszerzenia w trybie piaskownicy

Korzystanie z rozszerzenia w trybie piaskownicy
Korzystanie z rozszerzenia w trybie piaskownicy
Korzystanie z rozszerzenia w trybie piaskownicy
Korzystanie z rozszerzenia w trybie piaskownicy
Korzystanie z rozszerzenia w trybie piaskownicy
Korzystanie z rozszerzenia w trybie piaskownicy

Istnieją dwa sposoby korzystania z rozszerzenia piaskownicy. Najpierw możesz przesłać go na serwer WWW, a następnie załadować do modu SheepTester's Scratch. Po drugie, możesz zakodować go w adresie URL danych i załadować go do moda Scratch. Właściwie używam drugiej metody do testowania, ponieważ pozwala to uniknąć obaw o to, że starsze wersje rozszerzenia będą buforowane przez serwer. Zauważ, że chociaż możesz hostować JavaScript z Github Pages, nie możesz tego zrobić bezpośrednio ze zwykłego repozytorium github.

Mój fetch.js jest hostowany na https://arpruss.github.io/fetch.js. Możesz też przekonwertować rozszerzenie na adres URL danych, przesyłając je tutaj, a następnie skopiuj do schowka. Adres URL danych to gigantyczny adres URL, który zawiera w sobie cały plik.

Przejdź do modu SheepTester's Scratch. Kliknij przycisk Dodaj rozszerzenie w lewym dolnym rogu. Następnie kliknij „Wybierz rozszerzenie” i wprowadź swój adres URL (możesz wkleić cały gigantyczny adres URL danych, jeśli chcesz).

Jeśli wszystko poszło dobrze, po lewej stronie ekranu Scratch pojawi się wpis dotyczący twojego numeru wewnętrznego. Jeśli coś nie poszło dobrze, otwórz konsolę JavaScript (shift-ctrl-J w Chrome) i spróbuj debugować problem.

Powyżej znajdziesz przykładowy kod, który pobiera i analizuje dane JSON ze stacji KNYC (w Nowym Jorku) amerykańskiej National Weather Service i wyświetla je, obracając duszka tak, jak wieje wiatr. Sposób, w jaki to zrobiłem, polegał na pobraniu danych do przeglądarki internetowej, a następnie obliczeniu tagów. Jeśli chcesz wypróbować inną stację pogodową, wprowadź pobliski kod pocztowy w polu wyszukiwania na weather.gov, a strona pogody dla Twojej lokalizacji powinna zawierać czteroliterowy kod stacji, którego możesz użyć zamiast KNYC w kod.

Możesz również umieścić swoje rozszerzenie w trybie piaskownicy bezpośrednio w adresie URL dla modu SheepTester, dodając argument „?url=”. Na przykład:

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

Krok 5: Pisanie rozszerzenia poza piaskownicą: wprowadzenie

Konstruktor rozszerzenia spoza piaskownicy otrzymuje obiekt Runtime. Możesz go zignorować lub użyć. Jednym z zastosowań obiektu Runtime jest użycie jego właściwości currentMSecs do synchronizowania zdarzeń („bloków kapelusza”). O ile wiem, wszystkie kody operacji bloku zdarzeń są regularnie odpytywane, a każda runda odpytywania ma jedną wartość currentMSecs. Jeśli potrzebujesz obiektu Runtime, prawdopodobnie rozpoczniesz swoje rozszerzenie od:

klasa ROZSZERZENIE KLASA {

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

Wszystkie standardowe obiekty okna mogą być używane w rozszerzeniu bez piaskownicy. Na koniec, twoje rozszerzenie poza piaskownicą powinno kończyć się tym kawałkiem magicznego kodu:

(funkcja() {

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

gdzie należy zastąpić EXTENSIONCLASS klasą swojego rozszerzenia.

Krok 6: Pisanie rozszerzenia poza piaskownicą: prosty gamepad

Zróbmy teraz proste rozszerzenie gamepada, które zapewnia blok pojedynczego zdarzenia („czapki”), gdy przycisk zostanie naciśnięty lub zwolniony.

Podczas każdego cyklu odpytywania bloku zdarzeń zapiszemy znacznik czasu z obiektu runtime oraz poprzedni i bieżący stan gamepada. Znacznik czasu służy do rozpoznania, czy mamy nowy cykl odpytywania. Tak więc zaczynamy od:

class ScratchSimpleGamepad {

konstruktor(runtime) { this.runtime = runtime this.currentMSecs = -1 this.previousButtons = this.currentButtons = } … } Będziemy mieli jeden blok zdarzeń z dwoma wejściami – numer przycisku i menu do wyboru, czy zdarzenie ma być wyzwalane po naciśnięciu czy zwolnieniu. Oto nasza metoda

zdobyć informacje() {

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": [{tekst:"naciśnij", wartość:1}, {tekst:"zwolnij", wartość:0}], } }; } Myślę, że wartości w menu rozwijanym nadal są przekazywane do funkcji opcode jako ciągi, mimo że są zadeklarowane jako liczby. Więc wyraźnie porównaj je z wartościami określonymi w menu w razie potrzeby. Teraz piszemy metodę, która aktualizuje stan przycisku za każdym razem, gdy nastąpi nowy cykl odpytywania zdarzeń

aktualizacja() {

if (this.runtime.currentMSecs == this.currentMSecs) return // nie nowy cykl odpytywania 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) { // inna liczba przycisków, więc nowy 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.wciśnięty) } Na koniec możemy zaimplementować nasz blok zdarzeń, wywołując metodę update(), a następnie sprawdzając, czy wymagany przycisk został właśnie naciśnięty lub zwolniony, porównując bieżący i poprzedni stan przycisku

buttonPressedReleased({b, eventType}) {

this.update() if (b < this.currentButtons.length) { if (eventType == 1) { // uwaga: będzie to ciąg, więc lepiej porównać go z 1 niż traktować jako wartość logiczną if (this.currentButtons && ! this.previousButtons) { return true } } else { if (!this.currentButtons && this.previousButtons) { return true } } } return false } I na koniec dodajemy nasz magiczny kod rejestracyjny rozszerzenia po zdefiniowaniu klasy

(funkcja() {

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

Pełny kod możesz pobrać tutaj.

Krok 7: Korzystanie z rozszerzenia poza piaskownicą

Korzystanie z rozszerzenia poza piaskownicą
Korzystanie z rozszerzenia poza piaskownicą

Ponownie umieść gdzieś swoje rozszerzenie i tym razem załaduj je za pomocą argumentu load_plugin= zamiast url= do modu SheepTester's Scratch. Na przykład, dla mojego prostego modu Gamepad, przejdź do:

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

(Nawiasem mówiąc, jeśli chcesz bardziej wyrafinowanego gamepada, po prostu usuń „prosty” z powyższego adresu URL, a będziesz miał obsługę dudnienia i osi analogowych.)

Ponownie rozszerzenie powinno pojawić się po lewej stronie edytora Scratch. Powyżej znajduje się bardzo prosty program Scratch, który mówi „cześć” po naciśnięciu przycisku 0 i „do widzenia”, gdy go puścisz.

Krok 8: Podwójna kompatybilność i szybkość

Zauważyłem, że bloki rozszerzeń działają o rząd wielkości szybciej, korzystając z metody ładowania, której użyłem dla rozszerzeń poza piaskownicą. Jeśli więc nie zależy Ci na korzyściach związanych z bezpieczeństwem pracy w piaskownicy Web Worker, Twój kod skorzysta na załadowaniu z argumentem ?load_plugin=URL do modu SheepTestera.

Możesz uczynić rozszerzenie piaskownicy zgodne z obiema metodami ładowania, używając następującego kodu po zdefiniowaniu klasy rozszerzenia (zmień CLASSNAME na nazwę swojej klasy rozszerzenia):

(funkcja() {

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

Zalecana: