Cocos creator 源码解析(一)

序言

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

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

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

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

ps:之前读的老版本在github已经没有了,只能更新成新的版本,看了一下,大体类似,V2.4.5
github地址:https://github.com/cocos-creator/engine/blob/v2.4.5/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:<br/>
     * 1. debugMode<br/>
     *      "debugMode" possible values :<br/>
     *      0 - No message will be printed.                                                      <br/>
     *      1 - cc.error, cc.assert, cc.warn, cc.log will print in console.                      <br/>
     *      2 - cc.error, cc.assert, cc.warn will print in console.                              <br/>
     *      3 - cc.error, cc.assert will print in console.                                       <br/>
     *      4 - cc.error, cc.assert, cc.warn, cc.log will print on canvas, available only on web.<br/>
     *      5 - cc.error, cc.assert, cc.warn will print on canvas, available only on web.        <br/>
     *      6 - cc.error, cc.assert will print on canvas, available only on web.                 <br/>
     * 2. showFPS<br/>
     *      Left bottom corner fps information will show when "showFPS" equals true, otherwise it will be hide.<br/>
     * 3. exposeClassName<br/>
     *      Expose class name to chrome debug tools, the class intantiate performance is a little bit slower when exposed.<br/>
     * 4. frameRate<br/>
     *      "frameRate" set the wanted frame rate for your game, but the real fps depends on your game implementation and the running environment.<br/>
     * 5. id<br/>
     *      "gameCanvas" sets the id of your canvas element on the web page, it's useful only on web.<br/>
     * 6. renderMode<br/>
     *      "renderMode" sets the renderer type, only useful on web :<br/>
     *      0 - Automatically chosen by engine<br/>
     *      1 - Forced to use canvas renderer<br/>
     *      2 - Forced to use WebGL renderer, but this will be ignored on mobile browsers<br/>
     * 7. scenes<br/>
     *      "scenes" include available scenes in the current bundle.<br/>
     *<br/>
     * Please DO NOT modify this object directly, it won't have any effect.<br/>
     * !#zh
     * 当前的游戏配置,包括:                                                                  <br/>
     * 1. debugMode(debug 模式,但是在浏览器中这个选项会被忽略)                                <br/>
     *      "debugMode" 各种设置选项的意义。                                                   <br/>
     *          0 - 没有消息被打印出来。                                                       <br/>
     *          1 - cc.error,cc.assert,cc.warn,cc.log 将打印在 console 中。                  <br/>
     *          2 - cc.error,cc.assert,cc.warn 将打印在 console 中。                          <br/>
     *          3 - cc.error,cc.assert 将打印在 console 中。                                   <br/>
     *          4 - cc.error,cc.assert,cc.warn,cc.log 将打印在 canvas 中(仅适用于 web 端)。 <br/>
     *          5 - cc.error,cc.assert,cc.warn 将打印在 canvas 中(仅适用于 web 端)。         <br/>
     *          6 - cc.error,cc.assert 将打印在 canvas 中(仅适用于 web 端)。                  <br/>
     * 2. showFPS(显示 FPS)                                                            <br/>
     *      当 showFPS 为 true 的时候界面的左下角将显示 fps 的信息,否则被隐藏。              <br/>
     * 3. exposeClassName                                                           <br/>
     *      暴露类名让 Chrome DevTools 可以识别,如果开启会稍稍降低类的创建过程的性能,但对对象构造没有影响。 <br/>
     * 4. frameRate (帧率)                                                              <br/>
     *      “frameRate” 设置想要的帧率你的游戏,但真正的FPS取决于你的游戏实现和运行环境。      <br/>
     * 5. id                                                                            <br/>
     *      "gameCanvas" Web 页面上的 Canvas Element ID,仅适用于 web 端。                         <br/>
     * 6. renderMode(渲染模式)                                                         <br/>
     *      “renderMode” 设置渲染器类型,仅适用于 web 端:                              <br/>
     *          0 - 通过引擎自动选择。                                                     <br/>
     *          1 - 强制使用 canvas 渲染。
     *          2 - 强制使用 WebGL 渲染,但是在部分 Android 浏览器中这个选项会被忽略。     <br/>
     * 7. scenes                                                                         <br/>
     *      “scenes” 当前包中可用场景。                                                   <br/>
     * <br/>
     * 注意:请不要直接修改这个对象,它不会有任何效果。
     * @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<T extends Function>(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.<br/>
     * The target node must be placed in the root level of hierarchy, otherwise this API won't have any effect.
     * !#zh
     * 声明常驻根节点,该节点不会被在场景切换中被销毁。<br/>
     * 目标节点必须位于为层级的根节点,否则无效。
     * @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的底层架构。

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值