• 资源的分类如何在属性检查器里设置资源动态加载资源加载远程资源和设备资源资源的依赖和释放 资源的分类 目前的资源分成两种,一种叫做Asset,一种叫做Raw Asset。 Asset Creator 提供了名为 "Asset" 的资源类型,cc...

    最近公司从cocos2dx转cocoscreator。第一次使用,遇到很多坑。工欲善其事,必先利其器。学习官方文档是必须的。重要的地点标红,还有自己出错的地点就加了备注。以官方文档为基础。

    Cocos Creator 有一套统一的资源管理机制,在本篇教程,我们将介绍

    • 资源的分类
    • 如何在 属性检查器 里设置资源
    • 动态加载资源
    • 加载远程资源和设备资源
    • 资源的依赖和释放

    资源的分类

    目前的资源分成两种,一种叫做 Asset,一种叫做 Raw Asset。

    Asset

    Creator 提供了名为 "Asset" 的资源类型,cc.SpriteFramecc.AnimationClipcc.Prefab 等资源都属于 Asset。Asset 的加载是统一并且自动化的,相互依赖的 Asset 能够被自动预加载。

    例如,当引擎在加载场景时,会先自动加载场景关联到的资源,这些资源如果再关联其它资源,其它也会被先被加载,等加载全部完成后,场景加载才会结束。

    脚本中可以这样定义一个 Asset 属性:

    // NewScript.js
    
    cc.Class({
        extends: cc.Component,
        properties: {
    
            spriteFrame: {
                default: null,
                type: cc.SpriteFrame
            },
    
        }
    });

    Raw Asset

    Cocos2d 的一些旧 API 并没有使用上面提到的 Asset 对象,而是直接用 URL 字符串指代资源。为了兼容这些 API,我们把这类资源叫做 "Raw Asset"。图片(cc.Texture2D),声音(cc.AudioClip),粒子(cc.ParticleAsset)等资源都是 Raw Asset。Raw Asset 在脚本里由一个 url 字符串来表示,当你要在引擎中使用 Raw Asset,只要把 url 传给引擎的 API,引擎内部会自动加载这个 url 对应的资源。

    当你在脚本里声明一个类型是 cc.Texture2D 的 Raw Asset,一开始可能会想这样定义:

    cc.Class({
        extends: cc.Component,
        properties: {
    
            textureURL: {
                default: null,
                type: cc.Texture2D
            }
    
        }
    });

    这样写的问题在于,在代码中 textureURL 实际上是一个字符串,而不是 cc.Texture2D 的实例。为了不混淆 type 的语义,在 CCClass 中声明 Raw Asset 的属性时,要用 url: cc.Texture2D 而不是 type: cc.Texture2D

     

    cc.Class({
        extends: cc.Component,
        properties: {
    
            textureURL: {
                default: "",
                url: cc.Texture2D
            }
    
        }
    });

    如何在属性检查器里设置资源

    不论是 Asset 还是 Raw Asset,只要在脚本中定义好类型,就能直接在 属性检查器 很方便地设置资源。假设我们有这样一个组件:

    // NewScript.js
    
    cc.Class({
        extends: cc.Component,
        properties: {
    
            textureURL: {
                default: "",
                url: cc.Texture2D
            },
            spriteFrame: {
                default: null,
                type: cc.SpriteFrame
            },
    
        }
    });

    将它添加到场景后,属性检查器 里是这样的:

    asset-in-properties-null

    接下来我们从 资源管理器 里面分别将一张 Texture 和一个 SpriteFrame 拖到 属性检查器 的对应属性中:

    asset-in-properties-dnd

    结果如下:

    asset-in-properties-dnd

    这样就能在脚本里直接拿到设置好的资源:

        onLoad: function () {
            var spriteFrame = this.spriteFrame;
            var textureURL = this.textureURL;
    
            spriteFrame.setTexture(textureURL);
        }
    

    在 属性检查器 里设置资源虽然很直观,但资源只能在场景里预先设好,没办法动态切换。如果需要动态切换,你需要看看下面的内容。

     

    动态加载

     

    动态加载资源要注意两点,一是所有需要通过脚本动态加载的资源,都必须放置在 resources 文件夹或它的子文件夹下resources 需要在 assets 文件夹中手工创建,并且必须位于 assets 的根目录,就像这样:

    注意:拷贝项目内的资源,记住不能拷贝.meta资源。这会导致cocoscreator资源管理器添加不了资源。

    asset-in-properties-null

    这里的 image/imageprefabanimfont 都是常见的 Asset,而 imageaudio 则是常见的 Raw Asset。

    resources 文件夹里面的资源,可以关联依赖到文件夹外部的其它资源,同样也可以被外部场景或资源引用到。项目构建时,除了已在 构建发布 面板勾选的场景外,resources 文件夹里面的所有资源,连同它们关联依赖的 resources 文件夹外部的资源,都会被导出。如果一份资源不需要由脚本直接动态加载,那么千万不要放在 resources 文件夹里。

    第二个要注意的是 Creator 相比之前的 Cocos2d-html5,资源动态加载的时都是异步的,需要在回调函数中获得载入的资源。这么做是因为 Creator 除了场景关联的资源,没有另外的资源预加载列表,动态加载的资源是真正的动态加载。

    动态加载 Asset

    Creator 提供了 cc.loader.loadRes 这个 API 来专门加载那些位于 resources 目录下的 Asset。和 cc.loader.load 不同的是,loadRes 一次只能加载单个 Asset。调用时,你只要传入相对 resources 的路径即可,并且路径的结尾处不能包含文件扩展名。

    // 加载 Prefab
    cc.loader.loadRes("test assets/prefab", function (err, prefab) {
        var newNode = cc.instantiate(prefab);
        cc.director.getScene().addChild(newNode);
    });
    
    // 加载 AnimationClip
    var self = this;
    cc.loader.loadRes("test assets/anim", function (err, clip) {
        self.node.getComponent(cc.Animation).addClip(clip, "anim");
    });
    
    // 加载 SpriteAtlas(图集),并且获取其中的一个 SpriteFrame
    // 注意 atlas 资源文件(plist)通常会和一个同名的图片文件(png)放在一个目录下, 所以需要在第二个参数指定资源类型
    cc.loader.loadRes("test assets/sheep", cc.SpriteAtlas, function (err, atlas) {
        var frame = atlas.getSpriteFrame('sheep_down_0');
        sprite.spriteFrame = frame;
    });

    加载独立的 SpriteFrame

    图片设置为 Sprite 后,将会在 资源管理器 中生成一个对应的 SpriteFrame。但如果直接加载 test assets/image,得到的类型将会是 cc.Texture2D。你必须指定第二个参数为资源的类型,才能加载到图片生成的 cc.SpriteFrame:

    // 加载 SpriteFrame
    var self = this;
    cc.loader.loadRes("test assets/image", cc.SpriteFrame, function (err, spriteFrame) {
        self.node.getComponent(cc.Sprite).spriteFrame = spriteFrame;
    });

    如果指定了类型参数,就会在路径下查找指定类型的资源。当你在同一个路径下同时包含了多个重名资源(例如同时包含 player.clip 和 player.psd),或者需要获取“子资源”(例如获取 Texture2D 生成的 SpriteFrame),就需要声明类型。

    资源释放

    loadRes 加载进来的单个资源如果需要释放,可以调用 cc.loader.releaseResreleaseRes 可以传入和 loadRes 相同的路径和类型参数。

     

    cc.loader.releaseRes("test assets/image", cc.SpriteFrame);
    cc.loader.releaseRes("test assets/anim");

     

    此外,你也可以使用 cc.loader.releaseAsset 来释放特定的 Asset 实例。

     

    cc.loader.releaseAsset(spriteFrame);

     

    动态加载 Raw Asset

    Raw Asset 可以直接使用 url 从远程服务器上加载,也可以从项目中动态加载。对远程加载而言,原先 Cocos2d 的加载方式不变,使用 cc.loader.load 即可。对项目里的 Raw Asset,加载方式和 Asset 一样:

     

    // 加载 Texture,不需要后缀名
    cc.loader.loadRes("test assets/image", function (err, texture) {
        ...
    });

     

    cc.url.raw

    Raw Asset 加载成功后,如果需要传给一些 url 形式的 API,还是需要给出完整路径才行。你需要用 cc.url.raw 进行一次 url 的转换:

     

    // 原 url 会报错!文件找不到
    var texture = cc.textureCache.addImage("resources/test assets/image.png");
    
    // 改用 cc.url.raw,此时需要声明 resources 目录和文件后缀名
    var realUrl = cc.url.raw("resources/test assets/image.png");
    var texture = cc.textureCache.addImage(realUrl);

     

    资源批量加载

    cc.loader.loadResDir 可以加载相同路径下的多个资源:

     

    // 加载 test assets 目录下所有资源
    cc.loader.loadResDir("test assets", function (err, assets) {
        // ...
    });
    
    // 加载 sheep.plist 图集中的所有 SpriteFrame
    cc.loader.loadResDir("test assets/sheep", cc.SpriteFrame, function (err, assets) {
        // assets 是一个 SpriteFrame 数组,已经包含了图集中的所有 SpriteFrame。
        // 而 loadRes('test assets/sheep', cc.SpriteAtlas, function (err, atlas) {...}) 获得的则是整个 SpriteAtlas 对象。
    });

     

    加载远程资源和设备资源

    在目前的 Cocos Creator 中,我们支持加载远程贴图资源,这对于加载用户头像等需要向服务器请求的贴图很友好,需要注意的是,这需要开发者直接调用 cc.loader.load。同时,如果用户用其他方式下载了资源到本地设备存储中,也需要用同样的 API 来加载,上文中的 loadRes 等 API 只适用于应用包内的资源和热更新的本地资源。下面是这个 API 的用法:

     

    // 远程 url 带图片后缀名
    var remoteUrl = "http://unknown.org/someres.png";
    cc.loader.load(remoteUrl, function (err, texture) {
        // Use texture to create sprite frame
    });
    
    // 远程 url 不带图片后缀名,此时必须指定远程图片文件的类型
    remoteUrl = "http://unknown.org/emoji?id=124982374";
    cc.loader.load({url: remoteUrl, type: 'png'}, function () {
        // Use texture to create sprite frame
    });
    
    // 用绝对路径加载设备存储内的资源,比如相册
    var absolutePath = "/dara/data/some/path/to/image.png"
    cc.loader.load(absolutePath, function () {
        // Use texture to create sprite frame
    });

     

    目前的此类手动资源加载还有一些限制,对用户影响比较大的是:

    1. 远程加载不支持图片文件以外类型的资源(已在 1.5/1.6 支持计划中)
    2. 这种加载方式只支持 raw asset 资源类型,不支持 SpriteFrame、SpriteAtlas、Tilemap 等资源的直接加载和解析(需要后续版本中的 Assets Bundle 支持)
    3. Web 端的远程加载受到浏览器的 CORS 跨域策略限制,如果对方服务器禁止跨域访问,那么会加载失败,而且在 WebGL 渲染模式下,即便对方服务器允许 http 请求成功之后也无法渲染,这是 WebGL 的安全策略的限制

    资源的依赖和释放

    在加载完资源之后,所有的资源都会临时被缓存到 cc.loader 中,以避免重复加载资源时发送无意义的 http 请求,当然,缓存的内容都会占用内存,有些资源可能用户不再需要了,想要释放它们,这里介绍一下在做资源释放时需要注意的事项。

    首先最为重要的一点就是:资源之间是互相依赖的。

    比如下图,Prefab 资源中的 Node 包含 Sprite 组件,Sprite 组件依赖于 SpriteFrame,SpriteFrame 资源依赖于 Texture 资源,而 Prefab,SpriteFrame 和 Texture 资源都被 cc.loader 缓存起来了。这样做的好处是,有可能有另一个 SpriteAtlas 资源依赖于同样的一个 SpriteFrame 和 Texture,那么当你手动加载这个 SpriteAtlas 的时候,就不需要再重新请求贴图资源了,cc.loader 会自动使用缓存中的资源。

    在搞明白资源的相互引用之后,资源释放的问题也就呼之欲出了,当你选择释放一个 Prefab 时,我们是不会自动释放它依赖的其他资源的,因为有可能这些依赖资源还有其他的用处。所以用户在释放资源时经常会问我们,为什么我都把资源释放了,内存占用还是居高不下?原因就是真正占用内存的贴图等基础资源并不会随着你释放 Prefab 或者 SpriteAtlas 而被释放。

    接下来要介绍问题的另一个核心:JavaScript 中无法跟踪对象引用。

    在 JavaScript 这种脚本语言中,由于其弱类型特性,以及为了代码的便利,往往是不包含内存管理功能的,所有对象的内存都由垃圾回收机制来管理。这就导致 JS 层逻辑永远不知道一个对象会在什么时候被释放,这意味着引擎无法通过类似引用计数的机制来管理外部对象对资源的引用,也无法严谨得统计资源是否不再被需要了。基于以上的原因,目前 cc.loader 的设计实际上是依赖于用户根据游戏逻辑管理资源,用户可以决定在某一时刻不再需要某些资源以及它依赖的资源,立即将它们在 cc.loader 中的缓存释放。也可以选择在释放依赖资源的时候,防止部分共享资源被释放。下面是一个简单的示例:

     

    // 直接释放某个贴图
    cc.loader.release(texture);
    // 释放一个 prefab 以及所有它依赖的资源
    var deps = cc.loader.getDependsRecursively('prefabs/sample');
    cc.loader.release(deps);
    // 如果在这个 prefab 中有一些和场景其他部分共享的资源,你不希望它们被释放,有两种方法:
    // 1. 显式声明禁止某个资源的自动释放
    cc.loader.setAutoRelease(texture2d, false);
    // 2. 将这个资源从依赖列表中删除
    var deps = cc.loader.getDependsRecursively('prefabs/sample');
    var index = deps.indexOf(texture2d._uuid);
    if (index !== -1)
        deps.splice(index, 1);
    cc.loader.release(deps);

     

    最后一个值得关注的要点:JavaScript 的垃圾回收是延迟的。

    想象一种情况,当你释放了 cc.loader 对某个资源的引用之后,由于考虑不周的原因,游戏逻辑再次请求了这个资源。此时垃圾回收还没有开始(垃圾回收的时机不可控),或者你的游戏逻辑某处,仍然持有一个对于这个旧资源的引用,那么意味着这个资源还存在内存中,但是 cc.loader 已经访问不到了,所以会重新加载它。这造成这个资源在内存中有两份同样的拷贝,浪费了内存。如果只是一个资源还好,但是如果类似的资源很多,甚至不止一次被重复加载,这对于内存的压力是有可能很高的。如果观察到游戏使用的内存曲线有这样的异常,请仔细检查游戏逻辑,是否存在泄漏,如果没有的话,垃圾回收机制是会正常回收这些内存的。

    以上就是管理资源依赖和释放时需要注意的细节,这部分的功能和 API 设计还没有完全定案,我们还是希望尽力给大家带来尽可能方便的引擎 API,所以后续也会尝试一些其他的办法提升友好度,届时会更新这篇文档。

    展开全文
  • 动态加载资源要注意两点,一是所有需要通过脚本动态加载资源,都必须放置在resources文件夹或它的子文件夹下。resources需要在 assets 文件夹中手工创建,并且必须位于 assets 的根目录 resources文件夹中的...

    动态加载资源要注意两点,一是所有需要通过脚本动态加载的资源,都必须放置在 resources 文件夹或它的子文件夹下。resources 需要在 assets 文件夹中手工创建,并且必须位于 assets 的根目录

     

    resources 文件夹中的资源,可以引用文件夹外部的其它资源,同样也可以被外部场景或资源引用到。项目构建时,除了已在 构建发布 面板勾选的场景外,resources 文件夹中的所有资源,连同它们关联依赖的 resources 文件夹外部的资源,都会被导出。

    如果一份资源仅仅是被 resources 中的其它资源所依赖,而不需要直接被 cc.loader.loadRes 调用,那么 请不要 放在 resources 文件夹里。否则会增大包体和 settings.js 的大小,并且项目中无用的资源,将无法在构建的过程中自动剔除。同时在构建过程中,JSON 的自动合并策略也将受到影响,无法尽可能将零碎的 JSON 合并起来。

     

    // 动态加载 AnimationClip

    var self = this;

    cc.loader.loadRes("test assets/anim", function (err, clip) {

    self.node.getComponent(cc.Animation).addClip(clip, "anim");

    });

     

    第二个要注意的是 Creator 相比之前的 Cocos2d-JS,资源动态加载的时候都是 异步 的,需要在回调函数中获得载入的资源。这么做是因为 Creator 除了场景关联的资源,没有另外的资源预加载列表,动态加载的资源是真正的动态加载。

    动态加载 Asset

    Creator 提供了 cc.loader.loadRes 这个 API 来专门加载那些位于 resources 目录下的 Asset。和 cc.loader.load 不同的是,loadRes 一次只能加载单个 Asset。调用时,你只要传入相对 resources 的路径即可,并且路径的结尾处 不能 包含文件扩展名。

    // 加载 Prefab cc.loader.loadRes("test assets/prefab", function (err, prefab) { var newNode = cc.instantiate(prefab); cc.director.getScene().addChild(newNode); }); // 加载 AnimationClip var self = this; cc.loader.loadRes("test assets/anim", function (err, clip) { self.node.getComponent(cc.Animation).addClip(clip, "anim"); });

    加载 SpriteFrame

    图片设置为 Sprite 后,将会在 资源管理器 中生成一个对应的 SpriteFrame。但如果直接加载 test assets/image,得到的类型将会是 cc.Texture2D。你必须指定第二个参数为资源的类型,才能加载到图片生成的 cc.SpriteFrame:

    // 加载 SpriteFrame var self = this; cc.loader.loadRes("test assets/image", cc.SpriteFrame, function (err, spriteFrame) { self.node.getComponent(cc.Sprite).spriteFrame = spriteFrame; });

    如果指定了类型参数,就会在路径下查找指定类型的资源。当你在同一个路径下同时包含了多个重名资源(例如同时包含 player.clip 和 player.psd),或者需要获取 “子资源”(例如获取 Texture2D 生成的 SpriteFrame),就需要声明类型。

    加载图集中的 SpriteFrame

    对从 TexturePacker 等第三方工具导入的图集而言,如果要加载其中的 SpriteFrame,则只能先加载图集,再获取其中的 SpriteFrame。这是一种特殊情况。

    // 加载 SpriteAtlas(图集),并且获取其中的一个 SpriteFrame // 注意 atlas 资源文件(plist)通常会和一个同名的图片文件(png)放在一个目录下, 所以需要在第二个参数指定资源类型 cc.loader.loadRes("test assets/sheep", cc.SpriteAtlas, function (err, atlas) { var frame = atlas.getSpriteFrame('sheep_down_0'); sprite.spriteFrame = frame; });

    资源释放

    loadRes 加载进来的单个资源如果需要释放,可以调用 cc.loader.releaseRes,releaseRes 可以传入和 loadRes 相同的路径和类型参数。

    cc.loader.releaseRes("test assets/image", cc.SpriteFrame); cc.loader.releaseRes("test assets/anim");

    此外,你也可以使用 cc.loader.releaseAsset 来释放特定的 Asset 实例。

    cc.loader.releaseAsset(spriteFrame);

    资源批量加载

    cc.loader.loadResDir 可以加载相同路径下的多个资源:

    // 加载 test assets 目录下所有资源 cc.loader.loadResDir("test assets", function (err, assets) { // ... }); // 加载 test assets 目录下所有 SpriteFrame,并且获取它们的路径 cc.loader.loadResDir("test assets", cc.SpriteFrame, function (err, assets, urls) { // ... });

    加载远程资源和设备资源

    在目前的 Cocos Creator 中,我们支持加载远程贴图资源,这对于加载用户头像等需要向服务器请求的贴图很友好,需要注意的是,这需要开发者直接调用 cc.loader.load。同时,如果用户用其他方式下载了资源到本地设备存储中,也需要用同样的 API 来加载,上文中的 loadRes 等 API 只适用于应用包内的资源和热更新的本地资源。下面是这个 API 的用法:

    // 远程 url 带图片后缀名 var remoteUrl = "http://unknown.org/someres.png"; cc.loader.load(remoteUrl, function (err, texture) { // Use texture to create sprite frame }); // 远程 url 不带图片后缀名,此时必须指定远程图片文件的类型 remoteUrl = "http://unknown.org/emoji?id=124982374"; cc.loader.load({url: remoteUrl, type: 'png'}, function () { // Use texture to create sprite frame }); // 用绝对路径加载设备存储内的资源,比如相册 var absolutePath = "/dara/data/some/path/to/image.png" cc.loader.load(absolutePath, function () { // Use texture to create sprite frame });

    目前的此类手动资源加载还有一些限制,对用户影响比较大的是:

    1. 原生平台远程加载不支持图片文件以外类型的资源

    2. 这种加载方式只支持图片、声音、文本等原生资源类型,不支持 SpriteFrame、SpriteAtlas、Tilemap 等资源的直接加载和解析(需要后续版本中的 AssetBundle 支持)

    3. Web 端的远程加载受到浏览器的 CORS 跨域策略限制,如果对方服务器禁止跨域访问,那么会加载失败,而且由于 WebGL 安全策略的限制,即便对方服务器允许 http 请求成功之后也无法渲染。

    资源的依赖和释放

    在加载完资源之后,所有的资源都会临时被缓存到 cc.loader 中,以避免重复加载资源时发送无意义的 http 请求,当然,缓存的内容都会占用内存,有些资源可能用户不再需要了,想要释放它们,这里介绍一下在做资源释放时需要注意的事项。

    首先最为重要的一点就是:资源之间是互相依赖的。

    比如下图,Prefab 资源中的 Node 包含 Sprite 组件,Sprite 组件依赖于 SpriteFrame,SpriteFrame 资源依赖于 Texture 资源,而 Prefab,SpriteFrame 和 Texture 资源都被 cc.loader 缓存起来了。这样做的好处是,有可能有另一个 SpriteAtlas 资源依赖于同样的一个 SpriteFrame 和 Texture,那么当你手动加载这个 SpriteAtlas 的时候,就不需要再重新请求贴图资源了,cc.loader 会自动使用缓存中的资源。

     

    在搞明白资源的相互引用之后,资源释放的问题也就呼之欲出了,当你选择释放一个 Prefab 时,我们是不会自动释放它依赖的其他资源的,因为有可能这些依赖资源还有其他的用处。所以用户在释放资源时经常会问我们,为什么我都把资源释放了,内存占用还是居高不下?原因就是真正占用内存的贴图等基础资源并不会随着你释放 Prefab 或者 SpriteAtlas 而被释放。

    接下来要介绍问题的另一个核心:JavaScript 中无法跟踪对象引用。

    在 JavaScript 这种脚本语言中,由于其弱类型特性,以及为了代码的便利,往往是不包含内存管理功能的,所有对象的内存都由垃圾回收机制来管理。这就导致 JS 层逻辑永远不知道一个对象会在什么时候被释放,这意味着引擎无法通过类似引用计数的机制来管理外部对象对资源的引用,也无法严谨得统计资源是否不再被需要了。基于以上的原因,目前 cc.loader 的设计实际上是依赖于用户根据游戏逻辑管理资源,用户可以决定在某一时刻不再需要某些资源以及它依赖的资源,立即将它们在 cc.loader 中的缓存释放。也可以选择在释放依赖资源的时候,防止部分共享资源被释放。下面是一个简单的示例:

    // 直接释放某个贴图 cc.loader.release(texture); // 释放一个 prefab 以及所有它依赖的资源 var deps = cc.loader.getDependsRecursively('prefabs/sample'); cc.loader.release(deps); // 如果在这个 prefab 中有一些和场景其他部分共享的资源,你不希望它们被释放,可以将这个资源从依赖列表中删除 var deps = cc.loader.getDependsRecursively('prefabs/sample'); var index = deps.indexOf(texture2d._uuid); if (index !== -1) deps.splice(index, 1); cc.loader.release(deps);

    最后一个值得关注的要点:JavaScript 的垃圾回收是延迟的。

    想象一种情况,当你释放了 cc.loader 对某个资源的引用之后,由于考虑不周的原因,游戏逻辑再次请求了这个资源。此时垃圾回收还没有开始(垃圾回收的时机不可控),或者你的游戏逻辑某处,仍然持有一个对于这个旧资源的引用,那么意味着这个资源还存在内存中,但是 cc.loader 已经访问不到了,所以会重新加载它。这造成这个资源在内存中有两份同样的拷贝,浪费了内存。如果只是一个资源还好,但是如果类似的资源很多,甚至不止一次被重复加载,这对于内存的压力是有可能很高的。如果观察到游戏使用的内存曲线有这样的异常,请仔细检查游戏逻辑,是否存在泄漏,如果没有的话,垃圾回收机制是会正常回收这些内存的。

    以上就是管理资源依赖和释放时需要注意的细节。

    展开全文
  • CocosCreator 动态加载与远程加载资源汇总 概述 所有需要通过 cc.loader.loadRes 动态加载资源,都必须放置在 resources 文件夹或它的子文件夹下。如果一份资源仅仅是被 resources 中的其它资源所依赖,而不需要...

    CocosCreator 动态加载与远程加载资源汇总

    概述

    • 所有需要通过 cc.loader.loadRes 动态加载的资源,都必须放置在 resources 文件夹或它的子文件夹下。如果一份资源仅仅是被 resources 中的其它资源所依赖,而不需要直接被 cc.loader.loadRes 调用,那么请不要放在 resources 文件夹里。

    • resources 文件夹里面的资源,可以引用文件夹外部的其它资源,同样也可以被外部场景或资源引用到。项目构建时,除了已在构建发布面板勾选的场景外,resources 文件夹里面的所有资源,连同它们关联依赖的 resources 文件夹外部的资源,都会被导出。

    • 关于 resources 的更多信息,可参考文档:获取和加载资源 - 动态加载 Asset

    动态加载资源

    资源下载

    链接: 百度网盘 提取码: pj6f

    动态加载预制资源

    
    newNode(position, callBack) {
        let newNode = null;
        if (!this.nodePool) {
            this.nodePool = new cc.NodePool();
        }
        if (this.nodePool.size() > 0) {
            newNode = this.nodePool.get();
            this.setNode(newNode, position, callBack);
        } else {
            cc.loader.loadRes("prefab/leftOright", cc.Prefab, function (err, prefab) {
                if (err) {
                    return;
                }
                newNode = cc.instantiate(prefab);
                this.setNode(newNode, position, callBack);
            }.bind(this));
        }
    },
    

    动态加载字体

        setCurrentWater(type, number) {
            let currentWaterLabel = this.targetWaterNode.getChildByName("current").getComponent(cc.Label);
            currentWaterLabel.string = number;
            this.setFont("fonts/Yellowchars", currentWaterLabel);
        },
    
        /**
         * 设置字体
         * @param {*} url
         * @param {*} label 
         */
        setFont(url, label) {
            //动态加载字体
            cc.loader.loadRes(url, cc.Font, function (err, res) {
                if (err) {
                    return;
                }
                label.font = res;
            });
        },
    

    动态加载龙骨资源

    异步动态加载龙骨资源

    //异步加载龙骨动画
    this.runAsyncGetDragonRes("huajipao", this._currentTimeScale)
        .then(function (data) {
            console.log("回调:" + data.timeScale);
            that.armatureDisPlay = data;
        })
        .catch(function (reason) {
            console.log(reason);
        });
    
    
    /**
     * 获取龙骨资源并播放动画
     * @param {*} dragonName 龙骨文件夹名称 
     * @param {*} timeScale 动画缩放率
     */
    runAsyncGetDragonRes(dragonName, timeScale) {
        var p = new Promise(function (resolve, reject) {
            cc.loader.loadResDir('dragon/' + dragonName, function (err, assets) {
                if (err) {
                    return;
                }
                if (assets.length <= 0) {
                    return;
                }
                var newHero = new cc.Node();
                
                this.node.getChildByName("players").addChild(newHero);
                newHero.setPosition(cc.v2(0, 0));
                newHero.setScale(1, 1);
                let armatureDisPlay = newHero.addComponent(dragonBones.ArmatureDisplay);
                for (let i in assets) {
                    if (assets[i] instanceof dragonBones.DragonBonesAsset) {
                        armatureDisPlay.dragonAsset = assets[i];
                    }
                    if (assets[i] instanceof dragonBones.DragonBonesAtlasAsset) {
                        armatureDisPlay.dragonAtlasAsset = assets[i];
                    }
                }
                armatureDisPlay.armatureName = 'Armature';//龙骨支干
                armatureDisPlay.playAnimation('newAnimation');//龙骨动画名称
                armatureDisPlay.timeScale = timeScale;
                resolve(armatureDisPlay);
            }.bind(this));
        }.bind(this));
        return p;
    },
    

    动态加载声音

     cc.loader.loadRes("music/bgm", cc.AudioClip, function (err, clip) {
         if (err) {
             return;
         }
         this._dynamicAudio = clip;
     }.bind(this));
    
    
    playBgmAudio() {
      if (Global.isAudio && this._dynamicAudio && (!this.bgmAudioId || this.getState(this.bgmAudioId) != cc.audioEngine.AudioState.PLAYING)) {
        this.bgmAudioId = cc.audioEngine.play(this._dynamicAudio, true, 1);
      }
    },
        
    getState(id) {
      return cc.audioEngine.getState(id);
    },
    

    远程加载

    远程加载图片

    // 设置图片
    function setImg(imgNode, spriteFrame) {
        imgNode.getComponent(cc.Sprite).spriteFrame = spriteFrame;
    }
    // 加载远程资源
    function loadImgByUrl(imgNode, remoteUrl, imageType) {
        if (!imageType) {
            imageType = "png";
        }
        cc.loader.load({
            url: remoteUrl,
            type: imageType
        }, function (err, texture) {
            if (err) {
                return;
            }
            setImg(imgNode, new cc.SpriteFrame(texture));
        });
    }
    // 用绝对路径加载设备存储内的资源,比如相册
    function loadLocal(imgNode, absolutePath) {
        cc.loader.load(absolutePath, function (err, texture) {
            if (err) {
                return;
            }
            setImg(imgNode, new cc.SpriteFrame(texture));
        });
    }
    
    展开全文
  • cc.loadRes("Sound/game_turntable.mp3", cc.AudioClip, (err, audioClip)=> { console.log( "==>"+ typeof audioClip); });

    cc.loadRes("Sound/game_turntable.mp3", cc.AudioClip, (err, audioClip)=> {

    console.log( "==>"+ typeof audioClip);

    });

    展开全文
  • Cocos Creator 有一套统一的资源管理机制,在本篇教程,我们将介绍 资源属性的声明 如何在 属性检查器 里设置资源 动态加载资源 加载远程资源和设备资源 资源的依赖和释放 资源属性的声明 在 Creator 中,所有继承...

    转自官方文档

    Cocos Creator 有一套统一的资源管理机制,在本篇教程,我们将介绍

    • 资源属性的声明
    • 如何在 属性检查器 里设置资源
    • 动态加载资源
    • 加载远程资源和设备资源
    • 资源的依赖和释放

    资源属性的声明

    在 Creator 中,所有继承自 cc.Asset 的类型都统称资源,如 cc.Texture2D, cc.SpriteFrame, cc.AnimationClip, cc.Prefab 等。它们的加载是统一并且自动化的,相互依赖的资源能够被自动预加载。

    例如,当引擎在加载场景时,会先自动加载场景关联到的资源,这些资源如果再关联其它资源,其它也会被先被加载,等加载全部完成后,场景加载才会结束。

    脚本中可以这样定义一个 Asset 属性:

    // NewScript.js
    
    cc.Class({
    	extends: cc.Component,
    	properties: {
    		spriteFrame: {
            	default: null,
            	type: cc.SpriteFrame
        	},
    	}
    });
    

    如何在属性检查器里设置资源

    只要在脚本中定义好类型,就能直接在 属性检查器 很方便地设置资源。假设我们创建了这样一个脚本:

    // NewScript.js
    
    cc.Class({
    	extends: cc.Component,
    	properties: {
    
        	texture: {
            	default: null,
            	type: cc.Texture2D
        	},
        	spriteFrame: {
           		default: null,
            	type: cc.SpriteFrame
        	},
    	}
    });
    

    将它添加到节点后,在 属性检查器 中是这样的:
    在这里插入图片描述
    接下来我们从 资源管理器 里面分别将一张 Texture 和一个 SpriteFrame 拖到 属性检查器 的对应属性中:
    asset-in-properties-dnd
    结果如下:
    asset-in-properties-dnd
    这样就能在脚本里直接拿到设置好的资源:

    onLoad: function () {
        var spriteFrame = this.spriteFrame;
        var texture = this.texture;
    
        spriteFrame.setTexture(texture);
    }
    

    在 属性检查器 里设置资源虽然很直观,但资源只能在场景里预先设好,没办法动态切换。如果需要动态切换,你需要看看下面的内容。

    动态加载

    动态加载资源要注意两点,一是所有需要通过脚本动态加载的资源,都必须放置在 resources 文件夹或它的子文件夹下。resources 需要在 assets 文件夹中手工创建,并且必须位于 assets 的根目录,就像这样:
    在这里插入图片描述
    resources 文件夹中的资源,可以引用文件夹外部的其它资源,同样也可以被外部场景或资源引用到。项目构建时,除了已在 构建发布 面板勾选的场景外,resources 文件夹中的所有资源,连同它们关联依赖的 resources 文件夹外部的资源,都会被导出。

    如果一份资源仅仅是被 resources 中的其它资源所依赖,而不需要直接被 cc.loader.loadRes 调用,那么 请不要 放在 resources 文件夹里。否则会增大包体和 settings.js 的大小,并且项目中无用的资源,将无法在构建的过程中自动剔除。同时在构建过程中,JSON 的自动合并策略也将受到影响,无法尽可能将零碎的 JSON 合并起来。

    第二个要注意的是 Creator 相比之前的 Cocos2d-JS,资源动态加载的时候都是 异步 的,需要在回调函数中获得载入的资源。这么做是因为 Creator 除了场景关联的资源,没有另外的资源预加载列表,动态加载的资源是真正的动态加载。

    动态加载 Asset
    Creator 提供了 cc.loader.loadRes 这个 API 来专门加载那些位于 resources 目录下的 Asset。和 cc.loader.load 不同的是,loadRes 一次只能加载单个 Asset。调用时,你只要传入相对 resources 的路径即可,并且路径的结尾处 不能 包含文件扩展名。

    // 加载 Prefab
    cc.loader.loadRes("test assets/prefab", function (err, prefab) {
    	var newNode = cc.instantiate(prefab);
    	cc.director.getScene().addChild(newNode);
    });
    
    // 加载 AnimationClip
    	var self = this;
    	cc.loader.loadRes("test assets/anim", function (err, clip) {
    		self.node.getComponent(cc.Animation).addClip(clip, "anim");
    	});
    

    加载 SpriteFrame
    图片设置为 Sprite 后,将会在 资源管理器 中生成一个对应的 SpriteFrame。但如果直接加载 test assets/image,得到的类型将会是 cc.Texture2D。你必须指定第二个参数为资源的类型,才能加载到图片生成的 cc.SpriteFrame:

    // 加载 SpriteFrame
    var self = this;
    cc.loader.loadRes("test assets/image", cc.SpriteFrame, function (err, spriteFrame) {
        self.node.getComponent(cc.Sprite).spriteFrame = spriteFrame;
    });
    

    如果指定了类型参数,就会在路径下查找指定类型的资源。当你在同一个路径下同时包含了多个重名资源(例如同时包含 player.clip 和 player.psd),或者需要获取 “子资源”(例如获取 Texture2D 生成的 SpriteFrame),就需要声明类型。

    加载图集中的 SpriteFrame
    对从 TexturePacker 等第三方工具导入的图集而言,如果要加载其中的 SpriteFrame,则只能先加载图集,再获取其中的 SpriteFrame。这是一种特殊情况。

    // 加载 SpriteAtlas(图集),并且获取其中的一个 SpriteFrame
    // 注意 atlas 资源文件(plist)通常会和一个同名的图片文件(png)放在一个目录下, 所以需要在第二个参数指定资源类型
    cc.loader.loadRes("test assets/sheep", cc.SpriteAtlas, function (err, atlas) {
        var frame = atlas.getSpriteFrame('sheep_down_0');
        sprite.spriteFrame = frame;
    });
    

    资源释放
    loadRes 加载进来的单个资源如果需要释放,可以调用 cc.loader.releaseRes,releaseRes 可以传入和 loadRes 相同的路径和类型参数。

    cc.loader.releaseRes("test assets/image", cc.SpriteFrame);
    cc.loader.releaseRes("test assets/anim");
    

    此外,你也可以使用 cc.loader.releaseAsset 来释放特定的 Asset 实例。

    cc.loader.releaseAsset(spriteFrame);
    

    资源批量加载
    cc.loader.loadResDir 可以加载相同路径下的多个资源:

    // 加载 test assets 目录下所有资源
    cc.loader.loadResDir("test assets", function (err, assets) {
        // ...
    });
    
    // 加载 test assets 目录下所有 SpriteFrame,并且获取它们的路径
    cc.loader.loadResDir("test assets", cc.SpriteFrame, function (err, assets, urls) {
        // ...
    });
    

    加载远程资源和设备资源

    在目前的 Cocos Creator 中,我们支持加载远程贴图资源,这对于加载用户头像等需要向服务器请求的贴图很友好,需要注意的是,这需要开发者直接调用 cc.loader.load。同时,如果用户用其他方式下载了资源到本地设备存储中,也需要用同样的 API 来加载,上文中的 loadRes 等 API 只适用于应用包内的资源和热更新的本地资源。下面是这个 API 的用法:

    // 远程 url 带图片后缀名
    var remoteUrl = "http://unknown.org/someres.png";
    cc.loader.load(remoteUrl, function (err, texture) {
        // Use texture to create sprite frame
    });
    
    // 远程 url 不带图片后缀名,此时必须指定远程图片文件的类型
    remoteUrl = "http://unknown.org/emoji?id=124982374";
    cc.loader.load({url: remoteUrl, type: 'png'}, function () {
        // Use texture to create sprite frame
    });
    
    // 用绝对路径加载设备存储内的资源,比如相册
    var absolutePath = "/dara/data/some/path/to/image.png"
    cc.loader.load(absolutePath, function () {
        // Use texture to create sprite frame
    });
    

    目前的此类手动资源加载还有一些限制,对用户影响比较大的是:

    1. 原生平台远程加载不支持图片文件以外类型的资源
    2. 这种加载方式只支持图片、声音、文本等原生资源类型,不支持这种加载方式只支持图片、声音、文本等原生资源类型,不支持SpriteFrame、SpriteAtlas、Tilemap 等资源的直接加载和解析(需要后续版本中的 AssetBundle 支持)
    3. Web 端的远程加载受到浏览器的 CORS 跨域策略限制,如果对方服务器禁止跨域访问,那么会加载失败,而且由于 WebGL安全策略的限制,即便对方服务器允许 http 请求成功之后也无法渲染。

    资源的依赖和释放

    在加载完资源之后,所有的资源都会临时被缓存到 cc.loader 中,以避免重复加载资源时发送无意义的 http 请求,当然,缓存的内容都会占用内存,有些资源可能用户不再需要了,想要释放它们,这里介绍一下在做资源释放时需要注意的事项。

    首先最为重要的一点就是:资源之间是互相依赖的。

    比如下图,Prefab 资源中的 Node 包含 Sprite 组件,Sprite 组件依赖于 SpriteFrame,SpriteFrame 资源依赖于 Texture 资源,而 Prefab,SpriteFrame 和 Texture 资源都被 cc.loader 缓存起来了。这样做的好处是,有可能有另一个 SpriteAtlas 资源依赖于同样的一个 SpriteFrame 和 Texture,那么当你手动加载这个 SpriteAtlas 的时候,就不需要再重新请求贴图资源了,cc.loader 会自动使用缓存中的资源。
    在这里插入图片描述
    在搞明白资源的相互引用之后,资源释放的问题也就呼之欲出了,当你选择释放一个 Prefab 时,我们是不会自动释放它依赖的其他资源的,因为有可能这些依赖资源还有其他的用处。所以用户在释放资源时经常会问我们,为什么我都把资源释放了,内存占用还是居高不下?原因就是真正占用内存的贴图等基础资源并不会随着你释放 Prefab 或者 SpriteAtlas 而被释放。

    接下来要介绍问题的另一个核心:JavaScript 中无法跟踪对象引用

    在 JavaScript 这种脚本语言中,由于其弱类型特性,以及为了代码的便利,往往是不包含内存管理功能的,所有对象的内存都由垃圾回收机制来管理。这就导致 JS 层逻辑永远不知道一个对象会在什么时候被释放,这意味着引擎无法通过类似引用计数的机制来管理外部对象对资源的引用,也无法严谨得统计资源是否不再被需要了。基于以上的原因,目前 cc.loader 的设计实际上是依赖于用户根据游戏逻辑管理资源,用户可以决定在某一时刻不再需要某些资源以及它依赖的资源,立即将它们在 cc.loader 中的缓存释放。也可以选择在释放依赖资源的时候,防止部分共享资源被释放。下面是一个简单的示例:

    // 直接释放某个贴图
    cc.loader.release(texture);
    // 释放一个 prefab 以及所有它依赖的资源
    var deps = cc.loader.getDependsRecursively('prefabs/sample');
    cc.loader.release(deps);
    // 如果在这个 prefab 中有一些和场景其他部分共享的资源,你不希望它们被释放,可以将这个资源从依赖列表中删除
    var deps = cc.loader.getDependsRecursively('prefabs/sample');
    var index = deps.indexOf(texture2d._uuid);
    if (index !== -1)
        deps.splice(index, 1);
    cc.loader.release(deps);
    

    最后一个值得关注的要点:JavaScript 的垃圾回收是延迟的

    想象一种情况,当你释放了 cc.loader 对某个资源的引用之后,由于考虑不周的原因,游戏逻辑再次请求了这个资源。此时垃圾回收还没有开始(垃圾回收的时机不可控),或者你的游戏逻辑某处,仍然持有一个对于这个旧资源的引用,那么意味着这个资源还存在内存中,但是 cc.loader 已经访问不到了,所以会重新加载它。这造成这个资源在内存中有两份同样的拷贝,浪费了内存。如果只是一个资源还好,但是如果类似的资源很多,甚至不止一次被重复加载,这对于内存的压力是有可能很高的。如果观察到游戏使用的内存曲线有这样的异常,请仔细检查游戏逻辑,是否存在泄漏,如果没有的话,垃圾回收机制是会正常回收这些内存的。

    以上就是管理资源依赖和释放时需要注意的细节,这部分的功能和 API 设计还没有完全定案,我们还是希望尽力给大家带来尽可能方便的引擎 API,所以后续也会尝试一些其他的办法提升友好度,届时会更新这篇文档。

    展开全文
  • 直接上流程 1. 在项目的根路径下添加文件结构 2. loading-scene.js文件内容 var loadingBg = "...; var scene = new cc....
  • 获取和加载资源 Cocos Creator 有一套统一的资源管理机制 ,在本篇教程,我们将介绍 资源的分类如何在 属性检查器 里设置资源动态加载 Asset动态加载 Raw Asset 资源的分类 目前的资源分成两种,一种叫做 ...
  • Cocos Creator 资源调用

    2019-03-07 16:13:50
    Cocos Creator 资源的动态加载 resources下的资源 只有放在resources 下的资源才能用改路径结构访问 //图片 cc.loader.loadRes(&amp;amp;quot;textures/images/imgname&amp;amp;quot;, cc....
  • Cocos Creator 有一套统一的资源管理机制 ,在本篇教程,我们将介绍 资源的分类如何在 属性检查器 里设置资源动态加载 Asset动态加载 Raw Asset 资源的分类 目前的资源分成两种,一种叫做 Asset,一种...
  • 通过cc.loader.load来从服务器远程加载资源,但是,单独为了一个微信小游戏做资源管理是否成本过高? 通过引擎自动打包依赖,并把资源一股脑放在资源服务器,然后打开小游戏的时候,将自动使用内置的wx API自动下载...
  • 注意点: 在 Crea...
  • Cocos Creator 有一套统一的资源管理机制,在本篇教程,我们将介绍 资源属性的声明 如何在 属性检查器 里设置资源 动态加载资源 加载远程资源和设备资源 资源的依赖和释放 资源属性的声明 在 Creator 中...
  • Cocos Creator 有一套统一的资源管理机制 ,在本篇教程,我们将介绍资源的分类如何在 属性检查器 里设置资源动态加载 Asset动态加载 Raw Asset资源的分类目前的资源分成两种,一种叫做 Asset,一种叫做 Raw ...
  • Cocos2dx-- 资源热更新

    2015-10-12 17:56:04
    前言:游戏上线后,我们常常还会需要更新,如新增玩法,活动等,这种动态的更新资源我们称为游戏的热更新。热更新一般只适用于脚本语言,因为脚本不需要编译,是一种解释性语言,而如C++语言是很难热更新的,其代码...
  • 改了一个tmx,放到目录下,加载的时候报错,错误信息 403 。 一
  • 不过随着小游戏的发展,我们可以将资源放到CDN,通过远程拉取的方式加载资源,这与我们以前的页游相当。接下来就具体讨论下小游戏的加载优化有哪些及资源分配的细节。 -正文- 我们平时制作小游戏一般都不会使...
  • Cocos2d-x 的资源路径

    2013-04-18 02:30:48
    原因是资源文件加载的问题,找不到图片,所以黑屏了   那么cocos2d-x是怎么查找图片的呢 以下是关键部分源码:   获得完整路径   pathKey = CCFileUtils::sharedFileUtils()-&gt;fullPathForFilename...
  • 随着游戏技术的日新月异,玩家对游戏...我们的前辈已经给出了答案,因为cocos creator是可以支撑json文件动态加载的,所有我们仅需要将excel等文本文件转换为json文件,然后运用cocos creator进行动态加载便行了。 ...
  • Cocos2dx中播放背景音乐是一件很容易的事情,就一行代码,但是首先要导入Cocos2dx的音频引擎cocosDenshion。cocosDenshion对cocos2d工程提供了很方便的声音素材的调用和管理。同时要把要播放的音乐放到工程的资源...
1 2 3 4 5 ... 20
收藏数 2,119
精华内容 847
关键字:

cocos 加载外部资源