HTML5引擎Construct2技术剖析(六)

接上一节来介绍游戏主循环的实现。

(3) 游戏循环处理

tick函数负责游戏的周期更新,只有一个布尔参数参数background_wake,表示当前游戏是否运行在后台。如果等于true,则此次tick函数调用后将停止回调。tick函数的主要流程为:
1) 根据游戏运行速度和全屏模式,更新相关参数和Canvas的尺寸。
2) 如果是Layout加载界面,则检查加载状态,更新进度;如果加载完成则触发OnLoadFinished事件。

var done = this.areAllTexturesAndSoundsLoaded();
            this.loadingprogress = this.progress;
            if (done)
            {
                this.isloading = false;
                this.progress = 1;
            this.trigger(cr.system_object.prototype.cnds.OnLoadFinished, null);
            }

3) 调用logic函数处理游戏逻辑。
logic函数的主要流程是:
(a)记录运行耗时,用于统计帧率和CPU利用率等分析;只有2次logic函数调用时间间隔超过1秒时,才进行统计;

var cur_time = cr.performance_now();
        if (cur_time - this.last_fps_time >= 1000){
            this.last_fps_time += 1000; 
            this.fps = this.framecount;
            this.framecount = 0;
            this.cpuutilisation = this.logictime;
            this.logictime = 0;
        }

(b)更新游戏逻辑时间kahanTime和墙钟时间wallTime。dt1表示2次tick函数调用的墙钟时间间隔,如果tick时间间隔等于0,则按60FPS来计算;如果累计超过10次tick函数调用的时间间隔均等于0,则直接按60FPS来更新游戏时间(不再进行时间间隔计算)。timescale可以理解为游戏运行速度,与dt1相乘就是游戏的逻辑时间间隔。需要说明的是,有可能游戏处于调试状态,因此如果tick调用的时间间隔大于500毫秒,则认为游戏在调试,则停止更新游戏时间;如果tick调用的时间间隔大于100毫秒,则仍然按100毫秒间隔更新时间(游戏逻辑执行的最大时间间隔是100毫秒, 如果时间间隔大于100毫秒且小于500毫秒,游戏执行会变慢(类似慢镜头))。

var ms_diff = cur_time - this.last_tick_time;
…
this.dt1 = ms_diff / 1000.0;
        …
this.dt = this.dt1 * this.timescale;
        this.kahanTime.add(this.dt); 
        this.wallTime.add(this.dt1); 

(d) 根据全屏模式和当前状态,调整画面大小。
(e) 调用system_object的runWaits函数处理正在等待的事件逻辑,ClearDeathRow函数的作用是更新对象实例列表,确保新建的实例加入,并回收被删除的实例。isInOnDestroy用来防止对象实例列表被更新,即在runWaits函数运行时,如果调用了ClearDeathRow函数,则什么也不做。

this.ClearDeathRow();
        this.isInOnDestroy++; 
        this.system.runWaits();     
        this.isInOnDestroy--; 

runWaits函数的主要流程是:
首先获取当前使用的事件栈,getCurrentEventStack函数就是根据event_stack_index索引返回对应的栈对象。event_stack是栈数组。

    var evinfo = this.runtime.getCurrentEventStack();
    Runtime.prototype.getCurrentEventStack = function ()
    {
        return this.event_stack[this.event_stack_index];
    };

然后,遍历waits数组中每个等待状态的事件,判断其等待条件是否满足,如果满足则调用resume_actions_and_subevents继续执行动作,如果包含子事件,则继续处理子事件。如果触发器被成功触发,则设置deleteme为true,标志触发器失效等待回收。

for (i = 0, len = this.waits.length; i < len; i++)
        {
w = this.waits[i];
…
w.ev.resume_actions_and_subevents();
           this.runtime.clearSol(w.solModifiers);
           w.deleteme = true;
}

触发器分为2类:一类是信号触发,例如之前看到的OnLoadFinished事件;一类是时间触发,w.time表示时间执行的逻辑时间,如果大于等于kahanTime则被触发。

下面讲一下waits数组中的对象是怎么来的?在system_object的动作函数中有Wait、WaitForSignal等函数。WaitForSignal函数表示等待信号触发(信号通过标签tag字符串来区别),调用allocWaitObject会分配一个等待对象来记录等待信号的属性,并放入 waits数组中等待下一次运行runWaits函数时检测是否被触发。

WaitForSignal函数等待的信号由Signal函数来发出。事件块模式与对象实例是相对独立的关系,可以比喻为算法和数据的关系。一个事件块可以同时运行多次,每次事件块运行时会有自己的sol数据,相互不会干扰。

Wait函数与WaitForSignal函数类似,但不是等待信号触发,而是等待经过多长时间。当时间到达时就被触发。
最后,回收无用的触发器,freeWaitObject函数将触发器重新放入waitobjrecycle数组中等待复用。

for (i = 0, j = 0, len = this.waits.length; i < len; i++)
        {  
            w = this.waits[i];
            …
            if (w.deleteme)
                freeWaitObject(w);
        }

(f) 调用当前场景的EventSheet的run函数查找到符合条件的 EventBlock,检查条件函数是否成立,并执行相应的动作。

4) 执行完游戏逻辑,实例状态已经更新。这时调用渲染函数,更新游戏画面。如果设置了截屏标志,则会将当前游戏画面保存起来,并触发OnCanvasSnapshot事件,用户可以在事件触发动作中对截屏图片进行处理,例如保存到文件。

            if (this.glwrap)
                this.drawGL();
            else
                this.draw();
            …

if (this.snapshotCanvas) {
            this.snapshotData = this.canvas.toDataURL(this.snapshotCanvas[0], this.snapshotCanvas[1]);
        this.trigger(cr.system_object.prototype.cnds.OnCanvasSnapshot, null); 
}

drawGL函数的实现如下,调用当前layout的drawGL函数,将渲染命令写入批处理GLBatchJob中,然后调用present函数执行批处理命令更新画面。draw函数则直接调用当前layout的draw函数直接完成绘制。

    Runtime.prototype.drawGL = function ()
    {
         this.running_layout.drawGL(this.glwrap);
         this.glwrap.present();
    };

5) 请求下一次tick函数调用,如果不支持 requestAnimationFrame函数,则使用SetTimeout方式来执行渲染(16毫秒对应60FPS, 实际定时器时间很难保证精确)。记录raf_id是为了后面可能要调用cancelAnimationFrame函数取消渲染更新。记录timeout_id是为了后面可能要调用clearTimeout函数取消渲染更新。

if (raf)
    this.raf_id = raf(this.tickFunc, this.canvas);
else
{ 
    this.timeout_id = setTimeout(this.tickFunc, this.isMobile ? 1 : 16);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值