[Java] [Java]Let’s create a Minecraft Mod 1.14.4 [9. Add and generate trees]

12 minute read

(This article is one of a series of commentary articles)

First article: Introduction Previous article: 8. Addition and production of ore Next article: 99. Mod Output

Add tree

Previously learned about adding ore. Next, let’s add a tree and let the world generate it. It’s getting complicated, so let’s do our best together.

Add raw wood

First, add the raw wood blocks that make up the mind. 2. Add block will be added as a block, but there are some differences, so let’s take a look at them in order.

BlockList.java


//...
public class BlockList {
    public static Block ExampleLog = new LogBlock(
            MaterialColor.CYAN,
            Block.Properties.create(Material.WOOD, MaterialColor.BLUE)
                    .hardnessAndResistance(2.0F)
                    .sound(SoundType.WOOD)
                    .lightValue(6))
            .setRegistryName(new ResourceLocation(ExampleMod.MOD_ID, "example_log"));

    @SubscribeEvent
    public static void registerBlocks(RegistryEvent.Register<Block> event) {
        event.getRegistry().registerAll(
                ExampleLog
        );
    }

    @SubscribeEvent
    public static void registerBlockItems(RegistryEvent.Register<Item> event) {
        event.getRegistry().registerAll(
                new BlockItem(ExampleLog, new Item.Properties().group(ExampleItemGroup.DEFAULT))
                        .setRegistryName(new ResourceLocation(ExampleMod.MOD_ID, "example_log"))
        );
    }
}

It’s basically the same, but it is made with the LogBlock class, not just the Block class. This is a subclass of the Block class, RotatedPillarBlock. RotatedPillarBlock is an addition to Block related to rotation. And in LogBlock, it seems that things related to display on the map are added. The argument of the constructor is increased one by one, and pass the color to display on the map with the MaterialColor class. The argument is also increased in Block.Properties.create() and the color is passed, but this is the color on the map at the time of non-vertical installation, and the previous one seems to be the color at vertical installation. Let’s choose these appropriately. ___ I will set the resources as in the example, but since the texture of the log block differs depending on the surface, this setting is shown below. These are based on the minecraft log blocks, so this is fine unless you do something special.

\src\main\resources
   ├ assets
   │ └ example_mod
   │ ├ blockstates
   │ │ └ example_log.json
   │ ├ lang
   │ │ └ en_us.json
   │ │ └ ja_jp.json
   │ ├ models
   │ │ ├ block
   │ │ │ └ example_log.json
   │ │ └ item
   │ │ └ example_log.json
   │ └ textures
   │ ├ blocks
   │ │ ├ example_log.png
   │ │ └ example_log_top.png
   │ └ items
   └ data
      └ example_mod
         └ loot_tables
            └ blocks
               └ example_log.json
{
  "variants": {
    "axis=y": {"model": "example_mod:block/example_log" },
    "axis=z": {"model": "example_mod:block/example_log", "x": 90 },
    "axis=x": {"model": "example_mod:block/example_log", "x": 90, "y": 90}
  }
}

By writing in this way, the member variable axis (strictly, RotatedPillarBlock) of the class LogBlock (strictly speaking, RotatedPillarBlock) will have conditional branching depending on either x, y, or z. I am. (Official documenthassomethinglikethat.) The description such as "x":90"y":90 is the rotation angle. In short, it is rotated so that the display is a collapsed log based on the vertically installed state.

{
  "parent": "block/cube_column",
  "textures": {
    "end": "example_mod:blocks/example_log_top",
    "side": "example_mod:blocks/example_log"
  }
}

Specify block/cube_column for parent. This allows you to apply textures that are cubic and have different top and bottom faces. Specify the path to each texture file.

{
  "parent": "example_mod:block/example_log"
}

This should just inherit the model file of block as before.

en_us.jp


{
  "block.example_mod.example_log": "Example Log"
}

ja_jp.json


