目次
無機能なIGNISアイテムを作る
早速新規Item制作に取りかかります。
この先のやり方は、各Modderによって千差万別だと思います。私の場合は、アイテムを作るならまず無機能なアイテムとして作り、レンダー周りを作成していきます。要するに、見た目から作る方向です。
機能を一つずつ追加していった方がデバッグプレイ時に確認しやすいためでもありますし、早い段階で面白おかしい物がゲーム中に出現した方がモチベーションが維持しやすいのでこうしています。自分が作った物をゲーム中で見られるようになるのは、やはり何度やっても面白いものです。
無機能アイテムの追加
アイテムを追加するための最低限のパーツは、
- Item.class継承クラス
- アイコン画像
- langファイル
この3つです。
ただし今回は、アイテムの見た目はTechneで作るモデルを適用するつもりでいますので、アイコンは仮の物を適当にあてがいます。
ItemIgnis.class
今回、IGNISはバニラの弓のクラスであるItemBow.classを継承させます。 ItemBow.classのメソッドには依存しない造りにするつもりなので、本来であれば不要なのですが、アイテムに弓用のエンチャントが付けられるかの可否がItemBow.classを継承しているかで判定されているというバニラの処理の都合により、こうしています。
package defeatedcrow.flamethrower.item; import net.minecraft.client.renderer.texture.IIconRegister; import net.minecraft.item.ItemBow; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; public class ItemIgnis extends ItemBow { public ItemIgnisClone() { // スタックサイズ上限。スタック出来ないようにするので1にする。 this.setMaxStackSize(1); // この武器はメタデータ(耐久値)を持たない。 this.setMaxDamage(0); // クラフトでの修理を不可に。 this.setNoRepair(); } // Icon用テクスチャファイルの指定はここで行う。 // ItemBowクラスでも、このメソッドでテクスチャパスを指定しているため、 // オーバーライドして上書きしなければならない。(コンストラクタでのテクスチャパス指定は推奨しない。) @Override @SideOnly(Side.CLIENT) public void registerIcons(IIconRegister par1IconRegister) { this.itemIcon = par1IconRegister.registerIcon("dcsflame:flamethrower"); } }
テクスチャ
仮なので、最低限何か分かる程度の簡単なテクスチャで済ませています。
テクスチャの置き場は、src/main/resources/assets/dcsflame/textures/itemsフォルダのflamethrower.pngです。 "dcsflame"の部分はテクスチャ登録時に登録するドメイン。
余談ですが、テクスチャの数が多い場合、itemsフォルダの中でフォルダ分けしたい場合は、以下のように記述することで設定できます。
.registerIcon("dcsflame:tools/flamethrower");
このように、コロン(:)の後にフォルダ名を入れてスラッシュ(/)を入れれば、上記の場合は「tools」フォルダ内のテクスチャファイルを読み込ませることが出来ます。
Langファイル
1.6までは言語設定はソース内で設定できていました。 1.7からはそれが不可になり、別途langファイルを用意します。基本的には、英語(en_US)と日本語(ja_JP)を用意すれば十分ではないかと思います。
作り方は、
- 新規のテキストファイルを作成
- 一行目を空行にし、二行目以降に言語設定を記述していく
- ファイルを保存し、「en_US.lang」など所定の名前・拡張子を.langに変更
- できたファイルをsrc/main/resources/assets/MODのIDを小文字にしたもの/langに配置
注意点として、テクスチャなどと違いドメインを変えられません。MODIDを小文字にしたもの(IGNISの場合は「dcsflame」)固定です。 また、日本語等の2バイト文字を含む場合は、扱うソフトに注意して下さい。
- ja_JP.lang
item.dcsflame.flamethrower.name=火炎放射器
- en_US.lang
item.dcsflame.flamethrower.name=Flamethrower
作者の経験談:もらい物の中国語LangファイルをうっかりTerraPadで開いた際、中国語の漢字の大部分がバケ文字に変わってしまったことがありました。 化けないテキストエディタを探して、結局一番良かったのがeclipse内蔵のテキストエディタだったりします。 eclipse上のエクスプローラでファイルを右クリック→次で開く→テキストエディターを選択で、内蔵のテキストエディタから開けます。
ただ、TerraPadのように保存時の文字コードを選択できる点も有利になることがあるので、テキストエディタは用途によって使い分けるのが良さそうです。
登録処理
ここまでで最低限必要なパーツは揃いましたので、メインクラスに登録します。
メインクラスに、このItemのインスタンスと、preInitメソッドでの登録メソッドを追加します。
- FlameCore.class
package defeatedcrow.flamethrower; import java.io.IOException; import net.minecraft.creativetab.CreativeTabs; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.config.Configuration; import net.minecraftforge.common.config.Property; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import cpw.mods.fml.common.Mod; import cpw.mods.fml.common.Mod.EventHandler; import cpw.mods.fml.common.Mod.Instance; import cpw.mods.fml.common.SidedProxy; import cpw.mods.fml.common.event.FMLInitializationEvent; import cpw.mods.fml.common.event.FMLPostInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import defeatedcrow.flamethrower.common.CommonProxyF; import defeatedcrow.flamethrower.item.ItemIgnis; /* * @Modアノテーションです。 * MODのID、表示名、起動に必要な他MODなどを記述するところ。 */ @Mod(modid = "DCsFlame", name = "Flamethrower", version = "1.7.10_1.0a", dependencies = "required*after:Forge@[10.13.2.1291,)") public class FlameCore { @SidedProxy(clientSide = "defeatedcrow.flamethrower.client.ClientProxyF", serverSide = "defeatedcrow.flamethrower.common.CommonProxyF") public static CommonProxyF proxy; @Instance("DCsFlame") public static FlameCore instance; public static Logger logger = LogManager.getLogger("Flamethrower"); // FlameThrowerアイテムのインスタンス。 public static Item flamethrower; @EventHandler public void preInit(FMLPreInitializationEvent event) { // conf Configuration cfg = new Configuration(event.getSuggestedConfigurationFile()); try { cfg.load(); } catch (Exception e) { e.printStackTrace(); } finally { cfg.save(); } // itemの登録。 this.flamethrower = new ItemIgnis().setCreativeTab(CreativeTabs.tabCombat).setUnlocalizedName( "dcsflame.flamethrower"); GameRegistry.registerItem(flamethrower, "dcsflame.flamethrower"); } @EventHandler public void init(FMLInitializationEvent event) throws IOException { } @EventHandler public void postInit(FMLPostInitializationEvent event) { } }
変更点は2点です。
インスタンスを宣言することと、
public static Item flamethrower;
登録メソッド。
this.flamethrower = new ItemIgnis().setCreativeTab(CreativeTabs.tabCombat).setUnlocalizedName( "dcsflame.flamethrower"); GameRegistry.registerItem(flamethrower, "dcsflame.flamethrower");
ここで、flamethrowerインスタンスに中身(ItemIgnis.class)を入れ、GameRegistry.registerItem()メソッドで登録します。
UnlocalizedNameはLangファイル翻訳時に参照するアイテムの名前です。
.registerItem()メソッドの第二引数に入れるStringはアイテムの登録名で、GameRegistry.findItem()でアイテムを取得するときに使用します。
デバッグプレイで確かめてみよう!
さて、ここまでで無機能なItemの登録処理を書いてみたので、実際出来ているのか起動して確かめてみることが出来ます。
実行の設定は特に弄らなくても、実行ボタンを押せば動くと思います。
(GradleStartなどの、このあたりの指定を開発環境作成時に自動でやってくれるので。)
うまく起動できていれば、mod一覧画面に作成したFlameThrowerMOD、そしてライブラリとして導入したdeobf版MODが表示されます。
ゲーム内でアイテムを取り出してみて、クラッシュしなければひとまずOK。
アイテムの外見を作る
アイテムの機能を作るには、今回の場合発射する弾Entityを作る必要があります。
そちらには少し時間がかかるので、先にアイテムの外見を作ることにしました。
Techneモデル
モデルは、私はTechneですべて作っています。
今回は別ゲームのネタだったので、そちらを起動して武器を眺めつつ、モデルをゴリゴリ作っていきます。
- モデルを作ってみよう
プロジェクトの新規作成を選び、ModelName(出力時に変更するので適当でOK)、キューブの組み合わせで作るつもりなのでBaseClassはModelBaseを選択し、TextureWidthとHeightは必要に応じて選択して下さい。今回はパーツが多いのでテクスチャは128x64にしています。パーツが多かったり、大きなパーツが複数含まれる場合は64x64以上にした方が良いと思います。(後から変えようとするのが少々面倒なので。)
画面はこんな感じ。
- ①Dimensions
- キューブのサイズです。
- ②Position
- キューブの回転中心です。モデルを回転させる際にバラバラにならないよう、私は基本は中心点(0, 16, 0)に統一して、部分的に回転させたい場合だけ回転中心を変えています。ただ、回転中心が異なるモデルでもバラバラにならないよう一つのモデルとして回転する方法もあります。
- ③Offset
- 回転中心からの距離です。
- ④TextureOffset
- テクスチャの展開図(⑥)上のテクスチャ参照位置です。異なるテクスチャを持つパーツのテクスチャが重ならないよう、ここで設定します。
- ⑤Rotation
- XYZ軸それぞれの回転です。②の回転中心を中心として、③の影響をうけつつ回転させます。
- ⑥テクスチャマップ
- テクスチャ展開図です。このモデルでは小さすぎてよく見えませんが、右下のキューブ(R等と書いてある)のように、面ごとにデフォルトで色が付いているのでテクスチャを描く際の参考になります。
- ⑦新規キューブ作成ボタン
- これを押すと新しいキューブを追加します。
Techneでモデルを作る際は注意することがあります。
まず、RotationでZ軸回転させてはいけない。不具合か分からないのですが、Techneとゲーム上で回転処理が異なっています。Z軸回転を使うと、ゲーム中では予期せぬ方向に曲がってしまうので、Z軸は封印してX軸とY軸の回転だけでやりくりする必要があります。
もう一つは、Techne上とゲーム中で、テクスチャの向きが若干異なること。Techne上でもテクスチャを読み込んで確認は出来ますが、凝った物を作るのであればゲーム中でも確認や調整は必要です。
- モデルを出力してみる
モデルは、まず完成したら上部のタブのFiles→Save Asから名前を付けて保存しましょう。拡張子は.tcnです。起動中はあるていど自動保存機能がありますが、保存を忘れると辛いことになります。
次に、Files→Export As→JavaでJavaソースとして出力します。これをMODのソースに組み込みます。
また、Files→Export As→Texture Mapでテクスチャ展開図を出力します。
Moddingで利用するのはJavaソースとテクスチャマップの2つです。
MODのソースに組み込む
- ModelIgnis.class
まず、空のModelIgnisクラスを作成します。場所は、MODのメインクラスまわりで考えておいたように、client/modelフォルダに作成します。
空のクラスを作ったら、Techneで出力したJavaソースをメモ帳などで開いて、中身を全て選択→このクラスにべたっと貼り付けます。
貼り付けたら、エラーが沢山出ていると思います。必要なクラスのインポート、パッケージやクラス名の修正、整形などを行います。
package defeatedcrow.flamethrower.client.model; import net.minecraft.client.model.ModelBase; import net.minecraft.client.model.ModelRenderer; import net.minecraft.entity.Entity; public class ModelIgnis extends ModelBase { // fields ModelRenderer Shape1; ModelRenderer Shape2; ModelRenderer Shape3; ModelRenderer Shape4; ModelRenderer Shape5; ModelRenderer Shape6; ModelRenderer Shape7; ModelRenderer Shape8; ModelRenderer Shape9; ModelRenderer Shape10; ModelRenderer Shape11; ModelRenderer Shape11_2; ModelRenderer Shape12; ModelRenderer Shape13; ModelRenderer Shape14; ModelRenderer Shape15; ModelRenderer Shape16; ModelRenderer Shape17; ModelRenderer Shape18; ModelRenderer Shape19; ModelRenderer Shape20; ModelRenderer Shape21; ModelRenderer Shape22; ModelRenderer Shape23; ModelRenderer Shape24; ModelRenderer Shape25; public ModelIgnis() { textureWidth = 128; textureHeight = 64; Shape1 = new ModelRenderer(this, 5, 0); Shape1.addBox(-0.5F, -4F, -6F, 1, 1, 12); Shape1.setRotationPoint(0F, 16F, 0F); Shape1.setTextureSize(64, 32); Shape1.mirror = true; setRotation(Shape1, 0F, 0F, 0F); Shape2 = new ModelRenderer(this, 30, 16); Shape2.addBox(-0.5F, -2.3F, -4F, 1, 2, 3); Shape2.setRotationPoint(0F, 16F, 0F); Shape2.setTextureSize(64, 32); Shape2.mirror = true; setRotation(Shape2, 0F, 0F, 0F); Shape3 = new ModelRenderer(this, 0, 16); Shape3.addBox(-1F, -3F, -1F, 2, 2, 6); Shape3.setRotationPoint(0F, 16F, 0F); Shape3.setTextureSize(64, 32); Shape3.mirror = true; setRotation(Shape3, 0F, 0F, 0F); Shape4 = new ModelRenderer(this, 0, 0); Shape4.addBox(0F, -5F, 6F, 1, 2, 6); Shape4.setRotationPoint(0F, 16F, 0F); Shape4.setTextureSize(64, 32); Shape4.mirror = true; setRotation(Shape4, 0F, 0F, 0F); Shape5 = new ModelRenderer(this, 36, 0); Shape5.addBox(-1F, -3F, 5F, 2, 1, 5); Shape5.setRotationPoint(0F, 16F, 0F); Shape5.setTextureSize(64, 32); Shape5.mirror = true; setRotation(Shape5, 0F, 0F, 0F); Shape6 = new ModelRenderer(this, 52, 0); Shape6.addBox(-0.5F, -1F, 8F, 1, 4, 1); Shape6.setRotationPoint(0F, 16F, 0F); Shape6.setTextureSize(64, 32); Shape6.mirror = true; setRotation(Shape6, 0.1745329F, 0F, 0F); Shape7 = new ModelRenderer(this, 32, 8); Shape7.addBox(-1F, -2F, 6F, 2, 3, 1); Shape7.setRotationPoint(0F, 16F, 0F); Shape7.setTextureSize(64, 32); Shape7.mirror = true; setRotation(Shape7, 0F, 0F, 0F); Shape8 = new ModelRenderer(this, 40, 8); Shape8.addBox(-0.5F, -1F, 7F, 1, 1, 2); Shape8.setRotationPoint(0F, 16F, 0F); Shape8.setTextureSize(64, 32); Shape8.mirror = true; setRotation(Shape8, 0F, 0F, 0F); Shape9 = new ModelRenderer(this, 58, 0); Shape9.addBox(-1F, 3F, 7.5F, 2, 2, 2); Shape9.setRotationPoint(0F, 16F, 0F); Shape9.setTextureSize(64, 32); Shape9.mirror = true; setRotation(Shape9, 0.1745329F, 0F, 0F); Shape10 = new ModelRenderer(this, 18, 16); Shape10.addBox(-1F, -0.7F, -1F, 2, 3, 3); Shape10.setRotationPoint(0F, 16F, 0F); Shape10.setTextureSize(64, 32); Shape10.mirror = true; setRotation(Shape10, 0F, 0F, 0F); Shape11 = new ModelRenderer(this, 68, 0); Shape11.addBox(-1.1F, -2F, -1F, 0, 4, 3); Shape11.setRotationPoint(0F, 16F, 0F); Shape11.setTextureSize(64, 32); Shape11.mirror = true; setRotation(Shape11, 0F, 0F, 0F); Shape11_2 = new ModelRenderer(this, 76, 0); Shape11_2.addBox(1.1F, -2F, -1F, 0, 4, 3); Shape11_2.setRotationPoint(0F, 16F, 0F); Shape11_2.setTextureSize(64, 32); Shape11_2.mirror = true; setRotation(Shape11_2, 0F, 0F, 0F); Shape12 = new ModelRenderer(this, 22, 0); Shape12.addBox(-1F, -4.5F, -10F, 2, 2, 4); Shape12.setRotationPoint(0F, 16F, 0F); Shape12.setTextureSize(64, 32); Shape12.mirror = true; setRotation(Shape12, 0F, 0F, 0F); Shape13 = new ModelRenderer(this, 48, 8); Shape13.addBox(0F, -3F, -5F, 0, 1, 4); Shape13.setRotationPoint(0F, 16F, 0F); Shape13.setTextureSize(64, 32); Shape13.mirror = true; setRotation(Shape13, 0F, 0F, 0F); Shape14 = new ModelRenderer(this, 0, 28); Shape14.addBox(-1.5F, 0F, -6F, 3, 3, 3); Shape14.setRotationPoint(0F, 16F, 0F); Shape14.setTextureSize(64, 32); Shape14.mirror = true; setRotation(Shape14, 1.047198F, 0F, 0F); Shape15 = new ModelRenderer(this, 14, 28); Shape15.addBox(-1F, 0.5F, -6.5F, 2, 2, 4); Shape15.setRotationPoint(0F, 16F, 0F); Shape15.setTextureSize(64, 32); Shape15.mirror = true; setRotation(Shape15, 1.047198F, 0F, 0F); Shape16 = new ModelRenderer(this, 54, 28); Shape16.addBox(0F, 2.2F, -0.5F, 1, 1, 1); Shape16.setRotationPoint(0F, 16F, 0F); Shape16.setTextureSize(64, 32); Shape16.mirror = true; setRotation(Shape16, 0F, 0F, 0F); Shape17 = new ModelRenderer(this, 28, 28); Shape17.addBox(-1F, -2F, -5F, 2, 2, 3); Shape17.setRotationPoint(0F, 16F, 0F); Shape17.setTextureSize(64, 32); Shape17.mirror = true; setRotation(Shape17, 0.7853982F, 0F, 0F); Shape18 = new ModelRenderer(this, 40, 28); Shape18.addBox(-0.5F, -1.5F, -3F, 1, 1, 2); Shape18.setRotationPoint(0F, 16F, 0F); Shape18.setTextureSize(64, 32); Shape18.mirror = true; setRotation(Shape18, 0.7853982F, 0F, 0F); Shape19 = new ModelRenderer(this, 48, 28); Shape19.addBox(-0.5F, 0F, -4F, 1, 3, 1); Shape19.setRotationPoint(0F, 16F, 0F); Shape19.setTextureSize(64, 32); Shape19.mirror = true; setRotation(Shape19, 0.5235988F, 0F, 0F); Shape20 = new ModelRenderer(this, 40, 16); Shape20.addBox(-0.5F, -2.5F, -8F, 1, 2, 1); Shape20.setRotationPoint(0F, 16F, 0F); Shape20.setTextureSize(128, 64); Shape20.mirror = true; setRotation(Shape20, 0F, 0F, 0F); Shape21 = new ModelRenderer(this, 40, 16); Shape21.addBox(-0.5F, -0.4F, -8F, 1, 2, 1); Shape21.setRotationPoint(0F, 16F, 0F); Shape21.setTextureSize(128, 64); Shape21.mirror = true; setRotation(Shape21, 0F, 0F, 0F); Shape22 = new ModelRenderer(this, 40, 16); Shape22.addBox(-0.5F, 4.7F, -2.5F, 1, 2, 1); Shape22.setRotationPoint(0F, 16F, 0F); Shape22.setTextureSize(128, 64); Shape22.mirror = true; setRotation(Shape22, -0.5235988F, 0F, 0F); Shape23 = new ModelRenderer(this, 40, 16); Shape23.addBox(-0.5F, 6.3F, 2.5F, 1, 2, 1); Shape23.setRotationPoint(0F, 16F, 0F); Shape23.setTextureSize(128, 64); Shape23.mirror = true; setRotation(Shape23, -1.22173F, 0F, 0F); Shape24 = new ModelRenderer(this, 40, 16); Shape24.addBox(-0.5F, -0.7F, -9F, 1, 2, 1); Shape24.setRotationPoint(0F, 16F, 0F); Shape24.setTextureSize(128, 64); Shape24.mirror = true; setRotation(Shape24, 0.5235988F, 0F, 0F); Shape25 = new ModelRenderer(this, 40, 16); Shape25.addBox(-0.5F, 2F, -8F, 1, 2, 1); Shape25.setRotationPoint(0F, 16F, 0F); Shape25.setTextureSize(128, 64); Shape25.mirror = true; setRotation(Shape25, -0.0523599F, 0F, 0F); } @Override public void render(Entity entity, float f, float f1, float f2, float f3, float f4, float f5) { super.render(entity, f, f1, f2, f3, f4, f5); setRotationAngles(f, f1, f2, f3, f4, f5); Shape1.render(f5); Shape2.render(f5); Shape3.render(f5); Shape4.render(f5); Shape5.render(f5); Shape6.render(f5); Shape7.render(f5); Shape8.render(f5); Shape9.render(f5); Shape10.render(f5); Shape11.render(f5); Shape11_2.render(f5); Shape12.render(f5); Shape13.render(f5); Shape14.render(f5); Shape15.render(f5); Shape16.render(f5); Shape17.render(f5); Shape18.render(f5); Shape19.render(f5); Shape20.render(f5); Shape21.render(f5); Shape22.render(f5); Shape23.render(f5); Shape24.render(f5); Shape25.render(f5); } private void setRotation(ModelRenderer model, float x, float y, float z) { model.rotateAngleX = x; model.rotateAngleY = y; model.rotateAngleZ = z; } public void setRotationAngles(float f, float f1, float f2, float f3, float f4, float f5) { // Techneの出力ソースではここの第六引数がないためエラーになっています。 // 特に問題なければ、nullを追加してエラーを消します。 super.setRotationAngles(f, f1, f2, f3, f4, f5, null); } }
- テクスチャの作成
今回は、弾などと同じentityフォルダにテクスチャを格納しました。テクスチャの置き場は、src/main/resources/assets/dcsflame/textures/entityフォルダのentity_ignis.pngです。開発当初の構想でEntityとしての追加も考えていたためにEntityフォルダに入っていますが、こちらはItem追加時と違い、テクスチャの場所はフルパスで指定するのでdcsflame以下であれば任意の場所で大丈夫です。
作成にあたってはあまり決まりなどはありません。それっぽく見えるよう、好きなようにテクスチャを描いてみて下さい。
完成品はこんな感じ。
IItemRendererの実装
作成したモデルやテクスチャをゲーム内のアイテムに反映させるには、IItemRendererインターフェイスを実装したクラスを作成します。