Cocos creator 源码解析(一)

序言

许久没写博客了,这次借着读源码来写几篇关于源码的解析吧。加上最近用cocos比较多,底层看的比较少,准备开始对全部的源码来一次深刻的解读,此篇文章也是方便我自己以后查看。源码已经添加了对应方法的解释,在此基础上加入我个人的解释。

PS: 本人是web方向的,所以基本cocos用的都是html场景,原生部分就没能力解析了,其他部分解析后续会更新

首先我们先从CCGame.js开始解读,因为场景的创建初始化都由cc.game开始,具体可以看打包html场景后的main.js

本人不善于写作,有错误可以私信我,欢迎大家提出意见和批评,新手轻喷。

github地址:https://github.com/cocos-creator/engine/blob/master/cocos2d/core/CCGame.js

源码解析

// cc.EventTarget 对象类引入
var EventTarget = require('./event/event-target');
// cc.audioEngine 对象(提供音频播放暂停等接口)引入
require('../audio/CCAudioEngine');
// cc.debug 对象(提供游戏信息如fps等)导入
const debug = require('./CCDebug');
// cc.renderer 对象(提供渲染层接口等)导入
const renderer = require('./renderer/index.js');
// 如果是qq玩一玩环境则导入专门的 输入管理类 否则导入通用的 输入管理类
const inputManager = CC_QQPLAY ? require('./platform/BKInputManager') : require('./platform/CCInputManager');
// cc.dynamicAtlasManager 对象(动态图集管理)导入
const dynamicAtlasManager = require('../core/renderer/utils/dynamic-atlas/manager');

/**
 * @module cc
 */

/**
 * !#en An object to boot the game.
 * !#zh 包含游戏主体信息并负责驱动游戏的游戏对象。
 * @class Game
 * @extends EventTarget
 */
