Índice:

Extensões Scratch 3.0: 8 etapas
Extensões Scratch 3.0: 8 etapas

Vídeo: Extensões Scratch 3.0: 8 etapas

Vídeo: Extensões Scratch 3.0: 8 etapas
Vídeo: Scratch para iniciantes! Aprenda a programação básica agora! 2024, Julho
Anonim
Extensões Scratch 3.0
Extensões Scratch 3.0

As extensões do Scratch são pedaços de código Javascript que adicionam novos blocos ao Scratch. Enquanto Scratch vem com um monte de extensões oficiais, não existe um mecanismo oficial para adicionar extensões feitas pelo usuário.

Quando eu estava fazendo minha extensão de controle do Minecraft para Scratch 3.0, achei difícil começar. Este Instructable reúne informações de várias fontes (especialmente esta), além de algumas coisas que eu mesmo descobri.

Você precisa saber como programar em Javascript e como hospedar seu Javascript em um site. Para este último, recomendo o GitHub Pages.

O truque principal é usar o mod do Scratch do SheepTester, que permite carregar extensões e plug-ins.

Este Instructable irá guiá-lo por fazer duas extensões:

  • Buscar: carregar dados de um URL e extrair tags JSON, por exemplo, para carregar dados meteorológicos
  • SimpleGamepad: usando um controlador de jogo no Scratch (uma versão mais sofisticada está aqui).

Etapa 1: dois tipos de extensões

Existem dois tipos de extensões que chamarei de "não sandboxed" e "sandboxed". As extensões em sandbox são executadas como Web Workers e, como resultado, têm limitações significativas:

  • Os Web Workers não podem acessar os globais no objeto janela (em vez disso, eles têm um objeto self global, que é muito mais limitado), portanto, você não pode usá-los para coisas como acesso ao gamepad.
  • As extensões em área restrita não têm acesso ao objeto de tempo de execução Scratch.
  • As extensões em área restrita são muito mais lentas.
  • As mensagens de erro do console Javascript para extensões em sandbox são mais enigmáticas no Chrome.

Por outro lado:

  • Usar extensões de área restrita de outras pessoas é mais seguro.
  • As extensões em área restrita têm mais probabilidade de funcionar com qualquer suporte oficial de carregamento de extensão.
  • As extensões em área restrita podem ser testadas sem fazer upload para um servidor da web, codificando em um data: // URL.

As extensões oficiais (como Música, Caneta, etc.) estão todas fora da sandbox. O construtor da extensão obtém o objeto de tempo de execução do Scratch e a janela fica totalmente acessível.

A extensão Fetch está em uma área restrita, mas o Gamepad precisa do objeto navegador da janela.

Etapa 2: escrever uma extensão em sandbox: parte I

Para fazer uma extensão, você cria uma classe que codifica as informações sobre ela e, em seguida, adiciona um pouco de código para registrar a extensão.

O principal na classe de extensão é um método getInfo () que retorna um objeto com os campos obrigatórios:

  • id: o nome interno da extensão, deve ser único para cada extensão
  • nome: o nome amigável da extensão, aparecendo na lista de blocos do Scratch
  • blocos: uma lista de objetos que descrevem o novo bloco personalizado.

E há um campo de menus opcional que não é usado no Fetch, mas será usado no Gamepad.

Então, aqui está o modelo básico para Fetch:

