erget源码分析(1):入口文件分析
egret的github地址是https://github.com/egret-labs...,大家自己git clone到本地。
一.路口html文件
用ergetWing新建一个工程,打开根目录下的index.html,这个就是项目的入口文件,我们看下其中装载游戏内容的DIV容器
<div style="margin: auto;width: 100%;height: 100%;" class="egret-player" data-entry-class="Main" data-orientation="auto" data-scale-mode="exactFit" data-frame-rate="30" data-content-width="480" data-content-height="800" data-show-paint-rect="false" data-multi-fingered="2" data-show-fps="false" data-show-log="false" data-show-fps-style="x:0,y:0,size:12,textColor:0xffffff,bgAlpha:0.9"> </div> <script> /** * { * "renderMode":, //引擎渲染模式,"canvas" 或者 "webgl" * "audioType": 0 //使用的音频类型,0:默认,1:qq audio,2:web audio,3:audio * "antialias": //WebGL模式下是否开启抗锯齿,true:开启,false:关闭,默认为false * "retina": //是否基于devicePixelRatio缩放画布 * } **/ egret.runEgret({renderMode:"webgl", audioType:0}); </script>
这里是官方的配置说明,因为我们后面分析源码会用到,大家可以先粗略看一下
data-entry-class=”Main” 设置项目的入口文件,表示项目的入口类,默认为Main,如果需要自定义的话需要在项目中先创建类,然后在这里配置类的名字。data-orientation=”auto” 设置旋转模式。
data-scale-mode=”showAll” 设置缩放模式。
data-frame-rate=”30” 这里是运行的帧率。
data-content-width=”480” 和 data-content-height=”800” 用来设置舞台的设计宽和高
data-show-paint-rect=”false” 设置显示脏矩形的重绘区域。
data-multi-fingered=”2” 设置多指触摸
data-show-fps=”false” data-show-log=”false” 这里设置显示帧率和log,只有在调试时会显示,发布的版本会去掉。
data-log-filter=”” 设置一个正则表达式过滤条件,日志文本匹配这个正则表达式的时候才显示这条日志。如 data-log-filter="^egret" 表示仅显示以 egret 开头的日志。
data-show-fps-style=”x:0,y:0,size:30,textColor:0x00c200,bgAlpha:0.9” 这里设置fps面板的样式。目前支持默认的这几种设置,修改其值即可,比如修改面板位置可以设置x和y,改变大小可以设置size,改变文字颜色textColor,改变背景面板的透明度bgAlpha。
页面打开后会立即执行egret.runEgret(),这几个参数后面会详细讲,我们先理解erget的运行流程。
EgretWeb.ts入口脚本
我们在源码项目中搜索runEgret可以看到分别在
src/egret/native/EgretNative.ts
src/egret/player/EgretEntry.ts
src/egret/web/EgretWeb.ts中被定义。
这是egret的一个基本的结构,EgretEntry.ts定义了接口,而EgretNative.ts和EgretWeb.ts分别定义在原生平台和web平台上的实现。我们先按照web部分的思路进行分析。
1.初始化环境参数
let isRunning: boolean = false; /** * @private * 网页加载完成,实例化页面中定义的Egret标签 */ function runEgret(options?: runEgretOptions): void { if (isRunning) { return; } isRunning = true; if (!options) { options = {}; } Html5Capatibility._audioType = options.audioType; Html5Capatibility.$init(); //...... }
首先看runEntry函数的前面几行,这里利用一个闭包和isRunning,还有if语句来防止重复运行游戏。这里的isRunning这个变量明显只被runEntry函数使用,所以不把它定义为成员私有属性而是定义成一个变量。Html5Capatibility是一个静态类,调用$init()方法来初始化html5各项支持信息。
public static $init(): void { let ua: string = navigator.userAgent.toLowerCase(); Html5Capatibility.ua = ua; egret.Capabilities.$isMobile = (ua.indexOf('mobile') != -1 || ua.indexOf('android') != -1); //...... }
我们稍微来看一下$init()方法,这里还调用了一个全局静态类Capabilities,这个类在src/egret/system/Capabilities.ts下,主要是存储当前运行的设备(PC/IOS/Android)信息、平台信息(web/native)、渲染模式、引擎版本和客户端尺寸。
我们可以看出,在web平台关于运行环境的各项信息从Capabilities获得,HTML5的接口支持从Html5Capatibility获得。
2.配置渲染
// WebGL上下文参数自定义 function runEgret(options?: runEgretOptions): void { if (options.renderMode == "webgl") { // WebGL抗锯齿默认关闭,提升PC及某些平台性能 let antialias = options.antialias; WebGLRenderContext.antialias = !!antialias; // WebGLRenderContext.antialias = (typeof antialias == undefined) ? true : antialias; } sys.CanvasRenderBuffer = web.CanvasRenderBuffer; setRenderMode(options.renderMode); ...... }
这里有个小技巧就是利用两个!来转型,因为options.antialias可能是false、true、undefined中的一个,如果是false或者true,两个!!相当于没有作用,如果是undefined就被转换成false。
/** * 设置渲染模式。"auto","webgl","canvas" * @param renderMode */ function setRenderMode(renderMode: string): void { //...... if (renderMode == "webgl" && WebGLUtils.checkCanUseWebGL()) { sys.RenderBuffer = web.WebGLRenderBuffer; sys.systemRenderer = new WebGLRenderer(); sys.canvasRenderer = new CanvasRenderer(); sys.customHitTestBuffer = new WebGLRenderBuffer(3, 3); sys.canvasHitTestBuffer = new CanvasRenderBuffer(3, 3); Capabilities.$renderMode = "webgl"; } else { sys.RenderBuffer = web.CanvasRenderBuffer; sys.systemRenderer = new CanvasRenderer(); sys.canvasRenderer = sys.systemRenderer; sys.customHitTestBuffer = new CanvasRenderBuffer(3, 3); sys.canvasHitTestBuffer = sys.customHitTestBuffer; Capabilities.$renderMode = "canvas"; } //...... }
我们简单来看一下setRenderMode方法,如果用户把renderMode设置为webGL并且浏览器支持webGL就使用webGL否则使用canvas,WebGLUtils.checkCanUseWebGL()这个方法大家可以自己去看一下,同样使用了两个!!的技巧,关于webGL的使用大家可以看这里初识 WebGL
3.分辨率配置
function runEgret(options?: runEgretOptions): void { //...... let canvasScaleFactor; if (options.canvasScaleFactor) { canvasScaleFactor = options.canvasScaleFactor; } else if(options.calculateCanvasScaleFactor) { canvasScaleFactor = options.calculateCanvasScaleFactor(sys.canvasHitTestBuffer.context); } else { //based on : https://github.com/jondavidjohn/hidpi-canvas-polyfill let context = sys.canvasHitTestBuffer.context; let backingStore = context.backingStorePixelRatio || context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1; canvasScaleFactor = (window.devicePixelRatio || 1) / backingStore; } sys.DisplayList.$canvasScaleFactor = canvasScaleFactor; //...... }
如果用户配置了缩放比例就使用它,配置了计算缩放比例的方法就调用它,否则计算当前浏览器支持的最大精度的缩放比例。
4.执行系统定时器
function runEgret(options?: runEgretOptions): void { //...... let ticker = egret.ticker; startTicker(ticker); //...... }
egret.sys.$ticker是egret.SystemTicker类的单例对象,首先对它调用了startTicker方法:
function startTicker(ticker:egret.sys.SystemTicker):void { var requestAnimationFrame = window["requestAnimationFrame"] || window["webkitRequestAnimationFrame"] || window["mozRequestAnimationFrame"] || window["oRequestAnimationFrame"] || window["msRequestAnimationFrame"]; if (!requestAnimationFrame) { requestAnimationFrame = function (callback) { return window.setTimeout(callback, 1000 / 60); }; } requestAnimationFrame.call(window, onTick); function onTick():void { ticker.update(); requestAnimationFrame.call(window, onTick) } }
这里同样是判读浏览器是否存在requestAnimationFrame的API,存在则使用之,否则使用setTimeout方法,这里onTicker使用了延迟递归调用,实现每隔一段时间就调用一次ticker.update()方法,这里使用call方法确保调用该方法对象是全局window对象,避开js中this的坑。
5.屏幕适配和播放器的创建
function runEgret(options?: runEgretOptions): void { //...... if (options.screenAdapter) { egret.sys.screenAdapter = options.screenAdapter; } else if (!egret.sys.screenAdapter) { egret.sys.screenAdapter = new egret.sys.DefaultScreenAdapter(); } let list = document.querySelectorAll(".egret-player"); let length = list.length; for (let i = 0; i < length; i++) { let container = <HTMLDivElement>list[i]; let player = new WebPlayer(container, options); container["egret-player"] = player; //webgl模式关闭脏矩形 if (Capabilities.$renderMode == "webgl") { player.stage.dirtyRegionPolicy = DirtyRegionPolicy.OFF; } } if (Capabilities.$renderMode == "webgl") { egret.sys.DisplayList.prototype.setDirtyRegionPolicy = function () { }; } window.addEventListener("resize", function () { if (isNaN(resizeTimer)) { resizeTimer = window.setTimeout(doResize, 300); } }); //...... } //...... let resizeTimer: number = NaN; function doResize() { resizeTimer = NaN; egret.updateAllScreens(); if (customContext) { customContext.onResize(context); } }
接下来使用document.querySelectorAll()方法取得所有拥有"egret-player"的CSS class的DOM对象。就是我们一开始在index.html的body里看到的那个div标签。
遍历这些DOM对象,为每一个创建一个egret.WebPlayer对象,并赋值给DOM的"egret-player"属性(这是个自定义属性)。
这里还有一个值得注意的对方是resizeTimer,每当浏览器尺寸变化先进行一个判断,如果不存在重绘定时器(也就是resizeTimer为NaN),就启动一个定时器,在300毫秒后重新获取浏览器尺寸重新绘制,并把resizeTimer赋值为NaN表示这个定时器关闭了。这么做,是因为在PC端,我们修改浏览器尺寸是一个延续动作,也就是鼠标持续移动改变窗口尺寸,定义一个300毫秒的定时器延时重绘是防止过多的重绘请求占用资源。
小结
runEgret通过Html5Capatibility和Capatibilities这两个静态类初始化了项目运行的环境参数,然后创建了屏幕适配器egret.sys.screenAdapter根据不同的适配策略调整。然后通过监听winodw对象的resize事件监听客户端尺寸变化(包括旋转设备,改变浏览器窗口尺寸等)。最主要的事情是调用创建一个定时器无限地调用egret.sys.$ticker的update()方法进行全局的数据更新和视图渲染。那么整个游戏引擎大概的启用流程到这里就结束了。
下一篇:erget源码分析(2):全局哈希基类和全局异步函数对象接口