Scratch 3 Extension の作り方

## GamePad Extension を作る

先の投稿で、Scratch 3.0用の[MIDI Extension](https://qiita.com/UchiwaFuujinn/items/cf455acf2aecd6e024a9)の使い方を紹介しました。このページでは、Web APIを使ったExtensionの作り方を説明します。例に取るのは　[GamePad API](https://www.w3.org/TR/gamepad/)を使ったGamepad Extension です。

なお、作り方は、[Scratch Japan Wiki Scratch_3.0の拡張機能](http://bit.ly/2kljIKW)を作ってみよう を参考にさせていただきました。

## 準備
Windows10 home 32bit で開発しています。
node が必要になりますので、インストールしておきます。
　私の環境には、node v8.16.0 がインストールされています。
MITのGithub から２つのリポジトリをクローンします。

``` console
> git clone --depth 1 https://github.com/llk/scratch-vm.git
> git clone --depth 1 https://github.com/llk/scratch-gui.git
```

## アイコンの準備
Extension を読み出すアイコン２つ準備します。

- 600x370
- 80x80 

それぞれ下のように表示されます。80x80 他にも使われているかもしれません。（不明）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58417/1cca9514-4c74-89cf-fb16-4a134ff47e49.png)

ファイル名をそれぞれ、

- gamepadext.png
- gamepadextmin.png

としました。gamepadというホルダーを作って、この２つのファイルを中に保存します。

> scratch-gui\src\lib\libraries\extensions\gamepad

このextensions フォルダーには、もともと付属しているExtensionのアイコンも入っています。

### index.jsxの編集

追加したアイコンのファイルをindex.jsxに登録します。

> scratch-gui\src\lib\libraries\extensions\index.jsx

ファイルの先頭に、import している行が並んでいますので、いずれかに以下２行を追加します。

```index.jsx
import gamepadIconURL from './gamepad/gamepadext.png';
import gamepadInsetIconURL from './gamepad/gamepadextmin.png';
```

> export default [

の中に以下の情報を追加します。

```index.jsx
    {
        name: (
            <FormattedMessage
                defaultMessage="GamePad"
                description="Name for the 'GamePad' extension"
                id="gui.extension.gamepad.name"
            />
        ),
        extensionId: 'gamepad',
        iconURL: gamepadIconURL,
        insetIconURL: gamepadInsetIconURL,
        description: (
            <FormattedMessage
                defaultMessage="using GamePad"
                description="Description for the 'GamePad' extension"
                id="gui.extension.gamepad.description"
            />
        ),
        featured: true
    },

```

## ブロックアイコン

ブロックの左隅に表示するアイコンを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58417/df10832f-a9c7-e88d-ad5a-6fae461cfcdc.png)

Scratchのペイントエディター（コスチュームの描画）を使って、ベクターモードで40x40 のアイコンを作製します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58417/6610788e-3e02-a3a2-1799-0e9f07efd8e6.png)


描画をグループ化して、svg形式で保存します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58417/36fef747-d447-c74c-7e45-ec08d299d9af.png)

アイコンを右クリックし、「書き出し」を選択すると、ローカルにsvgファイルが保存できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58417/9c29221c-1ed0-f8fb-d561-7369dfaa7ef7.png)

保存した後は、<b>[ここ](https://2003kaito.com/tools/Base64_encode)</b>などでbase64形式に変換します。data:image/svg+xml;base64,で始まる文字列をコピーしておきます。

## Extension 本体のプログラム

scratch3_gamepadフォルダーを作成します。

> scratch-vm\src\extensions\scratch3_gamepad

このフォルダーに、index.jsを作成します。

``` index.js
const ArgumentType = require('../../extension-support/argument-type');
const BlockType = require('../../extension-support/block-type');
const Cast = require('../../util/cast');
const log = require('../../util/log');
const formatMessage = require('format-message');

/**
 * Icon svg to be displayed at the left edge of each extension block, encoded as a data URI.
 * @type {string}
 */
// eslint-disable-next-line max-len
const blockIconURI = 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdp...
dGFydCIgc3R5bGU9Im1peC1ibGVuZC1tb2RlOiBub3JtYWwiPjx0c3BhbiB4PSIwIiBkeT0iMCI+R1A8L3RzcGFuPjwvdGV4dD48L2c+PC9nPjwvc3ZnPg==';

/**
 * Icon svg to be displayed in the category menu, encoded as a data URI.
 * @type {string}
 */
// eslint-disable-next-line max-len
const menuIconURI = 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdp...
dGFydCIgc3R5bGU9Im1peC1ibGVuZC1tb2RlOiBub3JtYWwiPjx0c3BhbiB4PSIwIiBkeT0iMCI+R1A8L3RzcGFuPjwvdGV4dD48L2c+PC9nPjwvc3ZnPg==';
```

blockIconURIとmenuIconURIは中身同じものにしました。menuIconURIは、次の絵のように左端のメニューに表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58417/8fe4261d-720b-0d27-8e06-926934693e3c.png)

