H5游戏中的资源模式

背景

在使用tinyjs开发金币庄园游戏时,发现tinyjs对资源加载和资源生命周期的管理上比较分散,而且tinyjs并未开源,在线上出现加载问题时,不能很好的通过查看源码来协助分析问题,于是,按照白鹭引擎的资源加载管理模式,针对tinyjs仿写了金币庄园游戏内资源管理的模块,适应于tinyjs。

核心功能点

  • 支持资源编辑(使用白鹭引擎的资源编辑器)
  • 支持json、text、图片等形式的加载以及二进制模式加载
  • 支持资源分组加载、资源加载失败重试、资源多线程加载,资源分组优先级
  • 资源缓存,销毁等生命周期管理
  • 支持纹理合集的加载和解析

基本使用方式

相关类的结构

  • 图解:

    RES是面向用户的类(图中蓝色块),大部分情况我们只使用RES提供的这些静态方法就足够了。RES通过ResourceLoader来加载一个队列,加载完成的每一项资源都通过预先注册的各种Analyzer类解析,然后放入该analyzer的缓存器中,RES集中提供访问入口。

    这里面有一个SheetAnalyzer的类,专门用于处理纹理合集的加载和缓存。一个纹理合集的资源包含一个json配置和一张合图,在资源编辑器中只保留json配置,然后SheetAnalyzer加载时,会分别加载这两个文件。访问合图中的某个子图时,可以直接通过RES.getRes(subKey)的方式访问,sheetAnalyzer对象会遍历缓存来寻找该子图。

    ImageAnalyzer专门处理图片的加载和解析,加载时会使用ImageLoader,这个类优先尝试采用blob的格式加载图片,这样可以避免加载时的解码过程,提升加载效率。如果不支持blob加载,则会使用image.src形式加载。

    RES.setMaxLoadThread(thread), RES.setMaxRetryTimes(retry)分别用来设置最大同时加载线程数和某项资源加载失败时重试加载的次数。

  • 资源从加载到缓存的基本过程:

    首先,ResourceLoader会根据group的优先级(priority)来确定加载的资源的队列顺序,优先级越高,越在队列前面,只有等待高优先级的队列资源全部加载完毕,才去加载下一个优先级对应的一组资源,如果优先级相同,则按照添加至队列的顺序加载。每一项资源在加载时,会生成对应的ResourceItem,它包含一个唯一识别码hashCode、资源的名称、类型、url、版本号等信息,待资源加载完毕,RES会根据ResourceItem中的类型来请求预先注册在RES中的与本资源类型对应的Analyzer,解析出加载的数据,放入缓存中,以供使用。

    不同的Analyzer,都继承自一个基类BinAnalyzer,它拥有一个map结构fileDic,用于存放加载后处理过的数据;同时提供通过key访问和注销资源的接口。

核心API介绍

  • createGroup(name, keys, override)

    用于创建一个资源组。传入一个组名,一个包含资源key的数组,一个是否覆盖同名资源的标志。

    
    RES.createGroup('group', [
     'res1',
     'res2',
     'res3'
    ], true);
    

    资源数组中的key是在配置表里面预先配置过的,每个key对应的配置信息中已经包含了资源的url,版本号,资源类型等。

  • loadGroup(name, priority)

    加载配置好的资源组。这个资源组可以是代码动态创建的,如上所述的createGroup,也可以是资源编辑器中编辑好的。priority越大,越优先加载。

    
    RES.loadGroup('group', 10);
    RES.addEventListener(Event.GROUP_LOAD_COMPLETE, (resourceEvent) => {
      if (resourceEvent.groupName == 'tollgate') {
        Stage.initialize(); 
      }   
    });
    
  • getRes(key)

    同步获取资源池里面的某个资源,如果资源池中没有对应的资源,返回null。

    
    let texture = RES.getRes('texture_name');
    let sprite = new Tiny.Sprite(texture);
    stage.addChild(sprite);
    
  • getResAsync(key, onComplete)

    异步获取一个资源,这个资源的信息已经在配置表中存在,但是没有存在于缓存池中。

    
    RES.getResAsync('texture_name', (data, key) => {
      let sprite = new Tiny.Sprite(data);
      stage.addChild(sprite);
    });
    

    如果用这种形式获取一个已经存在于资源池的资源,使用方式是相同的,只不过资源是在下一个frame时返回。

  • getResByUrl(url, onComplete)

    异步从远程url获取一个资源。

    
    RES.getResByUrl('//alicdn.com/2018-tjb/1.0.8/Tbsxfdiwjksd240-120.png', (data, key) => {
      let sprite = new Tiny.Sprite(data);
      stage.addChild(sprite);
    });
    
  • destroyRes(groupName|resName, force = true)

    删除一组资源或者单个资源,传入组名或者资源的key。force决定是否删除多个组中的公共资源。比如当前组中的一个资源在其他组中也被引用到,force为true,会连带删除所有引用。

    
    RES.destroyRes('group', true);
    RES.destroyRes('a_res');