数据生成
为了简化我们自己手动编写json配置文件的过程,mdk提供了一些类来帮助我们进行json生成。
生成器
这里是生成器的汇总部分,我们通过事件总线,把各类数据的生成器汇总到一起来执行(使用DataGenerator来添加各类数据的provider),目前主要包含配方、战利品、方块/物品标签、方块、物品、语言文件。
其中配方、战利品、方块/物品标签是包含在服务端,方块、物品、语言文件放在客户端(方便客户端更换材质包与翻译)
java
@Mod.EventBusSubscriber(modid = TestMod.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD)
public class ModDataGenerator {
@SubscribeEvent
public static void gatherData(GatherDataEvent event) {
DataGenerator generator = event.getGenerator();
PackOutput packOutput = generator.getPackOutput();
ExistingFileHelper existingFileHelper = event.getExistingFileHelper();
CompletableFuture<HolderLookup.Provider> lookupProvider = event.getLookupProvider();
generator.addProvider(event.includeServer(), new ModRecipesProvider(packOutput));
generator.addProvider(event.includeServer(), ModLootTableProvider.create(packOutput));
BlockTagsProvider blockTagsProvider =
generator.addProvider(event.includeServer(), new ModBlockTagsProvider(packOutput, lookupProvider, existingFileHelper));
generator.addProvider(event.includeServer(), new ModItemTagsProvider(packOutput, lookupProvider, blockTagsProvider.contentsGetter(), existingFileHelper));
generator.addProvider(event.includeClient(), new ModBlockStateProvider(packOutput, existingFileHelper));
generator.addProvider(event.includeClient(), new ModItemModelsProvider(packOutput, existingFileHelper));
generator.addProvider(event.includeClient(), new ModEnUsLangProvider(packOutput));
generator.addProvider(event.includeClient(), new ModZhCnLangProvider(packOutput));
}
}
方块model、blockstate
对于方块类物品的models和blockstates,有BlockStateProvider可以使用
我们看一下它的源码:

首先是构造函数,output是生成json数据保存的路径,modid是命名空间,exFileHelper是资源文件检查工具(避免引用纹理缺失的数据),里面已经封装了BlockModelProvider和ItemModelProvider,能直接生成方块和方块物品的model以及方块的blockstate。
接下来可以看到一个run函数,和一个抽象方法registerStatesAndModels(这个就是需要我们重写自定义的方法),run函数先清空了之前生成的json,然后运行我们要生成的内容,接着把这些futures任务注册到异步事件列表中,后续我们从外部的DataGenerator依次调用这些事件运行来生成json。

接下来的一大部分都是各种各样的方块生成方法,可供我们调用:

