# MCEEキャンプ 開発者用ドキュメント
>作成:2022/07/19
>作成者:めいぷる(oishi_kaede@ca-techkids.com)
[TOC]
:::info
**略語の定義**
- MCEE:Minecraft Education Edition
- WS:WebSocket
- GAS:Google Application Script
:::
MCEEキャンプでは、エージェントと呼ばれるエンティティをプログラムによって操作し、進めていきます!
メンターと生徒のワールドをつなぐために、WSサーバーを建ててMCEEのワールドと外部をつなげています。ホストPCがWindowsPCであればCode Connectionを用いて外部からワールドにアクセスすることができるのですが、MacOS版のCode Connectionがなかったことと、MCEEと外部の**双方向通信**を可能にするために、WSサーバを作成しました。
例えば、スプレッドシートとMCEEのワールドを接続し、生徒の進捗を管理したりすることができます。
なにか不明な点等あれば、めいぷるまで!
## WebSocketサーバー
### 接続手順
グループホストPCで以下の手順を踏む。
#### 1. MCEEを起動する
:::warning
開かない場合、[トラブルシューティング](#MCEEが起動しない)を参照。
:::
#### 2. (初回接続のみ)設定>プロフィール>暗号化されたWebsocketの要求→OFF
WSサーバーとの接続のため、初回のみWSの設定を変更。
#### 3. デスクトップの`CAMP.sh`を実行
or
`npm start`をし、サーバーを起動する。
:::warning
起動しない場合、[トラブルシューティング](#CAMP.shでエラーが出る)を参照。
:::
#### 4. ワールドを開く
初回は、配布される`EECamp.mcworld`をインポートする。
#### 5. コマンドで`/connect <グループホストPCのIPアドレス>:9999`と入力
IPアドレスは、スプレッドシートで確認。
#### 6. 接続完了
### 仕様
MCEEクライアント内で何か変更が起きた場合や、メンターがワールドを操作する際、MCEEコマンド(ブロック)を用いて、WSサーバーにJSONデータを送信する。
:::info
厳密には、情報をサーバーに送信したいときに情報を含んだJSONを`/tellraw`コマンドがMCサーバー内に送信し、それをWSサーバーが検知する。
:::
:::warning
JSON以外の内容がチャットに送られた場合は、先頭に「?」が付いていれば、その内容をコマンドとしてWSサーバーが実行する。(WSサーバーにしか実行できないコマンドがあるため[[参考](https://minecraft.fandom.com/ja/wiki/%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89#%E9%9A%A0%E3%81%97%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89)])
例:「?querytarget *ID*」→「/querytarget *ID*」として実行
:::
送信されるデータの種類には様々なものがある。WSサーバーがこの後どのような処理をするか識別するために、すべてのデータには、共通のフォーマットのヘッダーが付与されている。
```json!
{
"header": {
"sender": "送信者",
"type": "送信情報の種類",
}
}
```
`type`の値によって、WSサーバーで行う処理がswitchされる。
#### 初期化
スプレッドシートに設定した情報をもとに、MCEE内の変数(`scoreboard`)に生徒の名前等を設定する。ワールドに生徒を入室させた状態で行う必要がある。
#### 1. 初期化ボタン(←要検討)
トリガーにより、`/tellraw`コマンドを実行する。
```json
{
"header": {
"sender": "oishic",
"type": "init",
},
"body": {
"group": "A",
}
}
```
#### 2. チャット検知
トリガーに伴い実行された`/tellraw`コマンドをWSサーバーが検知する。チャットに送信されたグループレターをもとにGASAPIに**GET**リクエストを送る。
レスポンスの可視化のため、ヘッダーに`"Accept-Encoding": "identity"`を指定する。
:::warning
GASAPIはセキュリティ保護の観点から、`script.google.com`ではなく、`script.googleusercontent.com`にリダイレクトしてからレスポンスを返す。そのため、GASAPIを叩く際はリダイレクトの遡求を許可しなければならない([参考](https://developers.google.com/apps-script/guides/content#redirects))。
<details open><summary>JavaScript(WSサーバー)</summary>
```javascript
fetch(URL, { redirect: "follow" })
```
</details>
<details><summary>cURL</summary>
```shell
curl -L URL
```
</details>
:::
#### 3. データ送信
GETリクエストに対するレスポンスとして、
- 名前
- アカウント名
- Days
- 原点座標
それぞれをリストとしてJSON形式で受け取り、WSサーバー側でオブジェクトのリストに整形する。
- 整形後のリスト
```json
[
{
"name": "オオイシカエデ", //String
"account": "oishic", //String
"days": "4days", //String
"coord": "0,6,0", //String
},
{
//同上
},
...
]
```
#### 4.初期化実行
WSサーバー内で整形されたデータを元に、WSサーバーがMCEEにコマンドリクエストを送る。
送信するコマンドは以下の通り。
- スコアボードのリセット(必要?)
```
/scoreboard objectives remove days
/scoreboard objectives add days dummy
```
- IDとDaysの紐付け(必要?)
```
/scoreboard players set {ID} days {日数}
```
既存のキャンプ(特にオンライン)では、生徒のDaysに応じてコースの内容が変わるため、開始前に参加生徒に応じたワールドの設定をする必要があった。
EEキャンプも同様に、生徒のDaysに応じてコースの内容が変わるが、開始前のセットアップの手間を省くため、スプシにまとめられているデータを元にコースを自動生成する。
そのために、MCEE内でプレーヤーとDaysの情報を紐づける必要がある。これを実現するために、`/scoreboard`コマンドを使用している。
- ワールドの生成
```
???
```
生徒のDaysに応じてコースを生成する。Daysの情報をスプシから取得し、WSサーバー上で`/structure`コマンドを生成する。
この処理はプレーヤー全員のコースに対して一括で行うが、MCEE内では階層的(ワールド→プレーヤー→コース)に処理を行うため、個別で処理を実行することもできる。
行う処理は以下の通り。
1. コースの生成
- 原点の取得
- `/structure`する座標の生成
- 生徒がプレイするステージの割り振りは今後決めていく。
- Daysに応じて?
- レベル(学年)に応じて?
- ひとつひとつのステージの区画の大きさは全て等しいため、ステージの原点からの相対座標も単純計算によって導出できる。
- `/clone`コマンドの生成
3. プレーヤーのセット
4. エージェントのスポーン
#### エージェントスポーン
基本操作チュートリアルをクリアすると、エージェントをスポーンさせる。
エージェントを制御するコマンド`/agent`はWSサーバーからのリクエストのみ受け付けられる。
そのため、MCEEクライアント内でチュートリアルのクリアを検知すると同時に、WSサーバーにプレイヤー情報をチャットに送信する。
```json
{
"header": {
"type": "intro_agent"
},
"body": {
"player": "oishic"
}
}
```
```
tellraw @a[tag=admin] { "rawtext": [ { "text": "{\"header\":{\"type\":\"intro_agent\"},\"body\":{\"player\":\""},{ "selector": "@p[tag=players]" },{ "text": "\"}}" } ] }
```
#### コースクリア時
#### 1. クリア検知
コース毎に設置されたコマンドブロックが、クリアを検知し`/tellraw`コマンドでクリア情報をチャットに送信する。
```json
{
"header": {
"sender": "oishic",
"type": "clear",
},
"body": {
"player": "テストタロウ",
"course": 1,
}
}
```
#### 2. チャット検知
クリアに伴い実行された`/tellraw`コマンドをWSサーバーが検知する。チャットに送信された内容をもとにGASAPIに**POST**リクエストを送る。
+ `server.js`
```javascript=85
case "clear":
commitClear(msgJson.body.player, msgJson.body.course);
break;
```
```javascript=124
function commitClear(player, course) {
var SendDATA = {
player: player,
course: course,
};
var postparam = {
method: "POST",
mode: "no-cors",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: JSON.stringify(SendDATA),
};
fetch(GAS_API_URL, postparam)
.then((response) => console.log("Success:", response))
.catch((error) => console.error("Error:", error));
}
```
#### 3. 進捗管理表の変更
GASスクリプトがPOSTリクエストを受け取り、スプレッドシートを変更する。
+ `APIServer.gs`
```javascript=
function doPost(e) {
var rawData = JSON.parse(e.postData.getDataAsString());
const app = SpreadsheetApp.openById(SPREAD_SHEET_ID);
//シートをシート名で取得
const sheet = app.getSheetByName(SHEET_NAME);
const lastRow = sheet.getLastRow();
var _changeRaw = sheet.getRange(2, 2, lastRow).getValues().flat();
var changeRaw = _changeRaw.indexOf(rawData.player) + HEADER_RAW;
var changeColumn = parseInt(rawData.course) + HEADER_COLUMN;
sheet.getRange(3, 16).setValue(rawData.course);
sheet.getRange(3, 17).setValue(rawData);
sheet.getRange(changeRaw, changeColumn).setValue(true);
}
```
## ビヘイビアーパック
MCEEでは、ワールド内で使用されるさまざまなリソースを管理するために、ビヘイビアーパックが用意されている。
ビヘイビアーパックはワールドデータの中に`develop_behaivior_pack`フォルダをルートディレクトリとして保存されており、最上位に`manifest.json`というマニフェストファイルが置かれている。
ルートディレクトリの中にリソースの種類に応じて、サブフォルダを作成。
このワールドでは、
+ `~/structures/`
+ `~/scenes/`
+ `~/functions/`
の二つを主に用いている。
<details><summary>マニフェストファイル</summary>
```json
{
}
```
</details>
:::warning
マニフェストファイルの中では、UUIDによってリソースを管理しています。
同じビヘイビアーパックの中でUUIDが衝突するとMCEEがクラッシュしたので注意。([参考](https://docs.microsoft.com/ja-jp/minecraft/creator/documents/behaviorpack))
:::
### structures
`structures`フォルダには、MCEEワールド内で繰り返し使用することのできる構造物が`.mcstructure`形式で保存されている。
### scenes
このワールドでは、NPCによってチュートリアル等が進められている。NPCが発する台詞等の管理を`scenes`フォルダ内の`scene.json`で管理している。
## トラブルシューティング
### MCEEが起動しない
### ○CAMP.commandでエラーが出る
WebSocketサーバーが起動しない場合、以下の理由が考えられます。
「ターミナル」で`node -v`を実行し、Node.js環境が構築されているかを確認してください。
#### Node.js環境が構築されていない
`SETUP.command`をダブルクリックして実行してください。
Node.js環境が構築されます。
#### 起動に必要なライブラリがインストールされていない
`LIBRARY.command`をダブルクリックして実行してください。
サーバーに必要なNode.jsライブラリがインストールされます。
# 開発メモ
+ `/minecraftpe/games/com.mojang/minecraftpe/options.txt`にオートジャンプの設定がある→変更可能
+ `sed -i -e "s/ctrl_autojump_mouse:1/ctrl_autojump_mouse:0/g" ./options.txt`
+ 2~4daysに応じて知識の数を変え、`/structure`によりコース全体を生成する
+ 縦に積み上げるとtickingarea問題が楽になる
+ 
+ ~~agentのスポーンにはWebsocketサーバーが必要~~
+ node-packageで環境構築
+ チュートリアルエディタ
+ ghostスニペット
+ コードブロック内のプログラムを構成するブロックをツールボックスに追加する
+ templateスニペット
+ コードブロック内のプログラムをエディタ内に表示する
+ blocksスニペット
+ コードブロック内のプログラムをヒントとして表示する
+ MCEE内のコードビルダーの制御
+ `/codebuilder`(WSサーバー限定)でチュートリアルをセットする
+ ```codebuilder navigate @s false https://minecraft.makecode.com/#tutorial:65527-90621-86679-67168```
+ `/code`でエディタを開く
+ `/codebuilder reset @s`でリセット出来なさそう、、?
1. `/codebuilder https://minecraft.makecode.com/#tutorial:xxx/yyy/zzz`
2. `/codebuilder https://minecraft.makecode.com/#editor`
3. `/codebuilder https://minecraft.makecode.com/`
4. 新しいプロジェクトを作らせる
5. `/codebuolder https://minecraft.makecode.com/#editor`
```md
### @hideIteration true
# TechKidsCamp
## コマンドを実行する
「start」とチャットに入力してエージェントに命令してみよう!
エージェントがスタートポータルを開いてくれるよ!
```template
player.onChat("start", function () {
agent.turn(LEFT_TURN)
agent.turn(LEFT_TURN)
for (let index = 0; index < 10; index++) {
agent.move(FORWARD, 1)
}
for (let index = 0; index < 4; index++) {
agent.move(UP, 1)
agent.move(FORWARD, 1)
}
for (let index = 0; index < 3; index++) {
agent.move(FORWARD, 1)
}
agent.destroy(DOWN)
agent.move(DOWN, 1)
agent.destroy(RIGHT)
agent.destroy(LEFT)
for (let index = 0; index < 2; index++) {
agent.destroy(FORWARD)
agent.move(FORWARD, 1)
agent.destroy(RIGHT)
agent.destroy(LEFT)
}
})
```//
```
+ https://education.minecraft.net/wp-content/uploads/Minecraft-Education-Edition-Multiplayer-Guide-1.pdf
+ リソースパックのentityフォルダにあるnpcのファイルのgeometryの部分をプレイヤーと同じにする(https://discord.com/channels/834393520898965504/899161647321600060/1023563988145029180)
+ チャット画面>設定>すべてのチャットをミュートで`tellraw`の出力を非表示にできる
+ `/structure load`では、ロードされているチャンクでないと即座にloadされないので、予め`/tickingarea`でロードさせておく
+ ```/tickingarea add 14 0 -196 78 150 -132 dungeon true```
+ 参加コードは、絵柄を数字に直し、base64でエンコードすると、URLに含むことができる。
+ `/wssocket`はコマンドブロックでは動作せず、`.mcfunction`ファイルで動作する。(非同期だから?)
+ `.mctemplate`で配布の手間が減る!
+ makecodeのひらがな化
+ https://note.com/topnyan/n/n6f9b2ecfbd0d
+ http://ict.puziro.com/2020/05/22/local_makecode/
+ `C:\Program Files (x86)\Microsoft Studios\Minecraft Education Edition\data\behavior_packs\agent_gametest`
+ `C:\Program Files (x86)\Microsoft Studios\Minecraft Education Edition\data\resource_packs\education\splashes.json`編集したらスプラッシュいじれた
+ 
+ 
+ https://notebooks.minecrafteduservices.com/prod/index.html?gameVersion=1.18.42&inGame=1&ipc=1&lang=ja-JP&protocolVersion=16908288
+ 
+ 
+ 
+ ターゲットセレクタに関して
+ x,y,zは指定しないとコマンドブロックの座標が継承される
+ 
+ https://yogurr19-minecraft.hatenablog.com/entry/2020/11/15/081935#can_destroy
+ `git clone https://~~`でだれでもclone可能
+ https://www.reddit.com/r/MCPE/comments/5qf4ah/websockets_huge_potential_currently_unusable/
# 設計メモ