{
  "block.example_mod.example_log": "Example log"
}

The language files are the same as before.

{
  "type": "minecraft:block",
  "pools": [
    {
      "rolls": 1,
      "entries": [
        {
          "type": "minecraft:item",
          "name": "example_mod:example_log"
        }
      ]
    }
  ]
}

As usual.

Capture.PNG I have added a block with a direction like this. ___ **There is one more thing to do with the log block. ** The block addition itself is completed up to this point, but we will need it later when constructing the tree, so let’s do it here.

Add the log block you just added to the tag of minecraft:logs. 6. Adding recipes also touched lightly, but a “tag” is a group of objects that have common factors. Minecraft has a tag that summarizes the equivalent of raw wood, and it is used when processing related to wood. Therefore, if you also include this log block in this tag, you will not have to uselessly implement it.

\src\main\resources
   ├ assets
   └ data
      ├ example_mod
      └ minecraft
         └ tags
            └ blocks
               └ logs.json

**In your project folder, create a \src\main\resources\data\minecraft\tags\blocks folder and put logs.json there. Make sure this name is the same.

logs.json


{
  "replace": false,
  "values": [
    "example_mod:example_log"
  ]
}

By adding false to replace, the description in this file will be integrated into minecraft:logs with the same name. Specify blocks in values.

Capture.PNGWhen you put the cursor on the block in debug mode, it is completed if #minecraft:logs is displayed like the part underlined in red.

Adding leaves

Next we will add the leaf blocks that make up the tree. It will be repeated, but let’s take a look at the base in order with reference to 2. Adding blocks.

BlockList.java


//...
public class BlockList {
    public static Block ExampleLeaves = new LeavesBlock(
            Block.Properties.create(Material.LEAVES)
                    .hardnessAndResistance(0.2F)
                    .tickRandomly()
                    .sound(SoundType.PLANT)
                    .lightValue(8))
            .setRegistryName(new ResourceLocation(ExampleMod.MOD_ID, "example_leaves"));

    @SubscribeEvent
    public static void registerBlocks(RegistryEvent.Register<Block> event) {
        event.getRegistry().registerAll(
                ExampleLeaves
        );
    }

    @SubscribeEvent
    public static void registerBlockItems(RegistryEvent.Register<Item> event) {
        event.getRegistry().registerAll(
                new BlockItem(ExampleLeaves, new Item.Properties().group(ExampleItemGroup.DEFAULT))
                        .setRegistryName(new ResourceLocation(ExampleMod.MOD_ID, "example_leaves"))
        );
    }
}

Leaf blocks are made with Leaves Block. Others are as usual. ___ Set up resources.

\src\main\resources
   ├ assets
   │ └ example_mod
   │ ├ blockstates
   │ │ └ example_leaves.json
   │ ├ lang
   │ │ └ en_us.json
   │ │ └ ja_jp.json
   │ ├ models
   │ │ ├ block
   │ │ │ └ example_leaves.json
   │ │ └ item
   │ │ └ example_leaves.json
   │ └ textures
   │ ├ blocks
   │ │ └ example_leaves.png
   │ └ items
   └ data
      └ example_mod
         └ loot_tables
            └ blocks
               └ example_leaves.json
{
  "variants": {
    "": {"model": "example_mod:block/example_leaves"}
  }
}
{
  "parent": "block/leaves",
  "textures": {
    "all": "example_mod:blocks/example_leaves"
  }
}

Specify block/leaves for parent. A semi-transparent texture can be applied (depending on the graphic settings).

{
  "parent": "example_mod:block/example_leaves"
}

en_us.jp


{
  "block.example_mod.example_leaves": "Example Leaves"
}

ja_jp.json


{
  "block.example_mod.example_leaves": "example leaves"
}

These are as usual.

