[LayaAir 2.0]深入理解LayaAir引擎架构和实现原理(三)引擎渲染主循环与AOP介入控制

最近完善了下LayaTree,顺便看了看Laya引擎源码的主循环和渲染部分。这节的话,来聊聊Laya引擎的渲染主循环,以及如何在外部暂停和单帧步进调试。
1.引擎启动入口​
工程的入口文件一般为Main.ts,这里面不仅需要处理项目相关的逻辑初始化,也会调用Laya3D.init()进行Laya引擎的初始化。
在init中,可以看到创建了两个画布,mainCanvas是引擎的主画布,所有游戏内的canvas和webgl渲染都用这个画布。canvas是一个全局离线画布,不会加到DOM上,主要用来测量字体、获取image数据。
 

主画布

同样在初始化中还创建了渲染器Render,它是一个管理渲染的单例。

2.渲染结构
Rander

来看Rander的构造函数发现,游戏渲染的主循环是通过window.requestAnimationFrame不断loop实现的,是程序的主要更新源头。 需要注意的是window.requestAnimationFrame原生的浏览器重绘接口函数都不兼容时。Laya引擎会使用setTimeout代替。
 win.requestAnimationFrame = win.requestAnimationFrame || win.webkitRequestAnimationFrame || win.mozRequestAnimationFrame || win.oRequestAnimationFrame || win.msRequestAnimationFrame || function (fun: any): any {
return win.setTimeout(fun, 1000 / 60);
}

initRender()为画布初始化函数,创建和保存了WebGL上下文环境。如需更改canvas的参数,如设置是否抗锯齿,请在初始化引擎之前设置。

Stage
Stage是一个单例舞台类,显示列表的根节点,所有显示对象都在舞台上才能被显示。Stage中的_loop()函数就是帧循环函数,函数内部调用render函数用于渲染当前context渲染上下文和更新逻辑。
这边主要的功能是:清空画布——提交渲染——更新逻辑。引擎内所有的计时器timer都是在这里更新的。其中super.render(context, x, y); 属于stage的父类Sprite类的方法,渲染具体实现的地方。与其他引擎不同的是,Laya的帧率只有两种,高帧率模式固定60帧,低帧率模式 为双帧处理渲染异常渲染,固定30帧。
Sprite
Stage的父类是Sprite。 Sprite是基本的显示图形的显示列表节点。默认没有宽高,默认不接受鼠标事件。通过graphics可以绘制图片或者矢量图,支持旋转,缩放,位移等操作。同时也是容器类,可用来添加多个子节点。LayaAir引擎API设计精简巧妙。核心显示类只有一个Sprite。Sprite针对不同的情况做了渲染优化,所以保证一个类实现丰富功能的同时,又达到高性能。
Sprite的renders函数对renders数组(RenderSprite数组)中的第this._renderType个RenderSprite精灵进行渲染(调用_fun函数),_fun函数是RenderSprite类的方法,定义了属于不同类型的不同的渲染方式,里面都是通过调用Context类的方法进行绘制的 需要注意的是RenderSprite在Laya.init初始化的时候就已经创建了,默认是预创建了8192*2个精灵渲染器。它的渲染_fun类型会根据传入的Sprite的_renderType决定渲染方式。
Context
Context扩展类主要用来进行绘制。流程图

 3.AOP介入面向切面编程
面向切面编程(AOP是Aspect Oriented Program的首字母缩写), 是通过预编译方式和运行期动态代理的方式实现不修改源代码的情况下给程序动态统一添加功能的技术。比如装饰器就是在预编译阶段执行的,本文介绍的控制游戏主流程的方法是在运行时执行的。
AOP面向切面编程是针对业务处理过程中的切面进行提取,它所面对的是处理过程中某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。所以可以在不修改Laya引擎的情况下实现功能。
实现AOP介入控制Laya游戏暂停和单步运行的代码
// 注入脚本的代码
export default function () {
// 检测是否包含Laya变量
var isLayaGame = true;
try {
var layaGame = Laya;
} catch (e) {
isLayaGame = false;
}


if (isLayaGame) {
// laya 暂停状态
window.layaStatePause = false;

// laya 需要单步次数
window.layaStepCount = 0;

let rawRequestAnimationFrame = window.requestAnimationFrame;

let renderRec = null;

function loopCheck(stamp) {
if (renderRec != null) {
window.requestAnimationFrame(renderRec);
}
}

window.requestAnimationFrame = function (render) {
if (renderRec == null) {
renderRec = render;
}

if (window.layaStatePause) {
if ( window.layaStepCount > 0) {
window.layaStepCount -= 1;
rawRequestAnimationFrame(renderRec);
} else {
rawRequestAnimationFrame(loopCheck);
}
} else {
rawRequestAnimationFrame(renderRec);
}
};
}
}
运行效果
已邀请:

陆仁毅

赞同来自:

requestAnimationFrame 用的是原生的,在requestAnimationFrame和各种兼容接口都没有的情况下,才会使用setTimeout方法代替

要回复问题请先

商务合作
商务合作