数据生成 1.21 Fabric
本篇教程的视频:
本篇教程源代码
GitHub地址:TutorialMod-DataGen-1.21
注意事项!!!
在开始本篇教程之前,请确保你在生成模板文件的时候,勾选了Data Generation,否则你很有可能无法正常使用数据生成
介绍
数据生成(DataGen)是一个很方便的用于生成各类json文件的工具,可以生成包括模型文件、方块状态文件、语言文件、Tags文件、配方文件等等。
而当你有大量模组内容时,手动编写这些文件会非常繁琐,所以我们可以使用数据生成器来生成这些文件
比如说,在我开发的Arknights Furniture模组中,有大量的家具,目前所加入的模型总数为1000+,如果我要手动编写这些模型的数据文件,那我可能已经没了。
而且只是写模型文件和方块状态文件,暂时还没考虑配方文件
Minecraft本身有数据生成器,我们在本期教程中也将使用原版的一些方法。
不过,因为要讲的内容比较多,所以源代码这个东西暂时不讲,我会提供相关的路径,可以自行研究
另外,数据生成这个东西,不仅仅可以使用这里教程中的方法,你可以使用其他的json生成器,也可以使用其他的语言来编写。
Such as,在我之前开发的Arknights Furniture模组中,在尚未使用数据生成之前,是使用Python批量生成对应模型的数据文件(后面实在受不了了,就开始用Python生成Java了)
Tags文件数据生成
虽然说我们的教程中,尚未真正讲到Tags,但是我们之前写战利品列表的时候,已经写了相关的Tags文件,所以这里我们也就讲了
Tags文件其实有很多种,我们这里就暂时指写方块和物品的Tags文件,其他的Tags文件可以自行研究(比如画这种,也是有Tag的)
原版的Tag生成器可见TagProvider
方块Tags文件
创建ModBlockTagsProvider类,继承FabricTagProvider.BlockTagProvider,并实现super函数和configure方法
(是的,我们继承的是Fabric API中的类,因为这样会相对方便一点)
注意,在实现super函数时,参数选择FabricDataOutput和CompletableFuture<RegistryWrapper.WrapperLookup>这两个,不要选择三个参数的,那第三个参数是给ItemTag的
1 | public class ModBlockTagsProvider extends FabricTagProvider.BlockTagProvider { |
在configure方法中,我们可以写入我们的Tags文件,这里我们写之前战利品列表中写过的文件
1 |
|
这里我们使用getOrCreateTagBuilder方法来获取一个Tags文件,然后使用add方法来添加我们的方块
我们之前写的Tag是属于BlockTags,所以这里我们使用BlockTags中的相关字段来获取我们的Tags文件
物品Tags文件
创建ModItemTagsProvider类,继承FabricTagProvider.ItemTagProvider,并实现super函数和configure方法
1 | public class ModItemTagsProvider extends FabricTagProvider.ItemTagProvider { |
我们暂时还没有写物品的Tags文件,所以就简单创建一下即可,后面我们会写的
语言文件数据生成
在之前我们也写了语言文件,这里我们也可以使用数据生成来生成语言文件。不过,原版是没有语言文件生成器的
en_us
创建ModENUSLanProvider类,继承FabricLanguageProvider,并实现super函数和generateTranslations方法
1 | public class ModENUSLanProvider extends FabricLanguageProvider { |
这里的super可以在中间加入一个"en_us",这个是语言文件的文件名,如果你要写其他语言文件,可以在这里修改。
不写的话,默认就是en_us
然后在generateTranslations方法中,我们可以写入我们的语言文件
1 |
|
这里我们使用add方法来添加我们的语言文件,第一个参数是id,第二个参数是翻译的值。add能够接受的参数很多,比如Item、Block、String等等
其实这里的物品栏,如果你是像原版一样写的,单独为物品栏注册了RegistryKey,那也可以直接引用这个RegistryKey来写入语言文件
zh_cn
中文的语言文件也是一样的,只是文件名不一样,我们创建ModZHCNLanProvider类,继承FabricLanguageProvider,并实现super函数和generateTranslations方法。
然后super中的参数改为"zh_cn",这个是中文的文件名
1 | public class ModZHCNLanProvider extends FabricLanguageProvider { |
翻译的东西这里就不再赘述了,和上面的en_us一样
战利品列表数据生成
创建ModBlockLootTableProvider类,继承FabricBlockLootTableProvider(注意中间有Block),并实现super函数和generate方法
原版生成器可见BlockLootTableGenerator,不过注意,我们这里只讲了方块的战利品列表
1 | public class ModBlockLootTableProvider extends FabricBlockLootTableProvider { |
在generate方法中,我们可以写入我们的战利品列表
1 |
|
这里我们使用addDrop方法来添加我们的战利品列表,这里单个参数的话是默认掉落其本身
而矿石的掉落是需要额外再使用oreDrops方法来添加的,这个方法接受两个参数,第一个是矿石,第二个是掉落物品
值得一提的是,这个oreDrops方法是只掉落一个物品的,如果你要掉落多个物品,那么我们就要去看铜矿石的掉落列表(青金石、红石亦可),然后自己写了
我们到BlockLootTableGenerator中可以看到原版的战利品列表的各种生成方法,我们找到copperOreDrops方法
1 | public LootTable.Builder copperOreDrops(Block drop) { |
这里我们可以看到,掉落的是Items.RAW_COPPER,掉落的数量是2-5,而且还有FORTUNE附魔(时运)的加成(当然,还有SilkTouch(精准采集)的附魔)。
这个方法我们并不能直接拿来用,因为我们的矿石和掉落物品不一样,所以我们要自己写
其实搬过来,自己改写一下即可
1 | public LootTable.Builder copperOreLikeDrops(Block drop, Item dropItem) { |
这里我们只是将Items.RAW_COPPER改为了dropItem,并添加了一个形参Item dropItem,这样我们就可以自定义掉落物品了。
而附魔相关的东西,你可以自行探究
随后,我们就可以使用这个方法来添加我们的掉落物品了
1 | addDrop(ModBlocks.ICE_ETHER_ORE, copperOreLikeDrops(ModBlocks.ICE_ETHER_ORE, ModItems.RAW_ICE_ETHER)); |
这样,我们的矿石也就可以掉落多个物品了
模型文件数据生成
创建ModModelProvider类,继承FabricModelProvider,并实现super函数、generateBlockStateModels和generateItemModels方法
原版模型生成器可见ModelProvider
1 | public class ModModelsProvider extends FabricModelProvider { |
这里我们重写的两个方法,一个是生成方块状态模型,一个是生成物品模型。
生成方块状态文件的同时,对应的物品模型也会生成,所以我们只需要写方块状态文件即可
在generateBlockStateModels方法中,我们可以写入我们的方块状态文件
1 |
|
我们之前写的都是最简单的方块状态文件,也就是立方体的六个面都一样,所以我们使用registerSimpleCubeAll方法来注册我们的方块状态文件
这里的registerSimpleCubeAll方法接受一个参数,就是我们的方块
在generateItemModels方法中,我们可以写入我们的物品模型文件
1 |
|
这里我们使用register方法来注册我们的物品模型文件,第一个参数是我们的物品,第二个参数是我们的模型的渲染模式,这里我们使用了Models.GENERATED,这个是一般物品的渲染模式(后面讲到工具时,我们还将采用另外的)
配方文件数据生成
创建ModRecipeProvider类,继承FabricRecipeProvider,并实现super函数和generate方法
原版配方生成器可见RecipeProvider
1 | public class ModRecipesProvider extends FabricRecipeProvider { |
在generate方法中,我们可以写入我们的配方文件
合成配方
这里我们先写之前写的1个方块合成9个物品的配方及其逆配方,这个用一个方法即可完成两个配方
1 | offerReversibleCompactingRecipes(exporter, RecipeCategory.MISC, ModItems.ICE_ETHER, |
这里我们使用offerReversibleCompactingRecipes方法来添加我们的配方,
这个方法有很多参数,注意不要漏
第一个是exporter
第二个是配方的类型,是后者(Block/Item)合成该参数的后一个产物(Item/Block)的分类
第三个是合成的物品(或方块)
第四个是配方的类型,是前者(Item/Block)合成该参数的后一个产物(Block/Item)的分类
第五个是合成的方块(或物品)
有点小绕,不过你可以看看相关的源代码,就能理解了
熔炼配方
我们写之前的熔炉和高炉配方,但在此之前,我们需要一个List类型的字段来存放我们需要燃烧的物品
1 | private static final List<ItemConvertible> ICE_ETHER = List.of(ModItems.RAW_ICE_ETHER, Items.ICE); |
这里我们使用List.of方法来创建一个List,这个方法接受一个或多个参数,这里我们传入了两个参数,一个是我们的物品,一个是原版的物品。
后面你像继续加更多的物品,也是可以的
注意,一个List对应一个熔炼后的产物,也就是说这里的RAW_ICE_ETHER和ICE熔炼后会得到同一个产物
如果你要得到不同的产物,那么你就要创建多个List,然后在generate方法中写入多个熔炼配方
现在我们来写熔炼配方
1 | offerSmelting(exporter, ICE_ETHER, RecipeCategory.MISC, ModItems.ICE_ETHER, |
这里我们使用offerSmelting方法来添加我们的熔炉配方,
而offerBlasting方法来添加我们的高炉配方
这两个方法的参数本质上是一样的,其中它们的第二个参数是我们的List,
第三个参数是配方的分类,第四个参数是配方的产物,
第五个参数是熔炼经验,第六个参数是熔炼时间,
第七个参数是配方所属的组
营火配方
前面我们没讲营火配方,但曾经有人问过营火的配方怎么使用数据生成,很简单啦,看源代码先
可能你会发现,怎么没有类似于高炉和熔炉的方法呢?没有offerCampfire啥啥啥的。
但是,我们能发现一个offerFoodCookingRecipe的方法,这个方法是用来添加食物的烹饪配方的,而绝大多数的食物不仅有高炉和熔炉的配方,还有营火的配方(以及烟熏炉)
那好了,这个东西我们能不能挖出点什么来呢?我们先看看其中一个例子
1 | offerFoodCookingRecipe(exporter, cooker, serializer, recipeFactory, cookingTime, Items.BEEF, Items.COOKED_BEEF, 0.35F); |
它是generateCookingRecipes方法下的一条语句,而除了后面的具体的物品,前面那一堆直接使用形参的是什么呢?
这里我们进一步溯源,一路找到VanillaRecipeProvider中,它有这么两条语句
1 | generateCookingRecipes(exporter, "smoking", RecipeSerializer.SMOKING, SmokingRecipe::new, 100); |
So?我们找到了烟熏炉和营火的配方生成器的写法,而照着offerBlasting和offerSmelting的写法,我们可以写出营火的配方生成器
1 | public static void offerCampfireCooking(RecipeExporter exporter, List<ItemConvertible> inputs, RecipeCategory category, ItemConvertible output, float experience, int cookingTime, String group) { |
无非将高炉和熔炉里的参数换成了营火的参数
然后我们就可以使用这个方法来添加我们的营火配方了
1 | offerCampfireCooking(exporter, ICE_ETHER_LIST, RecipeCategory.MISC, ModItems.ICE_ETHER, |
不过,我到后来才发现,其实还有更简单的方法,那就是直接使用offerFoodCookingRecipe方法
1 | offerFoodCookingRecipe(exporter, "campfire_cooking", RecipeSerializer.CAMPFIRE_COOKING, CampfireCookingRecipe::new, |
而只是将其中的形参换成了具体的实参而已。
当然,如果你有大量的配方需要添加,我的建议还是提炼个方法出来。
不然每次都要写一大堆,不仅麻烦,而且容易出错
举一反三
前面的教程举了另外两个有序合成和无序合成的例子,这里我们也来写一下
1 | ShapedRecipeJsonBuilder.create(RecipeCategory.MISC, Items.SUGAR,3) |
这里我们使用ShapedRecipeJsonBuilder来创建有序合成配方
pattern是合成表格,这里只有一行,如果是一个完整的九宫格,那么就是像下面这样
1 | .pattern("###") |
input是输入,也就是你九宫格中单字符对应的物品,这里我们使用#来代表BEETROOT;
而有多个字符时,也是多写几个input即可
criterion是条件,这个属于隐形进度,也就是不同于“石器时代”、“获得升级”这种有类似于任务书一样可见进度。
配方在游戏中是按照你游戏时触发的条件解锁的,比如你入水了,会解锁和船有关系的配方;获得了原木,则会解锁和木头相关的配方等等。数据生成中是必须写的
offerTo是输出,里面的Identifier是配方的命名空间和id,命名空间不写的话是默认在minecraft下的
1 | ```json |
这里我们使用ShapelessRecipeJsonBuilder来创建无序合成配方
input是输入,这里我们有两个输入,一个是RAW_ICE_ETHER,一个是STONE,最多也不能超过9个
criterion是条件,这里我们有两个条件,一个是RAW_ICE_ETHER,一个是STONE,这里的条件和有序合成一样,也是必须写的.
不过是写一个也行,写多个也行,这里的话,只要有一个条件满足,那么这个配方就会解锁
offerTo是输出,不再赘述
注册数据生成器
噼里啪啦写了一堆,一共是7个类(实际上视频教程只有6个,中文的语言文件生成器没写),但现在我们还要注册这些数据生成器,不然它们是不会生效的
我们找到TutorialModDataGenerator,在它的onInitializeDataGenerator注册我们的数据生成器
1 | public class TutorialModDataGenerator implements DataGeneratorEntrypoint { |
当然了,这里我们把fabricDataGenerator.createPack()拿出来了,单独赋给了一个Pack,这样我们就可以使用pack来添加我们的数据生成器了
然后确保我们的所有数据生成器都是public的,不然是无法正确引用的
运行Data Generation
找到右上角Minecraft Client的下拉框,选择Data Generation,然后等待数据生成完成即可
或者你也可以找Gradle中Tasks/fabric中的runDatagen,也是可以的
数据生成的速度很快,基本上几十毫秒就可以搞定,不过如果你的模组内容很多,那么可能会慢一点
这样想想,假设你和我一样有着千百个模型,纯手写这些数据文件,估计Minecraft都更新了好几代了
注意
另外,请在正式运行游戏之前,将原本你写的这些数据文件删除,不然会出现重复的数据文件,导致游戏无法正常运行
当然你不放心的话,可以先找个地方放一下,然后运行游戏,看看是否正常
之后的教程除了特殊情况,基本上会采用数据生成来写数据文件,这样不仅方便,而且还能够减少出错的概率











