====== 自定义数据包/资源包资源 ======
===== 前言 =====
虽然说一般不必要,但是建议你先习惯了Fabric模组编写的基础以及json解析,再来钻研这些。走一遍[[zh_cn:tutorial:recipes|添加配方类型]]的教程可以让你熟悉json的使用。
而且,本列表中的大多数例子都是处理数据包,但是处理资源包的过程差不多也是相同的。
===== Reload Listeners =====
数据包和资源包都是Minecraft模组编写场景中的数据驱动设计的核心,因此要熟练地编写模组,把数据包和资源包调教好是非常重要的一步。
原版有大量的数据和资源包的资源,比如我们所知道的材质(texture)、模型(model),以及在1.16.2被大改过的生物群系(biome),这些都是从数据包、资源包加载并扩展的内容。扩展资源系统的过程来适应你的模组,这是本文的主要话题,首先我们从 ''ResourceManagerHelper'' 开始讲起。
所有你所做的与资源有关的东西都需要通过 ''ResourceManagerHelper'' 注册,具体来说,其资源类型中的一个是通过 ''ResourceManagerHelper#get(ResourceType)'' 注册的,这个“类型”可以是 ''SERVER_DATA'' 或者 ''CLIENT_RESOURCES'',分别对应数据包和资源包,或者在开发环境中,分别是指在 ''resources/assets'' 和 ''resources/data'' 文件夹中找到的文件(你没想错,你的模组的资源(assets)和数据最初是按照和资源包和数据包相同的方式处理的,所以这也会处理我们放在这里的一切)。
本文的例子主要集中在数据包中,使用 ''ResourceType.SERVER_DATA'',记住处理数据包和资源包的过程是相同的,除了被调用的 ''ResourceType'' 不同之外。
注册你的reload listener是通过 ''ResourceManagerHelper#get(ResourceType).registerReloadListener(IdentifiableResourceReloadListener)'' 完成的。注册完之后,你的listener不仅会处理资源的初始加载,还会处理后续的重载,即 ''/reload'' 和 ''F3 + T''。
public class ExampleMod implements ModInitializer {
ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener([...]);
[...]
}
===== Reload Listeners 2: The Listener =====
我们已经说完了注册 reload listener 的过程,但是你仍然还是需要//写//一个,这个过程的那一部分会在本段讨论(换句话说,本段将会讨论 ''SimpleSynchronousResourceReloadListener'',然后再讨论async listeners)。
在所有的编程法则中,你应该无法在Java中实例化一个接口,因为其方法太抽象难以调用。然而,模组编写者并不关心实际的编程者觉得怎样,所以我们仍旧这么做。
new SimpleSynchronousResourceReloadListener() {
@Override
public Identifier getFabricId() {
return new Identifier("tutorial", "my_resources");
}
@Override
public void apply(ResourceManager manager) {
[...]
}
}
你在这里看到的是将要通过 ''ResourceManagerHelper'' 注册的实际上的资源reload listener(技术上讲,是//一个//资源reload listener,然而这不是唯一的类型,不过是最容易实现的)。如你所见,这里有两个重要的部分:''getFabricId'' 和 ''apply'' 方法。''getFabricId'' 方法是这两个中间相似的,你只需要返回一个独一无二的标识符来让 reload listener 考虑,没什么稀奇的。然而,apply方法是主角。
''apply'' 方法为你提供一个 ''ResourceManager'',有了它,就可以在注册表的资源类型的限制范围内加载任何你想加载的数据(记住,SERVER_DATA 只能访问数据包,CLIENT_RESOURCES 只能访问资源包)。你对resource manager做的事情取决于你, if you are making some aspect of your mod data-driven then you probably want to read from a specific //folder//. 这样做很简单,但是需要多留意一点。
首先,你会需要清除或者准备更新任何存储你准备去通过这个manager获取的信息的东西,否则你的模组一到 ''/reload'' 或 ''F3+T'' 的时候就有可能崩掉。完成之后,你应该使用 ''ResourceManager#findResources(String path, Predicate pathPredicate)'' 来获取包含在你想要的任意文件夹的文件(注意:这个路径是从所有处理过的数据包/资源包地data或者assets文件夹开始的)。这会给你一个所有在指定路径范围内且符合谓词准则(meet the predicate's criteria,你很有可能想要筛选特定的文件类型,例如在本教程中,筛选出所有的 ''.json'')的文件的路径(path)的集合(collection),然后你会遍历这个集合然后对这些路径做些事情。
@Override
public void apply(ResourceManager manager) {
// 在这里清除缓存
for(Identifier id : manager.findResources("my_resource_folder", path -> path.endsWith(".json"))) {
try(InputStream stream = manager.getResource(id).getInputStream()) {
// Consume the stream however you want, medium, rare, or well done.
} catch(Exception e) {
TUTORIAL_LOG.error("Error occurred while loading resource json " + id.toString(), e);
}
}
[...]
}
}
这里注意两点。首先,所有的资源都必须作为 ''InputStreams''(输入流)处理。这意味着,你需要在处理之后//关闭//(//close//)这些资源,忘掉这样做的结果就是导致资源泄漏。要确保所有处理过的资源都关闭,最简单的方法是,使用 try-with-resource 语句块来处理,就像上面展示的那样。第二点,注意你没办法获取你处理的原始文件,所以“所有的资源都必须作为 ''InputStreams'' 处理”中的“必须”事实上是“必须”而不是“应该”。不过,有很多的解析器(parser)能够从这个输入流中获得你想要的文件,尤其是对于json。
完成之后,你的代码应该看上去像这样。此时,恭喜你已经成功地让你的模组的一部分由数据驱动了!任何包含在数据包或模组数据中的 "my_resource_folder" 文件夹中的所有文件 will get picked up by the this.
public class ExampleMod implements ModInitializer {
ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(new SimpleSynchronousResourceReloadListener() {
@Override
public Identifier getFabricId() {
return new Identifier("tutorial", "my_resources");
}
@Override
public void apply(ResourceManager manager) {
// Clear Caches Here
for(Identifier id : manager.findResources("my_resource_folder", path -> path.endsWith(".json"))) {
try(IntputStream stream = manager.getResource(id).getInputStream()) {
// Consume the stream however you want, medium, rare, or well done.
} catch(Exception e) (
TUTORIAL_LOG.error("Error occurred while loading resource json " + id.toString(), e);
}
}
}
});
[...]
}