{
  "type": "minecraft:block",
  "pools": [
    {
      "rolls": 1,
      "entries": [
        {
          "type": "minecraft:alternatives",
          "children": [
            {
              "type": "minecraft:item",
              "conditions": [
                {
                  "condition": "minecraft:alternative",
                  "terms": [
                    {
                      "condition": "minecraft:match_tool",
                      "predicate": {
                        "item": "minecraft:shears"
                      }
                    },
                    {
                      "condition": "minecraft:match_tool",
                      "predicate": {
                        "enchantments": [
                          {
                            "enchantment": "minecraft:silk_touch",
                            "levels": {
                              "min": 1
                            }
                          }
                        ]
                      }
                    }
                  ]
                }
              ],
              "name": "example_mod:example_leaves"
            },
            {
              "type": "minecraft:item",
              "conditions": [
                {
                  "condition": "minecraft:survives_explosion"
                },
                {
                  "condition": "minecraft:table_bonus",
                  "enchantment": "minecraft:fortune",
                  "chances": [
                    0.05,
                    0.0625,
                    0.083333336,
                    0.1
                  ]
                }
              ],
              "name": "example_mod:example_sapling"
            }
          ]
        }
      ]
    }
  ]
}

The loot_table file has been quite different so far. A leaf block (though it’s been cut off until now) has multiple drops and is also dependent on enchantments and tools, which makes it look like this: For details, refer to Reference Page, but I will briefly explain. This is a “drop from a block, returns one, one of children. There are two children, and first returns a leaf block if it is destroyed with a scissors or Silk Touch I tool. Next, it returns seedlings with random probability (probability increase with good luck encha) in other cases.” Although it appears first here and the order is reversed, seedlings will be added in the next section. This is based on the Ork leaf of minecraft. Look at each one and write as you like. ___ Finally, set the MaterialColor. This has the effect of changing the color of the leaves and grass depending on the biome. This can be ignored if it is troublesome. If you ignore it, the same color will be displayed in all biomes, so prepare a texture with that color.

BlockList.java


//...
public class BlockList {
    //...
    @SubscribeEvent
    public static void registerBlockColors(ColorHandlerEvent.Block event) {event.getBlockColors().register((p_210229_0_, p_210229_1_, p_210229_2_, p_210229_3_) -> {
            return p_210229_1_ != null && p_210229_2_ != null ?BiomeColors.getFoliageColor(p_210229_1_, p_210229_2_) :FoliageColors.getDefault();
        }, ExampleLeaves);
    }

    @SubscribeEvent
    public static void registerBlockItemColors(ColorHandlerEvent.Item event) {
        event.getItemColors().register((p_210235_1_, p_210235_2_) -> {
            BlockState blockstate = ((BlockItem)p_210235_1_.getItem()).getBlock().getDefaultState();
            return event.getBlockColors().getColor(blockstate, (IEnviromentBlockReader)null, (BlockPos)null, p_210235_2_);
        }, ExampleLeaves);
    }
}

I will also register this at the same time with BlockList.java that is declared and registered. There is a ColorHandlerEvent, so it can be reflected by doing register() using this. Do this for each block and item. Variables etc. are a list of unclear characters, but you can change this as it is just pulled from the obfuscated minecraft code. For the block, the color corresponding to the biome in which the block exists is acquired and registered by BiomeColors.getFoliageColor(). For items, default colors are retrieved and registered. In both cases, pass the color (obtained by the lambda expression) in the first argument of register() and the object to set the color in the second argument. This area doesn’t have to be deeply understood unless you try to do something difficult.

For actual colors, see Reference Page. Basically, it seems that a grayscale leaf block texture is prepared and the colors are posted, but when I prepared a file with a tint this time, the actual block also became that tint, so probably inside I wonder if they are adding up.

Add TreeFeature class and Tree class

These are difficult to explain the difference in words, but both are classes that manage trees. The Tree class is a class that manages the tree itself, and is required in connection with seedlings that will be added later. On the other hand, the TreeFeature class manages only things related to tree generation, and you can also get the corresponding TreeFeature from the Tree class.

