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()方法进行全局的数据更新和视图渲染。那么整个游戏引擎大概的启用流程到这里就结束了。