数据生成 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
文件夹下,里面的格式也是和我们之前写的一样的
之后,我们就可以启动游戏进行测试了