Tabla de contenido:

Extensiones Scratch 3.0: 8 pasos
Extensiones Scratch 3.0: 8 pasos

Video: Extensiones Scratch 3.0: 8 pasos

Video: Extensiones Scratch 3.0: 8 pasos
Video: Agregar extensiones en Scratch 3 2024, Mes de julio
Anonim
Extensiones Scratch 3.0
Extensiones Scratch 3.0

Las extensiones de Scratch son fragmentos de código Javascript que agregan nuevos bloques a Scratch. Si bien Scratch se incluye con un montón de extensiones oficiales, no existe un mecanismo oficial para agregar extensiones creadas por el usuario.

Cuando estaba haciendo mi extensión de control de Minecraft para Scratch 3.0, me resultó difícil comenzar. Este Instructable recopila información de varias fuentes (especialmente esta), además de algunas cosas que descubrí yo mismo.

Necesita saber cómo programar en Javascript y cómo alojar su Javascript en un sitio web. Para este último, recomiendo GitHub Pages.

El truco principal es usar el mod de Scratch de SheepTester que te permite cargar extensiones y complementos.

Este Instructable lo guiará a través de la creación de dos extensiones:

  • Obtener: cargar datos de una URL y extraer etiquetas JSON, por ejemplo, para cargar datos meteorológicos
  • SimpleGamepad: usando un controlador de juego en Scratch (una versión más sofisticada está aquí).

Paso 1: dos tipos de extensiones

Hay dos tipos de extensiones a las que llamaré "sin zona de pruebas" y "con zona de pruebas". Las extensiones de espacio aislado se ejecutan como Web Workers y, como resultado, tienen limitaciones importantes:

  • Los Web Workers no pueden acceder a los globales en el objeto de la ventana (en su lugar, tienen un objeto self global, que es mucho más limitado), por lo que no puede usarlos para cosas como el acceso al gamepad.
  • Las extensiones de espacio aislado no tienen acceso al objeto de tiempo de ejecución de Scratch.
  • Las extensiones de espacio aislado son mucho más lentas.
  • Los mensajes de error de la consola de JavaScript para las extensiones de espacio aislado son más crípticos en Chrome.

Por otra parte:

  • El uso de las extensiones de espacio aislado de otras personas es más seguro.
  • Es más probable que las extensiones de espacio aislado funcionen con cualquier soporte de carga de extensión oficial eventual.
  • Las extensiones de espacio aislado se pueden probar sin cargarlas en un servidor web mediante la codificación en una URL de datos: //.

Las extensiones oficiales (como Music, Pen, etc.) no están incluidas en la zona de pruebas. El constructor de la extensión obtiene el objeto en tiempo de ejecución de Scratch y la ventana es completamente accesible.

La extensión Fetch está en un espacio aislado, pero el Gamepad necesita el objeto del navegador de la ventana.

Paso 2: escribir una extensión de espacio aislado: parte I

Para hacer una extensión, crea una clase que codifica información sobre ella y luego agrega un poco de código para registrar la extensión.

Lo principal en la clase de extensión es un método getInfo () que devuelve un objeto con los campos requeridos:

  • id: el nombre interno de la extensión, debe ser único para cada extensión
  • nombre: el nombre descriptivo de la extensión, que aparece en la lista de bloques de Scratch
  • bloques: una lista de objetos que describen el nuevo bloque personalizado.

Y hay un campo de menús opcional que no se usa en Fetch pero se usará en Gamepad.

Entonces, aquí está la plantilla básica para Fetch:

