--- title: "統合版マイクラで 1100 万ブロックのドット絵を「コマンド 1 回」で建てるまで — tick.json と先読みで組んだ配置パイプライン" description: "統合版(Bedrock)のマインクラフトで、約 1100 万ブロックのドット絵をコマンド 1 回で自動建築するまでの記録。tick.json とスコアボードでタイマーを組み、tickingarea の上限(1 ワールド 10 領域・1 領域 100 チャンク)の中で 3 つ先まで先読みする。「そんな関数はない」と言われて関数が丸ごと消える 3 つの原因(名前の #、片側だけの範囲、min_engine_version)も。「schedule は無い」と思い込んでいた反省つき。" url: https://ryuuneko.com/blog/minecraft-bedrock-auto-build published: 2026-09-12 05:49:50.169104 updated: 2026-09-12 05:49:50.169104 --- 前に「[画像をマインクラフトのドット絵にして建てる](/blog/minecraft-build-tools-dot-geoglyph)」という記事で、ドット絵ツール(/dot/)の出力に「自動建築」があると書きました。Java 版はデータパックを入れて `/function dot:install` を打つだけで、1 tick に 1 タイルずつ勝手に建っていきます。 その記事の中で、統合版(Bedrock)については「導入の流れが異なります」の一文で済ませました。実はその一文の裏で、Java 版と同じやり方が通らなくて作り直しています。この記事はその作り直しの話です。マイクラのコマンドを少し触ったことがあれば読めるように書きます。 --- ## まず、Java 版はなぜ簡単だったか — schedule と forceload Java 版のデータパックには `schedule` という命令があります。「この関数を N tick 後に実行して」と予約できる。なので自動建築は、 1. タイル 1 枚ぶんのブロックを置く 2. 次のタイルを `schedule` で 1 tick 後に予約する 3. 予約が来たら 1 に戻る という素直な連鎖で書けます。1 tick に置ける命令数には上限がありますが(既定 65,536)、タイルを 192×192 に収めれば足ります。 チャンクの読み込みも `forceload` で先に指定できるので、遠くの領域でも「置いたつもりで置けていない」が起きません。 --- ## 統合版で同じことをすると、何が起きるか — 関数の中は全部同じ tick で走る 統合版にも関数(mcfunction)はあります。ただし**関数の中のコマンドは全部同じ tick で走る**。1 回の関数呼び出しで実行できるコマンドは最大 10,000 個で、それ以上は切れます。 最初に試したのは、`/fill` を並べた関数を順に呼ぶ方式でした。同じ tick に全部走って、途中で止まる。しかも止まった理由がゲーム内に出ないので、「途中まで建って、その先が無い」という結果だけが残ります。 ### ここで 1 つ、私の思い込みを先に書いておきます 作っていた当時、私は「統合版には `schedule` が無い」と思って設計しました。**これは間違いです。** 現在の統合版には `/schedule delay add`(指定 tick 後に関数を実行)が 1.21.50 で正式に追加されていて、さらに領域の読み込みが終わった時点で関数を呼ぶ `schedule on_area_loaded` まであります。Java 版に近いことが普通にできます。 では、この記事の方式に意味が無いかというと、そうでもありません。このツールが出すパックは**互換下限を 1.21.0 に置いている**ので、1.21.50 より前の環境では `/schedule` は使えません。以下は「`/schedule` に頼らずに同じことをやる」方法として読んでください。私が知らずに遠回りした、という事実は事実として残しますが。 --- ## tick.json とスコアボードで、自分でタイマーを作る `/schedule` を使わずに「時間をまたいで動く」ための足場が、`tick.json` です。ビヘイビアパックにこのファイルを置くと、指定した関数を**毎 tick 自動で実行**してくれます。これと**スコアボード**(数値を持てる仕組み)を組み合わせると、タイマーが作れます。 考え方はこうです。 - 毎 tick 呼ばれる関数が、スコアボードの `timer` を 1 増やす - `timer` が 16 になったら(0.8 秒)、`timer` を 0 に戻して `beat` を 1 増やす - `beat` が N のとき、N 番目の領域を置く関数を呼ぶ 「N 番目のときだけ呼ぶ」は `execute if score beat matches N run function dot/load/lN` のように書けます。領域の数だけこの行を並べた「振り分け関数」を生成しておけば、beat が進むたびに次の領域が置かれる。これで、関数の中から予約する代わりに、**外から毎 tick 叩いて自分で数える**形になります。 `/schedule delay add` が使える版なら、予約の連鎖でも書けたはずです。それでも結果的にこの方式でよかったと思っているのは、後で出てくる進捗表示・停止・再開が「毎 tick 動いている関数がある」ことを前提にしていて、予約の連鎖だとそれを別に作る必要があるからです。ただ、それは後付けの理由で、作ったときは単に知らなかった。 ブロックの配置自体は `.mcstructure` という構造ファイルを領域ごとに書き出し、`structure load` で一気に置きます。1 領域は 128×128 ブロック(64 チャンク)にしました。 ![毎 tick 呼ばれる関数が timer と beat を数え、beat ごとに「先読み・配置・解放」を 1 段ずつずらして進めるパイプラインの図](/blog/uploads/minecraft-bedrock-pipeline.svg) *▲ beat が 1 進むたびに、3 つ先の領域を読み込み開始、今の領域を配置、1 つ前の領域を解放。読み込み待ちが配置の裏に隠れる。* --- ## 読み込み待ちを「先読み」で隠す — tickingarea の上限と付き合う もう 1 つ、Java 版の `forceload` にあたるものが要ります。遠くの領域は読み込まれていないので、そこに `structure load` してもブロックが置かれません。統合版では `tickingarea` で「ここは常に読み込んでおいて」と指定できます。ただし 1 ワールドに最大 10 領域で、1 領域あたり最大 100 チャンクまで。全部を最初に指定することはできません。 最初は「領域を指定して、読み込みを待って、置いて、解放して、次」と**直列**に書きました。動きはするのですが、毎回読み込み待ちが挟まるので遅い。 なので**パイプライン**にしました。beat が N のとき、 - 領域 N+3 の `tickingarea` を追加(3 つ先を読み込み始める) - 領域 N を `structure load`(3 beat 前に読み込みを始めた領域なので、もう読める) - 領域 N-1 の `tickingarea` を削除(置き終わったので解放) これで、読み込み待ちは「3 beat 分の裏」に隠れて、1 領域あたり実質 16 tick で進みます。1 領域は 64 チャンクなので 100 チャンクの制限に収まり、同時に存在する `tickingarea` は 4 個前後なので、10 領域の制限にも収まる。実機で `/tickingarea list` を見ながら確認しましたが、ずっと `4/10` のままで溜まりません。 --- ## 「そんな関数はない」と言われる — 黙って弾かれる 3 つの罠 ここからが、この記事を書きたかった本当の理由です。統合版の関数は、**読めない行が 1 つでもあると、関数ごと登録されない**。エラーは出ず、`/function` の補完に名前が出てこないだけ。「そんな関数はない」と言われて、どの行が悪いのか分からない。 ### 罠 1: スコアボードの名前に `#` を使うと、関数ごと消える Java 版では `#run` のような `#` 始まりの名前を「表示されない仮のプレイヤー」として使う慣習があります。それを持ち込んだら、`execute if score #run …` を含む関数が丸ごと消えました。統合版では行頭の `#` はコメントとして OK ですが、名前の中の `#` は解析で弾かれるようです。`beat` `timer` `done` のような素の英字名に変えて解決。 ### 罠 2: `matches 5..` のような片側だけの範囲が読めないことがある 「beat が N 以上なら終了」を `matches N..` と書いたら終了判定が動かない。`matches N..2147483647` と上限を明示したら動きました。Java 版では当たり前に書ける書き方が、統合版では版によって読めない。 ### 罠 3: `min_engine_version` が低いと、新しい構文が全部「読めない行」になる `execute if score … run …` という構文は比較的新しいもので、パックの `min_engine_version` が低いと旧構文として解析され、失敗します。そして罠 1 と同じく関数ごと消える。**1.21.0 以上**に設定して解決。同じ理由で `/fill` のブロック指定も、古い「`concrete 7`」形式は 1.19.70 で廃止されているので「`gray_concrete`」の形で出す必要があります。 3 つとも症状は同じ「関数が無い」です。原因を切り分けるには、`/structure load dot:r0` を手で打って構造ファイル自体は読めるか、進捗表示の経過秒が増えるかで `tick.json` は動いているか、と**部品ごとに確かめる**しかありませんでした。1 行ずつ疑っては何度もパックを入れ直すより、先に「どこまでは生きているか」を確定させる方が早い。これは統合版に限らず、エラーが出ない壊れ方の共通の対処です。 --- ## 進捗バーと、停止・再開 毎 tick 動く関数があるなら、進捗表示もそこで出せます。置き終えた領域数 `done`、経過 tick `elapsed` から「配置中 123 / 689 経過 4分12秒」をタイトル欄に出しています。統合版の `scoreboard players operation` は割る数に定数を直接書けないので、`div20` `div60` という名前で 20 と 60 を持ったスコアを先に用意しておいて、それで割ります。完了時には所要時間つきで「完了」を出す。 停止(`dot/stop`)、再開(`dot/start`)、最初から(`dot/reset`)も同じ仕組みの上に乗っています。停止は「beat を進めるのをやめる」だけなので、途中の領域は壊れません。 --- ## 実機で 689 領域・約 1100 万ブロック 自宅の統合版で、689 領域・約 1100 万ブロックの画像を置き切りました。設定は領域 128 ブロック、16 tick 刻み、先読み 3。 ただ、大きくなるほど「段々重くなる」現象があって、最初は `tickingarea` が溜まっているのを疑いました。上に書いたとおりそれは違って、**置いたブロックの数とライティング(光の計算)が純粋に重い**だけでした。対策は刻みを 16 から 24 に伸ばして負荷をならすこと。それ以上の規模は、Java 版のデータパックか .schem / .litematic に倒す方が現実的です。ツールの出力画面では版を選べるようにしてあるので、無理に統合版で押し切る必要はありません。 --- ## おわりに Java 版で 1 日で動いたものが、統合版では「同じ tick で全部走る」「読めない行があると黙って消える」「そもそも schedule の有無を勘違いしていた」の 3 つで、作り直しになりました。出来上がったものは `/function dot/start` の 1 行で、使う側からは Java 版と同じに見えます。 前の記事で「ぜんぶ『でかいのを建てたい』から生まれた」と書きました。統合版の自動建築もその一つで、たぶんこのツールの中で一番、見えないところに手間がかかっています。 --- *※この記事は 2026 年 6 月時点の実装をもとにしています。統合版の関数の挙動はゲームの版で変わることがあり、ここに書いた罠も将来解消されているかもしれません。`/schedule` の件は、記事の下書きを読んだ人の指摘で直しました。*