这里讨论场景切换的完整流程,从我们调用了loadScene开始切换场景,到场景切换完成背后发生的事情。整个流程可以分为场景加载和场景切换两部分,另外还简单讨论了场景的预加载。

  • 加载场景的流程

loadScene主要做了3件事,通过_getSceneUuid获取要加载场景的信息,对于原生平台的非启动场景执行了cc.LoaderLayer.preload(但查询了所有的代码,并没有发现LoaderLayer的实现,也没有发现任何对cc.runtime赋值的地方),最后通过_loadSceneByUuid加载场景

    loadScene: function (sceneName, onLaunched, _onUnloaded) {
// 同一时间只能有一个场景在加载
if (this._loadingScene) {
cc.errorID(1213, sceneName, this._loadingScene);
return false;
}
// 获取场景的信息
var info = this._getSceneUuid(sceneName);
if (info) {
var uuid = info.uuid;
// 触发一个场景开始加载的事件
this.emit(cc.Director.EVENT_BEFORE_SCENE_LOADING, sceneName);
// 设置当前正在加载的场景
this._loadingScene = sceneName;
// 在原生运行时且该场景并非启动场景时,可以进行异步加载。
if (CC_JSB && cc.runtime && uuid !== this._launchSceneUuid) {
var self = this;
var groupName = cc.path.basename(info.url) + '_' + info.uuid;
console.log('==> start preload: ' + groupName);
var ensureAsync = false;
// 如果cc.LoaderLayer.preload是异步的,会在preload结束后执行_loadSceneByUuid。否则会在preload结束的下一帧执行_loadSceneByUuid。
cc.LoaderLayer.preload([groupName], function () {
console.log('==> end preload: ' + groupName);
if (ensureAsync) {
self._loadSceneByUuid(uuid, onLaunched, _onUnloaded);
} else {
setTimeout(function () {
self._loadSceneByUuid(uuid, onLaunched, _onUnloaded);
}, 0);
}
});
ensureAsync = true;
} else {
this._loadSceneByUuid(uuid, onLaunched, _onUnloaded);
}
return true;
} else {
cc.errorID(1214, sceneName);
return false;
}
},

Creator2.x版本的loadScene则直接多了,执行_getSceneUuid,触发EVENT_BEFORE_SCENE_LOADING事件,再调用_loadSceneByUuid。

    loadScene: function (sceneName, onLaunched, _onUnloaded) {
if (this._loadingScene) {
cc.errorID(1208, sceneName, this._loadingScene);
return false;
}
var info = this._getSceneUuid(sceneName);
if (info) {
var uuid = info.uuid;
this.emit(cc.Director.EVENT_BEFORE_SCENE_LOADING, sceneName);
this._loadingScene = sceneName;
this._loadSceneByUuid(uuid, onLaunched, _onUnloaded);
return true;
} else {
cc.errorID(1209, sceneName);
return false;
}
},

_loadSceneByUuid方法也很简单,调用了cc.AssetLibrary.loadAsset加载资源,并指定了资源加载结束后的回调,也就是执行runSceneImmediate以及用户传入的onLaunched回调。

_loadSceneByUuid方法在Creator2.x和Creator1.x中没有区别

    _loadSceneByUuid: function (uuid, onLaunched, onUnloaded, dontRunScene) {
if (CC_EDITOR) {
if (typeof onLaunched === 'boolean') {
dontRunScene = onLaunched;
onLaunched = null;
}
if (typeof onUnloaded === 'boolean') {
dontRunScene = onUnloaded;
onUnloaded = null;
}
}
console.time('LoadScene ' + uuid);
cc.AssetLibrary.loadAsset(uuid, function (error, sceneAsset) {
console.timeEnd('LoadScene ' + uuid);
var self = cc.director;
self._loadingScene = '';
if (error) {
error = 'Failed to load scene: ' + error;
cc.error(error);
} else {
// runSceneImmediate启动场景
if (sceneAsset instanceof cc.SceneAsset) {
var scene = sceneAsset.scene;
scene._id = sceneAsset._uuid;
scene._name = sceneAsset._name;
if (CC_EDITOR) {
if (!dontRunScene) {
self.runSceneImmediate(scene, onUnloaded, onLaunched);
} else {
scene._load();
if (onLaunched) {
onLaunched(null, scene);
}
}
} else {
self.runSceneImmediate(scene, onUnloaded, onLaunched);
}
return;
} else {
error = 'The asset ' + uuid + ' is not a scene';
cc.error(error);
}
}
if (onLaunched) {
onLaunched(error);
}
});
},

