疲れたらやすむ

Javaを学ぶ上でハマったところを書いていきます。iPhoneアプリ開発や日常ネタもあるかも。

【Java】【Minecraft】Mod作成基礎編

今回解説するのは以下の6点です。
・ブロックとアイテムの追加
・レシピの追加
・テクスチャの設定
・クリエイティブタブの追加
・日本語表記対応
・jarの生成

たくさんありますがMod作成としては初歩的な部分だと思いますので1つの記事にまとめます。
Minecraftのバージョンは1.7.10で進めていきます。
開発環境構築については以下の記事を参考にしてください。

【Java】【Minecraft】Modの開発環境構築

今の所は1.7.10以外のバージョンではModを作成を行う予定はありません。
ですが、1.12.2もModの数が豊富なので気持ちが揺らいでいる部分はあります。

完成イメージ

新規ブロックの追加。

f:id:chibiCat:20190510172034p:plain

レシピの追加。
既存ブロックのレシピも追加可能です。
例えば土からダイヤモンドをクラフトするレシピも追加できます。

f:id:chibiCat:20190510172031p:plain

そしてクリエイティブタブの追加と和名表示。

f:id:chibiCat:20190510172028p:plain

ソース

パッケージ構成は以下のようになっています。

f:id:chibiCat:20190510175733p:plain

ここまで分ける必要があるか怪しいですが。
後々ブロックやアイテムの数が増えることを想定すると妥当かなと思っています。

以降は順番にソースの紹介です。

Mainのクラスです。
このクラスがModの核心になります。
クラス宣言の@ModのアノテーションによりModであることを宣言します。必須です。

そしてpreInitとinitの2つのメソッドがあります。
どちらも@EventHandlerのアノテーションを記述しています。これも必須。
文字通りではありますが、初期化処理です。
Modはまず、FMLPreInitializationEventを受け処理を行います。
次にFMLInitializationEventを受け処理を行います。
ブロックの追加やレシピの追加の順序は正しく行う必要があり、不正な順序である場合正しく動作しません。

SampleMod.java

package samplemod.main;

import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.Mod.EventHandler;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import net.minecraft.creativetab.CreativeTabs;
import samplemod.block.BlockManager;
import samplemod.creativetab.SampleModTab;
import samplemod.item.ItemManager;
import samplemod.recipe.RecipeManager;

@Mod(modid = SampleMod.MODID, name = SampleMod.MODNAME, version = SampleMod.VERSION)
public class SampleMod {

	public static final String MODID = "sampleMod";
	public static final String MODNAME = "SampleMod";
	public static final String VERSION = "[1.7.10]1.0.0";

	//クリエイティブタブ追加
	public static final CreativeTabs SAMPLEMOD_TAB = new SampleModTab();


	// 一番最初に実行される処理
	@EventHandler
	public void preInit(FMLPreInitializationEvent event) {
		// ブロックの登録
		BlockManager.registerBlock();
		// アイテムの登録
		ItemManager.registerItem();
	}

	// preInitの次に実行される処理
	@EventHandler
	public void init(FMLInitializationEvent event) {
		// レシピの登録
		RecipeManager.registerRecipe();
	}
}

ブロックの管理クラス。

BlockManager.java

package samplemod.block;

import cpw.mods.fml.common.registry.GameRegistry;
import net.minecraft.block.Block;
import samplemod.block.blocks.SampleBlock;
import samplemod.main.SampleMod;

public class BlockManager {

	public static Block sampleBlock;

	public static void registerBlock() {
		// SampleBlockを生成し登録する
		sampleBlock = new SampleBlock()
				.setBlockName("sampleBlock")
				.setCreativeTab(SampleMod.SAMPLEMOD_TAB)
				.setBlockTextureName("samplemod:sampleBlock");
		GameRegistry.registerBlock(sampleBlock, "sampleBlock");
	}
}

追加するブロッククラス

SampleBlock.java

package samplemod.block.blocks;

import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import samplemod.main.SampleMod;

public class SampleBlock extends Block {

	public SampleBlock() {
		super(Material.rock);
		this.setHardness(1.5F);
		this.setResistance(10.0F);
		this.setStepSound(soundTypePiston);
	}
}

アイテムの管理クラス

ItemManager.java

package samplemod.item;

import cpw.mods.fml.common.registry.GameRegistry;
import net.minecraft.item.Item;
import samplemod.item.items.SampleItem;
import samplemod.main.SampleMod;

