導入
皆さんは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が表示されます。
このような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でプログラミング初体験!」みたいな人を救えたならと思います…。