Minecraft Modding

コードが書きたい。

食べ物を追加するMod その2 食べ物を追加する(本編)

目次

これから何する

前記事から引き続き食べ物を追加していきます。今回がいわば本編、食べ物を追加します。記事は3回くらいに分けて書く予定の2回目です。

開発環境
食べ物を定義する

まずはjavaソースフォルダ配下にportionmod.itemパッケージを追加し、パッケージにPortionクラスを追加します 。

Portion.java

package portionmod.item;

import net.minecraft.item.ItemFood;
import portionmod.PortionMod;

/***
 * アイテムとしてのポーションの機能を定義します。
 * 
 * @author atsushi
 *
 */
public class Portion extends ItemFood {
    public Portion() {
        super(
                4,      // 満腹度回復量
                1.2F,   // 隠し満腹度(腹持ち)
                false   // 狼の餌か?
        );
        
        /*
         *  Minecraftへの登録名
* en_US.lang内に表示名が見つからない場合はこの名前が表示されます */ setRegistryName(PortionMod.MODID, "portion"); // 言語ファイルから一致する値をアイテム名として表示します setUnlocalizedName(PortionMod.MODID + ".portion"); // 満腹でも食べます setAlwaysEdible(); } }

 

食べ物にテクスチャを関連付ける

初めにテクスチャを指定する為モデルを定義します。
まずはresourcesソースフォルダ配下にportionmod.models.itemパッケージを追加し、パッケージにportion.jsonを追加します。

portion.json

{
"parent": "item/generated",
"textures": {
"layer0": "portionmod:items/portion"
}
}

 

次にテクスチャですね。
resourcesソースフォルダ配下にportionmod.textures.itemsパッケージを追加し、パッケージにportion.pngを追加します。
テクスチャのサイズは正方形であれば大丈夫そうです。

 

食べ物をゲームへ登録する

定義したアイテムをゲーム内へ反映させるためにはアイテムの登録とモデル及びテクスチャを読み込む必要があります。
まずはportionmodパッケージにModItemsクラスを追加し、次のように定義します。

ModItems.java

package portionmod;

import net.minecraft.item.Item;
import portionmod.item.Portion;

/***
* このModで追加されるアイテムを保持します。
* 
* @author atsushi
*
*/
public class ModItems {
    public static Item PORTION = new Portion();
}

 

次に追加した食べ物をゲームへ登録します。

CommonProxy.java

package portionmod.proxy;

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;
import portionmod.ModItems;
import portionmod.PortionMod;

/***
 * クライアントとサーバー共通でアイテム、ブロック、ツールの読み込みます。
 * 
 * @author atsushi
 *
 */
@Mod.EventBusSubscriber
public class CommonProxy {

    // なんか処理がある(省略)

    /***
     * Modで追加したいアイテムを読み込みます。
     * 勿論アイテムのクラスは自分で定義する必要があります。
     * 
     * @param event
     */
    @SubscribeEvent
    public static void registerItems(RegistryEvent.Register event) {
        PortionMod.logger.info("CommonProxy.registerItems");
        // 自作したアイテム(食べ物)のPortionを登録します
        event.getRegistry().register(ModItems.PORTION);
    }
}

 

モデルを読み込みます。

package portionmod.proxy;

import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import portionmod.ModItems;
import portionmod.PortionMod;

/***
 * クライアント側でリソースを読み込みます。
 * 
 * @author atsushi
 *
 */
@Mod.EventBusSubscriber(Side.CLIENT)
public class ClientProxy extends CommonProxy {
    @Override
    public void preInit(FMLPreInitializationEvent event) {
        super.preInit(event);
        PortionMod.logger.info("ClientProxy.preInit");
    }

    /***
     * ブロックやアイテムのモデル(テクスチャは何を使うとかテクスチャの向きとか定義したファイル)を読み込みます。
     * 
     * @param event
     */
    @SubscribeEvent
    public static void registerModels(ModelRegistryEvent event) {
        PortionMod.logger.info("ClientProxy.registerModels");
        
        // Portionのモデルを読み込みます
        ModelLoader.setCustomModelResourceLocation(
                ModItems.PORTION,   // Portionのインスタンス
                0,                  // メタデータ
                new ModelResourceLocation( // モデル定義ファイルのパス
                        new ResourceLocation(PortionMod.MODID, "portion"), "inventory"));
    }
}

 

食べ物にゲーム内で表示する名称を設定する

resourcesソースフォルダのassets.portionmod.langパッケージに次のファイルを追加します。

en_US.lang

item.portionmod.portion.name=Portion

ja_JP.lang

item.portionmod.portion.name=ポーション

実際にMinecraftを起動し、言語設定を変えてアイテム名が期待通り表示されるか確認してみると良いでしょう。

 

食べ物のレシピを追加する

それでは最後にレシピですが、1.12ではレシピ登録にクラスは不要となり、JSONのみで定義することが可能になりました。
resourcesソースフォルダ配下にassets.portionmod.recipesパッケージを追加し、パッケージにportion.jsonを追加します。実際はファイル名に制限はありませんが、便宜上ファイル名はアイテム名に合わせます。

{
  "type": "minecraft:crafting_shapeless",
  "ingredients": [
    {
      "item": "minecraft:dirt",
      "data": 0
    }
  ],
  "result": {
    "item": "portionmod:portion",
    "count": 1
  }
}

 

今回の例では不定形レシピの追加方法になります。定形レシピはまだ勉強中です。
ingredientsの子要素のdataはメタデータのことで、土ブロックなど一部のアイテムやブロックではメタデータにより状態が変化するものが存在します。今回の土ブロックの場合、通常の土ブロックの他粗い土、ポドゾルが存在します。

 

Github

以上で新食べ物ポーションを追加出来ました。GitHub上から確認出来ます。
まずは今回の差分。

github.com

 

そして変更後の構成。

PortionMod

github.com

食べ物を追加するMod その1 プロジェクトの下準備

目次

これから何する

MinecraftのForgeを利用してModを開発していきます。Modは食べ物を追加するだけのチュートリアル的なものを作ります。Mod開発に興味を持った人への入門向けな内容を目指します。記事は3回くらいに分けて書く予定です。

開発環境
Mod開発スタート地点
MDKExample

フォルダ構成と定義が上記の状態から開発に取り掛かります。
GitHubはこちらです。

github.com

作りたいModに合わせて名称を変更する

では早速始めていきましょう。まずはプロジェクト名やらMod名やらをそれっぽく変更します。
変更する項目は次の通りです。

  • プロジェクト名
  • Mod本体のクラス名
  • Modのパッケージ名
  • Mod本体に書かれているプロキシクラスのパッケージ階層文字列
  • resoucesパッケージ名
  • mcmod.infoとMod本体で定義されるmodid

 

ソースコードの差分こんな感じです。

github.com

 

修正後の構成です。

PortionMod

 

一先ずModを作っていく為の下準備が完了したという事で、一度ここで締めます。
当初の予定ではアイテムを定義してMinecraftに追加するところまでやりたかったのですが、めげてしまいそうだったのでここまでにしたいと思います。
引き続き次の記事で解説していくので、次の記事も読んでもらえたら嬉しいです。

雛形を更新したのでもうちょっと入門らしく

今日の目次

動作確認

プロジェクトのセットアップが完了している前提で始めます。
まずは正常にプロジェクトが実行出来ること、modが読み込まれることを確認しましょう。
f:id:atushi-info:20170913022704p:plain

実行の仕方

f:id:atushi-info:20170913034226p:plain

プロジェクトの設定

確認が出来れば早速コーディングに移りたいですが、その前にちょっとだけプロジェクトの設定を変更したいと思います。
「メニューバー -> ウインドウ -> 設定」から下記のスクリーンショットのようにしましょう。
この設定では日本語のコメントを書いた時に文字化けしない為の設定です。
※UIが真っ黒なのは別途テーマをインストールしているからです(黒いとかっこいい!)。
f:id:atushi-info:20170913024745p:plain

Modのパッケージ構成を変更

あまり行う必要がないと言えばないのですが、個人的な趣向になってしまいますが階層を浅くしたい為変更します。
現在は「com.example.examplemod」となっていると思いますが「com.example」を削除し、「examplemod」にします。基本的にはMod名にあたるパッケージ名で十分重複するようなことはないと思ったのでこのようにしました。

パッケージ名の変更

一応方法が分からない方のために手順を載せます。
パッケージ・エクスプローラーに表示されているパッケージ「com.example.examplemod」を右クリックします。
「表示されたメニュー -> リファクタリング -> 名前変更」で下記のスクリーンショットのように編集し、OKボタンを押せば反映されます。
※名前変更は「com.example.examplemod」をクリックし、F2を押しても行うことが出来ます。
f:id:atushi-info:20170913032302p:plain

Mod本体の処理を定義

やって参りましたようやくコーディングです。
何も手を付けていなければExampleModクラスではinitメソッドが以下のように定義されていると思います。
※ちなみに@Modが宣言されているクラスがMod本体になります。
ExampleMod.java

   @EventHandler
    public void init(FMLInitializationEvent event)
    {
        // some example code
        System.out.println("DIRT BLOCK >> "+Blocks.DIRT.getUnlocalizedName());
    }

この定義を次のコードで上書きします。

    // ログ出力で使います
    public static Logger logger;

    /***
     * Mod本体のメソッド中最初に呼ばれます。<br>
     * 変数の初期化など後続の処理に影響のあることを済ませます。
     * 
     * @param event
     */
    @Mod.EventHandler
    public void preInit(FMLPreInitializationEvent event) {
        logger = event.getModLog();
    }

    /***
     * preinitの後に呼ばれます。<br>
     * 言わばこのModの本体とも言える主要なメソッドです。<br>
     * とにかく主要なことはここで処理します。
     * 
     * @param event
     */
    @Mod.EventHandler
    public void init(FMLInitializationEvent event) {
    }

    /***
     * initの後に呼ばれます。つまりMod本体の中では最後に呼ばれます。<br>
     * リソースの開放や不要なデータの削除などお片付け的なことをします。
     * 
     * @param event
     */
    @Mod.EventHandler
    public void postInit(FMLPostInitializationEvent event) {
    }

編集出来ればエラーにならないか一度実行してみましょう。
最初に実行したときのように問題なく動作すれば大丈夫です。

クライアントとサーバーで処理を分岐

サーバーではテクスチャやモデルと言った画面表示周りの処理が不要である為、それらの処理を書き分けられるようにします。
まずはパッケージ・エクスプローラーよりexamplemodパッケージ配下にproxyパッケージを作りましょう。
「examplemodパッケージを右クリック -> 新規 -> パッケージ」を選択すると下記のようにウインドウが出るので名前をexamplemod.proxyにします。
f:id:atushi-info:20170913041749p:plain

次に同じ要領で「proxyパッケージを右クリック -> 新規 -> クラス」で表示されたウインドウに下記のように入力してCommonProxy、ClientProxy、ServerProxyクラスをそれぞれ作ります。
f:id:atushi-info:20170913042616p:plain

各クラスは次のように書きましょう。
CommonProxy.java

package examplemod.proxy;

import examplemod.ExampleMod;
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;

/***
 * クライアントとサーバー共通でアイテム、ブロック、ツールの読み込みます。
 * 
 * @author youname
 *
 */
@Mod.EventBusSubscriber
public class CommonProxy {
    public void preInit(FMLPreInitializationEvent event) {
        ExampleMod.logger.info("CommonProxy.preInit");
    }

    public void init(FMLInitializationEvent event) {
        ExampleMod.logger.info("CommonProxy.init");
    }

    public void postInit(FMLPostInitializationEvent event) {
        ExampleMod.logger.info("CommonProxy.postInit");
    }

    @SubscribeEvent
    public static void registerBlocks(RegistryEvent.Register<Block> event) {
        ExampleMod.logger.info("CommonProxy.registerBlocks");
    }

    /***
     * Modで追加したいアイテムを読み込みます。<br>
     * 勿論アイテムのクラスは自分で定義する必要があります。
     * 
     * @param event
     */
    @SubscribeEvent
    public static void registerItems(RegistryEvent.Register<Item> event) {
        ExampleMod.logger.info("CommonProxy.registerItems");
    }
}

ClientProxy.java

package examplemod.proxy;

import examplemod.ExampleMod;
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;

/***
 * クライアント側でリソースを読み込みます。
 * 
 * @author youname
 *
 */
@Mod.EventBusSubscriber(Side.CLIENT)
public class ClientProxy extends CommonProxy {
    @Override
    public void preInit(FMLPreInitializationEvent event) {
        super.preInit(event);
        ExampleMod.logger.info("ClientProxy.preInit");
    }

    /***
     * ブロックやアイテムのモデル(テクスチャは何を使うとかテクスチャの向きとか定義したファイル)を読み込みます。<br>
     * 
     * @param event
     */
    @SubscribeEvent
    public static void registerModels(ModelRegistryEvent event) {
        ExampleMod.logger.info("ClientProxy.registerModels");
    }
}

ServerProxy.java

package examplemod.proxy;

import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.relauncher.Side;

/***
 * サーバー側で行う処理。
 * 
 * @author youname
 *
 */
@Mod.EventBusSubscriber(Side.SERVER)
public class ServerProxy extends CommonProxy {
}

プロキシクラスが定義出来たらExampleModクラスから呼び出しましょう。
次のようになります。
ExampleMod.java

package examplemod;

import org.apache.logging.log4j.Logger;

import examplemod.proxy.CommonProxy;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.SidedProxy;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;

/***
 * Modの本体で、@Modを宣言することで本体として認識されます。<br>
 * modidの文字列はmcmod.infoのmodidと同一にしなければ、mcmod.infoが読み込まれないので設定値を合わせる必要があります。<br>
 * このクラスでMinecraftにブロックやアイテムの登録を行います。<br>
 * 
 * @author myname
 * @version forge-1.12-14.21.1.2426
 */
@Mod(modid = ExampleMod.MODID, version = ExampleMod.VERSION)
public class ExampleMod {

    // Modの識別やリソースのドメイン名に利用されます
    public static final String MODID = "examplemod";
    // Modのバージョン
    public static final String VERSION = "0.0.1";

    // プロキシ(読み込み処理)のパッケージ階層
    public static final String CLIENT_PROXY = "examplemod.proxy.ClientProxy";
    public static final String SERVER_PROXY = "examplemod.proxy.ServerProxy";

    // サーバー、クライアントを識別しインスタンスを保持します
    @SidedProxy(clientSide = CLIENT_PROXY, serverSide = SERVER_PROXY)
    public static CommonProxy proxy;

    // いまいち必要性が分からない
    @Mod.Instance
    public static ExampleMod instance;

    // ログ出力で使います
    public static Logger logger;

    /***
     * Mod本体のメソッド中最初に呼ばれます。<br>
     * 変数の初期化など後続の処理に影響のあることを済ませます。
     * 
     * @param event
     */
    @Mod.EventHandler
    public void preInit(FMLPreInitializationEvent event) {
        logger = event.getModLog();
        proxy.preInit(event);
    }

    /***
     * preinitの後に呼ばれます。<br>
     * 言わばこのModの本体とも言える主要なメソッドです。<br>
     * とにかく主要なことはここで処理します。
     * 
     * @param event
     */
    @Mod.EventHandler
    public void init(FMLInitializationEvent event) {
        proxy.init(event);
    }

    /***
     * initの後に呼ばれます。つまりMod本体の中では最後に呼ばれます。<br>
     * リソースの開放や不要なデータの削除などお片付け的なことをします。
     * 
     * @param event
     */
    @Mod.EventHandler
    public void postInit(FMLPostInitializationEvent event) {
        proxy.postInit(event);
    }
}

それではこれで実行してみましょう。
無事に実行出来ればコンソールに出力されるログの中から下記のような出力を見つけることが出来るはずです。

[05:07:21] [main/INFO] [examplemod]: CommonProxy.preInit
[05:07:21] [main/INFO] [examplemod]: ClientProxy.preInit
~~~
[05:07:21] [main/INFO] [examplemod]: CommonProxy.registerBlocks
~~~
[05:07:21] [main/INFO] [examplemod]: CommonProxy.registerItems
~~~
[05:07:21] [main/INFO] [examplemod]: ClientProxy.registerModels
~~~
[05:07:50] [main/INFO] [examplemod]: CommonProxy.init
~~~
[05:07:50] [main/INFO] [examplemod]: CommonProxy.postInit

出力されていれば雛形の完成です。長々とお付き合いありがとうございました。
コードはGithubで公開しています。

Github

github.com

やっぱり最初は雛形からでしょうか

次のコードは以下のバージョンに対応しています。

  • forge-1.12-14.21.1.2426

ExampleMod.java

package example.examplemod;

import example.examplemod.util.Utils;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventHandler;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;

/***
 * Modの本体で、@Modを宣言することで本体として認識されます。<br>
 * modidの文字列はmcmod.infoのmodidと同一にしなければ、mcmod.infoが読み込まれないので設定値を合わせる必要があります。<br>
 * このクラスでMinecraftにブロックやアイテムの登録を行います。<br>
 * @author atsushi
 * @version forge-1.12-14.21.1.2426
 */
@Mod(modid = ModInfo.MODID)
public class ExampleMod {

	/***
	 * ブロックやアイテムなどの登録を行います。
	 * @param event
	 */
	@EventHandler
	public void preInit(FMLInitializationEvent event) {
		Utils.getLogger().info("Pre Initialize.");
	}

	/***
	 * レシピまたはイベントの登録を行います。
	 * @param event
	 */
	@EventHandler
	public void init(FMLInitializationEvent event) {
		Utils.getLogger().info("Initialize.");
	}

	/***
	 * 何をするメソッドなのか不明です。
	 * @param event
	 */
	@EventHandler
	public void postInit(FMLInitializationEvent event) {
		Utils.getLogger().info("Post Initialize.");
	}
}

ModInfo.java

package example.examplemod;

/***
 * Mod情報の定義<br>
 * 詳細はmcmod.infoに記述する。
 * @author atsushi
 * @version forge-1.12-14.21.1.2426
 */
public class ModInfo {
	
	public static final String MODID = "modid";
	public static final String NAME = "examplemod";
	public static final String VERSION = "0.0.1";
}

Utils.java

package example.examplemod.util;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import example.examplemod.ModInfo;

/***
 * 一先ずはロガー用
 * @author atsushi
 * @version forge-1.12-14.21.1.2426
 */
public class Utils {

	private static Logger logger;
	
	public static Logger getLogger() {
		if(logger == null) {
			logger = LogManager.getFormatterLogger(ModInfo.MODID);
		}
		return logger;
	}
}

mcmod.info

[
{
  "modid": "modid",
  "name": "Example Mod",
  "description": "Example Mod desctiption.",
  "version": "0.0.1",
  "mcversion": "1.12",
  "url": "",
  "updateUrl": "",
  "authorList": ["atsuhi"],
  "credits": "The Forge and FML guys, for making this example",
  "logoFile": "",
  "screenshots": [],
  "dependencies": []
}
]

ソースコードはこちらにアップロードしています。
github.com