class ScratchFetch {

constructor () {} getInfo () {return {"id": "Obtener", "nombre": "Obtener", "bloques": [/* agregar más tarde * /]}} / * agregar métodos para bloques * /} Scratch.extensions.register (nuevo ScratchFetch ())

Paso 3: escribir una extensión de espacio aislado: parte II

Ahora, necesitamos crear la lista de bloques en el objeto getInfo (). Cada bloque necesita al menos estos cuatro campos:

  • opcode: este es el nombre del método que se llama para hacer el trabajo del bloque
  • blockType: este es el tipo de bloque; los más comunes para las extensiones son:

    • "comando": hace algo pero no devuelve un valor
    • "reportero": devuelve una cadena o un número
    • "Booleano": devuelve un valor booleano (tenga en cuenta las mayúsculas)
    • "sombrero": bloque de captura de eventos; Si su código Scratch usa este bloque, el tiempo de ejecución de Scratch sondea regularmente el método asociado que devuelve un booleano para decir si el evento ha ocurrido.
  • texto: esta es una descripción amigable del bloque, con los argumentos entre paréntesis, por ejemplo, "recuperar datos de "
  • argumentos: este es un objeto que tiene un campo para cada argumento (por ejemplo, "url" en el ejemplo anterior); este objeto a su vez tiene estos campos:

    • tipo: "cadena" o "número"
    • defaultValue: el valor predeterminado que se completará previamente.

Por ejemplo, aquí está el campo de bloques en mi extensión Fetch:

"bloques": [{"opcode": "fetchURL", "blockType": "reporter", "text": "obtener datos de ", "argumentos": {"url": {"type": "string", "defaultValue ":" https://api.weather.gov/stations/KNYC/observations "},}}, {" opcode ":" jsonExtract "," blockType ":" reporter "," text ":" extract [nombre] from [datos] "," argumentos ": {" nombre ": {" tipo ":" cadena "," valor predeterminado ":" temperatura "}," datos ": {" tipo ":" cadena "," valor predeterminado ": '{"temperatura": 12.3}'},}},]

Aquí, definimos dos bloques: fetchURL y jsonExtract. Ambos son reporteros. El primero extrae datos de una URL y los devuelve, y el segundo extrae un campo de los datos JSON.

Finalmente, debe incluir los métodos para dos bloques. Cada método toma un objeto como argumento, y el objeto incluye campos para todos los argumentos. Puede decodificarlos usando llaves en los argumentos. Por ejemplo, aquí hay un ejemplo sincrónico:

jsonExtract ({nombre, datos}) {

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

El código extrae el campo de nombre de los datos JSON. Si el campo contiene una cadena, un número o un valor booleano, lo devolvemos. De lo contrario, re-JSONificamos el campo. Y devolvemos una cadena vacía si falta el nombre en el JSON.

A veces, sin embargo, es posible que desee crear un bloque que utilice una API asincrónica. El método fetchURL () utiliza la API de recuperación que es asíncrona. En tal caso, debe devolver una promesa de su método que hace el trabajo. Por ejemplo:

fetchURL ({url}) {

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

Eso es todo. La extensión completa está aquí.

Paso 4: uso de una extensión de espacio aislado

Usar una extensión de espacio aislado
Usar una extensión de espacio aislado
Usar una extensión de espacio aislado
Usar una extensión de espacio aislado
Usar una extensión de espacio aislado
Usar una extensión de espacio aislado

Hay dos formas de utilizar la extensión de espacio aislado. Primero, puede cargarlo en un servidor web y luego cargarlo en el mod Scratch de SheepTester. En segundo lugar, puede codificarlo en una URL de datos y cargarlo en el mod Scratch. De hecho, utilizo bastante el segundo método para realizar pruebas, ya que evita preocupaciones sobre las versiones anteriores de la extensión que el servidor almacena en caché. Tenga en cuenta que, si bien puede alojar javascript desde las páginas de Github, no puede hacerlo directamente desde un repositorio de github normal.

Mi fetch.js está alojado en https://arpruss.github.io/fetch.js. O puede convertir su extensión a una URL de datos cargándola aquí y luego copiarla al portapapeles. Una URL de datos es una URL gigante que contiene un archivo completo.

Vaya al mod Scratch de SheepTester. Haga clic en el botón Agregar extensión en la esquina inferior izquierda. Luego haga clic en "Elegir una extensión" e ingrese su URL (puede pegar toda la URL de datos gigantes si lo desea).

Si todo salió bien, tendrá una entrada para su extensión en el lado izquierdo de la pantalla de Scratch. Si las cosas no salieron bien, debe abrir su consola Javascript (shift-ctrl-J en Chrome) e intentar depurar el problema.

Arriba encontrará un código de ejemplo que obtiene y analiza datos JSON de la estación KNYC (en Nueva York) del Servicio Meteorológico Nacional de EE. UU., Y lo muestra, mientras gira el objeto para que mire de la misma manera en que sopla el viento. La forma en que lo hice fue obteniendo los datos en un navegador web y luego descubriendo las etiquetas. Si desea probar una estación meteorológica diferente, ingrese un código postal cercano en el cuadro de búsqueda en weather.gov, y la página del clima para su ubicación debe darle un código de estación de cuatro letras, que puede usar en lugar de KNYC en el código.

También puede incluir su extensión de espacio aislado en la URL del mod de SheepTester agregando un argumento "? Url =". Por ejemplo:

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

Paso 5: escribir una extensión sin caja de pruebas: introducción

Al constructor de una extensión no incluida en la zona de pruebas se le pasa un objeto en tiempo de ejecución. Puede ignorarlo o usarlo. Un uso del objeto Runtime es usar su propiedad currentMSecs para sincronizar eventos ("bloques de sombrero"). Por lo que puedo decir, todos los códigos de operación del bloque de eventos se sondean con regularidad, y cada ronda del sondeo tiene un único valor currentMSecs. Si necesita el objeto Runtime, probablemente comenzará su extensión con:

clase EXTENSIONCLASS {

constructor (tiempo de ejecución) {this.runtime = tiempo de ejecución…}…}

Todos los objetos de ventana estándar se pueden usar en la extensión no incluida en la zona de pruebas. Finalmente, su extensión no incluida en la zona de pruebas debería terminar con este código mágico:

(función () {

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

donde debe reemplazar EXTENSIONCLASS con la clase de su extensión.

Paso 6: escribir una extensión sin caja de pruebas: Gamepad simple

Hagamos ahora una extensión simple de control de juegos que proporcione un bloque de evento único ("sombrero") para cuando se presiona o suelta un botón.

Durante cada ciclo de sondeo de bloques de eventos, guardaremos una marca de tiempo del objeto de tiempo de ejecución y los estados anterior y actual del gamepad. La marca de tiempo se usa para reconocer si tenemos un nuevo ciclo de sondeo. Entonces, comenzamos con:

class ScratchSimpleGamepad {

constructor (tiempo de ejecución) {this.runtime = tiempo de ejecución this.currentMSecs = -1 this.previousButtons = this.currentButtons = }…} Tendremos un bloque de eventos, con dos entradas: un número de botón y un menú para seleccionar si queremos que el evento se dispare al presionar o soltar. Entonces, aquí está nuestro método

obtener información() {

return {"id": "SimpleGamepad", "name": "SimpleGamepad", "blocks": [{"opcode": "buttonPressedReleased", "blockType": "hat", "text": "button [eventType] "," argumentos ": {" b ": {" type ":" número "," defaultValue ":" 0 "}," eventType ": {" type ":" número "," defaultValue ":" 1 "," menu ":" pressReleaseMenu "},},},]," menus ": {" pressReleaseMenu ": [{text:" press ", valor: 1}, {text:" release ", valor: 0}],}}; } Creo que los valores en el menú desplegable aún se pasan a la función de código de operación como cadenas, a pesar de estar declarados como números. Por lo tanto, compárelos explícitamente con los valores especificados en el menú según sea necesario. Ahora escribimos un método que actualiza los estados de los botones cada vez que ocurre un nuevo ciclo de sondeo de eventos.

actualizar() {

if (this.runtime.currentMSecs == this.currentMSecs) return // no es un nuevo ciclo de sondeo 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) { // diferente número de botones, así que nuevo 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)} Finalmente, podemos implementar nuestro bloque de eventos, llamando al método update () y luego verificando si el botón requerido se acaba de presionar o soltar, comparando los estados del botón actual y anterior.

buttonPressedReleased ({b, eventType}) {

this.update () if (b <this.currentButtons.length) {if (eventType == 1) {// nota: esto será una cadena, así que es mejor compararlo con 1 que tratarlo como un booleano if (this.currentButtons &&! this.previousButtons ) {return true}} else {if (! this.currentButtons && this.previousButtons ) {return true}}} return false} Y finalmente agregamos nuestro código de registro de extensión mágico después de definir la clase

(función () {

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

Puede obtener el código completo aquí.

Paso 7: uso de una extensión sin zona de pruebas

Uso de una extensión sin zona de pruebas
Uso de una extensión sin zona de pruebas

Una vez más, aloje su extensión en algún lugar, y esta vez cárguela con el argumento load_plugin = en lugar de url = al mod Scratch de SheepTester. Por ejemplo, para mi mod simple de Gamepad, ve a:

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

(Por cierto, si desea un gamepad más sofisticado, simplemente elimine "simple" de la URL anterior y tendrá soporte para rumble y eje analógico).

Nuevamente, la extensión debería aparecer en el lado izquierdo de su editor de Scratch. Arriba hay un programa Scratch muy simple que dice "hola" cuando presiona el botón 0 y "adiós" cuando lo suelta.

Paso 8: doble compatibilidad y velocidad

He notado que los bloques de extensión se ejecutan en un orden de magnitud más rápido usando el método de carga que usé para las extensiones no incluidas en la zona de pruebas. Entonces, a menos que le preocupen los beneficios de seguridad de ejecutar en un entorno de pruebas de Web Worker, su código se beneficiará de que se cargue con el argumento? Load_plugin = URL en el mod de SheepTester.

Puede hacer que una extensión de espacio aislado sea compatible con ambos métodos de carga utilizando el siguiente código después de definir la clase de extensión (cambie CLASSNAME por el nombre de su clase de extensión):

(función () {

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

Recomendado: