当前位置:   article > 正文

Cocos creator 源码解析(一)_cocos源码分析

cocos源码分析

序言

许久没写博客了,这次借着读源码来写几篇关于源码的解析吧。加上最近用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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 445
  • 446
  • 447
  • 448
  • 449
  • 450
  • 451
  • 452
  • 453
  • 454
  • 455
  • 456
  • 457
  • 458
  • 459
  • 460
  • 461
  • 462
  • 463
  • 464
  • 465
  • 466
  • 467
  • 468
  • 469
  • 470
  • 471
  • 472
  • 473
  • 474
  • 475
  • 476
  • 477
  • 478
  • 479
  • 480
  • 481
  • 482
  • 483
  • 484
  • 485
  • 486
  • 487
  • 488
  • 489
  • 490
  • 491
  • 492
  • 493
  • 494
  • 495
  • 496
  • 497
  • 498
  • 499
  • 500
  • 501
  • 502
  • 503
  • 504
  • 505
  • 506
  • 507
  • 508
  • 509
  • 510
  • 511
  • 512
  • 513
  • 514
  • 515
  • 516
  • 517
  • 518
  • 519
  • 520
  • 521
  • 522
  • 523
  • 524
  • 525
  • 526
  • 527
  • 528
  • 529
  • 530
  • 531
  • 532
  • 533
  • 534
  • 535
  • 536
  • 537
  • 538
  • 539
  • 540
  • 541
  • 542
  • 543
  • 544
  • 545
  • 546
  • 547
  • 548
  • 549
  • 550
  • 551
  • 552
  • 553
  • 554
  • 555
  • 556
  • 557
  • 558
  • 559
  • 560
  • 561
  • 562
  • 563
  • 564
  • 565
  • 566
  • 567
  • 568
  • 569
  • 570
  • 571
  • 572
  • 573
  • 574
  • 575
  • 576
  • 577
  • 578
  • 579
  • 580
  • 581
  • 582
  • 583
  • 584
  • 585
  • 586
  • 587
  • 588
  • 589
  • 590
  • 591
  • 592
  • 593
  • 594
  • 595
  • 596
  • 597
  • 598
  • 599
  • 600
  • 601
  • 602
  • 603
  • 604
  • 605
  • 606
  • 607
  • 608
  • 609
  • 610
  • 611
  • 612
  • 613
  • 614
  • 615
  • 616
  • 617
  • 618
  • 619
  • 620
  • 621
  • 622
  • 623
  • 624
  • 625
  • 626
  • 627
  • 628
  • 629
  • 630
  • 631
  • 632
  • 633
  • 634
  • 635
  • 636
  • 637
  • 638
  • 639
  • 640
  • 641
  • 642
  • 643
  • 644
  • 645
  • 646
  • 647
  • 648
  • 649
  • 650
  • 651
  • 652
  • 653
  • 654
  • 655
  • 656
  • 657
  • 658
  • 659
  • 660
  • 661
  • 662
  • 663
  • 664
  • 665
  • 666
  • 667
  • 668
  • 669
  • 670
  • 671
  • 672
  • 673
  • 674
  • 675
  • 676
  • 677
  • 678
  • 679
  • 680
  • 681
  • 682
  • 683
  • 684
  • 685
  • 686
  • 687
  • 688
  • 689
  • 690
  • 691
  • 692
  • 693
  • 694
  • 695
  • 696
  • 697
  • 698
  • 699
  • 700
  • 701
  • 702
  • 703
  • 704
  • 705
  • 706
  • 707
  • 708
  • 709
  • 710
  • 711
  • 712
  • 713
  • 714
  • 715
  • 716
  • 717
  • 718
  • 719
  • 720
  • 721
  • 722
  • 723
  • 724
  • 725
  • 726
  • 727
  • 728
  • 729
  • 730
  • 731
  • 732
  • 733
  • 734
  • 735
  • 736
  • 737
  • 738
  • 739
  • 740
  • 741
  • 742
  • 743
  • 744
  • 745
  • 746
  • 747
  • 748
  • 749
  • 750
  • 751
  • 752
  • 753
  • 754
  • 755
  • 756
  • 757
  • 758
  • 759
  • 760
  • 761
  • 762
  • 763
  • 764
  • 765
  • 766
  • 767
  • 768
  • 769
  • 770
  • 771
  • 772
  • 773
  • 774
  • 775
  • 776
  • 777
  • 778
  • 779
  • 780
  • 781
  • 782
  • 783
  • 784
  • 785
  • 786
  • 787
  • 788
  • 789
  • 790
  • 791
  • 792
  • 793
  • 794
  • 795
  • 796
  • 797
  • 798
  • 799
  • 800
  • 801
  • 802
  • 803
  • 804
  • 805
  • 806
  • 807
  • 808
  • 809
  • 810
  • 811
  • 812
  • 813
  • 814
  • 815
  • 816
  • 817
  • 818
  • 819
  • 820
  • 821
  • 822
  • 823
  • 824
  • 825
  • 826
  • 827
  • 828
  • 829
  • 830
  • 831
  • 832
  • 833
  • 834
  • 835
  • 836
  • 837
  • 838
  • 839
  • 840
  • 841
  • 842
  • 843
  • 844
  • 845
  • 846
  • 847
  • 848
  • 849
  • 850
  • 851
  • 852
  • 853
  • 854
  • 855
  • 856
  • 857
  • 858
  • 859
  • 860
  • 861
  • 862
  • 863
  • 864
  • 865
  • 866
  • 867
  • 868
  • 869
  • 870
  • 871
  • 872
  • 873
  • 874
  • 875
  • 876
  • 877
  • 878
  • 879
  • 880
  • 881
  • 882
  • 883
  • 884
  • 885
  • 886
  • 887
  • 888
  • 889
  • 890
  • 891
  • 892
  • 893
  • 894
  • 895
  • 896
  • 897
  • 898
  • 899
  • 900
  • 901
  • 902
  • 903
  • 904
  • 905
  • 906
  • 907
  • 908
  • 909
  • 910
  • 911
  • 912
  • 913
  • 914
  • 915
  • 916
  • 917
  • 918
  • 919
  • 920
  • 921
  • 922
  • 923
  • 924
  • 925
  • 926
  • 927
  • 928
  • 929
  • 930
  • 931
  • 932
  • 933
  • 934
  • 935
  • 936
  • 937
  • 938
  • 939
  • 940
  • 941
  • 942
  • 943
  • 944
  • 945
  • 946
  • 947
  • 948
  • 949
  • 950
  • 951
  • 952
  • 953
  • 954
  • 955
  • 956
  • 957
  • 958
  • 959
  • 960
  • 961
  • 962
  • 963
  • 964
  • 965
  • 966
  • 967
  • 968
  • 969
  • 970
  • 971
  • 972
  • 973

结论

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

  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的底层架构。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/959499
推荐阅读
相关标签
  

闽ICP备14008679号