第十六章:AssetManager接口介绍

下面的文章,是我早期阅读jME3源代码时整理的笔记,主要介绍了 AssetManager 中的主要接口。结合上一篇文章,可以深入了解AssetManager的具体用法。

核心组件介绍

AssetManager

这是JME3资源管理器的核心接口,它提供了统一的方式来管理各种资源。

(1) 注册资源加载器

public void registerLoader(Class loaderClass, String ... extensions)

根据后缀名来注册资源加载器。没有注册过的资源类型是无法被AssetManager识别的。
例:

assetManager.registerLoader(AWTLoader.class, "jpg");
assetManager.registerLoader(WAVLoader.class, "wav");

(2) 注册资源定位器

public void registerLocator(String rootPath, Class locatorClass)

注册资源位置,以及定位器。加载资源的时候,AssetManager会到注册过的位置来查找资源。例:

assetManager.registerLocator("/", ClasspathLocator.class);
assetManager.registerLocator("res/models.zip", ZipLocator.class);

(3) 定位资源位置

public AssetInfo locateAsset(AssetKey<?> key)

这个方法将根据AssetKey中的路径,按顺序遍历所有注册过的资源位置,直到查询到了一个匹配的资源为止。

如果找到了资源,就会返回一个AssetInfo对象,否则将返回null。
AssetInfo对象包含了资源的InputStream,我们可以直接解析资源数据,也可以通过AssetLoader来加载资源。

(4) 加载资源

public <T> T loadAsset(AssetKey<T> key);

这个方法用于加载资源,具体的加载过程我们后面再详细分析。
AssetKey中包含了资源的后缀名,若该资源类型的AssetLoader没有在AssetManager中注册过,程序就会抛出异常。

AssetKey

AssetKey是用来从缓存中寻找资源的钥匙,可以使用资源路径来构造一个AssetKey。

AssetKey = new AssetKey("Common/MatDefs/Misc/Unshaded.j3md");

一旦资源路径设置完成后,它的值就无法改变了,因为AssetKey没有提供任何方法来修改资源路径。
AssetKey会自动帮我们计算资源的后缀名、文件夹。

(1) 资源全路径

public String getName()

返回资源的全路径。

"Common/MatDefs/Misc/Unshaded.j3md"

(2) 资源文件夹

public String getFolder()

返回资源所在的文件夹。

"Common/MatDefs/Misc/"

(3) 资源后缀名
后缀名不分大小写。

public String getExtension()

例:

"j3md"

(4) 资源的缓存类型

public Class<? extends AssetCache> getCacheType()

资源加载的同时,会在缓存中保存一份,防止直接被GC回收。
AssetKey默认使用SimpleCacheType,这意味着直接使用JME3自带的AssetKey的话,我们就需要自己手动去释放缓存。。

(5) 资源加载后的处理器

public Class<? extends AssetProcessor> getProcessorType()

默认为null
jpg、tga等图片资源作为纹理加载时,首先会变成一个Image对象。通过TextrueProcesser处理后才会变成一个程序中所需要的Texture对象。

AssetLocator

AssetLocator是一个接口,用于从指定位置查询资源信息。

(1)资源根目录

public void setRootPath(String rootPath)

资源定位器允许我们在指定一个资源加载的根路径。
定位资源的时候,调用AssetKey的getName()方法可以获得资源在这个根目录中的相对位置。

举个例子:注册一个ZipLocatoer,设置资源根目录为"res/models.zip"。查找资源"img/avatar.png"的时候,这个ZipLocatoer就会在models.zip文件找去查询img/avatar.png文件。

(2)定位资源

public AssetInfo locate(AssetManager manager, AssetKey key)

在AssetLocator定位了资源位置后,将会返回一个AssetInfo对象。

AssetInfo

AssetInfo是AssetLocater定位资源后返回的结构,其中提供了指定资源的InputStream。

(1)资源数据

public abstract InputStream openStream();

AssetInfo是一个抽象类,调用openStream()方法即可获得资源的InputStream,通过这个InputStream就可以读取实际的资源数据了。

(2)getKey

public AssetKey getKey()

