2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

れびゅーAdvent Calendar 2022

Day 1

@minecraft/server-uiのレスポンスはPromiseだよという話

Last updated at Posted at 2022-12-01

導入

 皆さんはMinecraftをご存じでしょうか。まぁあの神ゲーを知らない方はほとんどいらっしゃらないかと思います。私もずっとMinecraftBEで遊んでいます。

 さて、最近"Gametest Framework"なるものと出会いました。なんでもビヘイビアパック等を追加したときにゲームが正常に動作するかどうかを確認するために追加された要素だそうです。
しかしそれは表向きで、実際は正常に動作するか以外の用途にも十分使えるような、便利なモジュールが用意されています。1.19.50現在、gametestはjavascriptでのみ記述でき、アイテム使用の検知からエンティティのコンポーネントの上書きまで、様々なことが可能です。

formとは

 formはGametestFrameworkが提供するモジュールの一つ、@minecraft/server-uiモジュールが提供する、Minecraft内でプレイヤーの入力をGUIで受け付けることができる機能です。manifest.jsonのdependenciesを適切に設定した状態で、以下のスクリプトを、エントリーで指定したファイルに記述します。

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

const form = new ActionFormData()
                .title("hoge")
                .body("hogehoge")
                .button("hogehogehoge");

for(let player of world.getPlayers()) { 
    form.show(player); 
} 

この状態で、このビヘイビアパックの導入されたワールドに入ると、以下の画像のようなformが表示されます。image.png
 このようなformを簡単に表示できる機能があるのは素晴らしかったのですが、私のようなプログラミング初心者には難しい落とし穴が多くあり、かなり苦しみました。
 この記事ではその落とし穴のうちの一つ、"form.show()の返り値はPromise"について書いていきます。

Promiseとは

 formは、そのインスタンスのshow()メゾットを呼ぶことで、その引数に指定したプレイヤーに表示させることが可能です。そのメゾットの返り値はPromiseとなっています。では、そのPromiseとはなんでしょうか。MDNによれば、

プロミス (Promise) は、作成された時点では分からなくてもよい値へのプロキシーです。非同期のアクションの成功値または失敗理由にハンドラーを結びつけることができます。これにより、非同期メソッドは結果の値を返す代わりに、未来のある時点で値を提供するプロミスを返すことで、同期メソッドと同じように値を返すことができるようになります。

だそうです。意味が分からないですね。要は、formでプレイヤーの入力を待っている間もプログラムは一時停止せず続きが実行されるよという意味です。ここで躓きました。たとえば以下のようなコードがあったとしましょう。

for(let player of world.getPlayers()){
    //ここでformの結果を変数に代入している。
    const result = form.show(player);
    log(result.selection);

 理想の挙動は「playerのform入力の結果が出力される」ことだと思いますが、これは正常に動作しません。なぜかわかりますか?
 その理由は、「resultに結果が代入される前にlog()が実行されるから」です。レスポンスの型がPromiseであるformは、高速化のため、プレイヤーがformに入力するのを待っている間も並行してどんどん次の処理を実行していってしまうのです。ではどうすれば理想の挙動をしてくれるのでしょうか。

Promiseの並列処理制限

 Promiseの並列処理を止めることは、then()によって実現できます。

 formの返り値Promiseで.then()を呼び、その中にアロー関数でformが入力された後に実行したい処理を記述していきます。たとえば

for(let player of world.getPlayers()){
    form.show(player).then((result) => {
        log(result.selection);
    });
}

というような具合です。アロー関数の第一引数にformの入力情報とかが代入されています。
注意すべきは、この方法の場合、実際には並列処理が行われている点です。then()を使うともう並列処理はなされないのかというとそうではなく、then()の外側に記述された処理は余裕でform入力を待たずして実行されていきます。
また、escキーを押した場合や、右上のバツ印を押されて、ボタンをどれも選ばずformを閉じた場合、selectionに0が代入されてしまうため、不具合の原因となります。canceledを評価することで、どれも選んでいない状態の場合、弾きましょう。

最後に

 この記事は、私が初めて自分で書いた記事ですので、読みにくかった点や分かりにくかった点などがあるかもしれません。もしそういった気になる部分があればどんどん教えていただきたいです。
 ほかにもプログラミング初心者には難しい、私自身が長時間悩まされた落とし穴はあります。今後もそういったポイントの解説記事を書いていき、一人でも多くの「gametestでプログラミング初体験!」みたいな人を救えたならと思います…。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?