MinecraftのModdingで必要なjsonを生成するライブラリを作った

Minecraft1.8からItemModelやBlockModelのjsonを書く必要が出てきて面倒じゃないですか?
僕は見た目の部分をコードから排除するのはすごい良いと思うんだけど、使い回しができずにファイルを複製してひたすら増えていくのはどうかと思うんです。

そこでテンプレートをベースにコンパイル時に生成するライブラリを作りました。
コードはこんなかんじになります。

public class BlockSample {

    @BlockModel(name = "block1", args = {"blocks/brick", "blocks/clay"}, template = "sample:block_base")
    @BlockState(name = "block1", args = {"sample:block1"}, template = "sample:state_base")
    @ItemModel(name = "block1", args = {"sample:block/block1"}, template = "sample:item_block_base")
    @Languages({
            @Language(value = "サンプルブロック1", name = "sampleBlock1", lang = LangType.JA_JP, target = LangTarget.BLOCK),
            @Language(value = "Sample Block 1", name = "sampleBlock1", lang = LangType.EN_US, target = LangTarget.BLOCK)
    })
    public static final Block block1 = new SimpleBlock();
    @BlockModel(name = "block2", args = {"blocks/clay", "blocks/brick"}, template = "sample:block_base")
    @BlockState(name = "block2", args = {"sample:block2"}, template = "sample:state_base")
    @ItemModel(name = "block2", args = {"sample:block/block2"}, template = "sample:item_block_base")
    @Languages({
            @Language(value = "サンプルブロック2", name = "sampleBlock2", lang = LangType.JA_JP, target = LangTarget.BLOCK),
            @Language(value = "Sample Block 2", name = "sampleBlock2", lang = LangType.EN_US, target = LangTarget.BLOCK)
    })
    public static final Block block2 = new SimpleBlock();

    public static void init() {
        CoreProxy.proxy.registerBlock(block1, "block1");
        CoreProxy.proxy.registerBlock(block2, "block2");
    }
}

@BlockModelテンプレートはこんな感じ

{
  "parent": "block/cube",
  "textures": {
    "particle": "{1}",
    "down": "{0}",
    "up": "{0}",
    "north": "{1}",
    "south": "{1}",
    "west": "{1}",
    "east": "{1}"
  }
}

テンプレートの{index}は@BlockModelのargsの各インデックスにバインドされています。
これで同じテンプレートからテクスチャだけ違うBlockModelのjsonが生成されます。
同じように@ItemModelと@BlockStateも利用できます。

次に@Languageはlangファイルへの書き込みを行います。
1つのアイテムやブロックに複数の翻訳を指定したい場合は@Languagesに配列で@Languageアノテーションを入れます。
valueに翻訳後の値
nameにunlocalizedName
langに翻訳言語
targetに翻訳のタイプを指定します。
コンパイル時にアノテーションが処理されてlangが生成されます。

次に必須となるアノテーションを紹介

@Mod(modid = SampleMod.MODID, version = SampleMod.VERSION)
@ResourcesDomain("sample")
@ModID(SampleMod.MODID)
public class SampleMod
{
    public static final String MODID = "sample";
    public static final String VERSION = "1.0";
    
    @EventHandler
    public void init(FMLInitializationEvent event)
    {
        ItemSample.init();
        BlockSample.init();
    }
}

これはエントリポイントのクラスですが、クラスに付けられている@ResourcesDomeinと@ModIDがこのライブラリのアノテーションです。
@ResourcesDomeinは生成されるファイルのドメインを指定します。
多重定義はできません。これが使用されると各リソース生成用アノテーション(@BlockModelとか@ItemModel)のdomeinのデフォルト値として利用されます。
複数のドメインを使いたい場合は1つだけ宣言しておき、その他のドメインはdomein要素を指定してください。

@ModIDはModIDを指定します。
これは多重定義不可で、必ず指定する必要があります。

この2つのアノテーションはエントリポイントとなるクラスに置くのが良いと思います。

最後にこのライブラリの利用方法。
ビルド済みのjarを用意して{projectDir}/libs/MinecraftForgeAnnotations-1.0.jarのように配置します。
build.gradleに以下を追加

