MinecraftBEでscriptingAPI！part2.自作uiを表示する

#はじめに
今回は棒でブロックを叩くと3つのボタンが現れ、3つ目のボタンを押すと消える、というscriptを解説したいと思います。ファイル構成がやや複雑なのでページ下のサンプルパックをダウンロードすることをおすすめします。
#準備
今回はuiを追加するにあたって前回と違いいくつかのファイルを使用します。
client.js, server.jsに加えて button.html, button.css, hud_screen.jsonが必要になります。(今回はbuttonという名前をつけていますが、名前はなんでも大丈夫です)

リソースパック、ビヘイビアーパックのファイル構成は以下の通りになります。
your_resource
├ manifest.json
├ ui
│ └ hud_screen.json
└ experimental_ui
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;└ button.html
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;└ button.css

your_behavior
├ manifest.json
└ scripts
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├ client
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│ └ client.js
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;└ server
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;└ server.js

今回はclient.js, server.js の2つの解説をしたいと思います。
###追記
win10だと中央の十字が表示されなくなるようです。原因と解決方法が分かり次第編集します。
uiオプションの`loadEventData.data.options.is_showing_menu = true;`にするとwin10でも正常に動作するようです。(情報提供buttonさん)
スマホだと1部uiが消えるので注意です。


#client.js編
前回扱ったserver.jsとは扱える内容が少し違います。前回使用したblock_Interacted_withはここでは使用できない、といったように使えるものと使えないものがあります。(逆にclient.jsでしか使えないものもあります。)

早速コードを見てみましょう。
↓のものがclient.jsのコード全体です

```javascript:client.js
const system = client.registerSystem(0, 0);

system.initialize = function () {
   this.listenForEvent('minecraft:ui_event', (eventData) => this.onUIMessage(eventData));
   
   //指定イベントがブロードキャストされる毎に呼び出されるJavaScriptオブジェクトを登録
   this.listenForEvent('stick:loadui', (eventData) => this.loadui(eventData));
   
   //eventDataを登録
   
   this.registerEventData('stick:clickA', {});
   this.registerEventData('stick:clickB', {});
   this.registerEventData('stick:clickC', {});
};

system.loadui = function (eventData) {
   //uiのオプション
   var loadEventData = this.createEventData('minecraft:load_ui');
   loadEventData.data.path = 'button.html';
   loadEventData.data.options.always_accepts_input = false;
   loadEventData.data.options.is_showing_menu = false;
   loadEventData.data.options.absorbs_input = false;
   loadEventData.data.options.should_steal_mouse = true;
   loadEventData.data.options.force_render_below = true;
   loadEventData.data.options.render_only_when_topmost = true;
   loadEventData.data.options.render_game_behind = true;
   this.broadcastEvent('minecraft:load_ui', loadEventData);
};

//ボタンを押した時の処理
system.onUIMessage = function (eventData) {
   var BroadcastEventData = this.createEventData('minecraft:display_chat_event');
   
   if (!eventData.data) {
      return;
   }
   if (eventData.data === 'buttonAClicked') {
      //server側にブロードキャスト
      var clickEventDataA = this.createEventData('stick:clickA');
      this.broadcastEvent('stick:clickA', clickEventDataA);
      
      //チャットに'buttonA'と送る
      BroadcastEventData.data.message = 'buttonA';
      this.broadcastEvent('minecraft:display_chat_event', BroadcastEventData);
   }
   if (eventData.data === 'buttonBClicked') {
      //server側にブロードキャスト
      var clickEventDataB = this.createEventData('stick:clickB');
      this.broadcastEvent('stick:clickB', clickEventDataB);
      
      //チャットに'buttonB'と送る
      BroadcastEventData.data.message = 'buttonB';
      this.broadcastEvent('minecraft:display_chat_event', BroadcastEventData);
   }
   if (eventData.data === 'buttonCClicked') {
      //server側にブロードキャスト
      var clickEventDataC = this.createEventData('stick:clickC');
      this.broadcastEvent('stick:clickC', clickEventDataC);
      
      //ボタンを削除
      var unloadEventData = this.createEventData('minecraft:unload_ui');
      unloadEventData.data.path = 'button.html';
      this.broadcastEvent('minecraft:unload_ui', unloadEventData);
      
      //チャットに'buttonC'と送る
      BroadcastEventData.data.message = 'buttonC';
      this.broadcastEvent('minecraft:display_chat_event', BroadcastEventData);
   }
};
```

###system.initialize
まず、今回client.jsで使用するイベントは`minecraft:ui_event`です。これはhtmlファイル(今回でいうbutton.html)からブロードキャストされたとき呼び出されるイベントです。
######this.listenForEvent
これを使用して`minecraft:ui_event`を`this.onUIMessage`で扱えるようにするのと同様のことは前回やりましたが、今回はまた別の使い方もします。
実は第1引数である`eventIdentifier`には自作のイベント(今回でいう`stick:loadui`)も入れることができます。これを利用し、自作イベントがブロードキャストされる毎に呼び出されるjsオブジェクト(今回でいう`this.loadui`)が登録できます。
######this.registerEventData
これを使用する事で自作イベント(mcjixif:test の形)を登録する事が出来ます。今回は`stick:clientA`, `stick:clientB`, `stick:clientC`を登録します。

