cocos creator 3.x 资源的动态加载
在cocos creator开发过程中,可以通过prefab做界面,但是一个非常常见的场景是动态资源的加载。本文纪录一下cocos creator在开发过程中动态资源的加载方法。本文示例基于cocos creatro 3.x 版本。资源使用assetmanager.bundle来组织。
测试工程的目录如下所示:
其中dynamic设置为bundle,所有资源的加载都必须先加载 bundle
加载bundle的加载方法:
public loadBundle(bundleName:string):Promise<boolean>{
return new Promise<boolean>((resolve,reject)=>{
assetManager.loadBundle(bundleName,(err,bundle:AssetManager.Bundle)=>{
if(err != null){
resolve(false);
}
this.bundle = bundle;
resolve(true);
})
});
}
异步方式加载bundle,如果 let isload = await loadBundle(“dynaimc”); 如果是 isload == true 表示bundle加载成功。可以继续下面的操作。
动态加载spriteFrame
spriteFrame最常见的场景是图片换图,比如:头像更换,捕鱼游戏上鱼的更换,按钮状态的改变,更换不同的图片。
/**
* 加载单个bundle中的图片 获取其中的SpriteFrame
*/
public loadBundleImage(imgFilePath:string):Promise<SpriteFrame|null>{
return new Promise<SpriteFrame | null>((resolve,reject)=>{
if(this.bundle == null){
console.log("bundle没有加载成功");
resolve(null);
return
}
this.bundle.load<SpriteFrame>(imgFilePath,SpriteFrame,(err,sp:SpriteFrame)=>{
if(err != null){
console.log(err);
resolve(null);
return;
}
resolve(sp);
return;
})
});
}
其实bundle.load 不仅仅可以加载sprite,同样也可以加载CCTexture2D,只需要传入不同的资源类型就可以。
let sp = await ResMgr.getInstance().loadBundleImage("image/item1/spriteFrame");
//testSp为测试拖动到prefab中的一个节点。节点已经添加了Sprite组件
this.testSp!.getComponent(Sprite)!.spriteFrame = sp;
通过以上方法能够成功后去spriteFrame,然后在Sprite的Compontent直接设置对应的spriteFrame即可,这里需要注意的是:资源的加载是异步进行的。
这里需要注意的是:imgage/item1 是拖动到bundle目录下的资源,但是如果加载的是spriteFrame,则需要路径为:image/item1/spriteFrame 这点纠结了一会。
动态加载fnt字体文件
游戏在结算界面可能根据胜利和失败,来决定使用什么字体;当然可以通过prefab拖动两个节点,根据不同的结果,决定显示按个节点。这里不讨论这种方式。
动态字体的加载和spriteFrame的动态加载非常类似
public loadFnt(fntFilePath:string):Promise<Font | null>{
return new Promise<Font | null>((resolve,reject)=>{
if(this.bundle == null){
console.log("bundle没有加载成功");
resolve(null);
return;
}
this.bundle.load<Font>(fntFilePath,Font,(err,atlas:Font)=>{
if(err != null){
console.log(err);
resolve(null);
return;
}
console.log(atlas);
resolve(atlas);
return;
});
});
}
仅仅是资源传入资源的类型不同。和异步返回的类型不同,其他都一样。
使用如下:
let atlas = await ResMgr.getInstance().loadFnt("fnt/fish_blue_tips");
this.testFnt!.getComponent(Label)!.font = atlas!;
其中this.testFnt为绑定了Label组件的节点(node)。
spine的动态加载
在捕鱼游戏中,如果与的资源是spine,那么根据服务器发送过来的出鱼不同,就需要加载不同的spine资源。
public loadSpine(spineFileName:string):Promise<sp.SkeletonData | null>{
return new Promise<sp.SkeletonData | null>((resolve,reject)=>{
if (this.bundle == null){
console.log("当前bundle加载失败不能继续处理了");
resolve(null);
return;
}
this.bundle.load<sp.SkeletonData>(spineFileName,sp.SkeletonData,(err,skeletonData:sp.SkeletonData)=>{
if(err != null){
console.log(err);
resolve(null);
return;
}
console.log(skeletonData);
resolve(skeletonData);
});
});
}
同样,动态加载的资源类型不同,其他流程和方法也是一样的。
使用:
let data = await ResMgr.getInstance().loadSpine("spine/bangzhu");
this.testSpine!.getComponent(sp.Skeleton)!.skeletonData = data!;
this.testSpine!.getComponent(sp.Skeleton)!.animation = "animation";
音效的动态加载
音效的当然可以通过,界面操作,在prefab上进行拖动,然后设置对应的属性,这当然是没有问题的,但是他的耦合性比较大。每次修改一下音效,都需要重新拖动。于是决定使用动态资源的加载方法,对音效资源进行加载
public loadAudioClip(audioFilePath:string):Promise<AudioClip | null>{
return new Promise<AudioClip | null>((resolve,reject)=>{
if(this.bundle == null){
console.log("当前的bundle加载失败不能继续处理了");
resolve(null);
return;
}
this.bundle.load<AudioClip>(audioFilePath,AudioClip,(err,audioclip:AudioClip)=>{
if(err != null){
console.log(err);
resolve(null);
return;
}
console.log(audioclip);
resolve(audioclip);
});
});
}
使用,同样给一个节点或者通过界面绑定,或者代码绑定的方法绑定一个AudioSource组件,给该组件设置对应的clip也就是上面代码中返回的AudioClip。就能够通过代码动态控制音效的播放,暂停。
需要注意
其他资源的加载和上面方法类似,仅仅是资源类型不同。但是通过动态加载的资源和通过界面编辑的预制体(prefab),有一个很重要的区别。资源的引用计数,也就是资源的释放问题。
- 通过prefab进行拖拽的资源,在prefab销毁的时候,不需要手动管理引用计数,引擎会自动减少引用计数
- 通过动态加载的资源,在资源使用功能的时候都需要进行addRef()操作,在界面关闭的时候,都需要手动调用decRef()操作,否则资源的引用计数就有问题。具体可以参照官方文档