\src\main\java\jp\koteko\example_mod\
   ├ blocks
   │ └ trees
   │ └ ExampleTree.java
   ├ items
   ├ lists
   ├ world
   │ └ features
   │ └ ExampleTreeFeature.java
   └ ExampleMod.java

(I plan to refer to the file layout in various ways, but if you are worried, it is relatively appropriate.)

ExampleTreeFeature.java


package jp.koteko.example_mod.world.features;

import jp.koteko.example_mod.lists.BlockList;
import net.minecraft.world.gen.feature.NoFeatureConfig;
import net.minecraft.world.gen.feature.TreeFeature;
import net.minecraftforge.common.IPlantable;

public class ExampleTreeFeature extends TreeFeature {
    public ExampleTreeFeature() {
        super(NoFeatureConfig::deserialize, false, 4, BlockList.ExampleLog.getDefaultState(), BlockList.ExampleLeaves.getDefaultState(), false);
        setSapling((IPlantable) BlockList.ExampleSapling);
    }
}

Create an ExampleTreeFeature class by inheriting the TreeFeature class. The arguments passed to the inheritance source constructor are as follows. The first argument is an instance of NoFeatureConfig and is probably not used very effectively. The second argument is a boolean value that decides whether or not to send some kind of notification, which was false in other trees, so it was imitated. I will add it if I understand something. The third argument is an int value that determines the minimum height of the tree. The fourth argument is the BlockState of the main block. The fifth argument is the BlockState of the leaf block. The sixth argument is a boolean value that determines whether to grow the ivy on the tree. Also, setSapling() sets the seedling and passes the seedling instance here. Seedlings will be implemented in the next section.

ExampleTree.java


package jp.koteko.example_mod.blocks.trees;

import jp.koteko.example_mod.world.features.ExampleTreeFeature;
import net.minecraft.block.trees.Tree;
import net.minecraft.world.gen.feature.AbstractTreeFeature;
import net.minecraft.world.gen.feature.NoFeatureConfig;

import javax.annotation.Nullable;
import java.util.Random;

public class ExampleTree extends Tree {
    @Nullable
    protected AbstractTreeFeature<NoFeatureConfig> getTreeFeature(Random random) {
        return new ExampleTreeFeature();
    }
}

Create an ExampleTree class by extending the abstract class Tree. Define a method called getTreeFeature. Let’s return an instance of the ExampleTreeFeature we defined earlier. I don’t use a random number as an argument, but it is used when an oak tree is divided into a normal species and a giant species with a probability.

Adding seedlings

Next, add seedlings. First, prepare a seedling class.

\src\main\java\jp\koteko\example_mod\
   ├ blocks
   │ ├ trees
   │ └ ExampleSapling.java
   ├ items
   ├ lists
   ├ world
   └ ExampleMod.java

BlockExampleSapling.java


package jp.koteko.example_mod.blocks;

import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.BushBlock;
import net.minecraft.block.IGrowable;
import net.minecraft.block.trees.Tree;
import net.minecraft.state.IntegerProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;

import java.util.Random;

public class BlockExampleSapling extends BushBlock implements IGrowable {
    public static final IntegerProperty STAGE = BlockStateProperties.STAGE_0_1;
    protected static final VoxelShape SHAPE = Block.makeCuboidShape(2.0D, 0.0D, 2.0D, 14.0D, 12.0D, 14.0D);
    private final Tree tree;

    public BlockExampleSapling(Tree p_i48337_1_, Block.Properties properties) {
        super(properties);
        this.tree = p_i48337_1_;
        this.setDefaultState(this.stateContainer.getBaseState().with(STAGE, Integer.valueOf(0)));
    }public VoxelShape getShape(BlockState state, IBlockReader worldIn, BlockPos pos, ISelectionContext context) {
        return SHAPE;
    }

