资源

资源

资源是游戏使用的外部文件,但它们不是代码。最典型的资源是纹理,不过 Minecraft 生态中还有许多其他类型的资源。当然,所有这些资源都需要代码侧的使用者,因此,本节也会一并介绍使用这些资源的系统。

Minecraft 的资源大体分为两类:供逻辑客户端 使用的资源称为资产(assets),供逻辑服务端 使用的资源称为数据(data)。资产主要是用于呈现的信息,例如纹理、显示模型、翻译或声音;数据则包括影响游戏玩法的各种内容,例如战利品表、配方或世界生成信息。它们分别从资源包和数据包中加载。NeoForge 会为每个模组生成内置的资源包和数据包。

资源包和数据包通常都需要 pack.mcmeta 文件;不过,现代 NeoForge 会在运行时自动生成这些文件,因此不需要自行处理。

如果对某种格式有疑问,可以查看原版资源。NeoForge 开发环境不仅包含原版代码,也包含原版资源。可以在 External Resources(IntelliJ)或 Project Libraries(Eclipse)中找到它们:Minecraft 资源名为 ng_dummy_ng.net.minecraft:client:client-extra:<minecraft_version>,NeoForge 资源名为 ng_dummy_ng.net.neoforged:neoforge:<neoforge_version>。

资产

另见 Minecraft Wiki 的资源包 页面。

资产,也称客户端资源,是所有仅与客户端 有关的资源。它们从资源包加载。资源包有时也沿用旧称“材质包”,因为在早期版本中,它们只能影响纹理。资源包本质上是一个 assets 文件夹,其中为资源包包含的各个命名空间分别设置子文件夹。例如,模组 ID 为 coolmod 的模组资源包通常会包含 coolmod 命名空间,但也可以包含 minecraft 等其他命名空间。

NeoForge 会自动将所有模组资源包收集到 Mod resources 包中,它位于资源包菜单“已选”一侧的底部。目前无法禁用 Mod resources 包。不过,位于 Mod resources 上方的资源包会覆盖下方资源包定义的资源。这使资源包作者可以覆盖模组资源,也使模组开发者能够在需要时覆盖 Minecraft 资源。

资源包可以包含以下文件夹,用于影响相应内容:

文件夹名称 内容
atlases 纹理图集来源
blockstates 方块状态文件
equipment 装备信息
font 字体定义
items 客户端物品
lang 翻译文件
models 模型
particles 粒子定义
post_effect 屏幕后处理效果
shaders 元数据、片段着色器和顶点着色器
sounds 声音文件
texts 各种文本文件
textures 纹理
waypoint_style 路径点图标元数据

数据

另见 Minecraft Wiki 的数据包 页面。

与资产相对应,数据是所有服务端 资源的统称。数据通过数据包(data packs,也写作 datapacks)加载。与资源包类似,数据包由 pack.mcmeta 文件 和名为 data 的根文件夹组成。data 文件夹同样为包包含的各个命名空间分别设置子文件夹。例如,模组 ID 为 coolmod 的模组数据包通常会包含 coolmod 命名空间,但也可以包含 minecraft 等其他命名空间。

NeoForge 会在创建新世界时自动应用所有模组数据包。目前无法禁用模组数据包。不过,大多数数据文件都可以由优先级更高的数据包覆盖,因此也可以通过用空文件替换来移除相应内容。将额外的数据包放入世界的 datapacks 子文件夹后,可以使用 /datapack 命令启用或禁用它们。

说明 目前没有内置机制将一组自定义数据包应用于每个世界,但有一些模组可以实现这一点。

数据包可以包含以下文件夹,用于影响相应内容:

文件夹名称 内容
advancement 进度
banner_pattern 旗帜图案
cat_variant, chicken_variant, cow_variant, frog_variant, pig_variant, wolf_variant, zombie_nautilus_variant 实体变种
cat_sound_variant, chicken_sound_variant, cow_sound_variant, pig_sound_variant, wolf_sound_variant 实体声音变种
damage_type 伤害类型
datapacks 内置数据包
dialog 对话菜单
enchantment, enchantment_provider 附魔
instrument, jukebox_song 声音引用元数据
painting_variant 画
loot_table 战利品表
recipe 配方
tags 标签
test_environment, test_instance 游戏测试
trade_set, villager_trade 村民交易
trial_spawner 战斗挑战
trim_material, trim_pattern 盔甲纹饰
neoforge/data_maps 数据映射
neoforge/loot_modifiers 全局战利品修改器
dimension, dimension_type, structure, timeline, worldgen, neoforge/biome_modifier 世界生成文件

