MOD作成環境が整ったのでちょっと作ってみる。ちなみにMac OS X Lionで。JavaとEclipseは導入済み。ModLoaderを前提とするMOD開発なので、当然ModLoader導入済み。
ある程度のjava用語を使いまくるのですが、そういうものだと思ってしまった方が吉。
Windowsの場合、.shを.batと読み替えコンソールをコマンドプロンプトと読み替えれば意味はほぼ同じです。
.batの実行はダブルクリックでいいのでコンソールに頼ることはなさそうなのですが。
# わかる人向け : インスタンスはオブジェクトに言い換えます。どちらもほぼ同義であり、一般に「モノ」としてわかりやすいと思う言葉を選択。
ある程度のjava用語を使いまくるのですが、そういうものだと思ってしまった方が吉。
Windowsの場合、.shを.batと読み替えコンソールをコマンドプロンプトと読み替えれば意味はほぼ同じです。
.batの実行はダブルクリックでいいのでコンソールに頼ることはなさそうなのですが。
# わかる人向け : インスタンスはオブジェクトに言い換えます。どちらもほぼ同義であり、一般に「モノ」としてわかりやすいと思う言葉を選択。
Minecraft Coder Pack(MCP)から、MCP5.0のダウンロード。
ダウンロードしたら解答して解凍したディレクトリ(以下MCP_ROOT)にあるjarsディレクトリにminecraftのbinディレクトリをそのままコピーする。
ここからコンソールで作業。
ダウンロードしたら解答して解凍したディレクトリ(以下MCP_ROOT)にあるjarsディレクトリにminecraftのbinディレクトリをそのままコピーする。
ここからコンソールで作業。
$ cd MCP_ROOT $ ./decompile.shカレントディレクトリを移動。移動したディレクトリにあるdecompile.shを実行。デコンパイル終了。次はEclipseでの作業。
ModLoaderの仕様としてmod_で始まるクラスを読み込む。なのでこのクラスを作る。
# 頭文字が小文字なのが若干気になるのはJavaやったことある人ならわかると思う。たいした問題ではない。
今回は、無機能ブロックなのでmod_AfunctionBlockとした。英語には突っ込んではならない。
Eclipseの使い方についても省略。スーパークラスはBaseModにしておく。
実装が必要なメソッドは既に用意されているのでこれに機能を書き込んでいくことになる。
getVersionとloadメソッドがそれとなる。製品版からコンストラクタに記述していたものをloadメソッドに記述するようになったのかな?
以前のソースコードはそうだったのでそうなんだろうと思っておく。今回初めてのMOD作成なので間違いがあるかもしれない。
とりあえず、versionメソッドは適当な文字列を返すように。minecraftのバージョン_MODのバージョンあたりがわかりやすいかな。
なので、今回は1.0.0_01とでも。
# 頭文字が小文字なのが若干気になるのはJavaやったことある人ならわかると思う。たいした問題ではない。
今回は、無機能ブロックなのでmod_AfunctionBlockとした。英語には突っ込んではならない。
Eclipseの使い方についても省略。スーパークラスはBaseModにしておく。
実装が必要なメソッドは既に用意されているのでこれに機能を書き込んでいくことになる。
getVersionとloadメソッドがそれとなる。製品版からコンストラクタに記述していたものをloadメソッドに記述するようになったのかな?
以前のソースコードはそうだったのでそうなんだろうと思っておく。今回初めてのMOD作成なので間違いがあるかもしれない。
とりあえず、versionメソッドは適当な文字列を返すように。minecraftのバージョン_MODのバージョンあたりがわかりやすいかな。
なので、今回は1.0.0_01とでも。
minecraftプログラム中でブロックを識別するためのIDを設定する。
このIDの範囲は1〜255で、そのうちで現在minecraftで設定されているIDは1〜115となっている。これ以外なら自由に振っていいのだが……
星の数ほど(ってほどないけど)あるMODの中で一意な値を設定するのは無理がある。そのため、これはMOD使用者が自由に設定できるように外だししておく必要がある。
それを支援するのがModLoaderにはすでにあり、MLPropアノテーションとして備わっている。
が、これは通常の状態ではエラーになるのでModLoaderの公開サイトから落とす必要がある。
ModLoaderを落としてきたリンクの横にDecompile FixesというのがあるからそれをDLしてデコンパイルソースコードに上書きする。
その後、そのファイルの1行目のスラッシュ2つを消して保存。これでMLPropアノテーションが有効になる。
先に作成してたmod_AfunctionBlockに公開クラスフィールドとしてint型で適当な名前(今回はafunctionBlockId)の変数を定義。
この上に先ほどのMLPropアノテーションを適用させる。
この調子でどんどんと先に進めよう。
このIDの範囲は1〜255で、そのうちで現在minecraftで設定されているIDは1〜115となっている。これ以外なら自由に振っていいのだが……
星の数ほど(ってほどないけど)あるMODの中で一意な値を設定するのは無理がある。そのため、これはMOD使用者が自由に設定できるように外だししておく必要がある。
それを支援するのがModLoaderにはすでにあり、MLPropアノテーションとして備わっている。
が、これは通常の状態ではエラーになるのでModLoaderの公開サイトから落とす必要がある。
ModLoaderを落としてきたリンクの横にDecompile FixesというのがあるからそれをDLしてデコンパイルソースコードに上書きする。
その後、そのファイルの1行目のスラッシュ2つを消して保存。これでMLPropアノテーションが有効になる。
先に作成してたmod_AfunctionBlockに公開クラスフィールドとしてint型で適当な名前(今回はafunctionBlockId)の変数を定義。
この上に先ほどのMLPropアノテーションを適用させる。
@MLProp(min=1, max=255) public static int afunctionBlockId = 201;アノテーションのパラメータとしてminとmaxを設定。これはBlockIdの最小最大値。
この調子でどんどんと先に進めよう。
実はまだminecraft内部に踏み込んでいないModLoader部分のコードしか書いてない。
mod_で始まるクラスというのはModLoaderがminecraftにMODを適用させるためのインタフェイスでしかない。
つまり実際のminecraftで使われるクラスではないということ。ここからその部分に手を付ける。
新たにBlockAfunctionというクラスを作る。スーパークラスはBlockクラス。このクラスはminecraftでブロックとして生成されているすべてのものを抽象化したクラス。
MODとは関係ないが、全てのブロックをブロックとして認識できるのはこのクラスがあるから。明確に原木ブロックだとか区別する必要がない部分ではそれがブロック以外の何者でもないのである。
MODでブロックを簡単に追加できるのも、この抽象化のおかげである。どんなブロックが追加されようとも、minecratはそれをブロックとして扱うだけなのだから。
閑話休題。
クラスを作ったらコンストラクタの記述。こんな感じに。
super(...)はスーパークラスのコンストラクタを実行するという意味。少なくともスーパークラスで実装されているコンストラクタがある以上、
それはそのクラスとして必要な処理であるはずなので、大抵はサブクラス(スーパークラスを継承したクラス)で呼び出される。
今回はこれだけなのだけど、ほかにも書く場合は、super(...)は最初に書く必要があり、後に書くことはできないことを覚えておこう。Javaの約束。
Materialはおそらくブロックの基本的な性質を定義するクラスで、groundは土の性質を示しているオブジェクトである。
中身を見たけど、マップ上で表示される色とかが定義されてた。あとは燃えるかとかそんなの。
ひとまずここでは作成するブロックが既存のどのブロックに近いかで選択するといいと思う。詳しくはソースを見よう。
次は壊したとき、どのアイテムになるかとその個数を記述する。
どのアイテムになるかはidDropped(int, Random, int)メソッドの戻り値で、個数はquantityDropped(Random random)メソッドの戻り値で決まる。
引数にRandomオブジェクトをとる理由については、プログラム中でのRandomオブジェクトを共有できるようにするためかな。
それぞれのメソッドでいちいちオブジェクト生成を行うのは愚策だし、シングルトンも良策とはいえない(たかが乱数生成機だしスコープ広げるだけだし融通きかない)。
idDroppedメソッドは引数が3つあることに注意。自分が参考にした既存の情報ソースでは最後のintはないので、いつだかのアップデートで増えたことになる。
今回のブロックは壊したらそのままそのブロックをアイテム化するように自分自身のIDを返却する。
どうやらブロックIDはアイテムIDも兼ねているようで、設置すると単純にブロックとなるものは自分自身のIDを返却するだけで問題ないようである。
quantityDroppedメソッドは1を返却するようにする。今回のように壊して出たアイテムを設置すると壊したブロックになるようなものの場合、
2以上を設定すると壊すたびに増えていくので基本は1となる。
mod_で始まるクラスというのはModLoaderがminecraftにMODを適用させるためのインタフェイスでしかない。
つまり実際のminecraftで使われるクラスではないということ。ここからその部分に手を付ける。
新たにBlockAfunctionというクラスを作る。スーパークラスはBlockクラス。このクラスはminecraftでブロックとして生成されているすべてのものを抽象化したクラス。
MODとは関係ないが、全てのブロックをブロックとして認識できるのはこのクラスがあるから。明確に原木ブロックだとか区別する必要がない部分ではそれがブロック以外の何者でもないのである。
MODでブロックを簡単に追加できるのも、この抽象化のおかげである。どんなブロックが追加されようとも、minecratはそれをブロックとして扱うだけなのだから。
閑話休題。
クラスを作ったらコンストラクタの記述。こんな感じに。
public BlockAfunction(int i, int j) { super(i, j, Material.ground); }さらっと使ってるコンストラクタという言葉ですが、クラスをもとに作られるデータであるオブジェクトが生成される時に最初に実行される処理です。
super(...)はスーパークラスのコンストラクタを実行するという意味。少なくともスーパークラスで実装されているコンストラクタがある以上、
それはそのクラスとして必要な処理であるはずなので、大抵はサブクラス(スーパークラスを継承したクラス)で呼び出される。
今回はこれだけなのだけど、ほかにも書く場合は、super(...)は最初に書く必要があり、後に書くことはできないことを覚えておこう。Javaの約束。
Materialはおそらくブロックの基本的な性質を定義するクラスで、groundは土の性質を示しているオブジェクトである。
中身を見たけど、マップ上で表示される色とかが定義されてた。あとは燃えるかとかそんなの。
ひとまずここでは作成するブロックが既存のどのブロックに近いかで選択するといいと思う。詳しくはソースを見よう。
次は壊したとき、どのアイテムになるかとその個数を記述する。
どのアイテムになるかはidDropped(int, Random, int)メソッドの戻り値で、個数はquantityDropped(Random random)メソッドの戻り値で決まる。
引数にRandomオブジェクトをとる理由については、プログラム中でのRandomオブジェクトを共有できるようにするためかな。
それぞれのメソッドでいちいちオブジェクト生成を行うのは愚策だし、シングルトンも良策とはいえない(たかが乱数生成機だしスコープ広げるだけだし融通きかない)。
idDroppedメソッドは引数が3つあることに注意。自分が参考にした既存の情報ソースでは最後のintはないので、いつだかのアップデートで増えたことになる。
今回のブロックは壊したらそのままそのブロックをアイテム化するように自分自身のIDを返却する。
どうやらブロックIDはアイテムIDも兼ねているようで、設置すると単純にブロックとなるものは自分自身のIDを返却するだけで問題ないようである。
quantityDroppedメソッドは1を返却するようにする。今回のように壊して出たアイテムを設置すると壊したブロックになるようなものの場合、
2以上を設定すると壊すたびに増えていくので基本は1となる。
public int idDropped(int i, Random random, int j) { return this.blockID; } public int quantityDropped(Random random) { return 1; }これでブロックの基本的な部分は実装が完了したと思われる。
表現としてこれが正しいかは別にして、今のままではプログラムに存在するが表に出てこない隠しデータとなるので、
ModLoaderを通じてユーザが使える形にもっていく。
mod_AfunctionBlockクラスのloadメソッドにその部分を記述する。
その前にクラスフィールドにBlock型の変数を定義。なぜか公開された変数。非公開だとどういう問題があるのかわからない。
説明が長いのでコードにすると...
このオブジェクトにModLoaderを用いて以下の処理。
minecraftに登録する。
テクスチャを設定する。
名前を追加する。
第二引数は実際のテクスチャのパス。テスト時はbin/minecraftディレクトリの配下にあるafunc/afunc.pngをテクスチャに設定することになる。
最後に、ゲーム内で作れるようにレシピを登録する。
微妙に厄介なのでコードを先に示す。そして説明は簡単にすませる。レシピだけでもかなりのものができそうなので。
ItemStackオブジェクトは実際に作られるアイテム。生成されたアイテムはインベントリスタック状態なのでItemStackとなる(のだと思う)。
Object配列は実際のレシピ。"XXX"はアイテムの配置。同じ文字のものは同じ素材となる。
Character.valueOf('X'), Block.dirtが文字に対応する素材の指定。今回は土を素材としてブロックを作る。
これで一通りの作業が終了したことになる。
ModLoaderを通じてユーザが使える形にもっていく。
mod_AfunctionBlockクラスのloadメソッドにその部分を記述する。
その前にクラスフィールドにBlock型の変数を定義。なぜか公開された変数。非公開だとどういう問題があるのかわからない。
public static Block afunctionBlock;loadメソッドで、その変数に先ほど作成したBlockAfunctionクラスのオブジェクトを生成し、そのオブジェクトのsetBlockNameメソッドをブロック名を指定して呼び出した戻り値を代入。
説明が長いのでコードにすると...
afunctionBlock = new BlockAfunction(afunctionBlockId, 0).setBlockName("AfunctionBlock");ここから駆け足で。
このオブジェクトにModLoaderを用いて以下の処理。
minecraftに登録する。
テクスチャを設定する。
名前を追加する。
ModLoader.RegisterBlock(afunctionBlock); afunctionBlock.blockIndexInTexture = ModLoader.AddOverride("/terrain.png", "/afunc/afunc.png"); ModLoader.AddName(afunctionBlock, "AfunctionBlock");テクスチャの設定は、いまいち詳しい処理はわからないが、/terrain.pngはminecraftのテクスチャ画像ファイルでjarファイルに格納されている。よってこれは固定。
第二引数は実際のテクスチャのパス。テスト時はbin/minecraftディレクトリの配下にあるafunc/afunc.pngをテクスチャに設定することになる。
最後に、ゲーム内で作れるようにレシピを登録する。
微妙に厄介なのでコードを先に示す。そして説明は簡単にすませる。レシピだけでもかなりのものができそうなので。
ModLoader.AddRecipe(new ItemStack(afunctionBlock, 1), new Object[] {"XXX", Character.valueOf('X'), Block.dirt});軽く説明すると、今回作ったブロックは土1つから1つ作れる。
ItemStackオブジェクトは実際に作られるアイテム。生成されたアイテムはインベントリスタック状態なのでItemStackとなる(のだと思う)。
Object配列は実際のレシピ。"XXX"はアイテムの配置。同じ文字のものは同じ素材となる。
Character.valueOf('X'), Block.dirtが文字に対応する素材の指定。今回は土を素材としてブロックを作る。
これで一通りの作業が終了したことになる。
実際にコンパイルしてみる。decompile.shを実行した手順と同様に、コンソールでrecompile.shを実行する。
エラーがある場合、コードのどこかにミスがあるはずなので、修正する。エラーがなければ成功なので、テスト行程へ移れる。
テストはまたも同様にstartclient.shを実行する。
ここで問題なくプレイできれば最初の壁はクリアです。
次の壁は実際のそのブロックを作ったときにどうなるかです。
適当に素材を集めて、作成して適当に触ってみて問題なければとりあえずはOKかと。
エラーがある場合、コードのどこかにミスがあるはずなので、修正する。エラーがなければ成功なので、テスト行程へ移れる。
テストはまたも同様にstartclient.shを実行する。
ここで問題なくプレイできれば最初の壁はクリアです。
次の壁は実際のそのブロックを作ったときにどうなるかです。
適当に素材を集めて、作成して適当に触ってみて問題なければとりあえずはOKかと。
配布ファイルを実際にminecraftに適用して動作するかを確認する。このときバックアップはとっておくように。
ここでも問題がなければ実際に配布が可能な状況となる。今回は配布できるようなものじゃないけど。
ここでも問題がなければ実際に配布が可能な状況となる。今回は配布できるようなものじゃないけど。
今回作成したソースコードの全容は以下の通り
mod_AfunctionBlock.java
mod_AfunctionBlock.java
package net.minecraft.src; public class mod_AfunctionBlock extends BaseMod { @MLProp(min=1, max=255) public static int afunctionBlockId = 201; public static Block afunctionBlock; @Override public String getVersion() { return "1.0.0_01"; } @Override public void load() { afunctionBlock = new BlockAfunction( afunctionBlockId, 0).setBlockName("AfunctionBlock"); ModLoader.RegisterBlock(afunctionBlock); afunctionBlock.blockIndexInTexture = ModLoader.addOverride("/terrain.png", "/afunc/afunc.png"); ModLoader.AddName(afunctionBlock, "AfunctionBlock"); ModLoader.AddRecipe(new ItemStack(afunctionBlock, 1), new Object[] {"XXX", Character.valueOf('X'), Block.dirt}); } }BlockAfunction.java
package net.minecraft.src; import java.util.Random; public class BlockAfunction extends Block { public BlockAfunction(int i, int j) { super(i, j, Material.ground); } public int idDropped(int i, Random random, int j) { return this.blockID; } public int quantityDropped(Random random) { return 1; } }
コメントをかく