アレフガルド in マインクラフト Part1 マインクラフトでの地形生成

# はじめに
　先日投稿した記事のおまけとして、「話しかける防具屋」をつくる仕組みを作った時に思ったのです。「この仕組みを使ったら、ドラクエの村とかも再現できそうだな（見た目は全員村人になるけど…）」と。そう思ったら、いてもたってもいられなくなり、実際に作ってみることにしました。小学生の頃、ファミコンのドット絵を見ながらワクワク冒険していたアレフガルドを3Dの世界で旅してみたい！というノスタルジーを原動力に、マインクラフトの世界でアレフガルドの再現に取り組んでみようと思います。

# 用意したもの
　ドラゴンクエストは、FC版に始まりSFC版、スマホ版等、リメイクを含めると色々なバージョンがあります。ここでは、再現のために参照する各種情報についてまとめておきます。

## マップ（地形）の情報
　最初にアレフガルドのマップ情報を探してみました。[堀井雄二さんが投稿したアレフガルドの地図](https://twitter.com/YujiHorii/status/691557997357637632?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E691557997357637632%7Ctwgr%5Eed2f7ea920b585d886fb226f8d90599850d4475f%7Ctwcon%5Es1_&ref_url=https%3A%2F%2Fgcgx.games%2Fdq1%2Fmap%2Fworld_map.html)などから、アレフガルド全土は、おおむね120×120くらいのマスに収まるように見えます。ただ、FC版の1マスをマインクラフトの1ブロックに単純に置き換えると、箱庭のようなアレフガルドになってしまいます。もっと広大なアレフガルドを旅したいんですよねぇ…

　ここで先ほどの堀井さんの投稿で「ドラゴンクエストビルダーズ」というタイトルに言及されていることに気が付きました。ナンバリングタイトルしかプレイしていない私としてはノーマークでしたが、[公式HP](https://www.dragonquest.jp/builders/)から世界観を覗いてみると…

![ドラゴンクエストビルダーズの世界観](https://cache-www.dragonquest.jp/builders/assets_v1/img/introduction/world_read.png)
ドラゴンクエストビルダーズ公式HPより

　ここにもうアレフガルドの3Dマップが作られている雰囲気です。ブロックで構成されているということなので、マインクラフトとも相性がよさそうです。「闇に覆われてしまった」ということなので、一部の地形が変わってしまったり、城や村は無事ではなかったりしそうですが、基本的な地形はドラゴンクエストビルダーズを基にすると再現できそうな気がしてきました。奇しくも[スマートフォン版「ドラゴンクエストビルダーズ」が63％オフとなる特別セール](https://www.4gamer.net/games/634/G063405/20240314043/)が開催中だったので、ポチっておきます。

## 住民たちの情報
　大まかな3D地形はビルダーズを基にするとして、闇に覆われる前のアレフガルドの城や村に住んでいる人々の様子については、FC版のドラゴンクエストを参照する必要がありそうです。 [^1]
　とはいうものの、手元にファミコンもなければFC版ドラゴンクエストもありません。再現を検討する上で、一次情報としてFC版ドラゴンクエストは何とか手に入れたい…と思って、調査をしてみたところ、ドラゴンクエスト25周年記念として、WiiでFC版ドラゴンクエスト1・2・3が発売されていたことがわかりました。さすがに新品在庫はどこにもないので、Wii本体ごと中古で確保します。
　これで、アレフガルドに住む人々の会話を含めた情報についても、確認することができそうです。

# マインクラフトでのマップ再現方法
　必要な情報のめどがついたところで、さっそく地形の再現に取り掛かります。ドラゴンクエストビルダーズの世界は、マインクラフトと同様ブロックで構成されており、ブロック一つのサイズ感もほぼ一緒なので、ブロックレベルで1:1対応するように地形を複製すれば、マインクラフト上にアレフガルドを再現できそうです。
　ただ、ビルダーズの地形を目視と手作業でマインクラフト上に複製するのは、大変な手間となりそうなので、プログラムを使って効率化していきます。マインクラフト側も、プログラムを使えるようにCode Builderが組み込まれているEducation Editionを使います。

## 地形データの用意
　まずは、ビルダーズの地形をExcelのデータとして格納します。マインクラフトの座標軸との対応関係が取れるように、Excel表の横軸方向をマインクラフトのx軸、縦軸方向をz軸と考えて、座標(x,z)での高さの情報をセルに記録します。また、土や石など地形を構成するブロックの種類については、セルの背景色で表現します。簡単なサンプルとして作った地形データが以下の図です。

　![sample.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/414ab7b8-5c99-15b0-9412-7daadf4ed25d.jpeg)

　灰色は石ブロック（STONE）、茶色は土ブロック（DIRT）、緑は草ブロック（GRASS）、アイボリーは砂ブロック（SAND）、というように、背景色とブロックの対応関係はあらかじめ決めておきます。セルの数値は地形の高さで、大きいほうが高い場所となります。マインクラフトの場合、ブロックを設置可能な高さは既定で-63～319となっているので、その範囲に収まるように高さを調整する必要があります。

## 地形生成コマンドの用意
　Excelの地形データから、実際に地形を生成するコマンドを用意します。ファイルから読み取る内容は以下の通りです。
 - セルの位置からx座標とz座標を決める
 - セルの値からy座標を決める
 - セルの背景色から、設置するブロックを決める

Excelファイルの特定のセル(i,j)から値を読み取る部分の処理は、PowerShellでは以下のようナコードになります。

```powershell
#Excel起動、ファイルオープン
$Excel = New-Object -ComObject Excel.Application 
$Excel.Visible = $false 
$Workbook = $Excel.Workbooks.Open("<file_path>")

#セル(i,j)の値の格納
$Value = $Worksheet.Cells.Item($i,$j).Value()

#ファイルクローズ、Excel終了
$Workbook.Close()
$Excel.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($Excel)
```

セル(i,j)の位置、値をマイクラの(x,y,z)座標に変換する部分の処理は以下のようになります。原点ではなく、特定の座標を起点に変換したい場合には、起点となる座標の値を加算するとよいでしょう。

```
$x = $j - 1
$y = $Value
$z = $i - 1
```

また、セル(i,j)の背景色の読み取りは以下のような処理になります。[^2]

```powershell
Add-Type -AssemblyName System.Drawing

$Color = $Worksheet.Cells.Item($i,$j).Interior.Color
$Color = ([System.Drawing.ColorTranslator]::FromOle($Color)).Name
```

以下は、前のセクションの地形サンプルのExcelファイルを読み取って、座標や設置ブロックを出力すコードサンプルです

```powershell

Add-Type -AssemblyName System.Drawing
$Excel = New-Object -ComObject Excel.Application 
$Excel.Visible = $false 
$Workbook = $Excel.Workbooks.Open("<path_to_sample.xlsx>")
$Worksheet = $Workbook.Sheets.Item(1)

for ($i = 1; $i -le 8; $i++) {
    for ($j = 1; $j -le 9; $j++) {
        $Value = $Worksheet.Cells.Item($i,$j).Value()

        $x = $x_base + $j - 1
        $y = $y_base + $Value
        $z = $z_base + $i - 1

        $Color = $Worksheet.Cells.Item($i,$j).Interior.Color
        $Color = ([System.Drawing.ColorTranslator]::FromOle($Color)).Name
        if ($Color -eq "ff92d050") {
            $block = "GRASS"
        } elseif ($Color -eq "ffffffcc") {
            $block = "SAND"
        } elseif ($Color -eq "ffcc9900") {
            $block = "DIRT"
        } elseif ($Color -eq "ffa6a6a6") {
            $block = "STONE"
        } else {
            $block = $Color
        }
        echo "x: $x y: $y z: $z Background Color: $Color Block: $block"
    }
}

$Workbook.Close()
$Excel.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($Excel)
Remove-Variable Excel
```

コードの実行結果は以下のようになりました。
<details><summary>実行結果</summary>

```
x: 0 y: 3 z: 0 Background Color: ffcc9900 Block: DIRT
x: 1 y: 3 z: 0 Background Color: ffcc9900 Block: DIRT
x: 2 y: 3 z: 0 Background Color: ffcc9900 Block: DIRT
x: 3 y: 3 z: 0 Background Color: ffcc9900 Block: DIRT
x: 4 y: 3 z: 0 Background Color: ffcc9900 Block: DIRT
x: 5 y: 3 z: 0 Background Color: ffcc9900 Block: DIRT
x: 6 y: 4 z: 0 Background Color: ffa6a6a6 Block: STONE
x: 7 y: 4 z: 0 Background Color: ffa6a6a6 Block: STONE
x: 8 y: 4 z: 0 Background Color: ffa6a6a6 Block: STONE
x: 0 y: 3 z: 1 Background Color: ffcc9900 Block: DIRT
x: 1 y: 2 z: 1 Background Color: ffcc9900 Block: DIRT
x: 2 y: 2 z: 1 Background Color: ffcc9900 Block: DIRT
x: 3 y: 2 z: 1 Background Color: ffcc9900 Block: DIRT
x: 4 y: 2 z: 1 Background Color: ffcc9900 Block: DIRT
x: 5 y: 3 z: 1 Background Color: ffcc9900 Block: DIRT
x: 6 y: 3 z: 1 Background Color: ffcc9900 Block: DIRT
x: 7 y: 3 z: 1 Background Color: ffcc9900 Block: DIRT
x: 8 y: 3 z: 1 Background Color: ffcc9900 Block: DIRT
x: 0 y: 2 z: 2 Background Color: ffcc9900 Block: DIRT
x: 1 y: 2 z: 2 Background Color: ffcc9900 Block: DIRT
x: 2 y: 1 z: 2 Background Color: ff92d050 Block: GRASS
x: 3 y: 1 z: 2 Background Color: ff92d050 Block: GRASS
x: 4 y: 2 z: 2 Background Color: ffcc9900 Block: DIRT
x: 5 y: 2 z: 2 Background Color: ffcc9900 Block: DIRT
x: 6 y: 2 z: 2 Background Color: ffcc9900 Block: DIRT
x: 7 y: 2 z: 2 Background Color: ffcc9900 Block: DIRT
x: 8 y: 2 z: 2 Background Color: ffcc9900 Block: DIRT
x: 0 y: 1 z: 3 Background Color: ff92d050 Block: GRASS
x: 1 y: 1 z: 3 Background Color: ff92d050 Block: GRASS
x: 2 y: 1 z: 3 Background Color: ff92d050 Block: GRASS
x: 3 y: 1 z: 3 Background Color: ff92d050 Block: GRASS
x: 4 y: 1 z: 3 Background Color: ff92d050 Block: GRASS
x: 5 y: 1 z: 3 Background Color: ff92d050 Block: GRASS
x: 6 y: 1 z: 3 Background Color: ff92d050 Block: GRASS
x: 7 y: 2 z: 3 Background Color: ffcc9900 Block: DIRT
x: 8 y: 2 z: 3 Background Color: ffcc9900 Block: DIRT
x: 0 y: 1 z: 4 Background Color: ffffffcc Block: SAND
x: 1 y: 1 z: 4 Background Color: ffffffcc Block: SAND
x: 2 y: 1 z: 4 Background Color: ffffffcc Block: SAND
x: 3 y: 1 z: 4 Background Color: ffffffcc Block: SAND
x: 4 y: 1 z: 4 Background Color: ffffffcc Block: SAND
x: 5 y: 1 z: 4 Background Color: ff92d050 Block: GRASS
x: 6 y: 1 z: 4 Background Color: ff92d050 Block: GRASS
x: 7 y: 1 z: 4 Background Color: ff92d050 Block: GRASS
x: 8 y: 1 z: 4 Background Color: ff92d050 Block: GRASS
x: 0 y: 0 z: 5 Background Color: ffffffcc Block: SAND
x: 1 y: 0 z: 5 Background Color: ffffffcc Block: SAND
x: 2 y: 0 z: 5 Background Color: ffffffcc Block: SAND
x: 3 y: 0 z: 5 Background Color: ffffffcc Block: SAND
x: 4 y: 1 z: 5 Background Color: ffffffcc Block: SAND
x: 5 y: 1 z: 5 Background Color: ffffffcc Block: SAND
x: 6 y: 1 z: 5 Background Color: ff92d050 Block: GRASS
x: 7 y: 1 z: 5 Background Color: ff92d050 Block: GRASS
x: 8 y: 1 z: 5 Background Color: ff92d050 Block: GRASS
x: 0 y: -1 z: 6 Background Color: ffffffcc Block: SAND
x: 1 y: -1 z: 6 Background Color: ffffffcc Block: SAND
x: 2 y: -1 z: 6 Background Color: ffffffcc Block: SAND
x: 3 y: 0 z: 6 Background Color: ffffffcc Block: SAND
x: 4 y: 0 z: 6 Background Color: ffffffcc Block: SAND
x: 5 y: 1 z: 6 Background Color: ffffffcc Block: SAND
x: 6 y: 1 z: 6 Background Color: ff92d050 Block: GRASS
x: 7 y: 1 z: 6 Background Color: ff92d050 Block: GRASS
x: 8 y: 1 z: 6 Background Color: ff92d050 Block: GRASS
x: 0 y: -1 z: 7 Background Color: ffffffcc Block: SAND
x: 1 y: -1 z: 7 Background Color: ffffffcc Block: SAND
x: 2 y: -1 z: 7 Background Color: ffffffcc Block: SAND
x: 3 y: -1 z: 7 Background Color: ffffffcc Block: SAND
x: 4 y: 0 z: 7 Background Color: ffffffcc Block: SAND
x: 5 y: 1 z: 7 Background Color: ffffffcc Block: SAND
x: 6 y: 1 z: 7 Background Color: ff92d050 Block: GRASS
x: 7 y: 1 z: 7 Background Color: ff92d050 Block: GRASS
x: 8 y: 1 z: 7 Background Color: ff92d050 Block: GRASS
```
</details>
　座標と設置ブロックを特定できれば、マインクラフト側のコマンド・コードで地形を生成することができるはずです。ブロック単体の設置は`setblock`コマンドで実行可能ですが、隣接するブロックとの高低差が2つ以上ある場合には、空隙が生まれてしまうため、`fill`コマンドで一定の高さ(y_bottom)から上を対象ブロックで埋める形にします。

 ```
/fill <x> <y_bottom> <z> <x> <y> <z> <block> 
 ```

　地形の生成対象範囲内のすべての(x,z)で、上記コマンドを実行することで地形の再現が可能となります。ただ、(x,z)に対するブロック設置を1つずつ全量繰り返しで実行すると、非常に時間がかかるため、隣接するブロックの高さが同じ場合には一つのfillコマンドにまとめるなど、ほどほどに効率化します。

 ## コマンドの一括投入（コードビルダー）
　マインクラフトのコマンドラインは、一度に一つのコマンドしか投入できません。今回地形を生成するコマンドは非常に大量になるので、Education Editionのコードビルダーを使って、コマンドを一括投入します。コードビルダーではコードの実行に使用する言語を選べますが、今回はJavaScriptを選択します。JavaScriptでfillコマンドに相当する処理を実行する際の構文は、以下のようになります。

```javascript
player.onChat("run", function () {
blocks.fill(<block>,world(<x>, <y_bottom>, <z>),world(<x>, <y>, <z>),FillOperation.Replace)
})
```
この例では、プレイヤーがマイクラのチャット上で`run`を入力したときにfillコマンドに相当する処理が実行されます。`blocks.fill()`の処理を複数行書き込んでおくことで、fillをまとめて実行できます。

# アレフガルド（の一部）を生成する
　ドラゴンクエストビルダーズはメルキドの跡地から始まります。これまでに述べた方法を使って、メルキド跡地周辺の132ブロックx90ブロック分の地形をマインクラフト上に生成してみました。[^3]
 
![2024-03-24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/a43eade3-dedd-efd6-3c24-fe93dd64c371.png)

　ダイヤモンドブロックとなっている30ブロックx30ブロックのエリアがメルキド跡地です。FC版のメルキドはちょうど30ブロックx30ブロックだったので、人や建物の配置をそのまま再現すれば、ちょうど収まりきるかもしれません。このあたりの村の再現については、次回以降に検討してみようと思います。

　

[^1]:リメイク版からはゆうてい・みやおう・きむこうがいなくなっていたりもするので…
[^2]:私はCopilot for Microsoft365を使っているのですが、これらのExcel処理については、Copilotに問い合わせることで、実際に動くコードサンプルを提示してもらうことができました。
[^3]:地形情報のマッピングメモに使ったノートが、30x44の5mm方眼だったので、ノート9ページ分の地形を生成したことになります。