1
0

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を作ってみる(7)

Last updated at Posted at 2025-12-08

前回からの続き

前回は光源ブロックを一つ追加しましたが、今回も光源ブロック、壁面行燈のブロックを作成・追加します。よくお店の入口にある店名が入ってる光る表札のようなアレです。お寿司屋さんとかお蕎麦屋さんでよく見かけるような……。いや、ラーメン屋さんにあってもいいんですけど。
壁面行燈の名前の通り、壁につけるため厚み半分で作成しました。なので、置いたときに向きが変わるようにしないと、常に北側に寄った状態で設置されてしまうため、MOD内で向きを変えられるようにします。

ついでに、ハーフブロックの上に設置できないかやってみます。というのも、下付きハーフブロックの上にブロック設置すると、半分浮いたようになってしまいます。これを、下付きハーフブロックに置いたときは、ブロック表示が0.5下にならないかな、と思って。和風建築するときに、床の間とかほしいので。

今回の作業後画面

image.png

クラス設計について

ブロックが増えてくると、フォルダの構成とかどっから継承するかとかややこしいことになってきたので、この辺で設計について考えてみます。
向きがあるブロックについては、Blockクラスを継承して、向きを付けて、とするのでクラス1個作って向きの処理はまとめて、と考えていました。(後述の「開発:1 没案」の部分です)
でもこの後、座椅子を作る時に向きも設定できて、座れる設定して、となると1個しか継承できないので困るなぁと……🤨なので、継承ではなく、クラス内のメソッドをstaticにしておいて他クラスから呼び出せるようにして、カスタムブロックのクラスはimplementsでインターフェースを実装するようにします。(インターフェースは何個でもひっつけれる)後述の「開発:2」がその実装部分になります。やりたいことが色々出てくると、フォルダとかごちゃごちゃに処理が散らばってるとわかりにくくなるかと思いますが、ベテランプロ級は最初から考えるんでしょうね。きっと。うむむ。

開発:0 モデル作成

地面にも設置できるけど、基本壁に設置するので、厚さ半分です。この前のオークの行燈同様、ライトの部分とダークオークの部分にテクスチャを分けています。
image.png

開発:1 (没案)

  • AbstractFacingBlock クラス → 向きの処理をまとめたクラスを作成する
  • blockstates の編集 → 向きによってY軸で90度回転するよう設定
  • ModBlocks クラスで登録 → ブロック登録でAbstractFacingBlockをnewする

別にこの実装でも問題なく、向きは変えることができます。後々で、機能はまとめておいて、カスタムブロック作成の時に好きな組み合わせ機能を上乗せしていく方が使いまわしできるかな、と、一回作ったけど没としました。

1.AbstractFacingBlock クラス作成

Block クラスを継承して、向きの処理をまとめたクラスを作成します。1つ作っておくと他ブロックを追加したときに、このクラスからインスタンスを作るようにすると、同じように回転できます。
デフォルトの向きを設定する際、HORIZONTAL_FACINGではなくFACINGにしています。今後、階段ブロックみたいにX軸でも回転するブロックを作るかも知れないので。

AbstractFacingBlock.java

public class AbstractFacingBlock extends Block {

    // コンストラクタ
    public AbstractFacingBlock(Settings settings) {
        super(settings);
        //ブロックの "向き" を初期化
        setDefaultState(getDefaultState()
                .with(Properties.FACING, Direction.NORTH)
         );
    }

    // このブロックは向きを持つ ということをブロックの状態として追加しています
    @Override
    protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
        builder.add(Properties.FACING);
    }

    // 配置時にプレイヤーの向きを取得して BlockState に反映
    // プレイヤーに向くようにブロックを設置する
    @Override
    public BlockState getPlacementState(ItemPlacementContext ctx) {
        return Objects.requireNonNull(super.getPlacementState(ctx))
                .with(Properties.FACING, ctx.getHorizontalPlayerFacing().getOpposite());

    }

}

2.blockstates の編集

向きを変えたいブロックのblockstatesを先ほど設定したFACINGによって、Y軸で回転するよう設定を入れます。

{
  "variants": {
    "facing=north": { "model": "自分のModid:block/dark_oak_lantern", "y": 180 },
    "facing=east": { "model": "自分のModid:block/dark_oak_lantern", "y": 270 },
    "facing=south": { "model": "自分のModid:block/dark_oak_lantern" },
    "facing=west": { "model": "自分のModid:block/dark_oak_lantern", "y": 90 }
  }
}