通过这个方法可以获得资源的AssetKey

(3)getManager

public AssetManager getManager()

通过这个方法可以获得加载该资源的AssetManager

AssetLoader

AssetLoader用于加载指定类型的资源,资源类型通过文件的后缀名来匹配。
AssetLoader接口中只有一个用于加载的接口:

public Object load(AssetInfo assetInfo) throws IOException;

AssetLoader将调用AssetInfo的openStream()方法来获得资源的输入流,并将数据解析成一个我们所需要的对象。

AssetLoader和AssetLocator

AssetManager加载资源前,首先要注册各种AssetLoader和AssetLocator,否则AssetManager将不知道怎么去加载资源。

注册AssetLoader

AssetLoader由AssetManger管理,加载资源时,AssetManager通过后缀名来匹配AssetLoader。
例如:

assetManager.loadAsset("Interfaces/background.jpg");

当这段代码执行时,assetManager会根据后缀名"jpg"去查找AssetLoader实例。如果AssetManager中没有匹配"jpg"后缀的AssetLoader,那么这个资源就在加载不了了。幸好JME3中自带了一些常用类型的资源加载器,并且默认在启动时就给它们注册了,如下:

assetManager.registerLoader(AWTLoader.class, "jpg");  

AssetManager使用 Map<String, AssetLoader> 来保存资源后缀名与AssetLoader之间的映射管理。如果2个AssetLoader都注册了同样的后缀名,那么后注册的AssetLoader会挤掉先定义的AssetLoader。

注册AssetLocator

JME3允许你从不同的位置加载资源,诸如:

这些都是通过AssetLocator实现的。

初学JME3时,我们一般是在项目中新建一个assets源码文件夹(source folder),然后在这个文件夹下创建Interface、Model、Material等包(package),然后再需要使用的资源放在这些目录下。加载这些资源时,其实是ClasspathLocater在起作用。

如果你不满足于这种模式,想使用另外地方的资源,那么可以尝试如下的方式:

assetManager.registerLocator("res", FileLocator.class);//注册程序相对路径res
assetManager.registerLocator("C:/", FileLocator.class);// 注册绝对路径(Windows)
assetManager.registerLocator("/usr/yan/myassets/", FileLocator.class);// 注册绝对路径(Linux)
assetManager.registerLocator("/", ClasspathLocator.class);  

AssetManager把所有注册过的AssetLocator保存在一个 List<AssetLocater> 中,使用时按从前往后的顺序查找。如果2个AssetLocator都能找到相同路径的资源,那么先注册的AssetLocator会被使用,后面的会被忽略。

默认注册

JME3 Desktop应用启动时,默认注册了下面这些AssetLoader和AssetLocater。

LOCATOR / com.jme3.asset.plugins.ClasspathLocator
LOADER com.jme3.texture.plugins.AWTLoader : jpg, bmp, gif, png, jpeg
LOADER com.jme3.audio.plugins.WAVLoader : wav
LOADER com.jme3.audio.plugins.OGGLoader : ogg
LOADER com.jme3.cursors.plugins.CursorLoader : ani, cur, ico
LOADER com.jme3.material.plugins.J3MLoader : j3m
LOADER com.jme3.material.plugins.J3MLoader : j3md
LOADER com.jme3.material.plugins.ShaderNodeDefinitionLoader : j3sn
LOADER com.jme3.font.plugins.BitmapFontLoader : fnt
LOADER com.jme3.texture.plugins.DDSLoader : dds
LOADER com.jme3.texture.plugins.PFMLoader : pfm
LOADER com.jme3.texture.plugins.HDRLoader : hdr
LOADER com.jme3.texture.plugins.TGALoader : tga
LOADER com.jme3.export.binary.BinaryImporter : j3o
LOADER com.jme3.export.binary.BinaryImporter : j3f
LOADER com.jme3.scene.plugins.OBJLoader : obj
LOADER com.jme3.scene.plugins.MTLLoader : mtl
LOADER com.jme3.scene.plugins.ogre.MeshLoader : meshxml, mesh.xml
LOADER com.jme3.scene.plugins.ogre.SkeletonLoader : skeletonxml, skeleton.xml
LOADER com.jme3.scene.plugins.ogre.MaterialLoader : material
LOADER com.jme3.scene.plugins.ogre.SceneLoader : scene
LOADER com.jme3.scene.plugins.blender.BlenderModelLoader : blend
LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, glsl, glsllib  

