MOD開発講座

概要

TNT Moddersが作成した、MOD開発の始め方に関する解説です。Javaの基礎知識を前提とし、その解説は行っておりませんのでご了承ください。

準備から入門編までの各項目は、それまでのコードに追加する形になっており、「ビルド」まででMODを一つ完成させます。初級編以降の各項目は、入門編のコードを基準として追加を行います。

MODの開発は自己責任で行ってください。この講座により生じたいかなる損害についても、TNT Moddersは一切責任を負いません。

ページ一覧

2021年3月22日時点でのForge開発チームによるサポート対象は1.16.5と1.15.2です。それ以前のバージョンのForgeは今後更新されません。

1.16.5

入門編

  1. 環境構築
  2. アイテムの追加
  3. ブロックの追加
  4. レシピの追加
  5. MODの情報の登録
  6. ビルド

初級編

1.16.4

入門編

  1. 環境構築
  2. アイテムの追加
  3. ブロックの追加
  4. レシピの追加
  5. MODの情報の登録
  6. ビルド

初級編

1.14.4

入門編

  1. 環境構築
  2. アイテムの追加
  3. ブロックの追加
  4. レシピの追加
  5. MODの情報の登録
  6. ビルド

1.12.2 (Forge 14.23.0.2491)

入門編

  1. 環境構築
  2. アイテムの追加
  3. ブロックの追加
  4. レシピの追加
  5. MODの情報の登録
  6. ビルド

初級編

1.7.10 (Forge 10.13.4.1558)

1.7.10の環境構築はGradleで問題が発生しており、動作を確認できていません。詳しくは「環境構築 2 (1.7.10)」のコメント欄をご覧ください。

準備

  1. 前提知識
  2. 環境構築 1
  3. 環境構築 2 (1.7.10)

入門編

  1. アイテムの追加
  2. ブロックの追加
  3. リソースの作成
  4. レシピの追加
  5. MODの情報の登録
  6. ビルド

初級編

中級編

上級編

解説の追加要望について

このページのコメント欄では追加要望を受け付けていますが、TNT Modders側の都合により断らせていただく場合があります。 また、要望を承った後、実際に追加するまで時間がかかる場合があります。 ご了承ください。

コメント欄

このページのコメント欄には、解説の追加要望や開発講座全体に関する意見などを投稿してください。個別の解説をご要望の際は、Minecraftのバージョンと求める動作の詳しい説明も記載してください。

解説ページのコメント欄には、それぞれの解説に関する質問などを投稿してください。また、解説の誤りや、より良い方法などがありましたら、お知らせください。