public class ItemManager {

	public static Item sampleItem;

	public static void registerItem() {
		// SampleItemを生成し登録する
		sampleItem = new SampleItem()
				.setUnlocalizedName("sampleItem")
				.setCreativeTab(SampleMod.SAMPLEMOD_TAB)
				.setTextureName("samplemod:sampleItem");
		GameRegistry.registerItem(sampleItem, "sampleItem");
	}
}

追加するアイテムクラス

SampleItem.java

package samplemod.item.items;

import net.minecraft.item.Item;
import samplemod.main.SampleMod;

public class SampleItem extends Item {

	public SampleItem() {
		super();
	}
}

レシピの管理クラス

RecipeManager.java

package samplemod.recipe;

import cpw.mods.fml.common.registry.GameRegistry;
import net.minecraft.init.Blocks;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;
import samplemod.block.BlockManager;
import samplemod.item.ItemManager;

public class RecipeManager {

	public static final BlockManager BLOCK = new BlockManager();
	public static final ItemManager ITEM = new ItemManager();

	public static void registerRecipe() {
		// SampleBlockのレシピ追加
		GameRegistry.addRecipe(new ItemStack(BLOCK.sampleBlock, 1),
				" A ",
				"ABA",
				" A ",
				'A', ITEM.sampleItem,
				'B', new ItemStack(Items.dye, 1, 1));

		// SampleItemのレシピ追加
		GameRegistry.addRecipe(new ItemStack(ITEM.sampleItem, 2),
				"A",
				"B",
				'A', new ItemStack(Items.dye, 1, 12),
				'B', Blocks.iron_block);
	}
}

追加するクリエイティブタブクラス

SampleModTab.java

package samplemod.creativetab;

import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import samplemod.block.BlockManager;

public class SampleModTab extends CreativeTabs {

	public SampleModTab() {
		super("sampleMod");
	}

	@Override
	public ItemStack getIconItemStack() {
		return new ItemStack(BlockManager.sampleBlock);
	}

	@Override
	public Item getTabIconItem() {
		return null;
	}
}

以上がソースです。
あとは日本語表記に対応させるためのlangファイルが必要です。

ja_JP.lang

// ブロック和名
tile.sampleBlock.name=サンプルブロック

// アイテム和名
item.sampleItem.name=サンプルアイテム

// クリエイティブタブ和名
itemGroup.sampleMod=サンプルモッド

テクスチャ用の画像は6x16pxや32x32px等のpngが必要になります。
もしテクスチャを指定しなかった場合は黒と紫(ピンク)のテクスチャになります。
詳しくは後述します。

ブロックの追加

ブロックの追加はBlockManagerクラスで管理しています。
registerBlockメソッドで追加したいブロックを生成しGameRegistryクラスで登録します。
このメソッドをSampleModクラスのpreInitメソッドで呼び出します。

public static void registerBlock() {
	// SampleBlockを生成し登録する
	sampleBlock = new SampleBlock()
			.setBlockName("sampleBlock")
			.setCreativeTab(SampleMod.SAMPLEMOD_TAB)
			.setBlockTextureName("samplemod:sampleBlock");
	GameRegistry.registerBlock(sampleBlock, "sampleBlock");
}

setBlockName
Stringの引数を渡しブロックの名前を決めます。
sampleBlockを渡した場合、ブロック名は「tile.sampleBlock.name」になります。

setCreativeTab
CreativeTabsを引数に渡し登録先のクリエイティブタブを指定します。
SampleMod.SAMPLEMOD_TABを指定し今回追加するMod用のクリエイティブタブに登録します。
既存のクリエイティブタブに登録する場合はCreativeTabsクラスのメンバ変数を参照してください。
例えばCreativeTabs.tabBlockを指定することで建築ブロックタブに追加できます。

setBlockTextureName
Stringの引数を渡しブロックのテクスチャ名をドメイン名:テクスチャ画像名で指定します。
ドメイン名を指定しなかった場合は自動的にドメイン名が「minecraft」になります。
基本的にはドメイン名はModの名前を半角小文字で指定すれば良いと思います。
今回はModの名前がSampleModなのでドメイン名はsampleMod、テクスチャ画像名をsampleModにしています。


そして生成しているSampleBlockクラスはBlockクラスを継承しています。
コンストラクタを呼んでnewします。

public SampleBlock() {
	super(Material.rock);
}

