====== 自定义数据包/资源包资源 ====== ===== 前言 ===== 虽然说一般不必要,但是建议你先习惯了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); } } } }); [...] }