住民との会話システム（アレフガルド in マインクラフト Part4）

# はじめに
　[Part3](https://qiita.com/yamamorisoba/items/26905b328cff581102d0)ではメルキドの街並みを生成するコードをMakeCodeのJavascriptで実装しました。今回は、街に配置する住民たちが所定のセリフを発してくれるように会話システムを実装してみます。プレイヤーに話しかけてくるようになった住民達の様子を動画にまとめました。

<iframe width="560" height="315" src="https://www.youtube.com/embed/XNSC9ziZxsQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

　ここからは、今回実装した仕組みについて説明します。


# セリフの設定
　マインクラフトの村人（Villager）に決まったセリフを言わせる仕組みについては、[マインクラフトで表現する組み合わせ最適化問題](https://qiita.com/yamamorisoba/items/24728c2554835b0b4e3f#%E4%BB%98%E9%8C%B23-%E8%A9%B1%E3%81%97%E3%81%8B%E3%81%91%E3%81%A6%E3%81%8F%E3%82%8B%E9%98%B2%E5%85%B7%E5%B1%8B)の中で、「話しかけてくる防具屋」として紹介しました。ここでは概要のみの説明にとどめますが、リピートのコマンドブロックに以下のようなコマンドを設定し、プレイヤーと住民との距離が<distance>以内になっているかどうかを繰り返し判定して、
 
 ```
 /execute as @p at @p run testfor @e[name="<villager_name>",r=<distance>]
 ```

　判定結果が真となったときに、住民にセリフを発するためのコマンドブロックに動力を流して作動させるという仕掛けです。セリフを発する部分のコマンドは以下のように設定します。

 ```
 /execute as @e[name="<villager_name>"] run /say <message>
 ```

 この仕組みを応用して、住民の数だけセリフを設定します。住民一人ひとりを識別してコマンドブロックを用意する必要があるため、住民には一意の名前を付けます。「話しかけてくる防具屋」のケースではセリフは一言のみでしたが、住民の中には、セリフが一言では終わらない方もいます。セリフが複数続く住民の場合は、セリフを発するコマンドブロックを条件付きのチェーンでつないで、複数のセリフを連続して発言するようにします。
 
![スクリーンショット 2024-04-26 175149.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/ad9afaf3-d399-0e6f-6c0c-ced7cbf8e364.png)


　上図の中央のコマンドブロックは、インパルスのコマンドブロックに続く形でチェーンのコマンドブロックが3つ連なっています。これはメルキドの長老のセリフを設定している部分で、以下のように4つのセリフを続けて発するようにコマンドを定義しています。
 |ブロック|セリフ|
 |:--:|:--|
 |インパルス|ゆうしゃのため　いのりましょう。|
 |チェーン①|ひかりが　そなたと　ともに　ありますように…|
 |チェーン②|ゆくがよい。そして　さがすがよい。|
 |チェーン③|ラダトームの　おしろまで　きたに７０　にしに４０の　そのばしょを！|

　メルキドには20人の住民がいるので、このようなセリフを定義するコマンドブロックを20人分用意します。[^1]
 
[^1]:ドラゴンクエスト内の住民との会話の中には、「はい」「いいえ」でプレイヤーが応答を返すインタラクティブなものもあるのですが、今回はインタラクティブな会話については割愛します。

# セリフデータの収集
　住民一人ひとりの会話の内容は、[Part1](https://qiita.com/yamamorisoba/items/a8736bfe8acb802dd2ae#%E4%BD%8F%E6%B0%91%E3%81%9F%E3%81%A1%E3%81%AE%E6%83%85%E5%A0%B1)で紹介した、Wiiで復刻されたFC版ドラゴンクエスト1・2・3で確認しました。メルキドは、ドラゴンクエスト本編では終盤に訪れる街なので、それなりにレベルを上げていないとたどり着けませんが、レベルMAX勇者の「ふっかつのじゅもん」が脳に刻み込まれているため、全く問題なくメルキドにたどりつけました。セーブデータを用いないレトロゲームもこういう時には便利です。

　![スクリーンショット 2024-04-25 221147.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/8716088e-bde7-83fe-3784-5315a2dfc1d8.png)
「ふっかつのじゅもん」から数十年ぶりの再開[^3]

[^3]:王様に叱責される勇者というのも初代ドラクエならではというか…後のシリーズではあんまり見かけませんね。

# コマンドブロックの有効化制御
### プレイヤーとの距離依存性の解消
　ところで、マインクラフトのコマンドブロックは、通常はプレイヤーと一定以上の距離が離れてしまうと動いてくれなくなります。会話システムをコマンドブロックで実装する場合、プレイヤーとの距離に関係なくコマンドブロックには機能してほしいので、tickingareaというコマンドを用いて常時コマンドブロックを読み込んでくれる範囲を設定します。
```
/tickingarea add <from:x y z> <to:x y z> <area_name>
```
　上記コマンドで設定したtickingarea内に配置するコマンドブロックはプレイヤーとの距離に関係なく機能してくれます。

### 街の中にいる時だけ稼働させる
　tickingareaを定義することで、コマンドブロックを常時機能させることができますが、セリフを設定するコマンドブロックを住民の数だけ作ることを考えると、別の問題を考慮する必要が出てきます。セリフを設定するコマンドブロックは、対象の住民とプレイヤーとの距離を`testfor`コマンドで繰り返し測定することで、発言のトリガーを判定しています。これをそのまま実装すると常に住民全員分の`testfor`判定が反復実行されてしまい、必要以上に計算機リソースを消費してしまう可能性があります。`testfor`の反復実行を必要最小限にするには、ある街のマップ内にいる時には、その街の住民との距離判定を行うコマンドブロックだけが機能する状態が理想的です。
 　プレイヤーが街のマップ内にいるかどうかを`testfor`の位置条件で判定することも可能ですが、街の数や判定エリアの広さを考えるとあまり効率がよくないので、今回は`testforblock`を使うことにします。このコマンドは、指定した座標(x,y,z)に、指定したカテゴリのブロックがあるかどうかを判定します。
```
/testforblock <x> <y> <z> <block>
```
　フィールドマップから町マップへの切り替え（テレポート）を行う時に、街マップに入ったことを意味するフラグとして特定個所にブロックを設置し、町マップからフィールドマップに移動するときには、フラグ用ブロックを消すようにすると、フラグ用ブロックの有無を`testforblock`で確認することで、プレイヤーが特定の街マップにいるかどうかを判定することができます。`testforblock`を実行しているコマンドブロックからコンパレータとリピーターを使ってレッドストーン回路を伸ばし、フラグ用ブロックがある時のみ会話用コマンドブロックに動力が提供されるようにすることで、街の中にいる時だけ会話用コマンドブロックが稼働する仕組みを作ることができます。

# 住民の配置制御
### 配置の固定化
 住民と適切に会話ができる状態にするためには、住民の配置にも気を付ける必要があります。例えば、カウンター越しに会話をする住民の場合、カウンターを挟んで距離判定を行うため、r=2としてプレイヤーとの距離が2ブロック以内の場合にセリフを発するように制御しますが、住民がカウンターから離れてしまうと会話ができません[^2]。かといって、r=3のように会話の発動距離を大きくしてしまうと、建物の壁越しでも会話ができてしまう状況となり好ましくありません。そのため、カウンター越しに会話をする住民はカウンター前に配置を固定しておくような配置の制御を行う必要があります。今回は、見た目はあんまりよくないですが、トロッコに乗せることで住民をカウンター前に固定します。このトロッコの配置もコード化したいので、以下のようにMakeCodeのJavaScriptで実装します。

[^2]:ドラゴンクエスト本編でもそのようなシチュエーションはありますが、移動範囲が比較的狭いので、カウンターに来てくれるまで待つことができました。マインクラフトで再現したマップの場合、移動範囲がFC版よりも広いためカウンターに戻るまで待つのはかなり根気のいる対応となります。

```javascript
mobs.execute(mobs.target(MY_AGENT),world(<x1> <y1> <z1>),"summon minecart <x2> <y2> <z2>")
```
　x1,y1,z1は、summonコマンドを実行するエージェントの位置を示す座標で、今回の場合はどこでも問題ありません。x2,y2,z2がトロッコ(minecart)を設置する座標です。トロッコと同じ座標に住民を配置(summon)することで、住民をトロッコに乗せ、配置を固定することができます。

### マップ外への逃走防止
　また、配置を固定しない住民の場合、時間経過とともに街マップの外に移動してしまう場合があります。街マップの中に住民をとどめておくために、いくつかの工夫が必要です。メルキドの場合、城塞都市である特徴を活かして、街自体を城壁で囲むことで、住民の逃走をある程度抑止できます。ただ、出入り口に相当する城壁の開口部からは、住民が出入りできてしまうため、開口部に対しては別の対応が必要となります。Education Editionには「ボーダー」と「バリア」という2種類の不可視ブロックが存在しており、どちらもMOBやプレイヤーの移動を制限するのに用いることができます。今回は統合版（Bedrock Edition）でも利用可能なバリアブロックを用いて、開口部をふさぎ住民の逃走を防止します。バリアブロックは、MakeCodeのJanaScriptのblock.fillでは指定できないため、mobs.executeを使って配置します。

 ```javascript
mobs.execute(mobs.target(MY_AGENT),world(<x1>,<y1>,<z1>),"fill <from:x y z> <to:x y z> barrier
 ```

![Minecraft Education 2024_04_24 23_57_59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/6789936f-3621-260e-827b-9a113c8f9537.png)

図はバリアブロックでふさがれた開口部の様子です。プレイヤーがバリアブロックを持っているときには、通行止めのマークでバリアブロックの存在が可視化されていますが、バリアブロックを持たない状態のときにはブロックが見えなくなり不可視の壁のように働きます。
 


# メルキド用コマンドブロック群
　ここまでに述べたコマンドブロック類を実際に配置した実装例が以下の図です。
 ![スクリーンショット 2024-04-24 225955.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/83667ea5-f504-dcd3-82bd-c68d6a9860bd.png)

|番号|説明|
|:-:|:-|
|①|フィールドマップからメルキドの街マップに切り替えを行うコマンドブロック|
|②|マップ切り替え時に一瞬暗転効果を入れるための中継地点<br>（上がフィールドから街マップに切り替わる時、下は街マップからフィールドマップに切り替わる時の中継点）|
|③|メルキドマップに入ったことを示すフラグ用ブロック|
|④|フラグ用ブロックの有無をチェックするコマンドブロック|
|⑤|メルキドの街マップの境界から外に出たことを判定するコマンドブロック<br>④の判定が真だった時に、動力の供給を受けて起動|
|⑥|住民の会話のセリフを設定しているコマンドブロック<br>④の判定が真だった時に、動力の供給を受けて起動|

# まとめ
　今回は、メルキドの住民と会話ができるようになるための会話システムをコマンドブロックで実装しました。同様の方法を適用することで他の城や街・村の住民との会話も再現できそうです。

# おまけ：メルキド地方生成状況
　メルキドの街を再現することと並行して、メルキド地方のフィールドマップも少しずつ再現を進めています。メルキド跡地を起点として、岩山や海で遮られるまで範囲については一通り再現できました。FC版でメルキドにわたる橋があったあたりから眺めた構図が以下の画像です。

![Minecraft Education 2024_04_25 21_50_46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/4cf2009d-6969-531f-46a4-8a64b88a2743.png)

　かなり広いマップとなってきたので、撮影用に描画距離を30チャンクまで拡張しています。この地形の再生のために作成した測量データのExcelファイルはこのような状態になっています。
 ![スクリーンショット 2024-04-25 215533.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/7c92341a-1c20-b862-dd58-90eac2e8ab17.png)

　俯瞰してみてみると、FC版の地形を模したのか、16x16ブロック程度の大きさを基本単位として角ばった地形になっていることがわかります。FC版のマス目に換算すると縦16マスx横25マス程度は再現できたといったところでしょうか。オリジナルのマップ全体が120マスx120マス程度の広さなので、進捗は3%弱といったところです。この道わが旅がきこえてきそうだなあ…
 また、岩山や海による地形の遮られ方もFC版とは少し異なっているようです。このあたりは、ドラゴンクエストビルダーズ側のシナリオを進めて、ビルダーズ内の測量範囲を広げていくことで、いずれ明らかになっていくと思われます。ちなみに、スタート地点のメルキド周辺の測量を優先していたため、ビルダーズのシナリオ自体はほとんど進んでいません。メルキドが一段落したのでそろそろ次の地域に測量対象を広げていこうと思います。