目次
概要
TNT Moddersが作成した、MOD開発の始め方に関する解説です。Javaの基礎知識を前提とし、その解説は行っておりませんのでご了承ください。
準備から入門編までの各項目は、それまでのコードに追加する形になっており、「ビルド」まででMODを一つ完成させます。初級編以降の各項目は、入門編のコードを基準として追加を行います。
MODの開発は自己責任で行ってください。この講座により生じたいかなる損害についても、TNT Moddersは一切責任を負いません。
ページ一覧
2022年5月29日時点でのForge開発チームによるサポート対象は1.18.2と1.16.5です。最新の情報はForgeのフォーラムを確認してください。
1.18.2
入門編
1.16.5
入門編
初級編
1.16.4
入門編
初級編
1.14.4
入門編
1.12.2 (Forge 14.23.0.2491)
入門編
初級編
1.7.10 (Forge 10.13.4.1558)
1.7.10の環境構築はGradleで問題が発生しており、動作を確認できていません。詳しくは「環境構築 2 (1.7.10)」のコメント欄をご覧ください。
準備
入門編
初級編
中級編
上級編
解説の追加要望について
このページのコメント欄では追加要望を受け付けていますが、TNT Modders側の都合により断らせていただく場合があります。 また、要望を承った後、実際に追加するまで時間がかかる場合があります。 ご了承ください。
コメント欄
このページのコメント欄には、解説の追加要望や開発講座全体に関する意見などを投稿してください。個別の解説をご要望の際は、Minecraftのバージョンと求める動作の詳しい説明も記載してください。
解説ページのコメント欄には、それぞれの解説に関する質問などを投稿してください。また、解説の誤りや、より良い方法などがありましたら、お知らせください。
“MOD開発講座” への86件のフィードバック
マルチブロック設備はどのように作ればいいですか?
装置の状態を保持してGUIを提供するためには、装置の核となる1ブロックにTileEntityを持たせる必要があります。
装置を構成するブロックの破壊や隣接ブロックの更新などのタイミングで、構成条件が満たされているか確認します。
構成ブロックのIDやBlockStateを変更すれば、装置が完成しているかどうか外観の変化で伝えることもできます。
コードについては、似た機能を持つMODのものを参考にするとよいでしょう。
オファレンMODでは、機械系ブロックの強化に使える「オファレン処理装置」がマルチブロック設備です。
TileEntityを持つのは機械系ブロックだけで、作業開始時に処理装置の配置が条件を満たしてるか確認して処理速度を決めています。
チェストの解説、1.7.10待ってます。
TileEntityの追加とGUIの実装で基本的な部分は解説しています。チェストの向きやラージチェストへの変化などについては、バニラのチェストのコードを参考にしてください。
1.7.10で経験値を回収するためには、どうすればいいですか?
回収とは具体的にどのような動作でしょうか。経験値オーブは
EntityXPOrb
として存在し、近くのプレイヤーに引き寄せられ、一定距離まで近づくと蓄えている経験値量をプレイヤーの経験値に加算します。オファレンMODのコレクター(
ItemCollector
)は、範囲内の経験値オーブをプレイヤーの座標に移動することで、周囲の経験値を回収しています。また、コレクターブロック(TileEntityCollector
)は、範囲内の経験値オーブを消し、蓄えられていた経験値量を「経験値の結晶」という追加アイテムに変換して回収します。ありがとうございます。
もう一個分からないことがあるのですが、
特定の防具をインベントリの防具スロットから検出して、TrueとFalseをログ出力したいのですが、調べてもForgeAPIとかを見ても、それらしきものがないので、教えていただきたいです。
EntityPlayer.getCurrentArmor
で部位に応じた数値(足から頭で0~3)を渡すと防具のItemStack
が得られます。また、追加防具についてはItemArmor.onArmorTick
をオーバーライドすれば、プレイヤーが装備しているときに毎tick呼ばれます。オファレンMODでは、
ItemOfalenArmor
で部位に応じたポーション効果を与えています。ただし、おそらくオファレンMODの実装は不適当で、onArmorTick
ではItemStack
が引数で渡されているので、getCurrentArmor
を呼ぶ必要はないと思います。ログ出力については
println
でもいいですが、オファレンMODではutil.OfalenLog
を介してlog4j.LogManager
を使っています。ベッドなどのようにチャット欄に出力するにはEntityPlayer.addChatMessage
を使います。こちらについてはOfalenUtil.addChatTranslationMessage
とその呼び出し元をご覧ください。if(player.getHeldIyem() != null && player.getHeldItem().getItem( ) == Items.diamond)
上のif文があるのですが、このif文でブロックを持っていると(なんのブロックでもいい)Trueを出力して、アイテムや剣などブロック以外のものを手に持っていると、Falseをログ出力する方法を教えてください。
player.getHeldItem() instanceof ItemBlock
で判定できます。ItemBlock
はItem
の子クラスで、アイテム状態のブロックの種類を表します。Item.getItemFromBlock
とBlock.getBlockFromItem
で、Block
とItemBlock
を相互に変換できます。何度もすみません。
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 は、同じエラーで、
シンボルを解決できません。と出ました。
後ろに()をつけてみても、エラーを吐きブロックを指定しろとかアイテムを指定しろと言ってるみたいなエラーを吐き出しました。
なぜエラーを吐くのでしょうか?
原因を教えてください。
申し訳ありません。先ほどの回答が間違っていて、正しくは
player.getHeldItem().getItem() instanceof ItemBlock
です。また、Block
とItemBlock
の変換については参考情報としてお伝えしたかったのですが、わかりづらい表現になってしまいすみません。最終的に、該当のif文は
if (player.getHeldItem() != null && player.getHeldItem().getItem() instanceof ItemBlock) { /* ここに分岐後の処理 */ }
としてください。Javaの基礎知識、特にクラスやオブジェクト、メソッドなどについて調べていただくと、今後のMOD開発に役立つと思います。
プレイヤーの真下のブロックを、他のブロックに置き換えるプログラムが分かりません。
教えていただきたいです。
ブロックの置き換えは、
World.setBlock
やWorld.setBlockMetadataWithNotify
で行います。プレイヤーの座標(Entity.posX, posY, posZ
)からXとZを切り捨て、Yを-1して切り捨ててブロック座標に変換し、これらのメソッドに渡せばよいでしょう。ブロックの置き換えは出来ました。
ですが次の問題で、リアルタイムにブロックを設置してくれません。
ワールドを出てもう1回入ると、ブロックが設置されています。
描画更新とかはないのでしょうか?
setBlock
やsetBlockMetadataWithNotify
がサーバー側で呼ばれていることを確認してください。また、引数のint flag
について、ドキュメントコメントを参照の上、適切な値(おそらく3
)に設定してください。もしこれらのメソッドがクライアントでも呼ばれているなら、クライアントでの呼び出しを回避するか、サーバー側と同じ座標・同じブロックが引数に渡されていることを確認してください。Minecraftは、シングルプレイでもサーバーとクライアントで処理を分けているため、双方の同期をとらなければ正しく動作しません。そこで、
setBlock
のサーバー側の実装には、クライアントへ変更を伝える処理が内包されています。クライアント側の実装にはサーバーへ変更を伝える処理は入っていないので、クライアント側のみでブロック操作を行うと望まぬ動作を招きます。先ほど説明した
log4j.LogManager
を使えば、クライアントとサーバーのどちらでロガーが呼ばれたか区別できます。また、World.isRemote
を条件にすると、true
ならクライアント側、false
ならサーバー側として処理を分岐できます。赤砂蛇 凪浜さんのおかげmodが完成致しました!!
ありがとうございます。
でもテストプレイで欠陥を見つけてしまって、もう一個だけ教えて貰えませんでしょうか?
ハーフブロックをWorld.setblockでBlock.getBlockfromItem(player.getHeldItem().getItem())をブロック指定したところ、出来ました。
ですが、メタデータ?を持つ、ブロック、
Stoneやハーフブロックなど、はメタデータが0になってしまい、レンガのハーフブロックとかをやっても、石のハーフブロックを設置してしまいます。
検索してみたところ、Block.getBlockStatesというプログラムを見つけ、試したのですがシンボルが見つからないと言われてしまい、Blockのclassを見てみたところ、getBlockStatesがなく、それらしきものもありません。
何度も質問してしまい申し訳ございません。
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などと書かれることがあります。
メタデータを持つアイテムの追加とメタデータを持つブロックの追加もご覧ください。
敵mobを倒した時に、ランダムの個数で特定のアイテムを落とす、Eventプログラムを教えてください。
追加したMOBのドロップについては、MOBの追加をご覧ください。
バニラのMOBのドロップにアイテムを追加したい場合は、
LivingDropsEvent
を受け取って、条件を満たした場合にdrops
にEntityItem
を追加すればよいでしょう。敵性の判定はentityLiving
がEntityMob
のインスタンスであるか確認すればいいと思います。Immersive Engineeringは、ボスが倒されたときにシェーダーバッグというアイテムを追加でドロップさせています。EventHandler.javaの365行目あたりで実装しているので、参考にしてください。ウィザーならシェーダーバッグのレアリティを下げ、Configで指定されたEntityを対象から外した後に、レアリティをNBTに記録してドロップに追加するという処理があります。
草を壊すとランダム個数で特定アイテムを落とす方法をしえてください。
私が実装したことはなく、現在1.7.10の開発環境を持っていないため、詳しく説明することはできません。
Immersive Engineeringは工業用麻(Industrial Hemp)の種を草からのドロップに追加しています。IEContent.javaの266行目周辺に
MinecraftForge.addGrassSeed
という記述があるので、これで登録しているのかもしれません。質問に丁寧に答えていただきありがとうございました。
赤砂蛇 凪浜さんのおかげでModを完成させることが出来ました!
Replace Walk ModというModでプレイヤーの真下のブロックを手に持っているブロックに置換するmodです。
レシピとかは、まだサイトと動画が作れてないので、クラフトレシピなどで確認をお願い致します。
もし、暇な時間があったらやってみて下さい!
サバイバルではほぼチートModなのでw
ダウンロードページ→https://locu-akito.github.io/
Mod開発はまだまだ続けるので、また質問するかもしれませんがよろしくお願い致しますm(_ _)m
完成をしたと言ったのですが、プレイヤー下の座標が定まりません。
どういう事かと言うと、プレイヤー下にブロックを設置することもあれば、プレイヤー下の座標からX座標が1個ズれて設置されたり、z軸が1個ズレたりします。
サーバー座標とか関係あるのかなと思って、サーバー座標(player.serverPosX,Y,Z)をWorld.setblockの座標を指定するところにやってみたのですが、出来ないのでなんでだろ、と思い原因が分からないので、また質問をさせてもらいました。
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.0
は0.0 ~ 0.99…
とともに0
に変換されます。一方、Math.floor
は、負数を負の無限大に向かって切り捨てるので、-1.0 ~ 0.0
(0.0
を含まない)は-1
に変換されます。よって、今回の変換ではMath.floor
を使う必要があります。あるいは、サーバーとクライアントでプレイヤー座標がずれていることが原因かもしれません。
World.setBlock
はサーバー側のみで呼び出すようにしてください。解決しなければ、ずれて設置されたときのサーバー側のプレイヤー座標(
double
)と設置座標(int
)をログで出力し、F3で表示されるクライアント側のプレイヤー座標とともにお知らせください。座標がズレることなく、設置ができるようになりました!
毎回の質問に丁寧に答えて頂きありがとうございます。
キー入力イベントはどうやって作るんですか?
public void onArmorTickの中でKeyEventのif文を作りたいです。
追加した防具を装備していて特定のキーを押している間、何らかの処理を毎tick行いたいということですね。
ダッシュやしゃがみなど、プレイヤーの状態を条件にするなら、比較的簡単に実装できます。これらの状態はバニラで同期されているので、
onArmorTick
の中でEntityPlayer.isSprinting
やEntityPlayer.isSneaking
を呼べば判定できます。一方、キーの割り当てを追加したい場合は、実装が複雑になります。まずは、クライアントの初期化時に
ClientRegistry.registerKeyBinding
で新しいKeyBinding
を登録しておきます。KeyInputEvent
はクライアントのみで毎フレーム発生するので、これを受け取ってキーが押されているか判定します。この状態は防具のNBTなどに保存しておき、onArmorTick
で判定することになります。行いたい処理にサーバーの操作も必要な場合は、パケットを追加して状態の変化をサーバーに同期しなければなりません。キー割り当ての追加、パケットの追加に関しては解説記事がありません。イベントの利用については記事があるのでご覧ください。
Mekanismの1.7.10版には、防具のモード切替キーが押されたときに、装備されたジェットパックのモードを変更する処理があります。サーバーへの同期も行っていますが、飛行処理には
onArmorTick
を使っていません。MekanismKeyHandlerや関連するファイルをご覧ください。オファレンMODには設定キーを追加し、押しながら右クリックで詳細設定画面を開く処理があります。パケットの追加は参考になると思いますが、キー割り当ての登録や状態の保持の実装はあまりよいものではないのでMekanismをご覧ください。
1.12.2版のForgeの公式ドキュメント(英語)には、イベントの使い方やパケットの実装についての記事があります。
農業+食料系mod(pam's harvest craft的な)と工業mod(gregtech的な)物を1.12と1.16で自作で作りたいのですが情報がなく困っているので、できれば解説お願いします。
あと、なんでバージョンが異なると新しくバージョンに対応したものを作らないといけないのかも教えてください。
作りたいMODの内容に似た要素を持つMODがある場合は、そのコードを読んで参考にするとよいでしょう。ただし、コードやテクスチャなどには著作権があるので、複製が含まれるMODの配布などの際には複製元のライセンスに従う必要があります。
Minecraft本体のコードは、MODの作りやすさなどは考えずに更新されていきます。また、Forgeは前提MODとして、Minecraftのコードの上に、MODの開発を容易にしたり競合を避けたりするための様々な機能(レジストリやイベントなど)を提供しています。Minecraftのバージョンが上がると、Forgeはその変更に対応するとともに、MODが利用する機能の仕様を変えることがあります。各バージョンの入門編を見比べてみてもわかると思いますが、バージョンが変わると同じコードでは動かなくなる場合があるのです。
1.7.10でワールドのブロックを破壊する方法を教えてください。
設置するのはWorld.setblockだと知っています。
対象座標を空気(
Blocks.air
)に置き換えることでブロックを消せます。ドロップについては、ドアなどの自壊なら比較的単純ですが、プレイヤーの持つツールやエンチャントの情報などを考慮するなら少し複雑になります。オファレンMODのブロックブレーカー(
TileEntityBreaker
)でシルクタッチ破壊や経験値のドロップなどを行っているので、ご覧ください。返信ありがとうございます。
ブロックを消した後にドロップをしたいのですが、Block.airだと対象のブロックをairで置換することになってしまいます。
airで置換した後に、対象ブロックをドロップするにはどうすればいいですか?
すいませんm(_ _)m
最初に
World.getBlock
で対象座標のブロックの種類を取得してから、岩盤や空気、液体などでないか必要に応じて判定し、破壊処理を進めます。ブロックの種類を保持できていれば
World.setBlock
での置き換えとドロップアイテムの決定の順序は関係ないと思いますが、バニラの処理順序に従ったほうが安全だと思います。詳しくはバニラのブロック破壊処理(
Block.removedByPlayer
やBlock.harvestBlock
などの関連メソッド)や、オファレンブロックブレーカーのコードとその呼び出し先を読んでください。オファレンブロックブレーカーでは、World.setBlock
は直接実行せずに、バニラの破壊処理を呼び出して任せていると思います。ほんとありがとうございますm(*_ _)m
また詰まってしまい、1.7.10で指定した秒数後にarmorTickの中でsetblockをしたブロックを、指定秒後に破壊するプログラムを考えているんですが、探しても見つかりません!
やってみたことが、X_oldとかの変数をdoubleで作って、X(プレイヤーの座標が格納されている)の値をX_oldに格納して、if文でX_oldとXが違かったらSystem.out.printlnでログ出力するという文を作ったんですが、ArmorTick内なのでX_oldの値が常に変わってしまいます。
どうすればいいんでしょうか?
以前の質問の内容と合わせると、手に持った任意のブロックを足元に生成し続ける防具が実装済みで、その機能で生成されたブロックを時間経過で消滅させたいということでよろしいでしょうか。求める動作の実現は簡単ではないので、どのように実装するか検討しなければなりません。
「手に持った任意のブロック」という条件をあきらめれば、時間経過で消えるブロックを独自追加してそれを生成するという方法が考えられます。消滅するまでの時間を一定にするかランダムにするかで少し方法が変わりますが、前者ならボタンなどが、後者なら作物などが使っている機能を使えば、TileEntityなしで実装できるはずです。
「任意のブロック」の条件を見た目だけでよしとするなら、上記の実装に加えて、その独自追加ブロックの外見を任意のブロックに変更できるようにするという方法が考えられます。匠Craftの超硬質ブロックが外見を変更できる機能を実現していますが、TileEntityが必要になり、描画処理を扱わなければなりません。
「任意のブロック」の条件が必須なら、防具の機能で生成されたブロックの座標をワールドのセーブデータとともに保持する必要があるでしょう。この方法の具体例は示せませんが、ワールドの更新や保存、ブロックの破壊をイベント購読して管理する必要があると思います。
選んだ方針を伝えていただければ、さらに詳しい説明も可能です。
「任意のブロック」の条件付きで教えて頂けないでしょうか?
難しいと思いますが、完成したら達成感と楽しさがあると思うので教えてくださいm(_ _)m
求める動作を実現するためには、以下の課題があります。
課題1・2の解決として、static変数に
HashMap<int, HashMap<BlockPos, int>>
を用意し、onArmorTick
でブロックを置き換えた際にサーバー側でのみ置き換えた座標と破壊までの残り時間を記録します。1.7.10にはバニラのBlockPos
がないと思うので、オファレンMODのutil.BlockPos
を参考に作ってください。外側のHashMapはディメンションごとに情報を分けるために必要です。ディメンションIDはEntityPlayer.dimension
やWorld.provider.dimension
で取得できます。課題3の解決として、
World
の更新イベントをnet.minecraftforge.event.Event
の継承関係から「Ctrl + H」で探し、購読(Subscribe)します。WorldTickEvent
のような名前だと思います。ディメンションごとに呼ばれるので、サーバー側でのみ、先ほどの記録の残り時間の更新と破壊処理を行います。課題4の解決として、ワールド保存のイベントを探して購読します。セーブデータの切り替えや再読み込みに対応するため、記録をセーブフォルダ内の外部ファイルに保存して
clear
します。保存方法は、JacksonやGsonが依存関係に入っていたら使えるほか、バニラによるNBTのエンコード(文字列かbyte列)や自力でのエンコード実装などが考えられます。このファイルをワールド読み込みのイベントで読み込み、記録に反映させます。課題5の部分的な解決として、ブロック破壊のイベントでその座標を記録から削除します。課題5と生成やドロップの実装、プレイヤーの行動によっては意図せぬ挙動をすると思います。例えば、チェストが生成されてプレイヤーが中に何か入れたとき、時間になってチェストが破壊されると、破壊とドロップの実装次第で中身が消える可能性があります。
課題1・2のところで、HashMapはできたと思います。
static HashMap<Integer, HashMap<BlockPos, Integer>> BPos = new HashMap();
BlockPosは、オファレンModのBlockPosをNBTを抜かしてほぼ訳も分からずコピーしました。
それで次の、ディメンションIDというのがわかんなくて、別ファイルだと思うんですけどどうやってディメンションを書けばいいのかわかりません。
HashMapで、<Integer,HashMap<EntityPlayer.dimension,Integer>>かなと思ったんですけど、エラーが出てしまい、World.provider.dimensionに書き換えればできるかと思ったら、案の定できませんでした。
それでディメンションの書き方を探したんですけど、new dimention();みたいなのが出てきて、なにかにつけるのかなと思ったんですが、何につけるのかもわかりませんでした。
調べて分かったのが、ボタンやスイッチなどのサイズを変更できることコンポーネントでした。
onArmorTick
でsetBlock
などを実行した次の行に、以下のようなコードを書けば動くと思います。動作確認していないので間違っている部分があるかもしれません。ありがとうございます!!
問題なく動きました。
引き続き課題にチャレンジしたいと思います。