本記事について
Turbowarpのカスタム拡張機能を作る方法の説明、その4です。
前記事までで、外部Javascriptファイルを読み込む処理、読み込むファイルをブロックで指定できるようにする、ところまでをやりました。
今回は、テスト用Javascriptのなかで『Scratch』操作をさせてみようと思います。
やりたいことの提示
- ブロックで入力したSTEP数だけ動かす
~あらかじめ設定されている向きの方向に動かす - 位置情報を言う(ふきだし)
~「〇〇と言う」と「〇〇と考える」をプルダウンで選択可能
~「〇〇秒の間だけ」の機能もつける
動画解説
カスタム拡張機能コード
拡張機能コンスタント
Extension.js
/**
* Turbowarpの『カスタム拡張機能』を使おう【4】
* 外部JSファイルにて、Scratch標準ブロックを再現させる
* 1)指定されたSTEP数だけ動かす
* 2)現在の位置を吹き出し表示する(言う、考える)
*/
((Scratch) => {
const ExtensionID = 'MYEXTENSION';
const ExtensionName = '独自拡張練習';
// 歯車画像URL
const GEAR_IMAGE_SVG_URI
= 'https://amami-harhid.github.io/turbowarpExtensions/assets/gear.svg';
// テスト用JSファイルの場所 URL
const TEST_URL
= 'http://127.0.0.1:5500/turbowarpExtensions/_04_extension';
拡張機能定義情報(カラー付き)
const MyExtensionInfo = {
id : ExtensionID,
name : ExtensionName,
color1 : '#000000', // 背景を黒に( 文字色は白固定なので背景を白にすると文字が読めない )
color2 : '#ffffff', // ブロックリストの円周の色( 白 )
color3 : '#0000ff', // ブロックの周囲の線の色( 青 )
color1, color2, color3 はオプション、省略可能。
blocks : [
{
opcode: 'loadJSFileSetting',
blockType: Scratch.BlockType.COMMAND,
text: '[IMG_GEAR]JSファイルを指定する[JSURL]',
arguments: {
IMG_GEAR: {
type: Scratch.ArgumentType.IMAGE, //タイプ
dataURI: GEAR_IMAGE_SVG_URI, //歯車画像のURI
},
JSURL: {
type: Scratch.ArgumentType.STRING,
defaultValue: `${TEST_URL}/sub.js`,
},
},
},
{
opcode: 'setup',
blockType: Scratch.BlockType.COMMAND,
text: '[IMG_GEAR]事前準備',
arguments: {
IMG_GEAR: {
type: Scratch.ArgumentType.IMAGE, //タイプ
dataURI: GEAR_IMAGE_SVG_URI, //歯車画像のURI
},
},
},
{
opcode : 'moveStep',
blockType : Scratch.BlockType.COMMAND,
text : '[GEAR_IMAGE] 動かす [STEPS]',
arguments: {
GEAR_IMAGE : {
type: Scratch.ArgumentType.IMAGE,
dataURI: GEAR_IMAGE_SVG_URI,
},
STEPS : {
type: Scratch.ArgumentType.NUMBER,
defaultValue: 10,
}
},
},
位置を知らせる(プルダウン付き)
{
opcode : 'sayPosition',
blockType : Scratch.BlockType.COMMAND,
text : '[GEAR_IMAGE] 位置を知らせる [TYPE]',
arguments: {
GEAR_IMAGE : {
type: Scratch.ArgumentType.IMAGE,
dataURI: GEAR_IMAGE_SVG_URI,
},
TYPE : {
type: Scratch.ArgumentType.STRING,
menu: 'FukidashiMenu', // menusのキー
defaultValue: 'say',
},
},
},
〇秒、位置を知らせる(プルダウン付き)
{
opcode : 'sayPositionForSec',
blockType : Scratch.BlockType.COMMAND,
text : '[GEAR_IMAGE] [SECS]秒、位置を知らせる [TYPE]',
arguments: {
GEAR_IMAGE : {
type: Scratch.ArgumentType.IMAGE,
dataURI: GEAR_IMAGE_SVG_URI,
},
SECS : {
type: Scratch.ArgumentType.NUMBER,
defaultValue: 2,
},
TYPE : {
type: Scratch.ArgumentType.STRING,
menu: 'FukidashiMenu', // menusのキー
defaultValue: 'say',
},
},
},
],
プルダウンメニューの定義
// メニューの定義
menus: {
FukidashiMenu: {
items: [
{'text':'話す','value':'say'},
{'text':'考える','value':'think'},
],
}
}
}
拡張機能のクラス
sayPositionForSecメソッドにはasyncをつけている。終わるまで待つ必要がある(await)ため。
class MyExtension {
getInfo() {
return MyExtensionInfo;
}
/**
* ロードするJSファイルのURLを設定する
* @param {*} args
* @param {*} util
*/
loadJSFileSetting( args, util ){
this.jsUrl = args.JSURL;
}
/**
* JSファイルをロードする
* @param {*} args
* @param {*} util
*/
async setup( args, util ){
console.log(this.jsUrl)
try{
const _t = new Date().getTime();
const sub = await import(`${this.jsUrl}?_t=${_t}`);
// 読み込むJSは export {TestJS} をしている前提。
this.testJS = new sub.TestJS();
}catch(e){
const message = '読み込みに失敗した、'
+'もしくはクラス定義が存在しないみたいです';
console.error( message, e );
alert(message);
}
}
moveStep( args, util ) {
console.log( 'moveStep STEPS=', args.STEPS );
// sub.js 内のメソッドを実行する
this.testJS.moveStep(args, util);
}
sayPosition( args, util ) {
console.log( 'sayPosition TYPE=', args.TYPE ); // say or think
// sub.js 内のメソッドを実行する
this.testJS.sayPosition(args, util);
}
async sayPositionForSec( args, util ) {
console.log( 'sayPosition TYPE=', args.TYPE ); // say or think
console.log( 'sayPosition SECS=', args.SECS); // second
// sub.js 内のメソッドを実行する
await this.testJS.sayPositionForSec(args, util);
}
}
Scratch.extensions.register(new MyExtension());
})(Scratch);
外部ファイル
角度(°) を radianに直す共通処理を書いてます
const MathUtil = class{
static degToRad (deg) {
return deg * Math.PI / 180;
}
}
クラスの定義( TestJS )
const TestJS = class{
向きの方向へ指定したSTEPS分、位置を移動させる処理
Scratch VM のコードを真似しています。
moveStep(args, util){
// 向きの方向へ STEPSの長さ分 位置を変える(移動させる)
const steps = Scratch.Cast.toNumber(args.STEPS);
const radians = MathUtil.degToRad(90 - util.target.direction);
const dx = steps * Math.cos(radians);
const dy = steps * Math.sin(radians);
util.target.setXY(util.target.x + dx, util.target.y + dy);
}
話す処理( 言う:say, 考える: think )
sayPosition(args, util){
const type = args.TYPE;
const position = {x:util.target.x, y:util.target.y};
const message = `x=(${position.x}), y=(${position.y})`;
util.runtime.emit('SAY', util.target, type, message);
}
〇秒間、話す処理( 言う:say, 考える: think )
〇秒間経過した後に ブランクで言い直しています。
async sayPositionForSec(args, util){
const type = args.TYPE;
const position = {x:util.target.x, y:util.target.y};
const message = `x=(${position.x}), y=(${position.y})`;
util.runtime.emit('SAY', util.target, type, message);
const secs = Scratch.Cast.toNumber(args.SECS);
await this._sleep(secs*1000); // 秒数だけ待つ(次の処理へ進ませない)
util.runtime.emit('SAY', util.target, type, ''); // ふきだしバブルを消す
}
〇ミリ秒停止させる処理 ( Promiseを使っています )
async _sleep(msec){
return new Promise(resolve => setTimeout(resolve, msec));
}
}
最後につける
export {TestJS};
続き
次の記事【5】へ続きます。