メッセージフォームの使い方 / MessageFormData [Script API]

すべてのマインクラフターとプログラミングの楽しさを共有したい。こんにちは、管理人のぽっちーです。

今回はMinecraft(マインクラフト、以下マイクラ)のアドオン作りに欠かせないフォームの中でも、メッセージとボタン2つだけのシンプルなメッセージフォームの作り方について解説します。

メッセージフォームを使えるようになると、プレイヤーに対して「OK」「キャンセル」ボタンによる実行確認画面や2択の選択肢を表示できるようになるため、アドオンの幅が広がります。

それでは早速使い方を解説していきます。

サンプルコードについて

サンプルコードはマインクラフト統合版1.19.70で動作確認をしております。(2023/03/23現在)

メッセージフォームの使い方

メッセージフォームは@minecraft/server-uiモジュールのMessageFormDataクラスを利用することで作成および表示することができます。

基本的なメッセージフォームの作り方と表示方法は以下の通りです。

メッセージフォームの基本的な作り方
  1. MessageFormDataクラスとインポートする
  2. MessageFormDataクラスをインスタンス化する
  3. 部品を追加する
  4. メッセージフォームを表示させる
  5. メッセージフォームのボタンが押された後の処理を書く

模式的なコードを書くと以下のような感じになります。

// MessageFormDataクラスをインポートする
import { MessageFormData } from "@minecraft/server-ui";

// MessageFormDataクラスをインスタンス化する
const messageForm = new MessageFormData();

// 部品を追加する
messageForm.title("サンプルタイトル"); // タイトル
messageForm.body("実行しますか?");    // メッセージのボディ
messageForm.button1("はい");          // ボタン1つめ
messageForm.button2("キャンセル");    // ボタン2つめ

// メッセージフォームを表示する
messageForm.show(/* プレイヤー */).then(res => {
  // メッセージフォームの選択肢が選択されたあとの処理を記述していく
});

メッセージフォームを表示する箇所で「/* プレイヤー */」となっているところは、実際のプレイヤーのインスタンスを指定します。

実際に動かせるサンプルコードは後ほど紹介します。

関連クラス

メッセージフォームに関連するクラスは以下の通りです。

クラス概要リファレンス
MessageFormDataメッセージフォームの作成と表示を担当するクラス公式リファレンス
MessageFormResponseプレイヤーの選択結果を格納するクラス公式リファレンス
FormResponseMessageFormResponseの親クラス。
フォームがキャンセルされた理由などが格納されるクラス。
公式リファレンス
メッセージフォームの関連クラス

サンプルコード

細かいことは後々公式リファレンスで学ぶとして、まずはサンプルコード見てメッセージフォームの作り方と表示の仕方を体で覚えてしまいましょう。

棒を使ったらメッセージフォームを表示する

まずは何も機能を持たないメッセージフォームを表示してみます。メッセージフォームを含む各フォーム(メッセージフォーム、アクションフォーム、モーダルフォーム)は何かしらのイベントが発火したときに表示させることになりますので、ここではBeforeItemUseEventを利用して棒(minecraft:stick)が使われた時にメッセージフォームを表示してみましょう。

import { world } from "@minecraft/server";
import { MessageFormData } from "@minecraft/server-ui";

// アイテムを使ったときにフォームが表示されるように処理を書く
world.events.beforeItemUse.subscribe(beforeItemUseEvent => {
  if (beforeItemUseEvent.item.typeId === "minecraft:stick") { // 棒を使ったとき
    // Playerを変数に取り出します。
    const player = beforeItemUseEvent.source;

    // メッセージフォームビルダーを取得してメッセージフォームを組み立てます。
    const messageForm = new MessageFormData();
    messageForm.title("メッセージフォームの表示");
    messageForm.body("メッセージボディの文字列です。");
    messageForm.button1("1つ目のボタンです。");
    messageForm.button2("こちらは2つ目のボタンです。");

    // メッセージフォームビルダーで組み立てたものを表示します。
    messageForm.show(player);
  }
});

このコードを実行し、棒を使うと次のようなフォームが表示されます。

MessageForm_1.jsの実行結果

おめでとうございます、メッセージフォームが表示されました!このコードにはボタンが押された時の処理が書いてないため、今はボタンを押しても何も起こりません。ボタンを押したときの処理は次のサンプルで説明します。

showメソッドの引数について [中級者向け解説]

MessageForm_1.jsでは、MessageFormクラスのshowメソッドの引数にplayerbeforechatEvent.source)を指定します。

showメソッドはPlayerクラスのインスタンスを引数に取るメソッドであるのに、サンプルではEntityクラスであるbeforechatEvent.sourceを渡していますが、PlayerクラスはEntityクラスのサブクラスであるため問題ありません。

BeforeItemUseイベントのsourceプロパティについて [中級者向け解説]

BeforeItemUseEventsourceプロパティはPlayer以外のEntityを返す場合があります(例えば敵モブがアイテムを使った場合)。したがって実際にアドオンを作る場合はアイテムを使ったEntityPlayerかどうかを確認する必要があります。

ここではメッセージフォームの説明に主眼を置いているため確認に関しては省略してあります。

メッセージフォームのボタンを押したら半径16ブロックにあるアイテムを削除する