3.ModBlocks クラスで登録

ModBlocks.java
public class ModBlocks {

// 今回追加する部分
// new AbstractFacingBlockにする
public static final Block DARK_OAK_LANTERN = register("dark_oak_lantern",
            settings -> new AbstractFacingBlock(settings.strength(0.5f).luminance(state -> 12).nonOpaque()),
            AbstractBlock.Settings.create().sounds(BlockSoundGroup.WOOD), true
    );

開発:2 (こちらで実装)

いや~色々やってみて、なんにも動かずどこまで触ったかわからず、あげくの果てにゲームも起動しなくなり、いじったところをブランチごと削除すること数回……😱😱😱

インターフェースと向きを変えるクラスでまとめて、ブロックを回転させるところまでは大丈夫だったんですが、ハーフブロックの上に設置したとき、モデルを0.5下げる、がうまく行かず。
BlockEntities使って、BlockRendererとかで、matrices.translate(0, -0.5, 0)にすれば簡単にできるみたいなのをChatGPTに聞きながらやったのですが……エラーの嵐と何から直せばいいのかわからず、2週間くらい時間をかけたあと、初心者には無理だなとこのやり方はあきらめました。他にもBakedModelを使って、みたいな実装方法も検索で引っかかったのですが、BakedModelってインターフェースは無いし、実装方法がまったくわからずで。たぶん、BakedModelってのがなくなって、FabricBlockStateModel になったんじゃないかと思うのですが、さっぱりやり方わからずでした。向きつけるのと同じように、blockstatesでY軸を上下できたらいいんですが、回転しかないようで。うむむ。

最終的に 力技 ちょっと無理やりですが、モデルを2種類(普通のと半ブロック下げたもの)を用意して、blockstatesで切り替える方法で実装。これだと、モデルが増えるのでなんだフォルダが汚い。ツリー開いて、なんで2個ずつあるねん!とツッコミ入りそう。うーむ。無念。いつかどこかでいい感じのやり方見つけたら、もしくはJAVAの仕組みがもうちょっとわかってきて、ちゃんとコード読めるようになったら修正します😭

さて、AbstractFacingBlockを使わない方の実装なのですが追加するものは下記の通り。

  • インターフェース「IFacing」向き
  • インターフェース「ISlabAwareBlock」ハーフブロックの上かどうかの判定
  • ランタンブロッククラス「LanternBlock」
    その他、モデルを半ブロック下げたものを追加(Blockbenchで下にずらしてjsonのみ出力)

先述した通り、クラス継承は1個しかできません。例えば↓の図のように、椅子には向きもあるし、座れるようにしたいな~とかなったとき、両方は継承できないので困りそう。
となると向き返る処理は、各ブロック内で書くことになるのかな、と。なので、implementsでインターフェースを実装して、必要なクラスが継承できるように作ります。

カスタムブロック 向き 座る
椅子
照明 -
テーブル - -

椅子はまだ作成してないけど、player.startRiding()みたいなのが、net.minecraft.entityパッケージの中にあったので、たぶん椅子は、Entityクラスか、BlokwithEntityクラスを継承して、向きのインターフェースを実装する、みたいな作り方になるのでは?と予想しています。

インターフェース「IFacing」

向きのインターフェースに default メソッドを定義して、実質「ヘルパーメソッドを持つ抽象クラス」みたいに使えるようにします。

IFacing.java
// 向きがあることを示すインターフェース
public interface IFacing {

    // ブロックの向きを表すプロパティを定数としてまとめる(FACINGはブロックの向きを表す EnumProperty 定数)
    public static final EnumProperty<Direction> FACING = Properties.FACING;

    // 配置時にプレイヤーの向きを反映するメソッド
    // ItemPlacementContext にはプレイヤーの向き情報が入っている
    // .getHorizontalPlayerFacing() → プレイヤーが向いている方向(N, S, E, W)
    // .getOpposite() → ブロックは プレイヤーと向かい合う方向になるのが普通
    // 新しい BlockState を返す
    default BlockState withPlacementFacing(BlockState state, ItemPlacementContext ctx) {
        return state.with(Properties.FACING, ctx.getHorizontalPlayerFacing().getOpposite());
    }
}

インターフェース「ISlabAwareBlock」

ここで、設置ブロックの下が下付きハーブロックかどうかをbooleanで返します。

ISlabAwareBlock.java
public interface ISlabAwareBlock {