loadAsset做的事情也非常简单,就是调用Loader.load去做真正的加载,在加载完成之后将场景所依赖的资源设置给asset.scene.dependAssets,用于场景的释放,另外因为场景并不作为一个可重复使用的资源,所以这里会将场景从Loader中移除。

loadAsset方法在Creator2.x和Creator1.x中没有区别

    loadAsset: function (uuid, callback, options) {
if (typeof uuid !== 'string') {
return callInNextTick(callback, new Error('[AssetLibrary] uuid must be string'), null);
} var item = {
uuid: uuid,
type: 'uuid'
};
if (options && options.existingAsset) {
item.existingAsset = options.existingAsset;
}
Loader.load(item, function (error, asset) {
if (error || !asset) {
error = new Error('[AssetLibrary] loading JSON or dependencies failed: ' + (error ? error.message : 'Unknown error'));
} else {
if (asset.constructor === cc.SceneAsset) {
if (CC_EDITOR && !asset.scene) {
Editor.error('Sorry, the scene data of "%s" is corrupted!', uuid);
} else {
var key = cc.loader._getReferenceKey(uuid);
// 这里其实是递归获取场景这个item的dependKeys数组(去重复)
asset.scene.dependAssets = AutoReleaseUtils.getDependsRecursively(key);
}
}
if (CC_EDITOR || isScene(asset)) {
var id = cc.loader._getReferenceKey(uuid);
Loader.removeItem(id);
}
}
if (callback) {
callback(error, asset);
}
});
},
  • 场景运行与切换

runSceneImmediate做的事情非常多,大概可以分为以下几个事情(虽然Creator2.x的runSceneImmediate方法写法有些变化,但大体做的事情类似):

  • 新场景的初始化(_load方法)
  • 将持久节点从旧场景挪到新场景中
  • 销毁旧场景、自动释放应该释放的资源(旧场景中标记为自动释放且新场景中没有引用到的资源)
  • 一系列场景切换流程的回调和事件执行
    • 开始启动场景的回调和事件
    • 激活并运行新场景
    • 场景启动完成的回调和事件
    runSceneImmediate: function (scene, onBeforeLoadScene, onLaunched) {
const console = window.console; // should mangle
const INIT_SCENE = CC_DEBUG ? 'InitScene' : 'I';
const AUTO_RELEASE = CC_DEBUG ? 'AutoRelease' : 'AR';
const DESTROY = CC_DEBUG ? 'Destroy' : 'D';
const ATTACH_PERSIST = CC_DEBUG ? 'AttachPersist' : 'AP';
const ACTIVATE = CC_DEBUG ? 'Activate' : 'A'; // 场景的初始化,scene._load会调用CCNode的_onBatchCreated
// 1. PrefabHelper.syncWithPrefab(this); 大多数情况下会跳过
// 2. _updateDummySgNode将自己的属性同步给sgNode,并确保sgNode是自己的子节点
// 3. 如果当前节点未激活,则调用ActionManager和EventManager的pauseTarget
// 4. 遍历子节点调用它们的_onBatchCreated
if (scene instanceof cc.Scene) {
console.time(INIT_SCENE);
scene._load(); // ensure scene initialized
console.timeEnd(INIT_SCENE);
} // detach persist nodes
// 将持久节点从旧场景中移除,并暂时保存到persistNodeList中
var game = cc.game;
var persistNodeList = Object.keys(game._persistRootNodes).map(function (x) {
return game._persistRootNodes[x];
});
for (let i = 0; i < persistNodeList.length; i++) {
let node = persistNodeList[i];
game._ignoreRemovePersistNode = node;
node.parent = null;
game._ignoreRemovePersistNode = null;
} var oldScene = this._scene; // auto release assets
// 调用autoRelease进行资源释放,传入旧场景资源和新场景资源进行对比释放
// 当一个资源【勾选了自动释放且没有被新场景引用到时】就会被释放
console.time(AUTO_RELEASE);
var autoReleaseAssets = oldScene && oldScene.autoReleaseAssets && oldScene.dependAssets;
AutoReleaseUtils.autoRelease(autoReleaseAssets, scene.dependAssets, persistNodeList);
console.timeEnd(AUTO_RELEASE); // unload scene
// 释放旧的场景,销毁所有子节点和组件
console.time(DESTROY);
if (cc.isValid(oldScene)) {
oldScene.destroy();
} this._scene = null; // purge destroyed nodes belongs to old scene
cc.Object._deferredDestroy();
console.timeEnd(DESTROY); // 执行开始加载场景回调并触发对应的事件(其实这里应该是启动场景)
if (onBeforeLoadScene) {
onBeforeLoadScene();
}
this.emit(cc.Director.EVENT_BEFORE_SCENE_LAUNCH, scene); var sgScene = scene; // Run an Entity Scene
if (scene instanceof cc.Scene) {
this._scene = scene;
sgScene = scene._sgNode; // Re-attach or replace persist nodes
// 重新添加持久节点到新场景中,如果发现新场景有相同的节点,这里会执行一个替换的操作
console.time(ATTACH_PERSIST);
for (let i = 0; i < persistNodeList.length; i++) {
let node = persistNodeList[i];
var existNode = scene.getChildByUuid(node.uuid);
if (existNode) {
// scene also contains the persist node, select the old one
var index = existNode.getSiblingIndex();
existNode._destroyImmediate();
scene.insertChild(node, index);
}
else {
node.parent = scene;
}
}
// 激活新场景
console.timeEnd(ATTACH_PERSIST);
console.time(ACTIVATE);
scene._activate();
console.timeEnd(ACTIVATE);
} // Run or replace rendering scene
// 启动或替换场景
if (!this.getRunningScene()) {
this.runWithScene(sgScene);
}
else {
this.replaceScene(sgScene);
} // 执行场景启动完成的回调,并触发事件
if (onLaunched) {
onLaunched(null, scene);
}
this.emit(cc.Director.EVENT_AFTER_SCENE_LAUNCH, scene);
},

autoRelease传入2个场景的资源,以及持久节点,自动释放掉应该自动释放的资源(下个场景和持久节点引用到的资源不会被释放,标记为自动释放的资源会被释放)

    autoRelease: function (oldSceneAssets, nextSceneAssets, persistNodes) {
var releaseSettings = cc.loader._autoReleaseSetting;
var excludeMap = JS.createMap(); // collect next scene assets
// 收集下一个场景所需的资源
if (nextSceneAssets) {
for (let i = 0; i < nextSceneAssets.length; i++) {
excludeMap[nextSceneAssets[i]] = true;
}
} // collect assets used by persist nodes
// 收集常驻节点引用的资源
for (let i = 0; i < persistNodes.length; i++) {
visitNode(persistNodes[i], excludeMap)
} // remove ununsed scene assets
// 移除旧场景中不再使用的资源
if (oldSceneAssets) {
for (let i = 0; i < oldSceneAssets.length; i++) {
let key = oldSceneAssets[i];
if (releaseSettings[key] !== false && !excludeMap[key]) {
cc.loader.release(key);
}
}
} // remove auto release assets
// (releasing asset will change _autoReleaseSetting, so don't use for-in)
// 释放标记了auto release的资源
var keys = Object.keys(releaseSettings);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
if (releaseSettings[key] === true && !excludeMap[key]) {
cc.loader.release(key);
}
}
},

cc.loader.release的实现如下,release并不会去释放它依赖的资源,只是释放这个资源本身。将资源从cc.loader中移除,如果该资源的content是一个cc.Asset,会调用它的release、并release其rawUrls对应的资源。如果是纹理则会调用cc.textureCache.removeTextureForKey进行移除,而声音类型的资源会执行cc.audioEngine.uncache进行释放。

proto.release = function (asset) {
if (Array.isArray(asset)) {
for (let i = 0; i < asset.length; i++) {
var key = asset[i];
this.release(key);
}
} else if (asset) {
var id = this._getReferenceKey(asset);
var item = this.getItem(id);
if (item) {
var removed = this.removeItem(id);
asset = item.content;
if (asset instanceof cc.Asset) {
if (CC_JSB && asset instanceof cc.SpriteFrame && removed) {
// for the "Temporary solution" in deserialize.js
asset.release();
}
var urls = asset.rawUrls;
for (let i = 0; i < urls.length; i++) {
this.release(urls[i]);
}
} else if (asset instanceof cc.Texture2D) {
cc.textureCache.removeTextureForKey(item.rawUrl || item.url);
} else if (AUDIO_TYPES.indexOf(item.type) !== -1) {
cc.audioEngine.uncache(item.rawUrl || item.url);
}
if (CC_DEBUG && removed) {
this._releasedAssetChecker_DEBUG.setReleased(item, id);
}
}
}
};

