树的世界生成 26.1 Fabric
本篇教程的视频
本篇教程的源代码
GitHub地址:
本篇教程目标
- 搞清
ConfiguredFeature和PlacedFeature的分工 - 学会写树的放置特征
- 学会 Fabric 的群系注入,并知道它和
NeoForge差在哪 - 学会把放置特征挂进数据生成
介绍
上一期我们做完了树:树叶、树苗、构造特征,树苗放下能长成一棵冰以太树
但你去野外逛,一棵都找不到
因为「能长出来」和「会自然生成」是两件事
两层结构
地物在游戏里分两层
ConfiguredFeature(构造特征):这棵树长什么样。树干多高、叶子多大、用哪些方块
PlacedFeature(放置特征):这棵树摆在世界的哪里、一个区块放几棵
上一期我们做的是前者,这一期做后者
还有一层:群系
就算你写好了放置特征,它也不会自己跑到野外的
因为每个生物群系的「地物列表」都是原版写死的
平原、森林里长什么树,是原版在那个群系注册的时候定好的,我们得往那个列表里加一条
查看源代码
TreePlacements
原版树的放置特征都在 TreePlacements 里,看橡树
1 | public static final ResourceKey<PlacedFeature> OAK_CHECKED = PlacementUtils.createKey("oak_checked"); |
PlacementUtils.createKey 和上一期的 FeatureUtils.createKey 一样,是原版的方法,只会生成 minecraft: 命名空间,我们自己写得手写 ResourceKey.create
然后是注册
1 | HolderGetter<ConfiguredFeature<?, ?>> configured = context.lookup(Registries.CONFIGURED_FEATURE); |
context.lookup(Registries.CONFIGURED_FEATURE)是先获取构造特征这个注册表
getOrThrow(TreeFeatures.OAK)从表里取出相应的树
PlacementUtils.register是将树的构造特征和放置条件组合成一个放置特征,注册到放置特征注册表里
filteredByBlockSurvival(...)是用来检查这个位置能不能种下树苗,不能就放弃
树专用的放置组合
原版还有一组给树用的通用组合
1 | private static Builder<PlacementModifier> treePlacementBase(final PlacementModifier frequency) { |
五个步骤,从上到下依次筛
frequency:一个区块里有几次尝试InSquarePlacement.spread():把位置在区块里随机摊开,不能全挤在一个角TREE_THRESHOLD:水面以上的深度过滤,别让树长在水里HEIGHTMAP_OCEAN_FLOOR:取地形的实际高度,把树正确摆在地表(不要让树浮空!)BiomeFilter.biome():群系过滤器,确认这个位置属于这个群系,不然会串到隔壁去
treePlacement(frequency, sapling) 就是在这一串后面再接一个「树苗能活」的检查
数量
1 | PlacementUtils.countExtra(2, 0.1F, 2) |
三个参数:每区块基本数量、额外数量的概率、额外的数量
也就是「每区块 2 棵,另外有 10% 的概率再多 2 棵」
这里有个小陷阱:概率必须是 1/n 能整除的,你写 0.3F 会直接抛异常,因为它内部要换算成权重
动手写
ModPlacedFeatures
1 | public class ModPlacedFeatures { |
注册名加了个 _placed 后缀,和构造特征区分开
原版也是这个习惯:ice_ether_tree 是构造特征,ice_ether_tree_placed 是放置特征
PlacementUtils.register 的第三个参数是「构造特征的 Holder」,第四个是「放置条件」,这里直接用了原版那套树的组合
两个方法的区别
前面出现了两个跟「树位置」有关的方法,很容易混,单独说一下
先看 PlacementUtils.filteredByBlockSurvival
1 | public static BlockPredicateFilter filteredByBlockSurvival(final Block block) { |
注意返回类型:BlockPredicateFilter,一个放置修饰器
它只干一件事,检查这个位置上能不能种下这棵树苗,土对不对、上面有没有空间
再看 VegetationPlacements.treePlacement
1 | public static List<PlacementModifier> treePlacement(final PlacementModifier frequency, final Block sapling) { |
返回的是 List<PlacementModifier>,一整串
前面那五条(数量、撒点、水位、高度、群系)加上最后这条「树苗能活」,一共六条
也就是说,treePlacement 的最后一步,其实就是 filteredByBlockSurvival 干的事,只是它前面垫了五条别的
那原版为什么还单独用它
看原版橡木那条注册
1 | PlacementUtils.register(context, OAK_CHECKED, oak, PlacementUtils.filteredByBlockSurvival(Blocks.OAK_SAPLING)); |
名字叫 OAK_CHECKED,注意这个 CHECKED,带这个后缀的都不是给群系直接用的
它们是「树丛特征」里的选项
1 | FeatureUtils.register(context, TREES_SAVANNA, Feature.RANDOM_SELECTOR, |
TREES_SAVANNA 是「热带草原的树丛」,它才是挂到群系上的那个东西,坐标由它自己的放置特征算好
它随机抽一个选项(80% 是金合欢、剩下的是橡树),把算好的坐标传给被选中的那棵树
被选中的树这个时候只需要回答一个问题:这个坐标上我能不能长
所以它只需要一条 would_survive,不需要也不该再自己撒点、算高度,不然坐标会被算乱
我们为什么用 treePlacement
我们的树是直接挂在群系上的,外面没有树丛特征帮着算坐标
那就得自带一整套:一个区块试几次、撒在哪、取多高、是不是这个群系,最后才轮到「树苗能不能活」
一句话总结
filteredByBlockSurvival 是零件,给「被别的特征调用」的树用
treePlacement 是整机,给「直接挂群系」的树用
拿零件当整机,注册出来的放置特征就只会在区块的那个固定点上试一次,野外基本上一棵树都看不到
注入群系
我们要到主类的onInitialize里写上
1 | BiomeModifications.addFeature(BiomeSelectors.foundInOverworld(), |
foundInOverworld()就是主世界全部群系;BiomeSelectors.includeByKey(Biomes.PLAINS, Biomes.FOREST)只改平原和森林VEGETAL_DECORATION指的是生成阶段,Decoration下面还有很多阶段,比如矿石生成是UNDERGROUND_ORES- 最后一个是我们的放置特征
数据生成
挂上放置特征
和上一期一样,两步TutorialModDataGenerator 的 buildRegistry 里加一行
1 | registryBuilder.add(Registries.PLACED_FEATURE, ModPlacedFeatures::bootstrap); |
ModDynamicRegistryProvider 的 configure 里加一行
1 | entries.addAll(registries.lookupOrThrow(Registries.PLACED_FEATURE)); |
测试
跑完数据生成我们就可以启动游戏进行测试了
常见问题(大肥鱼总结版)
- 野外一棵都没有:
BiomeModifications.addFeature没写,或者写在客户端了 - 有树但只有几棵:可能你的群系里原版树太多,把位置占了,试试把数量调大
- 树长在石头里面、悬空:生成阶段写错了,应该是
VEGETAL_DECORATION - 树长在水里:
treePlacement的第二个参数(树苗)漏了,少了一道would_survive检查 - 用
filteredByBlockSurvival注册完,野外一棵树都没有:它只是一条「树苗能不能活」的检查,不生成坐标,那是给树丛特征当子选项用的;直接挂群系得用treePlacement - 数据生成报
Chance data cannot be represented as list weight:countExtra的概率不是1/n,换成0.5F、0.25F这种 - 改了数据生成但游戏里没变化:旧存档的区块已经生成过了,得新建世界
- 启动报找不到
tutorial:ice_ether_tree_placed:configure里没addAll(PLACED_FEATURE) worldgen/placed_feature目录是空的:buildRegistry里没挂ModPlacedFeatures::bootstrap- 树生成到了别的群系:
BiomeFilter被你自己那套放置条件覆盖掉了,用原版treePlacement就别自己拼 /place feature能出树,野外却没有:说明构造特征没问题,是群系注入或放置特征没生效