var game = {
    /**
     * !#en Event triggered when game hide to background.
     * Please note that this event is not 100% guaranteed to be fired on Web platform,
     * on native platforms, it corresponds to enter background event, os status bar or notification center may not trigger this event.
     * !#zh 游戏进入后台时触发的事件。
     * 请注意,在 WEB 平台,这个事件不一定会 100% 触发,这完全取决于浏览器的回调行为。
     * 在原生平台,它对应的是应用被切换到后台事件,下拉菜单和上拉状态栏等不一定会触发这个事件,这取决于系统行为。
     * @property EVENT_HIDE
     * @type {String}
     * @example
     * cc.game.on(cc.game.EVENT_HIDE, function () {
     *     cc.audioEngine.pauseMusic();
     *     cc.audioEngine.pauseAllEffects();
     * });
     */
    // 这个注释都比较清晰,平时多用于 原生web-view里音频播放 因为应用切换切入切出导致音频持续播放等业务问题
    EVENT_HIDE: "game_on_hide",

    /**
     * !#en Event triggered when game back to foreground
     * Please note that this event is not 100% guaranteed to be fired on Web platform,
     * on native platforms, it corresponds to enter foreground event.
     * !#zh 游戏进入前台运行时触发的事件。
     * 请注意,在 WEB 平台,这个事件不一定会 100% 触发,这完全取决于浏览器的回调行为。
     * 在原生平台,它对应的是应用被切换到前台事件。
     * @property EVENT_SHOW
     * @constant
     * @type {String}
     */
    // 跟上一个事件一样
    EVENT_SHOW: "game_on_show",

    /**
     * !#en Event triggered when game restart
     * !#zh 调用restart后,触发事件。
     * @property EVENT_RESTART
     * @constant
     * @type {String}
     */
    EVENT_RESTART: "game_on_restart",

    /**
     * Event triggered after game inited, at this point all engine objects and game scripts are loaded
     * @property EVENT_GAME_INITED
     * @constant
     * @type {String}
     */
    // 在cc.game.run(config, onStart)的onStart之前触发,引擎部分和业务脚本已全部加载,还未加载完成sence
    // 防止cc.game.on监听不到该事件,如果 _paused 为 false,将直接调用on的回调
    EVENT_GAME_INITED: "game_inited",

    /**
     * Event triggered after engine inited, at this point you will be able to use all engine classes. 
     * It was defined as EVENT_RENDERER_INITED in cocos creator v1.x and renamed in v2.0
     * @property EVENT_ENGINE_INITED
     * @constant
     * @type {String}
     */
    // 在cc.game.run(config, onStart)的onStart之前触发,引擎部分已全部加载,业务脚本还未加载
    // 防止cc.game.on监听不到该事件,如果 _prepared 为 true,将直接调用on的回调
    EVENT_ENGINE_INITED: "engine_inited",
    // deprecated
    EVENT_RENDERER_INITED: "engine_inited",

    /**
     * Web Canvas 2d API as renderer backend
     * @property RENDER_TYPE_CANVAS
     * @constant
     * @type {Number}
     */
    RENDER_TYPE_CANVAS: 0,
    /**
     * WebGL API as renderer backend
     * @property RENDER_TYPE_WEBGL
     * @constant
     * @type {Number}
     */
    RENDER_TYPE_WEBGL: 1,
    /**
     * OpenGL API as renderer backend
     * @property RENDER_TYPE_OPENGL
     * @constant
     * @type {Number}
     */
    RENDER_TYPE_OPENGL: 2,

	// 常驻节点列表(非数组)
    _persistRootNodes: {},

    // states
    _paused: true,// whether the game is paused
    _configLoaded: false,// whether config loaded
    _isCloning: false,// deserializing or instantiating
    _prepared: false, // whether the engine has prepared
    _rendererInitialized: false, // 是否已经加载完引擎和业务脚本初始化

	// 渲染上下文(webgl或者2d)
    _renderContext: null,

	// 主循环目标(用于暂停或重启停止动画帧循环)
    _intervalId: null,//interval target of main

	// 上一次渲染时间(用于帧数设置不为30帧或60帧时使用)
    _lastTime: null,
    // 每一帧间隔时间,帧数除于1000得到(用于帧数设置不为30帧或60帧使用)
    _frameTime: null,

    // Scenes list
    // 场景列表信息(无则空列表)
    _sceneInfos: [],

    /**
     * !#en The outer frame of the game canvas, parent of game container.
     * !#zh 游戏画布的外框,container 的父容器。
     * @property frame
     * @type {Object}
     */
    frame: null,
    /**
     * !#en The container of game canvas.
     * !#zh 游戏画布的容器。
     * @property container
     * @type {HTMLDivElement}
     */
    container: null,
    /**
     * !#en The canvas of the game.
     * !#zh 游戏的画布。
     * @property canvas
     * @type {HTMLCanvasElement}
     */
    canvas: null,

    /**
     * !#en The renderer backend of the game.
     * !#zh 游戏的渲染器类型。
     * @property renderType
     * @type {Number}
     */
    renderType: -1,

    /**
     * !#en
     * The current game configuration, including:
* 1. debugMode
* "debugMode" possible values :
* 0 - No message will be printed.
* 1 - cc.error, cc.assert, cc.warn, cc.log will print in console.
* 2 - cc.error, cc.assert, cc.warn will print in console.
* 3 - cc.error, cc.assert will print in console.
* 4 - cc.error, cc.assert, cc.warn, cc.log will print on canvas, available only on web.
* 5 - cc.error, cc.assert, cc.warn will print on canvas, available only on web.
* 6 - cc.error, cc.assert will print on canvas, available only on web.
* 2. showFPS
* Left bottom corner fps information will show when "showFPS" equals true, otherwise it will be hide.
* 3. exposeClassName
* Expose class name to chrome debug tools, the class intantiate performance is a little bit slower when exposed.
* 4. frameRate
* "frameRate" set the wanted frame rate for your game, but the real fps depends on your game implementation and the running environment.
* 5. id
* "gameCanvas" sets the id of your canvas element on the web page, it's useful only on web.
* 6. renderMode
* "renderMode" sets the renderer type, only useful on web :
* 0 - Automatically chosen by engine
* 1 - Forced to use canvas renderer
* 2 - Forced to use WebGL renderer, but this will be ignored on mobile browsers
* 7. scenes
* "scenes" include available scenes in the current bundle.
*
* Please DO NOT modify this object directly, it won't have any effect.
* !#zh * 当前的游戏配置,包括:
* 1. debugMode(debug 模式,但是在浏览器中这个选项会被忽略)
* "debugMode" 各种设置选项的意义。
* 0 - 没有消息被打印出来。
* 1 - cc.error,cc.assert,cc.warn,cc.log 将打印在 console 中。
* 2 - cc.error,cc.assert,cc.warn 将打印在 console 中。
* 3 - cc.error,cc.assert 将打印在 console 中。
* 4 - cc.error,cc.assert,cc.warn,cc.log 将打印在 canvas 中(仅适用于 web 端)。
* 5 - cc.error,cc.assert,cc.warn 将打印在 canvas 中(仅适用于 web 端)。
* 6 - cc.error,cc.assert 将打印在 canvas 中(仅适用于 web 端)。
* 2. showFPS(显示 FPS)
* 当 showFPS 为 true 的时候界面的左下角将显示 fps 的信息,否则被隐藏。
* 3. exposeClassName
* 暴露类名让 Chrome DevTools 可以识别,如果开启会稍稍降低类的创建过程的性能,但对对象构造没有影响。
* 4. frameRate (帧率)
* “frameRate” 设置想要的帧率你的游戏,但真正的FPS取决于你的游戏实现和运行环境。
* 5. id
* "gameCanvas" Web 页面上的 Canvas Element ID,仅适用于 web 端。
* 6. renderMode(渲染模式)
* “renderMode” 设置渲染器类型,仅适用于 web 端:
* 0 - 通过引擎自动选择。
* 1 - 强制使用 canvas 渲染。 * 2 - 强制使用 WebGL 渲染,但是在部分 Android 浏览器中这个选项会被忽略。
* 7. scenes
* “scenes” 当前包中可用场景。
*
* 注意:请不要直接修改这个对象,它不会有任何效果。 * @property config * @type {Object} */
config: null, /** * !#en Callback when the scripts of engine have been load. * !#zh 当引擎完成启动后的回调函数。 * @method onStart * @type {Function} */ // restart也会调用 onStart: null, //@Public Methods // @Game play control /** * !#en Set frame rate of game. * !#zh 设置游戏帧率。 * @method setFrameRate * @param {Number} frameRate */ setFrameRate: function (frameRate) { var config = this.config; config.frameRate = frameRate; // 如果已经开始渲染则取消之前的渲染 if (this._intervalId) window.cancelAnimFrame(this._intervalId); this._intervalId = 0; this._paused = true; // 设置帧循环事件等 this._setAnimFrame(); // 开始帧循环 this._runMainLoop(); }, /** * !#en Get frame rate set for the game, it doesn't represent the real frame rate. * !#zh 获取设置的游戏帧率(不等同于实际帧率)。 * @method getFrameRate * @return {Number} frame rate */ getFrameRate: function () { return this.config.frameRate; }, /** * !#en Run the game frame by frame. * !#zh 执行一帧游戏循环。 * @method step */ step: function () { cc.director.mainLoop(); }, /** * !#en Pause the game main loop. This will pause: * game logic execution, rendering process, event manager, background music and all audio effects. * This is different with cc.director.pause which only pause the game logic execution. * !#zh 暂停游戏主循环。包含:游戏逻辑,渲染,事件处理,背景音乐和所有音效。这点和只暂停游戏逻辑的 cc.director.pause 不同。 * @method pause */ pause: function () { if (this._paused) return; this._paused = true; // Pause audio engine if (cc.audioEngine) { cc.audioEngine._break(); } // Pause animation cc.director.stopAnimation(); // Pause main loop if (this._intervalId) window.cancelAnimFrame(this._intervalId); this._intervalId = 0; }, /** * !#en Resume the game from pause. This will resume: * game logic execution, rendering process, event manager, background music and all audio effects. * !#zh 恢复游戏主循环。包含:游戏逻辑,渲染,事件处理,背景音乐和所有音效。 * @method resume */ resume: function () { if (!this._paused) return; this._paused = false; // Resume audio engine if (cc.audioEngine) { cc.audioEngine._restore(); } cc.director._resetDeltaTime(); // Resume main loop this._runMainLoop(); }, /** * !#en Check whether the game is paused. * !#zh 判断游戏是否暂停。 * @method isPaused * @return {Boolean} */ isPaused: function () { return this._paused; }, /** * !#en Restart game. * !#zh 重新开始游戏 * @method restart */ restart: function () { // 在下一帧循环完成后调用 cc.director.once(cc.Director.EVENT_AFTER_DRAW, function () { // 移除常驻节点 for (var id in game._persistRootNodes) { game.removePersistRootNode(game._persistRootNodes[id]); } // Clear scene cc.director.getScene().destroy(); cc.Object._deferredDestroy(); // Clean up audio if (cc.audioEngine) { cc.audioEngine.uncacheAll(); } // 重新设置 cc.director.reset(); game.pause(); // 资源管理类内建资源注册后执行 cc.assetManager.builtins.init(() => { game.onStart(); game.emit(game.EVENT_RESTART); }); }); }, /** * !#en End game, it will close the game window * !#zh 退出游戏 * @method end */ end: function () { close(); }, // @Game loading // 初始化引擎 _initEngine () { if (this._rendererInitialized) { return; } // 注册渲染所需数据 this._initRenderer(); // 如果非编辑器环境 则注册部分输入事件 if (!CC_EDITOR) { this._initEvents(); } // 派发引擎初始化完成事件 this.emit(this.EVENT_ENGINE_INITED); }, _loadPreviewScript (cb) { // 未知 应该是某一环境的兼容 if (CC_PREVIEW && window.__quick_compile_project__) { window.__quick_compile_project__.load(cb); } else { cb(); } }, _prepareFinished (cb) { this._prepared = true; // Init engine this._initEngine(); // 设置帧循环事件等 this._setAnimFrame(); // 资源管理类内建资源注册后执行 cc.assetManager.builtins.init(() => { // Log engine version console.log('Cocos Creator v' + cc.ENGINE_VERSION); this._prepared = true; // 开始帧循环 this._runMainLoop(); // 派发游戏初始化完成事件 this.emit(this.EVENT_GAME_INITED); if (cb) cb(); }); }, // 绑定事件对象的注册监听方法 eventTargetOn: EventTarget.prototype.on, // 绑定事件对象的一次性注册监听方法 eventTargetOnce: EventTarget.prototype.once, /** * !#en * Register an callback of a specific event type on the game object. * This type of event should be triggered via `emit`. * !#zh * 注册 game 的特定事件类型回调。这种类型的事件应该被 `emit` 触发。 * * @method on * @param {String} type - A string representing the event type to listen for. * @param {Function} callback - The callback that will be invoked when the event is dispatched. * The callback is ignored if it is a duplicate (the callbacks are unique). * @param {any} [callback.arg1] arg1 * @param {any} [callback.arg2] arg2 * @param {any} [callback.arg3] arg3 * @param {any} [callback.arg4] arg4 * @param {any} [callback.arg5] arg5 * @param {Object} [target] - The target (this object) to invoke the callback, can be null * @return {Function} - Just returns the incoming callback so you can save the anonymous function easier. * @typescript * on(type: string, callback: T, target?: any, useCapture?: boolean): T */ on (type, callback, target) { // Make sure EVENT_ENGINE_INITED and EVENT_GAME_INITED callbacks to be invoked if ((this._prepared && type === this.EVENT_ENGINE_INITED) || (!this._paused && type === this.EVENT_GAME_INITED)) { callback.call(target); } else { this.eventTargetOn(type, callback, target); } }, /** * !#en * Register an callback of a specific event type on the game object, * the callback will remove itself after the first time it is triggered. * !#zh * 注册 game 的特定事件类型回调,回调会在第一时间被触发后删除自身。 * * @method once * @param {String} type - A string representing the event type to listen for. * @param {Function} callback - The callback that will be invoked when the event is dispatched. * The callback is ignored if it is a duplicate (the callbacks are unique). * @param {any} [callback.arg1] arg1 * @param {any} [callback.arg2] arg2 * @param {any} [callback.arg3] arg3 * @param {any} [callback.arg4] arg4 * @param {any} [callback.arg5] arg5 * @param {Object} [target] - The target (this object) to invoke the callback, can be null */ once (type, callback, target) { // Make sure EVENT_ENGINE_INITED and EVENT_GAME_INITED callbacks to be invoked if ((this._prepared && type === this.EVENT_ENGINE_INITED) || (!this._paused && type === this.EVENT_GAME_INITED)) { callback.call(target); } else { this.eventTargetOnce(type, callback, target); } }, /** * !#en Prepare game. * !#zh 准备引擎,请不要直接调用这个函数。 * @param {Function} cb * @method prepare */ prepare (cb) { // Already prepared if (this._prepared) { if (cb) cb(); return; } // 加载准备脚本之后调用 this._loadPreviewScript(() => { this._prepareFinished(cb); }); }, /** * !#en Run game with configuration object and onStart function. * !#zh 运行游戏,并且指定引擎配置和 onStart 的回调。 * @method run * @param {Object} config - Pass configuration object or onStart function * @param {Function} onStart - function to be executed after game initialized */ run: function (config, onStart) { // 注册配置数据 this._initConfig(config); this.onStart = onStart; // 开始准备 this.prepare(game.onStart && game.onStart.bind(game)); }, // @ Persist root node section /** * !#en * Add a persistent root node to the game, the persistent node won't be destroyed during scene transition.
* The target node must be placed in the root level of hierarchy, otherwise this API won't have any effect. * !#zh * 声明常驻根节点,该节点不会被在场景切换中被销毁。
* 目标节点必须位于为层级的根节点,否则无效。 * @method addPersistRootNode * @param {Node} node - The node to be made persistent */
addPersistRootNode: function (node) { // 确保是节点且存在uuid if (!cc.Node.isNode(node) || !node.uuid) { cc.warnID(3800); return; } var id = node.uuid; // 防止出现同个uuid的节点(一般不会) if (!this._persistRootNodes[id]) { var scene = cc.director._scene; // 是否存在场景 if (cc.isValid(scene)) { if (!node.parent) { node.parent = scene; } // 节点的parent不属于cc.Scene类 else if ( !(node.parent instanceof cc.Scene) ) { cc.warnID(3801); return; } // 节点的parent不等于当前节点 else if (node.parent !== scene) { cc.warnID(3802); return; } } this._persistRootNodes[id] = node; // 节点的是否常驻节点属性 node._persistNode = true; // 通过资源管理添加常驻节点资源(具体以后详细讲) cc.assetManager.finalizer._addPersistNodeRef(node); } }, /** * !#en Remove a persistent root node. * !#zh 取消常驻根节点。 * @method removePersistRootNode * @param {Node} node - The node to be removed from persistent node list */ removePersistRootNode: function (node) { var id = node.uuid || ''; if (node === this._persistRootNodes[id]) { delete this._persistRootNodes[id]; node._persistNode = false; cc.assetManager.finalizer._removePersistNodeRef(node); } }, /** * !#en Check whether the node is a persistent root node. * !#zh 检查节点是否是常驻根节点。 * @method isPersistRootNode * @param {Node} node - The node to be checked * @return {Boolean} */ isPersistRootNode: function (node) { return node._persistNode; }, //@Private Methods // @Time ticker section _setAnimFrame: function () { // 获取当前时间 this._lastTime = performance.now(); // 用户设置的帧数 var frameRate = game.config.frameRate; // 帧间隔时间 this._frameTime = 1000 / frameRate; // 2倍的帧数存储 cc.director._maxParticleDeltaTime = this._frameTime / 1000 * 2; // 是否原生环境(这个不是很清楚) if (CC_JSB || CC_RUNTIME) { jsb.setPreferredFramesPerSecond(frameRate); window.requestAnimFrame = window.requestAnimationFrame; window.cancelAnimFrame = window.cancelAnimationFrame; } else { // 如用户设置的帧数不为60或30 则使用自定义的循环函数 if (frameRate !== 60 && frameRate !== 30) { window.requestAnimFrame = this._stTime; window.cancelAnimFrame = this._ctTime; } // 使用动画帧循环事件 else { window.requestAnimFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || this._stTime; window.cancelAnimFrame = window.cancelAnimationFrame || window.cancelRequestAnimationFrame || window.msCancelRequestAnimationFrame || window.mozCancelRequestAnimationFrame || window.oCancelRequestAnimationFrame || window.webkitCancelRequestAnimationFrame || window.msCancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame || window.oCancelAnimationFrame || this._ctTime; } } }, _stTime: function(callback){ var currTime = performance.now(); // 获取当前至下一帧时间 var timeToCall = Math.max(0, game._frameTime - (currTime - game._lastTime)); var id = window.setTimeout(function() { callback(); }, timeToCall); game._lastTime = currTime + timeToCall; return id; }, _ctTime: function(id){ window.clearTimeout(id); }, //Run game. _runMainLoop: function () { if (CC_EDITOR) { return; } if (!this._prepared) return; var self = this, callback, config = self.config, director = cc.director, skip = true, frameRate = config.frameRate; // 设置在左下角是否显示fps信息 debug.setDisplayStats(config.showFPS); callback = function (now) { // 非暂停状态 if (!self._paused) { self._intervalId = window.requestAnimFrame(callback); // 不为原生(这里不明确)且 用户帧数设置为30 if (!CC_JSB && !CC_RUNTIME && frameRate === 30) { // 每2帧跳1帧执行 if (skip = !skip) { return; } } // 执行下一帧循环 director.mainLoop(now); } }; self._intervalId = window.requestAnimFrame(callback); // 游戏已初始化完成所有非暂停状态 self._paused = false; }, // @Game loading section _initConfig (config) { // Configs adjustment if (typeof config.debugMode !== 'number') { config.debugMode = 0; } config.exposeClassName = !!config.exposeClassName; if (typeof config.frameRate !== 'number') { config.frameRate = 60; } let renderMode = config.renderMode; if (typeof renderMode !== 'number' || renderMode > 2 || renderMode < 0) { config.renderMode = 0; } if (typeof config.registerSystemEvent !== 'boolean') { config.registerSystemEvent = true; } if (renderMode === 1) { config.showFPS = false; } else { config.showFPS = !!config.showFPS; } // Collide Map and Group List this.collisionMatrix = config.collisionMatrix || []; this.groupList = config.groupList || []; // 根据配置重新设置 debug设置 debug._resetDebugSetting(config.debugMode); this.config = config; this._configLoaded = true; }, _determineRenderType () { let config = this.config, userRenderMode = parseInt(config.renderMode) || 0; // Determine RenderType this.renderType = this.RENDER_TYPE_CANVAS; let supportRender = false; if (userRenderMode === 0) { // 是否可使用webgl功能 if (cc.sys.capabilities['opengl']) { this.renderType = this.RENDER_TYPE_WEBGL; supportRender = true; } // 是否可使用canvas else if (cc.sys.capabilities['canvas']) { this.renderType = this.RENDER_TYPE_CANVAS; supportRender = true; } } // 是否可使用canvas else if (userRenderMode === 1 && cc.sys.capabilities['canvas']) { this.renderType = this.RENDER_TYPE_CANVAS; supportRender = true; } // 是否可使用webgl功能 else if (userRenderMode === 2 && cc.sys.capabilities['opengl']) { this.renderType = this.RENDER_TYPE_WEBGL; supportRender = true; } // 都使用不了canvas和webgl功能,无法使用引擎 if (!supportRender) { throw new Error(debug.getError(3820, userRenderMode)); } }, _initRenderer () { // Avoid setup to be called twice. if (this._rendererInitialized) return; let el = this.config.id, width, height, localCanvas, localContainer; // 是否原生环境(这里不明确) if (CC_JSB || CC_RUNTIME) { this.container = localContainer = document.createElement("DIV"); this.frame = localContainer.parentNode === document.body ? document.documentElement : localContainer.parentNode; localCanvas = window.__canvas; this.canvas = localCanvas; } // 通用 else { var element = (el instanceof HTMLElement) ? el : (document.querySelector(el) || document.querySelector('#' + el)); // 已经存在canvas,只需添加一层容器包裹 if (element.tagName === "CANVAS") { width = element.width; height = element.height; //it is already a canvas, we wrap it around with a div this.canvas = localCanvas = element; this.container = localContainer = document.createElement("DIV"); if (localCanvas.parentNode) localCanvas.parentNode.insertBefore(localContainer, localCanvas); } // 不存在指定canvas,添加一个新的canvas else { //we must make a new canvas and place into this element // 配置提前写好的的id必须是个div if (element.tagName !== "DIV") { cc.warnID(3819); } width = element.clientWidth; height = element.clientHeight; this.canvas = localCanvas = document.createElement("CANVAS"); this.container = localContainer = document.createElement("DIV"); element.appendChild(localContainer); } localContainer.setAttribute('id', 'Cocos2dGameContainer'); localContainer.appendChild(localCanvas); this.frame = (localContainer.parentNode === document.body) ? document.documentElement : localContainer.parentNode; function addClass (element, name) { var hasClass = (' ' + element.className + ' ').indexOf(' ' + name + ' ') > -1; if (!hasClass) { if (element.className) { element.className += " "; } element.className += name; } } addClass(localCanvas, "gameCanvas"); localCanvas.setAttribute("width", width || 480); localCanvas.setAttribute("height", height || 320); localCanvas.setAttribute("tabindex", 99); } // 获取可用的渲染模式 this._determineRenderType(); // WebGL context created successfully if (this.renderType === this.RENDER_TYPE_WEBGL) { var opts = { // 使用 8 位模板缓冲区 'stencil': true, // MSAA is causing serious performance dropdown on some browsers. // 在创建 WebGL Context 时是否开启抗锯齿,如需要修改则在cc.game.run之前赋值为true,会影响性能 // 少部分使用软件级别抗锯齿算法的设备或浏览器上,这个选项会对性能产生比较大的影响 'antialias': cc.macro.ENABLE_WEBGL_ANTIALIAS, // 是否开启canvas背景 alpha通道,如需要背景透明则在cc.game.run之前赋值为true,仅web环境,会影响性能 'alpha': cc.macro.ENABLE_TRANSPARENT_CANVAS }; // 是否微信小游戏或者qq玩一玩环境,则开启在绘图完成后保留绘图缓冲区 if (CC_WECHATGAME || CC_QQPLAY) { opts['preserveDrawingBuffer'] = true; } // 注册webgl renderer.initWebGL(localCanvas, opts); this._renderContext = renderer.device._gl; // Enable dynamic atlas manager by default // CLEANUP_IMAGE_CACHE // 是否在将贴图上传至 GPU 之后删除原始图片缓存,删除之后图片将无法进行 动态合图。 // 在 Web 平台,你通常不需要开启这个选项,因为在 Web 平台 Image 对象所占用的内存很小。 // 但是在微信小游戏平台的当前版本,Image 对象会缓存解码后的图片数据,它所占用的内存空间很大。 // 所以我们在微信平台默认开启了这个选项,这样我们就可以在上传 GL 贴图之后立即释放 Image 对象的内存,避免过高的内存占用 if (!cc.macro.CLEANUP_IMAGE_CACHE && dynamicAtlasManager) { dynamicAtlasManager.enabled = true; } } // 如果不存在渲染上下文,则默认使用canvas模式 if (!this._renderContext) { this.renderType = this.RENDER_TYPE_CANVAS; // Could be ignored by module settings renderer.initCanvas(localCanvas); this._renderContext = renderer.device._ctx; } // 用于电脑端鼠标右键菜单事件 this.canvas.oncontextmenu = function () { if (!cc._isContextMenuEnable) return false; }; this._rendererInitialized = true; }, _initEvents: function () { var win = window, hiddenPropName; // register system events // 注册pc端事件 if (this.config.registerSystemEvent) cc.internal.inputManager.registerSystemEvent(this.canvas); // 兼容浏览器的页面是否显示属性 if (typeof document.hidden !== 'undefined') { hiddenPropName = "hidden"; } else if (typeof document.mozHidden !== 'undefined') { hiddenPropName = "mozHidden"; } else if (typeof document.msHidden !== 'undefined') { hiddenPropName = "msHidden"; } else if (typeof document.webkitHidden !== 'undefined') { hiddenPropName = "webkitHidden"; } var hidden = false; function onHidden () { if (!hidden) { hidden = true; // 派发页面隐藏事件 game.emit(game.EVENT_HIDE); } } // In order to adapt the most of platforms the onshow API. function onShown (arg0, arg1, arg2, arg3, arg4) { if (hidden) { hidden = false; // 派发页面显示事件 game.emit(game.EVENT_SHOW, arg0, arg1, arg2, arg3, arg4); } } if (hiddenPropName) { var changeList = [ "visibilitychange", "mozvisibilitychange", "msvisibilitychange", "webkitvisibilitychange", "qbrowserVisibilityChange" ]; for (var i = 0; i < changeList.length; i++) { // 注册监听列表 document.addEventListener(changeList[i], function (event) { var visible = document[hiddenPropName]; // QQ App visible = visible || event["hidden"]; if (visible) onHidden(); else onShown(); }); } } else { // 目前未知,应该是用于非页面状态打开或者非浏览器打开 win.addEventListener("blur", onHidden); win.addEventListener("focus", onShown); } if (navigator.userAgent.indexOf("MicroMessenger") > -1) { win.onfocus = onShown; } // 设置微信小游戏兼容 if (CC_WECHATGAME && cc.sys.browserType !== cc.sys.BROWSER_TYPE_WECHAT_GAME_SUB) { wx.onShow && wx.onShow(onShown); wx.onHide && wx.onHide(onHidden); } // 设置其他兼容 if ("onpageshow" in window && "onpagehide" in window) { win.addEventListener("pagehide", onHidden); win.addEventListener("pageshow", onShown); // Taobao UIWebKit document.addEventListener("pagehide", onHidden); document.addEventListener("pageshow", onShown); } // 默认注册的事件 this.on(game.EVENT_HIDE, function () { game.pause(); }); this.on(game.EVENT_SHOW, function () { game.resume(); }); } }; EventTarget.call(game); // 将EventTarget的原型链复制到game里 cc.js.addon(game, EventTarget.prototype); /** * @module cc */ /** 1. !#en This is a Game instance. 2. !#zh 这是一个 Game 类的实例,包含游戏主体信息并负责驱动游戏的游戏对象。。 3. @property game 4. @type Game */ cc.game = module.exports = game;

结论

一:
我们来整理一下整个文件看完能得出的生命周期:

  1. 首先cocos-2d.js脚本加载
  2. 执行main.js的window.boot函数
  3. 根据配置调用cc.game.run函数
  4. 调用prepare开始准备引擎
  5. 调用_prepareFinished配置其他部分
  6. 调用_initEngine配置渲染模式等渲染层数据,配置输入事件(包括默认页面隐藏显示事件),派发EVENT_ENGINE_INITED事件
  7. 调用_setAnimFrame设置帧循环事件
  8. 调用_runMainLoop开始帧循环
  9. 派发EVENT_GAME_INITED事件
  10. 最后调用onStart

至此我们已经了解了从引擎文件加载后调用window.boot后,大概是怎样的流程。至于加载cocos-2d引擎时具体执行的顺序,还需要进一步的解读其他的代码。

二:
我们了解了渲染模式的生成,底层是用的什么方法实现帧循环的。还了解了游戏的开始,暂停,和重新开始等功能,此处的功能都包括游戏逻辑和音频、动画等部分。
这些有助于我们接下来更加深入的了解cocos的底层架构。

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