这里我们只用simpleBlockWithItem,生成最简单的model与blockstate
先包装一下我们模组的BlockStateProvider
java
public class ModBlockStateProvider extends BlockStateProvider {
public ModBlockStateProvider(PackOutput output, ExistingFileHelper exFileHelper) {
super(output, TestMod.MOD_ID, exFileHelper);
}
@Override
protected void registerStatesAndModels() {
simpleBlockWithItem(ModBlocks.CANDY_BLOCK.get(),cubeAll(ModBlocks.CANDY_BLOCK.get()));
simpleBlockWithItem(ModBlocks.CANDY_ORE.get(),cubeAll(ModBlocks.CANDY_ORE.get()));
}
}
我们继承了BlockStateProvider,后续调用的时候可以直接使用DataGenerator添加我们自定义的Provider,从而执行数据生成事件。
物品model
比方块更加简单,ItemModelProvider中也只有basicItem一个函数,需要注意的是,只添加物品,不用再添加方块物品了。
java
public class ModItemModelsProvider extends ItemModelProvider {
public ModItemModelsProvider(PackOutput output, ExistingFileHelper existingFileHelper) {
super(output, TestMod.MOD_ID, existingFileHelper);
}
@Override
protected void registerModels() {
basicItem(ModItems.CANDY_STICK.get());
basicItem(ModItems.CANDY_STAFF.get());
}
}
翻译文件lang
没啥好讲的,继承一下LanguageProvider,复写addTranslations,然后一个一个加翻译就行了
注意物品和方块由于使用的延迟注册器,最好用get获取他们的名字
java
public class ModEnUsLangProvider extends LanguageProvider {
public ModEnUsLangProvider(PackOutput output) {
super(output, TestMod.MOD_ID, "en_us");
}
@Override
protected void addTranslations() {
add(ModItems.CANDY_STAFF.get(), "Candy Staff");
add(ModItems.CANDY_STICK.get(), "Candy Stick");
add(ModBlocks.CANDY_BLOCK.get(), "Candy Block");
add(ModBlocks.CANDY_ORE.get(), "Candy Ore");
add("itemGroup.test_tab", "Test Tab");
}
}
配方recipe
这里注意,要继承RecipeProvider,同时实现IConditionBuilder的接口,但是IConditionBuilder具有默认方法,所以我们也不需要复写。
我们只需要实现buildRecipes函数,其中采用的也是建造者模式。这些方法都比较显然,但是注意这些配方的命名空间是根据合成产物的命名空间放置的,所以要注意把部分配方的保存位置在save中进行修改。
java
public class ModRecipesProvider extends RecipeProvider implements IConditionBuilder {
public ModRecipesProvider(PackOutput pOutput) {
super(pOutput);
}
@Override
protected void buildRecipes(Consumer<FinishedRecipe> pWriter) {
ShapedRecipeBuilder.shaped(RecipeCategory.BUILDING_BLOCKS, ModBlocks.CANDY_BLOCK.get())
.group("candy_block")
.define('#', Items.SUGAR)
.pattern("###")
.pattern("###")
.pattern("###")
.unlockedBy(getHasName(Items.SUGAR), has(Items.SUGAR))
.save(pWriter);
ShapedRecipeBuilder.shaped(RecipeCategory.MISC, ModItems.CANDY_STICK.get())
.group("candy_stick")
.define('#', Items.SUGAR)
.pattern("#")
.pattern("#")
.unlockedBy(getHasName(Items.SUGAR), has(Items.SUGAR))
.save(pWriter);
ShapedRecipeBuilder.shaped(RecipeCategory.COMBAT, ModItems.CANDY_STAFF.get())
.group("candy_staff")
.define('c', ModItems.CANDY_STICK.get())
.define('d', Items.DIAMOND)
.pattern(" d")
.pattern(" c ")
.pattern("c ")
.unlockedBy(getHasName(ModItems.CANDY_STICK.get()), has(ModItems.CANDY_STICK.get()))
.unlockedBy(getHasName(Items.DIAMOND), has(Items.DIAMOND))
.save(pWriter);
ShapelessRecipeBuilder.shapeless(RecipeCategory.MISC, Items.SUGAR, 9)
.group("sugar")
.requires(ModBlocks.CANDY_BLOCK.get())
.unlockedBy(getHasName(Items.SUGAR), has(Items.SUGAR))
.save(pWriter, TestMod.MOD_ID + ":" + "sugar_from_candy_block");
}
}
战利品loot_table
首先,我们已经知道战利品列表有很多种,比如实体掉落、战利品箱、方块掉落、钓鱼等等
这里我们只是用了方块掉落(只用一个次级的Provider),所以我们继承BlockLootSubProvider,实现generate方法。
写之前先阅读一下BlockLootSubProvider的源码

以青金石掉落为例,json文件:
java
{
"type": "minecraft:block",
"pools": [
{
"bonus_rolls": 0.0,
"entries": [
{
"type": "minecraft:alternatives",
"children": [
{
"type": "minecraft:item",
"conditions": [
{
"condition": "minecraft:match_tool",
"predicate": {
"enchantments": [
{
"enchantment": "minecraft:silk_touch",
"levels": {
"min": 1
}
}
]
}
}
],
"name": "minecraft:lapis_ore"
},
{
"type": "minecraft:item",
"functions": [
{
"add": false,
"count": {
"type": "minecraft:uniform",
"max": 9.0,
"min": 4.0
},
"function": "minecraft:set_count"
},
{
"enchantment": "minecraft:fortune",
"formula": "minecraft:ore_drops",
"function": "minecraft:apply_bonus"
},
{
"function": "minecraft:explosion_decay"
}
],
"name": "minecraft:lapis_lazuli"
}
]
}
],
"rolls": 1.0
}
],
"random_sequence": "minecraft:blocks/lapis_ore"
}
对应的生成代码:
java
protected LootTable.Builder createLapisOreDrops(Block pBlock) {
return createSilkTouchDispatchTable(pBlock, (LootPoolEntryContainer.Builder)this
.applyExplosionDecay(pBlock, LootItem.lootTableItem(Items.LAPIS_LAZULI)
.apply(SetItemCountFunction.setCount(UniformGenerator.between(4.0F, 9.0F)))
.apply(ApplyBonusCount.addOreBonusCount(Enchantments.BLOCK_FORTUNE))));
}
这里可以知道,createSilkTouchDispatchTable,就是创建了alternatives的函数,然后把精准采集的掉落物放到了第一个。
然后接下来的几个apply都相当于加function字段,然后并且设置对应的函数。
接下来我们可以仿照上面的写法,自定义一个创建掉落物的函数,其实这里也不用传入item作为参数,因为每个方块掉落物一般不会完全一样,设置的属性也一般都不一样,最好还是每个物品单独写一个createXXXDrops。
对于普通方块,直接使用dropSelf生成即可。
java
public class ModBlockLootTablesProvider extends BlockLootSubProvider {
public ModBlockLootTablesProvider() {
super(Set.of(), FeatureFlags.REGISTRY.allFlags());
}
@Override
protected void generate() {
dropSelf(ModBlocks.CANDY_BLOCK.get());
add(ModBlocks.CANDY_ORE.get(), block -> createLapisOreLikeDrops(ModBlocks.CANDY_ORE.get(), Items.SUGAR));
}
protected LootTable.Builder createLapisOreLikeDrops(Block pBlock, Item item) {
return createSilkTouchDispatchTable(pBlock,
this.applyExplosionDecay(pBlock, LootItem.lootTableItem(item)
.apply(SetItemCountFunction.setCount(UniformGenerator.between(4.0F, 9.0F)))
.apply(ApplyBonusCount.addOreBonusCount(Enchantments.BLOCK_FORTUNE))));
}
@Override
protected Iterable<Block> getKnownBlocks() {
return ModBlocks.BLOCKS.getEntries().stream().map(RegistryObject::get)::iterator;
}
}
完成SubProvider的复写之后
我们再包装一下,让他生成LootTableProvider
先看一下LootTableProvider的内容,Set里面定位ResourceLocation,List就是新创建的SubProviders,对于SubProviderEntry,设置类型BLOCK