    public void tick(BlockState state, World worldIn, BlockPos pos, Random random) {
        super.tick(state, worldIn, pos, random);
        if (!worldIn.isAreaLoaded(pos, 1)) return; // Forge: prevent loading unloaded chunks when checking neighbor's light
        if (worldIn.getLight(pos.up()) >= 9 && random.nextInt(7) == 0) {
            this.grow(worldIn, pos, state, random);
        }

    }

    public void grow(IWorld worldIn, BlockPos pos, BlockState state, Random rand) {
        if (state.get(STAGE) == 0) {
            worldIn.setBlockState(pos, state.cycle(STAGE), 4);
        } else {
            if (!net.minecraftforge.event.ForgeEventFactory.saplingGrowTree(worldIn, rand, pos)) return;
            this.tree.spawn(worldIn, pos, state, rand);
        }

    }

    /**
     * Whether this IGrowable can grow
     */
    public boolean canGrow(IBlockReader worldIn, BlockPos pos, BlockState state, boolean isClient) {
        return true;
    }

    public boolean canUseBonemeal(World worldIn, Random rand, BlockPos pos, BlockState state) {
        return (double)worldIn.rand.nextFloat() < 0.45D;
    }

    public void grow(World worldIn, Random rand, BlockPos pos, BlockState state) {
        this.grow(worldIn, pos, state, rand);
    }

    protected void fillStateContainer(StateContainer.Builder<Block, BlockState> builder) {
        builder.add(STAGE);
    }
}

このコードについてですが、(ほぼ)すべてminecraftの苗のコードと同じです。じゃあ何故わざわざ作ったか、というと、minecraftのSaplingクラスのコンストラクタがprotectedで、modのコード内から直接使えなかったためです。minecraft側がこれをどう扱っているかは理解する時間が不足していたためここでは考えないことにしますが、おそらくそちらに合わせた形でのもう少しうまい実装の仕方があると思います。 パッケージ名、クラス名、コンストラクタの3つのみ変更して使います。

BlockList.java


//...
public class BlockList {
    public static Block ExampleSapling = new BlockExampleSapling(
            new ExampleTree(),
            Block.Properties.create(Material.PLANTS)
                    .doesNotBlockMovement()
                    .tickRandomly()
                    .hardnessAndResistance(0.0F)
                    .sound(SoundType.PLANT))
            .setRegistryName(new ResourceLocation(ExampleMod.MOD_ID, "example_sapling"));

    @SubscribeEvent
    public static void registerBlocks(RegistryEvent.Register<Block> event) {
        event.getRegistry().registerAll(
                ExampleSapling
        );

    }

    @SubscribeEvent
    public static void registerBlockItems(RegistryEvent.Register<Item> event) {
        event.getRegistry().registerAll(
                new BlockItem(ExampleSapling, new Item.Properties().group(ExampleItemGroup.DEFAULT))
                        .setRegistryName(new ResourceLocation(ExampleMod.MOD_ID, "example_sapling"))
        );
    }
}

先程用意した苗のクラスでブロックを追加します。 第一引数は対応する木を渡すので、先ほど作ったExampleTreeクラスのインスタンスを渡します。

いつも通りresourcesの設定をしていきます。特に変わらないものは解説を省略します。

\src\main\resources
   ├ assets
   │  └ example_mod
   │     ├ blockstates
   │     │  └ example_sapling.json
   │     ├ lang
   │     │  └ en_us.json
   │     │  └ ja_jp.json
   │     ├ models
   │     │  ├ block
   │     │  │  └ example_sapling.json
   │     │  └ item
   │     │     └ example_sapling.json
   │     └ textures
   │        ├ blocks
   │        │  └ example_sapling.png
   │        └ items
   └ data
      └ example_mod
         └ loot_tables
            └ blocks
               └ example_sapling.json
{
  "variants": {
    "": { "model": "example_mod:block/example_sapling" }
  }
}
{
    "parent": "block/cross",
    "textures": {
        "cross": "example_mod:blocks/example_sapling"
    }
}

