[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"
}
]
}
]
}
照常进行。
在原木方块方面还有一件事情要做。
尽管添加方块本身已经完成,但在以后构建木材的阶段中会需要它,所以我们在这里先进行。
将新添加的原木方块添加到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中指定要替换的方块。
添加叶子
接下来我们要添加组成树的叶子块。虽然有层叠的部分,但基础是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"
}
]
}
]
}
生成木材
当我们来到这里的时候,只剩下一点儿了。让我们在生成世界时自动生成以前已经实现的树木。这与第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();
}
//...
}
我可以添加和生成木材了!
请为以下内容提供一个中文的本土化让步译文:参考
1.14.3 标签帮助 – 模组支持 – Forge 论坛
生物群系 – Minecraft Wiki
根据表 – Minecraft Wiki
地图物品格式 – Minecraft Wiki
以下的文章
接下来的文章
99. 模组的输出