许久没写博客了,这次借着读源码来写几篇关于源码的解析吧。加上最近用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;
一:
我们来整理一下整个文件看完能得出的生命周期:
至此我们已经了解了从引擎文件加载后调用window.boot后,大概是怎样的流程。至于加载cocos-2d引擎时具体执行的顺序,还需要进一步的解读其他的代码。
二:
我们了解了渲染模式的生成,底层是用的什么方法实现帧循环的。还了解了游戏的开始,暂停,和重新开始等功能,此处的功能都包括游戏逻辑和音频、动画等部分。
这些有助于我们接下来更加深入的了解cocos的底层架构。