MinecraftのblockstateファイルのJSON構造が気に食わないので、代替構造を作った
公開日:
最終更新日:
はじめに
実際、リソースパックの仕組みには無理があります。
「Mojangなんとかしろ」案件なのですがなんともならなそうなので、自力でなんとかすることにしました。
何をなんとかしたいのか
Minecraft Java Editionではリソースパックによってゲーム内のアイテムやブロックの見た目をある程度自由に改変できます。ブロックの見た目の決定では、ブロックが持つ状態(blockstate)に応じてモデル(model)を選ぶようになっていますが、これに関してもリソースパックでの改変が可能です。しかしながらその改変はとてつもなく面倒なものとなってしまっています。というのも、ブロックの状態に応じたモデルの定義はブロック毎に用意されたJSONファイルで行うのですが、このJSONの構造が非力で、複雑なことをしようとすると途端に面倒になってしまうのです。
例を挙げましょう。Minecraftには1.20から、Pink Petalsという装飾ブロックが追加されます。これはCherry Groveバイオームに生成されるもので、「桜の絨毯」っぽいものの厳密にはそうではない植物です。このブロックには東西南北の方向と、4つの成長段階があります。つまり16通りの見た目を取ります。これは次のようなブロック状態ファイル(blockstates/pink_petals.json
)によって定義されています。
{
"multipart": [
{
"apply": { "model": "minecraft:block/pink_petals_1" },
"when": { "facing": "north", "flower_amount": "1|2|3|4" }
},
{
"apply": { "model": "minecraft:block/pink_petals_1", "y": 90 },
"when": { "facing": "east", "flower_amount": "1|2|3|4" }
},
{
"apply": { "model": "minecraft:block/pink_petals_1", "y": 180 },
"when": { "facing": "south", "flower_amount": "1|2|3|4" }
},
{
"apply": { "model": "minecraft:block/pink_petals_1", "y": 270 },
"when": { "facing": "west", "flower_amount": "1|2|3|4" }
},
{
"apply": { "model": "minecraft:block/pink_petals_2" },
"when": { "facing": "north", "flower_amount": "2|3|4" }
},
// 以下略
]
}
16通りの状態をそれぞれ定義し、multipart
で同じ座標に重ねるようなことをしています。……面倒じゃありませんか? 「4つの成長段階について事前に定義した1つのオブジェクト」と「それを参照しつつ4つの方向を定義する4つのオブジェクト」というような書き方はできないのでしょうか? ……残念ながらできません。どうやらMojangはこういった需要を見越した仕様にはしていないようです。
自力でなんとかする
であれば自力でなんとかするまでです。そのJSONファイルを直接編集するのではなく、書きやすいオレオレ構造なJSONをプログラムで変換するようにしてしまえばいいのです。
そうしてできたのがgithub.com/okayurisotto/alt-blockstate
です。
使っている技術
私が唯一扱える言語であるTypeScriptを今回も使うために、Denoにご登場頂きました。コマンドラインからうまく使えるように、Cliffyライブラリにもご協力頂きました。
オレオレJSON構造について
JSONファイルの編集の際にエディタ補完がされるようにJSON Schemaを書きましたので、JSON Schemaを読める人は/schema.json
を読んでください。また、/samples/*.json
が例です。例を読むだけでなんとなく雰囲気はわかるかもしれません。TypeScriptでの定義は/types.ts
にあります。
簡単にまとめると、「事前定義したオブジェクトを参照する機能」と「モデル全体の回転などについての定義を継承する機能」によって、書きやすくメンテナンスしやすい構造が実現されました。
オレオレJSON構造には、6種類のモデル定義オブジェクト形式があります。
1つ目はroot
オブジェクトです。このオブジェクトのmodel
プロパティでそのブロックに対するモデルを記述します。refs
プロパティも持ち、これは参照機能で使われます。
2つ目はref
オブジェクトです。このオブジェクトはref
プロパティで、事前にroot
オブジェクトのrefs
プロパティで定義していたオブジェクトを参照します。ref
オブジェクトがさらに別のオブジェクトを参照することも可能な作りになっていると思われますが試していません。
3つ目はnormal
オブジェクトです。このオブジェクトが、model
プロパティによってモデルファイルへのリンク文字列を持ちます。
4つ目はcondition
オブジェクトです。このオブジェクトはwhen
プロパティとmodel
プロパティを持ちます。役割としては特に目新しいものはありません。従来のJSON構造のwhen
と同じです。
5つ目はallOf
オブジェクトです。このオブジェクトを使うと、models
プロパティで指定した複数のモデル定義オブジェクトすべてを適用することができます。
6つ目はoneOf
オブジェクトです。これは土ブロックなどでやっているような「複数のモデルからランダムに選ぶ」というような動作をするときに使うものです。
これらを組み合わせることで、うまくモデルを定義していきます。
具体例
pink_petals.json
を例に、実際の定義方法についてまとめます。
{
"type": "root",
"options": { "y": { "type": "+", "value": 90 } }, // 回転の基準となる値を事前に定義しておく(初期値0に90を足すことで90を定義)
"model": {
"type": "allOf", // すべてを適用するが実際に使われるかは状態次第
"models": [
// conditionオブジェクトを使ってそれぞれの方角のモデル(計4つ)を定義する
{
"type": "condition",
"when": { "facing": ["north"] },
"options": { "y": { "type": "*", "value": 0 } }, // 事前定義した値とかけ算をすることで、具体的にどの程度回転されるのかが求まる
"model": { "type": "ref", "ref": "flower_amount" } // 参照している
},
{
"type": "condition",
"when": { "facing": ["east"] },
"options": { "y": { "type": "*", "value": 1 } }, // ここは90
"model": { "type": "ref", "ref": "flower_amount" }
},
{
"type": "condition",
"when": { "facing": ["south"] },
"options": { "y": { "type": "*", "value": 2 } }, // ここは180
"model": { "type": "ref", "ref": "flower_amount" }
},
{
"type": "condition",
"when": { "facing": ["west"] },
"options": { "y": { "type": "*", "value": 3 } }, // ここは270
"model": { "type": "ref", "ref": "flower_amount" }
}
]
},
"refs": {
"flower_amount": { // これが参照される
"type": "allOf", // すべてを適用するが実際に使われるかは状態次第
"models": [
{
"type": "condition",
"when": { "flower_amount": ["1", "2", "3", "4"] }, // 配列であることに注意
"model": {
"type": "normal",
"model": "minecraft:block/pink_petals_1"
}
},
{
"type": "condition",
"when": { "flower_amount": ["2", "3", "4"] },
"model": {
"type": "normal",
"model": "minecraft:block/pink_petals_2"
}
},
{
"type": "condition",
"when": { "flower_amount": ["3", "4"] },
"model": {
"type": "normal",
"model": "minecraft:block/pink_petals_3"
}
},
{
"type": "condition",
"when": { "flower_amount": ["4"] },
"model": {
"type": "normal",
"model": "minecraft:block/pink_petals_4"
}
}
]
}
}
}
この他にも、モデルを名前で参照するときに使えるテンプレート文字列機能(variables
)があります。詳しくはsamples/oak_door.json
を見てください。
使い方
Denoをインストールした上で、下のように実行すると、従来のJSON構造に変換されて標準出力に出力されます。
deno run --allow-read main.ts path/to/file.json
おわりに
Mojangなんとかしなくていいよ。もう自力でなんとかしたから。