はじめに
Part5で、海と森の生成を試みましたが、森の方に少し心残りがありました。コマンドで苗木を植えていくアプローチでは、
- 自然に成長させた場合、周囲の地形に対して大きく育ちすぎる
- 苗木を成長させるために、プレイヤーが苗木にある程度近づく必要がある
という2点をクリアするのが困難でした。今回は別のアプローチで森を生成し、上記2点の課題に対応しようと思います。
前回のアプローチで生成した森と、今回改良したアプローチで生成した森とを比較した画像が以下となります。
掲載 | 画像 |
---|---|
前回 | |
今回 |
前回と比べて、樹高が抑制され岩山などの周辺地形とのバランスが良くなりました。また、苗木からの成長問題を解消するため、成木の状態で森を生成できるようにプログラムを改修しています。成木が配置されていく様子を動画(5倍速)にまとめたものがこちらです。
今回の改善ポイント
樹高の制御
マインクラフト本編で植林場を作っている方々には割とおなじみの情報みたいなのですが、マインクラフトの樹木は、苗木の周辺の特定位置にあらかじめブロックを配置しておくことで、樹高や枝の伸び方をある程度制御できるそうです1 2。今回はオークの木を対象としましたが、今後別の地域でオーク以外の樹木を配置する場合にも応用できそうです。参照先のサイトでは、制御ブロックとして可視ブロックを使っていますが、今回は制御ブロックをあからさまに見せたくはないので、バリアブロックを制御ブロックとして使うことにします。
成木の配置
苗木を植えるというアプローチでは苗木を成木に成長させる工夫が必要になります。成長の促進方法としてよく用いられるのは骨粉ですが、骨粉はコマンドで直接的に扱うことができず、骨粉入りのディスペンサーなどを介して使用する必要がありそうでした。
これはさすがに効率が悪すぎるので、苗木からの成長はあきらめて、成木の状態の樹木を設置していく形に方針を見直すことにしました。一度、苗木から木を成長させておき、それを配置先に複製する作業を繰り返すことで森を形成するのです。複製する方法としてはclone
コマンドとstructure
コマンドの2種類が考えられますが、それぞれ特性が異なるので、いくつかの観点で両者を比較した上で、どちらを採用するかを決定しました。
比較観点1:複製可能な距離
clone
コマンドで複製を行う場合、複製元と複製先は基本的にプレイヤーの周辺の読み込まれているチャンクの範囲内である必要があります。今回のような比較的広い範囲を対象とする地形形成の場合、複製元の木がある座標と複製先の座標が離れすぎてしまい、双方をチャンクの読み込み範囲内に収めることが難しくなります。チャンクの読み込み範囲外にあるブロックを強制的に読み込ませるために、tickingarea
コマンドを使って、常時読み込みをかける領域(ティック領域)を指定することも可能ですが、ティック領域はワールドあたり10個までという制約がある3ため、むやみに使用することは避けておきたいところです。
structure
コマンドで複製を行う場合、複製元の領域をいったん以下の構文で一度メモリやディスク上に格納する必要があります4。
/structure save <structure_name> <from:x y z> <to:x y z> [saveMode: StructureSaveMode]
saveModeとして、memoryを指定した場合、指定した領域の情報はメモリに格納されます。diskを指定した場合、情報はディスク上のセーブデータ内に格納されます。メモリに格納された情報は、ワールドを閉じると消えてしまいますが、ディスクに格納された情報は、ワールドを閉じた後も保持され、再びワールドを開きなおしても使用することができます。
一度メモリやディスクに格納した領域は、以下の構文を用いて配置することが可能です。
/structure load <structure_name> <to:x y z>
cloneコマンドと大きく異なるのは、で指定する座標が、読み込まれているチャンクエリアの外側であっても実行可能であるという点です。プレイヤーの位置に依存せず、格納済みの構造を任意の位置に配置することができる点で、非常に使い勝手がよいといえます。
比較観点2:複製先の地形への影響
平坦な地面の上で成長した木を、起伏のある地形に複製する場合、木を配置する周辺の地形への影響を考慮する必要があります。複製元の木を含む直方体の領域を、複製先に単純に配置すると、重複する領域があった場合に複製先の地形を削り取ってしまう恐れがあります。
clone
コマンドには、maskModeというオプションがあります。このオプションでmaskedを指定した場合、複製元の領域内の空気ブロックは複製されません。このため、複製先の地形に多少の起伏があり、複製元となる直方体の領域と重なる部分があったとしても、地形を削り取ってしまうリスクを抑制することができます。
一方、structure
コマンドには、cloneにおけるmaskModeのようなオプションがありません。そのため、地形の削り取りを回避するためには、「幹」の部分と「枝葉」の部分で構造を分け、それぞれを複製するという対応をとる必要がありそうです。
比較観点3:複製のバリエーション
成木の複製で森を生成する場合、1本の成木を複製するだけでは、すべての木が同じ形となってしまい、森が単調になります。森に多少なりとも変化・多様性を持たせるために、複製にバリエーションを持たせることを考えます。最もシンプルなのは、複製元のデータを複数種類用意することです。
structure
コマンドの場合、複製時のオプションとして、元の構造を90度、180度、270度回転させた形での配置や、x軸、z軸に対して反転させた形で配置することが可能です。これにより1つの複製元データから複数種類のバリエーションを生み出すことが可能です。
clone
コマンドの場合、複製時に回転・反転により複製元の形を変化させることはできません。
比較まとめ
以上の比較の結果を簡単にまとめたものが以下の表です。
観点 | clone | structure |
---|---|---|
距離の制約 | × 読み込みチャンクエリア外は配置不可 |
〇 任意の座標に配置可能 |
地形への影響 | 〇 maskModeオプションで対応可能 |
△ 構造を「幹」と「枝葉」に分けて対応する必要あり |
複製のバリエーション | △ 回転・反転なし |
〇 回転・反転あり |
clone
コマンドはmaskModeを使った複製で地形への影響を抑えられるのが魅力的ですが、コマンドを実行できる距離の制約が厳しすぎるので、今回はstructure
コマンドを採用することにします。
実装
ここまで検討した内容を踏まえ、実際のワールド上に森を展開します。
複製元データの用意
今回は下図のように4種類のオークの成木を用意しました。木の直上や周辺に配置されている、通行禁止マークのようなブロックは、バリアブロックです。
それぞれの木に対して、幹の部分と枝葉の部分に分けてstructure save
コマンドで構造を保存します。今回は以下のように名前を付けています5。
structure_name | 説明 |
---|---|
oak1-1 | 1つ目のオークの幹 |
oak1-2 | 1つ目のオークの枝葉 |
oak2-1 | 2つ目のオークの幹 |
oak2-2 | 2つ目のオークの枝葉 |
oak3-1 | 3つ目のオークの幹 |
oak3-2 | 3つ目のオークの枝葉 |
oak4-1 | 4つ目のオークの幹 |
oak4-2 | 4つ目のオークの枝葉 |
配置時のロジック
苗木を植えた時と同様、配置先エリアに対して一定の確率で成木を置いていきます。苗木の時と違うのは、幹と枝葉の2段階で設置している点と、設置するときに回転・反転のオプションでバリエーションを持たせている点です。以下のコード(powershell)では、地形データを入れたExcelファイル6を読み込んで、x=1~c_max、z=1~r_maxの領域の中にstructureコマンドで木を配置するMakeCode Javasccript用のコードを生成しています。
Add-Type -AssemblyName System.Drawing
$Excel = New-Object -ComObject Excel.Application
$Excel.Visible = $false
$Workbook = $Excel.Workbooks.Open("<path_to_file>")
$Worksheet = $Workbook.Sheets.Item(1)
for ($i = 1; $i -le <r_max>; $i++) {
for ($j = 1; $j -le <c_max>; $j++) {
$Value = $Worksheet.Cells.Item($i,$j).Value()
$FontColor = $Worksheet.Cells.Item($i,$j).Font.Color()
# FontColorが255となっているエリアに木を配置していく
if($FontColor -eq 255){
# 木をはやすかどうかを抽選するための乱数
$Random = Get-Random -Minimum 0.0 -Maximum 63.0
if( $Random -lt 2.0 ){
#植える木のパターンを4種類から選ぶ
$Random_t = Get-Random -Minimum 0.0 -Maximum 4.0
if($Random_t -lt 1.0){
$tree1 = "oak1-1"
$tree2 = "oak1-2"
}elseif($Random_t -lt 2.0){
$tree1 = "oak2-1"
$tree2 = "oak2-2"
}elseif($Random_t -lt 3.0){
$tree1 = "oak3-1"
$tree2 = "oak3-2"
}else{
$tree1 = "oak4-1"
$tree2 = "oak4-2"
}
#Structureの回転有無の設定
$Random_r = Get-Random -Minimum 0.0 -Maximum 4.0
if($Random_r -lt 1.0){
$rotation = "0_degrees"
}elseif($Random_r -lt 2.0){
$rotation = "90_degrees"
}elseif($Random_r -lt 3.0){
$rotation = "180_degrees"
}else{
$rotation = "270_degrees"
}
#Structureの反転有無の設定
$Random_m = Get-Random -Minimum 0.0 -Maximum 2.0
if($Random_m -lt 1.0){
$mirror = "x"
}else{
$mirror = "none"
}
#木のStructureをloadする座標の設定
$x = $j - 1
$z = $i - 1
$y = $Value + 1
$x2 = $x - 2
$y2 = $y + 2
$z2 = $z - 2
#Structureの呼び出し(フィールドへの木の配置)
echo "mobs.execute(mobs.target(MY_AGENT),world($x,$y,$z),""structure load $tree1 $x $y $z"")"
echo "mobs.execute(mobs.target(MY_AGENT),world($x,$y,$z),""structure load $tree2 $x2 $y2 $z2 $rotation $mirror"")"
}
}
}
}
このPowershellスクリプトを用いて生成されるコードを用いて、メルキドの周辺に森を形成した結果が、冒頭の画像と動画です。
おわりに
今回はPart4で課題が残った森の生成部分を改良し、①樹木の高さを一定以下に抑えつつ、②苗木からの成長促進が不要な方法を検討し、森を生成しなおしました。
今回配置したオークの木は、成木になった時の対称性が高い(幹が常に中心にある)ので、「幹」と「枝葉」に構造を分けた場合でも、回転・反転処理をして配置することが比較的容易でした。横方向に枝が伸びるアカシアの木などを植える時には、幹が枝葉の中心にない可能性の方が高いので、回転・反転処理を行う時の幹と枝葉のつなぎ方は、よく計算してプログラミングする必要がありそうです。
-
https://minecraft.fandom.com/ja/wiki/%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89/tickingarea ↩
-
https://minecraft.fandom.com/ja/wiki/%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89/structure ↩
-
ちなみに、structureコマンドにはlistのオプションがないので、saveした領域は自分でちゃんとメモを残しておかないと、何をsaveしたのかわからなくなります… ↩