Table des matières:

Extensions Scratch 3.0 : 8 étapes
Extensions Scratch 3.0 : 8 étapes

Vidéo: Extensions Scratch 3.0 : 8 étapes

Vidéo: Extensions Scratch 3.0 : 8 étapes
Vidéo: Scratch Tutorial | How to make a AI Assistant in Scratch 3.0 | Scratch Extensions | Scratch 3.0 2024, Novembre
Anonim
Extensions Scratch 3.0
Extensions Scratch 3.0

Les extensions Scratch sont des morceaux de code Javascript qui ajoutent de nouveaux blocs à Scratch. Bien que Scratch soit fourni avec un tas d'extensions officielles, il n'y a pas de mécanisme officiel pour ajouter des extensions créées par l'utilisateur.

Lorsque je créais mon extension de contrôle Minecraft pour Scratch 3.0, j'ai eu du mal à démarrer. Ce Instructable rassemble des informations provenant de diverses sources (en particulier cela), ainsi que quelques choses que j'ai découvertes moi-même.

Il faut savoir programmer en Javascript et héberger son Javascript sur un site internet. Pour ce dernier, je recommande GitHub Pages.

L'astuce principale est d'utiliser le mod de Scratch de SheepTester qui vous permet de charger des extensions et des plugins.

Ce Instructable vous guidera à travers la fabrication de deux extensions:

  • Fetch: chargement de données depuis une URL et extraction de balises JSON, par exemple pour charger des données météo
  • SimpleGamepad: utilisation d'un contrôleur de jeu dans Scratch (une version plus sophistiquée est ici).

Étape 1: Deux types d'extensions

Il existe deux types d'extensions que j'appellerai "sans bac à sable" et "avec bac à sable". Les extensions en bac à sable s'exécutent en tant que Web Workers et présentent par conséquent des limitations importantes:

  • Les Web Workers ne peuvent pas accéder aux globals dans l'objet window (au lieu de cela, ils ont un objet self global, qui est beaucoup plus limité), vous ne pouvez donc pas les utiliser pour des choses comme l'accès à la manette de jeu.
  • Les extensions en bac à sable n'ont pas accès à l'objet d'exécution Scratch.
  • Les extensions en bac à sable sont beaucoup plus lentes.
  • Les messages d'erreur de la console Javascript pour les extensions en bac à sable sont plus cryptiques dans Chrome.

D'autre part:

  • L'utilisation des extensions sandbox d'autres personnes est plus sûre.
  • Les extensions en bac à sable sont plus susceptibles de fonctionner avec toute prise en charge officielle du chargement d'extensions.
  • Les extensions en bac à sable peuvent être testées sans téléchargement sur un serveur Web en les encodant dans une URL data://.

Les extensions officielles (telles que Music, Pen, etc.) sont toutes non sandbox. Le constructeur de l'extension obtient l'objet d'exécution à partir de Scratch et la fenêtre est entièrement accessible.

L'extension Fetch est en bac à sable, mais la manette de jeu a besoin de l'objet navigateur de la fenêtre.

Étape 2: Écriture d'une extension en bac à sable: Partie I

Pour créer une extension, vous créez une classe qui encode les informations à son sujet, puis ajoutez un peu de code pour enregistrer l'extension.

La chose principale dans la classe d'extension est une méthode getInfo() qui renvoie un objet avec les champs requis:

  • id: le nom interne de l'extension, doit être unique pour chaque extension
  • name: le nom convivial de l'extension, apparaissant dans la liste des blocs de Scratch
  • blocs: une liste d'objets décrivant le nouveau bloc personnalisé.

Et il y a un champ de menus facultatif qui n'est pas utilisé dans Fetch mais sera utilisé dans Gamepad.

Voici donc le modèle de base pour Fetch:

classe ScratchFetch {

constructor() { } getInfo() { return { "id": "Fetch", "name": "Fetch", "blocks": [/* ajouter plus tard */] } } /* ajouter des méthodes pour les blocs */ } Scratch.extensions.register (nouveau ScratchFetch())

Étape 3: Écriture d'une extension en bac à sable: Partie II

