6日目 Mobの追加2 (スポーン関連、loot_tableなど)
前回、とりあえずMobだけ追加しましたが、スポーンエッグや自然スポーンを登録していなかったので、それらを行なっていこうと思います。
また、Mobが倒された時にドロップするアイテムの設定などはすべてルートテーブルで行なうので、そちらも簡単に設定していこうと思います。
少し長くなったので、簡単に目次をつけておきます。
①スポーンエッグの登録
②自然スポーンの設定
③ルートテーブルの設定
④子供のモデルの設定
おまけ:コンテナーをもつMob
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
①スポーンエッグの登録
まず、スポーンエッグの登録から行ないます。
@Mod.EventBusSubscriber(bus=Mod.EventBusSubscriber.Bus.MOD) public class ModItems { public static final Item TEST_ITEM = new Item(new Item.Properties().group(TestItemGroups.TEST_GROUP)); public static final Item EXAMPLE_ITEM = new ExampleItem(new Item.Properties().group(TestItemGroups.EXAMPLE_GROUP)); public static final Item TEST_EGG = new SpawnEggItem(ModEntities.TEST_ENTITY, 0x25fff6, 0x251184, new Item.Properties().group(TestItemGroups.TEST_GROUP)); @SubscribeEvent public static void registerItems(final RegistryEvent.Register<Item> event) { TEST_ITEM.setRegistryName(TestMod.MODID,"test_item"); EXAMPLE_ITEM.setRegistryName(TestMod.MODID,"example_item"); TEST_EGG.setRegistryName(TestMod.MODID, "test_egg"); event.getRegistry().registerAll(TEST_ITEM,EXAMPLE_ITEM, TEST_EGG); } }
上のコードの7行目でスポーンエッグのアイテムを宣言しています。
new SpawnEggItemで宣言し、引数は(EntityType, color1, color2, ItemGroup)で、colorはそれぞれスポーンエッグの卵のベース色と斑点の色です。
登録は、その他のアイテムと同様にsetRegistryNameで名前を登録してから、registerAllに渡すことで登録できます。
登録しただけだと、アイテムのテクスチャが表示されないので、jsonでモデルを設定します。
resources/assets/MODID/models/item
に登録名.jsonのファイルを作成します。
今回の例では、test_egg.jsonです。
{ "parent": "item/template_spawn_egg" }
内容はこれだけです。
これでjava内で設定した配色のスポーンエッグのテクスチャが表示されます。
また、登録名は他のアイテムと同様にlangフォルダのjsonファイルで設定できます。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
②自然スポーンの設定
続いて自然スポーンの設定を行なっていきます。
Modのメインのファイルのsetupメソッド (preInit)で登録します。
直接メインファイルに書くと、メインファイルが複雑になるので、今回はWorldGenManagerというクラスを作成し、その中にaddSpawnというメソッドを作成しました。
public class WorldGenManager { public void addSpawn() { ForgeRegistries.BIOMES.forEach(biome -> { biome.getSpawns(ModEntities.TEST_ENTITY.getClassification()) .add(new SpawnListEntry(ModEntities.TEST_ENTITY, 10, 2, 8)); }); } }
自然スポーンを登録するには、BiomeクラスのgetSpawnを呼び出して、EntityのClassificationを渡してリストを取得します。
そのリストに新たなエントリーをaddすることで追加できます。
SpawnListEntryの引数は(登録したいEntityType, スポーンの重み, 集団スポーンの最小値, 集団スポーンの最大値) です。
重みに関しては具体的な値は分かりませんが、大きい程よりスポーンしやすくなり10でも結構な確率でスポーンしているように感じました。
このaddSpawnメソッドをModのメインクラスで呼び出します。
少し省略していますがコードを乗せます。
public class TestMod { ~~ public static final WorldGenManager WORLD_GEN = new WorldGenManager(); ~~ private void setup(final FMLCommonSetupEvent event) { LOGGER.info("Setup method registered."); WORLD_GEN.addSpawn(); } ~~ }
うまく登録されると画像のように自然にスポーンするようになります。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
③ルートテーブルの設定
モンスターのドロップアイテムは基本的にルートテーブルで設定します。
これも一から自分で書くのは大変なので、バニラのjsonファイルを参考にしながら切り貼りするのが良いでしょう。
MC Assets - Browser for Minecraft Asset Files
assets/data/MODID/loot_tables/entitiesに
登録名.jsonのjsonファイルを作成します。
今回の例ではtest_entity.jsonになります。
{ "type": "minecraft:entity", "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "functions": [ { "function": "minecraft:set_count", "count": { "min": 1.0, "max": 3.0, "type": "minecraft:uniform" } }, { "function": "minecraft:looting_enchant", "count": { "min": 0.0, "max": 1.0 } } ], "name": "minecraft:slime_ball" } ] } ] }
今回は、スライムのルートテーブルをコピーしました。2種類のアイテムをドロップするようなMobを作るときは牛やニワトリなんかを参考にすれば良いのではないでしょうか。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
④子供のモデルの設定
AnimalEntityを継承したEntityは繁殖で増やすことが出来ます。その場合、Modelクラスで適切に設定することで、バニラのように生まれてくる子供のサイズを小策することができます。
public class TestModel extends EntityModel<TestEntity> { public ModelRenderer body; public ModelRenderer head; public ModelRenderer arm1; public ModelRenderer arm2; //func_228301_a_ is addBox public TestModel() { ~~~ //This method is render @Override public void func_225598_a_(MatrixStack p_225598_1_, IVertexBuilder p_225598_2_, int p_225598_3_, int p_225598_4_, float p_225598_5_, float p_225598_6_, float p_225598_7_, float p_225598_8_) { if (this.isChild) { p_225598_1_.func_227860_a_(); p_225598_1_.func_227862_a_(0.56666666F, 0.56666666F, 0.56666666F); p_225598_1_.func_227861_a_(0.0D, 1.375D, 0.125D); ImmutableList.of(body, head, arm1, arm2).forEach((p_228292_8_) -> { p_228292_8_.func_228309_a_(p_225598_1_, p_225598_2_, p_225598_3_, p_225598_4_, p_225598_5_, p_225598_6_, p_225598_7_, p_225598_8_); }); p_225598_1_.func_227865_b_(); } else { p_225598_1_.func_227860_a_(); p_225598_1_.func_227862_a_(1.0F, 1.0F, 1.0F); p_225598_1_.func_227861_a_(0.0D, 0.0D, 0.0D); ImmutableList.of(body, head, arm1, arm2).forEach((p_228290_8_) -> { p_228290_8_.func_228309_a_(p_225598_1_, p_225598_2_, p_225598_3_, p_225598_4_, p_225598_5_, p_225598_6_, p_225598_7_, p_225598_8_); }); p_225598_1_.func_227865_b_(); } } ~~~ }
他の部分は省略してしまいましたが、 func_225598_a_ (renderに相当) 内で設定します。
p_225598_1_.func_227862_a_(x, y, z)はそれぞれx,yおよびz方向のスケールを設定します。今回は子供のサイズは0.566倍に設定しました。
次の行のp_225598_1_.func_227861_a_(x, y, z); は各軸方向のオフセットを補正します。スケールを変更する際に、おそらくmobの中心を起点に拡大または縮小されるので、そのまま表示すると浮いたり地面に埋まったりします。なので、オフセットを変更して適切な位置にMobが表示されるようにします。y座標を最も変更することになるとおもいますが、yは正の値を設定するとオフセットが下にずれます。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
おまけ
コンテナーをもつMob
機能として、コンテナー (チェストのような) を持つEntityを追加したので載せておきます。
Entityのクラスに加えて、Containerクラス、Screenクラスを作成します。
ExampleEntity.class
public class ExampleEntity extends AnimalEntity implements INamedContainerProvider { private ItemStackHandler handler = new ItemStackHandler(9); private int count = 0; private boolean obtained_flag=false; protected ExampleEntity(EntityType<? extends AnimalEntity> type, World p_i48564_2_) { super(type, p_i48564_2_); } @Override public AgeableEntity createChild(AgeableEntity ageable) { return null; } @Override public void writeAdditional(CompoundNBT compound) { super.writeAdditional(compound); compound.putInt("count", count); compound.putBoolean("flag", obtained_flag); ListNBT listnbt = new ListNBT(); for(int i = 0; i < handler.getSlots(); ++i) { ItemStack itemstack = handler.getStackInSlot(i); if (!itemstack.isEmpty()) { CompoundNBT compoundnbt = new CompoundNBT(); compoundnbt.putByte("Slot", (byte)i); itemstack.write(compoundnbt); listnbt.add(compoundnbt); } } compound.put("Items", listnbt); } @Override public void readAdditional(CompoundNBT compound) { super.readAdditional(compound); ListNBT listnbt = compound.getList("Items", 10); for(int i = 0; i < listnbt.size(); ++i) { CompoundNBT compoundnbt = listnbt.getCompound(i); int j = compoundnbt.getByte("Slot") & 255; handler.setStackInSlot(j, ItemStack.read(compoundnbt)); } obtained_flag = compound.getBoolean("flag"); count = compound.getInt("count"); } @Override public boolean processInteract(PlayerEntity player, Hand hand) { if(!obtained_flag && !world.isRemote) { handler.setStackInSlot(0, new ItemStack(Items.EMERALD, 20)); obtained_flag = true; } if(!world.isRemote()) { count++; NetworkHooks.openGui((ServerPlayerEntity) player, (INamedContainerProvider)this,buf -> buf.writeInt(getEntityId())); return true; } return false; } @Override public Container createMenu(int p_createMenu_1_, PlayerInventory p_createMenu_2_, PlayerEntity p_createMenu_3_) { return new TestContainer(p_createMenu_1_, p_createMenu_2_, getEntityId()); } @SuppressWarnings("unchecked") @Nonnull @Override public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) { if (cap == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) { return LazyOptional.of(() -> (T)handler); } return super.getCapability(cap, side); } }
インベントリの内容はnbtデータで管理しますが、Entityは既にたくさんのnbtデータ (体力、座標など) を持っているので、writeやreadをオーバーライドすることが出来ません。
なので、wrtie/read additionalで追加のnbtデータを管理します。
続いてコンテナークラスを作成します。
TestContainer.class
public class TestContainer extends Container { public static ContainerType<TestContainer> TESTBLOCK_CONTAINER = IForgeContainerType.create(TestContainer::new); public TestContainer(int windowId, PlayerInventory playerInventory, PacketBuffer extraData) { this(windowId, playerInventory, extraData.readInt()); } public TestContainer(int windowId, PlayerInventory playerInventory, int id) { super(TESTBLOCK_CONTAINER, windowId); ExampleEntity entity = (ExampleEntity) playerInventory.player.world.getEntityByID(id); //Obtained entity's itemHandler, and set Slot. entity.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY).ifPresent(h -> { for(int i = 0; i < 3; ++i) { for(int j = 0; j < 3; ++j) { addSlot(new SlotItemHandler(h, j + i * 3, 62 + j * 18, 17 + i * 18)); } } }); //Set player's inventory Slot. for(int k = 0; k < 3; ++k) { for(int i1 = 0; i1 < 9; ++i1) { this.addSlot(new Slot(playerInventory, i1 + k * 9 + 9, 8 + i1 * 18, 84 + k * 18)); } } for(int l = 0; l < 9; ++l) { this.addSlot(new Slot(playerInventory, l, 8 + l * 18, 142)); } } @Override public boolean canInteractWith(PlayerEntity playerIn) { return true; } @Override public ItemStack transferStackInSlot(PlayerEntity playerIn, int index) { ItemStack itemstack = ItemStack.EMPTY; Slot slot = this.inventorySlots.get(index); if (slot != null && slot.getHasStack()) { ItemStack itemstack1 = slot.getStack(); itemstack = itemstack1.copy(); if (index < 9) { if (!this.mergeItemStack(itemstack1, 9, 45, true)) { return ItemStack.EMPTY; } } else if (!this.mergeItemStack(itemstack1, 0, 9, false)) { return ItemStack.EMPTY; } if (itemstack1.isEmpty()) { slot.putStack(ItemStack.EMPTY); } else { slot.onSlotChanged(); } if (itemstack1.getCount() == itemstack.getCount()) { return ItemStack.EMPTY; } slot.onTake(playerIn, itemstack1); } return itemstack; } }
このクラスでは、iTemHandelerを受け取り、それぞれにスロットを設定します。GUI上での二次元座標を与えてインベントリの表示位置を決定します。
そして、どこのクラスでもいいので、 @SubscribeEventで登録を行ないます。
@SubscribeEvent public static void onContainerRegistry(final RegistryEvent.Register<ContainerType<?>> event) { TestContainer.TESTBLOCK_CONTAINER.setRegistryName("test_container"); event.getRegistry().register(TestContainer.TESTBLOCK_CONTAINER); }
最後に、Screenクラスを作成します。
public class TestScreen extends ContainerScreen<TestContainer> { private ResourceLocation GUI = new ResourceLocation(TestMod.MODID, "textures/gui/dispenser.png"); public TestScreen(TestContainer container, PlayerInventory inv, ITextComponent name) { super(container, inv, name); } @Override public void render(int mouseX, int mouseY, float partialTicks) { this.renderBackground(); super.render(mouseX, mouseY, partialTicks); this.renderHoveredToolTip(mouseX, mouseY); } @Override protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY) { } @Override protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY) { GlStateManager.func_227637_a_(1.0f, 1.0f, 1.0f, 1.0f);; this.minecraft.getTextureManager().bindTexture(GUI); int relX = (this.width - this.xSize) / 2; int relY = (this.height - this.ySize) / 2; this.blit(relX, relY, 0, 0, this.xSize, this.ySize); } }
このクラスの主な役割は、guiのテクスチャー設定です。
今回はディスペンサーのGUIに合わせました。
スクリーンはクライエント側にしか必要ないので、MODのメインクラスのクライエント処理で登録を行ないます。
private void clientRegistries(final FMLClientSetupEvent event) { ScreenManager.registerFactory(TestContainer.TESTBLOCK_CONTAINER, TestScreen::new); }
以上を登録してEntityに右クリックを行なうとGUIが開いてインベントリを扱えます。
長くなりましたが、今回は以上です。
次回は元々予定していた、nbtデータを使った構造物の追加を行ないます。