数据生成 1.20 Fabric 长线教程计划
本篇教程的视频
本篇教程的源代码
GitHub地址:TutorialMod-DataGen-1.20.1
本篇教程目标
- 学会使用数据生成
- 学会利用原版已有数据生成方法改写自定义的数据生成方法
注意事项
请确保在第一期模板生成已经选择了Data Generation选项,不然你得自己去配置数据生成相关的内容
fabric.mod.json文件中的entrypoints要有fabric-datagen及其对应的数据生成类
模组的数据生成主类实现DataGeneratorEntrypoint接口,并重写onInitializeDataGenerator方法
各个数据生成类
前述
数据生成是我们模组开发中一个非常好用的工具,用于帮助我们生成各种各样的json文件
不知道大家前面写的物品和方块的模型文件、方块状态文件、语言文件、战利品列表、配方等等json文件,
有没有写得头大,往往会写错命名空间或者方块、物品的名字,导致加载失败
那么现在,我们使用数据生成就可以避免这些问题
不过,为什么说我们的教程并不是一开始就来讲数据生成呢?因为你终归还是要理解json才能来写数据生成的东西
虽然本期教程用的都是原版的数据生成方法和Fabric API,但当我们加入自己的一些东西时,
比如我们未来会讲的方块状态,在你没有理解方块状态文件的各种参数之前,那是写不出来的;
方块实体的自定义配方同样如此
像战利品列表中的随机池,里面的参数有一堆,复合抽取项、单一抽取项和特殊抽取项,它们下面还有各个不同的值;如果你这些没能理解,
那自然也不会写
那么在你理解json文件的格式之后,你不仅可以自己去写各种数据生成方法,同
样也可以使用其他语言来写json文件——你觉得模组开发中的数据生成类不够直观,
引入图形界面来辅助(当然,其实有网站已经实现了)
所以,终归还是得理解各类json的基本格式,才能更好地使用数据生成,而不是我一上来就讲数据生成
源代码的话其实没什么好讲的,因为其实质就是拿Java生成json文件,而且本期要讲的东西稍微有点多,
所以我们直接来讲数据生成
Tag数据生成
那么Tag分为很多,用到的最多的是方块和物品的标签,这里我们先写两个类
ModBlockTagsProvider
方块的标签我们在前面写战利品列表的时候已经接触过了,我们这里创建一个ModBlockTagsProvider类,
继承FabricTagProvider.BlockTagProvider类,并实现super方法
还有要重写configure方法
1 | public class ModBlockTagsProvider extends FabricTagProvider.BlockTagProvider { |
那么接下来就是在configure方法中写数据生成方法
这里我们使用getOrCreateTagBuilder方法来写
1 |
|
getOrCreateTagBuilder要传入的参数是一个BlockTag,BlockTags里面注册有原版所有的方块标签,
这里是镐子能挖掘的方块标签,其对应的就是我们之前写的mineable文件夹下的pickaxe.json(这个你看BlockTags在它的注册名就知道了)
再后面就是所以add方法,将我们模组的方块传入
另一个需要铁质工具采集的方块标签同样如此
另外,和add类似的还有一个forceAddTag方法,相比之下,这个方法是加入Tag的,而不是加入方块的
在本系列重制的教程中,探矿器这个例子去掉了,旧教程中还有,它是与后面一期的Tag配合起来的,
其中,在数据生成中,就使用了forceAddTag方法,将原版的各种矿石加入
虽然我们这里还没到Tag的教程,不过感兴趣的同学可以去看看旧教程
探矿器旧教程:探矿器
Tag旧教程:Tag
ModItemTagsProvider
那么和方块标签类似的,物品的也差不多
我们创建一个ModItemTagsProvider类,继承FabricTagProvider.ItemTagProvider类,
并实现super方法
这里要注意,用IDEA来补全super方法时,选择的构造函数,一定一定要选择形参只有两个的构造函数!!!
那三个形参的最后一个是给BlockTag的
另外也是重写configure方法
1 | public class ModItemTagsProvider extends FabricTagProvider.ItemTagProvider { |
不过,这里我们暂时还没有物品的标签要写,所以就先空着好了
语言文件
那么现在,我们讲语言文件的生成,这里就讲美式英语语言文件和简体中文语言文件的编写
美式英语
这里我们创建一个ModEnUsLangProvider类,继承FabricLanguageProvider类,实现super方法
不过,这里我们选择构造函数的时候,可以只选择只有一个形参的,然后在super方法中指定语言文件的名字
1 | public class ModEnUsLangProvider extends FabricLanguageProvider { |
另外重写generateTranslations方法
在generateTranslations方法中,我们就可以来写语言文件的数据生成方法,
使用translationBuilder.add方法
1 |
|
add有一堆重载方法,第一个参数可以接受物品、方块、物品栏注册键、字符串等,
第二个参数为字符串,也是翻译后的内容
在这里,我们可以发现创造模式物品栏那期教程中,我特地提到的它们的区别
第一个参数只能接受物品栏的注册键(RegistryKey<ItemGroup>),而不能是物品栏(ItemGroup)
所以我们使用第二种简化方法写的,也就只能拿翻译键来翻译了
简体中文
那简体中文的语言文件也是一样的,只是在super中,指定的是zh_cn
我们创建ModZhCnLangProvider类,继承FabricLanguageProvider类,实现super方法
1 | public class ModZhCnLangProvider extends FabricLanguageProvider { |
战利品列表文件
接下来我们创建ModLootTableProvider类,继承FabricBlockLootTableProvider类,实现super方法
还要重写generate方法
1 | public class ModLootTableProvider extends FabricBlockLootTableProvider { |
同样的,我们在generate方法中来写
1 |
|
这里我们使用addDrop方法来为方块添加战利品列表
其中我们为矿石再加入一个矿石的战利品列表生成方法,这个生成方法返回类型是LootTable.Builder,
这也是addDrop方法中第二个形参的类型
这个oreDrops方法,第一个参数是方块,第二个参数是掉落物,
另外精准采集这个附魔的特殊情况也会在最终文件中生成
但是,这个矿石的掉落只有一个,而我们想像铜矿石、青金石等这样能够掉落多个的,怎么办呢?
找源代码中的方法
1 | public LootTable.Builder copperOreDrops(Block drop) { |
我们在BlockLootTableGenerator类中可以找到这么一个方法
dropsWithSilkTouch方法,即为在生成时加上精准采集附魔的情况,这是它的原方法(得追个几层)
1 | public static LootTable.Builder dropsWithSilkTouch(ItemConvertible drop) { |
再往后,applyExplosionDecay方法,即爆炸衰减,这个是我们在之前的战利品列表那期也探讨过
它接受的参数是方块和掉落物,这里的掉落物是粗铜
这里的掉落物使用ItemEntry.builder来创建,里面填掉落物,后面的apply则写我们在之前教程中讲到过的各个function,即物品修饰器
第一个那自然是修改数量的,这里让粗铜掉落2~5个
第二个是根据时运附魔来修改掉落物数量
然鹅,我们想用还不能用,因为这里的ItemEntry.builder(Items.RAW_COPPER)是写死的粗铜,
另外的青金石、红石也是这样,所以我们不妨自己写一个类似的
在我们数据生成类,创建一个likeCopperOreDrops方法
1 | public LootTable.Builder likeCopperOreDrops(Block drop, Item item, float min, float max) { |
这里我们把ItemEntry.builder中的Items.RAW_COPPER改为item,后面两个参数为掉落物数量的最小值和最大值
并在形参中加入相关的参数,这样,我们就可以自定义掉落物和掉落数量了
然后我们就可以在generate方法中使用这个方法了
1 | // addDrop(ModBlocks.ICE_ETHER_ORE, oreDrops(ModBlocks.ICE_ETHER_ORE, ModItems.RAW_ICE_ETHER)); |
那么,游戏中其他的,非一般的战利品列表同样如此,大家可以自行探索
模型文件
随后我们创建ModModelsProvider类,继承FabricModelProvider,实现super方法
还要重写generateBlockStateModels和generateItemModels方法
1 | public class ModModelsProvider extends FabricModelProvider { |
generateBlockStateModels方法,显然易见,是用于生成方块的方块状态文件的,不过,
它也会生成方块和方块物品的模型文件
1 |
|
使用blockStateModelGenerator.registerSimpleCubeAll来生成我们模组的方块相关文件,
这里的CubeAll其实就是我们前面写的最简单的方块模型,即六面相同
这个方法只要将方块传入即可
未来我们还将遇到更多的方法,用于生成带有不同属性的方块状态文件
generateItemModels方法,则是用于生成物品的模型文件的
1 |
|
类似的,使用itemModelGenerator.register来生成
这里面传入两个参数,第一个是物品,第二个是模型类型,这里我们使用Models.GENERATED,
这也是我们之前写过的,最简单的物品模型
配方文件
最后,我们就来到了配方文件,创建ModRecipesProvider类,继承FabricRecipeProvider,实现super方法
还要重写generate方法
1 | public class ModRecipesProvider extends FabricRecipeProvider { |
generate方法,用于生成我们模组的配方文件
9->1 | 1 -> 9 可逆合成配方
我们先来写1个方块合成9个物品和9个物品合成1个方块的配方,这个直接用原版已经封装好的方法即可
1 | offerReversibleCompactingRecipes(exporter, RecipeCategory.MISC, ModItems.ICE_ETHER, |
这里我们直接写两个,用offerReversibleCompactingRecipes方法来创建,一个方法即可生成两个配方
这个方法接受5个参数,第一个是配方导出器,后面的分别的配方类型和合成产物,
两两一块一起看——即前面两个是合成ICE ETHER时,所属的配方类型和合成产物(ICE ETHER);
后面两个是合成ICE ETHER BLOCK时,所属的配方类型和合成产物(ICE ETHER BLOCK)
另一个也是同样的
熔炼配方
熔炉和高炉的配方也有封装好的方法,但在此之前,我们得先写一个列表
1 | public static final List<ItemConvertible> ICE_ETHER = List.of(ModItems.RAW_ICE_ETHER, ModBlocks.ICE_ETHER_ORE); |
这个列表是List<ItemConvertible>类型,其存放的是都可以熔炼为ICE ETHER的物品(结合下方的)
之后,我们就可以利用这个List来创建熔炼配方了
1 | offerSmelting(exporter, ICE_ETHER, RecipeCategory.MISC, ModItems.ICE_ETHER, 0.7f, 200, "ice_ether"); |
offerSmelting方法,用于创建熔炉配方,接受6个参数,
分别是配方导出器,可以熔炼为ICE ETHER的物品列表,配方类型,加工产物,
产物掉落物的经验值,熔炼时间,以及group配方组的名字
offerBlasting方法,用于创建高炉配方,接受6个参数,同上
有序合成
接下来我们看有序合成
1 | ShapedRecipeJsonBuilder.create(RecipeCategory.FOOD, Items.SUGAR, 3) |
有序合成我们使用ShapedRecipeJsonBuilder.create来创建
方法接受3个参数,分别是配方类型,配方产物,产物数量
下面的pattern和input就与前面写过的类似了
值得注意的是,input方法接受的第一个参数是字符型(char)的变量,而不是字符串类型(string)的,
所以要使用单引号
criterion方法,用于设置解锁配方的条件,接受2个参数,第一个是字符串,第二个是解锁条件
这个拿它最终生成的进度文件来看可能会更好理解一点
1 | "has_beetroot": { |
这是其中一部分,在criterion方法中,hasItem(Items.BEETROOT)对应的是其中的has_beetroot,也就是这里的键;
而后面的conditionsFromItem(Items.BEETROOT)对应的是后面的一串
inventory_changed表示的是物品栏改变,这里合起来就变成了当甜菜进入玩家物品栏时,则解锁配方
最后的offerTo方法,用于将配方导出,接受2个参数,第一个是配方导出器,第二个是配方名字
无序合成
无序合成也是类似的
1 | ShapelessRecipeJsonBuilder.create(RecipeCategory.MISC, ModBlocks.ICE_ETHER_ORE, 1) |
它也有对应的ShapelessRecipeJsonBuilder.create方法
它和有序合成的方法不同的,就在于其input方法,这里的input就对应的是之前配方中的ingredients内容
食物类烹饪配方(以营火为例)
那么,如果说我们要写营火或者烟熏炉的配方该怎么办呢?
营火、烟熏炉这样的用于食物的烹饪配方,并没有像熔炼、高炉那样有封装好的方法,这个时候,我们就得去看看源代码了
在原版的RecipeProvider类中,我们可以看到这一堆
1 | public static void generateCookingRecipes( |
这里有一个offerFoodCookingRecipe,原版是把所有食物烹饪的配方写在这里了
不过问题是,像其中的cooker、serializer、cookingTime这样的参数,我们应该怎么写呢?
我们可以在另外一个地方找到generateCookingRecipes方法调用的地方
1 | generateCookingRecipes(exporter, "smoking", RecipeSerializer.SMOKING, 100); |
那么在这里,我们就可以找到所需要的参数,不过营火和烟熏炉的是分开的,它们的cooker和serializer参数是对应的
那么,我们就可以根据以上内容写我们自己的食物烹饪配方
1 | offerFoodCookingRecipe(exporter, "campfire_cooking", RecipeSerializer.CAMPFIRE_COOKING, |
数据生成调用
虽然上面的一堆类写好了,但是我们还是无法生成我们的数据,因为这些类现在也还没有被调用
接下来我们要到TutorialModDataGenerator类中调用这些类,这个类是一开始就随我们的模板文件一起生成的
1 |
|
我们先写一句FabricDataGenerator.Pack pack = fabricDataGenerator.createPack();
然后,在addProvider方法中,我们就可以调用我们之前写好的类了
不过要注意的是,每个类的构造函数的访问修饰符都要改成public,否则无法调用
随后,在运行我们的数据生成之前,我们要将原来已经写好的各种json文件删除,因为是不能有重复文件的,
会报错
当然我更建议的是找一个地方移动过去,不然数据生成出问题了,而原来的东西没了,还得重新写
运行数据生成
我们找到右上偏中间位置的配置栏,找到其中的ata Generation,点击运行
当运行窗口中输出Build Successful时,就表示数据生成成功了
我们也可以看到它生成所需的时间,一般生成也就几百毫秒,而我们前面写了多长时间呢?
生成好的数据在generated文件夹下,里面的格式也是和我们之前写的一样的
之后,我们就可以启动游戏进行测试了