cc.loader._autoReleaseSetting记录了所有资源是否会自动释放。通过cc.loader.setAutoRelease或setAutoReleaseRecursively可以控制是否自动释放。

proto.setAutoRelease = function (assetOrUrlOrUuid, autoRelease) {
var key = this._getReferenceKey(assetOrUrlOrUuid);
if (key) {
this._autoReleaseSetting[key] = !!autoRelease;
}
else if (CC_DEV) {
cc.warnID(4902);
}
}; proto.setAutoReleaseRecursively = function (assetOrUrlOrUuid, autoRelease) {
autoRelease = !!autoRelease;
var key = this._getReferenceKey(assetOrUrlOrUuid);
if (key) {
this._autoReleaseSetting[key] = autoRelease; var depends = AutoReleaseUtils.getDependsRecursively(key);
for (var i = 0; i < depends.length; i++) {
var depend = depends[i];
this._autoReleaseSetting[depend] = autoRelease;
}
}
else if (CC_DEV) {
cc.warnID(4902);
}
};

所有loadRes加载进来的资源都会自动执行setAutoReleaseRecursively(uuid, false),如果我们将某个资源设置为自动释放,然后用loadRes加载了一个依赖了该资源的新资源,之前的自动释放设置会被覆盖。

  • 预加载场景

preloadScene的实现非常简单,拿到场景信息之后触发EVENT_BEFORE_SCENE_LOADING事件并调用cc.loader.load加载资源。这个流程与切换场景并不冲突,只是让场景资源加载的这个流程提前了而已,预加载的场景就算不是接下来要切换的场景,也不会冲突,但可能造成性能和内存的浪费。

Creator2.x的preloadScene比1.x多了一个onProgress参数,在cc.loader.load的时候传入。

    preloadScene: function (sceneName, onLoaded) {
var info = this._getSceneUuid(sceneName);
if (info) {
this.emit(cc.Director.EVENT_BEFORE_SCENE_LOADING, sceneName);
cc.loader.load({ uuid: info.uuid, type: 'uuid' }, function (error, asset) {
if (error) {
cc.errorID(1210, sceneName, error.message);
}
if (onLoaded) {
onLoaded(error, asset);
}
});
} else {
var error = 'Can not preload the scene "' + sceneName + '" because it is not in the build settings.';
onLoaded(new Error(error));
cc.error('preloadScene: ' + error);
}
},

Cocos Creator 资源加载流程剖析【六】——场景切换流程的更多相关文章

  1. Cocos Creator 资源加载流程剖析【一】——cc.loader与加载管线

    这系列文章会对Cocos Creator的资源加载和管理进行深入的剖析.主要包含以下内容: cc.loader与加载管线 Download部分 Load部分 额外流程(MD5 Pipe) 从编辑器到运 ...

  2. Cocos Creator 资源加载流程剖析【二】——Download部分

    Download流程的处理由Downloader这个pipe负责(downloader.js),Downloader提供了各种资源的"下载"方式--即如何获取文件内容,有从网络获取 ...

  3. Cocos Creator 资源加载流程剖析【三】——Load部分

    Load流程是整个资源加载管线的最后一棒,由Loader这个pipe负责(loader.js).通过Download流程拿到内容之后,需要对内容做一些"加载"处理.使得这些内容可以 ...

  4. Cocos Creator 资源加载流程剖析【五】——从编辑器到运行时

    我们在编辑器中看到的资源,在构建之后会进行一些转化,本章将揭开Creator对资源进行的处理. 资源处理的整体规则 首先我们将Creator的开发和运行划分为以下几个场景: 编辑器 当我们将资源放到编 ...

  5. Cocos Creator 资源加载(笔记)

    cc.loader 加载资源动态加载资源要注意两点,一是所有需要通过脚本动态加载的资源,都必须放置在 resources 文件夹或它的子文件夹下.resources 需要在 assets 文件夹中手工 ...

  6. Cocos Creator 资源加载流程剖析【四】——额外流程(MD5 PIPE)

    当我们将游戏构建发布到web平台时,勾选Md5 Cache选项可以开启MD5 Pipe,它的作用是给构建后的资源加上md5后缀,避免浏览器的缓存导致部分资源不是最新,因为使用了md5后缀后,当资源内容 ...

  7. 记一次cocos项目的加载速度优化

    半个月前,我们用cosos creator做了一个简单的小游戏,也许算不上小游戏吧..一边学cocos,一边做,几经波折后终于上线了.然鹅,功能是实现了,但是加载速度十分感人(毕竟没经验嘛,无辜脸). ...

  8. MyBatis 源码篇-资源加载

    本章主要描述 MyBatis 资源加载模块中的 ClassLoaderWrapper 类和 Java 加载配置文件的三种方式. ClassLoaderWrapper 上一章的案例,使用 org.apa ...

  9. 【原】从一个bug浅谈YUI3组件的资源加载

    篇前声明:为了不涉及业务细节,篇内信息统一以某游戏,某功能代替 前不久,某游戏准备内测客户端,开发人员测试过程中发现某功能突然不灵了,之前的测试一切ok,没有发现任何异常,第一反应是,游戏内浏览器都是 ...

随机推荐

  1. Maven搭建SpringMvc

    Maven搭建SpringMvc,只需跟着一步步操作 项目结构 1 创建Maven项目 index,jsp报错不用管,配置完pom就好了,也可以直接删除掉 2 pom.xml添加依赖 <depe ...

  2. 新浪短网址最新api接口

    1,雨林短网址 网站链接:http://yldwz.cn 雨林短网址采用新浪.腾讯官方API接口,强大的多功能API,简单易用,质量高官 网提供强技术支持,99.9% SLA服务稳定安全可靠的校验机制 ...

  3. 【Android - 自定义View】之自定义可下拉刷新或上拉加载的ListView

    首先来介绍一下这个自定义View: (1)这个自定义View的名称叫做 RefreshableListView ,继承自ListView类: (2)在这个自定义View中,用户可以设置是否支持下拉刷新 ...

  4. 【Android - 自定义View】之View的工作过程简介

    View的工作过程分为三个过程: View的measure过程: View的layout过程: View的draw过程. 我们知道,一个Activity就是一个窗口,这个窗口中包含一个Window.一 ...

  5. rug

    rug()函数 :给图添加rug representation. · 一维的 · rug的方式是补充,仅仅包括落在图像区域内的x的值,丢失掉任何有限的值,将会被警告:而丢失任何非有限的值,则静静地丢失 ...

  6. python迭代器生成器-迭代器和list区别

    迭代 生成 for循环遍历的原理 for循环遍历的原理就是迭代,in后面必须是可迭代对象 为什么要有迭代器 对于序列类型:字符串.列表.元组,我们可以使用索引的方式迭代取出其包含的元素.但对于字典.集 ...

  7. Mysql密码忘记怎么修改?

    做开发的过程中多少会用到MySQL数据库,所以忘记密码也就成为一些马虎的同学的家常便饭了,今天发布一个忘记MySQL密码如何修改的文章作为记录. 1>首先将MySQL的服务关闭,两种方法:1,打 ...

  8. Netty源码分析之ChannelPipeline(二)—ChannelHandler的添加与删除

    上篇文章中,我们对Netty中ChannelPipeline的构造与初始化进行了分析与总结,本篇文章我们将对ChannelHandler的添加与删除操作进行具体的的代码分析: 一.ChannelHan ...

  9. 在vue中使用基于d3为基础的dagre-d3.js搞定一个流程图组件

    项目中想搞定一个流程图,开始使用了阿里的G6,但是G6目前不支持手势,这样就很郁闷了,因为公司的领导都是使用iPad看的,你不支持手势是不行的,后来又想到了百度的echarts,试了试,感觉还不错,手 ...

  10. Flask使用bootstrap为HttpServer添加上传文件功能

    关于模态框 使用bootstrap实现点击按钮弹出窗口,简直不要太简单.我们只需要将写好的窗口内容隐藏,然后调用bootstrap的框架即可,简单几行就能完成相关功能实现.... 前提条件是,我们需要 ...