cocos creator 3.x 资源的动态加载

在cocos creator开发过程中,可以通过prefab做界面,但是一个非常常见的场景是动态资源的加载。本文纪录一下cocos creator在开发过程中动态资源的加载方法。本文示例基于cocos creatro 3.x 版本。资源使用assetmanager.bundle来组织。

测试工程的目录如下所示:

其中dynamic设置为bundle,所有资源的加载都必须先加载 bundle

加载bundle的加载方法:

  1. public loadBundle(bundleName:string):Promise<boolean>{
  2. return new Promise<boolean>((resolve,reject)=>{
  3. assetManager.loadBundle(bundleName,(err,bundle:AssetManager.Bundle)=>{
  4. if(err != null){
  5. resolve(false);
  6. }
  7. this.bundle = bundle;
  8. resolve(true);
  9. })
  10. });
  11. }

异步方式加载bundle,如果 let isload = await loadBundle(“dynaimc”); 如果是 isload == true 表示bundle加载成功。可以继续下面的操作。

动态加载spriteFrame

spriteFrame最常见的场景是图片换图,比如:头像更换,捕鱼游戏上鱼的更换,按钮状态的改变,更换不同的图片。

  1. /**
  2. * 加载单个bundle中的图片 获取其中的SpriteFrame
  3. */
  4. public loadBundleImage(imgFilePath:string):Promise<SpriteFrame|null>{
  5. return new Promise<SpriteFrame | null>((resolve,reject)=>{
  6. if(this.bundle == null){
  7. console.log("bundle没有加载成功");
  8. resolve(null);
  9. return
  10. }
  11. this.bundle.load<SpriteFrame>(imgFilePath,SpriteFrame,(err,sp:SpriteFrame)=>{
  12. if(err != null){
  13. console.log(err);
  14. resolve(null);
  15. return;
  16. }
  17. resolve(sp);
  18. return;
  19. })
  20. });
  21. }

其实bundle.load 不仅仅可以加载sprite,同样也可以加载CCTexture2D,只需要传入不同的资源类型就可以。

  1. let sp = await ResMgr.getInstance().loadBundleImage("image/item1/spriteFrame");
  2. //testSp为测试拖动到prefab中的一个节点。节点已经添加了Sprite组件
  3. this.testSp!.getComponent(Sprite)!.spriteFrame = sp;

通过以上方法能够成功后去spriteFrame,然后在Sprite的Compontent直接设置对应的spriteFrame即可,这里需要注意的是:资源的加载是异步进行的。

这里需要注意的是:imgage/item1 是拖动到bundle目录下的资源,但是如果加载的是spriteFrame,则需要路径为:image/item1/spriteFrame 这点纠结了一会。

动态加载fnt字体文件

游戏在结算界面可能根据胜利和失败,来决定使用什么字体;当然可以通过prefab拖动两个节点,根据不同的结果,决定显示按个节点。这里不讨论这种方式。

动态字体的加载和spriteFrame的动态加载非常类似

  1. public loadFnt(fntFilePath:string):Promise<Font | null>{
  2. return new Promise<Font | null>((resolve,reject)=>{
  3. if(this.bundle == null){
  4. console.log("bundle没有加载成功");
  5. resolve(null);
  6. return;
  7. }
  8. this.bundle.load<Font>(fntFilePath,Font,(err,atlas:Font)=>{
  9. if(err != null){
  10. console.log(err);
  11. resolve(null);
  12. return;
  13. }
  14. console.log(atlas);
  15. resolve(atlas);
  16. return;
  17. });
  18. });
  19. }

仅仅是资源传入资源的类型不同。和异步返回的类型不同,其他都一样。

使用如下:

  1. let atlas = await ResMgr.getInstance().loadFnt("fnt/fish_blue_tips");
  2. this.testFnt!.getComponent(Label)!.font = atlas!;

其中this.testFnt为绑定了Label组件的节点(node)。

spine的动态加载

在捕鱼游戏中,如果与的资源是spine,那么根据服务器发送过来的出鱼不同,就需要加载不同的spine资源。

  1. public loadSpine(spineFileName:string):Promise<sp.SkeletonData | null>{
  2. return new Promise<sp.SkeletonData | null>((resolve,reject)=>{
  3. if (this.bundle == null){
  4. console.log("当前bundle加载失败不能继续处理了");
  5. resolve(null);
  6. return;
  7. }
  8. this.bundle.load<sp.SkeletonData>(spineFileName,sp.SkeletonData,(err,skeletonData:sp.SkeletonData)=>{
  9. if(err != null){
  10. console.log(err);
  11. resolve(null);
  12. return;
  13. }
  14. console.log(skeletonData);
  15. resolve(skeletonData);
  16. });
  17. });
  18. }

同样,动态加载的资源类型不同,其他流程和方法也是一样的。

使用:

  1. let data = await ResMgr.getInstance().loadSpine("spine/bangzhu");
  2. this.testSpine!.getComponent(sp.Skeleton)!.skeletonData = data!;
  3. this.testSpine!.getComponent(sp.Skeleton)!.animation = "animation";

音效的动态加载

音效的当然可以通过,界面操作,在prefab上进行拖动,然后设置对应的属性,这当然是没有问题的,但是他的耦合性比较大。每次修改一下音效,都需要重新拖动。于是决定使用动态资源的加载方法,对音效资源进行加载

  1. public loadAudioClip(audioFilePath:string):Promise<AudioClip | null>{
  2. return new Promise<AudioClip | null>((resolve,reject)=>{
  3. if(this.bundle == null){
  4. console.log("当前的bundle加载失败不能继续处理了");
  5. resolve(null);
  6. return;
  7. }
  8. this.bundle.load<AudioClip>(audioFilePath,AudioClip,(err,audioclip:AudioClip)=>{
  9. if(err != null){
  10. console.log(err);
  11. resolve(null);
  12. return;
  13. }
  14. console.log(audioclip);
  15. resolve(audioclip);
  16. });
  17. });
  18. }

使用,同样给一个节点或者通过界面绑定,或者代码绑定的方法绑定一个AudioSource组件,给该组件设置对应的clip也就是上面代码中返回的AudioClip。就能够通过代码动态控制音效的播放,暂停。

需要注意

其他资源的加载和上面方法类似,仅仅是资源类型不同。但是通过动态加载的资源和通过界面编辑的预制体(prefab),有一个很重要的区别。资源的引用计数,也就是资源的释放问题。

  1. 通过prefab进行拖拽的资源,在prefab销毁的时候,不需要手动管理引用计数,引擎会自动减少引用计数
  2. 通过动态加载的资源,在资源使用功能的时候都需要进行addRef()操作,在界面关闭的时候,都需要手动调用decRef()操作,否则资源的引用计数就有问题。具体可以参照官方文档