Minecraft Modding 1.12.2
MinecraftのMod開発は大きいバージョンだけではなく、小さいバージョンでも細かい差異があり、わかりにくい状況が続いています。
なので、この記事にたどり着いたユーザーの助けになるかはわかりませんが、少しでもヒントになればと思い書いております。
ソースコードを直接確認したい方はGithubを参照ください。
下記の内容を随時作成していこうと思っています。
- Proxy機能
- アイテムの追加
- 食べ物の追加
- 魚の追加
- 作物の追加
- クリエイティブタブ追加
私自身、まだまだ勉強している最中ですので説明に間違いなどがありましたら、コメントをお願いします。
環境
- IntelliJ IDEA
- Minecraft 1.12.2
- Minecraft Forge 14.23.5.2838
開発環境はここを参考に構築してください。
src/main/javaディレクトリにjp/artan/tmディレクトリを作成し、そこに`TutorialMod.java‘を作成します。
package jp.artan.tm;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLConstructionEvent;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import org.apache.logging.log4j.Logger;
@Mod(modid = TutorialMod.MODID, name = TutorialMod.NAME, version = TutorialMod.VERSION)
public class TutorialMod
{
    public static final String MODID = "tm";
    public static final String NAME = "Tutorial Mod";
    public static final String VERSION = "1.0";
    public static Logger logger;
    @Mod.EventHandler
    public void construct(FMLConstructionEvent event) {
    }
    @Mod.EventHandler
    public void preInit(FMLPreInitializationEvent event) {
        logger = event.getModLog();
        logger.info("TutorialMod.preInit");
    }
    @Mod.EventHandler
    public void init(FMLInitializationEvent event) {
        logger.info("TutorialMod.init");
    }
    @Mod.EventHandler
    public void postInit(FMLPostInitializationEvent event) {
        logger.info("TutorialMod.postInit");
    }
}
これから作成するすべてのjavaファイルはsrc/main/java/jp/artan/tmを基点にして説明します。
また、テクスチャなどのpngデータやjsonデータはsrc/main/assets/tmを基点にして説明します。
Proxy機能
ProxyとはModを呼び出したのが、クライアント/サーバーなのかを判断して、適切なイベントを自動で呼び出すための仕組みです。
なぜその仕組みが必要かというと、これ以降で説明するアイテムなどに必要なModel情報はサーバーで実行してはいけないことになっているためです。
では、次のファイルを作成してください。
package jp.artan.tm.proxy;
import jp.artan.tm.TutorialMod;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
@Mod.EventBusSubscriber(modid = TutorialMod.MODID)
public abstract class CommonProxy {
    public void preInit(FMLPreInitializationEvent event) {
        TutorialMod.logger.info("CommonProxy.preInit");
    }
    public void init(FMLInitializationEvent event) {
        TutorialMod.logger.info("CommonProxy.init");
    }
    public void postInit(FMLPostInitializationEvent event) {
        TutorialMod.logger.info("CommonProxy.postInit");
    }
}
package jp.artan.tm.proxy;
import jp.artan.tm.TutorialMod;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
public class ClientProxy extends CommonProxy {
    @Override
    public void preInit(FMLPreInitializationEvent event) {
        super.preInit(event);
        TutorialMod.logger.info("ClientProxy.preInit");
    }
}
package jp.artan.tm.proxy;
public class ServerProxy extends CommonProxy {
 // 今回はサーバーの説明はしないので、何も記述はしない
 // 必要に応じてpreinitなどを作成して利用してください。
}
CommonProxyにクライアント/サーバーで共通の処理を記述し、それぞれだけで行う処理に関してはClientProxyやServerProxyに記述する。
では、ClientProxyやServerProxy‘を自動で選択し、呼び出すための処理をTutorialMod.java`に記述していく。
コメントで追記と書かれているところが今回追記したものになる。
package jp.artan.tm;
import jp.artan.tm.proxy.CommonProxy;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.SidedProxy;
import net.minecraftforge.fml.common.event.FMLConstructionEvent;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import org.apache.logging.log4j.Logger;
@Mod(modid = TutorialMod.MODID, name = TutorialMod.NAME, version = TutorialMod.VERSION)
public class TutorialMod
{
    public static final String MODID = "tm";
    public static final String NAME = "Tutorial Mod";
    public static final String VERSION = "1.0";
    public static Logger logger;
    public static final String CLIENT_PROXY = "jp.artan." + MODID + ".proxy.ClientProxy"; // 追記
    public static final String SERVER_PROXY = "jp.artan." + MODID + ".proxy.ServerProxy"; // 追記
    @SidedProxy(clientSide = CLIENT_PROXY, serverSide = SERVER_PROXY) // 追記
    public static CommonProxy proxy; // 追記
    @Mod.EventHandler
    public void construct(FMLConstructionEvent event) {
        MinecraftForge.EVENT_BUS.register(proxy); // 追記
    }
    @Mod.EventHandler
    public void preInit(FMLPreInitializationEvent event) {
        logger = event.getModLog();
        logger.info("TutorialMod.preInit");
        proxy.preInit(event); // 追記
    }
    @Mod.EventHandler
    public void init(FMLInitializationEvent event) {
        logger.info("TutorialMod.init");
        proxy.init(event); // 追記
    }
    @Mod.EventHandler
    public void postInit(FMLPostInitializationEvent event) {
        logger.info("TutorialMod.postInit");
        proxy.postInit(event); // 追記
    }
}
一部を抜粋して説明をしていく。
public static final String CLIENT_PROXY = "jp.artan." + MODID + ".proxy.ClientProxy";
public static final String SERVER_PROXY = "jp.artan." + MODID + ".proxy.ServerProxy";
@SidedProxy(clientSide = CLIENT_PROXY, serverSide = SERVER_PROXY)
public static CommonProxy proxy;
変数のproxyについているアノテーションSidedProxyが重要になる。
このアノテーションを指定することで、指定されたclientSideとserverSideのファイルディレクトリ情報を基にインスタンスを自動で生成して変数に格納してくれる。
※この時に指定するパスはsrc/main/javaを基点としたパスになる
この段階でクライアントとサーバーを起動するとそれぞれのインスタンスが自動で用意されていることが確認できるかと思う。
[17:25:39] [Client thread/INFO] [tm]: TutorialMod.preInit
[17:25:39] [Client thread/INFO] [tm]: CommonProxy.preInit
[17:25:39] [Client thread/INFO] [tm]: ClientProxy.preInit
[17:25:44] [Client thread/INFO] [tm]: TutorialMod.init
[17:25:44] [Client thread/INFO] [tm]: CommonProxy.init
[17:25:44] [Client thread/INFO] [tm]: TutorialMod.postInit
[17:25:44] [Client thread/INFO] [tm]: CommonProxy.postInit
[17:33:43] [Server thread/INFO] [tm]: TutorialMod.preInit
[17:33:43] [Server thread/INFO] [tm]: CommonProxy.preInit
[17:33:43] [Server thread/INFO] [tm]: TutorialMod.init
[17:33:43] [Server thread/INFO] [tm]: CommonProxy.init
[17:33:44] [Server thread/INFO] [tm]: TutorialMod.postInit
[17:33:44] [Server thread/INFO] [tm]: CommonProxy.postInit
アイテムの追加
一括登録処理
次に何の機能もないアイテムを実装してみたいと思います。
その前に、アイテムをgemeRegistryやModelLoderに一括で登録するための仕組みを作成します。
package jp.artan.tm.event;
import net.minecraft.item.Item;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.event.RegistryEvent;
public interface IItemRegisterEvent {
    /**
     * Register Item
     *
     * @param event
     */
    void registerItem(RegistryEvent.Register<Item> event);
    /**
     * Register Model
     *
     * @param event
     */
    void registerModel(ModelRegistryEvent event);
}
package jp.artan.tm.init;
import jp.artan.tm.event.IItemRegisterEvent;
import java.util.ArrayList;
import java.util.List;
public class ItemInit {
    public static final List<IItemRegisterEvent> ITEMS = new ArrayList<IItemRegisterEvent>();
}
package jp.artan.tm.proxy;
import jp.artan.tm.TutorialMod;
import jp.artan.tm.init.ItemInit;
import net.minecraft.item.Item;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@Mod.EventBusSubscriber(modid = TutorialMod.MODID)
public abstract class CommonProxy {
    public void preInit(FMLPreInitializationEvent event) {
        TutorialMod.logger.info("CommonProxy.preInit");
    }
    public void init(FMLInitializationEvent event) {
        TutorialMod.logger.info("CommonProxy.init");
    }
    public void postInit(FMLPostInitializationEvent event) {
        TutorialMod.logger.info("CommonProxy.postInit");
    }
// 以下追記
    @SubscribeEvent
    public void registerItems(RegistryEvent.Register<Item> event) {
        TutorialMod.logger.info("CommonProxy.registerItems");
        ItemInit.ITEMS.forEach(f -> f.registerItem(event));
    }
// 以上追記
}
package jp.artan.tm.proxy;
import jp.artan.tm.TutorialMod;
import jp.artan.tm.init.ItemInit;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
public class ClientProxy extends CommonProxy {
    @Override
    public void preInit(FMLPreInitializationEvent event) {
        super.preInit(event);
        TutorialMod.logger.info("ClientProxy.preInit");
    }
// 以下追記
    @SubscribeEvent
    public void registerModels(ModelRegistryEvent event) { 
        TutorialMod.logger.info("ClientProxy.registerModels");
        ItemInit.ITEMS.forEach(f -> f.registerModel(event));
    }
// 以上追記
}
IItemRegisterEventを継承しているオブジェクトをITEMSで一括管理し、1個ずつgemeRegistryやModelLoderに登録していく。
※registerModelsイベントはクライアントのみで実行すること。
アイテム実装
では、本題のアイテム登録を行いたいと思います。
package jp.artan.tm.item;
import jp.artan.tm.TutorialMod;
import jp.artan.tm.event.IItemRegisterEvent;
import jp.artan.tm.init.ItemInit;
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.item.Item;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.event.RegistryEvent;
public class TutorialItem extends Item implements IItemRegisterEvent {
    private final String name;
    public TutorialItem(String name) {
        super();
        this.name = name;
        this.setUnlocalizedName(this.name);
        this.setRegistryName(TutorialMod.MODID, this.name);
        this.setCreativeTab(CreativeTabs.MISC);
        ItemInit.ITEMS.add(this);
    }
    @Override
    public void registerItem(RegistryEvent.Register<Item> event) {
        event.getRegistry().register(this);
    }
    public void registerModel(ModelRegistryEvent event) {
        ModelLoader.setCustomModelResourceLocation(this, 0,
                new ModelResourceLocation(new ResourceLocation(TutorialMod.MODID, this.name), "inventory"));
    }
}
TutorialItemが作成できたら、ItemInitでインスタンスを作成します。
package jp.artan.tm.init;
import jp.artan.tm.event.IItemRegisterEvent;
import jp.artan.tm.item.TutorialItem;
import net.minecraft.item.Item;
import java.util.ArrayList;
import java.util.List;
public class ItemInit {
    public static final List<IItemRegisterEvent> ITEMS = new ArrayList<IItemRegisterEvent>();
    public static final Item TUTORIAL_ITEM = new TutorialItem("tutorial_item"); // 追記
}
これで実行すると下記のようにその他のタブにアイテムが登録されるかと思います。
テクスチャと言語設定
このままでは、読みにくいうえにテクスチャを設定していない状態ですので、とても使える状況とは言えません。
なので、テクスチャと言語設定を行っていきます。
今回はnew TutorialItem("tutorial_item")のようにインスタンスを生成していますので、アイテムの名前はtutorial_itemになっています。
{
  "parent": "item/generated",
  "textures": {
    "layer0": "tm:items/tutorial_item"
  }
}
textures/items/tutorial_item.png [ ]
]
※画像の大きさが 16ピクセル × 16ピクセルでないと正しく読みだされないので気をつけてください。
# English Language File
# Items
item.tutorial_item.name=Tutorial Item
# 日本語言語ファイル
# Items
item.tutorial_item.name=チュートリアルアイテム
※Forgeのバージョンによって、en_us.lang,ja_jp.langはen_US.lang,ja_JP.langと指定する必要があります。
次の画像のようにデータがあっていれば問題ありません。
クリエイティブタブ追加
自身で作成したアイテムやブロックといったものは一定のまとまりで、クリエイティブタブに登録したいと思います。
今回はチュートリアルタブを作成したいと思います。
クリエイティブタブのアイコンには前述で作成したアイテムを設定したいと思います。
package jp.artan.tm.tab;
import jp.artan.tm.init.ItemInit;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.item.ItemStack;
public class TutorialTab extends CreativeTabs {
    public TutorialTab() {
        super("tutorial_tab");
    }
    @Override
    public ItemStack getTabIconItem() {
        return new ItemStack(ItemInit.TUTORIAL_ITEM);
    }
}
CreativeTabsのコンストラクタに渡した文字列は英小文字で渡すようにしてください。
また、ここで指定した名前が書きの言語設定にで利用する文字列になります。
# English Language File
# Tabs
itemGroup.tutorial_tab=Tutorial Tab
# Items
item.tutorial_item.name=Tutorial Item
# 日本語言語ファイル
# Tabs
itemGroup.tutorial_tab=チュートリアルタブ
# Items
item.tutorial_item.name=チュートリアルアイテム
package jp.artan.tm;
import jp.artan.tm.proxy.CommonProxy;
import jp.artan.tm.tab.TutorialTab;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.SidedProxy;
import net.minecraftforge.fml.common.event.FMLConstructionEvent;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import org.apache.logging.log4j.Logger;
@Mod(modid = TutorialMod.MODID, name = TutorialMod.NAME, version = TutorialMod.VERSION)
public class TutorialMod
{
    public static final String MODID = "tm";
    public static final String NAME = "Tutorial Mod";
    public static final String VERSION = "1.0";
    public static Logger logger;
    public static final String CLIENT_PROXY = "jp.artan." + MODID + ".proxy.ClientProxy";
    public static final String SERVER_PROXY = "jp.artan." + MODID + ".proxy.ServerProxy";
    @SidedProxy(clientSide = CLIENT_PROXY, serverSide = SERVER_PROXY)
    public static CommonProxy proxy;
    public static CreativeTabs creativeTabs = new TutorialTab(); // 追記
    @Mod.EventHandler
    public void construct(FMLConstructionEvent event) {
        MinecraftForge.EVENT_BUS.register(proxy);
    }
    @Mod.EventHandler
    public void preInit(FMLPreInitializationEvent event) {
        logger = event.getModLog();
        logger.info("TutorialMod.preInit");
        proxy.preInit(event);
    }
    @Mod.EventHandler
    public void init(FMLInitializationEvent event) {
        logger.info("TutorialMod.init");
        proxy.init(event);
    }
    @Mod.EventHandler
    public void postInit(FMLPostInitializationEvent event) {
        logger.info("TutorialMod.postInit");
        proxy.postInit(event);
    }
}
クリエイティブタブの作成ができましたので、前述で作成したアイテムの登録先をその他タブから作成したタブに変更します。
package jp.artan.tm.item;
import jp.artan.tm.TutorialMod;
import jp.artan.tm.event.IItemRegisterEvent;
import jp.artan.tm.init.ItemInit;
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.item.Item;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.event.RegistryEvent;
public class TutorialItem extends Item implements IItemRegisterEvent {
    private final String name;
    public TutorialItem(String name) {
        super();
        this.name = name;
        this.setUnlocalizedName(this.name);
        this.setRegistryName(TutorialMod.MODID, this.name);
        this.setCreativeTab(TutorialMod.creativeTabs); // 変更
        ItemInit.ITEMS.add(this);
    }
    @Override
    public void registerItem(RegistryEvent.Register<Item> event) {
        event.getRegistry().register(this);
    }
    public void registerModel(ModelRegistryEvent event) {
        ModelLoader.setCustomModelResourceLocation(this, 0,
                new ModelResourceLocation(new ResourceLocation(TutorialMod.MODID, this.name), "inventory"));
    }
}
下記の画像のように表示されれば成功です。
食べ物の追加
バナナのアイコンをしていましたが、先ほど作ったのは無機能アイテムなので食べることができませんでした。
次は食べれるアイテムの追加を行いたいと思います。
また、今回のアイテムのテクスチャはバニラのリンゴのテクスチャを流用しようと思います。
package jp.artan.tm.item;
import jp.artan.tm.TutorialMod;
import jp.artan.tm.event.IItemRegisterEvent;
import jp.artan.tm.init.ItemInit;
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.item.Item;
import net.minecraft.item.ItemFood;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.event.RegistryEvent;
public class TutorialFood extends ItemFood implements IItemRegisterEvent {
    private final String name;
    public TutorialFood(String name, int amount, float saturation) {
        super(amount, saturation, false);
        this.name = name;
        this.setUnlocalizedName(this.name);
        this.setRegistryName(TutorialMod.MODID, this.name);
        this.setCreativeTab(TutorialMod.creativeTabs);
        ItemInit.ITEMS.add(this);
    }
    @Override
    public void registerItem(RegistryEvent.Register<Item> event) {
        event.getRegistry().register(this);
    }
    @Override
    public void registerModel(ModelRegistryEvent event) {
        ModelLoader.setCustomModelResourceLocation(this, 0,
                new ModelResourceLocation(new ResourceLocation(TutorialMod.MODID, this.name), "inventory"));
    }
}
ItemFoodのコンストラクタの引数は次のようになっている。
第1引数 amount     食べた時の回復量
第2引数 saturation 食べた時の隠し満腹度の回復量
第3引数 isWolfFood オオカミの食べ物にするか
package jp.artan.tm.init;
import jp.artan.tm.event.IItemRegisterEvent;
import jp.artan.tm.item.TutorialFood;
import jp.artan.tm.item.TutorialItem;
import net.minecraft.item.Item;
import java.util.ArrayList;
import java.util.List;
public class ItemInit {
    public static final List<IItemRegisterEvent> ITEMS = new ArrayList<IItemRegisterEvent>();
    public static final Item TUTORIAL_ITEM = new TutorialItem("tutorial_item");
    public static final Item TUTORIAL_FOOD = new TutorialFood("tutorial_food", 1, 1.0F); // 追記
}
# English Language File
# Tabs
itemGroup.tutorial_tab=Tutorial Tab
# Items
item.tutorial_item.name=Tutorial Item
item.tutorial_food.name=Tutorial Food
# 日本語言語ファイル
# Tabs
itemGroup.tutorial_tab=チュートリアルタブ
# Items
item.tutorial_item.name=チュートリアルアイテム
item.tutorial_food.name=チュートリアル食べ物
{
  "parent": "item/generated",
  "textures": {
    "layer0": "minecraft:items/apple"
  }
}
これで実行すると次のようになるかと思います。
作物の追加
次は、畑の作物の追加を行いたいと思います。
作物を作成するために必要なものは、種アイテム・作物アイテム・作物ブロックの3つが必要になります。
一括登録処理
まずは、アイテムの一括登録処理と同じようにブロックを一括登録できるように設定を行います。
package jp.artan.tm.event;
import net.minecraft.block.Block;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.event.RegistryEvent;
public interface IBlockRegisterEvent {
    /**
     * Register Block
     *
     * @param event
     */
    void registerBlock(RegistryEvent.Register<Block> event);
    /**
     * Register Model
     *
     * @param event
     */
    void registerModel(ModelRegistryEvent event);
}
package jp.artan.tm.init;
import jp.artan.tm.block.TutorialPlant;
import jp.artan.tm.event.IBlockRegisterEvent;
import net.minecraft.block.BlockCrops;
import java.util.ArrayList;
import java.util.List;
public class BlockInit {
    public static final List<IBlockRegisterEvent> BLOCKS = new ArrayList<IBlockRegisterEvent>();
}
package jp.artan.tm.proxy;
import jp.artan.tm.TutorialMod;
import jp.artan.tm.init.BlockInit;
import jp.artan.tm.init.ItemInit;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@Mod.EventBusSubscriber(modid = TutorialMod.MODID)
public abstract class CommonProxy {
    public void preInit(FMLPreInitializationEvent event) {
        TutorialMod.logger.info("CommonProxy.preInit");
    }
    public void init(FMLInitializationEvent event) {
        TutorialMod.logger.info("CommonProxy.init");
    }
    public void postInit(FMLPostInitializationEvent event) {
        TutorialMod.logger.info("CommonProxy.postInit");
    }
    @SubscribeEvent
    public void registerItems(RegistryEvent.Register<Item> event) {
        TutorialMod.logger.info("CommonProxy.registerItems");
        ItemInit.ITEMS.forEach(f -> f.registerItem(event));
    }
    @SubscribeEvent
    public void registerBlocks(RegistryEvent.Register<Block> event) { // 追記
        TutorialMod.logger.info("CommonProxy.registerBlocks");
        BlockInit.BLOCKS.forEach(f -> f.registerBlock(event));
    }
}
package jp.artan.tm.proxy;
import jp.artan.tm.TutorialMod;
import jp.artan.tm.init.BlockInit;
import jp.artan.tm.init.ItemInit;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
public class ClientProxy extends CommonProxy {
    @Override
    public void preInit(FMLPreInitializationEvent event) {
        super.preInit(event);
        TutorialMod.logger.info("ClientProxy.preInit");
    }
    @SubscribeEvent
    public void registerModels(ModelRegistryEvent event) {
        TutorialMod.logger.info("ClientProxy.registerModels");
        ItemInit.ITEMS.forEach(f -> f.registerModel(event));
        BlockInit.BLOCKS.forEach(f -> f.registerModel(event)); // 追記
    }
}
作物ブロックと種アイテムの作成
作物ブロックと種アイテムはそれぞれがインスタンス内で、相方のインスタンスを利用する仕組みになっている。
さらに、種アイテムのインスタンスを生成するときは作物ブロックのインスタンスを必要としている。
まずは、それぞれのインスタンスをBlockInitとItemInitに配置する。
それぞれのクラス名はTutorialPlantとTutorialSeedとする。
package jp.artan.tm.init;
import jp.artan.tm.block.TutorialPlant;
import jp.artan.tm.event.IBlockRegisterEvent;
import net.minecraft.block.BlockCrops;
import java.util.ArrayList;
import java.util.List;
public class BlockInit {
    public static final List<IBlockRegisterEvent> BLOCKS = new ArrayList<IBlockRegisterEvent>();
    public static final BlockCrops TUTORIAL_PLANT = new TutorialPlant("tutorial_plant");
}
package jp.artan.tm.init;
import jp.artan.tm.event.IItemRegisterEvent;
import jp.artan.tm.item.TutorialFood;
import jp.artan.tm.item.TutorialItem;
import jp.artan.tm.item.TutorialSeed;
import net.minecraft.item.Item;
import net.minecraft.item.ItemFood;
import net.minecraft.item.ItemSeeds;
import java.util.ArrayList;
import java.util.List;
public class ItemInit {
    public static final List<IItemRegisterEvent> ITEMS = new ArrayList<IItemRegisterEvent>();
    public static final Item TUTORIAL_ITEM = new TutorialItem("tutorial_item");
    public static final ItemFood TUTORIAL_FOOD = new TutorialFood("tutorial_food", 1, 1.0F);
    public static final ItemSeeds TUTORIAL_SEED = new TutorialSeed("tutorial_seed");
}
それぞれのインスタンスを配置できたので、TutorialPlantとTutorialSeedを作成していく。
package jp.artan.tm.block;
import jp.artan.tm.TutorialMod;
import jp.artan.tm.event.IBlockRegisterEvent;
import jp.artan.tm.init.BlockInit;
import jp.artan.tm.init.ItemInit;
import net.minecraft.block.Block;
import net.minecraft.block.BlockCrops;
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.item.Item;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.event.RegistryEvent;
public class TutorialPlant extends BlockCrops implements IBlockRegisterEvent {
    protected final String Name;
    public TutorialPlant(String name) {
        super();
        this.Name = name;
        this.setUnlocalizedName(this.Name);
        this.setRegistryName(TutorialMod.MODID, this.Name);
        BlockInit.BLOCKS.add(this);
    }
    @Override
    public Item getSeed() {
        return ItemInit.TUTORIAL_SEED;
    }
    @Override
    public Item getCrop() {
        return ItemInit.TUTORIAL_FOOD;
    }
    @Override
    public void registerBlock(RegistryEvent.Register<Block> event) {
        event.getRegistry().register(this);
    }
    @Override
    public void registerModel(ModelRegistryEvent event) {
        ModelLoader.setCustomModelResourceLocation(Item.getItemFromBlock(this), 0,
                new ModelResourceLocation(new ResourceLocation(TutorialMod.MODID, this.Name), "inventory"));
    }
}
getSeedで作物ブロックを破壊したときにドロップする種アイテムを指定すること
getCropで作物ブロックを破壊したときにドロップする作物アイテムを指定すること
package jp.artan.tm.item;
import jp.artan.tm.TutorialMod;
import jp.artan.tm.event.IItemRegisterEvent;
import jp.artan.tm.init.BlockInit;
import jp.artan.tm.init.ItemInit;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.item.Item;
import net.minecraft.item.ItemSeeds;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumActionResult;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.common.EnumPlantType;
import net.minecraftforge.event.RegistryEvent;
public class TutorialSeed extends ItemSeeds implements IItemRegisterEvent {
    private final String name;
    public TutorialSeed(String name) {
        super(BlockInit.TUTORIAL_PLANT, Blocks.FARMLAND);
        this.name = name;
        this.setUnlocalizedName(this.name);
        this.setRegistryName(TutorialMod.MODID, this.name);
        this.setCreativeTab(TutorialMod.creativeTabs);
        ItemInit.ITEMS.add(this);
    }
    @Override
    public EnumActionResult onItemUse(EntityPlayer player, World worldIn, BlockPos pos, EnumHand hand,
                                      EnumFacing facing, float hitX, float hitY, float hitZ) {
        ItemStack stack = player.getHeldItem(hand);
        IBlockState state = worldIn.getBlockState(pos);
        if (facing == EnumFacing.UP && player.canPlayerEdit(pos.offset(facing), facing, stack)
                && state.getBlock().canSustainPlant(state, worldIn, pos, EnumFacing.UP, this)
                && worldIn.isAirBlock(pos.up())) {
            worldIn.setBlockState(pos.up(), BlockInit.TUTORIAL_PLANT.getDefaultState());
            if (player.capabilities.isCreativeMode || !worldIn.isRemote) {
                stack.shrink(1);
            }
            return EnumActionResult.SUCCESS;
        }
        return EnumActionResult.FAIL;
    }
    @Override
    public EnumPlantType getPlantType(IBlockAccess world, BlockPos pos) {
        return EnumPlantType.Crop;
    }
    @Override
    public IBlockState getPlant(IBlockAccess world, BlockPos pos) {
        return BlockInit.TUTORIAL_PLANT.getDefaultState();
    }
    @Override
    public void registerItem(RegistryEvent.Register<Item> event) {
        event.getRegistry().register(this);
    }
    @Override
    public void registerModel(ModelRegistryEvent event) {
        ModelLoader.setCustomModelResourceLocation(this, 0,
                new ModelResourceLocation(new ResourceLocation(TutorialMod.MODID, this.name), "inventory"));
    }
}
テクスチャと言語設定
次に言語設定とテクスチャの設定を行う
# English Language File
# Tabs
itemGroup.tutorial_tab=Tutorial Tab
# Items
item.tutorial_item.name=Tutorial Item
item.tutorial_food.name=Tutorial Food
item.tutorial_seed.name=Tutorial Seed
# 日本語言語ファイル
# Tabs
itemGroup.tutorial_tab=チュートリアルタブ
# Items
item.tutorial_item.name=チュートリアルアイテム
item.tutorial_food.name=チュートリアル食べ物
item.tutorial_seed.name=チュートリアル種
{
  "parent": "item/generated",
  "textures": {
    "layer0": "tm:items/tutorial_seed"
  }
}
{
  "parent": "block/cube_all",
  "textures": {
    "all": "tm:blocks/tutorial_plant"
  }
}
{
    "variants": {
        "age=0": {
            "model": "tm:tutorial_plant0"
        },
        "age=1": {
            "model": "tm:tutorial_plant1"
        },
        "age=2": {
            "model": "tm:tutorial_plant2"
        },
        "age=3": {
            "model": "tm:tutorial_plant3"
        },
        "age=4": {
            "model": "tm:tutorial_plant4"
        },
        "age=5": {
            "model": "tm:tutorial_plant5"
        },
        "age=6": {
            "model": "tm:tutorial_plant6"
        },
        "age=7": {
            "model": "tm:tutorial_plant7"
        }
    }
}
上記のファイルで作物が成長する7段階に対応したテクスチャ情報が格納されているjsonファイルを指定できるようになる
下記には成長していない最初の状態のデータだけを記載しておくので、0~7の8個のjsonデータを作成すること
{
  "parent": "block/crop",
  "textures": {
    "crop": "tm:blocks/tutorial_plant0"
  }
}
最大成長したあとに破壊したときにTutorialPlantで指定した、作物アイテムと種アイテムがドロップすればよい。







