├── alipay.jpg ├── wechatpay.jpg ├── README.md └── ResourceManager.ts /alipay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwincyLi/ResourceManager/HEAD/alipay.jpg -------------------------------------------------------------------------------- /wechatpay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwincyLi/ResourceManager/HEAD/wechatpay.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Resource manager for cocos creator 2 | ### cocos creator version : 2.4.3 3 | --- 4 | 5 | TODO: 移除动态属性(避免js引擎创建冗余的隐藏类),使用Map记录标记 6 | 7 | --- 8 | 一个cocos creator的资源管理方案。将引擎的[资源的静态引用](https://docs.cocos.com/creator/manual/zh/asset-manager/release-manager.html#%E8%B5%84%E6%BA%90%E7%9A%84%E9%9D%99%E6%80%81%E5%BC%95%E7%94%A8)和[资源的动态引用](https://docs.cocos.com/creator/manual/zh/asset-manager/release-manager.html#%E8%B5%84%E6%BA%90%E7%9A%84%E5%8A%A8%E6%80%81%E5%BC%95%E7%94%A8)统一,通过引用计数实现资源的自动释放。 9 | 10 | 如果你使用过unity的addressables的话,你可以将其理解为一个cocos creator的addressables的近似实现,配合bundle集成配置插件使用效果更佳: [qsbundle](https://github.com/QinSheng-Li/qsbundle) 11 | 12 | --- 13 | - @author : lqs 14 | - @license : MIT 15 | - @email : 1053128593@qq.com 16 | --- 17 | ### FAQ: 18 | - 1.如何使用: 将ResourceManager文件引入项目并将其挂载在常驻节点或者其子节点上(节点实例化、销毁 资源加载、替换请使用下文Api内接口) 19 | - 2.预加载: 直接使用引擎接口或者preloadAsset 20 | - 3.资源常驻: 直接调用引擎资源接口```cc.Asset.prototype.addRef```)即可 21 | - 4.资源加载接口与引擎接口的区别: 对资源的动态引用进行标记(建议已加载的资源也使用此接口获取进行标记检查)。并且如果将syncCallback设为true的话,则已经加载到内存中了资源则立即执行回调而不是使用引擎的延迟模拟异步 22 | - 5.场景的资源自动释放是否需要勾选: 勾选(引擎场景资源自动释放也是通过引用计数减少的方式,所以不再重复实现,如果需要场景切换对某些资源不释放,参考第三点:资源常驻) 23 | - 6.节点销毁后资源为啥没有立即被释放: 为了避免某些场景下资源被频繁的卸载加载,我们会延迟一段时间定期释放,这个间隔可以通过```releaseDelay : number```参数进行控制,默认值为5s 24 | - 7.调用decRef为什么被立即释放: 这是由引擎实现所决定的,如果希望同第6点,需调用引擎资源接口```asset.decRef(false)//传入false只减少引用计数,不释放 ``` 后 调用本模块的 ```resMgr.tryRelease(asset._uuid)``` 25 | - 8.资源加载完成后为什么立即被放入待释放队列: 希望用户能有更清晰的资源管理概念,理清什么动态资源需要常驻,什么动态资源在界面或者场景关闭后需要释放,避免资源被加载后却遗忘释放,如果避免被释放的话参考第3点。 26 | eg: 如果是同某界面一起的生命周期 你可以在onLoad中动态加载并addRef,然后在onDestroy中decRef 27 | ### API: 28 | #### 1. 节点实例化及销毁 29 | ``` typescript 30 | /** 31 | * 实例化一个预制或者节点 32 | * 并对节点所来源的预制或者其依赖的动态加载资源进行引用计数+1 33 | * 34 | * 为了保证资源被正确引用计数,请使用此接口代替cc.instantiate 35 | */ 36 | instantiateNode(prefabOrNode: cc.Prefab | cc.Node): cc.Node 37 | /** 38 | * 销毁一个节点, 39 | * 并对节点所来源的预制或者其依赖的动态加载资源进行引用计数-1 40 | * 如果节点所使用的预制没有其他实例,则会放入待释放队列中进行释放 41 | * 42 | * 为了保证资源被正确引用计数,请使用此接口代替cc.Node中的destroy方法 43 | */ 44 | destroyNode(node: cc.Node) 45 | /** 46 | * 销毁一个节点的所有子节点 47 | * 48 | * 为了保证资源被正确引用计数,请使用此接口代替cc.Node中的destroyAllChildren方法 49 | */ 50 | destroyAllChildrenNode(node: cc.Node) 51 | ``` 52 | #### 2.资源替换 53 | ``` typescript 54 | /** 55 | * 替换SpriteFrame 56 | * 为了保证资源被正确引用计数,在替换时请使用此接口而不是直接使用引擎接口 57 | * 错误示例: sprite.spriteFrame = newSpriteFrame 或者 sprite.spriteFrame = null 58 | */ 59 | setSpriteFrame(image: cc.Sprite | cc.Mask, newSpriteFrame: cc.SpriteFrame) 60 | /** 61 | * 替换字体 62 | * 为了保证资源被正确引用计数,在替换时请使用此接口而不是直接使用引擎接口 63 | * 错误示例: label.font = newFont 或者 label.font = null 64 | */ 65 | setFont(label: cc.Label | cc.RichText, newFont: cc.Font) 66 | /** 67 | * 替换材质 68 | * 为了保证资源被正确引用计数,在替换时请使用此接口而不是直接使用引擎接口 69 | * 错误示例: sprite.setMaterial(0, newMaterial) 70 | */ 71 | setMaterial(render: cc.RenderComponent, index: number, newMaterial: cc.Material) 72 | /** 73 | * 替换按钮状态纹理 74 | */ 75 | setButtonSpriteFrame(button: cc.Button, newNormalSpriteFrame: cc.SpriteFrame, newPressedSpriteFrame: cc.SpriteFrame, newHoverSpriteFrame: cc.SpriteFrame, newDisableSpriteFrame: cc.SpriteFrame) 76 | /** 77 | * 替换龙骨资源 78 | */ 79 | setDragonBones(dragonBones: dragonBones.ArmatureDisplay, newDragonBonesAsset: dragonBones.DragonBonesAsset, newDragonBonesAltas: dragonBones.DragonBonesAtlasAsset) 80 | /** 81 | * 替换spine资源,为了保证资源被正确计数,请使用此接口进行替换 82 | */ 83 | setSpine(skeleton: sp.Skeleton, newSkeletonData: sp.SkeletonData)` 84 | ``` 85 | #### 3. 资源动态加载(请使用以下接口替代引擎接口, 注: 资源加载完成后会被放入待释放队列) 86 | ``` typescript 87 | /** 88 | * 加载bundle 若已缓存则直接同步执行回调 89 | * @param bundleName bundle包名 90 | */ 91 | loadBundle(bundleName: string, onLoad : (err : string, bundle : cc.AssetManager.Bundle)=> void) 92 | /** 93 | * 从指定资源包中加载指定资源 94 | * 95 | * @param bundleName bundle包名 96 | * @param assetName 资源名称 97 | * @param type 资源类型 98 | * @param callback 加载完成 回调函数 99 | */ 100 | loadAsset(bundleName: string, assetName: string, type: typeof cc.Asset, callback?: (err?: string, asset?: cc.Asset) => void) 101 | //下面的这些具体类型 只是为了避免每次加载时都需要声明资源类型 102 | loadSpriteFrame(bundleName: string, imageName: string, callback: (err?: string, spriteFrame?: cc.SpriteFrame) => void) 103 | loadFont(bundleName: string, fontPath: string, callback?: (err?: string, font?: cc.Font) => void) 104 | loadMaterial(bundleName: string, materialPath: string, callback?: (err?: string, material?: cc.Material) => void) 105 | loadPrefab(bundleName: string, prefabPath: string, callback: (err?: string, prefab?: cc.Prefab) => void) 106 | loadAudioClip(bundleName: string, audioClipPath: string, callback: (err: string, clip: cc.AudioClip) => void) 107 | loadAnimationClip(bundleName: string, clipPath: string, callback: (err: string, clip: cc.AnimationClip) => void) 108 | loadAtlas(bundleName: string, atlasPath: string, callback?: (err?: string, atlas?: cc.SpriteAtlas) => void) 109 | loadSpine(bundleName: string, spinePath: string, callback?: (err?: string, spine?: sp.SkeletonData) => void) 110 | ``` 111 | --- 112 | ### 如果觉得有用的话,也可以支持一波:pray: 113 | ![img](https://raw.githubusercontent.com/QinSheng-Li/qsbundle/master/images/alipay.jpg) 114 | ![img](https://raw.githubusercontent.com/QinSheng-Li/qsbundle/master/images/wechatpay.jpg) 115 | -------------------------------------------------------------------------------- /ResourceManager.ts: -------------------------------------------------------------------------------- 1 | const { ccclass, property, menu } = cc._decorator; 2 | 3 | 4 | enum DynamicState { 5 | STATIC = undefined, 6 | UNUSE = 0, 7 | //USE = 1, //大于这个值都是USE 8 | } 9 | 10 | interface DynamicAsset extends cc.Asset { 11 | _ref: number 12 | _uuid: string 13 | _dynamic: DynamicState 14 | } 15 | 16 | const dependUtil = cc.assetManager.dependUtil 17 | const assets = cc.assetManager.assets 18 | 19 | @ccclass 20 | export default class ResourceManager extends cc.Component { 21 | 22 | @property({ tooltip: "是否启动自动释放管理, 游戏运行时不可以再修改" }) 23 | public autoRelease: boolean = true 24 | 25 | @property({ tooltip: "资源自动释放延迟间隔,引用计数归0后多久释放资源", type: cc.Float, min: 0.0 }) 26 | public releaseDelay: number = 5.0 27 | 28 | @property({ tooltip: "是否直接执行回调,如果资源已经下载缓存了的话" }) 29 | public syncCallback: boolean = false 30 | 31 | /** 等待释放的资源uuid */ 32 | private _waitDelete: Map = new Map() 33 | 34 | onLoad() { 35 | //循环进行资源检测 36 | if (CC_PREVIEW) { 37 | this.schedule(() => { 38 | cc.log(assets) 39 | }, 5) 40 | } 41 | if (this.autoRelease) { 42 | //引擎内置资源常驻 43 | assets.forEach((asset, uuid) => { asset.addRef() }) 44 | this.schedule(() => { 45 | this._doRelease() 46 | }, this.releaseDelay / 2.0) 47 | } 48 | } 49 | 50 | private _doRelease() { 51 | if (this._waitDelete.size) { 52 | let now = performance.now() 53 | this._waitDelete.forEach((deleteTick, uuid) => { 54 | //超过时间进行删除检测 55 | if (now >= deleteTick) { 56 | let asset = assets.get(uuid) 57 | if (asset) { 58 | //@ts-expect-error 59 | cc.assetManager._releaseManager._free(asset) 60 | //cc.log(`Resource : do release asset ${asset.name} ${cc.js.getClassName(asset)} ${uuid} , refCount : ${asset.refCount}`) 61 | } 62 | this._waitDelete.delete(uuid) 63 | 64 | } 65 | }) 66 | } 67 | } 68 | 69 | private _visitAsset(asset: cc.Asset, deps: string[]) { 70 | let dynamicAsset = asset as DynamicAsset 71 | if (!dynamicAsset._uuid) return 72 | if (dynamicAsset._dynamic != DynamicState.STATIC && dynamicAsset._dynamic != DynamicState.UNUSE) { 73 | //@ts-expect-error 74 | if (!deps.includes(asset._uuid)) { 75 | //@ts-expect-error 76 | deps.push(asset._uuid) 77 | } 78 | } 79 | } 80 | 81 | private _visitComponent(comp: cc.Component, deps: string[]) { 82 | let props = Object.getOwnPropertyNames(comp); 83 | for (let i = 0; i < props.length; i++) { 84 | let propName = props[i]; 85 | if (propName === 'node' || propName === '__eventTargets') continue; 86 | let value = comp[propName]; 87 | if (typeof value === 'object' && value) { 88 | if (Array.isArray(value)) { 89 | for (let j = 0; j < value.length; j++) { 90 | let val = value[j]; 91 | if (val instanceof cc.Asset) { 92 | this._visitAsset(val, deps); 93 | } 94 | } 95 | } 96 | else if (!value.constructor || value.constructor === Object) { 97 | let keys = Object.getOwnPropertyNames(value); 98 | for (let j = 0; j < keys.length; j++) { 99 | let val = value[keys[j]]; 100 | if (val instanceof cc.Asset) { 101 | this._visitAsset(val, deps); 102 | } 103 | } 104 | } 105 | else if (value instanceof cc.Asset) { 106 | this._visitAsset(value, deps); 107 | } 108 | } 109 | } 110 | } 111 | 112 | private _visitNode(node, deps) { 113 | for (let i = 0; i < node._components.length; i++) { 114 | this._visitComponent(node._components[i], deps); 115 | } 116 | for (let i = 0; i < node._children.length; i++) { 117 | this._visitNode(node._children[i], deps); 118 | } 119 | } 120 | 121 | /** 122 | * 尝试删除(将其放入待删除容器中,真正删除在定时器中调用) 123 | * @param uuid 资源的uuid 124 | */ 125 | public tryRelease(uuid: string) { 126 | let deleteTimeStamp = performance.now() + this.releaseDelay * 1000 127 | this._waitDelete.set(uuid, deleteTimeStamp) 128 | } 129 | 130 | /** 131 | * 获取节点动态加载的资源依赖 132 | * @param node 133 | * @param delta 134 | */ 135 | private _addNodeDependAssetsRef(node: cc.Node, delta: number = 1) { 136 | let deps = []; 137 | this._visitNode(node, deps); 138 | for (let i = 0, l = deps.length; i < l; i++) { 139 | let dependAsset = assets.get(deps[i]); 140 | if (dependAsset) { 141 | this._addRef(dependAsset, delta) 142 | } 143 | } 144 | } 145 | 146 | /** 147 | * 将节点中动态加载的资源引用计数减少 148 | * @param node 149 | * @param delta 150 | */ 151 | private _decNodeDependAssetsRef(node: cc.Node) { 152 | let deps = [] 153 | this._visitNode(node, deps) 154 | for (let i = 0, l = deps.length; i < l; i++) { 155 | var dependAsset = assets.get(deps[i]); 156 | if (dependAsset) { 157 | this._decRef(dependAsset) 158 | } else { 159 | cc.warn(`Resource : node dec asset , ${node.name} not fount depend asset ${deps[i]}`) 160 | } 161 | } 162 | } 163 | 164 | /** 165 | * 实例化一个预制或者节点,为了保证资源被正确引用计数,请使用此接口代替cc.instantiate 166 | * @param prefabOrNode 167 | */ 168 | public instantiateNode(prefabOrNode: cc.Prefab | cc.Node) { 169 | let node = cc.instantiate(prefabOrNode as cc.Node) 170 | if (this.autoRelease) { 171 | if (prefabOrNode instanceof cc.Prefab) { 172 | prefabOrNode.addRef() 173 | //hack 标记资源的预制来源 174 | //@ts-expect-error 175 | node._uuid = prefabOrNode._uuid 176 | 177 | //动态加载的资源 引用计数 178 | let dynamicAsset = prefabOrNode as any as DynamicAsset 179 | if (dynamicAsset._dynamic != DynamicState.STATIC) { 180 | dynamicAsset._dynamic += 1 181 | } 182 | } else { 183 | //检查节点里动态加载的资源并增加引用计数 184 | this._addNodeDependAssetsRef(prefabOrNode) 185 | } 186 | } 187 | return node 188 | } 189 | 190 | /** 191 | * 销毁一个节点,为了保证资源被正确引用计数,请使用此接口代替cc.Node的destroy方法 192 | * @param node 193 | */ 194 | public destroyNode(node: cc.Node) { 195 | if (node && cc.isValid(node)) { 196 | let name = node.name 197 | if (this.autoRelease) { 198 | this._decNodeDependAssetsRef(node) 199 | //hack 根据标记的预制来源 减少预制的引用 200 | //@ts-expect-error 201 | if (node._uuid) { 202 | //@ts-expect-error 203 | let prefab = cc.assetManager.assets.get(node._uuid) as cc.Prefab 204 | if (prefab) { 205 | this._decRef(prefab) 206 | } 207 | } 208 | } 209 | node.destroy() 210 | } 211 | } 212 | 213 | /** 214 | * 销毁一个节点的所有子节点 215 | * 为了保证资源被正确引用计数,请使用此接口代替cc.Node中的destroyAllChildren方法 216 | */ 217 | public destroyAllChildrenNode(node: cc.Node) { 218 | if (node) { 219 | let count = node.childrenCount 220 | for (let i = 0; i < count; i++) { 221 | this.destroyNode(node.children[i]) 222 | } 223 | } 224 | } 225 | 226 | /** 227 | * 给动态加载的资源增加引用计数(静态资源不生效) 228 | * @param asset 229 | * @param delta 230 | */ 231 | private _addRef(asset: cc.Asset, delta: number = 1) { 232 | if (this.autoRelease && asset) { 233 | if (!cc.isValid(asset, true)) { 234 | cc.warn(`Resource: asset addRef ${asset.name} not valid, addRef failuer`) 235 | return 236 | } 237 | 238 | if (asset instanceof cc.MaterialVariant) { 239 | //@ts-expect-error 240 | asset = asset.material 241 | } 242 | let dynamicAsset = asset as DynamicAsset 243 | if (dynamicAsset._dynamic != DynamicState.STATIC) { 244 | dynamicAsset._dynamic += delta 245 | dynamicAsset._ref += delta 246 | } 247 | } 248 | } 249 | 250 | /** 251 | * 给动态加载的资源减少引用计数(静态资源不生效) 252 | * @param asset 253 | */ 254 | private _decRef(asset: cc.Asset, delta: number = 1) { 255 | if (this.autoRelease && asset) { 256 | if (asset instanceof cc.MaterialVariant) { 257 | //@ts-expect-error 258 | asset = asset.material 259 | } 260 | let dynamicAsset = asset as DynamicAsset 261 | if (dynamicAsset._dynamic != DynamicState.STATIC && dynamicAsset._dynamic != DynamicState.UNUSE) { 262 | dynamicAsset._dynamic -= delta 263 | dynamicAsset._ref -= delta 264 | //引用计数不为0 可能是循环引用也需要做释放检测 265 | this.tryRelease(dynamicAsset._uuid) 266 | } 267 | } 268 | } 269 | 270 | /** 271 | * 加载bundle 若已缓存则直接同步执行回调 272 | * @param bundleName 273 | * @param onLoad 274 | */ 275 | public loadBundle(bundleName: string, onLoad) { 276 | if (this.syncCallback) { 277 | let cacheBundle = cc.assetManager.bundles.get(bundleName) 278 | if (cacheBundle) { 279 | onLoad(null, cacheBundle) 280 | return 281 | } 282 | } 283 | 284 | cc.assetManager.loadBundle(bundleName, (err, bundle: cc.AssetManager.Bundle) => { 285 | if (err) { 286 | cc.warn(err) 287 | onLoad(err, null) 288 | return 289 | } 290 | //检测bundle包的依赖 291 | let deps = bundle.deps 292 | if (deps) { 293 | for (let i = 0; i < deps.length; i++) { 294 | let depBundleName = deps[i] 295 | if (cc.assetManager.bundles.get(depBundleName)) { 296 | 297 | } else { 298 | cc.assetManager.loadBundle(depBundleName, (depErr, depBundle) => { 299 | if (depErr) { 300 | cc.warn(depErr) 301 | return 302 | } 303 | }) 304 | } 305 | } 306 | } 307 | onLoad(null, bundle) 308 | }) 309 | } 310 | 311 | /** 312 | * 替换SpriteFrame 313 | * 为了保证资源被正确引用计数,在替换时请使用此接口而不是直接使用引擎接口 314 | * 错误示例: sprite.spriteFrame = newSpriteFrame 或者 sprite.spriteFrame = null 315 | */ 316 | public setSpriteFrame(image: cc.Sprite | cc.Mask, newSpriteFrame: cc.SpriteFrame) { 317 | if (!image) return 318 | let oldSpriteFrame = image.spriteFrame 319 | if (oldSpriteFrame == newSpriteFrame) return 320 | if (oldSpriteFrame) { 321 | this._decRef(oldSpriteFrame) 322 | } 323 | if (newSpriteFrame) { 324 | this._addRef(newSpriteFrame) 325 | } 326 | 327 | try { 328 | image.spriteFrame = newSpriteFrame 329 | } catch (e) { 330 | cc.warn(e) 331 | } 332 | } 333 | 334 | /** 335 | * 替换按钮状态纹理 336 | */ 337 | public setButtonSpriteFrame(button: cc.Button, newNormalSpriteFrame: cc.SpriteFrame, newPressedSpriteFrame: cc.SpriteFrame, newHoverSpriteFrame: cc.SpriteFrame, newDisableSpriteFrame: cc.SpriteFrame) { 338 | if (!button) return 339 | let oldNormalSpriteFrame = button.normalSprite 340 | let oldPressedSpriteFrame = button.pressedSprite 341 | let oldHoverSpriteFrame = button.hoverSprite 342 | let oldDisableSpriteFrame = button.disabledSprite 343 | 344 | if (oldNormalSpriteFrame) 345 | this._decRef(oldNormalSpriteFrame) 346 | if (oldPressedSpriteFrame) 347 | this._decRef(oldPressedSpriteFrame) 348 | if (oldHoverSpriteFrame) 349 | this._decRef(oldHoverSpriteFrame) 350 | if (oldDisableSpriteFrame) 351 | this._decRef(oldDisableSpriteFrame) 352 | 353 | if (newNormalSpriteFrame) 354 | this._addRef(newNormalSpriteFrame) 355 | if (newPressedSpriteFrame) 356 | this._addRef(newPressedSpriteFrame) 357 | if (newHoverSpriteFrame) 358 | this._addRef(newHoverSpriteFrame) 359 | if (newDisableSpriteFrame) 360 | this._addRef(newDisableSpriteFrame) 361 | 362 | button.normalSprite = newNormalSpriteFrame 363 | button.pressedSprite = newPressedSpriteFrame 364 | button.hoverSprite = newHoverSpriteFrame 365 | button.disabledSprite = newDisableSpriteFrame 366 | } 367 | 368 | /** 369 | * 替换字体 370 | * 为了保证资源被正确引用计数,在替换时请使用此接口而不是直接使用引擎接口 371 | * 错误示例: label.font = newFont 或者 label.font = null 372 | */ 373 | public setFont(label: cc.Label | cc.RichText, newFont: cc.Font) { 374 | if (!label) return 375 | let oldFont = label.font 376 | if (oldFont == newFont) return 377 | if (this.autoRelease) { 378 | if (oldFont) 379 | this._decRef(oldFont) 380 | if (newFont) 381 | this._addRef(newFont) 382 | } 383 | label.font = newFont 384 | } 385 | 386 | /** 387 | * 替换龙骨资源 388 | */ 389 | public setDragonBones(dragonBones: dragonBones.ArmatureDisplay, newDragonBonesAsset: dragonBones.DragonBonesAsset, newDragonBonesAltas: dragonBones.DragonBonesAtlasAsset): void { 390 | if (!dragonBones) return 391 | let oldDragonBonesAsset = dragonBones.dragonAsset 392 | let oldDragonBonesAtlas = dragonBones.dragonAtlasAsset 393 | if (oldDragonBonesAsset) 394 | this._decRef(oldDragonBonesAsset) 395 | if (oldDragonBonesAtlas) 396 | this._decRef(oldDragonBonesAtlas) 397 | 398 | if (newDragonBonesAltas) 399 | this._addRef(newDragonBonesAltas) 400 | if (newDragonBonesAsset) 401 | this._addRef(newDragonBonesAsset) 402 | 403 | dragonBones.dragonAsset = newDragonBonesAsset 404 | dragonBones.dragonAtlasAsset = newDragonBonesAltas 405 | } 406 | 407 | /** 408 | * 替换spine资源,为了保证资源被正确计数,请使用此接口进行替换 409 | */ 410 | public setSpine(skeleton: sp.Skeleton, newSkeletonData: sp.SkeletonData) { 411 | if (!skeleton) return 412 | let oldSkeletonData = skeleton.skeletonData 413 | if (oldSkeletonData) 414 | this._decRef(oldSkeletonData) 415 | if (newSkeletonData) 416 | this._addRef(newSkeletonData) 417 | 418 | skeleton.skeletonData = newSkeletonData 419 | } 420 | 421 | /** 422 | * 替换材质 423 | * 为了保证资源被正确引用计数,在替换时请使用此接口而不是直接使用引擎接口 424 | * 错误示例: sprite.setMaterial(0, newMaterial) 425 | */ 426 | public setMaterial(render: cc.RenderComponent, index: number, newMaterial: cc.Material) { 427 | //边界条件检查 428 | if (index < 0) return 429 | if (index >= render.getMaterials().length) return 430 | let oldMaterial = render.getMaterial(index) 431 | //if (oldMaterial == newMaterial) return //getMaterial获取的是材质变体 肯定无法相等 在引用管理内部会对此进行处理(_autoReleaseAsset) 432 | if (oldMaterial) { 433 | this._decRef(oldMaterial) 434 | } 435 | if (newMaterial) 436 | this._addRef(newMaterial) 437 | render.setMaterial(index, newMaterial) 438 | } 439 | 440 | public loadSpriteFrame(bundleName: string, imageName: string, callback: (err?: string, spriteFrame?: cc.SpriteFrame) => void) { 441 | this.loadAsset(bundleName, imageName, cc.SpriteFrame, callback) 442 | } 443 | 444 | public loadFont(bundleName: string, fontPath: string, callback?: (err?: string, font?: cc.Font) => void) { 445 | this.loadAsset(bundleName, fontPath, cc.Font, callback) 446 | } 447 | 448 | public loadMaterial(bundleName: string, materialPath: string, callback?: (err?: string, material?: cc.Material) => void) { 449 | this.loadAsset(bundleName, materialPath, cc.Material, callback) 450 | } 451 | 452 | public loadPrefab(bundleName: string, prefabPath: string, callback?: (err?: string, prefab?: cc.Prefab) => void) { 453 | this.loadAsset(bundleName, prefabPath, cc.Prefab, callback) 454 | } 455 | 456 | public loadAtlas(bundleName: string, atlasPath: string, callback?: (err?: string, atlas?: cc.SpriteAtlas) => void) { 457 | this.loadAsset(bundleName, atlasPath, cc.SpriteAtlas, callback) 458 | } 459 | 460 | public loadSpine(bundleName: string, spinePath: string, callback?: (err?: string, spine?: sp.SkeletonData) => void) { 461 | this.loadAsset(bundleName, spinePath, sp.SkeletonData, callback) 462 | } 463 | 464 | // public loadDragBones(bundleName: string, dragonBonesPath: string, callback?: (err?: string, dragonBones?: dragonBones.DragonBonesAsset) => void) { 465 | // this.loadAsset(bundleName, dragonBonesPath, dragonBones.DragonBonesAsset, callback) 466 | // } 467 | 468 | public loadAudioClip(bundleName: string, audioClipPath: string, callback: (err: string, clip: cc.AudioClip) => void) { 469 | this.loadAsset(bundleName, audioClipPath, cc.AudioClip, callback) 470 | } 471 | 472 | public loadAnimationClip(bundleName: string, clipPath: string, callback: (err: string, clip: cc.AnimationClip) => void) { 473 | this.loadAsset(bundleName, clipPath, cc.AnimationClip, callback) 474 | } 475 | 476 | // /** 477 | // * 与引擎接口含义一致 478 | // */ 479 | // public loadScene(sceneName: string, callback?: (err: string, scene: cc.Scene) => void) { 480 | // //@ts-expect-error 481 | // let bundleOfScene = cc.assetManager.bundles.find(function (bundle) { 482 | // return bundle.getSceneInfo(sceneName); 483 | // }); 484 | // if (!bundleOfScene) { 485 | // cc.warn("Resource : loadScene failed, not found scene info from bundles") 486 | // return 487 | // } 488 | // cc.assetManager.loadAny({ 'scene': sceneName }, { preset: "scene", bundle: bundleOfScene.name }, (err, sceneAsset) => { 489 | // if (err) { 490 | // cc.warn(err) 491 | // //@ts-expect-error 492 | // callback && callback(err, null) 493 | // return 494 | // } 495 | // // let dynamicAsset = sceneAsset as DynamicAsset 496 | // // if (dynamicAsset._dynamic == DynamicState.STATIC) { 497 | // // dynamicAsset._dynamic = DynamicState.UNUSE 498 | // // } 499 | // // this.addRef(sceneAsset) 500 | // cc.director.runScene(sceneAsset, null, (err, newScene: cc.Scene) => { 501 | // callback && callback(err, newScene) 502 | // }) 503 | // }) 504 | // } 505 | 506 | /** 507 | * 从指定资源包中加载指定资源 508 | * 509 | * @param bundleName bundle包名 510 | * @param assetName 资源名称 511 | * @param type 资源类型 512 | * @param callback 加载完成 回调函数 513 | */ 514 | public loadAsset(bundleName: string, assetName: string, type: typeof cc.Asset, callback?: (err?: string, asset?: cc.Asset) => void) { 515 | this.loadBundle(bundleName, (err: string, bundle: cc.AssetManager.Bundle) => { 516 | if (!err) { 517 | let info = bundle.getInfoWithPath(assetName, type) 518 | if (info) { 519 | let uuid = info.uuid //cc.assetManager.assets里面存储的key 520 | if (uuid) { 521 | let cachedAsset = cc.assetManager.assets.get(uuid) 522 | if (cachedAsset && cc.isValid(cachedAsset)) { 523 | this._initDynamicLoadAsset(cachedAsset) 524 | callback && callback(null, cachedAsset) 525 | return 526 | } 527 | } 528 | } 529 | 530 | bundle.load(assetName, type, (err, asset: cc.Asset) => { 531 | if (err) { 532 | cc.warn(err) 533 | //@ts-expect-error 534 | callback && callback(err) 535 | } else { 536 | this._initDynamicLoadAsset(asset) 537 | callback && callback(null, asset) 538 | } 539 | }); 540 | } else { 541 | cc.warn(err) 542 | callback && callback(err) 543 | } 544 | }) 545 | } 546 | 547 | private _initDynamicLoadAsset(asset: cc.Asset) { 548 | if (this.autoRelease) { 549 | let dynamicAsset = asset as DynamicAsset 550 | if (dynamicAsset._dynamic == DynamicState.STATIC) { 551 | dynamicAsset._dynamic = DynamicState.UNUSE 552 | } 553 | this.tryRelease(dynamicAsset._uuid) //资源加载后不使用也会被自动释放,防止资源一直占据内存,如果需要常驻 请调用asset.addRef 添加引用计数 554 | } 555 | } 556 | 557 | public preloadPrefab(bundleName: string, prefabPath: string, callback?: (err?: string) => void) { 558 | this.preloadAsset(bundleName, prefabPath, cc.Prefab, callback) 559 | } 560 | 561 | public preloadAsset(bundleName: string, assetPath: string, type: typeof cc.Asset, callback?: (err?: string) => void) { 562 | this.loadBundle(bundleName, (err: string, bundle: cc.AssetManager.Bundle) => { 563 | if (!err) { 564 | let info = bundle.getInfoWithPath(assetPath, type) 565 | if (info) { 566 | let uuid = info.uuid //cc.assetManager.assets里面存储的key 567 | if (uuid) { 568 | let cachedAsset = cc.assetManager.assets.get(uuid) 569 | if (cachedAsset && cc.isValid(cachedAsset)) { 570 | callback && callback(null) 571 | return 572 | } 573 | } 574 | } 575 | bundle.preload(assetPath, type, (err, items) => { 576 | if (err) { 577 | cc.warn(err) 578 | //@ts-expect-error 579 | callback && callback(err) 580 | } else { 581 | callback && callback(null) 582 | } 583 | }); 584 | } else { 585 | cc.warn(err) 586 | callback && callback(err) 587 | } 588 | }) 589 | } 590 | } 591 | 592 | --------------------------------------------------------------------------------