super
親クラスであるBlockのコンストラクタです。
Materialを引数に渡しブロックの素材を指定します。
何のことかいまいちわかりづらいですが、簡単に言えばブロックの硬さや回収に必要な道具等が決められます。
例えばMaterial.woodを指定すると素手でブロックを破壊し回収できます。
今回指定しているMaterial.rockではピッケルを使わないと回収できません。
ですが、setHardnessメソッドで硬さを指定できますし、setHarvestLevelメソッドで回収に必要な道具も指定できます。
そのためMaterialはそこまで重要ではない気がしています。

アイテムの追加

基本はブロックの追加と同様です。
アイテムの追加はItemManagerクラスで管理しています。
registerBlockメソッドで追加したいブロックを生成しGameRegistryクラスで登録します。
呼び出し元はSampleModクラスのpreInitメソッドです。

public static void registerItem() {
	// SampleItemを生成し登録する
	sampleItem = new SampleItem()
			.setUnlocalizedName("sampleItem")
			.setCreativeTab(SampleMod.SAMPLEMOD_TAB)
			.setTextureName("samplemod:sampleItem");
	GameRegistry.registerItem(sampleItem, "sampleItem");
}

setUnlocalizedName
Stringの引数を渡しアイテムの名前を決めます。
sampleItemを渡した場合、アイテム名は「item.sampleBlock.name」になります。

setCreativeTab
ブロックと同様、CreativeTabsを引数に渡し登録先のクリエイティブタブを指定します。

setBlockTextureName
ブロックと同様、Stringの引数を渡しブロックのテクスチャを「ドメイン名:テクスチャ画像名」で指定します。

生成しているSampleItemはItemクラスを継承したクラスです。
同じくnewしてコンストラクタを呼びます。

public SampleItem() {
	super();
}

アイテムの場合はMaterialを指定する必要はありません。

レシピの追加

レシピの追加はRecipeManagerクラスで管理しています。
メンバ変数に追加するブロックやアイテムのクラスを定義しておきます。

public static final BlockManager BLOCK = new BlockManager();
public static final ItemManager ITEM = new ItemManager();

そしてregisterRecipeメソッドでレシピを追加し、GameRegistryクラスのaddRecipeメソッドで登録します。
SampleModクラスのinitメソッドで呼び出します。
Initで呼び出す理由はブロックやアイテムの追加済みの状態で処理するためです。

public static void registerRecipe() {
	// SampleBlockのレシピ追加
	GameRegistry.addRecipe(new ItemStack(BLOCK.sampleBlock, 1),
			" A ",
			"ABA",
			" A ",
			'A', ITEM.sampleItem,
			'B', new ItemStack(Items.dye, 1, 1));

	// SampleItemのレシピ追加
	GameRegistry.addRecipe(new ItemStack(ITEM.sampleItem, 2),
			"A",
			"B",
			'A', new ItemStack(Items.dye, 1, 12),
			'B', Blocks.iron_block);
}

addRecipe
第1引数にItemStack、第2引数にObject型の可変長を指定します。
具体的には、第1引数であるItemStackとは作成するブロックやアイテムです。
そして第2引数以降には以下の順番で指定します。
1.String型のクラフトの配置の仕方を表すもの
2.Char型のクラフトの配置を表す記号
3.Block型やItem型、ItemStack型の記号に紐付かせるブロックやアイテム

例えば上記のソースのSampleBlockの例の場合。
まず第1引数にはSampleBlockをItemStackでnewしています。
SampleBlockを1つ作るレシピだと言うことを示しています。

続いてStirng型で第2引数に" A "、第3引数に"ABA"、第4引数に" A "を指定しています。
クラフトをする場合、作業台を使って3x3のマス目にブロックやアイテムを入れると思います。
まさにそれです。
任意の文字や空白を使用しどのマスにどんなブロックやアイテムを配置するかを決めます。
ここでは同じ文字は同じブロックやアイテムとして扱われます。

そして第5引数にChar型の'A'、第6引数にItem型の今回追加するアイテムであるSampleItemを指定しています。
これはセットとして考え、「Aが表すアイテムはSampleItem」であることを定義しています。
同様に'B'が赤色の染料であることも定義します。
染料や羊毛の場合、引数によって色を判別します。

new ItemStack(Items.dye, 1, 1)

の3つ目の1が赤色を表す引数です。
SampleItemのレシピの

new ItemStack(Items.dye, 1, 12)

は水色の染料です。

