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):全局哈希基类和全局异步函数对象接口

相关推荐