Maintenant, nous devons créer la liste des blocs dans l'objet de getInfo(). Chaque bloc a besoin d'au moins ces quatre champs:

  • opcode: c'est le nom de la méthode qui est appelée pour faire le travail du bloc
  • blockType: c'est le type de bloc; les plus courantes pour les extensions sont:

    • "commande": fait quelque chose mais ne renvoie pas de valeur
    • "reporter": renvoie une chaîne ou un nombre
    • "Boolean": renvoie un booléen (notez la majuscule)
    • "hat": bloc de capture d'événement; si votre code Scratch utilise ce bloc, le runtime Scratch interroge régulièrement la méthode associée qui renvoie un booléen pour dire si l'événement s'est produit
  • text: il s'agit d'une description conviviale du bloc, avec les arguments entre parenthèses, par exemple "récupérer les données de "
  • arguments: il s'agit d'un objet ayant un champ pour chaque argument (par exemple, "url" dans l'exemple ci-dessus); cet objet à son tour a ces champs:

    • type: soit « chaîne » ou « nombre »
    • defaultValue: la valeur par défaut à pré-remplir.

Par exemple, voici le champ des blocs dans mon extension Fetch:

"blocs": [{ "opcode": "fetchURL", "blockType": "reporter", "text": "récupérer les données de ", "arguments": { "url": { "type": "string", "defaultValue ": "https://api.weather.gov/stations/KNYC/observations" }, } }, { "opcode": "jsonExtract", "blockType": "reporter", "text": "extract [nom] from [data]", "arguments": { "name": { "type": "string", "defaultValue": "temperature" }, "data": { "type": "string", "defaultValue": '{"température": 12.3}' }, } },]

Ici, nous avons défini deux blocs: fetchURL et jsonExtract. Tous deux sont reporters. Le premier extrait les données d'une URL et les renvoie, et le second extrait un champ des données JSON.

Enfin, vous devez inclure les méthodes pour deux blocs. Chaque méthode prend un objet comme argument, l'objet incluant des champs pour tous les arguments. Vous pouvez les décoder en utilisant des accolades dans les arguments. Par exemple, voici un exemple synchrone:

jsonExtract({nom, données}) {

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

Le code extrait le champ de nom des données JSON. Si le champ contient une chaîne, un nombre ou un booléen, nous le renvoyons. Sinon, nous re-JSONifierons le champ. Et nous retournons une chaîne vide si le nom est manquant dans le JSON.

Parfois, cependant, vous souhaiterez peut-être créer un bloc qui utilise une API asynchrone. La méthode fetchURL() utilise l'API fetch qui est asynchrone. Dans un tel cas, vous devez retourner une promesse de votre méthode qui fait le travail. Par exemple:

fetchURL({url}) {

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

C'est ça. L'extension complète est ici.

Étape 4: Utilisation d'une extension en bac à sable

Utiliser une extension en bac à sable
Utiliser une extension en bac à sable
Utiliser une extension en bac à sable
Utiliser une extension en bac à sable
Utiliser une extension en bac à sable
Utiliser une extension en bac à sable

Il existe deux façons d'utiliser l'extension en bac à sable. Tout d'abord, vous pouvez le télécharger sur un serveur Web, puis le charger dans le mod Scratch de SheepTester. Deuxièmement, vous pouvez l'encoder dans une URL de données et la charger dans le mod Scratch. En fait, j'utilise beaucoup la deuxième méthode pour les tests, car elle évite les soucis concernant les anciennes versions de l'extension mises en cache par le serveur. Notez que même si vous pouvez héberger du javascript à partir des pages Github, vous ne pouvez pas le faire directement à partir d'un référentiel github ordinaire.

Mon fetch.js est hébergé sur https://arpruss.github.io/fetch.js. Ou vous pouvez convertir votre extension en URL de données en la téléchargeant ici, puis en la copiant dans le presse-papiers. Une URL de données est une URL géante qui contient un fichier entier.

Accédez au module Scratch de SheepTester. Cliquez sur le bouton Ajouter une extension dans le coin inférieur gauche. Cliquez ensuite sur "Choisir une extension", et entrez votre URL (vous pouvez coller l'intégralité de l'URL des données géantes si vous le souhaitez).

Si tout s'est bien passé, vous aurez une entrée pour votre extension sur le côté gauche de votre écran Scratch. Si les choses ne se sont pas bien passées, vous devez ouvrir votre console Javascript (shift-ctrl-J dans Chrome) et essayer de déboguer le problème.

Ci-dessus, vous trouverez un exemple de code qui récupère et analyse les données JSON de la station KNYC (à New York) du US National Weather Service, et les affiche, tout en tournant le sprite pour faire face à la même direction que le vent souffle. La façon dont je l'ai fait était de récupérer les données dans un navigateur Web, puis de déterminer les balises. Si vous souhaitez essayer une autre station météo, entrez un code postal à proximité dans le champ de recherche de weather.gov, et la page météo de votre emplacement devrait vous donner un code de station à quatre lettres, que vous pouvez utiliser à la place de KNYC dans le code.

Vous pouvez également inclure votre extension sandbox directement dans l'URL du mod de SheepTester en ajoutant un argument "?url=". Par exemple:

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

Étape 5: Écriture d'une extension sans sandbox: Introduction

Le constructeur d'une extension non sandbox reçoit un objet Runtime. Vous pouvez l'ignorer ou l'utiliser. L'une des utilisations de l'objet Runtime consiste à utiliser sa propriété currentMSecs pour synchroniser les événements (« blocs de chapeau »). Pour autant que je sache, tous les opcodes des blocs d'événements sont interrogés régulièrement et chaque tour d'interrogation a une seule valeur currentMSecs. Si vous avez besoin de l'objet Runtime, vous démarrerez probablement votre extension avec:

classe EXTENSIONCLASS {

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

Toutes les choses d'objet fenêtre standard peuvent être utilisées dans l'extension unsandbox. Enfin, votre extension non sandbox devrait se terminer par ce bout de code magique:

(fonction() {

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

où vous devez remplacer EXTENSIONCLASS par la classe de votre extension.

Étape 6: Écriture d'une extension sans bac à sable: Manette de jeu simple

Créons maintenant une simple extension de manette de jeu qui fournit un seul bloc d'événement ("chapeau") lorsqu'un bouton est enfoncé ou relâché.

Au cours de chaque cycle d'interrogation de bloc d'événement, nous enregistrerons un horodatage de l'objet d'exécution et les états de la manette de jeu précédent et actuel. L'horodatage est utilisé pour reconnaître si nous avons un nouveau cycle d'interrogation. Alors, on commence par:

class ScratchSimpleGamepad {

constructeur(runtime) { this.runtime = runtime this.currentMSecs = -1 this.previousButtons = this.currentButtons = } … } Nous aurons un bloc d'événement, avec deux entrées - un numéro de bouton et un menu pour sélectionner si nous voulons que l'événement se déclenche à la pression ou au relâchement. Alors voici notre méthode

obtenirInfo() {

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}], } }; } Je pense que les valeurs du menu déroulant sont toujours transmises à la fonction opcode sous forme de chaînes, bien qu'elles soient déclarées sous forme de nombres. Comparez-les donc explicitement aux valeurs spécifiées dans le menu si nécessaire. Nous écrivons maintenant une méthode qui met à jour les états des boutons chaque fois qu'un nouveau cycle d'interrogation d'événement se produit

mettre à jour() {

if (this.runtime.currentMSecs == this.currentMSecs) return // pas un nouveau cycle d'interrogation 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) { // nombre différent de boutons, donc nouvelle manette this.previousButtons = pour (var i = 0; i < gamepad.buttons.length; i++) this.previousButtons.push(false) } else { this.previousButtons = this. currentButtons } this.currentButtons = pour (var i = 0; i < gamepad.buttons.length; i++) this.currentButtons.push(gamepad.buttons.pressed) } Enfin, nous pouvons implémenter notre bloc d'événements, en appelant la méthode update(), puis en vérifiant si le bouton requis vient d'être enfoncé ou relâché, en comparant les états du bouton actuel et précédent

boutonAppuyéLibéré({b, eventType}) {

this.update() if (b < this.currentButtons.length) { if (eventType == 1) { // note: ce sera une chaîne, donc mieux vaut la comparer à 1 que de la traiter comme un booléen if (this.currentButtons && ! this.previousButtons) { return true } } else { if (!this.currentButtons && this.previousButtons) { return true } } } return false } Et enfin, nous ajoutons notre code d'enregistrement d'extension magique après avoir défini la classe

(fonction() {

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

Vous pouvez obtenir le code complet ici.

Étape 7: Utilisation d'une extension sans sandbox

Utilisation d'une extension sans sandbox
Utilisation d'une extension sans sandbox

Encore une fois, hébergez votre extension quelque part, et cette fois chargez-la avec l'argument load_plugin= plutôt que url= dans le mod Scratch de SheepTester. Par exemple, pour mon mod Gamepad simple, allez sur:

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

(Au fait, si vous voulez une manette de jeu plus sophistiquée, supprimez simplement "simple" de l'URL ci-dessus, et vous aurez la prise en charge du grondement et de l'axe analogique.)

Encore une fois, l'extension devrait apparaître sur le côté gauche de votre éditeur Scratch. Ci-dessus se trouve un programme Scratch très simple qui dit "bonjour" lorsque vous appuyez sur le bouton 0 et "au revoir" lorsque vous le relâchez.

Étape 8: Double compatibilité et vitesse

J'ai remarqué que les blocs d'extension s'exécutent un ordre de grandeur plus rapidement en utilisant la méthode de chargement que j'ai utilisée pour les extensions sans sandbox. Donc, à moins que vous ne vous souciez des avantages de sécurité de l'exécution dans un bac à sable Web Worker, votre code bénéficiera du chargement avec l'argument ?load_plugin=URL du mod de SheepTester.

Vous pouvez rendre une extension en bac à sable compatible avec les deux méthodes de chargement en utilisant le code suivant après avoir défini la classe d'extension (remplacez CLASSNAME par le nom de votre classe d'extension):

(fonction() {

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

Conseillé: