1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JAVAの勉強のためにマイクラMODを作ってみる(6)

Last updated at Posted at 2025-11-29

前回からの続き

前回は、塩にぎりのレシピを登録してみました。だんだんJAVAも慣れてきたような(?)気がしなくもない 気のせい です。わかるようになったという意味ではなく、まだまだドキュメントをコピペして作成してる状態だけど前より頭痛しなくなった的な?まだいまいち、クラス継承とオーバーライドがわからんのよな……

  • 原作小説
     → 親クラス
  • アニメ化した映画
     → 子クラス 原作を継承してエンディングを変更(オーバーライド)
  • 実写化したスピンオフ
     → 孫クラス アニメ化作品を継承して別の登場人物を主人公にする(オーバーライド)。さらに昔の映画監督のカメラワークを使って戦闘シーンを盛り上げる(監督クラスのstaticメソッドを使う)

みたいな感じ?🤔🤔🤔

とりあえず今回は、アイテムをいくつか作成して登録してみようかと思います。
作るのは、光源ブロックと装飾ブロック。光源ブロックは、比較的簡単らしい。装飾ブロック(盆栽)は、植木鉢みたいな機能で盆栽鉢に向かって苗木を右クリックしたら、モデルを変更するように作成。今後作りたいものとして、座椅子と障子なんだけど、かなり難易度が高そう。一応実装方法を確認だけしたので、何度と手順は下記の通り。

  • 光源ブロック(行燈) 難易度★
    光源ブロックの追加は、ブロッククラスの作成とモデルのjson作成などで比較的簡単らしい。普通にブロック追加+光源の設定すれば行けるかも?と予想。
  • 装飾ブロック(盆栽) 難易度★★
    植木鉢みたいに盆栽鉢に苗木持って右クリックで、盆栽を作れるようにしたい。ブロック作成に追加して、右クリックで苗木を植える処理、右クリックで苗木を回収する処理が必要。
  • 装飾ブロック(?)(座椅子) 難易度★★★
    この辺から実装難易度は中級くらいになる。通常のブロック追加以外に、乗れるエンティティの作成、右クリック時のエンティティ操作が必要(乗るとき、降りるとき)あと、サーバー・クライアント同期。座るエンティティとプレイヤーを乗せる、というのがサーバー側の処理で、見た目がクライアント側らしい。説明を聞いて、実装の予想がつかず。
  • 横スライドドア(2枚で両開きの障子) 難易度★★★★
    状態管理(BlockState)と、隣接処理・状態同期が必要。BlockState変更、隣接ブロックの同期がサーバー側。開閉アニメーション、音がクライアント側。ひぃ。むつかしそう……

今回の作業後画面

bonsai.gif

開発

0.その前に、フォルダ構成について整理

MODを作成していくと、階層が深くなっていってどこに何を保存するのか混乱するので、一旦再確認。

階層(折りたたんでます)
my-mod/
├── build.gradle
├── settings.gradle
├── gradle/
├── gradlew
├── gradlew.bat
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/
│   │   │        └── mymod/
│   │   │               ├── MyMod.java           ← メインMODクラス
│   │   │               ├── MyModClient.java     ← クライアントMODクラス
│   │   │               ├── item/
│   │   │               │   └── ModItems.java    ← アイテム登録
│   │   │               ├── block/
│   │   │               │   ├── BonsaiBlocks.java   ← 今回作った盆栽ブロック
│   │   │               │   └── ModBlocks.java     ← ブロック登録
│   │   │               └── blockentity/
│   │   │                   └── ・・・
│   │   ├── resources/
│   │   │   ├── fabric.mod.json
│   │   │   ├── data
│   │   │   │   └── mymod/
│   │   │   │        ├── loot_table/
│   │   │   │        │   └── blocks/     ← ドロップの設定(jsonファイルを入れる)
│   │   │   │        └────── recipe/    ← レシピ登録
│   │   │   └── assets/
│   │   │       └── mymod/
│   │   │           ├── blockstates/    ← ブロック状態の設定(jsonファイルを入れる)
│   │   │           ├── items/       ← アイテムのモデルを指定(jsonファイルを入れる)
│   │   │           ├── models/
│   │   │           │   ├── block/     ← Blockbenchで作ったブロックモデルなど(jsonファイルを入れる)
│   │   │           │   └── item/      ← アイテムのテクスチャを指定(jsonファイルを入れる)
│   │   │           ├── textures/
│   │   │           │   ├── block/
│   │   │           │   └── item/
│   │   │           └── lang/
│   │   │               └── en_us.json     ← 言語ファイル

1.光源ブロック

とりあえず、難易度が高くない順に追加していこうと思います。光源ブロックを作成するために追加するのは、以下の通り。赤字の箇所が今回作るフォルダとファイル。

  1. ブロッククラス作成とブロック登録 パッケージ/block/〇〇.java
  2. アイテムグループ登録 パッケージ/item/第4回で作成のアイテムグループ.java
  3. 言語のファイルに名前を追加 assets/自分のmodid/lang/
  4. モデルとテクスチャ assets/自分のmodid/models/block/〇〇.json
  5. blockstates追加 assets/自分のmodid/blockstates/〇〇.json
  6. ドロップの設定 data/自分のmodid/loot_table/blocks/

そのほか、そのツールでとれるのかの設定(data/minecraft/tags/block/mineable/
)、マイニングレベル(data/minecraft/tags/block/)の設定とかあるみたいだけど、とりあえずその辺、今回は無視して見た目だけ追加します。

① ブロッククラス作成とブロック登録

パッケージの下に「block」フォルダ作成、その中に「ModBlocks」という名前でブロックのクラスを作成します。

ModBlocks.java
public class ModBlocks {

    // ブロックを追加
    public static final Block OAK_LANTERN = register(
            "oak_lantern",
            settings -> new Block(settings
                    .strength(0.5f)           // 硬さ 手でも壊せるレベルに設定
                    .luminance(state -> 10)   // 光源レベル 0~15 時代劇っぽく10くらいにしています
                    .nonOpaque() // 透過部分を描画 これを忘れると隣接ブロックの境目が透明になります
            ),
            AbstractBlock.Settings.create().sounds(BlockSoundGroup.WOOD),
            true // ブロックをアイテム登録するかどうか
    );

    // Blockオブジェクトを返す register メソッド
    private static Block register(String name, Function<AbstractBlock.Settings, Block> blockFactory, AbstractBlock.Settings settings, boolean shouldRegisterItem) {

        // BlockのRegistryKey(登録キー)を作成
        RegistryKey<Block> blockKey = keyOfBlock(name);

        // Blockのインスタンスを作成
        Block block = blockFactory.apply(settings.registryKey(blockKey));

        // Blockをアイテム登録するかしないかを設定。
        //(作動中ピストンみたいにアイテム持たないブロックはfalseにする)
        if (shouldRegisterItem) {

            // "shouldRegisterItem" が true のときだけ
            // ブロックに対応するアイテムを作成
            RegistryKey<Item> itemKey = keyOfItem(name);

            BlockItem blockItem = new BlockItem(block, new Item.Settings().registryKey(itemKey).useBlockPrefixedTranslationKey());
            Registry.register(Registries.ITEM, itemKey, blockItem);
        }

        return Registry.register(Registries.BLOCK, blockKey, block);
    }

    private static RegistryKey<Block> keyOfBlock(String name) {
        return RegistryKey.of(RegistryKeys.BLOCK, Identifier.of(PonmaruMod.MOD_ID, name));
    }

    private static RegistryKey<Item> keyOfItem(String name) {
        return RegistryKey.of(RegistryKeys.ITEM, Identifier.of(PonmaruMod.MOD_ID, name));
    }

    public static void initialize() {
        // ログに出力して、Modが正しく初期化されたことを確認する
        PonmaruMod.LOGGER.info("ModBlocksクラス:ブロックの登録 " + PonmaruMod.MOD_ID);
    }

}

② アイテムグループ登録

前回作ったアイテムグループに今回のブロックを追加しておきます。

ModItemGroups.java
        // カスタムアイテムグループにアイテムを追加
        ItemGroupEvents.modifyEntriesEvent(PONMARU_GROUP_KEY).
                register(itemGroup -> {
                    itemGroup.add(ModItems.ONIGIRI_SHIO);
                    itemGroup.add(ModItems.ONIGIRI);
                    itemGroup.add(ModItems.ONIGIRI_UME);
                    itemGroup.add(ModItems.KIZUSHI);
                    itemGroup.add(ModItems.SANSYOKUDANGO); //アイテム「ModItems.」

                    itemGroup.add(ModBlocks.OAK_LANTERN); //今回はブロック「ModBlocks.」
                });

③ 言語のファイルに名前を追加

ja_jp.json
  "item.自分のmodid.onigiri": "おにぎり",      ← 前回まではアイテムだったので、「item.」
  "block.自分のmodid.oak_lantern": "オークの行燈" ← 今回はブロックなので、「block.」で登録する

④ モデルとテクスチャ

今回、光源ブロックは行燈の形にしたかったので、Blockbenchを使って3Dモデルを作成。ささっと簡単に作ってみました。
テクスチャを塗り塗りしてもいいんだけど、リソースパックを入れたときに、同じ世界観にしたいので、ブロックもテクスチャも枠の部分とライトの部分に分けて作成。Fileから「書き出し」→「Json Model」でマイクラ用のJSONを作成。
image.png
作成したJSONを assets/自分のmidid/models/block/ の中に名前を付けて入れます。JSONを開くと ↓ のような感じになっているので、親ブロックとかテクスチャの設定をバニラから持ってきます。

{
  "format_version": "1.9.0",
  "credit": "Made with Blockbench",
  "render_type": "cutout",
  "texture_size": [64, 64],
  "textures": {
    "particle": "minecraft:block/oak_planks",  ← パーティクルの設定(オークの板材)壊したとき飛び散るアレ
    "light": "minecraft:block/ochre_froglight_top", ← ライト部分のテクスチャ(フロッグライト(の上面))
    "waku": "minecraft:block/oak_planks"       ← 枠のテクスチャ(オークの板材)
  },
  "elements": [
		{
			"name": "light",
			"from": [4, 6.25, 4],
			"to": [12, 12.75, 12],

・・・以下省略

カスタムのアイテムグループに登録したり、は、他のアイテムで登録したときと同じように。MOD本体のクラスで、ブロッククラスの呼び出しも追加。

    @Override
    public void onInitialize() {
        ModItems.initialize(); // ModItemsクラスをロード
        ModBlocks.initialize(); // ModBlocksクラスをロード
        ModItemGroups.registerItemGroups(); // ModItemGroupsクラスをロード
    }

⑤ blockstates 追加

フォルダを作って、その中にjsonを入れます。まだドキュメントを読み込んでないので、サンプルをそのまま名前変えて登録。向きがあるブロックとか、成長するブロックとかは、色々設定するみたい。

oak_lantern.json
{
  "variants": {
    "": {
      "model": "自分のmodid:block/oak_lantern"
    }
  }
}

⑥ ドロップの設定

ここもドキュメントのまま名前を変えてとりあえず登録。ブロックが壊れた時と爆破された時に、1つドロップ。

oak_lantern.json
{
  "type": "minecraft:block",
  "pools": [
    {
      "rolls": 1,
      "entries": [
        {
          "type": "minecraft:item",
          "name": "自分のmodid:oak_lantern"
        }
      ],
      "conditions": [
        {
          "condition": "minecraft:survives_explosion"
        }
      ]
    }
  ]
}

⑦ 光源ブロック作成後マイクラ起動して動作確認

アイテムグループに登録されてるか、アイテム名出てるか、ちゃんと光ってるか、壊したときのパーティクル出た後アイテム化されて拾えるか、などを確認。ざざっと作ったので、色々と微修正したい所はあるけど、ブロック追加できました。
image.png

2.盆栽

植木鉢に花差すのもいいけど、盆栽作りたいよな。と思ったのでちょっと作ってみます。見た目変えるだけなら、リソースパックで実装したらいいのでは?と思わなくもないけど。😓
色々調べてみた結果、FlowerPotBlockを継承すると拡張性がなさそうなので、Block継承で独自の盆栽Block作成して、BlockEntityで右クリックしたときにアイテムを入れ替えできるように作ってみる。
作成するクラスは

  1. モデル
  2. 苗木の種類を設定
  3. BonsaiBlockクラス作成 本体。右クリックで苗木の出し入れの処理を書く
  4. モデルを切りかえられるようにいろいろ変える
  5. blockstatesの作成

① モデルの確認

まず、一旦ブロックとして登録して、テクスチャなどの表示の確認をしてみます。光源ブロック作成時と同じく、Blockbenchで作成。このまま普通に登録すると、テクスチャで透明にしたい部分が透明にならないので、クライアント側でブロックの一部を透明にする設定を追加する。fabricのwikiを確認すると、FabricAPIにBlockRenderLayerMapというクラスがあるので、それを利用。IDEからBlockRenderLayerMapクラスを見てみると、単体ブロックか、複数ブロックか、単体の流体ブロックか、複数の流体のブロックかのメソッドあり。今回は単体のブロックなのでputBlockにして、その場合渡すパラメータは「block」と 「layer」。なので、ブロックに自作の盆栽を指定し、レイヤーに透過を処理する「CUTOUT」を指定。
image.png
左:Blockbenchの3Dモデル
中:クライアントクラスOverrideなし
右:クライアントクラスOverride追記

Client.java
public class MyModClient implements ClientModInitializer {

    @Override
    public void onInitializeClient() {
        // ブロックの一部を透明にする
        BlockRenderLayerMap.putBlock(ModBlocks.BONSAI_POT, BlockRenderLayer.CUTOUT);
    }

}

② 苗木の種類を設定

持ってる苗木の種類によって結果を変えたいため、enumに苗木の種類を登録しておく

SaplingType.java
//苗木のタイプのEnum を書く
public enum SaplingType implements StringIdentifiable {

    CHERRY("cherry", Items.CHERRY_SAPLING), // 桜
    SPRUCE("spruce", Items.SPRUCE_SAPLING); // 松(マイクラのトウヒ)

    private final String name;
    private final Item item;

    SaplingType(String name, Item item) {
        this.name = name;
        this.item = item;
    }

    public Item getItem() {
        return this.item;
    }

    @Override
    public String asString() {
        return this.name;
    }

    // SaplingType に変換するメソッド
    public static SaplingType fromItem(Item item) {
        for (SaplingType type : values()) {
            if (type.item == item) {
                return type;
            }
        }
        return null;
    }
}

③ 盆栽ブロック本体

このカスタムブロック本体に、右クリックしたら、とかの処理を書いていく。
作ったあとで、ちょっと失敗してました。苗木を右クリックして盆栽にしたとき、持ってる苗木の数が減っていません!そのうち直す予定😅

BonsaiBlock.java
public class BonsaiBlock extends Block {

    // ここにプロパティを宣言
    public static final BooleanProperty EMPTY = BooleanProperty.of("empty");
    public static final EnumProperty<SaplingType> SAPLING = EnumProperty.of("sapling", SaplingType.class);

    // コンストラクタで設定を渡す
    public BonsaiBlock(AbstractBlock.Settings settings) {
        super(settings);

        // デフォルト状態を EMPTY=true に設定
        this.setDefaultState(this.getStateManager().getDefaultState()
                .with(EMPTY, true));
    }

    @Override
    protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
        // BlockState に EMPTY、SAPLING を登録
        builder.add(EMPTY, SAPLING);
    }


    // onUse()メソッドをオーバーライドして、右クリック処理を記述します。
    @Override
    protected ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit) {

        // クライアント側なら何もしない(クライアント側ではパーティクルや音など描画処理のみ)
        if (world.isClient()) return ActionResult.SUCCESS;

        // プレイヤーのメインハンドにあるアイテムスタックを取得する(なんの苗木を持ってるか)
        ItemStack stack = player.getStackInHand(Hand.MAIN_HAND);

        // ここに右クリック時の処理を書く
        // EMPTY が true の場合 → 植える
        if (state.get(EMPTY)) {

            // 手に苗木がなければ何もしない
            SaplingType sapling = SaplingType.fromItem(stack.getItem());
            if (sapling == null) return ActionResult.PASS;

            // 種類ごとに置き換えるブロックを決める
            switch (sapling) {
                case CHERRY, SPRUCE -> {
                    world.setBlockState(pos, state.with(EMPTY, false).with(SAPLING, sapling));
                }
            }

        }
        // EMPTY が falseの場合 → 何か植わってたら取り出せる
        else {

            // ここで植えられた苗木の種類を取得する
            SaplingType planted = state.get(SAPLING);

            // アイテムとしてプレイヤーに返す
            ItemStack dropStack = ItemStack.EMPTY;

            // 植わってる種類に応じて返す苗木を決める
            switch (planted) {
                case CHERRY -> dropStack = new ItemStack(Items.CHERRY_SAPLING);
                case SPRUCE -> dropStack = new ItemStack(Items.SPRUCE_SAPLING);
            }

            if (!player.getInventory().insertStack(dropStack)) {
                player.dropItem(dropStack, false);
            }

            // ここでブロックを元に戻す
            world.setBlockState(pos, this.getDefaultState().with(EMPTY, true));

        }

        return ActionResult.SUCCESS;
    }

}

④ モデルの設定を変える

Blockbenchで作成するのは、親モデルになる盆栽鉢のみ。で、植わってる植物部分のテクスチャだけ変えて子モデルを作成します。

親モデル.json
{
	"format_version": "1.21.6",
	"credit": "Made with Blockbench",
	"textures": {
		"particle": "ponmaru_mod:block/bonsai_hachi", ← パーティクル
		"bonsai_hachi": "ponmaru_mod:block/bonsai_hachi", ← 盆栽鉢の部分
		"dirt": "ponmaru_mod:block/dirt",          ← 土の部分
		"sapling": "ponmaru_mod:block/empty"        ← 盆栽鉢のみなので、空のテクスチャを作っておいて、それを設定する
	},
	"elements": [
		{
			"from": [9.3024, 0, 0.87973],
・・・以下省略
桜の盆栽(子モデル).json
{
  "parent": "ponmaru_mod:block/bonsai_pot", ← 親モデルに盆栽鉢を指定する
  "textures": {
    "sapling": "ponmaru_mod:block/sakura"  ← 植わってる部分だけ桜のテクスチャを指定する
  }
}
松の盆栽(子モデル).json
{
  "parent": "ponmaru_mod:block/bonsai_pot", ← 親モデルに盆栽鉢を指定する
  "textures": {
    "sapling": "ponmaru_mod:block/matsu"  ← 植わってる部分だけ松のテクスチャを指定する
  }
}

⑤ blockstatesの作成

ブロックの状態を定義するところで、植わってる植物によって、モデルを切り替える

{
  "variants": {
    "empty=true": {
      "model": "ponmaru_mod:block/bonsai_pot"
    },
    "empty=false,sapling=cherry": {
      "model": "ponmaru_mod:block/bonsai_sakura"
    },
    "empty=false,sapling=spruce": {
      "model": "ponmaru_mod:block/bonsai_matsu"
    }
  }
}

とりあえず、本日はここまで。

光源ブロックはそうでもなかったけど、盆栽ブロック、めっちゃ難しかったです……
挙動におかしなところがあるけど(差したり抜いたりすると苗木がどんどん増えていく)ちょっと力尽きたので、そのうち修正する予定。

ソースコード、そっと公開します
シリーズの最初

参考URL

7へ続く

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?