资源
资源是游戏使用的外部文件,但它们不是代码。最典型的资源是纹理,不过 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。