树 1.21 Fabric
本篇教程的视频:
本篇教程源代码
GitHub地址:TutorialMod-Tree-1.21
介绍
树在游戏中是一个非常重要的元素,它们可以提供木材
树应该也算是一种多方块结构(个人理解),它们是属于地物的(Feature),它们从一棵简单的树苗长成一棵大树,长出原木和树叶
前面我们也添加了一些原木和树叶,本篇教程我们将添加树,后面我们还将讲解树的世界生成
当然,因为现在的版本特性,世界生成的很多东西都是数据驱动的,这也就意味着,如果你会编写对应的json文件,同样也可以实现世界生成的一些东西
包括一些构造参数,也同样是通过json文件来实现的,我们写的一堆代码,除了必要的注册外,其他的都是为后面的数据生成做铺垫的
本篇教程和视频教程会有所区别,因为视频教程把后面的世界生成也讲了,而这里只是讲解树的添加
查看源代码
这里我们去看TreeConfiguredFeatures类,这个类是写了树的构造特征的,也就是这棵树它长什么样子
其实我们后面还会接触到其他的ConfiguredFeatures,感兴趣的可以去看看
1 | ... |
首先我们可以看到一堆的RegistryKey,这些RegistryKey是ConfiguredFeature的,它们是树的注册键
1 | public static RegistryKey<ConfiguredFeature<?, ?>> of(String id) { |
注册方法是这里的这个,但是我们也要自己更改一下命名空间,不能直接调用
我们继续往下看,可以看到一些具体的树的构造特征
1 | private static TreeFeatureConfig.Builder builder(Block log, Block leaves, int baseHeight, int firstRandomHeight, int secondRandomHeight, int radius) { |
我们看这几个树的构造特征,这里有橡树、白桦树、高白桦树、丛林树,它们调用的是同一个builder方法
我们再看看这个builder方法
TreeFeatureConfig.Builder方法前两个是原木和树干的生成器,这里的StraightTrunkPlacer是直树干生成器,上面的四个树都是直树干(当然橡树存在变种,但变种也是单独写的)
StraightTrunkPlacer的参数是基础高度 baseHeight、第一个随机高度 firstRandomHeight、第二个随机高度 secondRandomHeight,这里的基础高度是树的最小高度,第一个随机高度是在前者的基础上随机增加的高度,第二个随机高度是在第一个随机高度的基础上再随机增加的高度
第三个是树叶,第四个是树叶生成器,这里的BlobFoliagePlacer是其中一种树叶生成器,斑点状的树叶(应该是一团)
BlobFoliagePlacer的参数是半径 radius、偏移 offset、高度 height,这里的半径是树叶的半径,偏移是树叶的偏移量(相当于主干而言),高度是树叶的厚度
最后一个是树的最小尺寸,更准确来讲是树的外形特征,根据可生长空间的大小,树的外形会有所不同(具体可进一步深挖)
1 | private static TreeFeatureConfig.Builder fancyOak() { |
这里还有一些特殊的树,比如深色橡树、樱花树,它们的生成器也是不同的,比如LargeOakTrunkPlacer、CherryTrunkPlacer,它们的参数也是不同的
同样的,你也可以在实际开发过程中尝试自定义树干和树叶的生成器,也可以参考其他生物群系相关的模组
另外,我们还可以看到一个bootstrap,显然易见,待会数据生成得用上
1 | ConfiguredFeatures.register(featureRegisterable, OAK, Feature.TREE, oak().build()); |
里面其实写的就是前面写的builder方法,这里的oak().build()就是橡树的构造特征
当然还有其他一堆东西,这里就不一一赘述了,感兴趣的可以自行研究
添加树
接下来我们就来写我们自己的树的构造特征
创建ModConfiguredFeatures类
原版的树、矿物啥的构造特征类其实都是分开写的,我们也可以合在一起写,这里我们创建一个ModConfiguredFeatures类
1 | public class ModConfiguredFeatures { |
我们先把前面看到的of方法搬过来,并改写命名空间
1 | public static RegistryKey<ConfiguredFeature<?, ?>> of(String id) { |
而后,我们就可以写树构造特征的注册键了
1 | public static final RegistryKey<ConfiguredFeature<?, ?>> ICE_ETHER_TREE_KEY = of("ice_ether_tree"); |
然后我们直接来写bootstrap方法,并在这里面直接写我们的树的构造特征
1 | public static void bootstrap(Registerable<ConfiguredFeature<?, ?>> featureRegisterable) { |
视频教程中我们是把ConfiguredFeatures中的register方法给搬过来了,其实不用搬,因为它是public的,也没有要改的东西
TreeFeatureConfig那部分其实等同于上面的oak(),你也可以像原版那样给它单独拿出来写
我们的树按照橡树的写法,改了一些数值
各个参数上面也解释过了,这里就不再赘述
创建ModTreeGenerator类
视频教程多讲了一个ModPlacedFeatures类,这个类是用来放置特征的,本篇教程我们就不讲了,在后面一篇世界生成中讲
我们直接来写ModTreeGenerator类,这个类是用来生成树的,也就是让树苗长大成为一棵大树
1 | public class ModTreeGenerator { |
这个类和1.20里面的又不太一样,1.20中我们还是去继承了SaplingGenerator类,但在这里我们实例化SaplingGenerator
SaplingGenerator的参数分别是注册名 id、巨型变种 megaVariant、标准体 regularVariant、带蜂巢变种 beesVariant
这里我们只用到了注册名和标准体,巨型变种和带蜂巢变种我们都是Optional.empty(),也就是空的,因为我们没有编写相关的内容
当然,我们这里用的只是其中一个SaplingGenerator的构造函数,还有其他的构造函数,感兴趣的可以自行研究
1 | public static final SaplingGenerator OAK = new SaplingGenerator( |
比如橡树,就有一堆变种,它用的构造函数参数更多
注册树苗
那么接下来就是注册树苗了,毕竟我们要有树得先有树苗
1 | public static final Block ICE_ETHER_SAPLING = register("ice_ether_tree_sapling", |
这里我们注册了一个ICE_ETHER_SAPLING,实例化SaplingBlock,它的生成器是我们前面写的ICE_ETHER_TREE,后面是Settings
加入物品栏
不要忘了将我们的树苗加入物品栏
1 | entries.add(ModBlocks.ICE_ETHER_TREE_SAPLING); |
渲染层设置
最后我们还要设置渲染层,让树苗能够正常显示(一般都和我们的作物一样,带有透明通道的吧)
1 | BLOCKS.put(ModBlocks.ICE_ETHER_SAPLING, RenderLayer.getCutout()); |
上面是我们写的Mixin
也可以写在客户端类的onInitializeClient方法中
1 | BlockRenderLayerMap.INSTANCE.putBlock(ModBlocks.ICE_ETHER_SAPLING, RenderLayer.getCutout()); |
数据文件
数据生成
上面写的bootstrap方法,不要忘了在数据生成类的buildRegistry调用
1 | registryBuilder.addRegistry(RegistryKeys.CONFIGURED_FEATURE, ModConfiguredFeatures::bootstrap); |
创建ModWorldGen类
另外我们还要创建一个ModWorldGen类,这个是动态注册的,从它继承的类就能看出来
1 | public class ModWorldGen extends FabricDynamicRegistryProvider { |
继承FabricDynamicRegistryProvider,重写getName方法
随后我们在数据生成类中调用
1 | pack.addProvider(ModWorldGen::new); |
语言文件
1 | translationBuilder.add(ModBlocks.ICE_ETHER_SAPLING, "Ice Ether Sapling"); |
模型文件
1 | blockStateModelGenerator.registerTintableCross(ModBlocks.ICE_ETHER_SAPLING, BlockStateModelGenerator.TintType.NOT_TINTED); |
这里我们用到了BlockStateModelGenerator的registerTintableCross方法,十字型的方块模型,当然,它不可以被染色
而后我们就可以跑数据生成了,之后我们就可以在worldgen文件夹里面找到生成的一个构造特征的json文件
启动游戏继续测试吧