class ScratchFetch {

constructor () {} getInfo () {return {"id": "Fetch", "name": "Fetch", "blocks": [/* add later * /]}} / * add methods for blocks * /} Scratch.extensions.register (new ScratchFetch ())

Etapa 3: escrever uma extensão em sandbox: parte II

Agora, precisamos criar a lista de blocos no objeto getInfo (). Cada bloco precisa de pelo menos estes quatro campos:

  • opcode: este é o nome do método que é chamado para fazer o trabalho do bloco
  • blockType: este é o tipo de bloco; os mais comuns para extensões são:

    • "command": faz algo, mas não retorna um valor
    • "repórter": retorna uma string ou número
    • "Boolean": retorna um booleano (observe a capitalização)
    • "hat": bloco de captura de evento; se o seu código Scratch usa este bloco, o tempo de execução Scratch regularmente pesquisa o método associado que retorna um booleano para dizer se o evento aconteceu
  • texto: esta é uma descrição amigável do bloco, com os argumentos entre colchetes, por exemplo, "buscar dados de "
  • argumentos: este é um objeto que possui um campo para cada argumento (por exemplo, "url" no exemplo acima); este objeto, por sua vez, possui estes campos:

    • tipo: "string" ou "número"
    • defaultValue: o valor padrão a ser pré-preenchido.

Por exemplo, aqui está o campo de blocos na minha extensão Fetch:

"blocos": [{"opcode": "fetchURL", "blockType": "reporter", "text": "buscar dados de ", "arguments": {"url": {"type": "string", "defaultValue ":" https://api.weather.gov/stations/KNYC/observations "},}}, {" opcode ":" jsonExtract "," blockType ":" reporter "," text ":" extrair [nome] from [data] "," arguments ": {" name ": {" type ":" string "," defaultValue ":" temperature "}," data ": {" type ":" string "," defaultValue ": '{"temperatura": 12,3}'},}},]

Aqui, definimos dois blocos: fetchURL e jsonExtract. Ambos são repórteres. O primeiro extrai dados de um URL e os retorna, e o segundo extrai um campo de dados JSON.

Finalmente, você precisa incluir os métodos para dois blocos. Cada método recebe um objeto como argumento, com o objeto incluindo campos para todos os argumentos. Você pode decodificá-los usando chaves nos argumentos. Por exemplo, aqui está um exemplo síncrono:

jsonExtract ({name, data}) {

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

O código extrai o campo de nome dos dados JSON. Se o campo contiver uma string, número ou booleano, retornamos isso. Caso contrário, re-JSONify o campo. E retornamos uma string vazia se o nome estiver faltando no JSON.

Às vezes, entretanto, você pode querer fazer um bloco que use uma API assíncrona. O método fetchURL () usa a API fetch, que é assíncrona. Nesse caso, você deve retornar uma promessa de seu método que faz o trabalho. Por exemplo:

fetchURL ({url}) {

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

É isso. A extensão completa está aqui.

Etapa 4: usando uma extensão em sandbox

Usando uma extensão em sandbox
Usando uma extensão em sandbox
Usando uma extensão em sandbox
Usando uma extensão em sandbox
Usando uma extensão em sandbox
Usando uma extensão em sandbox

Existem duas maneiras de usar a extensão em área restrita. Primeiro, você pode carregá-lo em um servidor web e, em seguida, carregá-lo no mod Scratch do SheepTester. Em segundo lugar, você pode codificá-lo em uma URL de dados e carregá-lo no mod Scratch. Na verdade, eu uso o segundo método um pouco para teste, pois evita preocupações com versões mais antigas da extensão sendo armazenadas em cache pelo servidor. Observe que, embora você possa hospedar javascript de páginas do Github, você não pode fazer isso diretamente de um repositório github comum.

Meu fetch.js está hospedado em https://arpruss.github.io/fetch.js. Ou você pode converter sua extensão em um URL de dados enviando-o aqui e copiando-o para a área de transferência. Um URL de dados é um URL gigante que contém um arquivo inteiro.

Vá para o mod Scratch do SheepTester. Clique no botão Adicionar extensão no canto inferior esquerdo. Em seguida, clique em "Escolha uma extensão" e insira seu URL (você pode colar todo o URL de dados gigante, se desejar).

Se tudo correr bem, você terá uma entrada para o seu ramal no lado esquerdo da tela Scratch. Se as coisas não correram bem, você deve abrir seu console Javascript (shift-ctrl-J no Chrome) e tentar depurar o problema.

Acima você encontrará algum código de exemplo que busca e analisa dados JSON da estação KNYC (em Nova York) do Serviço Nacional de Meteorologia dos EUA e os exibe, enquanto gira o sprite para que fique da mesma maneira que o vento está soprando. A maneira como fiz isso foi buscar os dados em um navegador da web e, em seguida, descobrir as tags. Se você quiser tentar uma estação meteorológica diferente, insira um código postal próximo na caixa de pesquisa em weather.gov, e a página meteorológica de sua localização deverá fornecer um código de estação de quatro letras, que você pode usar no lugar de KNYC no código.

Você também pode incluir sua extensão em área restrita diretamente no URL do mod do SheepTester, adicionando um argumento "? Url =". Por exemplo:

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

Etapa 5: escrever uma extensão sem sandbox: introdução

O construtor de uma extensão sem sandbox recebe um objeto Runtime. Você pode ignorá-lo ou usá-lo. Um uso do objeto Runtime é usar sua propriedade currentMSecs para sincronizar eventos ("hat blocks"). Pelo que eu posso dizer, todos os opcodes do bloco de eventos são pesquisados regularmente e cada rodada da pesquisa tem um único valor currentMSecs. Se você precisar do objeto Runtime, provavelmente iniciará sua extensão com:

classe EXTENSIONCLASS {

construtor (tempo de execução) {this.runtime = tempo de execução…}…}

Todos os objetos de janela padrão podem ser usados na extensão sem sandbox. Finalmente, sua extensão fora da sandbox deve terminar com este código mágico:

(função () {

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

onde você deve substituir EXTENSIONCLASS pela classe de sua extensão.

Etapa 6: Escrever uma extensão sem sandbox: Gamepad simples

Vamos agora fazer uma extensão de gamepad simples que fornece um único bloco de evento ("chapéu") para quando um botão é pressionado ou solto.

Durante cada ciclo de votação do bloco de eventos, salvaremos um carimbo de data / hora do objeto de tempo de execução e os estados do gamepad anterior e atual. O carimbo de data / hora é usado para reconhecer se temos um novo ciclo de votação. Então, começamos com:

class ScratchSimpleGamepad {

construtor (tempo de execução) {this.runtime = tempo de execução this.currentMSecs = -1 this.previousButtons = this.currentButtons = }…} Teremos um bloco de eventos, com duas entradas - um número de botão e um menu para selecionar se queremos que o evento seja disparado ao pressionar ou soltar. Então, aqui está o nosso método

obter informação() {

return {"id": "SimpleGamepad", "name": "SimpleGamepad", "blocks": [{"opcode": "buttonPressedReleased", "blockType": "hat", "text": "botão [eventType] "," arguments ": {" b ": {" type ":" number "," defaultValue ":" 0 "}," eventType ": {" type ":" number "," defaultValue ":" 1 "," menu ":" pressReleaseMenu "},},},]," menus ": {" pressReleaseMenu ": [{text:" press ", valor: 1}, {text:" release ", valor: 0}],}}; } Acho que os valores no menu suspenso ainda são passados para a função opcode como strings, apesar de serem declarados como números. Portanto, compare-os explicitamente com os valores especificados no menu, conforme necessário. Agora escrevemos um método que atualiza os estados do botão sempre que um novo ciclo de votação de evento acontece

atualizar() {

if (this.runtime.currentMSecs == this.currentMSecs) retorna // não é um novo ciclo de votação 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) { // número diferente de botões, então novo gamepad this.previousButtons = for (var i = 0; i <gamepad.buttons.length; i ++) this.previousButtons.push (false)} else {this.previousButtons = this. currentButtons} this.currentButtons = para (var i = 0; i <gamepad.buttons.length; i ++) this.currentButtons.push (gamepad.buttons .pressed)} Finalmente, podemos implementar nosso bloco de eventos, chamando o método update () e, em seguida, verificando se o botão necessário acabou de ser pressionado ou liberado, comparando os estados atual e anterior do botão

buttonPressedReleased ({b, eventType}) {

this.update () if (b <this.currentButtons.length) {if (eventType == 1) {// observação: será uma string, então é melhor compará-la com 1 do que tratá-la como um booleano if (this.currentButtons &&! this.previousButtons ) {return true}} else {if (! this.currentButtons && this.previousButtons ) {return true}}} return false} E, finalmente, adicionamos nosso código de registro de extensão mágica após definir a classe

(função () {

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

Você pode obter o código completo aqui.

Etapa 7: usar uma extensão sem sandbox

Usando uma extensão sem sandbox
Usando uma extensão sem sandbox

Mais uma vez, hospede sua extensão em algum lugar e, desta vez, carregue-a com o argumento load_plugin = em vez de url = para o mod Scratch do SheepTester. Por exemplo, para o meu mod Gamepad simples, vá para:

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

(A propósito, se você quiser um gamepad mais sofisticado, apenas remova "simples" do URL acima, e você terá suporte para rumble e eixo analógico.)

Novamente, a extensão deve aparecer no lado esquerdo do seu editor Scratch. Acima está um programa Scratch muito simples que diz "olá" quando você pressiona o botão 0 e "adeus" quando você o solta.

Etapa 8: compatibilidade dupla e velocidade

Percebi que os blocos de extensão executam uma ordem de magnitude mais rápido usando o método de carregamento que usei para extensões fora da sandbox. Portanto, a menos que você se preocupe com os benefícios de segurança da execução em uma caixa de proteção do Web Worker, seu código se beneficiará ao ser carregado com o argumento? Load_plugin = URL para o mod do SheepTester.

Você pode tornar uma extensão em sandbox compatível com ambos os métodos de carregamento usando o seguinte código após definir a classe de extensão (altere CLASSNAME para o nome de sua classe de extensão):

(função () {

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: