A闪的 BLOG 技术与人文
因为工作需要对Cocos做一些较为底层的修改和定制化,近期对Cocos引擎源码做了一些分析。里面主要涉及到一些比较关键的点,其他部分阅读源码和注释就足够了。
网页版本和原生版本有一些差别,为了方便理解,先对web版本进行分析,理清引擎层逻辑,后面会事半功倍。现在引擎的最新版本是v3.1,和一年前相比,变化确实不小,因为2d 和 3d 已经合并,估计在可预见的未来,引擎整体架构不会发生太大变化。
调试运行web版,在index.html
中,需要关注的代码段如下:
<!-- Polyfills bundle. -->
<script src="/scripting/polyfills/bundle.js"></script>
<!-- Import map that map the engine feature unit modules. -->
<script src="/scripting/engine/bin/.cache/dev/preview/import-map.json" type="systemjs-importmap"></script>
<!-- Import map that map the entry of project module(s). -->
<script src="/scripting/import-map-project" type="systemjs-importmap"></script>
<!-- Import map that map 'cc', 'cc/env'. -->
<script src="/scripting/import-map-global" type="systemjs-importmap"></script>
<!-- SystemJS support. -->
<script src="/scripting/systemjs/system.js"></script>
<script src="/scripting/engine/bin/.cache/dev/preview/bundled/index.js"></script>
<!-- Manifest file. -->
<script name="setting" src="/settings.js?scene=current_scene"></script>
<script>
window.onload = function() {
System.import("/preview-app/index.js").then(function (mod) {
return mod.bootstrap({
engineBaseUrl: '/scripting/engine',
settings: window._CCSettings,
devices: {"Default":{"name":"Default","width":960,"height":640,"ratio":1},"iPhone SE":{"name":"iPhone SE","width":375,"height":667,"ratio":2},"iPhone XS Max":{"name":"iPhone XS Max","width":414,"height":896,"ratio":3},"iPhone XS":{"name":"iPhone XS","width":375,"height":812,"ratio":3},"iPhone XR":{"name":"iPhone XR","width":414,"height":896,"ratio":2},"iPhone 8 Plus":{"name":"iPhone 8 Plus","width":414,"height":736,"ratio":2.6},"iPhone 8":{"name":"iPhone 8","width":375,"height":667,"ratio":2},"Galaxy S9 Plus":{"name":"Galaxy S9 Plus","width":412,"height":846,"ratio":3.5},"Galaxy S9":{"name":"Galaxy S9","width":360,"height":740,"ratio":4},"Galaxy S7":{"name":"Galaxy S7","width":360,"height":640,"ratio":4},"Huawei P20 Lite":{"name":"Huawei P20 Lite","width":360,"height":760,"ratio":3},"Huawei P20 Pro":{"name":"Huawei P20 Pro","width":360,"height":747,"ratio":3},"Huawei P20":{"name":"Huawei P20","width":360,"height":748,"ratio":3},"Huawei P10":{"name":"Huawei P10","width":360,"height":640,"ratio":3},"Moto Z2":{"name":"Moto Z2","width":360,"height":640,"ratio":4},"Redmi Note 5":{"name":"Redmi Note 5","width":393,"height":786,"ratio":2.75},"Redmi Note 4":{"name":"Redmi Note 4","width":360,"height":640,"ratio":3},"Pixel 3 XL":{"name":"Pixel 3 XL","width":393,"height":786,"ratio":3.67},"Pixel 2 XL":{"name":"Pixel 2 XL","width":412,"height":823,"ratio":3.5},"Pixel 2":{"name":"Pixel 2","width":412,"height":640,"ratio":2.6},"Nexus 6P":{"name":"Nexus 6P","width":412,"height":732,"ratio":3.5},"Nexus 6":{"name":"Nexus 6","width":360,"height":640,"ratio":4},"LG K20":{"name":"LG K20","width":360,"height":640,"ratio":2},"LG K7":{"name":"LG K7","width":320,"height":570,"ratio":1.5},"ZTE ZMAX Pro":{"name":"ZTE ZMAX Pro","width":360,"height":640,"ratio":3},"ZTE Majesty Pro":{"name":"ZTE Majesty Pro","width":320,"height":570,"ratio":1.5}},
});
}).catch(function(err) {
console.error(err);
});
};
</script>
需要特别注意的是<script name="setting" src="/settings.js?scene=current_scene"></script>
这一行,里面对window._CCSettings
进行了赋值。这是引擎所需要的设置信息。具体每个设置项可以自行查阅引擎代码,后面也会涉及到一部分设置项。
在window.onload
事件中,借助SystemJS来动态加载preview-app/index.js
模块。
SystemJS相关信息可以查看 `github.com/systemjs/systemjs`
preview-app中主要内容就是绘制预览时候的页面,包括fps设置,暂停恢复引擎,设置预览设备和分辨率等等,简单来说这部分的代码是和editor相关,与引擎运行时关系不大。
preview-app模块加载完成后回调用bootstrap方法,在所有页面绘制完成,模块加载完成后,会最终调用Game中的init方法以启动引擎。由于preview-app模块属于编辑器的一部分,所以无法查看远吗,js代码经过编译混淆已经无法阅读,智能通过断点方式查找一些与引擎相关的关键操作。具体的操作流程如下:
相对来说,在预览状态下,很多操作都和preview功能相关,而且一些配置文件因为要与编辑器联动,很多配置都处于可设置状态。
相比调试版本,release版本的代码则显得“干净”许多。在index.html中,通过SystemJS加载index.js文 件和application.js文件,随后index.js会先创建页面中的canvas标签。紧接着就是加载引擎文件,这里我遇到了一个以前没有见过的东西,此环节加载了一个名为ammo.wasm的文件,实际上是 github.com/kripken/ammo.js 这个物理引擎的WebAssembly build 版本。
之后使用XMLHttpRequest家在并设置settings.json文件,并赋值给window._CCSettings。
window._CCSettings会经过一系列值的判断和处理,并将处理后的结果作为参数传递给cc.game.init ,之后则依然调用assetsManager.loadBundle,加载资源包。完成后调用cc.game.run启动引擎。当引擎启动完成后,会回调一个名为onGameStarted的函数,函数内容如下:
function onGameStarted(cc, settings) {
window._CCSettings = undefined;
cc.view.enableRetina(true);
cc.view.resizeWithBrowserSize(true);
if (cc.sys.isMobile) {
if (settings.orientation === 'landscape') {
cc.view.setOrientation(cc.macro.ORIENTATION_LANDSCAPE);
} else if (settings.orientation === 'portrait') {
cc.view.setOrientation(cc.macro.ORIENTATION_PORTRAIT);
}
cc.view.enableAutoFullScreen(false);
}
var launchScene = settings.launchScene; // load scene
cc.director.loadScene(launchScene, null, function () {
cc.view.setDesignResolutionSize(960, 640, 4);
console.log("Success to load scene: ".concat(launchScene));
});
}