第十二章:自定义资产路径

默认情况下,JME3 程序总是从 MyGame/assets 目录中加载资产。但这并不是唯一的选择,还可以在 AssetManager 中配置其他资产路径。

本文将介绍如何使用 jME3 提供的 AssetLocator 来配置自定义资产路径。后续章节中我会继续介绍 AssetLocator 的原理,并通过案例来演示如何实现从加密资源包中解析数据。在了解原理后,你可以根据具体的算法来实现自己的 AssetLocator。

AssetLocator

在介绍具体的做法之前,先考虑一下可能存在的情况:

  1. 游戏允许用户上传自定义模型和皮肤,这些文件不可能打包到游戏的可执行文件中,应该怎么加载它们?
  2. 我们的项目计划使用内容分发网络(CDN)和云存储(cloud storage)服务来管理游戏资产,这样用户只需要下载一个很小的客户端。应该如何访问网络服务器上的游戏资产呢?
  3. 我想和魔兽世界一样把游戏资产打成一个加密资源包,避免用户篡改数据,同时避免同行窃取游戏资产。应该如何从这些加密资源包中加载数据呢?

jME3 设计了抽象的资产定位器(AssetLocator),用于扩展支持上述所有情况。jME3的核心模块中提供了 5 种不同类型的 AssetLocator 实现。

  • com.jme3.asset.plugins.ClasspathLocator 用于加载 classpath 中的资产,包括 jar 文件。
  • com.jme3.asset.plugins.FileLocator 用于加载文件系统中的资产。
  • com.jme3.asset.plugins.HttpZipLocator 用于加载网站上的zip压缩包。
  • com.jme3.asset.plugins.UrlLocator 用于加载网络资产。
  • com.jme3.asset.plugins.ZipLocator 用于加载 zip 压缩包中的资产。

通过调用 AssetManager 中的 registerLocator(String rootPath, Class locatorClass) 方法,即可注册资产路径。这个方法有两个参数:其一是资产的根目录,其二是 AssetLocator 类型

ClasspathLocator

jME3程序能够从项目的 assets 目录中加载资产,是因为 SDK 已经把 assets 目录配置成了项目的 classpath,并且在 JME3 程序初始化时就执行下列语句:

assetManager.registerLocator("/", ClasspathLocator.class);

因此,AssetManager 才可以从 assets 目录中加载资产,还可以直接读取 jme3-core.jar 中的内置资产。

回顾 第五章:在其他IDE中管理资产,我在 Eclipse 中把 assets 目录配置成了 Build Path,在 IDEA 中把 assets 目录配置成了 Resources Root,实质上就是把它们添加到项目的 classpath 中,这样 AssetManager 就可以通过 ClasspathLocator 加载这些目录下的资产。

对于 Maven、Gradle 等项目来说, src/main/resources 也属于 classpath,因此存放其中的资产可以被加载。

手动打包jar文件

如果把资产打包成 assets.jar 文件,并其添加到项目的依赖库中,这样 ClasspathLocator 还可以直接加载 assets.jar 中的资产文件。

做法很简单:jar 文件本身使用的 zip 压缩,先把资源打包成 zip 文件,然后把后缀名改成 jar 即可。

注意:只压缩 assets 目录内的文件,不要压缩 assets 目录本身。

压缩好的 assets.zip 文件结构如下:

把后缀名改成 jar,然后添加到项目的依赖库中。下图是在 Eclispe + Gradle 环境中的配置截图。

然后就可以在 JME3 使用 Models/Monkey/monkey.j3o 加载模型, ClasspathLocator 会在幕后为我们做好一切工作。

一般来说,资产打包是由编译脚本自动化执行的。本文只是演示其原理,实际开发时请不要这样手动打包。

FileLocator

FileLocator 可以直接访问操作系统的文件目录,调用 AssetManager 的 registerLocator 方法来注册资产根目录即可。

例如:游戏资产保存在 F:\assets 目录中,目录结构如下:

F:\assets\Interface
F:\assets\MatDefs
F:\assets\Materials
F:\assets\Models
F:\assets\Models\Monkey
F:\assets\Models\Monkey\monkey.j3o
F:\assets\Scenes
F:\assets\Shaders
F:\assets\Sounds
F:\assets\Textures
F:\assets\Textures\Monkey
F:\assets\Textures\Monkey\DiffuseMap.png

在JME3中加载资源的代码如下:

@Override
public void simpleInitApp() {
    // 配置文件目录
    assetManager.registerLocator("F:\\assets", FileLocator.class);
    
    // 加载j3o模型
    Spatial model = assetManager.loadModel("Models/Monkey/monkey.j3o");
    rootNode.attachChild(model);

    // 添加光源
    // ..
}

在实际开发中,不建议像这样使用绝对路径,而应该尽量使用相对路径来加载资产。

