Scratch 3.0（公式）などで独自拡張機能を使わない外部との通信【ソースコード編】（ブラウザの開発者ツールのコンソール、JavaScript、WebSocket が関連）

以下の記事で書いていた仕組みについて、「【概要編】」では　ソースコードに関する部分に触れていなかったため、この記事ではその辺りの話を書いていきます。

●#Scratch 3.0（公式）などで独自拡張機能を使わない外部との通信【概要編】（ブラウザの開発者ツールのコンソール、JavaScript、WebSocket が関連、 #toio でも利用可） - Qiita
　https://qiita.com/youtoy/items/db6d34a9985eb182042f

## 動作している様子の再掲
「【概要編】」の記事でも掲載していた、この記事で扱う仕組みが動いている時の様子の動画です。

https://twitter.com/youtoy/status/1521537596056567808

構成の話などは、「[【概要編】の記事](https://qiita.com/youtoy/items/db6d34a9985eb182042f)」をご参照ください。　

## 準備手順
この後に、いくつかのプログラムのソースコード等が出てくるのですが、それらをどのような順番で動作させていくか等について書きます。

### 動作させる処理や手順
上で掲載していた動画に出てきていた仕組みを動かすためには、以下の処理を用いる必要があります。

- Scratch を開いたブラウザタブ
   - Scratch のプログラム
   - ブラウザの開発者ツールのコンソールで動かす JavaScript のプログラム 【1】
- toio Do を開いたブラウザ
   - toio Do のプログラム
   - ブラウザの開発者ツールのコンソールで動かす JavaScript のプログラム 【2】
- ターミナル
   - Node.js のプログラム

これらを以下の順番で動かしていきます。

1. 「Node.js のプログラム = WebSocketサーバー用の処理」
1. Scratch のプログラム
1. toio Do のプログラム
1. JavaScript のプログラム 【1】 （Scratch用）
1. JavaScript のプログラム 【2】 （toio Do用）

ここからは、上に書いた順番で各プログラムを紹介していきます。

## プログラムの内容
上に書いた 5つのプログラムの具体的な内容について触れていきます。

### Node.js のプログラム（WebSocketサーバー用の処理）
まずは、今回の 2つのブラウザタブ間をつなぐ通信も仲介役「WebSocketサーバーのプログラム」を記載しいます。

```javascript
const WebSocketServer = require("ws").Server;
wss = new WebSocketServer({ port: 8080 });

wss.on("connection", function connection(ws) {
  ws.on("message", function message(data) {
    console.log("received: %s", data);
    wss.clients.forEach(function each(client) {
      client.send(data.toString());
    });
  });
});
```

上記では　ws を使って WebSocketサーバーを動作させています（待ち受けをするポート番号は「8080」）。

通信処理については、あるクライアントからメッセージを受信した際に、接続中の全てのクライアントに対してメッセージを送信するという動作です。この時、送信するメッセージは、クライアントから受信したメッセージそのままです。
そのメッセージ送信について、メッセージを受信したクライアント 1つに返信する動きでなく、接続済みの全てのクライアント（今回の例では 2つ）にブロードキャストでメッセージを送る形になるため、 `forEach` を使った処理で全クライアントに対する `send()` の処理を行っています。

### Scratch のプログラムなど
次に Scratch のプログラムです。

以下は、1つだけ存在するスプライト（ネコのスプライト）のプログラムです。
![Scratch のプログラム](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/f59b48a0-c292-8ebe-dca3-78c4f437423d.png)
緑の旗が押された時の初期化用の処理があり、それ以外にキー押下に合わせて4種類ある「表示位置＋背景のセット」を切り替える処理があります。

また、背景は以下のように、見た目・色の異なるものを準備しているだけです。
![Scratchで設定した背景](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/fe0e0610-cba0-3e9f-a027-8afefd87a24d.png)

これらは基本的に、この後に出てくる toio Do のプログラムと似た見た目になるように作っています。

### toio Do のプログラムなど
次は toio Do についてです。

先に背景の設定を掲載します。先ほどの Scratch で設定していた内容と同じになります。
![toio Doで設定した背景](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/a54deaf3-415a-ccf3-04a0-aee6a7f30057.png)

次にプログラムです。
![toioのプログラム](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/5fa1d450-6739-138e-2901-1848aa3faef1.png)

基本的な構成要素は、一定の時間間隔で toio の姿勢検出の結果（数字）を変数に格納する処理と、姿勢検出で得られた結果によって「表示位置＋背景のセット」を切り替える処理です。これにより、toio のどの面を上に向けるかによって、ステージ上の表示が変化します。
また、ステージ上に変数の値を見えるようにしているのは、この後の処理で必要となる対応です。

### JavaScript のプログラム 【1】 （Scratch用）
ここからは、今回の仕組みで用いたトリッキーな実装の部分の話です。

以下に掲載しているプログラムは、Scratch のページを開いているブラウザタブで開発者ツールのコンソールを開いて、そこで実行します。

```javascript
const exampleSocket = new WebSocket("ws://localhost:8080");
exampleSocket.onopen = function (event) {
  console.log("open!");
};

exampleSocket.onmessage = function (event) {
  temp = parseInt(event.data);
  console.log(temp);
  if (temp === 1) {
    console.log("1");
    document.dispatchEvent(new KeyboardEvent("keydown", { key: " " }));
  } else if (temp === 5) {
    console.log("5");
    document.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowLeft" }));
  } else if (temp === 6) {
    console.log("6");
    document.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowRight" }));
  } else if (temp === 3) {
    console.log("3");
    document.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowUp" }));
  } else {
    console.log("どれでもない");
  }
};
```

主な処理は、ローカルで動かしている（Node.js で実行している）WebSocketサーバーへの接続と、WebSocketサーバーからのメッセージ受信をきかっけに動かすキーイベントを発生させる処理です。この時に受信したメッセージの内容（数字）によって、どのキーのイベントを発生させるかを変えています。
この部分は、先ほどの toio Do で変数に格納していた「姿勢検出結果を示す数字」によって、異なるキーが押されたことになるような実装になっています。

### JavaScript のプログラム 【2】 （toio Do用）
ここで以下に掲載しているプログラムは、先ほどの Scratch用のものと同様に、toio Do のページを開いているブラウザタブで開発者ツールのコンソールを開いて、そこで実行します。

```javascript
const exampleSocket = new WebSocket("ws://localhost:8080");
exampleSocket.onopen = function (event) {
    console.log("open!");
    
    const tempVal = document.querySelector("body > 【省略】 ")
    setInterval(function () {
        console.log(tempVal.innerText);
        exampleSocket.send(tempVal.innerText);
    }, 1000/2);
};
```

主な処理は、ローカルで動かしている（Node.js で実行している）WebSocketサーバーへの接続と、一定時間ごとに処理する WebSocketサーバーへのメッセージ送信です。この時、送信するメッセージは以下の画像で示した部分で表示されている数字の内容（toio の姿勢検出結果を示す数字）です。
![変数の内容](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/3087fc09-e431-9acb-9f80-e4ddc34648f0.png)
`document.querySelector()` の中は、上では一部のみしか書いてなく、実際はかなり長い文字列になります。どんな文字列になるかというと、上記の画像で示している div のパスです。

パスは、ブラウザの開発者ツールの要素を閲覧する機能を使って、JavaScript用のパスを取得して用いました（※ 以下の画像で示した内容）。
![div のパスの取得](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/862f7241-7f32-84f6-a1c9-adbc18405492.png)

これにより、WebSocketサーバーへ一定の時間間隔で、toio の姿勢検出結果が送られることになります。
そして、Node.js で作った WebSocketサーバーは、メッセージを受信するごとに受信内容をブロードキャストするので、この内容は Scratch を開いたブラウザタブへと届きます。その結果、Scratch を開いたブラウザタブで、toio の姿勢検出の内容に応じたキー押下のイベントが発生するわけです。

### JavaScript のプログラムに関する補足
#### JavaScript のプログラム 【1】 （Scratch用） について
「JavaScript のプログラム 【1】 （Scratch用）」の中では、Scratch上でのスプライトの表示位置・背景の種類の変更を、キー押下のイベントを介して行うようにしています。これは、もし可能であれば、Scratch の中で内部的に保持されているパラメータを直接変更する処理にしたかった部分です。しかし、自分がそのような処理の可否自体が分からなかったため、比較的容易に実現できる方法を用いました。

#### JavaScript のプログラム 【2】 （toio Do用） について
「JavaScript のプログラム 【2】 （toio Do用）」の中では、toio Do の中で扱われる変数の内容を取得する際に、変数の中身を画面上に表示する機能を経由させる構成にしています。これは本来、toio Do の中で変数を保持している部分から、直接値を取得できればシンプルになりそうな部分です。
そのような実装が可能であればそうしたかったのですが、自分がそのような処理の可否自体が分からない状況のため、その代替としてとりあえず容易に用いることが可能な手段として、上で書いた処理を用いています。
（もし、この部分をうまくやる方法をご存じの方がいらっしゃいましたら、記事へのコメントなどで教えていただけると嬉しいです）

## おわりに
以上のプログラムを用いることで、公式版の Scratch 3.0 と toio Do の間をつないだ処理を行うことができました。

### 【追記】 ブラウザの拡張機能を活用すると良さそう？
今回、開発者ツールのコンソールで動かしている処理の部分、ブラウザの拡張機能にしてしまうと使い勝手が良くなったりしそう？

### 【追記2】 既にブラウザの拡張機能で同じ仕組みが作られていた話
Facebook にこの件を投稿していたら、今回自分が記事に書いていた内容と同じことを Chrome拡張でやっていた話の動画を、作者ご本人に教えていただきました。

●Chrome拡張 castkey(キャストキー） - YouTube
　https://www.youtube.com/watch?v=ei1gu17mUow

動画のコメント欄を見たら、このような記載も。
![castkey(キャストキー）の処理内容](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/3cf3188e-346e-3598-73e6-cc671d7e6340.png)