Table of Contents

自定义数据包/资源包资源

前言

虽然说一般不必要,但是建议你先习惯了Fabric模组编写的基础以及json解析,再来钻研这些。走一遍添加配方类型的教程可以让你熟悉json的使用。

而且,本列表中的大多数例子都是处理数据包,但是处理资源包的过程差不多也是相同的。

Reload Listeners

数据包和资源包都是Minecraft模组编写场景中的数据驱动设计的核心,因此要熟练地编写模组,把数据包和资源包调教好是非常重要的一步。

原版有大量的数据和资源包的资源,比如我们所知道的材质(texture)、模型(model),以及在1.16.2被大改过的生物群系(biome),这些都是从数据包、资源包加载并扩展的内容。扩展资源系统的过程来适应你的模组,这是本文的主要话题,首先我们从 ResourceManagerHelper 开始讲起。

所有你所做的与资源有关的东西都需要通过 ResourceManagerHelper 注册,具体来说,其资源类型中的一个是通过 ResourceManagerHelper#get(ResourceType) 注册的,这个“类型”可以是 SERVER_DATA 或者 CLIENT_RESOURCES,分别对应数据包和资源包,或者在开发环境中,分别是指在 resources/assetsresources/data 文件夹中找到的文件(你没想错,你的模组的资源(assets)和数据最初是按照和资源包和数据包相同的方式处理的,所以这也会处理我们放在这里的一切)。

本文的例子主要集中在数据包中,使用 ResourceType.SERVER_DATA,记住处理数据包和资源包的过程是相同的,除了被调用的 ResourceType 不同之外。

注册你的reload listener是通过 ResourceManagerHelper#get(ResourceType).registerReloadListener(IdentifiableResourceReloadListener) 完成的。注册完之后,你的listener不仅会处理资源的初始加载,还会处理后续的重载,即 /reloadF3 + T

  1. public class ExampleMod implements ModInitializer {
  2. ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener([...]);
  3.  
  4. [...]
  5. }

Reload Listeners 2: The Listener

我们已经说完了注册 reload listener 的过程,但是你仍然还是需要一个,这个过程的那一部分会在本段讨论(换句话说,本段将会讨论 SimpleSynchronousResourceReloadListener,然后再讨论async listeners)。

在所有的编程法则中,你应该无法在Java中实例化一个接口,因为其方法太抽象难以调用。然而,模组编写者并不关心实际的编程者觉得怎样,所以我们仍旧这么做。

  1. new SimpleSynchronousResourceReloadListener() {
  2. @Override
  3. public Identifier getFabricId() {
  4. return new Identifier("tutorial", "my_resources");
  5. }
  6.  
  7. @Override
  8. public void apply(ResourceManager manager) {
  9. [...]
  10. }
  11. }

你在这里看到的是将要通过 ResourceManagerHelper 注册的实际上的资源reload listener(技术上讲,是一个资源reload listener,然而这不是唯一的类型,不过是最容易实现的)。如你所见,这里有两个重要的部分:getFabricIdapply 方法。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获取的信息的东西,否则你的模组一到 /reloadF3+T 的时候就有可能崩掉。完成之后,你应该使用 ResourceManager#findResources(String path, Predicate<String> pathPredicate) 来获取包含在你想要的任意文件夹的文件(注意:这个路径是从所有处理过的数据包/资源包地data或者assets文件夹开始的)。这会给你一个所有在指定路径范围内且符合谓词准则(meet the predicate's criteria,你很有可能想要筛选特定的文件类型,例如在本教程中,筛选出所有的 .json)的文件的路径(path)的集合(collection),然后你会遍历这个集合然后对这些路径做些事情。

  1. @Override
  2. public void apply(ResourceManager manager) {
  3. // 在这里清除缓存
  4.  
  5. for(Identifier id : manager.findResources("my_resource_folder", path -> path.endsWith(".json"))) {
  6. try(InputStream stream = manager.getResource(id).getInputStream()) {
  7. // Consume the stream however you want, medium, rare, or well done.
  8. } catch(Exception e) {
  9. TUTORIAL_LOG.error("Error occurred while loading resource json " + id.toString(), e);
  10. }
  11.  
  12. }
  13. [...]
  14. }
  15. }

这里注意两点。首先,所有的资源都必须作为 InputStreams(输入流)处理。这意味着,你需要在处理之后关闭close)这些资源,忘掉这样做的结果就是导致资源泄漏。要确保所有处理过的资源都关闭,最简单的方法是,使用 try-with-resource 语句块来处理,就像上面展示的那样。第二点,注意你没办法获取你处理的原始文件,所以“所有的资源都必须作为 InputStreams 处理”中的“必须”事实上是“必须”而不是“应该”。不过,有很多的解析器(parser)能够从这个输入流中获得你想要的文件,尤其是对于json。

完成之后,你的代码应该看上去像这样。此时,恭喜你已经成功地让你的模组的一部分由数据驱动了!任何包含在数据包或模组数据中的 “my_resource_folder” 文件夹中的所有文件 will get picked up by the this.

  1. public class ExampleMod implements ModInitializer {
  2. ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(new SimpleSynchronousResourceReloadListener() {
  3. @Override
  4. public Identifier getFabricId() {
  5. return new Identifier("tutorial", "my_resources");
  6. }
  7.  
  8. @Override
  9. public void apply(ResourceManager manager) {
  10. // Clear Caches Here
  11.  
  12. for(Identifier id : manager.findResources("my_resource_folder", path -> path.endsWith(".json"))) {
  13. try(IntputStream stream = manager.getResource(id).getInputStream()) {
  14. // Consume the stream however you want, medium, rare, or well done.
  15. } catch(Exception e) (
  16. TUTORIAL_LOG.error("Error occurred while loading resource json " + id.toString(), e);
  17. }
  18. }
  19. }
  20. });
  21. [...]
  22. }