可设置的战利品类型:

包装结果:
java
public class ModLootTableProvider {
public static LootTableProvider create(PackOutput packOutput){
return new LootTableProvider(packOutput, Set.of(), List.of(
new LootTableProvider.SubProviderEntry(ModBlockLootTablesProvider::new, LootContextParamSets.BLOCK)
));
}
}
标签tag
这个相对来说就没那么复杂了
tag设定标签,add往这个标签下添加物品。
稍微包装一下就搞定了
java
public class ModBlockTagsProvider extends BlockTagsProvider {
public ModBlockTagsProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> lookupProvider, @Nullable ExistingFileHelper exFileHelper) {
super(output, lookupProvider, TestMod.MOD_ID, exFileHelper);
}
@Override
protected void addTags(HolderLookup.Provider pProvider) {
tag(BlockTags.MINEABLE_WITH_PICKAXE)
.add(ModBlocks.CANDY_ORE.get());
tag(BlockTags.NEEDS_STONE_TOOL)
.add(ModBlocks.CANDY_ORE.get());
}
}
目前Item还没添加标签:
java
public class ModItemTagsProvider extends ItemTagsProvider {
public ModItemTagsProvider(PackOutput pOutput, CompletableFuture<HolderLookup.Provider> pLookupProvider, CompletableFuture<TagLookup<Block>> pBlockTags, @Nullable ExistingFileHelper existingFileHelper) {
super(pOutput, pLookupProvider, pBlockTags, TestMod.MOD_ID, existingFileHelper);
}
@Override
protected void addTags(HolderLookup.Provider provider) {
}
}
运行数据生成
点击右边的大象,双击runData,可以看到数据生成的结果,以及花费的时间。
食物
首先我们要理解MC食物机制的原理,先读一下源码:


游戏中一点饥饿度就是半个鸡腿,饱和度在原版中是看不见的,它最大值为当前饥饿度,Foods类中的.nutrition表示食物营养值(能够恢复的饥饿度),saturationMod表示食物的饱和度系数。
吃东西的数据更新方式如下:
新饥饿度=max(原饥饿度+食物营养值,20)
新饱和度=max(原饱和度+食物营养值*饱和度系数*2,新饥饿度)
更多的机制内容见百科
饥饿 - 中文Minecraft Wiki镜像_BWIKI_哔哩哔哩
我们在Item文件夹下添加一个ModFoods.java,然后在其中编写食物的效果:
在MobEffectInstance中,第一个表示效果类型,第二个是效果持续时间,第三个是效果等级,外面的是获取该效果的概率。
.alwaysEat可以让玩家在饱腹的情况下继续吃该食物。
java
public class ModFoods {
public static final FoodProperties CANDY_STICK = new FoodProperties.Builder().nutrition(1).saturationMod(0.5f)
.effect(() -> new MobEffectInstance(MobEffects.MOVEMENT_SPEED, 200, 1), 0.4f)
.effect(() -> new MobEffectInstance(MobEffects.WEAKNESS, 400, 1), 0.1f)
.effect(() -> new MobEffectInstance(MobEffects.CONFUSION, 200, 1), 0.1f)
.alwaysEat().build();
}
接着把上述效果使用.food添加到注册该物品的地方:
java
public static final RegistryObject<Item> CANDY_STICK =
ITEMS.register("candy_stick", () -> new Item(new Item.Properties().food(ModFoods.CANDY_STICK)));
运行测试

可以看到能够正常产生食物的效果。