例如,可以在工程目录下创建一个 res 目录,用来存放游戏资产,然后通过 FileLocator 来管理它。

@Override
public void simpleInitApp() {
    // 配置文件目录
    assetManager.registerLocator("res", FileLocator.class);
    
    // 加载j3o模型
    Spatial model = assetManager.loadModel("Models/Monkey/monkey.j3o");
    rootNode.attachChild(model);

    // 添加光源
    // ..
}

事实上,可以利用 FileLocator 来配置 assets 目录,这样在 Eclipse、IDEA等开发环境中就不需要把 assets 目录添加到 classpath 中了。

ZipLocator

ZipLocator 利用 Java 语言自带的 ZIP 算法实现了文件解压,JME3 可以通过 ZipLocator 直接从 zip 压缩文件中加载资产。

例如:把资产文件打包成 zip 文件,例如: assets.zip 文件结构如下:

assets.zip 放到 JME3 工程根目录下,然后在 AssetManager 中注册 assets.zip 文件所在的相对路径

@Override
public void simpleInitApp() {
    // 配置文件目录
    assetManager.registerLocator("assets.zip", ZipLocator.class);
    
    // 加载j3o模型
    Spatial model = assetManager.loadModel("Models/Monkey/monkey.j3o");
    rootNode.attachChild(model);

    // 添加光源
    // ..
}

当然,你也可以使用绝对路径,但我不建议你这么做。

@Override
public void simpleInitApp() {
    // 配置文件目录
    assetManager.registerLocator("D:\\WORKSPACE\\jME3Projects\\MyGame\\assets.zip", ZipLocator.class);
    
    // 加载j3o模型
    Spatial model = assetManager.loadModel("Models/Monkey/monkey.j3o");
    rootNode.attachChild(model);

    // 添加光源
    // ..
}

HttpZipLocator

HttpZipLocator 与 ZipLocator 的用法几乎完全相同,只不过它是通过 HTTP 协议去访问网上的资源包。

假设我将 assets.zip 上传到网站上,提供静态资源路径 http://www.jmecn.net/examples/assets.zip 供用户下载,那么在 JME3 程序中就可以这样做:

@Override
public void simpleInitApp() {
    // 配置文件目录
    assetManager.registerLocator("http://www.jmecn.net/examples/assets.zip",
            HttpZipLocator.class);
    
    // 加载模型
    Spatial model = assetManager.loadModel("Models/Monkey/monkey.j3o");
    rootNode.attachChild(model);

    // 添加光源
    // ..
}

UrlLocator

UrlLocator 的用途与 FileLocator 相似,只不过它是通过 URL 来定位网络资产目录。

假设我将 F:\\assets 目录中的内容上传到网站上,提供静态路径 http://www.jmecn.net/examples/assets/ 供用户访问,那么在 JME3 程序中就可以这样做:

@Override
public void simpleInitApp() {
    // 配置文件目录
    assetManager.registerLocator("http://www.jmecn.net/examples/assets/",
            UrlLocator.class);
    
    // 加载模型
    Spatial model = assetManager.loadModel("Models/Monkey/monkey.j3o");
    rootNode.attachChild(model);

    // 添加光源
    // ..
}

在这种方式下,用户应该可以通过超链接 http://www.jmecn.net/examples/assets/Models/Monkey/monkey.j3o 直接下载对应的模型文件。

多目录

AssetManager 允许开发者注册多个资产目录,它会按照注册的顺序来加载资产,这个特性很有用。通过这种方式非常容易实现用户自定义皮肤等功能,还可以对不同版本的资产包进行排序。

在项目中定义两个资产根目录,assets 目录存放游戏本身的资产,mod 目录存放用户文件。先注册 mod 再注册 assets,AssetManager 就会先去加载用户文件;若文件不存在,才会去 assets 目录中加载文件。

@Override
public void simpleInitApp() {
    // 配置文件目录
    assetManager.registerLocator("mod", FileLocator.class);
    assetManager.registerLocator("assets", FileLocator.class);
    
    // 加载j3o模型
    Spatial model = assetManager.loadModel("Models/Monkey/monkey.j3o");
    rootNode.attachChild(model);

    // 添加光源
    // ..
}

需要注意的是,JME3 在初始化时就已经执行了 assetManager.registerLocator("/", ClasspathLocator.class)。在上述规则下,它的顺位比所有 AssetLocator 都靠前。

如果你希望把其他资产目录排到 classpath 之前,先调用 unregisterLocator 方法取消注册 classpath,然后按自己期望的顺序添加 AssetLocator,再注册 classpath 即可。

代码如下:

    // 取消注册 classpath
    assetManager.unregisterLocator("/", ClasspathLocator.class);

    // 注册mod资产目录
    assetManager.registerLocator("mod", FileLocator.class);
    
    // 重新注册 classpath
    assetManager.registerLocator("/", ClasspathLocator.class);