そしてString型で表す配置の仕方ですが、3x3マスすべてを表す必要はありません。
SampleItemの例の様に指定した場合、場所を問わず縦に'A'と'B'を並べれば作成可能となります。

注意としては、ぱっと見Char型で指定する'A'や'B'をString型で"A"や"B"と指定しないことです。
コンパイルエラーは出ませんがClassCastExceptionが発生します。
必ずChar型で指定しましょう。

テクスチャの設定

追加したブロックやアイテムに対しテクスチャを設定します。
テクスチャを指定しない場合では透明ではなく、黒と紫(ピンク)のチェック柄のようなテクスチャになります。
面倒ではないのでテクスチャは設定することをおすすめします。

まずはブロックやアイテム時に設定したテクスチャ名を確認します。
例えばSampleBlockのテクスチャ名はsamplemod:sampleBlockです。
この場合ドメイン名はsamplemodです。
テクスチャ画像を配置する場所は「src/main/resources/assets/ドメイン名/textures」内に作成する「blocks」または「items」フォルダ内です。
そしてテクスチャ画像名はsampleBlockです。
sampleBlock.pngが紐付く画像になります。
つまり、sampleBlockのテクスチャの場合は「src/main/resources/assets/samplemod/textures/blocks」に「sampleMod.png」を入れることで指定できます。

テクスチャ画像のサイズは16x16pxや32x32pxなどにします。
試していませんが64x64px以上もOKのはず。

クリエイティブタブの追加

クリエイティブタブの追加はCreativeTabsクラスを継承したSampleModTabクラスで行います。

public SampleModTab() {
	super("sampleMod");
}

@Override
public ItemStack getIconItemStack() {
	return new ItemStack(BlockManager.sampleBlock);
}

@Override
public Item getTabIconItem() {
	return null;
}

SampleModTab
コンストラクタ。
Stringを引数に親クラスのCreativeTabsのコンストラクタを呼び出しクリエイティブタブの名前を決めます。
sampleModを渡した場合、クリエイティブタブ名は「itemGroup.sampleMod」になります。

getIconItemStack
CreativeTabsクラスを継承した際に実装する必要があるメソッド。
クリエイティブタブのアイコンを設定できます。
戻り値にアイコンにしたいブロックやアイテムをItemStack型で指定します。

getTabIconItem
CreativeTabsクラスを継承した際に実装する必要があるメソッド。
恐らくクリエイティブタブのアイコンを設定できます。
getIconItemStackメソッドで設定済みなのでnullを返しておきます。

そしてSampleModクラスのメンバ変数にSampleModTabクラスをnewします。

//クリエイティブタブ追加
public static final CreativeTabs SAMPLEMOD_TAB = new SampleModTab();

ブロックやアイテム追加時にsetCreativeTabメソッドの引数にSampleMod.SAMPLEMOD_TABを指定することで追加したクリエイティブタブに登録されます。

日本語表記対応

テクスチャの設定と似ており、和名を定義したファイルを追加することで日本語表記に対応します。
「ja_JP.lang」ファイルを「src/main/resources/assets/samplemod/lang」に配置します。
内容はソースに記載した通りです。
ブロック名と和名を紐付かせます。
SampleBlockの場合は

// ブロック和名
tile.sampleBlock.name=サンプルブロック

の様に記述します。
コメントはjavaの書き方をしなくても問題ありません。
個人的にそうしたかっただけです。

メモ帳などで作成し、ファイル形式をlangに変更しましょう。

jarの生成

通常Modはjar形式になっており、modsフォルダに入れることでModとして動作します。

まずはコマンドラインでワークスペースの1つ上のフォルダまで行きます。
名前を変更していなければ「forge-1.7.10〜src」のフォルダです。binフォルダやbuildフォルダがある所ですね。
Windowsの場合はエクスプローラーで該当フォルダを開き、ウインドウのアドレスバーに「cmd」を入力しENTER。
Macの場合はターミナルを開き「cd 」と入力後、該当フォルダをターミナルにドラッグ&ドロップでENTER。

次にコマンドラインで以下を入力しENTER。
Windowsの場合は「gradlew build」。
Macの場合は「./gradlew build」です。
ビルドに成功した場合は「BUILD SUCCESSFUL」と表示されます。

ビルド後、binフォルダ内にあるlibsフォルダにjarが生成されています。
生成時の名前は「modid-1.0.jar」となっているので手動で変更します。
生成したjarを使って実際にプレイしてみましょう。

今回は以上になります。