この記事は"Minecraft Forge Universal 14.23.1.2555~"を前提MODとしています。 |
旧方式を使ったItemの作成
1.12.2現在、ItemやBlockを登録する方法は2通りあります。
- RegistryEventを利用した登録: ForgeによってEventが呼ばれて登録処理を行います。
- 自分で登録用メソッドを呼ぶ: メインクラスのpreInitで、自分でメソッドを呼びます。
どちらでも可能ですが、今回は自分で呼ぶ方式を解説します。
処理の主な流れ
アイテムとは、インベントリ上で表示されたり、ドロップ状態でゲーム中に出現する概念です。
インゴットや、リンゴ、苗木など、バニラでもたくさんの種類があります。
このゲームでは、ItemやBlockは1種類につき1個だけインスタンスが生成され、それをForgeに登録します。以後は同じインスタンスがずっと使いまわされます。
ですので、アイテムを追加するときは、
- インスタンスを一つだけ生成する
- 登録メソッドを呼んで、Forgeに登録する
- モデルを登録する
というステップで進行します。
順番を間違えると、nullエラーなどが発生してしまうので、上記の順番で処理が進むように書きます。
ソースコード
CrowItem.java
MODの起動に必須のメインクラスです。
Forgeはゲームの起動時に@Modアノテーションのついているクラスを探して読み取り、含まれている起動時の処理を行ってくれます。(つまり、初期設定など起動時の処理で呼んでほしいものは、必ずメインクラスの適切なところで読み取られるように書いておかなければなりません。)
MODのIDやアイテムの名前などは好きなものに変えていい部分なので、自分の作りたいものに合わせて書き換えてください。
// ここのパッケージは、自分の開発環境に合わせて変えてください。 package defeatedcrow.tutorial; import net.minecraft.item.Item; import net.minecraft.util.ResourceLocation; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod.EventHandler; import net.minecraftforge.fml.common.Mod.Instance; import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; import net.minecraftforge.fml.common.registry.ForgeRegistries; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; @Mod(modid = CrowItem.MOD_ID, name = CrowItem.MOD_NAME, version = "1.0.0", dependencies = CrowItem.MOD_DEPENDENCIES, acceptedMinecraftVersions = CrowItem.MOD_ACCEPTED_MC_VERSIONS, useMetadata = true) public class CrowItem { // modidはすべて小文字にする // assetsのフォルダの名前にもなるので、フォルダ名に不適な文字は含めない方がよい public static final String MOD_ID = "karasuman_item"; public static final String MOD_NAME = "CrowItemMod"; public static final String MOD_DEPENDENCIES = ""; public static final String MOD_ACCEPTED_MC_VERSIONS = "[1.12,1.12.2]"; @Instance("karasuman_item") public static CrowItem instance; /* 追加アイテムのインスタンス */ public static Item crowFace; @EventHandler public void preInit(FMLPreInitializationEvent event) { // インスタンスの生成 // setRegistryNameが必須になったのが過去版との変更点 crowFace = new ItemCrowFace().setUnlocalizedName("crow_face").setRegistryName( new ResourceLocation(MOD_ID, "crow_face")); // Itemの登録。 // RegistryEventを利用しなくても過去バージョンのように登録用メソッドは呼び出せる ForgeRegistries.ITEMS.register(crowFace); // モデル登録もpreInitで呼ぶ。initでは遅い // モデルなどクライアント限定の要素は、サーバー側では読み込むだけでクラッシュしてしまう // なので、イベントのSideがどちらかを判別して、条件分けをする。 if (event.getSide().isClient()) { // クライアントサイドの時だけ、モデル登録を呼ぶ。 registerModels(); } } @SideOnly(Side.CLIENT) public void registerModels() { // モデル登録メソッド // フルパスで入れているのは、以下のクラスがClientOnlyのため、Serverサイドでのimport事故を防ぐ目的 net.minecraftforge.client.model.ModelLoader.setCustomModelResourceLocation(crowFace, 0, new net.minecraft.client.renderer.block.model.ModelResourceLocation(MOD_ID + ":crow_face", "inventory")); /* * いくつかの注意事項がある * 1: モデルはメタデータごとに設定しなければならない * なので、0~15までメタ値があるアイテムなら16回繰り返すことになる * . * 2: アイテムのモデルの場合はインベントリ内での表示である"inventory"だけでよい * . * 3: ModelResourceLocationに渡すStringはそのままファイルパスとファイル名になる * 上記の場合は、assets/modid名/models/item/crow_face.jsonとなる * . * 追加アイテムが多い場合など、jsonファイルをフォルダ分けしたい場合はここで設定できる * 例えばMOD_ID + ":domein/crow_face"とすれば、 * modelsフォルダ下の「domein」フォルダの中にあるcrow_face.jsonを読み取るようになる */ } }
CrowItem.java
Item継承のクラスは作らなくても一応Item作成はできるが、凝ったことがしたい場合はこのようにクラスファイルを作成した方が作りやすい。
package defeatedcrow.tutorial; import java.util.List; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; import net.minecraft.client.util.ITooltipFlag; import net.minecraft.creativetab.CreativeTabs; import net.minecraft.entity.SharedMonsterAttributes; import net.minecraft.entity.ai.attributes.AttributeModifier; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; import net.minecraft.init.SoundEvents; import net.minecraft.inventory.EntityEquipmentSlot; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.util.ActionResult; import net.minecraft.util.EnumActionResult; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumHand; import net.minecraft.util.NonNullList; import net.minecraft.util.SoundCategory; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; /* * オリジナルのItemのクラスを作る * クラスを作らなくてもItem追加はできるが、凝った機能をつけたい場合は必要 * 今回は、使いたい人が多そうな機能に絞って、Overrideすることで機能を追加できるメソッドの解説を添えた。 */ /* * Itemを継承(extends)させなければItemとして登録できない<br> * Itemを継承した別のアイテムクラスでもOK * */ public class ItemCrowFace extends Item { public ItemCrowFace() { super(); // この部分でもItemの設定を一部追加できる this.setCreativeTab(CreativeTabs.MISC);// 追加するクリエイティブタブ this.setMaxStackSize(16); // 最大のスタック数 } /* === 機能の例 === */ /** * このアイテムを持って、空中を右クリックしたときの動作 * * @return 処理が成功したかどうかと、結果のItemStackを返す * @param world 現在地のワールド(ディメンジョン) * @param player プレイヤー * @param hand 持ち手 メインハンドかオフハンドか */ @Override public ActionResult<ItemStack> onItemRightClick(World world, EntityPlayer player, EnumHand hand) { // プレイヤーが持っているItemStackは、プレイヤーから取得 ItemStack stack = player.getHeldItem(hand); // 今回は使用すると自分を回復するアイテムにしてみる player.heal(20.0F); // ハート20個 // 使用後にアイテムを減らす処理 // このメソッドでは、数を減らした後に0個の場合EMPTYに差し替える処理などはなくてOK stack.shrink(1); // 結果を返す return new ActionResult(EnumActionResult.SUCCESS, stack); } /** * ブロックをターゲットしながら右クリックした場合はこっち * * @return 処理が成功したかどうかを返す * @param world 現在地のワールド(ディメンジョン) * @param player プレイヤー * @param pos 右クリック対象のBlockPos * @param facing 右クリックしたブロックの面 * @param hitX 右クリックした場所がブロックのどのあたりか(中心なら0.5F) * @param hand 持ち手 メインハンドかオフハンドか */ @Override public EnumActionResult onItemUse(EntityPlayer player, World world, BlockPos pos, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) { ItemStack stack = player.getHeldItem(hand); // こちらは右クリックしたブロックが草ブロックだった場合に、草の小道ブロックに置き換えてみる // if (!player.canPlayerEdit(pos.offset(facing), facing, stack)) { return EnumActionResult.FAIL; } else { IBlockState state = world.getBlockState(pos); // IBlockStateは、その座標のBlockやメタデータ、Stateなどいろいろ確認できる // 今回は草ブロックかどうかの判定に使う if (state.getBlock() == Blocks.GRASS) { // 小道ブロック(GRASS_PATH)を設置 // ブロックを置くときもIBlockStateが必要なので、ここではgetDefaultState()で初期設定を使用 world.setBlockState(pos, Blocks.GRASS_PATH.getDefaultState()); // シャベルで地面をならしたときの効果音を呼んでいる world.playSound(player, pos, SoundEvents.ITEM_SHOVEL_FLATTEN, SoundCategory.BLOCKS, 1.0F, 1.0F); } } return EnumActionResult.SUCCESS; } /** * ブロックの破壊速度を変えることができる。 * <br> * ツールの破壊可能対象とは違うので、破壊できないブロックでも早く壊してしまう点に注意。 * * @return 速度をfloatで返す。1.0Fで素手と同じ、大きくすると早くなる。 * @param stack 持っているアイテム * @param state 対象のブロック */ @Override public float getDestroySpeed(ItemStack stack, IBlockState state) { // マテリアルが砂だと早く掘れるようにしてみる Material material = state.getMaterial(); if (material == Material.SAND) { return 15.0F; } else { return 1.0F; } } /** * Entityを殴ったときの攻撃力と攻撃速度を変更する。<br> * Entityのステータス変更はAttributeModifierというものを使用する。<br> * ちなみに、AttributeModifierを自作してオリジナルのステータス変化を作ることもできる。 * * @return AttributeModifiersの設定を入れたMultiMap * @param slot アイテムが装備されているスロット * @param stack 対象のアイテム */ @Override public Multimap<String, AttributeModifier> getAttributeModifiers(EntityEquipmentSlot slot, ItemStack stack) { Multimap<String, AttributeModifier> multimap = HashMultimap.<String, AttributeModifier>create(); // メインハンドの時のみ if (slot == EntityEquipmentSlot.MAINHAND) { // 攻撃力 // 数値(10.0F)以外は固定なのでそのままコピペで使用すると楽 multimap.put(SharedMonsterAttributes.ATTACK_DAMAGE.getName(), new AttributeModifier(ATTACK_DAMAGE_MODIFIER, "Weapon modifier", 10.0F, 0)); // 攻撃速度 // 数値(-2.5DF)以外は固定 multimap.put(SharedMonsterAttributes.ATTACK_SPEED.getName(), new AttributeModifier(ATTACK_SPEED_MODIFIER, "Weapon modifier", -2.5D, 0)); } return multimap; } /** * アイテムのツールチップの内容を増やす。<br> * 頭に@SideOnly(Side.CLIENT)のついているものは、サーバーサイドでは読み込めないので要注意!<br> * これを付け忘れると、マルチプレイのときだけクラッシュしてしまう、よくあるバグModを生み出す * * @param stack 対象のアイテム * @param world 現在のワールド * @param tooltipの文字列が入ったリスト * @param flag f3+Hで拡張ツールチップに切り替えているかどうか */ @Override @SideOnly(Side.CLIENT) public void addInformation(ItemStack stack, World world, List<String> tooltip, ITooltipFlag flag) { tooltip.add("呼ばれて飛び出てカラスマン!!!"); } /** * どのクリエイティブタブにこのアイテムを表示するか。<br> * 1.12では、ここで不必要なTabに出現しないように場合分けしないと、全Tabに出てくるようになってしまった(!!!)<br> * バグっぽい挙動なのだがよくわからない<br> * 複数のタブに出現させたり、特定のメタデータだけを表示させる、NBTデータを持たせて表示させるなどの細かい制御もここでできる。 * * @param tab クリエイティブタブの種類 * @param subItems アイテムのリスト */ @Override @SideOnly(Side.CLIENT) public void getSubItems(CreativeTabs tab, NonNullList<ItemStack> subItems) { // 表示したいタブと一致しているかの判定メソッド if (this.isInCreativeTab(tab)) { // このアイテムのスタックを作ってリストに追加する。 // 今回はメタ0、スタック数は1個とした // このスタックにはNBTを持たせられるので、エンチャントされた状態でタブに登録したりもできる subItems.add(new ItemStack(this, 1, 0)); } } }
crow_face.json
このアイテムのjsonモデル。
jsonモデルは、parent(親モデル)を指定することができる。親モデルの一部を変えたい場合は、変えたい部分のみを記述すればそこだけ置き換わる。
下記の例は、"item/generated"(ふつうの平面アイコン)を指定した上で"texture"だけ指定しているので、テクスチャだけ差し替えて、そのほかは親モデルと同じ平面アイコンを使うモデルになる。
テクスチャの種類(ここでは"layer0")は、親と同じものを指定することで、指定した種類を差し替えることができる。
テクスチャはファイルが入っているパスを指定する。
この場合、karasuman_item/textures/items/crow_face.pngになる。
{ "parent":"item/generated", "textures":{"layer0":"karasuman_item:items/crow_face"} }