MOD開発講座” への60件のフィードバック

  1. マルチブロック設備はどのように作ればいいですか?

    1. 装置の状態を保持してGUIを提供するためには、装置の核となる1ブロックにTileEntityを持たせる必要があります。
      装置を構成するブロックの破壊や隣接ブロックの更新などのタイミングで、構成条件が満たされているか確認します。
      構成ブロックのIDやBlockStateを変更すれば、装置が完成しているかどうか外観の変化で伝えることもできます。

      コードについては、似た機能を持つMODのものを参考にするとよいでしょう。
      オファレンMODでは、機械系ブロックの強化に使える「オファレン処理装置」がマルチブロック設備です。
      TileEntityを持つのは機械系ブロックだけで、作業開始時に処理装置の配置が条件を満たしてるか確認して処理速度を決めています。

  2. チェストの解説、1.7.10待ってます。

    1. TileEntityの追加GUIの実装で基本的な部分は解説しています。チェストの向きやラージチェストへの変化などについては、バニラのチェストのコードを参考にしてください。

  3. 1.7.10で経験値を回収するためには、どうすればいいですか?

    1. 回収とは具体的にどのような動作でしょうか。経験値オーブはEntityXPOrbとして存在し、近くのプレイヤーに引き寄せられ、一定距離まで近づくと蓄えている経験値量をプレイヤーの経験値に加算します。

      オファレンMODのコレクター(ItemCollector)は、範囲内の経験値オーブをプレイヤーの座標に移動することで、周囲の経験値を回収しています。また、コレクターブロック(TileEntityCollector)は、範囲内の経験値オーブを消し、蓄えられていた経験値量を「経験値の結晶」という追加アイテムに変換して回収します。

      1. ありがとうございます。

        もう一個分からないことがあるのですが、
        特定の防具をインベントリの防具スロットから検出して、TrueとFalseをログ出力したいのですが、調べてもForgeAPIとかを見ても、それらしきものがないので、教えていただきたいです。

        1. EntityPlayer.getCurrentArmorで部位に応じた数値(足から頭で0~3)を渡すと防具のItemStackが得られます。また、追加防具についてはItemArmor.onArmorTickをオーバーライドすれば、プレイヤーが装備しているときに毎tick呼ばれます。

          オファレンMODでは、ItemOfalenArmorで部位に応じたポーション効果を与えています。ただし、おそらくオファレンMODの実装は不適当で、onArmorTickではItemStackが引数で渡されているので、getCurrentArmorを呼ぶ必要はないと思います。

          ログ出力についてはprintlnでもいいですが、オファレンMODではutil.OfalenLogを介してlog4j.LogManagerを使っています。ベッドなどのようにチャット欄に出力するにはEntityPlayer.addChatMessageを使います。こちらについてはOfalenUtil.addChatTranslationMessageとその呼び出し元をご覧ください。

          1. if(player.getHeldIyem() != null && player.getHeldItem().getItem( ) == Items.diamond)

            上のif文があるのですが、このif文でブロックを持っていると(なんのブロックでもいい)Trueを出力して、アイテムや剣などブロック以外のものを手に持っていると、Falseをログ出力する方法を教えてください。

          2. player.getHeldItem() instanceof ItemBlockで判定できます。ItemBlockItemの子クラスで、アイテム状態のブロックの種類を表します。Item.getItemFromBlockBlock.getBlockFromItemで、BlockItemBlockを相互に変換できます。

          3. 何度もすみません。

            player.getHeldItem() instanceof ItemBlockで判定できます。ItemBlockはItemの子クラスで、アイテム状態のブロックの種類を表します。Item.getItemFromBlockとBlock.getBlockFromItemで、BlockとItemBlockを相互に変換できます。

            教えてもらった通りにやったのですが、

            EntityPlayer player;

            if(player.getHeldItem() instanceof ItemBlock != null null && Item.getItemFromBlock && Block.getBlockFromItem){}

            上のように、プログラムしてみましたが、エラーを吐き

            playet.getHeldItem() instanceof ItemBlock

            上のプログラムで、
            「変換できない型です。'net.minecraft.item.ItemStack' を 'net.minecraft.item.ItemBlock' にキャストできません。」

            上のエラーが出て、エラーをコピペして検索したり、int から stringみたいに変換できるのかと思いましたが、出てきませんでした。

            Item.getItemFromBlock と Block.getBlockFromItem は、同じエラーで、
            シンボルを解決できません。と出ました。

            後ろに()をつけてみても、エラーを吐きブロックを指定しろとかアイテムを指定しろと言ってるみたいなエラーを吐き出しました。

            なぜエラーを吐くのでしょうか?
            原因を教えてください。

          4. 申し訳ありません。先ほどの回答が間違っていて、正しくはplayer.getHeldItem().getItem() instanceof ItemBlockです。また、BlockItemBlockの変換については参考情報としてお伝えしたかったのですが、わかりづらい表現になってしまいすみません。

            最終的に、該当のif文はif (player.getHeldItem() != null && player.getHeldItem().getItem() instanceof ItemBlock) { /* ここに分岐後の処理 */ }としてください。

            Javaの基礎知識、特にクラスやオブジェクト、メソッドなどについて調べていただくと、今後のMOD開発に役立つと思います。

  4. プレイヤーの真下のブロックを、他のブロックに置き換えるプログラムが分かりません。

    教えていただきたいです。

    1. ブロックの置き換えは、World.setBlockWorld.setBlockMetadataWithNotifyで行います。プレイヤーの座標(Entity.posX, posY, posZ)からXとZを切り捨て、Yを-1して切り捨ててブロック座標に変換し、これらのメソッドに渡せばよいでしょう。

      1. ブロックの置き換えは出来ました。
        ですが次の問題で、リアルタイムにブロックを設置してくれません。

        ワールドを出てもう1回入ると、ブロックが設置されています。

        描画更新とかはないのでしょうか?

        1. setBlocksetBlockMetadataWithNotifyがサーバー側で呼ばれていることを確認してください。また、引数のint flagについて、ドキュメントコメントを参照の上、適切な値(おそらく3)に設定してください。もしこれらのメソッドがクライアントでも呼ばれているなら、クライアントでの呼び出しを回避するか、サーバー側と同じ座標・同じブロックが引数に渡されていることを確認してください。

          Minecraftは、シングルプレイでもサーバーとクライアントで処理を分けているため、双方の同期をとらなければ正しく動作しません。そこで、setBlockのサーバー側の実装には、クライアントへ変更を伝える処理が内包されています。クライアント側の実装にはサーバーへ変更を伝える処理は入っていないので、クライアント側のみでブロック操作を行うと望まぬ動作を招きます。

          先ほど説明したlog4j.LogManagerを使えば、クライアントとサーバーのどちらでロガーが呼ばれたか区別できます。また、World.isRemoteを条件にすると、trueならクライアント側、falseならサーバー側として処理を分岐できます。

          1. 赤砂蛇 凪浜さんのおかげmodが完成致しました!!
            ありがとうございます。

            でもテストプレイで欠陥を見つけてしまって、もう一個だけ教えて貰えませんでしょうか?

            ハーフブロックをWorld.setblockでBlock.getBlockfromItem(player.getHeldItem().getItem())をブロック指定したところ、出来ました。
            ですが、メタデータ?を持つ、ブロック、
            Stoneやハーフブロックなど、はメタデータが0になってしまい、レンガのハーフブロックとかをやっても、石のハーフブロックを設置してしまいます。

            検索してみたところ、Block.getBlockStatesというプログラムを見つけ、試したのですがシンボルが見つからないと言われてしまい、Blockのclassを見てみたところ、getBlockStatesがなく、それらしきものもありません。

            何度も質問してしまい申し訳ございません。

          2. World.setBlock(int x, int y, int z, Block block, int meta, int flag)World.setBlockMetadataWithNotify(int x, int y, int z, int meta, int flag)によって、ブロックのメタデータを指定できます。ItemStackのメタデータはItemStack.getItemDamage()で取得できるので、これを渡せばハーフブロックの種類は反映されるようになります。

            ただし、ハーフブロックの上下については、単純にItemStackのメタデータを反映させるとすべて下付きになるでしょう。メタデータに8を加えると同じ種類の上付きハーフブロックになると思いますが、どのように上下を決めるかは求める動作に応じて工夫してください。

            1.7.10では、TileEntityを持たないブロックの状態を0~15までの数値であるメタデータで管理します。ハーフブロックの種類と上下のほかにも、原木の向きや羊毛の色などがこの数値で表されています。BlockStateは1.8以降に導入された仕組みで、どのような状態を管理しているかわかりやすく表現できるようになっています。

            一方、ItemStackのメタデータはintの上限まで使えます。道具の消費した耐久値を表現するためにも使われているので、damageなどと書かれることがあります。

            メタデータを持つアイテムの追加メタデータを持つブロックの追加もご覧ください。

  5. 敵mobを倒した時に、ランダムの個数で特定のアイテムを落とす、Eventプログラムを教えてください。

    1. 追加したMOBのドロップについては、MOBの追加をご覧ください。

      バニラのMOBのドロップにアイテムを追加したい場合は、LivingDropsEventを受け取って、条件を満たした場合にdropsEntityItemを追加すればよいでしょう。敵性の判定はentityLivingEntityMobのインスタンスであるか確認すればいいと思います。

      Immersive Engineeringは、ボスが倒されたときにシェーダーバッグというアイテムを追加でドロップさせています。EventHandler.javaの365行目あたりで実装しているので、参考にしてください。ウィザーならシェーダーバッグのレアリティを下げ、Configで指定されたEntityを対象から外した後に、レアリティをNBTに記録してドロップに追加するという処理があります。

      1. 草を壊すとランダム個数で特定アイテムを落とす方法をしえてください。

        1. 私が実装したことはなく、現在1.7.10の開発環境を持っていないため、詳しく説明することはできません。

          Immersive Engineeringは工業用麻(Industrial Hemp)の種を草からのドロップに追加しています。IEContent.javaの266行目周辺にMinecraftForge.addGrassSeedという記述があるので、これで登録しているのかもしれません。

  6. 質問に丁寧に答えていただきありがとうございました。

    赤砂蛇 凪浜さんのおかげでModを完成させることが出来ました!
    Replace Walk ModというModでプレイヤーの真下のブロックを手に持っているブロックに置換するmodです。
    レシピとかは、まだサイトと動画が作れてないので、クラフトレシピなどで確認をお願い致します。

    もし、暇な時間があったらやってみて下さい!
    サバイバルではほぼチートModなのでw

    ダウンロードページ→https://locu-akito.github.io/

    Mod開発はまだまだ続けるので、また質問するかもしれませんがよろしくお願い致しますm(_ _)m

    1. 完成をしたと言ったのですが、プレイヤー下の座標が定まりません。

      どういう事かと言うと、プレイヤー下にブロックを設置することもあれば、プレイヤー下の座標からX座標が1個ズれて設置されたり、z軸が1個ズレたりします。
      サーバー座標とか関係あるのかなと思って、サーバー座標(player.serverPosX,Y,Z)をWorld.setblockの座標を指定するところにやってみたのですが、出来ないのでなんでだろ、と思い原因が分からないので、また質問をさせてもらいました。

      1. XとZについて、intへのキャストでプレイヤー座標からブロック座標に変換しているのではないでしょうか。Math.floor(double)を使ってください。

        Entity座標はdoubleで、ブロック座標はintで管理されているので、プレイヤーの座標からブロックの設置座標を求めるには、変換が必要です。Minecraftでは、Entity座標(0.0, 0.0, 0.0) ~ (1.0, 1.0, 1.0)とブロック座標(0, 0, 0)が対応しており、Entity座標-1.0 ~ 0.0はブロック座標-1に変換されなければなりません。

        JavaのdoubleからintへのキャストとMath.floorはどちらも切り捨てで、正数の処理は同じですが、負数の処理が異なります。キャストは、負数を0に向かって切り捨てるので、-0.99… ~ 0.00.0 ~ 0.99…とともに0に変換されます。一方、Math.floorは、負数を負の無限大に向かって切り捨てるので、-1.0 ~ 0.00.0を含まない)は-1に変換されます。よって、今回の変換ではMath.floorを使う必要があります。

        あるいは、サーバーとクライアントでプレイヤー座標がずれていることが原因かもしれません。World.setBlockはサーバー側のみで呼び出すようにしてください。

        解決しなければ、ずれて設置されたときのサーバー側のプレイヤー座標(double)と設置座標(int)をログで出力し、F3で表示されるクライアント側のプレイヤー座標とともにお知らせください。

        1. 座標がズレることなく、設置ができるようになりました!

          毎回の質問に丁寧に答えて頂きありがとうございます。

  7. キー入力イベントはどうやって作るんですか?
    public void onArmorTickの中でKeyEventのif文を作りたいです。

    1. 追加した防具を装備していて特定のキーを押している間、何らかの処理を毎tick行いたいということですね。

      ダッシュやしゃがみなど、プレイヤーの状態を条件にするなら、比較的簡単に実装できます。これらの状態はバニラで同期されているので、onArmorTickの中でEntityPlayer.isSprintingEntityPlayer.isSneakingを呼べば判定できます。

      一方、キーの割り当てを追加したい場合は、実装が複雑になります。まずは、クライアントの初期化時にClientRegistry.registerKeyBindingで新しいKeyBindingを登録しておきます。KeyInputEventはクライアントのみで毎フレーム発生するので、これを受け取ってキーが押されているか判定します。この状態は防具のNBTなどに保存しておき、onArmorTickで判定することになります。行いたい処理にサーバーの操作も必要な場合は、パケットを追加して状態の変化をサーバーに同期しなければなりません。

      キー割り当ての追加、パケットの追加に関しては解説記事がありません。イベントの利用については記事があるのでご覧ください。

      Mekanismの1.7.10版には、防具のモード切替キーが押されたときに、装備されたジェットパックのモードを変更する処理があります。サーバーへの同期も行っていますが、飛行処理にはonArmorTickを使っていません。MekanismKeyHandlerや関連するファイルをご覧ください。

      オファレンMODには設定キーを追加し、押しながら右クリックで詳細設定画面を開く処理があります。パケットの追加は参考になると思いますが、キー割り当ての登録や状態の保持の実装はあまりよいものではないのでMekanismをご覧ください。

      1.12.2版のForgeの公式ドキュメント(英語)には、イベントの使い方やパケットの実装についての記事があります。

コメントはこちらです。(スパム対策の為コメントは手動承認になっています。未承認のコメントは表示されないので連投はお控え下さい。)

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください