本篇教程的视频

(待发布)

本篇教程的源代码

(待发布)

本篇教程目标

  • 理解原版的树的构造特征
  • 学会自己编写基本的树的构造特征
  • 学会编写树的生成器

查看源代码

那么本期教程我们来写树,后面一期的话就是树的世界生成

树的话,在游戏里面属于地物(Feature)的一种,是一种多方块结构,这也就意味着,它有自己的构造特征

这里我们也来看看源代码相关的内容

我们找到TreeConfiguredFeatures这个类,后面我们还会接触TreePlacedFeatures这个类

前者是我们这期教程需要理解的构造特征类,后面是涉及其世界生成用的放置特征类

TreeConfiguredFeatures类似,原版其他地物也有各种各样的构造特征类,感兴趣的同学可以自行研究

构造特征类

我们直接来看源代码吧

1
public static final RegistryKey<ConfiguredFeature<?, ?>> OAK = ConfiguredFeatures.of("oak");

这里我们瞅瞅橡木的,可以看到,这里注册了一个构造特征的注册键

ConfiguredFeatures.of方法是一个公共的方法,我们自己写的时候也可以直接用,但要加上模组的命名空间

我们继续往下看

1
2
3
4
5
6
7
8
9
private static TreeFeatureConfig.Builder builder(Block log, Block leaves, int baseHeight, int firstRandomHeight, int secondRandomHeight, int radius) {
return new TreeFeatureConfig.Builder(
BlockStateProvider.of(log),
new StraightTrunkPlacer(baseHeight, firstRandomHeight, secondRandomHeight),
BlockStateProvider.of(leaves),
new BlobFoliagePlacer(ConstantIntProvider.create(radius), ConstantIntProvider.create(0), 3),
new TwoLayersFeatureSize(1, 0, 1)
);
}

这里我们看到了一个builder方法,这个方法返回一个TreeFeatureConfig.Builder对象

这个其实就是树它的构造特征的生成器,不过它是只用于最简单的直树干的,而像樱花树那样的,也有它自己的生成器

我们先来看看builder方法中的返回值

BlockStateProvider.of需要传入一个方块,第一个传入树干方块,第二个传入树叶方块

StraightTrunkPlacer是树干生成器,它需要传入树干的高度,以及两个随机高度,后面两个随机高度决定树干的下限上限高度

BlobFoliagePlacer是树叶生成器,它的第一个参数是树叶的半径,第二个参数是树叶的偏移量,第三个参数是树叶的生成高度

TwoLayersFeatureSize是配置树的层级结构,它的第一个参数是树底部的高度,一般指树干下方的叶子层数
第二个参数是树顶高度,一般指树干上方叶子层数,第三个是树顶高度的随机增量

1
2
3
private static TreeFeatureConfig.Builder oak() {
return builder(Blocks.OAK_LOG, Blocks.OAK_LEAVES, 4, 2, 0, 2).ignoreVines();
}

再往下看,我们就能看到一个oak方法,这个是橡树的构造生成方法,调用了上面的builder方法

1
2
3
public static void bootstrap(Registerable<ConfiguredFeature<?, ?>> featureRegisterable){
...
}

接下来我们能看到一个bootstrap方法,这个东西呢,在我们之后的开发过程中,你就将其理解为用于数据生成的方法,后面的数据生成我们也会用到

在这个方法中,我们也可以找到橡木相关的内容

1
ConfiguredFeatures.register(featureRegisterable, OAK, Feature.TREE, oak().build());

不过也就这一句话,因为它的构造方法已经在上面写了

而其他的树,其实也都类似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ConfiguredFeatures.register(
featureRegisterable,
DARK_OAK,
Feature.TREE,
new TreeFeatureConfig.Builder(
BlockStateProvider.of(Blocks.DARK_OAK_LOG),
new DarkOakTrunkPlacer(6, 2, 1),
BlockStateProvider.of(Blocks.DARK_OAK_LEAVES),
new DarkOakFoliagePlacer(ConstantIntProvider.create(0), ConstantIntProvider.create(0)),
new ThreeLayersFeatureSize(1, 1, 0, 1, 2, OptionalInt.empty())
)
.ignoreVines()
.build()
);

比如这个深色橡木,它的生成器写法和橡木其实差不多

它这里还有ignoreVines方法,这个方法的意思是忽略藤蔓,也就是生成树的时候,不会生成藤蔓

树的构造特征就这样差不多了,当然这里我们只看来最基本的橡树的构造特征,光是橡树也有很多变种,比如珍异橡木大型珍异橡木覆藤橡木等等,还有带蜂巢

不过呢,还是得从最基本的开始了解,后面的再慢慢研究

树苗生成器

我们还要来看树苗的生成器,让我们的树从树苗长成一个多方块结构的树

这里我们看看OakSaplingGenerator这个类,这个橡树树苗的生成类

1
2
3
4
5
6
7
8
9
10
public class OakSaplingGenerator extends SaplingGenerator {
@Override
protected RegistryKey<ConfiguredFeature<?, ?>> getTreeFeature(Random random, boolean bees) {
if (random.nextInt(10) == 0) {
return bees ? TreeConfiguredFeatures.FANCY_OAK_BEES_005 : TreeConfiguredFeatures.FANCY_OAK;
} else {
return bees ? TreeConfiguredFeatures.OAK_BEES_005 : TreeConfiguredFeatures.OAK;
}
}
}

这个类是继承SaplingGenerator,重写了getTreeFeature方法

当然,上面也说过了,橡树有很多变种,所以它这里就取随机来选择橡树的构造特征

如果我们没那么复杂的话,就直接返回构造特征的注册键就行

注册树

编写构造特征

这里我们创建ModConfiguredFeatures类,用于定义地物的构造特征,当然,你也可以像原版那样按照不同种类分开来

1
2
3
public class ModConfiguredFeatures {

}

这里我们先来写注册键

1
2
3
4
5
public static final RegistryKey<ConfiguredFeature<?, ?>> ICE_ETHER_TREE_KEY = of("ice_ether_tree");

public static RegistryKey<ConfiguredFeature<?, ?>> of(String id) {
return RegistryKey.of(RegistryKeys.CONFIGURED_FEATURE, new Identifier(TutorialModRe.MOD_ID, id));
}

注册键的注册方法用的是ConfiguredFeatures.of,不过我们加上了模组的命名空间

接下来我们创建bootstrap方法,用于注册构造特征,也用于后面的数据生成

1
2
3
public static void bootstrap(Registerable<ConfiguredFeature<?, ?>> featureRegisterable) {

}

bootstrap方法需要传入一个Registerable<ConfiguredFeature<?, ?>>对象,这个对象是用于注册构造特征的,这个与原版是一样的

接下来就是写树的构造特征

1
2
3
4
5
6
7
8
ConfiguredFeatures.register(featureRegisterable, ICE_ETHER_TREE_KEY, Feature.TREE,
new TreeFeatureConfig.Builder(
BlockStateProvider.of(ModBlocks.ICE_ETHER_LOG),
new StraightTrunkPlacer(4, 2, 1),
BlockStateProvider.of(ModBlocks.ICE_ETHER_LEAVES),
new BlobFoliagePlacer(ConstantIntProvider.create(3), ConstantIntProvider.create(2), 3),
new TwoLayersFeatureSize(1, 0, 2)
).build());

注册键传入我们上面写的注册键,原木和树叶都是我们在之前的教程中注册好的

另外的树干树叶层级构造特征我们就不一一解释了,上面都说过了

其实,树干、树叶以及层级的构造器都是可以自己自定义的,你可以参考参考相关的模组

树苗生成器

接下来我们创建IceEtherTreeGenerator类,用于定义树苗的生成器

1
2
3
4
5
6
public class IceEtherTreeGenerator extends SaplingGenerator {
@Override
protected @Nullable RegistryKey<ConfiguredFeature<?, ?>> getTreeFeature(Random random, boolean bees) {
return ModConfiguredFeatures.ICE_ETHER_TREE_KEY;
}
}

继承SaplingGenerator类,并重写getTreeFeature方法,这里我们没有那么多变种,所以就直接返回树的构造特征注册键

注册树苗方块

1
2
public static final Block ICE_ETHER_TREE_SAPLING = register("ice_ether_tree_sapling",
new SaplingBlock(new IceEtherTreeGenerator(), AbstractBlock.Settings.copy(Blocks.OAK_SAPLING)));

那么树苗它还是一个方块,所以我们还得来注册一下

这里实例化的是SaplingBlock,第一个参数是对应的树苗生成器,传入我们上面写的类即可

加入物品栏

1
entries.add(ModBlocks.ICE_ETHER_TREE_SAPLING);

最后别忘了加入物品栏

树苗渲染层设置

常规的,树苗的材质一般也有全透明的区域,所以我们得设置一下它的渲染层

到TutorialModReClient中添加

1
BlockRenderLayerMap.INSTANCE.putBlock(ModBlocks.ICE_ETHER_TREE_SAPLING, RenderLayer.getCutout());

数据生成

世界生成类

这里我们创建一个ModWorldGenerator,并继承FabricDynamicRegistryProvider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ModWorldGenerator extends FabricDynamicRegistryProvider {
public ModWorldGenerator(FabricDataOutput output, CompletableFuture<RegistryWrapper.WrapperLookup> registriesFuture) {
super(output, registriesFuture);
}

@Override
protected void configure(RegistryWrapper.WrapperLookup wrapperLookup, Entries entries) {
entries.addAll(wrapperLookup.getWrapperOrThrow(RegistryKeys.CONFIGURED_FEATURE));
}

@Override
public String getName() {
return "World Gen";
}
}

这个数据生成类与我们之前写的都不一样,它继承的是FabricDynamicRegistryProvider,这是Fabric提供的一个动态数据生成类,它允许我们在数据生成的时候动态的注册数据

这里我们重写了configure方法,这里我们要将CONFIGURED_FEATURE注册键的相关的数据注册到entries中,这样我们就可以在数据生成的时候动态的注册数据了

getName方法返回的是数据生成的名称,随便写一个就可以了

TutorialModReDataGenerator

接下来我们到模组的数据生成主类中,将我们写的世界生成类注册进去

onInitializeDataGenerator方法中添加

1
pack.addProvider(ModWorldGenerator::new);

然后,我们还要重写一个buildRegistry方法

1
2
3
4
@Override
public void buildRegistry(RegistryBuilder registryBuilder) {
registryBuilder.addRegistry(RegistryKeys.CONFIGURED_FEATURE, ModConfiguredFeatures::bootstrap);
}

用它来调用我们ModConfiguredFeatures中的bootstrap方法

语言文件

1
translationBuilder.add(ModBlocks.ICE_ETHER_TREE_SAPLING, "Ice Ether Sapling");

模型文件

树苗的模型文件生成方法与其他的方块有一点区别

1
blockStateModelGenerator.registerTintableCross(ModBlocks.ICE_ETHER_TREE_SAPLING, BlockStateModelGenerator.TintType.NOT_TINTED);

它用的是registerTintableCross方法,后面的NOT_TINTED表示不使用贴图颜色,意思就是会不会随着生物群系的改变而改变其颜色

测试

那么,放好树苗的贴图文件之后,数据生成也要运行一下

数据生成会生成树的构造特征的json文件,在数据文件夹中的world_gen/configured_features文件夹中

最后我们就可以进入游戏进行测试了