Cocos Creator 记录四-场景、资源

加载和切换场景

在 Cocos Creator 中,我们使用场景文件名(不包含扩展名)来索引指代场景。并通过以下接口进行加载和切换操作:

cc.director.loadScene("MyScene");

通过常驻节点进行场景资源管理和参数传递

cc.game.addPersistRootNode(myNode);

取消一个节点的常驻属性:

cc.game.removePersistRootNode(myNode);

加载场景时,可以附加一个参数用来指定场景加载后的回调函数:

cc.director.loadScene("MyScene", onSceneLaunched);

上一行里 onSceneLaunched 就是声明在本脚本中的一个回调函数,在场景加载后可以用来进一步的进行初始化或数据传递的操作。

由于回调函数只能写在本脚本中,所以场景加载回调通常用来配合常驻节点,在常驻节点上挂载的脚本中使用。

预加载场景

cc.director.loadScene 会在加载场景之后自动切换运行新场景,有些时候我们需要在后台静默加载新场景,并在加载完成后手动进行切换。那就可以预先使用 preloadScene 接口对场景进行预加载:

cc.director.preloadScene("table", function () {
    cc.log("Next scene preloaded");
});

之后在合适的时间调用 loadScene, 就可以真正切换场景。

cc.director.loadScene("table");

就算预加载还没完成,你也可以直接调用 cc.director.loadScene,预加载完成后场景就会启动。

注意
使用预加载场景资源配合 runScene 的方式进行预加载场景的方法已被废除: // 请不要再使用下面的方法预加载场景!
cc.loader.loadRes(‘MyScene.fire’, function(err, res) {
cc.director.runScene(res.scene); });

资源的分类

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

Asset

Creator 提供了名为 “Asset” 的资源类型,cc.SpriteFrame, cc.AnimationClip, cc.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 对应的资源。

 textureURL: {
            default: "",
            url: cc.Texture2D
        }

动态加载

动态加载资源要注意两点,一是所有需要通过脚本动态加载的资源,都必须放置在 resources 文件夹或它的子文件夹下。resources 需要在 assets 文件夹中手工创建,并且必须位于 assets 的根目录,就像这样:
Cocos Creator 记录四-场景、资源_第1张图片
这里的 image/image(SpriteFrame), prefab, anim, font 都是常见的 Asset,而 atom(particle), audio 则是常见的 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.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);

动态加载 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. 原生平台远程加载不支持图片文件以外类型的资源
  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 会自动使用缓存中的资源。
Cocos Creator 记录四-场景、资源_第2张图片

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

// 直接释放某个贴图
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 已经访问不到了,所以会重新加载它。这造成这个资源在内存中有两份同样的拷贝,浪费了内存。如果只是一个资源还好,但是如果类似的资源很多,甚至不止一次被重复加载,这对于内存的压力是有可能很高的。如果观察到游戏使用的内存曲线有这样的异常,请仔细检查游戏逻辑,是否存在泄漏,如果没有的话,垃圾回收机制是会正常回收这些内存的。

你可能感兴趣的:(COCOS,cocos,creator)