Defeatedcrow (トーク | 投稿記録) (ページの作成:「=無機能なIGNISアイテムを作る= 早速新規Item制作に取りかかります。<br /> この先のやり方は、各Modderによって千差万別だと思い...」) |
Defeatedcrow (トーク | 投稿記録) |
||
(同じ利用者による、間の7版が非表示) | |||
1行目: | 1行目: | ||
+ | <[[IGNIS制作記録|目次に戻る]]> | ||
=無機能なIGNISアイテムを作る= | =無機能なIGNISアイテムを作る= | ||
早速新規Item制作に取りかかります。<br /> | 早速新規Item制作に取りかかります。<br /> | ||
12行目: | 13行目: | ||
ただし今回は、アイテムの見た目はTechneで作るモデルを適用するつもりでいますので、アイコンは仮の物を適当にあてがいます。 | ただし今回は、アイテムの見た目はTechneで作るモデルを適用するつもりでいますので、アイコンは仮の物を適当にあてがいます。 | ||
− | + | ===ItemIgnis.class=== | |
今回、IGNISはバニラの弓のクラスであるItemBow.classを継承させます。 | 今回、IGNISはバニラの弓のクラスであるItemBow.classを継承させます。 | ||
30行目: | 31行目: | ||
// スタックサイズ上限。スタック出来ないようにするので1にする。 | // スタックサイズ上限。スタック出来ないようにするので1にする。 | ||
this.setMaxStackSize(1); | this.setMaxStackSize(1); | ||
− | // | + | // 耐久値がないとエンチャントできないと判明したので、気持ちだけ付けた |
− | this.setMaxDamage( | + | this.setMaxDamage(4); |
// クラフトでの修理を不可に。 | // クラフトでの修理を不可に。 | ||
this.setNoRepair(); | this.setNoRepair(); | ||
43行目: | 44行目: | ||
public void registerIcons(IIconRegister par1IconRegister) { | public void registerIcons(IIconRegister par1IconRegister) { | ||
this.itemIcon = par1IconRegister.registerIcon("dcsflame:flamethrower"); | this.itemIcon = par1IconRegister.registerIcon("dcsflame:flamethrower"); | ||
+ | } | ||
+ | |||
+ | // 手に持ったときの見た目が変わります。IItemRenderer調整時にも影響します。 | ||
+ | @Override | ||
+ | @SideOnly(Side.CLIENT) | ||
+ | public boolean isFull3D() { | ||
+ | return true; | ||
} | } | ||
} | } | ||
</source> | </source> | ||
+ | |||
+ | ===テクスチャ=== | ||
+ | |||
+ | https://dl-web.dropbox.com/s/gvtupvb3p5iif8d/flamethrower.png<br /> | ||
+ | 仮なので、最低限何か分かる程度の簡単なテクスチャで済ませています。<br /> | ||
+ | |||
+ | テクスチャの置き場は、'''src/main/resources/assets/dcsflame/textures/items'''フォルダの'''flamethrower.png'''です。 | ||
+ | "dcsflame"の部分はテクスチャ登録時に登録するドメイン。 | ||
+ | |||
+ | 余談ですが、テクスチャの数が多い場合、itemsフォルダの中でフォルダ分けしたい場合は、以下のように記述することで設定できます。 | ||
+ | <source lang="java"> | ||
+ | .registerIcon("dcsflame:tools/flamethrower"); | ||
+ | </source> | ||
+ | このように、コロン(:)の後にフォルダ名を入れてスラッシュ(/)を入れれば、上記の場合は「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 | ||
+ | <source lang="java"> | ||
+ | |||
+ | item.dcsflame.flamethrower.name=火炎放射器 | ||
+ | </source> | ||
+ | |||
+ | *en_US.lang | ||
+ | <source lang="java"> | ||
+ | |||
+ | item.dcsflame.flamethrower.name=Flamethrower | ||
+ | </source> | ||
+ | |||
+ | 作者の経験談:もらい物の中国語LangファイルをうっかりTerraPadで開いた際、中国語の漢字の大部分がバケ文字に変わってしまったことがありました。 | ||
+ | 化けないテキストエディタを探して、結局一番良かったのがeclipse内蔵のテキストエディタだったりします。 | ||
+ | eclipse上のエクスプローラでファイルを右クリック→次で開く→テキストエディターを選択で、内蔵のテキストエディタから開けます。 | ||
+ | |||
+ | ただ、TerraPadのように保存時の文字コードを選択できる点も有利になることがあるので、テキストエディタは用途によって使い分けるのが良さそうです。 | ||
+ | |||
+ | ===登録処理=== | ||
+ | |||
+ | ここまでで最低限必要なパーツは揃いましたので、メインクラスに登録します。 | ||
+ | |||
+ | メインクラスに、このItemのインスタンスと、preInitメソッドでの登録メソッドを追加します。 | ||
+ | *FlameCore.class | ||
+ | <source lang="java"> | ||
+ | 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) { | ||
+ | |||
+ | } | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | 変更点は2点です。 | ||
+ | |||
+ | インスタンスを宣言することと、 | ||
+ | <source lang="java"> | ||
+ | public static Item flamethrower; | ||
+ | </source> | ||
+ | |||
+ | 登録メソッド。 | ||
+ | <source lang="java"> | ||
+ | this.flamethrower = new ItemIgnis().setCreativeTab(CreativeTabs.tabCombat).setUnlocalizedName( | ||
+ | "dcsflame.flamethrower"); | ||
+ | GameRegistry.registerItem(flamethrower, "dcsflame.flamethrower"); | ||
+ | </source> | ||
+ | ここで、flamethrowerインスタンスに中身(ItemIgnis.class)を入れ、GameRegistry.registerItem()メソッドで登録します。<br /> | ||
+ | UnlocalizedNameはLangファイル翻訳時に参照するアイテムの名前です。<br /> | ||
+ | .registerItem()メソッドの第二引数に入れるStringはアイテムの登録名で、GameRegistry.findItem()でアイテムを取得するときに使用します。<br /> | ||
+ | |||
+ | ==デバッグプレイで確かめてみよう!== | ||
+ | さて、ここまでで無機能なItemの登録処理を書いてみたので、実際出来ているのか起動して確かめてみることが出来ます。<br /> | ||
+ | |||
+ | https://dl-web.dropbox.com/s/r4ymtk3d8g92ch6/jikkoubutton.PNG<br /> | ||
+ | 実行の設定は特に弄らなくても、実行ボタンを押せば動くと思います。 | ||
+ | (GradleStartなどの、このあたりの指定を開発環境作成時に自動でやってくれるので。) | ||
+ | |||
+ | https://dl-web.dropbox.com/s/x2msdni57u65ou6/modlist1.PNG<br /> | ||
+ | うまく起動できていれば、mod一覧画面に作成したFlameThrowerMOD、そしてライブラリとして導入したdeobf版MODが表示されます。 | ||
+ | |||
+ | https://dl-web.dropbox.com/s/i3m3reg43uwxbqe/ignisItem.png<br /> | ||
+ | ゲーム内でアイテムを取り出してみて、クラッシュしなければひとまずOK。 | ||
+ | <br /> | ||
+ | |||
+ | ==アイテムの外見を作る== | ||
+ | |||
+ | アイテムの機能を作るには、今回の場合発射する弾Entityを作る必要があります。<br /> | ||
+ | そちらには少し時間がかかるので、先にアイテムの外見を作ることにしました。<br /> | ||
+ | |||
+ | ===Techneモデル=== | ||
+ | |||
+ | モデルは、私はTechneですべて作っています。<br /> | ||
+ | 今回は別ゲームのネタだったので、そちらを起動して武器を眺めつつ、モデルをゴリゴリ作っていきます。 | ||
+ | |||
+ | *モデルを作ってみよう | ||
+ | プロジェクトの新規作成を選び、'''ModelName'''(出力時に変更するので適当でOK)、キューブの組み合わせで作るつもりなので'''BaseClass'''はModelBaseを選択し、'''TextureWidth'''と'''Height'''は必要に応じて選択して下さい。今回はパーツが多いのでテクスチャは128x64にしています。パーツが多かったり、大きなパーツが複数含まれる場合は64x64以上にした方が良いと思います。(後から変えようとするのが少々面倒なので。)<br /> | ||
+ | 画面はこんな感じ。<br /> | ||
+ | https://dl-web.dropbox.com/s/l6sibpfcgu68exu/techne2.png<br /> | ||
+ | ;①Dimensions:キューブのサイズです。 | ||
+ | ;②Position:キューブの回転中心です。モデルを回転させる際にバラバラにならないよう、私は基本は中心点(0, 16, 0)に統一して、部分的に回転させたい場合だけ回転中心を変えています。ただ、回転中心が異なるモデルでもバラバラにならないよう一つのモデルとして回転する方法もあります。 | ||
+ | ;③Offset:回転中心からの距離です。 | ||
+ | ;④TextureOffset:テクスチャの展開図(⑥)上のテクスチャ参照位置です。異なるテクスチャを持つパーツのテクスチャが重ならないよう、ここで設定します。 | ||
+ | ;⑤Rotation:XYZ軸それぞれの回転です。②の回転中心を中心として、③の影響をうけつつ回転させます。 | ||
+ | ;⑥テクスチャマップ:テクスチャ展開図です。このモデルでは小さすぎてよく見えませんが、右下のキューブ(R等と書いてある)のように、面ごとにデフォルトで色が付いているのでテクスチャを描く際の参考になります。 | ||
+ | ;⑦新規キューブ作成ボタン:これを押すと新しいキューブを追加します。 | ||
+ | |||
+ | Techneでモデルを作る際は注意することがあります。<br /> | ||
+ | まず、'''RotationでZ軸回転させてはいけない。'''不具合か分からないのですが、Techneとゲーム上で回転処理が異なっています。Z軸回転を使うと、ゲーム中では予期せぬ方向に曲がってしまうので、Z軸は封印してX軸とY軸の回転だけでやりくりする必要があります。<br /> | ||
+ | もう一つは、'''Techne上とゲーム中で、テクスチャの向きが若干異なる'''こと。Techne上でもテクスチャを読み込んで確認は出来ますが、凝った物を作るのであればゲーム中でも確認や調整は必要です。 | ||
+ | |||
+ | *モデルを出力してみる | ||
+ | モデルは、まず完成したら上部のタブの'''Files→Save As'''から名前を付けて保存しましょう。拡張子は.tcnです。起動中はあるていど自動保存機能がありますが、保存を忘れると辛いことになります。<br /> | ||
+ | 次に、'''Files→Export As→Java'''でJavaソースとして出力します。これをMODのソースに組み込みます。<br /> | ||
+ | また、'''Files→Export As→Texture Map'''でテクスチャ展開図を出力します。<br /> | ||
+ | |||
+ | Moddingで利用するのは'''Javaソース'''と'''テクスチャマップ'''の2つです。 | ||
+ | |||
+ | ===MODのソースに組み込む=== | ||
+ | *ModelIgnis.class | ||
+ | まず、空のModelIgnisクラスを作成します。場所は、[[MODのメインクラスまわり]]で考えておいたように、client/modelフォルダに作成します。<br /> | ||
+ | 空のクラスを作ったら、Techneで出力したJavaソースをメモ帳などで開いて、中身を全て選択→このクラスにべたっと貼り付けます。<br /> | ||
+ | 貼り付けたら、エラーが沢山出ていると思います。必要なクラスのインポート、パッケージやクラス名の修正、整形などを行います。<br /> | ||
+ | |||
+ | <source lang="java"> | ||
+ | package defeatedcrow.flamethrower.client.model; | ||
+ | |||
+ | import net.minecraft.client.model.ModelBase; | ||
+ | import net.minecraft.client.model.ModelRenderer; | ||
+ | import net.minecraft.entity.Entity; | ||
+ | |||
+ | @SideOnly(Side.CLIENT) | ||
+ | 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); | ||
+ | } | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | *テクスチャの作成 | ||
+ | |||
+ | 今回は、弾などと同じentityフォルダにテクスチャを格納しました。テクスチャの置き場は、'''src/main/resources/assets/dcsflame/textures/entity'''フォルダの'''entity_ignis.png'''です。開発当初の構想でEntityとしての追加も考えていたためにEntityフォルダに入っていますが、こちらはItem追加時と違い、テクスチャの場所はフルパスで指定するのでdcsflame以下であれば任意の場所で大丈夫です。<br /> | ||
+ | |||
+ | 作成にあたってはあまり決まりなどはありません。それっぽく見えるよう、好きなようにテクスチャを描いてみて下さい。 | ||
+ | |||
+ | 完成品はこんな感じ。<br /> | ||
+ | https://dl-web.dropbox.com/s/62lfil2z7ym6117/entity_ignis.png<br /> | ||
+ | |||
+ | ===IItemRendererの実装=== | ||
+ | 作成したモデルやテクスチャをゲーム内のアイテムに反映させるには、IItemRendererインターフェイスを実装したクラスを作成します。<br /> | ||
+ | ※このソースについては私のオリジナルではなく、[https://github.com/reginn/Tutorial-Renderer Reginn氏のチュートリアル]を参考に作成しています。 | ||
+ | |||
+ | *ItemRenderIgnis.class | ||
+ | <source lang="java"> | ||
+ | package defeatedcrow.flamethrower.client.item; | ||
+ | |||
+ | import net.minecraft.entity.Entity; | ||
+ | import net.minecraft.item.ItemStack; | ||
+ | import net.minecraft.util.ResourceLocation; | ||
+ | import net.minecraftforge.client.IItemRenderer; | ||
+ | |||
+ | import org.lwjgl.opengl.GL11; | ||
+ | |||
+ | import cpw.mods.fml.client.FMLClientHandler; | ||
+ | import defeatedcrow.flamethrower.client.model.ModelIgnis; | ||
+ | |||
+ | @SideOnly(Side.CLIENT) | ||
+ | public class ItemRenderIgnis implements IItemRenderer { | ||
+ | |||
+ | private static final ResourceLocation resource = new ResourceLocation("dcsflame:textures/entity/entity_ignis.png"); | ||
+ | private ModelIgnis model = new ModelIgnis(); | ||
+ | |||
+ | @Override | ||
+ | public boolean handleRenderType(ItemStack item, ItemRenderType type) { | ||
+ | return canRendering(item, type); | ||
+ | } | ||
+ | |||
+ | // どのItemRenderTypeを上書きするかの設定。 | ||
+ | // 今回は全て変更しているが、一人称だけモデルを使う、ドロップ状態の時だけ、など通常レンダーとの使い分けも可能。 | ||
+ | private boolean canRendering(ItemStack item, ItemRenderType type) { | ||
+ | switch (type) { | ||
+ | // ドロップ状態 | ||
+ | case ENTITY: | ||
+ | // 三人称の装備中 | ||
+ | case EQUIPPED: | ||
+ | // 一人称の装備中 | ||
+ | case EQUIPPED_FIRST_PERSON: | ||
+ | // インベントリ | ||
+ | case INVENTORY: | ||
+ | return true; | ||
+ | default: | ||
+ | return false; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public boolean shouldUseRenderHelper(ItemRenderType type, ItemStack item, ItemRendererHelper helper) { | ||
+ | switch (helper) { | ||
+ | case INVENTORY_BLOCK: | ||
+ | case ENTITY_BOBBING: | ||
+ | case ENTITY_ROTATION: | ||
+ | return true; | ||
+ | default: | ||
+ | return false; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public void renderItem(ItemRenderType type, ItemStack item, Object... data) { | ||
+ | if (canRendering(item, type)) { | ||
+ | |||
+ | GL11.glPushMatrix(); | ||
+ | /* | ||
+ | * 描画する種類によって回転, 平行移動を行う. | ||
+ | */ | ||
+ | switch (type) { | ||
+ | case INVENTORY: | ||
+ | glMatrixForRenderInInventory(); | ||
+ | break; | ||
+ | case EQUIPPED_FIRST_PERSON: | ||
+ | glMatrixForRenderInFirstPerson(); | ||
+ | case EQUIPPED: | ||
+ | glMatrixForRenderInEquipped(); | ||
+ | break; | ||
+ | case ENTITY: | ||
+ | glMatrixForRenderInEntity(); | ||
+ | default: | ||
+ | break; | ||
+ | } | ||
+ | |||
+ | /* | ||
+ | * リソースをTextureMangerにbindし, modelのrenderを呼んで描画する. | ||
+ | */ | ||
+ | // テクスチャのオーバーライド | ||
+ | FMLClientHandler.instance().getClient().getTextureManager().bindTexture(resource); | ||
+ | // モデルのrenderメソッドを呼ぶ | ||
+ | this.model.render((Entity) null, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0625F); | ||
+ | |||
+ | GL11.glPopMatrix(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | /* | ||
+ | * インベントリ内での描画位置の調整. | ||
+ | */ | ||
+ | private void glMatrixForRenderInInventory() { | ||
+ | GL11.glRotatef(-180F, 1.0F, 0.0F, 0.0F); | ||
+ | GL11.glTranslatef(0.0F, -1.0F, 0.0F); | ||
+ | } | ||
+ | |||
+ | /* | ||
+ | * 装備状態での描画位置の調整. | ||
+ | */ | ||
+ | private void glMatrixForRenderInEquipped() { | ||
+ | GL11.glRotatef(-270F, 1.0F, 0.0F, 0.0F); | ||
+ | GL11.glRotatef(-90F, 0.0F, 0.0F, 1.0F); | ||
+ | GL11.glRotatef(20F, 0.0F, 1.0F, 0.0F); | ||
+ | GL11.glScalef(-1.1F, 1.1F, 1.1F); | ||
+ | GL11.glTranslatef(0.0F, -0.4F, -0.6F); | ||
+ | } | ||
+ | |||
+ | private void glMatrixForRenderInFirstPerson() { | ||
+ | GL11.glScalef(1.5F, 1.5F, -1.5F); | ||
+ | GL11.glRotatef(-60F, 0.0F, 0.0F, 1.0F); | ||
+ | GL11.glRotatef(-20F, 1.0F, 0.0F, 0.0F); | ||
+ | GL11.glTranslatef(-0.3F, 0.5F, 0.0F); | ||
+ | } | ||
+ | |||
+ | /* | ||
+ | * ドロップ状態での描画位置の調整. | ||
+ | */ | ||
+ | private void glMatrixForRenderInEntity() { | ||
+ | GL11.glScalef(1.0F, -1.0F, 1.0F); | ||
+ | GL11.glTranslatef(0.0F, -1.5F, 0.0F); | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </source> | ||
+ | |||
+ | 描画処理はrenderItemメソッドの中で行います。 | ||
+ | ItemRenderTypeごとにGLでの回転、中心点移動などを行い、モデルクラスのrenderメソッドを呼んで描画するのが主な流れです。<br /> | ||
+ | |||
+ | 次に、登録処理を書きます。<br /> | ||
+ | Render関係の処理は、'''Server側では呼び出さない'''ように注意しなければなりません。そのときの対策として'''Proxy'''が役に立ちます。CommonProxyにメソッドを作り、ClientProxyでそれをオーバーライドすることで、そのメソッドを呼び出すとサーバーとクライアントで別々の処理を行うことが出来ます。<br /> | ||
+ | |||
+ | *CommonProxy.class<br /> | ||
+ | <source lang="java"> | ||
+ | package defeatedcrow.flamethrower.common; | ||
+ | |||
+ | public class CommonProxyF { | ||
+ | |||
+ | public void registerRenderers() { | ||
+ | |||
+ | } | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | |||
+ | *ClientProxy.class<br /> | ||
+ | <source lang="java"> | ||
+ | package defeatedcrow.flamethrower.client; | ||
+ | |||
+ | import net.minecraftforge.client.MinecraftForgeClient; | ||
+ | import cpw.mods.fml.client.registry.RenderingRegistry; | ||
+ | import defeatedcrow.flamethrower.FlameCore; | ||
+ | import defeatedcrow.flamethrower.client.item.ItemRenderIgnis; | ||
+ | import defeatedcrow.flamethrower.common.CommonProxyF; | ||
+ | |||
+ | public class ClientProxyF extends CommonProxyF { | ||
+ | |||
+ | @Override | ||
+ | public void registerRenderers() { | ||
+ | MinecraftForgeClient.registerItemRenderer(FlameCore.flamethrower, new ItemRenderIgnis()); | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </source> | ||
+ | |||
+ | ClientProxyにのみ、IItemRendererの登録処理を入れました。 | ||
+ | これを、メインクラスで呼ぶと、登録は完了です。 | ||
+ | |||
+ | *FlameCore.class | ||
+ | initメソッドの部分の抜粋です。 | ||
+ | <source lang="java"> | ||
+ | @EventHandler | ||
+ | public void init(FMLInitializationEvent event) throws IOException { | ||
+ | |||
+ | // render | ||
+ | proxy.registerRenderers(); | ||
+ | |||
+ | } | ||
+ | </source> | ||
+ | ---- | ||
+ | ここまで行うと、アイテムのモデル表示が登録出来るので、デバッグプレイを起動して実際のゲーム中の表示を確認し、調整していきます。 | ||
+ | |||
+ | 私の場合、移動や回転量を計算で出せる能力も無く、ホットコードもうまくいかないので、数値を微調整→デバッグ起動で確認、を延々繰り返すゴリ押し力業で調整しています。一つのアイテムの調整で数十回はデバッグ起動画面を眺めています。<br /> | ||
+ | 一番厄介なのはEQUIPPED_FIRST_PERSONの調整で、一人称の場合プレイヤーの視界外にアイテムがずれてしまうとどこに行ったのか分からなくなります。そういう場合は一度初期値に戻すとか、大きく値を変えて行方を探っています…<br /> | ||
+ | |||
+ | 上記のソースは完成後の物で、ゲーム中ではこのように表示されます。 | ||
+ | 装備表示(三人称)<br /> | ||
+ | https://dl-web.dropbox.com/s/3g3pywdw1xpawp6/thirdp_ignis.png<br /> | ||
+ | |||
+ | 装備表示(一人称)<br /> | ||
+ | https://dl-web.dropbox.com/s/2bslt3ftuo04w3o/firstp_ignis.png<br /> | ||
+ | |||
+ | ---- | ||
+ | ここまでで、見た目だけで効果の何もない、無機能なただのアイテムが出来上がります。<br /> |
2016年9月21日 (水) 10:52時点における最新版
<目次に戻る>
目次
無機能な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(4); // クラフトでの修理を不可に。 this.setNoRepair(); } // Icon用テクスチャファイルの指定はここで行う。 // ItemBowクラスでも、このメソッドでテクスチャパスを指定しているため、 // オーバーライドして上書きしなければならない。(コンストラクタでのテクスチャパス指定は推奨しない。) @Override @SideOnly(Side.CLIENT) public void registerIcons(IIconRegister par1IconRegister) { this.itemIcon = par1IconRegister.registerIcon("dcsflame:flamethrower"); } // 手に持ったときの見た目が変わります。IItemRenderer調整時にも影響します。 @Override @SideOnly(Side.CLIENT) public boolean isFull3D() { return true; } }
テクスチャ
仮なので、最低限何か分かる程度の簡単なテクスチャで済ませています。
テクスチャの置き場は、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; @SideOnly(Side.CLIENT) 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インターフェイスを実装したクラスを作成します。
※このソースについては私のオリジナルではなく、Reginn氏のチュートリアルを参考に作成しています。
- ItemRenderIgnis.class
package defeatedcrow.flamethrower.client.item; import net.minecraft.entity.Entity; import net.minecraft.item.ItemStack; import net.minecraft.util.ResourceLocation; import net.minecraftforge.client.IItemRenderer; import org.lwjgl.opengl.GL11; import cpw.mods.fml.client.FMLClientHandler; import defeatedcrow.flamethrower.client.model.ModelIgnis; @SideOnly(Side.CLIENT) public class ItemRenderIgnis implements IItemRenderer { private static final ResourceLocation resource = new ResourceLocation("dcsflame:textures/entity/entity_ignis.png"); private ModelIgnis model = new ModelIgnis(); @Override public boolean handleRenderType(ItemStack item, ItemRenderType type) { return canRendering(item, type); } // どのItemRenderTypeを上書きするかの設定。 // 今回は全て変更しているが、一人称だけモデルを使う、ドロップ状態の時だけ、など通常レンダーとの使い分けも可能。 private boolean canRendering(ItemStack item, ItemRenderType type) { switch (type) { // ドロップ状態 case ENTITY: // 三人称の装備中 case EQUIPPED: // 一人称の装備中 case EQUIPPED_FIRST_PERSON: // インベントリ case INVENTORY: return true; default: return false; } } @Override public boolean shouldUseRenderHelper(ItemRenderType type, ItemStack item, ItemRendererHelper helper) { switch (helper) { case INVENTORY_BLOCK: case ENTITY_BOBBING: case ENTITY_ROTATION: return true; default: return false; } } @Override public void renderItem(ItemRenderType type, ItemStack item, Object... data) { if (canRendering(item, type)) { GL11.glPushMatrix(); /* * 描画する種類によって回転, 平行移動を行う. */ switch (type) { case INVENTORY: glMatrixForRenderInInventory(); break; case EQUIPPED_FIRST_PERSON: glMatrixForRenderInFirstPerson(); case EQUIPPED: glMatrixForRenderInEquipped(); break; case ENTITY: glMatrixForRenderInEntity(); default: break; } /* * リソースをTextureMangerにbindし, modelのrenderを呼んで描画する. */ // テクスチャのオーバーライド FMLClientHandler.instance().getClient().getTextureManager().bindTexture(resource); // モデルのrenderメソッドを呼ぶ this.model.render((Entity) null, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0625F); GL11.glPopMatrix(); } } /* * インベントリ内での描画位置の調整. */ private void glMatrixForRenderInInventory() { GL11.glRotatef(-180F, 1.0F, 0.0F, 0.0F); GL11.glTranslatef(0.0F, -1.0F, 0.0F); } /* * 装備状態での描画位置の調整. */ private void glMatrixForRenderInEquipped() { GL11.glRotatef(-270F, 1.0F, 0.0F, 0.0F); GL11.glRotatef(-90F, 0.0F, 0.0F, 1.0F); GL11.glRotatef(20F, 0.0F, 1.0F, 0.0F); GL11.glScalef(-1.1F, 1.1F, 1.1F); GL11.glTranslatef(0.0F, -0.4F, -0.6F); } private void glMatrixForRenderInFirstPerson() { GL11.glScalef(1.5F, 1.5F, -1.5F); GL11.glRotatef(-60F, 0.0F, 0.0F, 1.0F); GL11.glRotatef(-20F, 1.0F, 0.0F, 0.0F); GL11.glTranslatef(-0.3F, 0.5F, 0.0F); } /* * ドロップ状態での描画位置の調整. */ private void glMatrixForRenderInEntity() { GL11.glScalef(1.0F, -1.0F, 1.0F); GL11.glTranslatef(0.0F, -1.5F, 0.0F); } }
描画処理はrenderItemメソッドの中で行います。
ItemRenderTypeごとにGLでの回転、中心点移動などを行い、モデルクラスのrenderメソッドを呼んで描画するのが主な流れです。
次に、登録処理を書きます。
Render関係の処理は、Server側では呼び出さないように注意しなければなりません。そのときの対策としてProxyが役に立ちます。CommonProxyにメソッドを作り、ClientProxyでそれをオーバーライドすることで、そのメソッドを呼び出すとサーバーとクライアントで別々の処理を行うことが出来ます。
- CommonProxy.class
package defeatedcrow.flamethrower.common; public class CommonProxyF { public void registerRenderers() { } }
- ClientProxy.class
package defeatedcrow.flamethrower.client; import net.minecraftforge.client.MinecraftForgeClient; import cpw.mods.fml.client.registry.RenderingRegistry; import defeatedcrow.flamethrower.FlameCore; import defeatedcrow.flamethrower.client.item.ItemRenderIgnis; import defeatedcrow.flamethrower.common.CommonProxyF; public class ClientProxyF extends CommonProxyF { @Override public void registerRenderers() { MinecraftForgeClient.registerItemRenderer(FlameCore.flamethrower, new ItemRenderIgnis()); } }
ClientProxyにのみ、IItemRendererの登録処理を入れました。 これを、メインクラスで呼ぶと、登録は完了です。
- FlameCore.class
initメソッドの部分の抜粋です。
@EventHandler public void init(FMLInitializationEvent event) throws IOException { // render proxy.registerRenderers(); }
ここまで行うと、アイテムのモデル表示が登録出来るので、デバッグプレイを起動して実際のゲーム中の表示を確認し、調整していきます。
私の場合、移動や回転量を計算で出せる能力も無く、ホットコードもうまくいかないので、数値を微調整→デバッグ起動で確認、を延々繰り返すゴリ押し力業で調整しています。一つのアイテムの調整で数十回はデバッグ起動画面を眺めています。
一番厄介なのはEQUIPPED_FIRST_PERSONの調整で、一人称の場合プレイヤーの視界外にアイテムがずれてしまうとどこに行ったのか分からなくなります。そういう場合は一度初期値に戻すとか、大きく値を変えて行方を探っています…
上記のソースは完成後の物で、ゲーム中ではこのように表示されます。
装備表示(三人称)
装備表示(一人称)
ここまでで、見た目だけで効果の何もない、無機能なただのアイテムが出来上がります。