此外,数据包还可以包含一些与命令集成的系统所使用的子文件夹。这些系统很少与模组一起使用,但仍值得列出:

文件夹名称 内容
chat_type 聊天类型
function 函数
item_modifier 物品修饰器
predicate 谓词

pack.mcmeta

另见 Minecraft Wiki 的 pack.mcmeta(资源包) 和 pack.mcmeta(数据包)。

pack.mcmeta 文件 保存资源包或数据包的元数据。对于模组,NeoForge 会自动合成 pack.mcmeta,因此无需再提供这一文件。如果仍需要 pack.mcmeta 文件,可以在上面链接的 Minecraft Wiki 页面中查阅完整规范。

数据生成

数据生成通常简称 datagen,是通过程序生成 JSON 资源文件的方式,用来避免手动编写时繁琐且容易出错的过程。这个名称略有误导性,因为它既适用于数据,也适用于资产。

数据生成通过 Data 运行配置执行,它会与 Client 和 Server 运行配置一同生成。数据生成运行配置会遵循模组生命周期,直到注册表事件触发之后。随后,它会触发某个 GatherDataEvent,你可以在其中以数据提供器的形式注册要生成的对象;接着将这些对象写入磁盘,最后结束进程。

它有两个在物理侧 上区分的子类型:GatherDataEvent.Client 和 GatherDataEvent.Server。GatherDataEvent.Client 可以包含所有需要生成内容的提供器,而 GatherDataEvent.Server 只能包含用于生成数据包条目的提供器。

提示 推荐使用以下两种提供器注册方式之一。第一种是在 GatherDataEvent.Client 中注册所有提供器,然后使用 runClientData 任务生成数据。第二种是在 GatherDataEvent.Client 中注册客户端提供器,在 GatherDataEvent.Server 中注册服务端提供器,分别运行 runClientData 和 runServerData 任务生成。

MDK 默认配置了 clientData,采用第一种方案,因此本文所有示例也采用第一种方式,将全部提供器注册到 GatherDataEvent.Client。

所有数据提供器都扩展 DataProvider 接口,通常需要重写一个方法。以下列出 Minecraft 和 NeoForge 提供的一些值得关注的数据生成器;链接的文章中有辅助方法等更多说明:

类 方法 生成内容 侧 备注
ModelProvider registerModels() 模型、方块状态文件、客户端物品 客户端
LanguageProvider addTranslations() 翻译 客户端 还需要在构造方法中传入语言。
EquipmentAssetProvider registerModels() 盔甲模型资产 客户端
ParticleDescriptionProvider addDescriptions() 粒子定义 客户端
SoundDefinitionsProvider registerSounds() 声音定义 客户端
SpriteSourceProvider gather() 精灵来源/图集 客户端
AdvancementProvider generate() 进度 服务端 需要额外的类才能正常工作,详见链接文章。
LootTableProvider generate() 战利品表 服务端 需要额外的方法和类才能正常工作,详见链接文章。
RecipeProvider buildRecipes(RecipeOutput) 配方 服务端 需要额外的类才能正常工作,详见链接文章。
RecipePrioritiesProvider start() 配方优先顺序 服务端
TagsProvider 的各种子类 addTags(HolderLookup.Provider) 标签 服务端 有多种专门的子类,详见链接文章。
DataMapProvider gather() 数据映射条目 服务端
GlobalLootModifierProvider start() 全局战利品修改器 服务端
DatapackBuiltinEntriesProvider 不适用 数据包内置条目,例如世界生成和伤害类型 服务端 不重写方法,而是在构造方法中的 lambda 内添加条目。详见链接文章。
JsonCodecProvider(抽象类) gather() 带有编解码器的对象 两者 可以扩展它,用于任何带有可编码数据的 codec 的对象。
PackMetadataGenerator add(MetadataSectionType<T>, T) pack.mcmeta 两者

这些提供器都遵循相同模式:首先创建子类,添加要生成的资源;然后在事件处理器 中将提供器添加到事件。以下是使用 RecipeProvider 的示例:

public class MyRecipeProvider extends RecipeProvider {
    public MyRecipeProvider(HolderLookup.Provider registries, RecipeOutput output) {
        super(registries, output);
    }

