[Java]让我们来创建Minecraft的Mod吧!1.14.4【9.添加和生成木材】

这篇文章是一系列解释性文章中的一篇。

首篇文章:入门篇
前一篇文章:8. 添加和生成矿石
下一篇文章:99. 模组的输出

添加木材

在前一篇文章中,我们学到了如何添加矿石。接下来,让我们尝试添加木材并在世界中生成它们。这变得相当复杂,所以让我们一起努力吧。

新增木材

首先,将原木块添加到构成树木的木块中。2. 参考添加块的方法进行添加,但由于有一些不同之处,所以我们逐步来看。

//...
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"))
        );
    }
}

基本上是相同的,但不只是一个简单的Block类,而是用LogBlock类创建。
这是Block类的子类RotatedPillarBlock的子类。RotatedPillarBlock是在Block基础上添加了旋转功能的类。而在LogBlock类中似乎还添加了关于在地图上的显示的内容。
构造函数的参数增加了一个,第一个参数是使用MaterialColor类来确定地图上的颜色。在Block.Properties.create()上的参数也增加了颜色的值,但这是非垂直安装时地图上的颜色,而之前的参数似乎是垂直安装时的颜色。这些可以随意选择。


按照惯例,我们将继续进行资源的设置。由于原木方块的纹理因面而异,我们在下面给出了这些设置。这些设置是参考了Minecraft的原木方块,所以除非有特殊情况,否则这应该是可以的。

\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 }
  }
}

通过这样的写法,很可能是根据LogBlock(严格来说是RotatedPillarBlock)类的成员变量axis(面的法线方向)的x、y、z三个值进行条件判断。(官方文档中也有类似的说明。)例如”x”:90″y”:90的描述是旋转角度。总而言之,通过将木材旋转来使其以垂直安装状态为基准倒置显示。

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

在parent参数中,指定block/cube_column。这样可以应用区分上下面和侧面纹理的立方体形状。请指定各个纹理文件的路径。

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

只需继续使用之前的 block 模型文件。

{
  "block.example_mod.example_log": "Example Log"
}
{
  "block.example_mod.example_log": "例原木"
}

语言文件跟以前一样。

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

照常进行。

キャプチャ.PNG

在原木方块方面还有一件事情要做。
尽管添加方块本身已经完成,但在以后构建木材的阶段中会需要它,所以我们在这里先进行。

将新添加的原木方块添加到minecraft:logs标签中。
6. 在添加配方时我们稍微提及了”标签”,它实际上指的是将具有共同特征的对象组合在一起的集合。在minecraft中有一个将原木方块等相关物品组合在一起的标签,当进行与木材相关的处理时,我们可以使用该标签。因此,将这个原木方块包含在这个标签中,可以避免不必要的实现。

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

在我的项目文件夹中创建一个\src\main\resources\data\minecraft\tags\blocks文件夹,并将logs.json文件放在那里。请确保文件名和此处描述的一样。

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

通过将replace设置为false,将会整合此文件中的描述内容到名为minecraft:logs的文件中。在values中指定要替换的方块。

キャプチャ.PNG

添加叶子

接下来我们要添加组成树的叶子块。虽然有层叠的部分,但基础是2。请根据添加块的参考,按顺序进行查看。

//...
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"))
        );
    }
}

叶块使用LeavesBlock制作。其他部分按照平常一样。


我們將進行資源的設定。

\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"
  }
}

让父级元素指定block/leaves。(根据图形设置)可以应用半透明纹理。

{
  "parent": "example_mod:block/example_leaves"
}
{
  "block.example_mod.example_leaves": "Example Leaves"
}
{
  "block.example_mod.example_leaves": "例の葉"
}

这些都一如既往。

{
  "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"
            }
          ]
        }
      ]
    }
  ]
}

掠夺表文件与以往有很大不同(虽然我们之前有点偷懒),因为叶块有多个掉落物品,并且还依赖附魔和工具,所以如果要进行正确的实现,就会像这样。
详细的说明请让我让给参考页面,但是我简单地解释一下。
这是“从方块中掉落物品,数量为1个,返回其中的一个children。children有两个,第一是使用剪刀或丝触控I工具破坏叶块时返回的结果。第二是其他情况下根据随机概率(在幸运附魔下概率增加)返回苗。”这样写。尽管顺序有些颠倒了,但我们将在下一部分中添加苗。
这是参考了Minecraft的橡树叶。大家可以根据自己的喜好进行编写。


最后,我们要进行MaterialColor的设置。这个设置可以根据生物群落来改变叶子和草的颜色。如果觉得麻烦的话,可以忽略这一步。忽略的话,所有生物群落将会显示相同的颜色,所以我们要为这个颜色准备好纹理。

//...
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);
    }
}