parentにはblock/crossを指定します。これによって平面が交差したような形でテクスチャの適用ができます(実際に形を見れば言ってる意味が分かると思います)。

{
    "parent": "item/generated",
    "textures": {
        "layer0": "example_mod:blocks/example_sapling"
    }
}

blockのモデルファイルを引き継ぐのではなく、item/generatedで指定します。

en_us.jp


{
  "block.example_mod.example_sapling": "Example Sapling"
}

ja_jp.json


{
  "block.example_mod.example_sapling": "例の苗"
}
{
  "type": "minecraft:block",
  "pools": [
    {
      "rolls": 1,
      "entries": [
        {
          "type": "minecraft:item",
          "name": "example_mod:example_sapling"
        }
      ]
    }
  ]
}

ゲームを起動して確認してみましょう。 キャプチャ.PNG 苗が追加され、骨粉で木が育つことが確認できると思います。 キャプチャ.PNG また、木の幹を除去すると、葉ブロックが自然消滅をはじめ、消える際に確率で苗をドロップすることが確認できると思います。

木の生成

ここまで来たらあと少しです。ここまでで実装した木をワールド生成時に自動で生成されるようにしましょう。 これは8. 鉱石の追加と生成とほぼ同じことをするのでそちらも参考にしてください。

   ├ blocks
   ├ items
   ├ lists
   ├ world
   │   ├ WorldGenOres.java
   │   └ WorldGenTrees.java
   └ ExampleMod.java

WorldGenTrees.javaを配置します。

WorldGenOres.java


package jp.koteko.example_mod.world;

import jp.koteko.example_mod.world.features.ExampleTreeFeature;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.gen.GenerationStage;
import net.minecraft.world.gen.feature.Feature;
import net.minecraft.world.gen.feature.IFeatureConfig;
import net.minecraft.world.gen.feature.NoFeatureConfig;
import net.minecraft.world.gen.placement.AtSurfaceWithExtraConfig;
import net.minecraft.world.gen.placement.Placement;
import net.minecraftforge.registries.ForgeRegistries;

public class WorldGenTrees {
    public static void setup() {
        addTreeToOverworld(new ExampleTreeFeature());
    }

    private static void addTreeToOverworld(Feature<NoFeatureConfig> featureIn) {
        for(Biome biome : ForgeRegistries.BIOMES) {
            if (!biome.getCategory().equals(Biome.Category.NETHER) && !biome.getCategory().equals(Biome.Category.THEEND)) {
                biome.addFeature(
                        GenerationStage.Decoration.VEGETAL_DECORATION,
                        Biome.createDecoratedFeature(
                                featureIn,
                                IFeatureConfig.NO_FEATURE_CONFIG,
                                Placement.COUNT_EXTRA_HEIGHTMAP,
                                new AtSurfaceWithExtraConfig(2, 0.1F, 1)
                        )
                );
            }
        }
    }
}

AtSurfaceWithExtraConfigの引数は、1チャンク当たりの抽選回数、追加抽選を行う可能性、追加抽選を行う場合の追加回数です。この例の場合、「1チャンク当たり2か所抽選を行い、10%の確率でさらに1回抽選を行う」です。

最後に、今定義したWorldGenOres.setup()をメインファイル内のsetup中で呼びます。

ExampleMod.java


//...
public class ExampleMod
{
    //...
    private void setup(final FMLCommonSetupEvent event)
    {
        WorldGenTrees.setup();
    }
    //...
}

ゲームを起動して新たにワールドを生成します。 2020-08-09_17.46.30.png なんとなく光らせておいたので夜だと一層目立ちます。

木の追加と生成ができました!

参考

1.14.3 Tags help - Modder Support - Forge Forums バイオーム - Minecraft Wiki ルートテーブル - Minecraft Wiki 地図アイテムフォーマット - Minecraft Wiki

次の記事

99. Modの出力