本期教程我们要讨论的是数据生成器。在之前的编写过程中,我们手动编写了一些博客状态和语言内容,还有一些尚未开始编写的部分,比如方块的掉落物列表。目前方块被破坏时不会掉落任何物品,另外,方块和物品的合成方式也没有编写。
也许我们还想要实现一些进度相关的功能,包括提高与其他模组的兼容性等,这些内容都还没有着手编写。当然,如果你对数据包很了解,可以在资源包中新建一个名为"data"的文件夹,然后像编写数据包一样将这些内容写入其中,这样就可以包含掉落物和合成配方等内容了。
Minecraft本身也提供了生成这些数据的方法,今天我们来讲解数据生成器,并利用它简单地生成一些内容。为了应用我们的数据生成器,在项目中新建一个软件包,命名为"对他进",然后在其中创建一个Java类,命名为"DataGenerator"。
关于数据生成器,我们可以参考Forge的文档,尽管文档中有些地方可能并不是很清晰,但仍可以参考一些基本信息。例如,文档提示我们需要监听某个事件,并通过AT Provider将我们自己的数据生成类添加进去。
既然我们要监听一个事件,我们肯定需要使用 EventBus Subscriber 模式,并设置 mod id 为事件总线,以及需要监听的事件。在观看代码后,可以发现这是一个Mod事件,所以我们使用 @Mod.EventBusSubscriber 注解,然后编写一个静态方法如下:
@Mod.EventBusSubscriber(modid = "your_mod_id", bus = Mod.EventBusSubscriber.Bus.FORGE)public static void generateGatherDataEvent(GatherDataEvent event) {// 在这里进行数据生成操作}
在这里,我们需要添加我们自己的数据生成类。因此,我们可以新建一个名为"Recipes"的类,该类属于服务器数据。我们可以参考“Recipes Generation”中的内容,这个Recipes类需要作为IRecipeProvider的子类,并实现buildRecipe方法。
我们可以继承IRecipeProvider,然后实现构造函数并修改参数名。要如何编写呢?我们可以参考原版的写法,在net.minecraft.data包中找到Recipes,然后查看其中的配方。在这里有很多方法,它们主要是通过这种方式来生成配方。
我们可以看到它有ShapedRecipeBuilder(有序合成)和ShapelessRecipeBuilder(无序合成),还有其他一些方法,我们也可以点进去查看最终是如何生成的。我们可以看到它有有序合成、无序合成以及特殊物品合成等。
如果我们想要合成某样东西,我们可以参考原版类似物品的合成,看看它是如何写的,然后学着来写。看了原版之后,我们就知道应该如何写了。假设我们现在要为小门创建合成配方,我们希望小门是有序合成的,那么我们可以使用ShapedRecipeBuilder.shapedRecipe,在其中需要提供RecipeType。
接下来我们可以定义合成的样式,类似于在写数据包时定义合成表。我们刚才查看原版,也可以看到原版是这样写的。对于小门的合成,我们可以选择特殊的方式,比如用铁锭和铜锭来合成。定义好合成的样式后,我们需要确保这些物品的命名都是正确的。
首先,我们需要定义这个铁锭。我们可以直接使用原版的铁锭,通过原版的items来获取。但是,我们需要考虑到其他Mod的兼容性。如果我们硬编码为原版的铁锭,而另一个Mod作者认为他们的物品也可以作为铁锭使用,如果我们写死了,就无法使用他们的铁锭,导致不兼容。
为了解决这个问题,Forge提供了一套标签系统。我们可以在data/forge/tags/items/ingots.json中查看这些标签。如果有一个Mod认为他们的物品也可用作这类物品时,他们会将自己的物品添加到这些标签中。为了实现互通性,我们只需使用Forge提供的这些标签即可。
在编写合成配方时,尽量使用标签,因为标签可以包含一类物品。我们可以使用Forge的标签,比如#forge:ingots/iron表示铁锭。继续定义时,我们可以使用define关键字指定具体的物品,例如#forge:ingots/copper表示铜锭。这种做法只适用于Forge的标签。
如果你想使用原版的标签,你需要去data/minecraft/tags中找到对应的文本文件。在这里,你会找到原版的标签,比如如果你想使用原版的木板,你需要使用#item:planks来调用原版的标签。它们位于不同的位置,所以要注意你想要使用哪种标签。
Forge不会重复注册已经存在的原版标签,通常会首先查看原版是否已有标签,然后再查看Forge是否有。如果两者都没有,而你认为未来可能会有类似物品出现,那么最好使用标签来处理。
如果需要注册自定义的标签,可以使用数据生成器来生成一个手写的方式,并确保它被正确加载和使用。
完成合成配方之后,我们还需要考虑这个配方的解锁方式。在游戏中,通常会解锁一些配方后,在右上角会弹出一个已解锁的提示,并且在原版的合成表中也能看到。尽管很多人可能会使用JEI来查看所有配方并直接合成,但原版仍有一套内置的游戏规则,即只能使用已解锁的配方进行合成。
为了兼容这个系统,我们需要编写unlock by,这里我们假设拿到铜锭就可以解锁这个配方。因此,我们可以定义一个条件叫做has copper,使用has items来指定原版的copper ingot。
首先演示如何使用原版的物品,之后我们可以改用标签。最后别忘了点击save consumer保存。如果要生成更多的合成配方,可以继续按照相同的格式编写。
除了编写合成配方外,我们还需要添加文本提供程序(TextProvider),它必须是TX的子类,并实现add text方法。在这里可以找到许多现有的提供程序,包括原版的文本和Forge的文本。如果想要注册自定义的内容到Forge中,可以使用BotItemTextProvider并添加相关信息。复制并扩展原版标签生成类,可以使用BlogTextProvider来确保我们的小门只能使用铁工具来挖掘。
在这里我们可以扩展BlockTextProvider,然后实现构造函数方法。修改参数名称以便理解。接着可以使用铁工具吗?实际上这是原版的一个标签,在net.minecraft.text下,我们可以看到有BlogTextProvider,里面包含了一些示例。例如,Aaron Top需要不同类型的工具,我们需要使用TagNeedsErial,然后写入我们需要的内容,比如ModelBlock,点击Get,再点击Add来添加我们的方块MoreDogBlock。
这样我们就把我们的门添加到需要铁工具这个标签里面了,非常简单。如果想要添加到其他标签里面,也是类似的写法,都是使用Tag找到对应的标签提供程序。构造方法会直接生成,TT方法中写上tag参数,即对应的标签变量,然后再点击Add,添加自己的内容即可,非常简单。
文本提供程序已经注册好了,接下来我们需要给我们的门添加一个战利品表,使其能够掉落所需物品。我们可以在net.minecraft.data中找到Root,这里有LootTableProvider和BlockLootSubProvider,用来为方块添加掉落物品。这些内容都是可以学习和应用的,也可以直接使用,比如你想为方块、实体等添加掉落物品,都可以直接使用。
在Block中,我们可以看到有LootTableProvider,用于创建通用的战利品表,还有用于给方块添加战利品的BlockLootSubProvider。因为这些很常用,所以它们被单独实现了,可以直接使用。原版有注册自己的东西,这些战利品表也可以使用,它们实现了Provider,继承即可直接使用。
我们直接拿过来,然后新建JavaLootTables,然后扩展AnnilaBlockLte。在这里我们可以看到,它告诉我们要重写getKnownBlock方法。我们看看它是怎么写的,他使用了DeferRegister这样写出来的。我们也有我们的DefaultRegister,我们直接拷贝过来就好了。如果要使用这些块,我们需要使用public对吧?这里都可以给大家使用public,用了public之后,我们就可以在这里选择testMode,点击blocks,再点击getInterests,然后点击stream,再点flatMap,::atEerator是吧?这一堆我们就直接复制好,然后这个ResistantObject我们也给它导入一下,最后给他return一下,好。然后我们就可以写public void gentlemen,那我们这个小门肯定要掉落自己嘛,就dropSelfEstimmode,点smallDogBlock,点get,这样就好了。
当然,如果你想要添加一些附属条件,比如说需要精准采集什么的,我们也可以去原本的这里找一个类似的方块,比如说我们的铁矿,铁矿的方法是createSilkTouchDispatchTable,好。这里你就照着写就行了,也不难啊。我们演示就直接用最简单的就好了,其他东西基本上就不用我演示了。我就写这几个吧。
当然,这些其他的东西都像这个BlockStateLong都是可以用代码生成的。不过这些大家就参考这个文档,还有原版里面那些类,看看原版是怎么写的,文档是怎么写的,然后自己摸索摸索也就出来了,这个东西真的不难。当然,就算你实在搞不明白,你也可以手写嘛。MC的wiki上也都有这个数据包和资源包的编写教程,文件的各种结构都是写好了,你就可以照着wiki去直接写文件,也就不写代码了。好,这里我们就可以去注册我们的事件了。首先我们要wereGenerator等于event.getGenerator(),然后还需要一个变量叫packOutput等于这个generator.getPackOutput(),还需要existingFileHelper就是event.getExistingFileHelper(),然后还需要oneLookupProvider等于event.getLookupProvider()。总共需要这四个变量,我们现在来开始生成我们的文本。
这个就是GeneratorAndProvider,这里需要的变量是event.includeServerAndClient(),选一个server是生成服务器端资源的,LT是生成客户端资源的。像我们的这个tag,把我们的战利品表,我们的合成表都是服务端资源,所以我们都要用这个includeServer。如果我们生成的是语言或者block states这种东西,我们就需要使用includeClient了,然后这里直接传入我们的text实例,第一个参数就是我们的packOutput,第二个参数就是我们的lookupProvider,第三个参数是我们的modId,第四个参数就是我们的这个existingFileHelper,这就结束了。
然后我们继续atProviderEvent,点includeServer。我们在注册我们的战利品表NewLootTables。不过战利品这里我们要注意,因为我们用的不是这个LootTableProvider,我们用的是前面这些SubProvider,偶尔我们需要的是LootTableProvider,所以我们这里需要写new ImplicitListOfNewLootTableProvider,点subproviderIntro,然后使我们的lootTablesNewNoteContextPromSetsBlock。我们可以稍微换一下好,这一行太长了,看得都不太清楚。
这个东西写完了以后,我们还需要给我们的recipes写,那就GeneratorAndProviderEvent,点includeServerNewRecipesPoutput。最后,我们再把我们写的东西都打开检查一下,看看还有没有什么问题。比如说这里有一个问题啊,就是这个getNoseBlock是让我们这个类知道有哪些方块需要生成战利品表,但是我们这里呢却只给我们的这个smallDogBlock生成了战利品表。而这里呢我们把我们注册的两个block都包含进去了,如果它检测的我们没有为这个badBlock写战利品表,它就会崩溃。
因此,我们有两种解决方法。一种是在此处再编写一个myBlock的战利品表,另一种是在这里添加过滤器,点击filter,然后进行过滤。这里使用了一个Lambda表达式,为我们的block添加了一个过滤条件,因为我们只需为这一个生成,所以我们可以直接让它等于该值即可。如果我们只想过滤掉myBlock,也可以这样做,让它不等于myBlock,这样除了myBlock,其他都保持一致。由于我们只有两个方块,若有更多方块,则需要根据需求来调整。修改后保存一下,检查一下text和recipes部分。
在recipes中,发现了一个小问题,我使用了myBlock,但实际上应该生成小门,因此应该使用smallBlock。检查无误后选择runData,然后运行,如果进程正常结束并且退出代码为零,说明生成成功。如果退出代码非零,则表示有错误,需要查看报错信息。生成完成后,在generated文件中查看生成结果。首先会生成needsIronTop,为smallBlock生成合成表和战利品表,以及smallBlock的配方解锁方式。
然而,解锁方式仍然依赖原版铜锭,这可能导致与其他mod不兼容。解决方法很简单,在recipes中,将相关部分改为text,确保使用正确的标签。重新生成后查看效果,确认是否生效。检查后拿出铜锭测试,查看是否解锁了新的配方,正常情况下应该能够合成出小门。如果一切顺利,表示操作成功,那么本期教程就到这里为止。