//依存関係にMinecraftForgeAnnotationsを追加
dependencies {
    compile files('libs/MinecraftForgeAnnotations-1.0.jar')
}

compileJava {
    //アノテーションプロセッサを指定
    options.compilerArgs += ['-processor', 'net.orekyuu.minecraftanotations.apt.MinecraftAnnotationProcessor', "-Adir=" + file('templates')]
    doLast {
        //コンパイルが終了したらアセットを適切な位置へコピー
        tasks.copyAssets.execute()
    }
}

task copyAssets(type: Copy) {
    from 'build/classes/main/assets/'
    into 'out/production/Minecraft/assets/'
}

-Adirオプションではテンプレートの配置ディレクトリを指定しています。

テンプレートはtemplatesフォルダに配置してください。
動く形になっているサンプルも用意しておいたので良かったら確認して下さい。
サンプルプロジェクト
MinecraftForgeAnnotationsのリポジトリ

バグ報告や要望などはリポジトリのIssueにお願いします。
プルリクエストも待ってます。

APTでModdingわいわい

by orekyuu 0 Comments

1.8のもぢんぐでは、アイテムの追加でjson書かないといけないみたい。
描画の設定を記述するんだけど、使い回しが聞かないのが辛い。
実際書いたjsonだけど、基本的にlayer0の値しか変わらない。
これだけのためにコピペしたりするの面倒じゃない?

{
  "parent": "builtin/generated",
  "textures": {
    "layer0": "sweetmod:items/green_candy_cane" ここしか変更しないのに!
  },
  "display": {
    "thirdperson": {
      "rotation": [0, 90, -35],
      "translation": [0, 1.25, -3.5],
      "scale": [0.85, 0.85, 0.85]
    },
    "firstperson": {
      "rotation": [0, -135, 25],
      "translation": [0, 4, 2],
      "scale": [1.7, 1.7, 1.7]
    }
  }
}

というわけでAnnotationProcessorを作ってみた。

方法については以下を参考にするとわかりやすいと思います。
「Java SE 6完全攻略」第94回 アノテーションを処理する その1
「Java SE 6完全攻略」第95回 アノテーションを処理する その2
「Java SE 6完全攻略」第96回 アノテーションを処理する その3
「Java SE 6完全攻略」第97回 アノテーションを処理する その4
「Java SE 6完全攻略」第98回 アノテーションを処理する その5
「Java SE 6完全攻略」第99回 アノテーションを処理する その6
「Java SE 6完全攻略」第100回 アノテーションを処理する その7

結果
アイテム生成はこんな感じになりました

@Languages({
    @Language(langType = LangType.EN_US, key = "item.RedCandyCane.name", name = "RedCandyCane"),
    @Language(langType = LangType.JA_JP, key = "item.RedCandyCane.name", name = "赤いキャンディーケイン")
})
@ItemModel(itemName = "RedCandyCane", textures = "sweetmod:items/red_candy_cane")
public static ItemCandyCane redCandyCane;

langファイルについても面倒なので一緒にAPTで処理できるようにしてみました。

コンパイルした時のファイルの出力先が/build/classes/main/の中になっていたので、コンパイルタスクが終わったら、ファイルを/out/production/{プロジェクト名}/にコピーするタスクをbuild.gradleに書いておしまい。

使ってみた感じかなり便利。AnnotationProcessorが入ったjarはMODのリポジトリのlibsフォルダに入っているので良かったら使ってみてください。

リポジトリ

おまけ
無題

MinecraftのModding環境の作り方メモ

ここからforgeのsrcをダウンロード

forgeを解凍してコンソールを開く
gradlew setupdev setupdecomp
gradlew idea
IDEAを起動してプロジェクトを開く
build.gradleのversion group archivesBaseNameをそれぞれ適当なものに変更して終了
Forgeのバージョン10.12.1.1060現在JDK8は使えないので注意

Modを配布するためのJarを作成するためには、IDEAでTerminalを開き
gradlew build
ProjectName/build/libs/にjarが作成されます