    // 配置時に下のブロックが下付きハーフかどうか判定
    default boolean isOnBottomSlab(BlockState stateBelow) {
        return stateBelow.getBlock() instanceof net.minecraft.block.SlabBlock
                && stateBelow.get(SlabBlock.TYPE) == SlabType.BOTTOM;
    }
}

LanternBlock クラス

ランタンは専用のクラスにまとめます。
プロパティでブロック情報を持たせています。

LanternBlock.java
// ランタンのブロックとしてまとめる
public class LanternBlock extends Block implements IFacing, ISlabAwareBlock {

    public static final BooleanProperty ON_SLAB = BooleanProperty.of("on_slab");

    // コンストラクタ(MODロード時(レジストリ登録時) に実行)
    public LanternBlock(Settings settings) {
        super(settings);
    }

    // プロパティを追加
    @Override
    protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
        builder.add(FACING, ON_SLAB);
    }

    // 配置時
    @Override
    public BlockState getPlacementState(ItemPlacementContext ctx) {
        BlockPos pos = ctx.getBlockPos();
        BlockState below = ctx.getWorld().getBlockState(pos.down());

        // FACING の反映
        BlockState state = withPlacementFacing(this.getDefaultState(), ctx);

        // ON_SLAB の反映
        boolean onSlab = isOnBottomSlab(below);
        state = state.with(ON_SLAB, onSlab);

        // 動いているかコンソールに出して確認
        System.out.println("ランタンブロックチェック: FACING = " + state.get(FACING));
        System.out.println("ランタンブロックチェック: ON_SLAB  = " + onSlab);

        return state;
    }
}

で、これをblockstatesでモデルを切り替えて呼びます。
2ってついてるモデルが半ブロック下げたものです。向きも設定してるから数が多い。

半ブロック分動かして、モデルは2つにします。
image.png
左:dark_oak_lantern(on_slab=false) 
右:dark_oak_lantern2(on_slab=true)

{
  "variants": {
    "facing=north,on_slab=false": { "model": "自分のmodid:block/dark_oak_lantern", "y": 180 },
    "facing=east,on_slab=false": { "model": "自分のmodid:block/dark_oak_lantern", "y": 270 },
    "facing=south,on_slab=false":  { "model": "自分のmodid:block/dark_oak_lantern"},
    "facing=west,on_slab=false":  { "model": "自分のmodid:block/dark_oak_lantern", "y": 90 },

    "facing=north,on_slab=true":  { "model": "自分のmodid:block/dark_oak_lantern2", "y": 180 },
    "facing=east,on_slab=true":  { "model": "自分のmodid:block/dark_oak_lantern2", "y": 270 },
    "facing=south,on_slab=true":   { "model": "自分のmodid:block/dark_oak_lantern2"},
    "facing=west,on_slab=true":   { "model": "自分のmodid:block/dark_oak_lantern2", "y": 90 }
  }
}

起動して動作確認後、前回までに作った盆栽、オークの行燈も半ブロック下げるようにしました。見た目だけで言えば、これで床の間っぽい感じになりました!😁
モデルの位置を半ブロック下げているだけなので、当たり判定は普通のブロックサイズになっています。クリックするときよく間違えます。
(右:当たり判定の位置は元のブロックと同じ)
image.png

これであとは刀と掛け軸あればいい感じです。障壁画とかも入れて、コードもスッキリ美しく、レシピ、アイテム、進捗、バフ効果など揃えられたら「なんちゃってニッポンの本丸」略して「ポン丸MOD」から「本丸御殿MOD」になれるかも知れません……フフフ 何年かかるかわからんけど。

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

今回は、半ブロック下げたい、の部分に2週間くらい四苦八苦しました。もっとうまいやり方があるはずと思いつつ、自分の理解が追い付かずです。難しぃぃ~~~
まだまだコードを読んだり(特にチェーンメソッドになると何をやっているのかわからない)が難しいので、なるべくバニラに乗ったMOD開発を楽しんで進めていけたらな、と思っています。

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

参考URL

1
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?