本篇教程的视频
本篇教程的源代码
GitHub地址:
本篇教程目标
- 搞清矿脉是怎么生成的:先铺岩石,再替换
- 学会写
OreConfiguration 和 RuleTest
- 学会给同一个矿石配三套维度规则
- 学会用高度范围和次数控制分布
介绍
矿脉也是地物,所以它和树一样,走的是「构造特征 + 放置特征 + 群系注入」这条链
不过它比树简单:树要算树干、树叶、层级,矿只要「替换掉哪块石头」和「一个区块挖几次」
按区块生成
原版的矿脉是按区块算的
每个区块单独掷骰子,决定这一次生成多少个矿石、生成在什么高度
所以你在游戏里看到的矿脉,边界经常正好卡在区块边界上
像 GTNH 那种 3×3 区块一起算的,是改过生成逻辑的,我们这期不涉及
查看源代码
OreFeatures
原版的矿物构造特征都在 OreFeatures 里,看煤矿
1 2 3
| RuleTest ruletest1 = new TagMatchTest(BlockTags.STONE_ORE_REPLACEABLES); RuleTest ruletest2 = new TagMatchTest(BlockTags.DEEPSLATE_ORE_REPLACEABLES); RuleTest ruletest3 = new BlockMatchTest(Blocks.NETHERRACK);
|
RuleTest 就是「能替换哪些方块」的规则,有两种
TagMatchTest:按标签匹配,比如「所有能被矿石替换的石头」
BlockMatchTest:按方块匹配,比如「只有末地石」
为什么要分这两种?因为主世界的石头有个「深板岩」变体,用标签能一把捞全;末地石只有一种,直接指定方块更省事
接着是组合
1 2 3 4
| List<OreConfiguration.TargetBlockState> list5 = List.of( OreConfiguration.target(ruletest1, Blocks.COAL_ORE.defaultBlockState()), OreConfiguration.target(ruletest2, Blocks.DEEPSLATE_COAL_ORE.defaultBlockState()) );
|
target(规则, 方块状态),意思是「碰到符合规则的方块,把它换成这个」
所以主世界的矿要写两条:碰到普通石头换成煤矿,碰到深板岩换成深层煤矿
最后注册
1
| FeatureUtils.register(context, ORE_COAL, Feature.ORE, new OreConfiguration(list5, 17));
|
OreConfiguration 三个参数:替换表、矿脉规模、丢弃概率(可选)
规模 17 是「一个矿脉最多连着几个矿石」
丢弃概率是给「埋藏矿」用的,值越大,矿石越不愿意暴露在空气里,避免悬空的方块
OrePlacements
1 2 3 4
| PlacementUtils.register(context, ORE_COAL_UPPER, holder12, commonOrePlacement(30, HeightRangePlacement.uniform(VerticalAnchor.absolute(136), VerticalAnchor.top()))); PlacementUtils.register(context, ORE_COAL_LOWER, holder13, commonOrePlacement(20, HeightRangePlacement.triangle(VerticalAnchor.absolute(0), VerticalAnchor.absolute(192))));
|
commonOrePlacement(次数, 高度范围) 就是「每区块试几次 + 在哪个高度区间」
高度有两种分布
uniform:在区间里均匀随机,上下一样密
triangle:中间多、两头少,像座山
煤矿分成上下两段就是这原因:高处均匀铺一层,低处用梯形堆一团
commonOrePlacement 这个方法在原版里是 private 的,我们得自己抄一份
1 2 3 4 5 6 7
| private static List<PlacementModifier> orePlacement(PlacementModifier frequency, PlacementModifier heightRange) { return List.of(frequency, InSquarePlacement.spread(), heightRange, BiomeFilter.biome()); }
private static List<PlacementModifier> commonOrePlacement(int count, PlacementModifier heightRange) { return orePlacement(CountPlacement.of(count), heightRange); }
|
四条:次数、区块内随机撒点、高度区间、群系过滤
动手写
构造特征
回到 ModConfiguredFeatures,先加三个注册键
1 2 3
| public static final ResourceKey<ConfiguredFeature<?, ?>> ICE_ETHER_ORE = createKey("ice_ether_ore"); public static final ResourceKey<ConfiguredFeature<?, ?>> NETHER_ICE_ETHER_ORE = createKey("nether_ice_ether_ore"); public static final ResourceKey<ConfiguredFeature<?, ?>> END_ICE_ETHER_ORE = createKey("end_ice_ether_ore");
|
然后写替换规则
1 2 3 4 5 6
| RuleTest stoneReplace = new TagMatchTest(BlockTags.STONE_ORE_REPLACEABLES); RuleTest deepslateReplace = new TagMatchTest(BlockTags.DEEPSLATE_ORE_REPLACEABLES); RuleTest netherReplace = new TagMatchTest(BlockTags.BASE_STONE_NETHER); RuleTest endReplace = new BlockMatchTest(Blocks.END_STONE);
BlockState ore = ModBlocks.ICE_ETHER_ORE.defaultBlockState();
|
末地没有「可替换的末地石」这种标签,所以只能用 BlockMatchTest 指定末地石
然后三份替换表
1 2 3 4 5 6 7
| List<OreConfiguration.TargetBlockState> overworldTargets = List.of( OreConfiguration.target(stoneReplace, ore), OreConfiguration.target(deepslateReplace, ore)); List<OreConfiguration.TargetBlockState> netherTargets = List.of( OreConfiguration.target(netherReplace, ore)); List<OreConfiguration.TargetBlockState> endTargets = List.of( OreConfiguration.target(endReplace, ore));
|
因为这里我们只有一个矿石方块,所以就直接共用了,大家根据自己的实际情况进行替换
注册
1 2 3
| FeatureUtils.register(context, ICE_ETHER_ORE, Feature.ORE, new OreConfiguration(overworldTargets, 9)); FeatureUtils.register(context, NETHER_ICE_ETHER_ORE, Feature.ORE, new OreConfiguration(netherTargets, 12)); FeatureUtils.register(context, END_ICE_ETHER_ORE, Feature.ORE, new OreConfiguration(endTargets, 10));
|
矿脉规模大家也可以根据实际情况进行修改
放置特征
到 ModPlacedFeatures,先把那两个辅助方法抄进来(就是前面贴的那段)
然后是三个注册键
1 2 3
| public static final ResourceKey<PlacedFeature> ICE_ETHER_ORE_PLACED = createKey("ice_ether_ore_placed"); public static final ResourceKey<PlacedFeature> NETHER_ICE_ETHER_ORE_PLACED = createKey("nether_ice_ether_ore_placed"); public static final ResourceKey<PlacedFeature> END_ICE_ETHER_ORE_PLACED = createKey("end_ice_ether_ore_placed");
|
注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| PlacementUtils.register(context, ICE_ETHER_ORE_PLACED, configured.getOrThrow(ModConfiguredFeatures.ICE_ETHER_ORE), commonOrePlacement(10, HeightRangePlacement.uniform( VerticalAnchor.absolute(-64), VerticalAnchor.absolute(128))));
PlacementUtils.register(context, NETHER_ICE_ETHER_ORE_PLACED, configured.getOrThrow(ModConfiguredFeatures.NETHER_ICE_ETHER_ORE), commonOrePlacement(12, HeightRangePlacement.uniform( VerticalAnchor.absolute(20), VerticalAnchor.absolute(80))));
PlacementUtils.register(context, END_ICE_ETHER_ORE_PLACED, configured.getOrThrow(ModConfiguredFeatures.END_ICE_ETHER_ORE), commonOrePlacement(8, HeightRangePlacement.uniform( VerticalAnchor.absolute(0), VerticalAnchor.absolute(80))));
|
三个维度的高度不一样,这是原版地形决定的
主世界 -64 到 128:从基岩层到地表附近,全程都能挖
下界 20 到 80:下界顶部是基岩顶,太低太高都没意义
末地 0 到 80:末地岛就那么薄,给宽了也是空
注入三个维度
1 2 3 4 5 6
| BiomeModifications.addFeature(BiomeSelectors.foundInOverworld(), GenerationStep.Decoration.UNDERGROUND_ORES, ModPlacedFeatures.ICE_ETHER_ORE_PLACED); BiomeModifications.addFeature(BiomeSelectors.foundInTheNether(), GenerationStep.Decoration.UNDERGROUND_ORES, ModPlacedFeatures.NETHER_ICE_ETHER_ORE_PLACED); BiomeModifications.addFeature(BiomeSelectors.foundInTheEnd(), GenerationStep.Decoration.UNDERGROUND_ORES, ModPlacedFeatures.END_ICE_ETHER_ORE_PLACED);
|
三行,三个维度,用的是同一个生成阶段 UNDERGROUND_ORES
数据生成
这期不用改数据生成的主类
前面做树的时候,我们已经在 buildRegistry 里挂了 CONFIGURED_FEATURE 和 PLACED_FEATURE,也已经在 ModDynamicRegistryProvider 里 addAll 过了,新的矿物会自动跟着生成
跑完会多出六个文件
1 2 3 4 5 6
| data/tutorial/worldgen/configured_feature/ice_ether_ore.json data/tutorial/worldgen/configured_feature/nether_ice_ether_ore.json data/tutorial/worldgen/configured_feature/end_ice_ether_ore.json data/tutorial/worldgen/placed_feature/ice_ether_ore_placed.json data/tutorial/worldgen/placed_feature/nether_ice_ether_ore_placed.json data/tutorial/worldgen/placed_feature/end_ice_ether_ore_placed.json
|
测试
跑完数据生成之后,我们就可以启动游戏进行测试了
常见问题(大肥鱼总结版)
- 挖不到矿:
BiomeModifications 那三行没写,或者写成了别的 GenerationStep
- 主世界有,下界没有:
foundInTheNether() 那行漏了,或者下界的 RuleTest 写成了主世界的石头标签
- 矿长在地表:生成阶段写成了
VEGETAL_DECORATION,应该是 UNDERGROUND_ORES
- 矿悬空、飘在空中:构造特征的第三个参数(丢弃概率)没给,试试
new OreConfiguration(targets, 9, 0.5F)
- 下界挖到的矿长得像主世界的:正常,我们三个维度共用同一个方块
- 末地替换不了:
BlockMatchTest(Blocks.END_STONE) 写错了,末地石只有这一种
- 数据生成没有新文件:
Registries.CONFIGURED_FEATURE / PLACED_FEATURE 没挂在 buildRegistry 里
- 启动报找不到
ice_ether_ore_placed:ModDynamicRegistryProvider 里没 addAll
- 矿特别多,满地都是:
count 给太大了,原版铁矿是每区块 10 次左右,照着调
- 矿石挖出来不掉东西:战利品表是之前那期做的,这期只改生成,不碰掉落