4日目 ブロックの追加 (単純なブロックとカスタムモデルを持つブロック)
今日はブロックの追加について書きたいと思います。
前半で基本的なブロックの登録、後半でカスタムモデルを持つブロックの登録について書いていきます。
~Part 1~
基本的なブロックの宣言と登録は次のようになります。
@Mod.EventBusSubscriber(bus=Mod.EventBusSubscriber.Bus.MOD) public class ModBlocks { public static final Block TEST_BLOCK = new Block(Block.Properties.create(Material.IRON).lightValue(15).sound(SoundType.METAL).hardnessAndResistance(1.5f, 2.5f)); @SubscribeEvent public static void registerBlocks(final RegistryEvent.Register<Block> event) { TEST_BLOCK.setRegistryName(TestMod.MODID, "test_block"); event.getRegistry().registerAll(TEST_BLOCK); } @SubscribeEvent public static void registerBlockItems(final RegistryEvent.Register<Item> event) { event.getRegistry().register(new BlockItem(TEST_BLOCK, new Item.Properties().group(TestItemGroups.TEST_GROUP)).setRegistryName(TEST_BLOCK.getRegistryName())); } }
BlockのコンストラクタのPropertiesでマテリアルやサウンドタイプ明るさ、堅さなどを設定できます。ブロックを登録する場合は、ブロックの登録に加えて、アイテムブロックの登録が必要になります。したがって、何個もブロックを登録すると、その度にアイテムブロックの作成をしなければならず、面倒くさいのに加えてミスも増えます。
なので私は、registryNameとitemGroupをコンストラクタに加え、Blockを継承したModBlockというクラスを作成し、ModBlocksクラスでnewした後にリストに渡すというやり方でBlockとItemBlockを一括登録するようにしています。
ModBlock.java
public class ModBlock extends Block { private ItemGroup group; public ModBlock(Properties properties, String name, ItemGroup group) { super(properties); this.group = group; setRegistryName(TestMod.MODID,name); } public ItemGroup getGroup() { return group; } }
@Mod.EventBusSubscriber(bus=Mod.EventBusSubscriber.Bus.MOD) public class ModBlocks { public static final ModBlock TEST_BLOCK = new ModBlock(Block.Properties.create(Material.IRON).lightValue(15).sound(SoundType.METAL).hardnessAndResistance(1.5f, 2.5f), "test_block", TestItemGroups.TEST_GROUP); public static final ModBlock EXAMPLE_BLOCK = new ExampleBlock(Block.Properties.create(Material.WOOD).lightValue(10).sound(SoundType.WOOD).hardnessAndResistance(0.5f), "example_block", TestItemGroups.EXAMPLE_GROUP); public static final List<ModBlock> LIST = new ArrayList<>(Arrays.asList(TEST_BLOCK,EXAMPLE_BLOCK)); @SubscribeEvent public static void registerBlocks(final RegistryEvent.Register<Block> event) { LIST.forEach(event.getRegistry()::register); } @SubscribeEvent public static void registerBlockItems(final RegistryEvent.Register<Item> event) { LIST.forEach(b -> event.getRegistry().register(new BlockItem(b, new Item.Properties().group(b.getGroup())).setRegistryName(b.getRegistryName()))); } }
これがベストな方法かどうかは分かりませんが、こうすることで、登録するブロックが増えてもリストにさえ入れてしまえば、BlockItemの登録漏れは起きないと思います。
ちなみにブロックもアイテム同様、簡単なブロックは宣言時の設定だけで十分でしょうし、複雑なブロックであれば専用のクラスを作るといいと思います。今回で言うと、TEST_BLOCKはModBlocksの宣言のみで作成し、EXAMPLE_BLOCKはクラス(後述)を作成しました。
これで登録はできたので、アイテム同様テクスチャ等の設定を行います。
ブロックはアイテムよりも作成しなければならないjsonファイル等の数が多く、
1. lang
2. blockstates
3. models
4. textures
5. loot_tables
の5つになります。
1. まずlangから、
{ "item.testmod.test_item": "Test Item", "item.testmod.example_item": "Example Item", "block.testmod.test_block": "Test Block", "block.testmod.example_block": "Example Block", "itemGroup.test": "Test Item Group", "itemGroup.example": "Example Item Group" }
”block.MODID.registryName":登録したい名前"
で登録できます。
2. 続いてblockstates
{ "variants": { "": { "model": "testmod:block/test_block" } } }
登録したいmodelのjsonファイルのパスを打ち込みます。
単純なブロックでは、今回の例のように1つのモデルの登録で済みますが、様々な状態を持つ複雑なブロック(例えばドアなど)は非常に複雑になります。以下のサイトからアセットをダウンロードして見てもらえば分かりますが、ドアには、ドアの向き、上下のブロック、右付き左付き、開閉など多くの状態があるので、その状態の数だけモデルを登録しなければならず、複雑になります。
MC Assets - Browser for Minecraft Asset Files
3. models
これも単純なブロックの場合は簡単です。
{ "parent": "block/cube_all", "textures": { "all": "testmod:block/test_block" } }
cube_allのところをcube、cube_column、などに変更することでそれぞれ6面や側面と上下面に個別のテクスチャを当てられるようになります。
これもバニラのアセットを参考にするといいでしょう。
{ "parent": "block/cube_column", "textures": { "end": "block/melon_top", "side": "block/melon_side" } }
また後述のモデルクリエイターを使うことで非キューブ状のカスタムモデルを作ることもできます。
4. テクスチャーの画像を登録します。16×16のpng画像をtextures/blockに入れます。
TestBlockのテクスチャーは氷塊を暗くしたものにしました。
六面や上下面と側面で異なるテクスチャーを登録した場合には必要な分だけ画像を入れます(~_top.png, ~_side.pngなど)。
5. ルートテーブルの設定。ここは1.12以前と大きく異なった点の一つです。以前はブロックを破壊した際のドロップアイテムはjavaのクラス内で記述していましたが、1.13からはそれらをルートテーブルで管理するようになりました。jsonファイルを記述するのは面倒ですが、使いようによっては以前より複雑な処理を簡単に実装できるような気もします。ルートテーブルは
resources/data/MODID/loot_tables/blocksにjson形式で設置します。
test_block.json
{ "type": "minecraft:block", "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "testmod:test_block" } ], "conditions": [ { "condition": "minecraft:survives_explosion" } ] } ] }
example_block.json
{ "type": "minecraft:block", "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:alternatives", "children": [ { "type": "minecraft:item", "conditions": [ { "condition": "minecraft:match_tool", "predicate": { "enchantments": [ { "enchantment": "minecraft:silk_touch", "levels": { "min": 1 } } ] } } ], "name": "testmod:example_block" }, { "type": "minecraft:item", "functions": [ { "function": "minecraft:set_count", "count": { "min": 3.0, "max": 9.0, "type": "minecraft:uniform" } }, { "function": "minecraft:apply_bonus", "enchantment": "minecraft:fortune", "formula": "minecraft:uniform_bonus_count", "parameters": { "bonusMultiplier": 1 } }, { "function": "minecraft:explosion_decay" } ], "name": "testmod:example_item" } ] } ] } ] }
今回は、TestBlockはブロックそのものをドロップ、ExampleBlockはシルクタッチではそのもの、通常破壊でExampleItemをドロップするようにしています。
これも、1からjsonを書くのは大変なのでバニラのルートテーブルから近いものを探して書き換えるのがいい気がします。
これでブロックの基本的な登録は終わりです。
~Part 2~
続いて、カスタムモデルを持つブロックの登録について書いていきます。
例えば、松明、ランタン、ホッパーなどは四角のブロックではなく、それぞれ固有の形を持っています。このようなブロックを作成します。
このようなブロックは基本的にmodels/blockの中のjsonファイルを書くことで作成することが出来ます。example_blockのmodelのjsonファイルを貼っておきます。
かなり長いjsonファイルですが、自分で書いたわけではなくモデルを作成するのに非常に便利なModel Creatorというツールがあるので、それを使用しました。
プロフラムはjavaで書かれているのでマイクラが起動できるなら起動できると思います。
作業中のスクリーンショットは以下のような感じです。
16×16×16の3次元空間に、ブロックを配置できるので右側のElementのタブでブロックのサイズ、位置を、Rotationで角度を、Facesでテクスチャをそれぞれ設定します。
テクスチャは自作のものでもバニラのものでも構いません。今回は作るのが面倒だったのでバニラのガラスとレッドストーンブロックのテクスチャを使いました。自作のものを使った場合にはassetsのtexturesにpngファイルを入れます。
作成し終えたら、FileからExport JSONを選択すると、modelsに置くことができるjson形式でファイルが出力されます。
このまま使うとパーティクルが設定されていないので、壊すときに紫と黒のパーティクルが出てしまいます。なので、jsonファイルの中のtexturesの項目にパーティクルを追加してmodelsの中に入れます。
~~ "textures": { "green_stained_glass": "minecraft:block/green_stained_glass", "redstone_block": "minecraft:block/redstone_block", "blue_stained_glass": "minecraft:block/blue_stained_glass", "particle":"minecraft:block/red_stained_glass" }, ~~
続いて、javaでブロッククラスの設定をしていきます。
public class ExampleBlock extends ModBlock { private static final VoxelShape SHAPE1 = Block.makeCuboidShape(3.0D, 0.0D, 3.0D, 13.0D, 1.0D, 13.0D); private static final VoxelShape SHAPE2 = Block.makeCuboidShape(3.0D, 5.0D, 4.0D, 13.0D, 6.0D, 13.0D); private static final VoxelShape SHAPE3 = Block.makeCuboidShape(7.0D, 0.0D, 7.0D, 9.0D, 8.0D, 9.0D); public ExampleBlock(Properties properties, String name, ItemGroup group) { super(properties, name, group); } @Override public VoxelShape getShape(BlockState state, IBlockReader worldIn, BlockPos pos, ISelectionContext context) { return VoxelShapes.or(VoxelShapes.or(SHAPE1, SHAPE2),SHAPE3); } @Override public VoxelShape getCollisionShape(BlockState state, IBlockReader worldIn, BlockPos pos, ISelectionContext context) { return VoxelShapes.empty(); } }
ここも1.12以前と変わっていて、以前はこのようなカスタムモデルのブロックに対しては
isOpaqueCube() や isFullCube() をfalseに設定していました。
しかし、1.13以降ではこれらのメソッドは削除され、代わりにget~Shape()が追加されています。
VoxelShapeは先程のModelCreatorで使っていたような16×16×16の3次元空間に存在する直方体を表します。Block.makeCuboidShape(x1, y1, z1, x2, y2, z2)で定義できて、引数のx1~z2はそれぞれ16×16×16の三次元空間における直方体の
x1:始点x座標
y1:始点y座標
z1:始点z座標
x2:終点x座標
y2:終点y座標
z2:終点z座標
を表します。
これらの直方体は
VoxelShapes.or(Shape1, Shape2);
で組み合わせることができます。
またカーソルの範囲とあたり判定の範囲は個別に設定することもできて、
getCollisionShapeを設定するとgetShapeで設定した判定とは別に当たり判定用のshapeを設定できます。今回は、当たり判定を無くすように設定しています。
shapeに関するより詳しいことは、以下のサイトに書いてありました。
Minecraft Modding: Block Shapes (VoxelShapes) [1.14.4+]
以上を設定して起動すると、画像のようになります。
カスタムモデルが反映されていて、当たり判定はなくなっています。
次回は、ストラクチャーブロックで保存したnbtを使った構造物の生成について書こうと思います。