### GamePad API の追加

index.jsにGamePad APIを起動するプログラムを追加します。

``` index.js
var mPad=null;

function mStartGamePad()
{
	window.requestAnimationFrame(mStartGamePad);
    var gamepad_info = '';
	var gamepads = navigator.getGamepads();
	var gamepad_num =gamepads.length;
	mPad = new Array(gamepad_num);

	for (var i=0; i<gamepad_num; i++) {
		if(gamepads[i]!=null) mPad[i] = gamepads[i];
	}
}
```

### ブロックの追加

index.jsに、ここでは、ゲームパッド右側のXYABの４つのボタンを、HAT BLOCK で検知するブロック４つを追加しています。

```index.js
class Scratch3GamePad {

    constructor (runtime) {
        /**
         * The runtime instantiating this block package.
         * @type {Runtime}
         */
        this.runtime = runtime;

		window.requestAnimationFrame(mStartGamePad);

        //this._onTargetCreated = this._onTargetCreated.bind(this);
        //this.runtime.on('targetWasCreated', this._onTargetCreated);
    }

    /**
     * @returns {object} metadata for this extension and its blocks.
     */
	getInfo () {
		return {
			id: 'gamepad',

            name: formatMessage({
                id: 'Game Pad for Scratch3',
                default: 'Game Pad',
                description: 'Label for the game pad extension category'
            }),

			menuIconURI: menuIconURI,
			blockIconURI: blockIconURI,

			blocks: [
				{
					opcode: 's_X_Button',
					text: 'X Button',
					blockType: BlockType.HAT,
				},
				{
					opcode: 's_Y_Button',
					text: 'Y Button',
					blockType: BlockType.HAT,
				},
				{
					opcode: 's_A_Button',
					text: 'A Button',
					blockType: BlockType.HAT,
				},
				{
					opcode: 's_B_Button',
					text: 'B Button',
					blockType: BlockType.HAT,
				},

			],
            menus: {
            }
		};
	}

/* ================================	*/
// BUTTON X- Y- A- B-
	s_X_Button() {
		return mPad[0].buttons[2].pressed;
	};

	s_Y_Button() {
		return mPad[0].buttons[3].pressed;
	};

	s_A_Button() {
		return mPad[0].buttons[0].pressed;
	};

	s_B_Button() {
		return mPad[0].buttons[1].pressed;
	};

}
module.exports = Scratch3GamePad;

```

## Exmansion Manager の修正

> scratch-vm\src\extension-support\extension-manager.js

に１行追加します。

```expansion-manager.js
    gamepad: () => require('../extensions/scratch3_gamepad')
```

## 使用例

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58417/2a8150ff-ac5c-4fa8-2ea3-3ea68d04e2fd.png)

## ローカルでの実行
制作したExtenshionをローカルで実行するには、コンソールで次のように立ち上げます。
ディレクトリはscratch-vmに居るものとします。

```console
scratch-vm > npm rm -g scratch-vm
scratch-vm > npm i
scratch-vm > npm link
scratch-vm > cd ..\scratch-gui
scratch-gui > npm i
scratch-gui > npm link scratch-vm
scratch-gui > npm start
```

これで、Scratchがローカルサーバで起動します。
ウェッブブラウザーで http://localhost:8601 にアクセスすると、Scratchを試すことができます。

## アプリケーション

このExtensionは[Guthub URL](http://bit.ly/2JxiENG)で使えます。お試し下さい。
私は、Playstationのゲームパッドをアダプターを介してUSBでPCにつないでいます。