资源元数据
扩展名为 .mcmeta 的文件可以为游戏中的任意资产或数据对象保存 JSON 元数据。它们最常用于定义包的信息和纹理的应用方式;不过,只要在文件名末尾加上 .mcmeta,就可以为任何文件提供元数据(例如,apple.png 的资源元数据文件是 apple.png.mcmeta)。
提示 数据包内的 JSON 对象也可以拥有资源元数据,但这些元数据不会被使用,因为相应信息可以直接放在 JSON 文件本身中。
元数据节
JSON 元数据对象分为多个节,键表示节的类型,值则是该节的数据。在代码中,这由 MetadataSectionType 表示,它接受一个 String 键以及用于序列化值的 Codec。
原版和 NeoForge 目前提供以下元数据节:
| 节 | 类 | 所在文件 | 用途 |
|---|---|---|---|
pack |
PackMetadataSection |
pack.mcmeta |
包的信息 |
features |
FeatureFlagsMetadataSection |
pack.mcmeta |
启用实验性功能 |
filter |
ResourceFilterSection |
pack.mcmeta |
过滤在此包之后应用的包中的文件 |
overlays / neoforge:overlays |
OverlayMetadataSection / GeneratingOverlayMetadataSection |
pack.mcmeta |
在给定条件下覆盖主包的子包 |
language |
LanguageMetadataSection |
pack.mcmeta |
额外语言,仅用于资源包 |
animation |
AnimationMetadataSection |
.png.mcmeta(纹理) |
纹理图集中的动画纹理 |
gui |
GuiMetadataSection |
.png.mcmeta(纹理) |
GUI 精灵纹理 |
texture |
TextureMetadataSection |
.png.mcmeta(纹理) |
纹理 |
villager |
VillagerMetadataSection |
.png.mcmeta(纹理) |
村民帽子的可见性 |
提示 模组的主
pack.mcmeta不需要PackMetadataSection,因为 NeoForge 会自动合成这一部分。不过,通过AddPackFindersEvent模组总线事件 添加的内置包必须包含它。
要获取某个元数据节中的数据,需要先通过 ResourceManager 获得该文件的 Resource,调用 Resource#metadata 获取 ResourceMetadata,再向 ResourceMetadata#getSection 传入 MetadataSectionType。
// For some `ResourceManager` resourceManager
// Get the metadata for the topmost resource
Optional<AnimationMetadataSection> waterStillMetadata = resourceManager.getResource(
// Identifier must specify exact path to the backing resource resource.
Identifier.fromNamespaceAndPath("minecraft", "textures/block/water_still.png")
).flatMap(resource -> {
try {
// Get the metadata for the resource if present, otherwise an empty optional.
return resource.metadata().getSection(AnimationMetadataSection.TYPE);
} catch (IOException e) {
// If an exception is thrown trying to read the metadata file.
return Optional.empty();
}
});
// Get the metadata of every resource for the identifier
List<AnimationMetadataSection> waterStillsMetadata = resourceManager.getResourceStack(
// Identifier must specify exact path to the backing resource resource.
Identifier.fromNamespaceAndPath("minecraft", "textures/block/water_still.png")
).map(resource -> {
try {
// Get the metadata for the resource if present, otherwise an empty optional.
return resource.metadata().getSection(AnimationMetadataSection.TYPE);
} catch (IOException e) {
// If an exception is thrown trying to read the metadata file.
return Optional.empty();
}
}).filter(Optional::isPresent).map(Optional::get);
客户端的 ResourceManager 可以通过 Minecraft#getResourceManager 获取。服务端则不会在 MinecraftServer#reloadResources 之外暴露 ResourceManager,因此只能在 PreparableReloadListener 中使用它。
自定义节
创建自定义元数据节只需要定义对象、用于序列化和反序列化该对象的 Codec,以及用于从 mcmeta 读取它的 MetadataSectionType。
public record ExampleMetadataSection(String value) {
public static final Codec<ExampleMetadataSection> CODEC = Codec.STRING.xmap(ExampleMetadataSection::new, ExampleMetadataSection::value);
public static final MetadataSectionType<ExampleMetadataSection> TYPE = new MetadataSectionType<>(
// The key for the section in the .mcmeta, should be prefixed with your mod id.
"examplemod:example_section",
// The codec to serialize and deserialize the section data.
CODEC
);
}
随后便可以将该元数据节添加到对象的 .mcmeta 文件中:
// In 'assets/examplemod/textures/block/example_block.png.mcmeta'
{
"examplemod:example_section": "Hello world!"
}
并像其他元数据节一样,通过对应类型获取它。
数据生成
原版通过 PackMetadataGenerator 为 pack.mcmeta 提供数据生成支持。其他文件的元数据需要使用自定义 DataProvider。
PackMetadataGenerator
PackMetadataGenerator 用于生成模组或其内置子包的 pack.mcmeta。通过 add 方法添加元数据节,传入 MetadataSectionType 及其值即可。PackMetadataGenerator 还提供 forFeaturePack,用于生成带有 PackMetadataSection,并可选带有 FeatureFlagsMetadataSection 的 pack.mcmeta:
@SubscribeEvent // on the mod event bus
public static void gatherData(GatherDataEvent.Client event) {
event.createProvider(PackMetadataGenerator::new)
// Can chain multiple `add` calls.
.add(
// The metadata section to add.
LanguageMetadataSection.TYPE,
// The value of the metadata section.
Map.of(
"hello_world",
new LanguageInfo(
"Programmer's Land",
"Hello world!",
false
)
)
);
}
其他资源元数据
其他文件的资源元数据需要使用自定义 DataProvider:
public class ResourceMetadataProvider implements DataProvider {
private final PackOutput output;
private final Map<Path, ResourceMetadata> metadata;
public ResourceMetadataProvider(PackOutput output) {
this.output = output;
this.metadata = new HashMap<>();
}
protected void add() {
// Add metadata here.
this.textureMetadata(Identifier.fromNamespaceAndPath(
"examplemod", "block/example_texture"
))
// Can chain multiple `add` calls.
.add(
// The metadata section to add.
TextureMetadataSection.TYPE
// The value of the metadata section.
new TextureMetadataSection(
true, TextureMetadataSection.DEFAULT_CLAMP, MipmapStrategy.AUTO, TextureMetadataSection.DEFAULT_ALPHA_CUTOFF_BIAS
)
).add(
ExampleMetadataSection.TYPE,
new ExampleMetadataSection("Hello world!")
);
}
protected ResourceMetadata textureMetadata(Identifier resource) {
return this.metadata(
PackOutput.Target.RESOURCE_PACK,
"textures",
resource.withSuffix(".png")
);
}
protected ResourceMetadata metadata(PackOutput.Target type, String directory, Identifier resource) {
return this.metadata.computeIfAbsent(
this.output.createPathProvider(type, directory).file(resource, "mcmeta"),
p -> new ResourceMetadata()
);
}
@Override
public CompletableFuture<?> run(CachedOutput cache) {
Executor executor = Util.backgroundExecutor().forName("serializeMetadata");
return CompletableFuture.allOf(
this.metadata.entrySet().stream().map(entry -> CompletableFuture.runAsync(() -> {
JsonObject result = new JsonObject();
entry.getValue().sections().forEach((type, data) -> result.add(type, data.get()));
return result;
}, executor).thenComposeAsync(json -> DataProvider.saveStable(cache, json, entry.getKey()), executor)
).toArray(CompletableFuture[]::new)
);
}
@Override
public final String getName() {
return "Resource Metadata";
}
public record ResourceMetadata(Map<String, Supplier<JsonElement>> sections) {
public ResourceMetadata() {
this(new HashMap<>());
}
public <T> ResourceMetadata add(MetadataSectionType<T> type, T value) {
this.sections.put(type.name(), () -> type.codec().encodeStart(JsonOps.INSTANCE, value).getOrThrow(IllegalArgumentException::new).getAsJsonObject());
return this;
}
}
}
然后可以将其添加到 GatherDataEvent:
@SubscribeEvent // on the mod event bus
public static void gatherData(GatherDataEvent.Client event) {
event.createProvider(ResourceMetadataProvider::new);
}
来源: NeoForged/Documentation(26.1)。中文翻译依据提交
4a680cdd4d15。