    @Override
    protected void buildRecipes() {
        // Register your recipes here.
    }

    // The data provider class
    public static class Runner extends RecipeProvider.Runner {

        public Runner(PackOutput output, CompletableFuture<HolderLookup.Provider> registries) {
            super(output, registries);
        }

        @Override
        protected RecipeProvider createRecipeProvider(HolderLookup.Provider registries, RecipeOutput output) {
            return new MyRecipeProvider(registries, output);
        }
    }
}

// In some event handler class
@SubscribeEvent // on the mod event bus
public static void gatherData(GatherDataEvent.Client event) {
    // Data providers should start by calling event.createDatapackRegistryObjects(...)
    // to register their datapack registry objects. This allows other providers
    // to use these objects during their own data generation.

    // From there, providers can generally be registered using event.createProvider(...),
    // which acts as a function that provides the PackOutput and optionally the
    // CompletableFuture<HolderLookup.Provider>.

    // Register the provider.
    event.createProvider(MyRecipeProvider.Runner::new);
    // Other data providers here.

    // If you want to create a datapack within the global pack, you can call
    // DataGenerator#getBuiltinDatapack. From there, you must use the
    // PackGenerator#addProvider method to add any providers to that pack.
    DataGenerator.PackGenerator examplePack = event.getGenerator().getBuiltinDatapack(
        true, // Should always be true.
        "examplemod", // The mod id.
        "example_pack" // The name of the pack.
    );
    
    examplePack.addProvider(output -> ...);
}

事件还提供了一些辅助方法和上下文:

  • event.createDatapackRegistryObjects(...):使用给定的 RegistrySetBuilder 创建并注册 DatapackBuiltinEntriesProvider,并强制此后使用的查找提供器包含数据生成创建的条目。
  • event.createProvider(...):通过 lambda 提供 PackOutput,以及可选的 CompletableFuture<HolderLookup.Provider>,从而注册提供器。
  • event.createBlockAndItemTags(...):注册 TagsProvider<Block> 和 TagsProvider<Item>,其中使用 TagsProvider<Block> 构造 TagsProvider<Item>。
  • event.getGenerator():返回用于注册提供器的 DataGenerator。
  • event.getPackOutput():返回 PackOutput,供部分提供器确定文件输出位置。
  • event.getResourceManager(PackType):返回 ResourceManager,供提供器检查文件是否已经存在。
  • event.getLookupProvider():返回 CompletableFuture<HolderLookup.Provider>,主要供标签和数据生成注册表引用其他可能尚不存在的元素。
  • event.includeDev() 和 event.includeReports():返回 boolean,用于检查是否启用了特定命令行参数(见下文)。

命令行参数

数据生成器可以接受多个命令行参数:

  • --mod examplemod:指定为该模组运行数据生成。NeoGradle 会自动为所属模组 ID 添加此参数;例如,同一项目包含多个模组时,可以自行添加。
  • --output path/to/folder:指定数据生成器的输出文件夹。建议使用 Gradle 的 file(...).getAbsolutePath(),根据相对于项目根目录的路径生成绝对路径。默认值是 file('src/generated/resources').getAbsolutePath()。
  • --existing path/to/folder:指定检查已有文件时应考虑的文件夹。与输出路径一样,建议使用 Gradle 的 file(...).getAbsolutePath()。
  • --existing-mod examplemod:指定检查已有文件时,应考虑给定模组 JAR 文件中的资源。
  • 生成器模式(以下都是布尔参数,不需要额外的值):
    • --includeDev:是否运行开发工具。模组通常不应使用。在运行时通过 GatherDataEvent#includeDev() 检查。
    • --includeReports:是否转储已注册对象的列表。在运行时通过 GatherDataEvent#includeReports() 检查。
    • --all:启用所有生成器模式。

可以在 build.gradle 中添加以下内容,将任意参数加入运行配置:

runs {
    // other run configurations here

    clientData {
        arguments.addAll '--arg1', 'value1', '--arg2', 'value2', '--all' // boolean args have no value
    }
}

例如,要复现默认参数,可以指定:

runs {
    // other run configurations here

    clientData {
        arguments.addAll '--mod', 'examplemod', // insert your own mod id
                '--output', file('src/generated/resources').getAbsolutePath(),
                '--all'
    }
}

来源: NeoForged/Documentation(26.1)。中文翻译依据提交 4a680cdd4d15。