上面的数据来自JME3 Desktop自带的配置文件:com/jme3/asset/Desktop.cfg

对于Android来说,JME3 Android启动时还加载了drawable文件夹和asset文件夹的Locator,具体是哪些类就不列出来了。

AssetConfig

除了在代码里面直接调用AssetManager的方法来注册,我们还可以利用配置文件来进行注册。

配置文件的格式就和上面的代码一样,AssetConfig类专门用于解析这种配置文件。然而实际上我们在编程的时候几乎不上AssetConfig,只要注意配置的方式就行了。我们这里主要谈谈怎么使用配置文件。

配置文件的使用有3个关键点:

  1. 配置文件必须放在工程的classpath之下,否则无法识别。
  2. 要在AppSettings中添加参数"AssetConfigURL",指定配置文件的加载路径。
  3. 如果使用自定义配置文件,那么jme3默认的配置文件就不会生效了!

你可以在自己的资源目录夹下面创建一个cfg文件,格式和内容可参考JME3自带的cfg文件,然后采用如下方式在程序启动时加载它:

public static void main(String[] args) {
    AppSettings settings = new AppSettings(false);
    // 设置文件路径
    settings.set("AssetConfigURL", "your/asset/path/Assets.cfg");
    // 启动程序
    SimpleApplication game = new MyGame();
    game.setSettings(settings);
    game.start();
}  

AssetManager初始化时,会从AppSettings中读取"AssetConfigURL"这个参数,然后再读取配置文件。如果找不到配置文件的话,就会使用默认配置文件。源码如下:

private void initAssetManager(){
    if (settings != null){
        String assetCfg = settings.getString("AssetConfigURL");
        if (assetCfg != null){
            URL url = null;
            try {
                url = new URL(assetCfg);
            } catch (MalformedURLException ex) {
            }
            if (url == null) {
                url = Application.class.getClassLoader().getResource(assetCfg);
                if (url == null) {
                    logger.log(Level.SEVERE, "Unable to access AssetConfigURL in asset config:{0}", assetCfg);
                    return;
                }
            }
            assetManager = JmeSystem.newAssetManager(url);
        }
    }
    if (assetManager == null){
        assetManager = JmeSystem.newAssetManager(
                Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/Desktop.cfg"));
    }
}

资源加载流程

JME3在加载资源的过程中,AssetManager会先根据AssetKey去缓存中查找资源,如果找得到的话就直接使用,找不到的话才会去AssetLocator注册的路径下搜索。

具体加载的流程是这样的:

在缓存中查找资源

检查AssetCache中的资源,若找不到就进行下一步,若找到就直接返回了。

AssetCache cache = handler.getCache(key.getCacheType());
Object obj = cache != null ? cache.getFromCache(key) : null;

匹配AssetLoader

根据资源后缀名来匹配AssetLoader,找不到的话抛出异常。

AssetLoader loader = handler.aquireLoader(key);

资源定位

遍历所有注册过的AssetLocater,返回AssetInfo,找不到的话会抛出异常。

AssetInfo info = handler.tryLocate(key);

加载资源

调用AssetLoader的load(AssetInfo info)方法,返回资源对象。

obj = loader.load(info);

后续处理

AssetLoader返回Object类型的对象,经过AssetProcessor处理后,转换成实际的对象类型。
比如AWTLoader读取图片数据后,返回Image类型的对象。再通过TextureProcessor处理后才变成Texture对象。

AssetProcessor proc = handler.getProcessor(key.getProcessorType());
if (proc != null){
    // do processing on asset before caching
    obj = proc.postProcess(key, obj);
}

保存到缓存

资源加载结束后,对象会保存到缓存中。

if (cache != null){
    // At this point, obj should be of type T
    cache.addToCache(key, (T) obj);
}