フォームを表示するだけでは面白くありませんので、ボタンを押したらプレイヤーの半径16ブロック内に存在するアイテムを削除する機能を追加してみます。

import { world } from "@minecraft/server";
import { FormCancelationReason, MessageFormData } from "@minecraft/server-ui";

world.events.beforeItemUse.subscribe(beforeItemUseEvent => {
  if (beforeItemUseEvent.item.typeId === "minecraft:stick") {
    // Playerを変数に取り出します。
    const player = beforeItemUseEvent.source;

    // メッセージフォームビルダーを取得してメッセージフォームを組み立てます。
    const messageForm = new MessageFormData();
    messageForm.title("アイテム削除");
    messageForm.body("半径16ブロックにあるアイテムを削除します。よろしいですか?");
    messageForm.button1("はい");
    messageForm.button2("キャンセル");

    // メッセージフォームビルダーで組み立てたものを表示します。
    messageForm.show(player).then(response => {
      // 1つ目のボタン(「はい」ボタン)が選択されたらkillコマンドを実行する。
      if (response.selection === 1) {
        player.runCommandAsync("kill @e[type=item, r=16]");
        player.runCommandAsync("title @s actionbar アイテムを削除しました!");
      }
      // 2つ目のボタンが押されたらコマンドで「キャンセルしました」と表示する
      else if (response.selection === 0) {
        player.runCommandAsync(`title @s actionbar キャンセルしました。`);
      }
      // ボタン以外でフォームが閉じられた時の処理
      else if (response.canceled === true) {
        player.runCommandAsync(`tell @s フォームが閉じられました。`);
        if (response.cancelationReason === FormCancelationReason.userClosed) {
          player.runCommandAsync(`tell @s フォームが閉じた理由: ユーザーがクローズしたため`);
        } else if (response.cancelationReason === FormCancelationReason.userBusy) {
          player.runCommandAsync(`tell @s フォームが閉じた理由: ユーザービジー(チャットが開いているなど)`);
        }
      }
    });
  }
});

これを実行すると次のようなメッセージフォームが表示され、「はい」を選択することでkillコマンドが実行されます。その結果、半径16ブロックのアイテムがkillされて無くなります。

MessageForm_2.jsの実行結果1
MessageForm_2.jsの実行結果2

1つ目のボタンを選択した場合はMessageFormResponseクラスのselectionプロパティの値が1になりますのでこのように判定を入れています。

MessageFormResponseについて

メッセージフォームの実行結果はMessageFormResponseのインスタンスとして返ってきます。サンプルコードではresponseというパラメータがそれにあたります。

button1が選択された場合、selectionプロパティの値は1、button2が押された場合はselectionプロパティの値が0になります。(button2だけど0が返ってくるので注意です)

また、ESCキーなどでフォームが閉じられた場合、selectionの値はundefinedになります。

JavaScript中級者向けのコード

先ほど紹介したコードは改善の余地があります。Promiseでthen句を使って書くよりもawait / asyncを使って書いた方が簡潔に書けると思いますので、その例を載せておきます。また、各メソッドの返却値はshowメソッドを除きMessageFormData自身を返すためメソッドチェーンが使えます。なので一例としてメソッドチェーンも使ってみます。(ついでにバグ低減のためPlayer判定と定数クラスも盛り込みます)。余力があれば見てみてください。

import { MinecraftEntityTypes, MinecraftItemTypes, world } from "@minecraft/server";
import { MessageFormData } from "@minecraft/server-ui";

// コールバック関数内でawaitを使うため、コールバックにasync修飾子を付ける
world.events.beforeItemUse.subscribe(async beforeItemUseEvent => {
  // 棒以外のアイテムが使われた場合は処理を抜ける
  if (beforeItemUseEvent.item.typeId !== MinecraftItemTypes.stick.id) {
    return;
  }
  // beforeItemUseEventのsourceがPlayerじゃない場合も処理を抜ける
  if (beforeItemUseEvent.source.typeId !== MinecraftEntityTypes.player.id) {
    return;
  }

  // Playerを変数に取り出します。
  const player = beforeItemUseEvent.source;

  // メッセージフォームビルダーを取得してメッセージフォームを組み立てます。
  const messageForm = new MessageFormData()
    .title("アイテム削除")
    .body("半径16ブロックにあるアイテムを削除します。よろしいですか?")
    .button1("はい")
    .button2("キャンセル");

  // メッセージフォームビルダーで組み立てたものを表示します。
  const response = await messageForm.show(player);

  // 「はい」が選択された場合にkillコマンドを実行
  if (response.selection === 1) {
    player.runCommandAsync("kill @e[type=item, r=16]");
  }
});

メッセージフォームまとめ

この記事ではMessageFormDataクラスの各メソッドの紹介とメッセージフォームの表示方法を解説しました。メッセージフォームに限らず、@minecraft/server-uiモジュールのフォームは表示した後の処理を非同期で処理するためPromiseを使わなくてはならず、JavaScript初心者の方には少々難しい内容かと思いますが、まずはこの記事に載せた使用例を真似して書いてみて少しずつ理解していけば必ずフォームを使えるようになります。

フォームを使えるようになるとプレイヤーの選択によって処理を変えることが容易にできるようになるため是非チャレンジしてみてください!