本篇教程的视频

本篇教程的源代码

GitHub地址:

本篇教程目标

  • 搞清花和树的区别:花是「一片一片」长的
  • 认识 26.1 的一个大改动:随机斑块被搬到了放置特征里
  • 学会用放置修饰器拼出「撒一片花」的效果
  • 学会只往指定群系里加

介绍

上一期我们做了花和盆栽,花能放、能进花盆,但野外还是没有

这一期把它塞进世界生成

花和树都是地物,但形态不一样

树是一棵一棵的,位置比较独立;花是一簇一簇的,一小片地方能挤十几朵

原版把这种「一小片」的形式叫随机斑块(random patch)

26.1 的一个大改动

在 1.21 及更早,随机斑块是一个构造特征

你会看到一个 Feature.FLOWER,配一个RandomPatchConfiguration,里面写着「撒几次、水平多大、垂直多大」

26.1 把这两个东西都删了

现在的花,构造特征就是「放一个方块」(Feature.SIMPLE_BLOCK),「撒一片」的效果改由放置特征来拼

所以你要是在网上找老教程照着写 new RandomPatchConfiguration(20, 4, 3, ...),26.1 里根本找不到这个类

现在我们得换一种方式了

查看源代码

构造特征

原版平原上的花,在 VegetationFeatures 里

1
FeatureUtils.register(context, FLOWER_DEFAULT, Feature.SIMPLE_BLOCK, new SimpleBlockConfiguration(provider));

就一句

Feature.SIMPLE_BLOCK 是「在这儿放一个方块」,SimpleBlockConfiguration 里给它一个方块状态提供器

provider 可以是单一方块,也可以是一组带权重的方块(比如蒲公英和虞美人按 2:1 混着长)

放置特征

一片花是怎么来的,看沼泽那组

1
2
3
4
5
6
7
8
PlacementUtils.register(context, FLOWER_SWAMP, flowerSwamp,
RarityFilter.onAverageOnceEvery(32),
InSquarePlacement.spread(),
PlacementUtils.HEIGHTMAP,
BiomeFilter.biome(),
CountPlacement.of(64),
RandomOffsetPlacement.ofTriangle(6, 2),
BlockPredicateFilter.forPredicate(BlockPredicate.ONLY_IN_AIR_PREDICATE));

七条修饰器,可以分成两段读

前四条决定「中心点选在哪」

  • RarityFilter.onAverageOnceEvery(32):平均每 32 个区块才选一次,也就是「稀疏」
  • InSquarePlacement.spread():在这个区块里随机取一个点
  • PlacementUtils.HEIGHTMAP:把这个点贴到地表
  • BiomeFilter.biome():确认这个位置属于当前群系

后三条决定「在中心点周围撒多大一片」

  • CountPlacement.of(64):以这个点为中心,撒 64 朵
  • RandomOffsetPlacement.ofTriangle(6, 2):水平方向 ±6 格、垂直方向 ±2 格,三角形分布(越靠中心越密)
  • BlockPredicateFilter:只往空气里塞,别把花塞进石头

后三条合起来,就是以前那个 RandomPatchConfiguration 干的事

为什么这么改(猜测)

虽然写起来更复杂了,但同样更灵活了,我们可以更多样地处理不同的植物生成

以前斑块只能整块用,现在你可以给「撒一片」这一步单独加条件

比如原版的花海,前面还会插一个NoiseThresholdCountPlacement,让花只长在噪声的低谷里,形成那种一片密一片稀的效果

这在以前的斑块配置里是做不到的(除非自己搓相关的方法)

动手写

构造特征

到 ModConfiguredFeatures 加一个注册键

1
public static final ResourceKey<ConfiguredFeature<?, ?>> SIMPLE_FLOWER = createKey("simple_flower");

然后在 bootstrap 里注册

1
2
FeatureUtils.register(context, SIMPLE_FLOWER, Feature.SIMPLE_BLOCK,
new SimpleBlockConfiguration(BlockStateProvider.simple(ModBlocks.SIMPLE_FLOWER)));

就一朵花,没有别的参数

放置特征

到 ModPlacedFeatures,注册键

1
public static final ResourceKey<PlacedFeature> SIMPLE_FLOWER_PLACED = createKey("simple_flower_placed");

注册

1
2
3
4
5
6
7
8
9
PlacementUtils.register(context, SIMPLE_FLOWER_PLACED,
configured.getOrThrow(ModConfiguredFeatures.SIMPLE_FLOWER),
RarityFilter.onAverageOnceEvery(4),
InSquarePlacement.spread(),
PlacementUtils.HEIGHTMAP,
BiomeFilter.biome(),
CountPlacement.of(32),
RandomOffsetPlacement.ofTriangle(6, 2),
BlockPredicateFilter.forPredicate(BlockPredicate.ONLY_IN_AIR_PREDICATE));

onAverageOnceEvery(4):平均每 4 个区块选一个中心点

CountPlacement.of(32):一个中心点撒 32 朵,比原版 64 少一半

注入群系

1
2
3
BiomeModifications.addFeature(BiomeSelectors.includeByKey(Biomes.PLAINS),
GenerationStep.Decoration.VEGETAL_DECORATION,
ModPlacedFeatures.SIMPLE_FLOWER_PLACED);

这里第一次用 includeByKey

前面做树和矿的时候我们用的是 foundInOverworld(),那是「整个主世界」

includeByKey(Biomes.PLAINS) 是「只有平原」,想多加几个就逗号隔开

Biomes 类是原版的生物群系常量类,Biomes.PLAINS 就是平原

花属于植被,阶段用 VEGETAL_DECORATION

数据生成

这期还是不用改主类,CONFIGURED_FEATURE 和 PLACED_FEATURE 前面已经挂过了

测试

跑完数据生成我们就可以启动游戏进行测试了

常见问题(大肥鱼总结版)

  • 编译报找不到 RandomPatchConfiguration:26.1 删了这个类,花要用 Feature.SIMPLE_BLOCK + 放置修饰器拼
  • 野外一朵花都没有:BiomeModifications 没写,或者 includeByKey(Biomes.PLAINS) 的群系选错了(你去的不是平原)
  • 只有孤零零一朵一朵,不成片:少了 CountPlacement + RandomOffsetPlacement 这两条,只剩「撒点」阶段
  • 花多到铺满地面:onAverageOnceEvery(4) 的 4 太小,或者 CountPlacement.of(32) 太大
  • 花嵌在石头里:PlacementUtils.HEIGHTMAP 用成了 HEIGHTMAP_OCEAN_FLOOR 之类,或者漏了空气过滤
  • 花长到别的群系去了:用了 foundInOverworld(),应该用 includeByKey(...)
  • 花长在树叶上:HEIGHTMAP 会取到树叶上方,想避开就把 MOTION_BLOCKING 换成 MOTION_BLOCKING_NO_LEAVES
  • 数据生成没新文件:Registries.CONFIGURED_FEATURE / PLACED_FEATURE 没挂在 buildRegistry 里
  • 改了参数游戏里没变化:旧存档区块已生成,得新建世界
  • 花开在水上:水面被 HEIGHTMAP 当成地表了,加一条 BlockPredicate 过滤水