同时在已经声明和注册的BlockList.java文件中也会将其注册。有一个名为ColorHandlerEvent的方法,通过使用它来注册(register)。这将应用于每个方块和物品。虽然变量等一些不清楚含义的字符序列是困惑的,但这只是从经过混淆的minecraft代码中直接提取过来的,所以可以进行更改。
对于方块,将通过BiomeColors.getFoliageColor()方法获取与该方块所在的生物群系相关的颜色,并进行注册。
对于物品,将获取默认颜色并进行注册。
对于这两者,我们将在register()方法的第一个参数中传递颜色(通过lambda表达式获取),并在第二个参数中传递一个对象来设置颜色。
除非您想进行复杂的操作,否则您不需要深入理解这些内容。

关于实际的颜色,详细内容在参考页面上有写。基本上是准备了灰度的叶子块纹理,然后添加颜色,但是这次我准备了带有一些色调的文件,实际的块也变成了那个色调,所以我猜内部可能是在进行加法运算。

增加TreeFeature类和Tree类

这些都是很难用言语解释清楚的区别,但它们都是管理树木的类。Tree类是一个管理树木本身的类,与稍后要添加的小苗有关。另一方面,TreeFeature类仅管理与树的生成相关的事项,并且还可以从Tree类中获取相应的TreeFeature。

\src\main\java\jp\koteko\example_mod\
   ├ blocks
   │   └ trees
   │      └ ExampleTree.java
   ├ items
   ├ lists
   ├ world
   │   └ features
   │      └ ExampleTreeFeature.java
   └ ExampleMod.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);
    }
}

创建ExampleTreeFeature类并继承TreeFeature类。传递给父类构造函数的参数顺序如下:
第一个参数是NoFeatureConfig的实例,可能不会被特别有效地使用。
第二个参数是一个布尔值,用于决定是否进行某种通知,因为在其他树中为false,所以进行了模仿。如果有了解,将进行补充说明。
第三个参数是决定树的最小高度的整数值。
第四个参数是作为主干的方块状态(BlockState)。
第五个参数是作为叶子的方块状态(BlockState)。
第六个参数是一个布尔值,用于决定是否在树上生长藤蔓。
另外,setSapling()用于设置树苗,并在这里传递苗的实例。树苗将在下一节中实现。

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();
    }
}

创建一个继承自抽象类Tree的ExampleTree类。定义一个名为getTreeFeature的方法。让它返回先前定义的ExampleTreeFeature的实例。尽管在参数中接收并未使用随机数,但这通常用于橡树等树木以概率分为常规种和巨大种的情况。

添加苗木

接下来会添加苗木。
首先准备好苗木的类。

\src\main\java\jp\koteko\example_mod\
   ├ blocks
   │   ├ trees
   │   └ ExampleSapling.java
   ├ items
   ├ lists
   ├ world
   └ ExampleMod.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类的构造函数是受保护的,无法直接从模组代码中使用。关于Minecraft是如何处理这个问题的,由于时间有限,我们暂不考虑,但我相信可能有更好的实现方式,以适应Minecraft的处理方式。
只需要更改包名、类名和构造函数这三个部分即可使用。

//...
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"
    }
}

在父级元素中,指定block/cross。这样可以以平面交叉的方式应用纹理(实际看到形状后,你应该能理解我所说的意思)。

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

不是继承block的模型文件,而是用item/generated指定。

{
  "block.example_mod.example_sapling": "Example Sapling"
}
{
  "block.example_mod.example_sapling": "例の苗"
}
{
  "type": "minecraft:block",
  "pools": [
    {
      "rolls": 1,
      "entries": [
        {
          "type": "minecraft:item",
          "name": "example_mod:example_sapling"
        }
      ]
    }
  ]
}
キャプチャ.PNG

生成木材

当我们来到这里的时候,只剩下一点儿了。让我们在生成世界时自动生成以前已经实现的树木。这与第8步中添加和生成矿石几乎相同,所以请参考那个步骤。

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

请将WorldGenTrees.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的参数是每个区块的抽奖次数、进行额外抽奖的可能性以及在进行额外抽奖时的额外次数。在这个例子中,是“每个区块进行2次抽奖,在10%的概率下再进行1次额外抽奖”。

最后,在主文件的setup函数中调用我们刚刚定义的WorldGenOres.setup()。

//...
public class ExampleMod
{
    //...
    private void setup(final FMLCommonSetupEvent event)
    {
        WorldGenTrees.setup();
    }
    //...
}
2020-08-09_17.46.30.png

我可以添加和生成木材了!

请为以下内容提供一个中文的本土化让步译文:参考

1.14.3 标签帮助 – 模组支持 – Forge 论坛
生物群系 – Minecraft Wiki
根据表 – Minecraft Wiki
地图物品格式 – Minecraft Wiki

以下的文章

接下来的文章

99. 模组的输出

广告
将在 10 秒后关闭
bannerAds