###system.loadui
これは先程`this.listenForEvent`で登録したものになります。`stick:loadui`がブロードキャストされた時に呼び出されます。
この中では主にuiの設定を行います。
######loadEventData.data.path
この1行により読み込まれるhtmlファイルを指定します。
######loadEventData.data.options
これでuiの細かい設定をします。設定は7つあります。(今回は説明を省きます。)

###system.onUIMessage
ここではボタンが押された時の処理を書きます。eventDataと先程登録したイベント(`stick:clickA`など)を比較し、一致したら処理するという流れになります。
######this.broadcastEvent
これを使うことでserver側にブロードキャストし、動作するようにします。
######minecraft:unload
今回buttonCを押した時、uiを消すという仕組みにしたいのでここだけこれを使います。
このイベントを使用して先程出したuiを消します。pathを指定しブロードキャストする事で消えます。

#server.js編
今回も前回と同じく`block_Interacted_with`を使用してブロックを棒で叩いたら動作する処理を書きたいと思います。

↓が全体のコードになります

```javascript:server.js
const system = server.registerSystem(0, 0);

var flag = false;

system.initialize = function () {
   this.listenForEvent('minecraft:block_interacted_with', (eventData) => this.onInteractedWith(eventData));
   
   //clientでregisterしたイベントを登録
   this.listenForEvent('stick:clickA', eventData => this.onClickA(eventData));
   this.listenForEvent('stick:clickB', eventData => this.onClickB(eventData));
   this.listenForEvent('stick:clickC', eventData => this.onClickC(eventData));
};

system.onInteractedWith = function (eventData) {
   var handContainer = this.getComponent(eventData.data.player, 'minecraft:hand_container');
   var carriedItem = handContainer.data[0].item;

   if (carriedItem == 'minecraft:stick' && !flag) {
      //clientのstick:loaduiをブロードキャスト
      this.broadcastEvent('stick:loadui', eventData);
      flag = true;
   }
};

system.onClickA = function (eventData) { 
   //処理
};

system.onClickB = function (eventData) { 
   //処理
};

system.onClickC = function (eventData) { 
   flag = false;
};
```
###flag
これはuiが表示されているかどうかをscriptが判断する為に必要なフラグです。これが無ければ複数重ねて表示されてしまうこともあります。表示されていれば`true`、されていなければ`false`になるように代入します。

###system.initialize
######this.listenForEvent
まず`block_Interacted_with`を登録します。これはいつも通りなので説明は省きます。
次に先程clientの`registerEventData`で登録したイベントを`listenForEvent`で登録します。ここで登録したjavascriptオブジェクト(今回でいう`this.onClickA`)はclientで`stick:clickA`がブロードキャストされた時に呼び出されます。

###system.onInteractedWith
ここでは前回行ったように`minecraft:stick`で叩いたら〜 となるようにif文を作ります。ここでflagが`false`になっていることも確認します。
持ち物が棒かつflagが`false`だった時、clientで登録した`stick:loadui`をブロードキャストします。これによりclientの`system.loadui`が呼び起こされuiが現れます。uiが現れたのでflagは`true`にします。

###system.onClickC
clientでuiが消えるようにしているのでflagは`false`にします。

#その他ファイル
コメントで解説があるので省略します。手抜きですみません🙇🙏

```html:button.html
<!DOCTYPE html>
<html>

<head>
	<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
	<script src="../UIEngine.js"></script>
	<link rel="stylesheet" href="button.css" />
</head>

<body>
  <div class="parent">
    <button id="buttonA" class="child"></button>
    <button id="buttonB" class="child"></button>
    <button id="buttonC" class="child"></button>
  </div>
</body>
<script type="text/javascript">
let scriptInterface = null;
engine.on("facet:updated:core.scripting", function (interface) {
    scriptInterface = interface;
});

engine.trigger("facet:request", ["core.scripting"]);

//ボタンのidからエレメントオブジェクトを取得
let buttonA = document.getElementById("buttonA");
let buttonB = document.getElementById("buttonB");
let buttonC = document.getElementById("buttonC");

//clickを感知してclient側にブロードキャスト
let broadcastEvent = function (event) {
    scriptInterface.triggerEvent(event);
}
buttonA.addEventListener("click", function () {
    broadcastEvent("buttonAClicked");
});
buttonB.addEventListener("click", function () {
    broadcastEvent("buttonBClicked");
});
buttonC.addEventListener("click", function () {
    broadcastEvent("buttonCClicked");
});
</script>

</html>
```

```css:button.css
@font-face {
  font-family: 'MinecraftTen';
  src: url(fonts/MinecraftTen.otf);
}
body {
  font-family: "MinecraftTen";
}
.parent {
  display: flex;
  flex-direction: row;
  justify-content: flex-end;
  height: 100%;
  width: 100%;
  opacity: 0.7;
}
.child {
  height: 6%;
  width: 6%;
}
```

```json:hud_screen.json
{
  "hud_screen@common.base_screen": {
    "always_accepts_input": true
  }
}
```

#参考リンク

[scriptingDocumentation](https://minecraft.gamepedia.com/Bedrock_Edition_scripting_documentation)
[official samplePack](https://minecraft.gamepedia.com/Add-on)

今回解説したものはこちらです。
[mcjixif samplePack](https://www.dropbox.com/s/a0wm0z1jcvhh5q5/%E3%80%90S%E3%80%91stick%20and%20ui%20v0.2.1.mcaddon?dl=0)